UNPKG

syms

Version:

Automatic/Convenience/Syntactic-Sugar Symbol.for() access: `const {WHATEVER,Vars,youWant} = require("syms")`

134 lines (106 loc) 6.14 kB
# syms - quick/easy/canonical Symbol.for() access This library is a "magic" proxy object, which generates `Symbol.for()` type symbols on property access *after* performing a few key normalization steps, resulting in the following: * `syms.foo` becomes `Symbol.for("foo")` * `syms.TWO_WORDS` becomes `Symbol.for("two.words")` * `syms._two_words` becomes `Symbol.for("two.words")` * `syms.twoWords` becomes `Symbol.for("two.words")` * `syms.TwoWords` becomes `Symbol.for("two.words")` * `syms.kTwoWords` becomes `Symbol.for("two.words")` In short, the following: ```javascript const syms = require("syms"); const { kSomething, ANOTHER_THING, yetAnotherThing, ["A Fourth thing"]: THING4 } = syms; const THING5 = syms["thing #5"] // with a namespace: const { example, kAnotherExample, EXAMPLE_THREE } = syms[syms.ns]('my.app.name') ``` Is equivalent to: ```javascript const kSomething = Symbol.for("something"); const ANOTHER_THING = Symbol.for("another.thing"); const yetAnotherThing = Symbol.for("yet.another.thing"); const THING4 = Symbol.for("a.fourth.thing"); const THING5 = Symbol.for("thing.5"); const example = Symbol.for("my.app.name.example") const kAnotherExample = Symbol.for("my.app.name.another.example") const EXAMPLE_THREE = Symbol.for("my.app.name.example.three") ``` ## Motivation JavaScript `Symbol` objects are an incredibly handy generic construct in modern JavaScript which can be utilized for many purposes, including: * marking/documenting "internal" methods -- for example using `this[Symbol.for('perform-internal-action')]()` rather than something like `this._performInternalAction()` * faking classical-OO style "private" methods / properties -- While these aren't technically "private", using regular string-keyed methods/props for "public" api and symbol-keyed methods/props for "private" api is one way to separate "intended public API" vs "implementation-specific" API is an entirely valid use of Symbols in javascript * attaching your own metadata to an object you don't control -- while this is not often considered good practice, if you *ARE* going to do it, it's probably safer to use a Symbol-keyed object * "hiding" data from `JSON.stringify` -- `JSON.stringify` will skip symbol-keyed entries when serializing objects There are two ways of constructing a symbol: `Symbol("some-name")` or `Symbol.for("some-name")`, the difference being that `Symbol.for()` will always return the same symbol object when given the same key, while `Symbol()` always returns a unique symbol object (in other words, `Symbol("foo") !== Symbol("foo")` while `Symbol.for("foo") === Symbol.for("foo")`). You can think of this library as `Symbol.fuzzyFor()`. ## Namespaces In order to be clean with `Symbol.for()`, it is helpful to namespace your keys with a unique prefix. For example, if you have a program named 'crayon box' and you want to define `red`, `green`, and `blue` symbols, you may want to define these as `Symbol.for('crayon.box.red')`, rather than `Symbol.for('red')`, to keep your symbols from unintentionally colliding with another color management program. A helper is provided for creating multiple symbols using the same prefix, accessed by `syms[Symbol.for('ns')]("my.namespace")` or `syms[syms.ns]("my.namespace")`: ```javascript const syms = require("syms"); const test = Symbol.for("one.two.three.four.five"); const {twoThreeFourFive: check1} = syms[Symbol.for('ns')]('one'); const {THREE_FOUR_FIVE: check2} = syms[Symbol.for('ns')]('one_two'); const {four_five: check3} = syms[syms.ns]('one :: two'); const {kFive: check4} = syms[syms.ns]('one/two/three'); assert.equal(test, check1); assert.equal(test, check2); assert.equal(test, check3); assert.equal(test, check4); ``` ## Why "." ? The reason that `.` is used as a word separator is because modern versions of node use `Symbol.for("nodejs.foo.bar")` style symbols, allowing things like this: ```javascript const syms = require("syms"); const sym1 = require("util").inspect.custom; const sym2 = Symbol.for("nodejs.util.inspect.custom"); const { NODEJS_UTIL_INSPECT_CUSTOM } = syms; const { nodejsUtilInspectCustom } = syms; // with the `syms[syms.ns](namespaceName)` helper, you can do stuff like this: const { inspectCustom } = syms[syms.ns]('nodejs.util'); // sym1, NODEJS_UTIL_INSPECT_CUSTOM, and nodejsUtilInspectCustom are all the same symbol, which is also the symbol returned by `` const {strict:assert} = require("assert") assert.equal(sym1, sym2) assert.equal(sym1, NODEJS_UTIL_INSPECT_CUSTOM); assert.equal(sym1, nodejsUtilInspectCustom); assert.equal(sym1, inspectCustom); class MyThing() { [inspectCustom]() { return "[[ my-thing instance ]]"; } } console.log(require("util").inspect(new MyThing())); ``` ## "Internal" Methods One handy usage is to "clean up" your public/official api, without actually restricting access to the actual underlying methods/properties, which can feel slightly nicer than naming like `this._internalThing`. ```javascript const { SEND, OUT_STREAM } = require("syms"); class Whatever { constructor({ out = process.stdout }) { this[OUT_STREAM] = out; } async doSomething() { await someOperation(); this[SEND](); } [SEND]() { this[OUT_STREAM].write("i'm sending you a message, friend, and i hope you receive it swiftly!\n"); } } ``` In the above, you could could change these vars to `kSend`+`kOutStream`, or `send`+`outStream`, or whatever other format pleases you. You could also use `const $ = require("syms")` and use `this[$.outStream]`. Consumers of a `Whatever` instance (`const whatevs = new Whatever({out});`) can easily call `whatevs[Symbol.for("send")]()` and `whatevs[Symbol.for("out stream")]`, without even having to know that you're using this library, and it would hopefully be pretty obvious that they're reaching into your internal / non-official / non-stable APIs and should do so at their own risk: ```javascript const out = fs.createWriteStream("/tmp/useless-file.txt"); const _send = Symbol.for('send'); const _outs = Symbol.for('out.stream'); const whatever = new Whatever({out}) whatever[_send](); whatever[_outs].write("hello"); ```