Repository: aarondcohen/id128 Branch: master Commit: 4a8f911e306e Files: 55 Total size: 144.9 KB Directory structure: gitextract_yj1hrjym/ ├── .eslintrc.js ├── .gitignore ├── CHANGES ├── LICENSE ├── README.md ├── benchmark/ │ ├── basics.js │ ├── factory-uuid.js │ ├── memory.js │ └── stats.js ├── index.d.ts ├── index.js ├── package.json ├── src/ │ ├── coder/ │ │ ├── base.js │ │ ├── crockford32.js │ │ ├── hex.js │ │ └── uuid.js │ ├── common/ │ │ ├── byte-array.js │ │ ├── epoch-converter.js │ │ ├── exception.js │ │ ├── fake-machine.js │ │ ├── machine.js │ │ ├── random-bytes-browser.js │ │ └── random-bytes.js │ ├── factory/ │ │ ├── id.js │ │ └── versioned-id.js │ └── id/ │ ├── base.js │ ├── ulid-monotonic.js │ ├── ulid.js │ ├── uuid-1.js │ ├── uuid-4.js │ ├── uuid-6.js │ ├── uuid-nil.js │ └── uuid.js ├── test/ │ ├── coder/ │ │ ├── crockford32.js │ │ ├── hex.js │ │ ├── shared.js │ │ └── uuid.js │ ├── common/ │ │ ├── byte-array.js │ │ ├── epoch-converter.js │ │ ├── fake-machine.js │ │ └── machine.js │ ├── factory/ │ │ ├── id.js │ │ └── versioned-id.js │ ├── id/ │ │ ├── shared.js │ │ ├── ulid-monotonic.js │ │ ├── ulid.js │ │ ├── uuid-1.js │ │ ├── uuid-4.js │ │ ├── uuid-6.js │ │ └── uuid-nil.js │ └── id128.js ├── types/ │ ├── test.ts │ ├── tsconfig.json │ └── tslint.json └── utils.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.js ================================================ /* eslint max-lines: "off" */ module.exports = { env: { browser: true, commonjs: true, es6: true, node: true, }, extends: 'eslint:recommended', parserOptions: { sourceType: 'module' }, rules: { 'accessor-pairs': ['error'], 'array-bracket-spacing': ['warn', 'never'], 'array-callback-return': ['warn'], 'arrow-body-style': ['error'], 'arrow-parens': ['error', 'always'], 'arrow-spacing': ['error', { after: true, before: true, }], 'block-scoped-var': ['off'], 'block-spacing': ['warn', 'always'], 'brace-style': ['warn', 'stroustrup', { allowSingleLine: true }], 'callback-return': ['warn'], 'camelcase': ['off'], //<- consider error, never 'capitalized-comments': ['off'], 'class-methods-use-this': ['off'], 'comma-dangle': ['error', 'always-multiline'], 'comma-spacing': ['error'], 'comma-style': ['error'], 'complexity': ['warn', { max: 15 }], 'computed-property-spacing': ['error'], 'consistent-return': ['off'], 'consistent-this': ['error'], 'constructor-super': ['error'], 'curly': ['error', 'all'], 'default-case': ['off'], 'dot-location': ['error', 'property'], 'dot-notation': ['error'], 'eol-last': ['warn', 'always'], 'eqeqeq': ['error'], 'func-call-spacing': ['error', 'never'], 'func-name-matching': ['off'], 'func-names': ['off'], 'func-style': ['off'], 'generator-star-spacing': ['error', 'after'], 'global-require': ['error'], 'guard-for-in': ['warn'], 'handle-callback-err': ['warn'], 'id-blacklist': ['off'], 'id-length': ['off'], 'id-match': ['off'], 'indent': ['error', 'tab'], 'init-declarations': ['off'], 'jsx-quotes': ['off'], 'key-spacing': ['error'], 'keyword-spacing': ['error', { after: true }], 'line-comment-position': ['off'], 'linebreak-style': ['error', 'unix'], 'lines-around-comment': ['off'], 'lines-around-directive': ['error'], 'max-depth': ['warn', { max: 4 }], 'max-len': ['error', { code: 100, ignoreRegExpLiterals: true, ignoreStrings: true, ignoreTemplateLiterals: true, ignoreUrls: true, tabWidth: 2, }], 'max-lines': ['error', { max: 200, skipBlankLines: true, skipComments: true, }], 'max-nested-callbacks': ['warn', { max: 5 }], 'max-params': ['warn', { max: 3 }], 'max-statements': ['warn', { max: 20 }], 'max-statements-per-line': ['error', { max: 2 }], 'multiline-ternary': ['off'], 'new-cap': ['off'], 'new-parens': ['off'], 'newline-after-var': ['warn', 'always'], 'newline-before-return': ['warn'], 'newline-per-chained-call': ['warn', { ignoreChainWithDepth: 3 }], 'no-alert': ['error'], 'no-array-constructor': ['error'], 'no-await-in-loop': ['warn'], 'no-bitwise': ['off'], 'no-caller': ['error'], 'no-case-declarations': ['error'], 'no-catch-shadow': ['error'], 'no-class-assign': ['error'], 'no-cond-assign': ['error', 'except-parens'], 'no-confusing-arrow': ['error', { allowParens: true }], 'no-console': ['error'], 'no-const-assign': ['error'], 'no-constant-condition': ['error'], 'no-continue': ['off'], 'no-control-regex': ['off'], 'no-debugger': ['error'], 'no-delete-var': ['error'], 'no-div-regex': ['error'], 'no-dupe-args': ['error'], 'no-dupe-class-members': ['error'], 'no-dupe-keys': ['error'], 'no-duplicate-case': ['error'], 'no-duplicate-imports': ['error'], 'no-else-return': ['off'], 'no-empty': ['error'], 'no-empty-character-class': ['error'], 'no-empty-function': ['off'], 'no-empty-pattern': ['error'], 'no-eq-null': ['error'], 'no-eval': ['error'], 'no-ex-assign': ['error'], 'no-extend-native': ['error'], 'no-extra-bind': ['error'], 'no-extra-boolean-cast': ['error'], 'no-extra-label': ['warn'], 'no-extra-parens': ['error', 'functions'], 'no-extra-semi': ['error'], 'no-fallthrough': ['error'], 'no-floating-decimal': ['error'], 'no-func-assign': ['error'], 'no-global-assign': ['error'], 'no-implicit-coercion': ['error'], 'no-implicit-globals': ['warn'], 'no-implied-eval': ['error'], 'no-inline-comments': ['off'], 'no-inner-declarations': ['error', 'functions'], 'no-invalid-regexp': ['error'], 'no-invalid-this': ['error'], 'no-irregular-whitespace': ['error'], 'no-iterator': ['error'], 'no-label-var': ['error'], 'no-labels': ['off'], 'no-lone-blocks': ['error'], 'no-lonely-if': ['error'], 'no-loop-func': ['error'], 'no-magic-numbers': ['error', { enforceConst: true, ignore: [-1, 0, 1], ignoreArrayIndexes: true, }], 'no-mixed-operators': ['off'], 'no-mixed-requires': ['off'], 'no-mixed-spaces-and-tabs': ['error', 'smart-tabs'], 'no-multi-assign': ['off'], 'no-multi-spaces': ['error'], 'no-multi-str': ['off'], 'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 1, }], 'no-negated-condition': ['off'], 'no-nested-ternary': ['off'], 'no-new': ['warn'], 'no-new-func': ['error'], 'no-new-object': ['error'], 'no-new-require': ['warn'], 'no-new-symbol': ['error'], 'no-new-wrappers': ['error'], 'no-obj-calls': ['error'], 'no-octal-escape': ['error'], 'no-param-reassign': ['error'], 'no-path-concat': ['warn'], 'no-plusplus': ['off'], 'no-process-env': ['error'], 'no-process-exit': ['error'], 'no-proto': ['error'], 'no-prototype-builtins': ['off'], 'no-redeclare': ['error'], 'no-regex-spaces': ['off'], 'no-restricted-globals': ['off'], 'no-restricted-imports': ['off'], 'no-restricted-modules': ['off'], 'no-restricted-properties': ['off'], 'no-restricted-syntax': ['off'], 'no-return-assign': ['error', 'except-parens'], 'no-return-await': ['error'], 'no-script-url': ['error'], 'no-self-assign': ['error'], 'no-self-compare': ['error'], 'no-sequences': ['error'], 'no-shadow': ['warn', { builtinGlobals: true }], 'no-shadow-restricted-names': ['error'], 'no-sparse-arrays': ['error'], 'no-sync': ['warn'], 'no-tabs': ['off'], 'no-template-curly-in-string': ['error'], 'no-ternary': ['off'], 'no-this-before-super': ['error'], 'no-throw-literal': ['error'], 'no-trailing-spaces': ['error'], 'no-undef': ['error'], 'no-undef-init': ['off'], 'no-undefined': ['off'], 'no-underscore-dangle': ['off'], 'no-unexpected-multiline': ['error'], 'no-unmodified-loop-condition': ['warn'], 'no-unneeded-ternary': ['error', { defaultAssignment: false }], 'no-unreachable': ['error'], 'no-unsafe-finally': ['error'], 'no-unsafe-negation': ['error'], 'no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: false, }], 'no-unused-labels': ['error'], 'no-unused-vars': ['error', { args: 'all', argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', ignoreRestSiblings: true, vars: 'all', varsIgnorePattern: '^_', }], 'no-use-before-define': ['error', { classes: false, functions: false, }], 'no-useless-call': ['error'], 'no-useless-computed-key': ['error'], 'no-useless-concat': ['warn'], 'no-useless-constructor': ['error'], 'no-useless-escape': ['warn'], 'no-useless-rename': ['warn'], 'no-useless-return': ['warn'], 'no-var': ['off'], 'no-void': ['warn'], 'no-warning-comments': ['warn'], 'no-whitespace-before-property': ['error'], 'no-with': ['error'], 'object-curly-newline': ['error', { minProperties: 3, multiline: true, }], 'object-curly-spacing': ['error', 'always'], 'object-property-newline': ['off'], 'object-shorthand': ['off'], 'one-var': ['off'], 'one-var-declaration-per-line': ['error', 'initializations'], 'operator-assignment': ['off'], 'operator-linebreak': ['error', 'before'], 'padded-blocks': ['error', 'never'], 'prefer-arrow-callback': ['warn'], 'prefer-const': ['warn'], 'prefer-destructuring': ['off'], 'prefer-numeric-literals': ['warn'], 'prefer-promise-reject-errors': ['error'], 'prefer-rest-params': ['warn'], 'prefer-spread': ['warn'], 'prefer-template': ['warn'], 'quote-props': ['error', 'consistent-as-needed'], 'quotes': ['error', 'single', { allowTemplateLiterals: true }], 'radix': ['error', 'always'], 'require-await': ['error'], 'require-jsdoc': ['off'], 'require-yield': ['error'], 'rest-spread-spacing': ['error', 'never'], 'semi': ['error', 'never'], 'semi-spacing': ['error', { after: true, before: false, }], 'sort-imports': ['off'], 'sort-keys': ['warn', 'asc', { caseSensitive: true, natural: true, }], 'sort-vars': ['warn'], 'space-before-blocks': ['error', 'always'], 'space-before-function-paren': ['error', { anonymous: 'always', asyncArrow: 'always', named: 'never', }], 'space-in-parens': ['error', 'never'], 'space-infix-ops': ['error', { int32Hint: false }], 'space-unary-ops': ['error', { nonwords: false, words: true, }], 'spaced-comment': ['off'], 'strict': ['error'], 'symbol-description': ['error'], 'template-curly-spacing': ['off'], 'template-tag-spacing': ['error', 'always'], 'unicode-bom': ['error'], 'use-isnan': ['error'], 'valid-jsdoc': ['warn'], 'valid-typeof': ['error'], 'vars-on-top': ['off'], 'wrap-iife': ['error', 'inside', { functionPrototypeMethods: true }], 'wrap-regex': ['off'], 'yield-star-spacing': ['error', 'after'], 'yoda': ['off'], }, } ================================================ FILE: .gitignore ================================================ .node-version node_modules/ yarn.lock *.swp ================================================ FILE: CHANGES ================================================ - 0.1.0 : Move extraneous exports from index.js into utils.js. - 0.2.0 : Add browser support for Browserify/Webpack. - 0.3.0 : Pass named arguments to .generate instead of positional. - 1.0.0 : Added support for Uuid V1 and extracted competitive benchmarks. - 1.0.1 : Remove lockfile from release. - 1.0.2 : Fix bad version push. - 1.0.3 : Delete the extra benchmarks for real. - 1.1.0 : Performance improvements and renamed resetClockSequence to reset. - 1.1.1 : Fixed repo link. - 1.2.0 : Added generic UUID factory. - 1.3.0 : Added UUID V6 support. - 1.4.0 : Exposed encoding verifiers. - 1.5.0 : Fixed benchmarks and added support for node +12.5. - 1.6.0 : Added Typescript support and top-level id comparators. - 1.6.1 : Tweaked random memory allocation and updated benchmarks. - 1.6.2 : Fixed docs - 1.6.3 : Fixed memory profile by reusing the buffer of random. - 1.6.4 : Fixed worker support - 1.6.5 : Removed erroneous packaged files - 1.6.6 : Fix CHANGES and add credit for worker support ================================================ FILE: LICENSE ================================================ Copyright 2018 Aaron Cohen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Id128 Generate 128-bit unique identifiers for various specifications. In particular: - [ULID](#ulid) - [Monotonic ULID](#ulidmonotonic) - [UUID 1 (Variant 1 Version 1)](#uuid1) - [UUID 4 (Variant 1 Version 4)](#uuid4) - [UUID 6 (Variant 1 Version 6)](#uuid6) - [Nil UUID (Variant 0 Version 0)](#uuidnil) - [Uuid (Unknown Variant and Version)](#uuid) # Common Usage ```es6 const { Ulid, UlidMonotonic, Uuid, Uuid1, Uuid4, Uuid6, UuidNil, idCompare, idEqual, } = require('id128'); // Id factories [ Ulid, UlidMonotonic, Uuid1, Uuid4, Uuid6, UuidNil, ].forEach((IdType) => { // Identify the factory console.log(IdType.name); // Generate a new id const id = IdType.generate(); // Get the smallest valid id const min = IdType.MIN(); // Get the largest valid id const max = IdType.MAX(); // Type-check the id console.log(id instanceof IdType.type) // Compare ids console.log(id.equal(id)); console.log(! id.equal(min)); console.log(! id.equal(max)); console.log(id.compare(min) === 1); console.log(id.compare(id) === 0); console.log(id.compare(max) === -1); // Encode the id in its canonical form const canonical = id.toCanonical(); console.log(canonical); // Encode the id for efficient db storage const raw = id.toRaw(); console.log(raw); // Verify a canonically formatted id console.log(IdType.isCanonical(canonical)); // Decode a valid canonically formatted id console.log(id.equal(IdType.fromCanonical(canonical))); // Decode a canonically formatted id, skipping validation console.log(id.equal(IdType.fromCanonicalTrusted(canonical))); // Verify a raw formatted id console.log(IdType.isRaw(raw)); // Decode a valid raw formatted id console.log(id.equal(IdType.fromRaw(raw))); // Decode a raw formatted id, skipping validation console.log(id.equal(IdType.fromRawTrusted(raw))); }); // Uuid Factory [0, 1, 4, 6].forEach((version) => { // Generate a new id const id = Uuid.generate({ version }); // Get the smallest valid id const min = Uuid.MIN({ version }); // Get the largest valid id const max = Uuid.MAX({ version }); // Type-check the id console.log(id instanceof Uuid.type) // Encode the id in its canonical form const canonical = id.toCanonical(); console.log(canonical); // Encode the id for efficient db storage const raw = id.toRaw(); console.log(raw); // Decode a valid canonically formatted id console.log(id.equal(Uuid.fromCanonical(canonical))); // Decode a canonically formatted id, skipping validation console.log(id.equal(Uuid.fromCanonicalTrusted(canonical))); // Decode a valid raw formatted id console.log(id.equal(Uuid.fromRaw(raw))); // Decode a raw formatted id, skipping validation console.log(id.equal(Uuid.fromRawTrusted(raw))); }); // Static Utilities // Equate arbitrary ids console.log(idEqual(Ulid.generate(), Uuid4.generate())) // Compare arbitrary ids console.log(idCompare(Ulid.generate(), Uuid4.generate())) ``` ## Common Factory Properties ### name Return the name of the generated id type. ### type Return the type of the generated id instances for type-checking with the `instanceof` operator. ## Common Factory Methods ### .construct(bytes) => id Return a new id instance without validating the bytes. ### .generate() => id Return a new id instance. ### .MIN() => id Return the id instance with the smallest valid value. ### .MAX() => id Return the id instance with the largest valid value. ### .fromCanonical(canonical_string) => id Decode an id from its canonical representation. Throw `InvalidEncoding` if the string is undecodable. ### .fromCanonicalTrusted(canonical_string) => id Decode an id from its canonical representation. Skip validation and assume the input is decodable. ### .fromRaw(raw_string) => id Decode an id from its raw representation. Throw `InvalidEncoding` if the string is undecodable. ### .fromRawTrusted(raw_string) => id Decode an id from its raw representation. Skip validation and assume the input is decodable. ### .toCanonical(id) => canonical_string Encode the given id in the canonical form. Throw `InvalidBytes` if the id is not 128-bit conformant. ### .toRaw(id) => raw_string Encode the given id in the raw form. Throw `InvalidBytes` if the id is not 128-bit conformant. ### .isCanonical(canonical_string) => (true|false) Verify if a string is a valid canonical encoding. ### .isRaw(raw_string) => (true|false) Verify if a string is a valid raw encoding. ## Common Instance Properties ### bytes Return the actual byte array representing the id. ## Common Instance Methods ### .clone() => deep_copy Return a new instance of the id with the same bit signature. ### .compare(other) => (-1|0|1) Determine how this id is ordered against another. ### .equal(other) => (true|false) Determine if this id has the same bytes as another. ### .toCanonical() => canonical_string Encode this id in its canonical form. ### .toRaw() => raw_string Encode this id in its raw form. ## Namespace Static Utilities ### idCompare(left_id, right_id) => (-1|0|1) Determine if the left id is `less than | equal to | greater than` the right id using lexicographical byte order. ### idEqual(left_id, right_id) => (true|false) Determine if 2 ids have the same byte value. # Ulid ```es6 const { Ulid } = require('id128'); ``` Ulid, as [specified](https://github.com/ulid/spec), has some nice properties: - collision resistant: 80-bits of randomness - k-ordered: prefixed with millisecond precision timestamp - database friendly: fits within a uuid and generally appends to the index - human friendly: canonically encodes as a case-insensitive Crockford 32 number It is useful when you need a distributed domain unique id. ## Additional Instance Properties ### time Return a Date object for the epoch milliseconds encoded in the id. ## Additional Factory Methods ### .generate({ time }) => id Return a new id instance. Set any argument to `null` or `undefined` to trigger its default behavior. `time` defaults to the current time. It can be given either as a `Date` object or epoch milliseconds (milliseconds since January 1st, 1970). Throw `InvalidEpoch` for times before the epoch or after approximately August 2nd, 10889. This is provided mostly for unit tests. ## Byte Format Format `tttt tttt tttt rrrr rrrr rrrr rrrr rrrr` where: - `t` is 4 bits of time - `r` is 4 bits of random # UlidMonotonic ```es6 const { UlidMonotonic } = require('id128'); ``` UlidMonotonic is inspired by the [specification](https://github.com/ulid/spec#monotonicity): - collision resistant: 15-bits of random seeded clock sequence plus 64-bits of randomness - total ordered: prefixed with millisecond precision timestamp plus 15-bit clock sequence - database friendly: fits within a uuid and generally appends to the index - human friendly: canonically encodes as a case-insensitive Crockford 32 number It is useful when you need to guarantee a process unique id. ## Additional Instance Properties ### time Return a Date object for the epoch milliseconds encoded in the id. ## Additional Factory Methods ### .generate({ time }) => id Return a new id instance. Set any argument to `null` or `undefined` to trigger its default behavior. `time` defaults to the current time. It can be given either as a `Date` object or epoch milliseconds (milliseconds since January 1st, 1970). Extra caution is required since setting a future time and subsequently calling `generate` guarantees usage of the clock sequence. Throw `InvalidEpoch` for times before the epoch or after approximately August 2nd, 10889. Throw `ClockSequenceOverflow` when the clock sequence is exhausted. This is provided mostly for unit tests. ### .reset() Return the clock sequence to its starting position. This is provided mostly for unit tests. ## Byte Format Format `tttt tttt tttt cccc rrrr rrrr rrrr rrrr` where: - `t` is 4 bits of time - `c` is 4 bits of random-seeded clock sequence - `r` is 4 bits of random More specifically, the clock sequence is a counter. When the first id for a new timestamp is generated, the clock sequence is seeded with random bits and the left-most clock sequence bit is set to 0, reserving 2^15 clock ticks. Whenever a time from the past seeds the generator, the previous id's time and clock sequence are used instead, with the clock sequence incremented by 1. This guarantees strict local monotonicity and preserves lexical ordering and general randomness. Without a seeded time, UlidMonotonic is unlikely to exceed the clock sequence (the clock sequence supports generating a new id every 31 nanoseconds). However, in the unlikely event of an overflow, id generation is aborted. # Uuid1 ```es6 const { Uuid1 } = require('id128'); ``` Uuid1 implements the [RFC 4122 time specification](https://tools.ietf.org/html/rfc4122#section-4.2): - time-based: encodes the current millisecond timestamp - location-based: encodes the mac address of the machine While this mostly adheres to the spec, there are a few nuances in the handling of time. Instead of encoding time as 100-nanoseconds since the Gregorian epoch, 48 bits encode milliseconds since the Gregorian epoch time and 12 bits count past time collisions, resetting whenever given a new future time. There are a few benefits: - high precision time is unreliable in the browser so this ensures better precision - the max supported date is now around the year 10502 instead of around 5236 - generating 4096 ids/ms (~4,000,000 ids/s) is wildly unlikely in real world uses - in the rare hi-res overflow, the count simply spills over to the clock sequence ## Additional Instance Properties ### clock_sequence Return the clock sequence encoded in the id. ### hires_time Return the number of prior ids generated while time stood still. ### node Return the MAC address encoded in the id. ### time Return a Date object for the epoch milliseconds encoded in the id. ### variant Return the variant as encoded in the id. Should be 1. ### version Return the version as encoded in the id. Should be 1. ## Additional Factory Methods ### .generate({ node, time }) => id Return a new id instance. Set any argument to `null` or `undefined` to trigger its default behavior. `time` defaults to the current time. It can be given either as a `Date` object or Gregorian milliseconds (milliseconds since October 15th, 1582). Extra caution is required since setting a future time and subsequently calling `generate` guarantees usage of the hi-res counter and clock sequence. Throw `InvalidEpoch` for times before the Gregorian epoch or after approximately May 17, 10502. This is provided mostly for unit tests. `node` defaults to the MAC address, or a random multicast address when the MAC address is unavailable. It can be given as an array of 6 bytes. ### .reset() Return the hi-res counter to its starting position and generate a new random clock sequence seed. This is provided mostly for unit tests. ## Byte Format Format `llll lnnn mmmm vhhh tccc aaaa aaaa aaaa` where: - `l` is 4 bits of low millisecond time - `n` is 4 bits of hi-res time - `m` is 4 bits of mid millisecond time - `v` is 4 bits of the version - `h` is 4 bits of high millisecond time - `t` is 2 bits of the variant followed by 2 bits of the clock sequence - `c` is 4 bits of the clock sequence - `a` is 4 bits of the machine address # Uuid4 ```es6 const { Uuid4 } = require('id128'); ``` Uuid4 implements the [RFC 4122 random uuid specification](https://tools.ietf.org/html/rfc4122#section-4.4): - 122 random bits - 2 bits reserved for the variant (1) - 4 bits reserved for the version (4) It is useful when you need a well-supported globally unique id. ## Additional Instance Properties ### variant Return the variant as encoded in the id. Should be 1. ### version Return the version as encoded in the id. Should be 4. ## Byte Format Format `rrrr rrrr rrrr vrrr trrr rrrr rrrr rrrr` where: - `r` is 4 bits of random - `v` is 4 bits of the version - `t` is 2 bits of the variant followed by 2 bits of random # Uuid6 ```es6 const { Uuid6 } = require('id128'); ``` Uuid6 implements this [controversial blog post](https://bradleypeabody.github.io/uuidv6/): - time-based: encodes the current millisecond timestamp - location-based: encodes the mac address of the machine This is essentially the same implementation as Uuid1, however the time bits are arranged in lexicographical order. If you're looking for a spacial UUID that is optimized for clustered indices, consider Uuid6 as a viable option. ## Additional Instance Properties ### clock_sequence Return the clock sequence encoded in the id. ### hires_time Return the number of prior ids generated while time stood still. ### node Return the MAC address encoded in the id. ### time Return a Date object for the epoch milliseconds encoded in the id. ### variant Return the variant as encoded in the id. Should be 1. ### version Return the version as encoded in the id. Should be 6. ## Additional Factory Methods ### .generate({ node, time }) => id Return a new id instance. Set any argument to `null` or `undefined` to trigger its default behavior. `time` defaults to the current time. It can be given either as a `Date` object or Gregorian milliseconds (milliseconds since October 15th, 1582). Extra caution is required since setting a future time and subsequently calling `generate` guarantees usage of the hi-res counter and clock sequence. Throw `InvalidEpoch` for times before the Gregorian epoch or after approximately May 17, 10502. This is provided mostly for unit tests. `node` defaults to the MAC address, or a random multicast address when the MAC address is unavailable. It can be given as an array of 6 bytes. ### .reset() Return the hi-res counter to its starting position and generate a new random clock sequence seed. This is provided mostly for unit tests. ## Byte Format Format `mmmm mmmm mmmm vnnn tccc aaaa aaaa aaaa` where: - `m` is 4 bits of millisecond time - `v` is 4 bits of the version - `n` is 4 bits of hi-res time - `t` is 2 bits of the variant followed by 2 bits of the clock sequence - `c` is 4 bits of the clock sequence - `a` is 4 bits of the machine address # UuidNil ```es6 const { UuidNil } = require('id128'); ``` UuidNil implements the [RFC 4122 nil uuid specification](https://tools.ietf.org/html/rfc4122#section-4.1.7): - 128 bits of glorious 0 It is useful as placeholder for other 128-bit ids. ## Additional Instance Properties ### variant Return the variant as encoded in the id. Should be 0. ### version Return the version as encoded in the id. Should be 0. ## Byte Format Format `0000 0000 0000 v000 t000 0000 0000 0000` where: - `0` is 4 bits of 0 - `v` is 4 bits of the version (also 0) - `t` is 2 bits of the variant (also 0) followed by 2 bits of 0 # Uuid ```es6 const { Uuid } = require('id128'); ``` Uuid is a factory for generating and decoding UUIDs when the version is unknown until runtime. If the version is supported, it will produce UUIDs of the appropriate type. In exchange for the runtime flexibility, there is a necessary performance degradation. It is recommended to use this for decoding data from uncontrolled sources rather than generating new ids. Uuid supports all the same methods as the other ID factories. All modifications to typical behavior are noted below. ## Factory Properties ### versioned_ids Return the factories of all the supported ids. ## Factory Methods ### .construct(bytes) => versioned_id Return a new versioned id instance without validating the bytes. Return a Uuid if an appropriate version does not exist. ### .generate({ version, ... }) => versioned_id Return a new versioned id instance. All additional arguments are passed through to the associated version. Throw `UnsupportedVersion` if no associated version exists. ### .MIN({ version }) => versioned_id Return the versioned id instance with the smallest valid value. Throw `UnsupportedVersion` if no associated version exists. ### .MAX({ version }) => versioned_id Return the versioned id instance with the largest valid value. Throw `UnsupportedVersion` if no associated version exists. ### .fromCanonical(canonical_string) => versioned_id Decode a versioned id from its canonical representation. Return a Uuid if an appropriate version does not exist. Throw `InvalidEncoding` if the string is undecodable. ### .fromCanonicalTrusted(canonical_string) => versioned_id Decode a versioned id from its canonical representation. Return a Uuid if an appropriate version does not exist. Skip validation and assume the input is decodable. ### .fromRaw(raw_string) => versioned_id Decode a versioned id from its raw representation. Return a Uuid if an appropriate version does not exist. Throw `InvalidEncoding` if the string is undecodable. ### .fromRawTrusted(raw_string) => versioned_id Decode a versioned id from its raw representation. Return a Uuid if an appropriate version does not exist. Skip validation and assume the input is decodable. # Exceptions ```es6 const { Exception } = require('id128'); ``` All exceptions are namespaced under `Exception` for clarity. ## Id128Error ```es6 const { Exception: { Id128Error } } = require('id128'); ``` Base exception class for generic error catching. ## ClockSequenceOverflow ```es6 const { Exception: { ClockSequenceOverflow } } = require('id128'); ``` Incrementing the clock sequence is impossible. Should not happen unless manually seeding `#generate`. ## InvalidBytes ```es6 const { Exception: { InvalidBytes } } = require('id128'); ``` Encoding something other than 16 bytes. Likely to happen when encoding untrusted user input. ## InvalidEncoding ```es6 const { Exception: { InvalidEncoding } } = require('id128'); ``` Decoding an invalid format or non-string object. Likely to happen when decoding untrusted user input. ## InvalidEpoch ```es6 const { Exception: { InvalidEpoch } } = require('id128'); ``` Generating an id with an invalid timestamp. Should not happen unless manually seeding `#generate`. ## UnsupportedVersion ```es6 const { Exception: { UnsupportedVersion } } = require('id128'); ``` Failed to find a factory for the desired version. Likely to happen when decoding untrusted user input. # Browser Support This module supports browser compilation though Webpack/Browserify with a few caveats: - Random number generation is optimized for memory usage over speed since only a handful of ids are likely to be generated during a user's session so the overhead of generating a page of random values has poor amortized cost. - The browser must have native support for `crypto`. `Math.random` is far too insecure to support as a fallback, especially since the fallback only makes sense for older browsers with proven security holes. `msCrypto` is not a supported fallback due to many of the other required features. - The browser must support: * classes * closures * `const` and `let` * `Uint8Array` * `Symbol` This library is intended for modern browsers that keep pace with Javascript's growing ecosystem. I philosophically object to supporting efforts of companies to pour more money into broken browsers that only cause headaches for developers to support. I expect these caveats to be unnecessary within the next 5 years. All that said, please notify me of any issues with modern browsers and I'll do my best to support you. # Typescript Support This module includes Typescript bindings for all primary usage patterns. I'd like to highlight some design decisions: ## Id Types and Factory Types Each factory is exported as an instance using the same name as the type of id it produces. In Javascript, this is desirable as it provides a uniform interface regardless of the implementation. However, this complicates the Typescript type imports. For simple cases, like constructing an id and passing it around the program, this will behave exactly as desired: ``` import { Ulid } from 'id128' const id: Ulid = Ulid.generate() ``` When you need to check the type of the id, you should use the `type` attribute: ``` import { Ulid } from 'id128' const id: Ulid = Ulid.generate() if (id instanceof Ulid.type) { ... } ``` If you wish to pass around the factory itself, you can import the factory type: ``` import { Ulid } from 'id128' import type { UlidFactory } from 'id128' function doSomething(factory: UlidFactory) { ... } doSomething(Ulid) ``` Finally, if you need to operate on any id or id factory, you can import base types: ``` import type { Id, AnyIdFactory } from 'id128' function makeOne(factory: AnyIdFactory): Id { return factory.generate() } ``` ## Exception Handling Exception classes are designed to be checked using `instanceof`. Unfortunately, Typescript broke `instanceof` `Error` support for a more compliant compilation. Fortunately, the included exceptions bypass the issues caused by inheriting from the native `Error` by never overriding the constructor and implementing `name` as a readonly getter, As a consequence, the exceptions actually violate the standard `Error` interface, but they fulfill the standard `Function` interface. Therefore, you can safely use `instanceof` as intended: ``` import { UlidMonotonic } from 'id128' import { Exception } from 'id128' try { UlidMonotonic.generate() } catch (err) { if (err instanceof Exception.ClockSequenceOverflow ) { ... } } ``` # Motivation Originally, I was looking for an id that is independent of the database, but plays nice with database indices and data types. Most databases have built-in support for storing UUIDs efficiently, but UUID v4 does not cluster well and the other UUIDs require bit manipulation to get good performance, which will likely cause future maintenance headaches. After a bit of research, ULID was determined to nicely solve the problem. However, the javascript implementation had 2 major issues: 1. lacks database support 2. it's slow, which in a single-threaded Node server is deadly I considered sending in a patch, however I saw an opportunity for a more expressive interface, which is typically a bit harder to modify once a project is in wide use. There was also a clear pattern for encoding 128-bit ids into various formats, which seems generally useful. Ultimately, this library strives to be: - secure: uses cryptographic randomness to ensure general uniqueness - performant: currently one of the fastest id generators available - maintainable: heavily tested isolated code with a consistent interface - extensible: modular design to easily add new ids and new encodings # Tests To run the tests: ```bash npm install npm run test-all ``` # Benchmarks Competitive benchmarks have been moved to [benchmark-guid](https://github.com/aarondcohen/benchmark-guid) To run the benchmarks: ```bash npm install npm run benchmark ``` ``` Platform info: ============== Darwin 18.2.0 x64 Node.JS: 15.0.0 V8: 8.6.395.16-node.15 Intel(R) Core(TM) i7-4578U CPU @ 3.00GHz × 4 Ulid ==== generate: (4,590,833rps) (avg: 217ns) MIN: (12,491,186rps) (avg: 80ns) MAX: (12,669,223rps) (avg: 78ns) fromCanonical: (1,707,717rps) (avg: 585ns) fromCanonicalTrusted: (2,078,278rps) (avg: 481ns) fromRaw: (1,483,373rps) (avg: 674ns) fromRawTrusted: (1,979,964rps) (avg: 505ns) toCanonical: (3,256,155rps) (avg: 307ns) toRaw: (6,012,244rps) (avg: 166ns) UlidMonotonic ============= generate: (3,787,685rps) (avg: 264ns) MIN: (6,306,928rps) (avg: 158ns) MAX: (6,301,217rps) (avg: 158ns) fromCanonical: (1,423,104rps) (avg: 702ns) fromCanonicalTrusted: (1,722,958rps) (avg: 580ns) fromRaw: (1,381,296rps) (avg: 723ns) fromRawTrusted: (1,698,639rps) (avg: 588ns) toCanonical: (3,205,394rps) (avg: 311ns) toRaw: (5,774,288rps) (avg: 173ns) Uuid1 ===== generate: (4,984,699rps) (avg: 200ns) MIN: (12,888,384rps) (avg: 77ns) MAX: (12,817,435rps) (avg: 78ns) fromCanonical: (1,226,007rps) (avg: 815ns) fromCanonicalTrusted: (1,578,429rps) (avg: 633ns) fromRaw: (1,306,295rps) (avg: 765ns) fromRawTrusted: (1,626,095rps) (avg: 614ns) toCanonical: (5,859,714rps) (avg: 170ns) toRaw: (5,973,139rps) (avg: 167ns) Uuid4 ===== generate: (6,492,849rps) (avg: 154ns) MIN: (6,400,528rps) (avg: 156ns) MAX: (6,617,714rps) (avg: 151ns) fromCanonical: (1,286,561rps) (avg: 777ns) fromCanonicalTrusted: (1,625,362rps) (avg: 615ns) fromRaw: (1,313,004rps) (avg: 761ns) fromRawTrusted: (1,672,463rps) (avg: 597ns) toCanonical: (6,103,543rps) (avg: 163ns) toRaw: (6,235,448rps) (avg: 160ns) Uuid6 ===== generate: (3,466,357rps) (avg: 288ns) MIN: (5,244,292rps) (avg: 190ns) MAX: (5,151,746rps) (avg: 194ns) fromCanonical: (1,324,905rps) (avg: 754ns) fromCanonicalTrusted: (1,676,541rps) (avg: 596ns) fromRaw: (1,357,353rps) (avg: 736ns) fromRawTrusted: (1,717,530rps) (avg: 582ns) toCanonical: (5,061,822rps) (avg: 197ns) toRaw: (4,839,125rps) (avg: 206ns) UuidNil ======= generate: (9,312,932rps) (avg: 107ns) MIN: (5,158,703rps) (avg: 193ns) MAX: (8,795,275rps) (avg: 113ns) fromCanonical: (1,293,946rps) (avg: 772ns) fromCanonicalTrusted: (1,629,605rps) (avg: 613ns) fromRaw: (1,472,042rps) (avg: 679ns) fromRawTrusted: (1,780,904rps) (avg: 561ns) toCanonical: (5,169,323rps) (avg: 193ns) toRaw: (5,196,170rps) (avg: 192ns) Uuid processing Uuid1 ===================== generate: (4,159,340rps) (avg: 240ns) MIN: (4,877,918rps) (avg: 205ns) MAX: (4,907,348rps) (avg: 203ns) fromCanonical: (1,045,214rps) (avg: 956ns) fromCanonicalTrusted: (1,255,223rps) (avg: 796ns) fromRaw: (1,021,436rps) (avg: 979ns) fromRawTrusted: (1,268,213rps) (avg: 788ns) Uuid processing Uuid4 ===================== generate: (5,695,823rps) (avg: 175ns) MIN: (4,886,337rps) (avg: 204ns) MAX: (4,907,325rps) (avg: 203ns) fromCanonical: (1,047,372rps) (avg: 954ns) fromCanonicalTrusted: (1,292,729rps) (avg: 773ns) fromRaw: (1,031,590rps) (avg: 969ns) fromRawTrusted: (1,266,122rps) (avg: 789ns) Uuid processing Uuid6 ===================== generate: (4,122,279rps) (avg: 242ns) MIN: (4,744,102rps) (avg: 210ns) MAX: (4,860,271rps) (avg: 205ns) fromCanonical: (1,066,004rps) (avg: 938ns) fromCanonicalTrusted: (1,298,925rps) (avg: 769ns) fromRaw: (1,053,871rps) (avg: 948ns) fromRawTrusted: (1,286,373rps) (avg: 777ns) Uuid processing UuidNil ======================= generate: (8,140,742rps) (avg: 122ns) MIN: (4,717,779rps) (avg: 211ns) MAX: (8,261,012rps) (avg: 121ns) fromCanonical: (1,052,765rps) (avg: 949ns) fromCanonicalTrusted: (1,285,968rps) (avg: 777ns) fromRaw: (1,130,468rps) (avg: 884ns) fromRawTrusted: (1,312,878rps) (avg: 761ns) ``` # Acknowledgments Much of this library would not exist without the great work and documentation of other engineers. In particular: - [ksuid](https://github.com/segmentio/ksuid): an in-depth exploration of the guid nuances - [ulid](https://github.com/ulid/javascript): an elegant solution to a persistent problem - [uuid-random](https://github.com/jchook/uuid-random): allocating pages of randomness is by far the biggest performance factor Also, thank you: - [ruleb](https://github.com/ruleb): researching and patching worker support # Contributing Feel free to make a branch and send a pull request through [github](https://github.com/aarondcohen/id128) # Issues Please report any issues or bugs through [github](https://github.com/aarondcohen/id128/issues) ================================================ FILE: benchmark/basics.js ================================================ const Benchmarkify = require('benchmarkify'); const Id128 = require('../'); const { IdFactory } = require('../utils'); const { VersionedIdFactory } = require('../utils'); const benchmark = new Benchmarkify("Basics").printHeader(); const suites_by_name = Object.fromEntries([ 'generate', 'MIN', 'MAX', 'fromCanonical', 'fromCanonicalTrusted', 'fromRaw', 'fromRawTrusted', 'toCanonical', 'toRaw' ].map((name) => [name, benchmark.createSuite(name, { cycles: 1000000 })])); Object.values(Id128) .filter(x => x instanceof IdFactory) .filter(x => !(x instanceof VersionedIdFactory)) .sort((l, r) => l.name.localeCompare(r.name)) .forEach((id_type) => { const name = id_type.name; const id = id_type.generate(); const canonical = id.toCanonical(); const raw = id.toRaw(); suites_by_name.generate.add(name, () => id_type.generate()); suites_by_name.MIN.add(name, () => id_type.MIN()); suites_by_name.MAX.add(name, () => id_type.MAX()); suites_by_name.fromCanonical.add(name, () => id_type.fromCanonical(canonical)); suites_by_name.fromCanonicalTrusted.add(name, () => id_type.fromCanonicalTrusted(canonical)); suites_by_name.fromRaw.add(name, () => id_type.fromRaw(raw)); suites_by_name.fromRawTrusted.add(name, () => id_type.fromRawTrusted(raw)); suites_by_name.toCanonical.add(name, () => id.toCanonical()); suites_by_name.toRaw.add(name, () => id.toRaw()); }); benchmark.run(Object.values(suites_by_name)); ================================================ FILE: benchmark/factory-uuid.js ================================================ const Benchmarkify = require('benchmarkify'); const Id128 = require('../'); const benchmark = new Benchmarkify("UUID Processing").printHeader(); const suites_by_name = Object.fromEntries([ 'generate', 'MIN', 'MAX', 'fromCanonical', 'fromCanonicalTrusted', 'fromRaw', 'fromRawTrusted', ].map((name) => [name, benchmark.createSuite(name, { cycles: 100000 })])); const factory = Id128.Uuid; factory.versioned_ids .sort((l, r) => l.name.localeCompare(r.name)) .forEach((id_type) => { const name = id_type.name; const id = id_type.generate(); const canonical = id.toCanonical(); const raw = id.toRaw(); const type = { version: id.version }; suites_by_name.generate.add(name, () => factory.generate(type)); suites_by_name.MIN.add(name, () => factory.MIN(type)); suites_by_name.MAX.add(name, () => factory.MAX(type)); suites_by_name.fromCanonical.add(name, () => factory.fromCanonical(canonical)); suites_by_name.fromCanonicalTrusted.add(name, () => factory.fromCanonicalTrusted(canonical)); suites_by_name.fromRaw.add(name, () => factory.fromRaw(raw)); suites_by_name.fromRawTrusted.add(name, () => factory.fromRawTrusted(raw)); }); benchmark.run(Object.values(suites_by_name)); ================================================ FILE: benchmark/memory.js ================================================ const ByteArray = require('common/byte-array'); let count = 0 let result = null; let now = new Date; try { while (++count) { result = ByteArray.generateRandomFilled(); result = null if (!(count % 10000000)) { global.gc && global.gc(); const used = process.memoryUsage(); console.log(`Runs: ${count}`) for (let key in used) { console.log(`${key} ${Math.round(used[key] / 1024 * 100) / 100} KB`); } } } } catch (err) { const end = new Date; console.log(err); console.log([String(count).padStart(12), ...result].join(':')); console.log(`Time: ${end - now}, ${count/(end - now)}ops/ms`); } ================================================ FILE: benchmark/stats.js ================================================ const Benchmarkify = require('benchmarkify'); const Id128 = require('../'); const { Uuid } = Id128; const benchmark = new Benchmarkify("Stats").printHeader(); const id_suites = [ Id128.Ulid, Id128.UlidMonotonic, Id128.Uuid1, Id128.Uuid4, Id128.Uuid6, Id128.UuidNil, ].map((id_type) => { const id = id_type.generate(); const canonical = id.toCanonical(); const raw = id.toRaw(); const suite = benchmark.createSuite(id_type.name, { cycles: 1000000 }); suite.add('generate:', () => id_type.generate()); suite.add('MIN:', () => id_type.MIN()); suite.add('MAX:', () => id_type.MAX()); suite.add('fromCanonical:', () => id_type.fromCanonical(canonical)); suite.add('fromCanonicalTrusted:', () => id_type.fromCanonicalTrusted(canonical)); suite.add('fromRaw:', () => id_type.fromRaw(raw)); suite.add('fromRawTrusted:', () => id_type.fromRawTrusted(raw)); suite.add('toCanonical:', () => id.toCanonical()); suite.add('toRaw:', () => id.toRaw()); return suite; }); const uuid_suites = Uuid.versioned_ids .sort((l, r) => l.name.localeCompare(r.name)) .map((id_type) => { const id = id_type.generate(); const canonical = id.toCanonical(); const raw = id.toRaw(); const type = { version: id.version }; const suite = benchmark.createSuite( `Uuid processing ${id_type.name}`, { cycles: 1000000 } ); suite.add('generate:', () => Uuid.generate(type)); suite.add('MIN:', () => Uuid.MIN(type)); suite.add('MAX:', () => Uuid.MAX(type)); suite.add('fromCanonical:', () => Uuid.fromCanonical(canonical)); suite.add('fromCanonicalTrusted:', () => Uuid.fromCanonicalTrusted(canonical)); suite.add('fromRaw:', () => Uuid.fromRaw(raw)); suite.add('fromRawTrusted:', () => Uuid.fromRawTrusted(raw)); return suite; }); benchmark.run(id_suites.concat(uuid_suites)); ================================================ FILE: index.d.ts ================================================ /* eslint-disable @typescript-eslint/no-empty-interface */ export {}; // Utility Types interface ConstructorOf { new(...args: any[]): C; } // Id Types export interface Id { readonly bytes: Uint8Array; readonly [Symbol.toStringTag]: string; clone(): this; toCanonical(): string; toRaw(): string; compare(rhs: Id): number; equal(rhs: Id): boolean; } export interface Uuid extends Id { readonly variant: number; readonly version: number; } export interface Ulid extends Id { readonly time: Date; } export interface UlidMonotonic extends Id { readonly time: Date; } export interface Uuid1 extends Uuid { readonly clock_sequence: number; readonly hires_time: number; readonly node: Uint8Array; readonly time: Date; } export interface Uuid4 extends Uuid {} export interface Uuid6 extends Uuid { readonly clock_sequence: number; readonly hires_time: number; readonly node: Uint8Array; readonly time: Date; } export interface UuidNil extends Uuid {} // Id Factories interface IdFactory { readonly name: string; readonly type: ConstructorOf; construct(bytes: Uint8Array): T; generate(options?: {}): T; MIN(options?: {}): T; MAX(options?: {}): T; fromCanonical(canonical: string): T; fromCanonicalTrusted(canonical: string): T; fromRaw(raw: string): T; fromRawTrusted(raw: string): T; toCanonical(id: T): string; toRaw(id: T): string; compare(lhs: Id, rhs: Id): number; equal(lhs: Id, rhs: Id): boolean; isCanonical(canonical: string): boolean; isRaw(raw: string): boolean; } interface VersionedIdFactory extends IdFactory { readonly versioned_ids: Array>; MIN(options: VersionOption): T; MAX(options: VersionOption): T; } interface NodeOption { node?: Uint8Array | null; } interface TimeOption { time?: Date | number | null; } interface VersionOption { version: number; } export type AnyIdFactory = IdFactory; export interface UlidFactory extends IdFactory { generate(options?: TimeOption): Ulid; } export interface UlidMonotonicFactory extends IdFactory { generate(options?: TimeOption): UlidMonotonic; } export interface UuidFactory extends VersionedIdFactory { generate(options: NodeOption & TimeOption & VersionOption): Uuid; } export interface Uuid1Factory extends IdFactory { generate(options?: NodeOption & TimeOption): Uuid1; } export interface Uuid4Factory extends IdFactory { generate(options?: {}): Uuid4; } export interface Uuid6Factory extends IdFactory { generate(options?: NodeOption & TimeOption): Uuid6; } export interface UuidNilFactory extends IdFactory { generate(options?: {}): UuidNil; } // Errors // NOTE: Actually extends Error, but typescript breaks instanceof support export interface Id128Error extends Function { readonly name: string; message: string; stack?: string; } export interface ClockSequenceOverflow extends Id128Error {} export interface InvalidBytes extends Id128Error {} export interface InvalidEncoding extends Id128Error {} export interface InvalidEpoch extends Id128Error {} export interface UnsupportedVersion extends Id128Error {} // Exported Functions export function idCompare(lhs: Id, rhs: Id): number; export function idEqual(lhs: Id, rhs: Id): boolean; // Exported Constants export const Ulid: UlidFactory; export const UlidMonotonic: UlidMonotonicFactory; export const Uuid: UuidFactory; export const Uuid1: Uuid1Factory; export const Uuid4: Uuid4Factory; export const Uuid6: Uuid6Factory; export const UuidNil: UuidNilFactory; export namespace Exception { const Id128Error: ConstructorOf; const ClockSequenceOverflow: ConstructorOf; const InvalidBytes: ConstructorOf; const InvalidEncoding: ConstructorOf; const InvalidEpoch: ConstructorOf; const UnsupportedVersion: ConstructorOf; } ================================================ FILE: index.js ================================================ const { Ulid } = require('./src/id/ulid'); const { UlidMonotonic } = require('./src/id/ulid-monotonic'); const { Uuid } = require('./src/id/uuid'); const { Uuid1 } = require('./src/id/uuid-1'); const { Uuid4 } = require('./src/id/uuid-4'); const { Uuid6 } = require('./src/id/uuid-6'); const { UuidNil } = require('./src/id/uuid-nil'); const Crockford32Coder = require('./src/coder/crockford32'); const HexCoder = require('./src/coder/hex'); const UuidCoder = require('./src/coder/uuid'); const { IdFactory } = require('./src/factory/id'); const { VersionedIdFactory } = require('./src/factory/versioned-id'); const Exception = require('./src/common/exception'); const namespace = { idCompare: function(lhs, rhs) { return lhs.compare(rhs); }, idEqual: function(lhs, rhs) { return lhs.equal(rhs); }, Exception, Ulid: new IdFactory({ id: Ulid, canonical_coder: Crockford32Coder, raw_coder: HexCoder, }), UlidMonotonic: new IdFactory({ id: UlidMonotonic, canonical_coder: Crockford32Coder, raw_coder: HexCoder, }), Uuid: new VersionedIdFactory({ abstract_id: Uuid, versioned_ids: [ Uuid1, Uuid4, Uuid6, UuidNil, ], canonical_coder: UuidCoder, raw_coder: HexCoder, }), }; namespace.Uuid.versioned_ids.reduce( (ns, uuid) => Object.assign(ns, {[uuid.name]: uuid}), namespace ); module.exports = namespace; ================================================ FILE: package.json ================================================ { "name": "id128", "description": "Collection of 128-bit Id generators", "version": "1.6.6", "author": "Aaron Cohen ", "license": "MIT", "main": "index.js", "types": "index.d.ts", "files": [ "index.d.ts", "index.js", "src/", "utils.js" ], "homepage": "https://github.com/aarondcohen/id128#id128", "repository": { "type": "git", "url": "git+https://github.com/aarondcohen/id128.git" }, "bugs": { "url": "https://github.com/aarondcohen/id128/issues" }, "browser": { "./src/common/machine.js": "./src/common/fake-machine.js", "./src/common/random-bytes.js": "./src/common/random-bytes-browser.js" }, "scripts": { "benchmark": "NODE_PATH='./src/' node benchmark/stats.js", "call": "NODE_PATH='./src/' node", "dtslint": "dtslint types", "test": "NODE_PATH='./src/' mocha", "test-all": "NODE_PATH='./src/' mocha test/*" }, "engines": { "node": ">=v6.9.0" }, "devDependencies": { "benchmarkify": "github:aarondcohen/benchmarkify", "chai": "*", "dtslint": "^3.4.1", "mocha": "*", "sinon": "*" }, "keywords": [ "128-bit", "crockford32", "guid", "id", "monotonic", "random", "rfc-4122", "rfc4122", "uid", "ulid", "unique", "universal", "uuid", "uuid-v1", "uuid-v4", "uuid-v6", "uuid1", "uuid4", "uuid6", "uuidv1", "uuidv4", "uuidv6" ] } ================================================ FILE: src/coder/base.js ================================================ 'use strict'; const { InvalidEncoding, InvalidBytes, } = require('../common/exception'); const _valid_encoding_pattern = Symbol('valid_encoding_pattern'); class BaseCoder { constructor({ valid_encoding_pattern, } = {}) { this[_valid_encoding_pattern] = valid_encoding_pattern; } decode(encoding) { if (this.isValidEncoding(encoding)) { return this.decodeTrusted(encoding); } else { throw new InvalidEncoding(`Encoding [${encoding}] does not satisfy ${this[_valid_encoding_pattern]}`); } } decodeTrusted(encoding) { return ByteArray.generateRandomFilled() } encode(bytes) { if (this.isValidBytes(bytes)) { return this.encodeTrusted(bytes); } else { throw new InvalidBytes('Requires a 16-byte Uint8Array'); } } encodeTrusted(bytes) { return '' } isValidBytes(bytes) { return true && (bytes instanceof Uint8Array) && bytes.length === 16; } isValidEncoding(encoding) { return true && (typeof encoding === 'string' || encoding instanceof String) && this[_valid_encoding_pattern].test(encoding); } } module.exports = { BaseCoder }; ================================================ FILE: src/coder/crockford32.js ================================================ 'use strict'; const { BaseCoder } = require('./base'); const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; const MAX_QUINTET = 0b11111; const CHAR_TO_QUINTET = Array.from(ALPHABET).reduce( (acc, chr, idx) => (acc[chr] = acc[chr.toLowerCase()] = idx, acc), { 'I': ALPHABET.indexOf('1'), 'i': ALPHABET.indexOf('1'), 'L': ALPHABET.indexOf('1'), 'l': ALPHABET.indexOf('1'), 'O': ALPHABET.indexOf('0'), 'o': ALPHABET.indexOf('0'), 'U': ALPHABET.indexOf('V'), 'u': ALPHABET.indexOf('V'), } ); const QUINTET_TO_CHAR = Array.from(ALPHABET); function _charToQuintet(chr) { return CHAR_TO_QUINTET[chr]; } function _quintetToChar(quintet) { return QUINTET_TO_CHAR[quintet & MAX_QUINTET]; } class Crockford32Coder extends BaseCoder { constructor() { super({ valid_encoding_pattern: /^[0-7][^\W_]{25}$/, }); } decodeTrusted(encoding) { const bytes = new Uint8Array(16); const quintets = []; for (let idx = 0, end = encoding.length; idx < end; ++idx) { quintets.push(_charToQuintet(encoding[idx])); } //Note: unrolled for performance bytes[0] = quintets[0] << 5 | quintets[1]; bytes[1] = quintets[2] << 3 | quintets[3] >> 2; bytes[2] = quintets[3] << 6 | quintets[4] << 1 | quintets[5] >> 4; bytes[3] = quintets[5] << 4 | quintets[6] >> 1; bytes[4] = quintets[6] << 7 | quintets[7] << 2 | quintets[8] >> 3; bytes[5] = quintets[8] << 5 | quintets[9]; bytes[6] = quintets[10] << 3 | quintets[11] >> 2; bytes[7] = quintets[11] << 6 | quintets[12] << 1 | quintets[13] >> 4; bytes[8] = quintets[13] << 4 | quintets[14] >> 1; bytes[9] = quintets[14] << 7 | quintets[15] << 2 | quintets[16] >> 3; bytes[10] = quintets[16] << 5 | quintets[17]; bytes[11] = quintets[18] << 3 | quintets[19] >> 2; bytes[12] = quintets[19] << 6 | quintets[20] << 1 | quintets[21] >> 4; bytes[13] = quintets[21] << 4 | quintets[22] >> 1; bytes[14] = quintets[22] << 7 | quintets[23] << 2 | quintets[24] >> 3; bytes[15] = quintets[24] << 5 | quintets[25]; return bytes; } encodeTrusted(bytes) { //Note: unrolled for performance let quintets = [ (bytes[0] >> 5), (bytes[0]), (bytes[1] >> 3), (bytes[1] << 2 | bytes[2] >> 6), (bytes[2] >> 1), (bytes[2] << 4 | bytes[3] >> 4), (bytes[3] << 1 | bytes[4] >> 7), (bytes[4] >> 2), (bytes[4] << 3 | bytes[5] >> 5), (bytes[5]), (bytes[6] >> 3), (bytes[6] << 2 | bytes[7] >> 6), (bytes[7] >> 1), (bytes[7] << 4 | bytes[8] >> 4), (bytes[8] << 1 | bytes[9] >> 7), (bytes[9] >> 2), (bytes[9] << 3 | bytes[10] >> 5), (bytes[10]), (bytes[11] >> 3), (bytes[11] << 2 | bytes[12] >> 6), (bytes[12] >> 1), (bytes[12] << 4 | bytes[13] >> 4), (bytes[13] << 1 | bytes[14] >> 7), (bytes[14] >> 2), (bytes[14] << 3 | bytes[15] >> 5), (bytes[15]), ]; //Note: Massive performance losses occured when // using the more legible Array.map and Array.join let encoding = ''; for (let idx = 0, end = quintets.length; idx < end; ++idx) { encoding += _quintetToChar(quintets[idx]); } return encoding; } } module.exports = new Crockford32Coder; ================================================ FILE: src/coder/hex.js ================================================ 'use strict'; const { BaseCoder } = require('./base'); const ALPHABET = '0123456789ABCDEF'; const BYTE_TO_HEX = Array .from({ length: ALPHABET.length * ALPHABET.length }) .map((_, key) => ( '' + ALPHABET.charAt(key / ALPHABET.length) + ALPHABET.charAt(key % ALPHABET.length) )); const HEX_TO_BYTE = Array.from(ALPHABET).reduce( (mapping, hex, idx) => Object.assign(mapping, { [hex.toUpperCase()]: idx, [hex.toLowerCase()]: idx, }), Object.create(null) ); class HexCoder extends BaseCoder { constructor() { super({ valid_encoding_pattern: /^[0-9A-Fa-f]{32}$/, }); } decodeTrusted(encoding) { let bytes = new Uint8Array(16); for ( let dst = 0, hi_hex = true, src = 0, end = encoding.length; src < end; ++src ) { const hex = encoding[src]; if (hi_hex) { bytes[dst] = HEX_TO_BYTE[hex] << 4; } else { bytes[dst++] |= HEX_TO_BYTE[hex]; } hi_hex = !hi_hex; } return bytes; } encodeTrusted(bytes) { let encoding = ''; for (let idx = 0, end = bytes.length; idx < end; ++idx) { encoding += BYTE_TO_HEX[bytes[idx]]; } return encoding; } } module.exports = new HexCoder; ================================================ FILE: src/coder/uuid.js ================================================ 'use strict'; const { BaseCoder } = require('./base'); const ALPHABET = '0123456789ABCDEF'; const BYTE_TO_HEX = Array .from({ length: ALPHABET.length * ALPHABET.length }) .map((_, key) => ( '' + ALPHABET.charAt(key / ALPHABET.length) + ALPHABET.charAt(key % ALPHABET.length) )); const HEX_TO_BYTE = Array.from(ALPHABET).reduce( (mapping, hex, idx) => Object.assign(mapping, { [hex.toUpperCase()]: idx, [hex.toLowerCase()]: idx, }), Object.create(null) ); class UuidCoder extends BaseCoder { constructor() { super({ valid_encoding_pattern: /^[0-9A-Fa-f]{4}(?:-?[0-9A-Fa-f]{4}){7}$/, }); } decodeTrusted(encoding) { let bytes = new Uint8Array(16); for ( let dst = 0, hi_hex = true, src = 0, end = encoding.length; src < end; ++src ) { const hex = encoding[src]; if(hex !== '-') { if (hi_hex) { bytes[dst] = HEX_TO_BYTE[hex] << 4; } else { bytes[dst++] |= HEX_TO_BYTE[hex]; } hi_hex = ! hi_hex; } } return bytes; } encodeTrusted(bytes) { let idx = -1; const encoding = ('' + BYTE_TO_HEX[bytes[++idx]] + BYTE_TO_HEX[bytes[++idx]] + BYTE_TO_HEX[bytes[++idx]] + BYTE_TO_HEX[bytes[++idx]] + '-' + BYTE_TO_HEX[bytes[++idx]] + BYTE_TO_HEX[bytes[++idx]] + '-' + BYTE_TO_HEX[bytes[++idx]] + BYTE_TO_HEX[bytes[++idx]] + '-' + BYTE_TO_HEX[bytes[++idx]] + BYTE_TO_HEX[bytes[++idx]] + '-' + BYTE_TO_HEX[bytes[++idx]] + BYTE_TO_HEX[bytes[++idx]] + BYTE_TO_HEX[bytes[++idx]] + BYTE_TO_HEX[bytes[++idx]] + BYTE_TO_HEX[bytes[++idx]] + BYTE_TO_HEX[bytes[++idx]] ); return encoding; } } module.exports = new UuidCoder; ================================================ FILE: src/common/byte-array.js ================================================ const { randomBytes } = require('./random-bytes'); const MAX_BYTES = 16; class ByteArray { compare(lhs, rhs) { const mismatch_idx = lhs.findIndex((byt, idx) => (byt !== rhs[idx])); return ~mismatch_idx && Math.sign(lhs[mismatch_idx] - rhs[mismatch_idx]); } generateOneFilled() { return new Uint8Array(MAX_BYTES).fill(0xFF); } generateRandomFilled() { return randomBytes(MAX_BYTES); } generateZeroFilled() { return new Uint8Array(MAX_BYTES).fill(0); } } module.exports = new ByteArray; ================================================ FILE: src/common/epoch-converter.js ================================================ const { InvalidEpoch } = require('./exception'); const MIN_MS = 0; const MAX_MS = Math.pow(2, 48); class EpochConverter { fromEpoch(origin_ms, epoch_ms) { return new Date(epoch_ms + origin_ms); } toEpoch(origin_ms, time = null) { const coerced_ms = time === null ? Date.now() : Number.isInteger(time) ? time : time instanceof Date ? time.getTime() : (() => { throw new InvalidEpoch(`Failed to coerce time [${time}] to epoch`); })(); const epoch_ms = coerced_ms - origin_ms; if (epoch_ms < MIN_MS || epoch_ms >= MAX_MS) { const min_iso = new Date(MIN_MS + origin_ms).toISOString(); const max_iso = new Date(MAX_MS - 1 + origin_ms).toISOString(); throw new InvalidEpoch(`Epoch must be between ${min_iso} and ${max_iso}`); } return epoch_ms; } } module.exports = new EpochConverter; ================================================ FILE: src/common/exception.js ================================================ class Id128Error extends Error { get name() { return this.constructor.name } } class ClockSequenceOverflow extends Id128Error {} class InvalidBytes extends Id128Error {} class InvalidEncoding extends Id128Error {} class InvalidEpoch extends Id128Error {} class UnsupportedVersion extends Id128Error {} module.exports = { Id128Error, ClockSequenceOverflow, InvalidBytes, InvalidEncoding, InvalidEpoch, UnsupportedVersion, }; ================================================ FILE: src/common/fake-machine.js ================================================ const { randomBytes } = require('./random-bytes'); const _mac_address = Symbol('mac-address'); class FakeMachine { constructor() { this.reset(); } get mac_address() { let mac_address = this[_mac_address]; if (! mac_address) { mac_address = this[_mac_address] = randomBytes(6); mac_address[0] |= 0b00000001; } return mac_address; } reset() { this[_mac_address] = null; } } module.exports = new FakeMachine; ================================================ FILE: src/common/machine.js ================================================ const Os = require('os'); const { randomBytes } = require('./random-bytes'); const _mac_address = Symbol('mac-address'); class Machine { constructor() { this.reset(); } get mac_address() { let mac_address = this[_mac_address]; if (! mac_address) { const { mac } = Object.values(Os.networkInterfaces()) .reduce((memo, arr) => memo.concat(arr), []) .find((iface) => ! iface.internal && iface.mac) || {}; mac_address = this[_mac_address] = mac ? Uint8Array.from(mac.split(':'), (hex) => Number.parseInt(hex, 16)) : randomBytes(6); if (! mac) { mac_address[0] |= 0b00000001; } } return mac_address; } reset() { this[_mac_address] = null; } } module.exports = new Machine; ================================================ FILE: src/common/random-bytes-browser.js ================================================ const Crypto = self.crypto; function randomBytes(size) { const bytes = new Uint8Array(size); Crypto.getRandomValues(bytes); return bytes; }; module.exports = { randomBytes }; ================================================ FILE: src/common/random-bytes.js ================================================ const Crypto = require('crypto'); const BUFFER_SIZE = 4096 /* typical page size */ - 96 /* Empty buffer overhead */; const buffer = new Uint8Array(BUFFER_SIZE) let offset = BUFFER_SIZE; function randomBytes(size) { if (offset + size >= BUFFER_SIZE) { offset = 0; Crypto.randomFillSync(buffer) } return buffer.slice(offset, offset += size); } module.exports = { randomBytes }; ================================================ FILE: src/factory/id.js ================================================ 'use strict'; const _id = Symbol('id'); const _canonical_coder = Symbol('canonical_coder'); const _raw_coder = Symbol('raw_coder'); class IdFactory { constructor({ id, canonical_coder, raw_coder, } = {}) { this[_id] = class extends id { static get name() { return id.name; } static get [Symbol.species]() { return id; } get [Symbol.toStringTag]() { return `${id.name} ${this.toRaw()}`; } toCanonical() { return canonical_coder.encodeTrusted(this.bytes); } toRaw() { return raw_coder.encodeTrusted(this.bytes); } }; this[_canonical_coder] = canonical_coder; this[_raw_coder] = raw_coder; } // Properties get name() { return this[_id].name; } get type() { return this[_id][Symbol.species]; } // Generators construct(bytes) { return new this[_id](bytes); } generate() { return this[_id].generate(...arguments); } MIN() { return this[_id].MIN(...arguments); } MAX() { return this[_id].MAX(...arguments); } // Coders fromCanonical(canonical) { return this.construct(this[_canonical_coder].decode(canonical)); } fromCanonicalTrusted(canonical) { return this.construct(this[_canonical_coder].decodeTrusted(canonical)); } fromRaw(raw) { return this.construct(this[_raw_coder].decode(raw)); } fromRawTrusted(raw) { return this.construct(this[_raw_coder].decodeTrusted(raw)); } toCanonical(id) { return this[_canonical_coder].encode(id.bytes); } toRaw(id) { return this[_raw_coder].encode(id.bytes); } // Comparators compare(lhs, rhs) { console.warn("Deprecated: use generic idCompare instead.") return lhs.compare(rhs); } equal(lhs, rhs) { console.warn("Deprecated: use generic idEqual instead.") return lhs.equal(rhs); } // Verifiers isCanonical(canonical) { return this[_canonical_coder].isValidEncoding(canonical); } isRaw(raw) { return this[_raw_coder].isValidEncoding(raw); } } module.exports = { IdFactory }; ================================================ FILE: src/factory/versioned-id.js ================================================ 'use strict'; const { IdFactory } = require('./id'); const { UnsupportedVersion } = require('../common/exception'); const _id_by_version = Symbol('id_by_version'); function detect(factory, { version } = {}) { return factory[_id_by_version][version] || (() => { throw new UnsupportedVersion( `No support for version [${version}]` ) })(); } class VersionedIdFactory extends IdFactory { constructor({ abstract_id, versioned_ids, canonical_coder, raw_coder, }) { super({ id: abstract_id, canonical_coder, raw_coder, }); this[_id_by_version] = versioned_ids.reduce( (mapping, id) => Object.assign(mapping, { [id.MIN().version]: new IdFactory({ id, canonical_coder, raw_coder, }) }), Object.create(null), ); } get versioned_ids() { return Object.values(this[_id_by_version]); } // Generators construct(bytes) { const id = super.construct(bytes); const version = id.version; try { return detect(this, { version }).construct(bytes); } catch (error) { if (error instanceof UnsupportedVersion) { return id } else { throw error } } } generate() { return detect(this, ...arguments).generate(...arguments); } MIN() { return detect(this, ...arguments).MIN(); } MAX() { return detect(this, ...arguments).MAX(); } } module.exports = { VersionedIdFactory }; ================================================ FILE: src/id/base.js ================================================ 'use strict'; const ByteArray = require('../common/byte-array'); const _bytes = Symbol('bytes'); class BaseId { //Constructors constructor(bytes) { this[_bytes] = bytes; } clone() { return new this.constructor(this.bytes.slice()); } // Accessors get bytes() { return this[_bytes]; } get [Symbol.toStringTag]() { return this.constructor.name; } // Comparators compare(rhs) { return ByteArray.compare(this.bytes, rhs.bytes); } equal(rhs) { return this.compare(rhs) === 0; } } module.exports = { BaseId }; ================================================ FILE: src/id/ulid-monotonic.js ================================================ const { Ulid, setTime } = require('./ulid'); const ByteArray = require('../common/byte-array'); const EpochConverter = require('../common/epoch-converter'); const { ClockSequenceOverflow } = require('../common/exception'); const TIME_OFFSET = 0; const CLOCK_SEQUENCE_OFFSET = 6; const RANDOM_OFFSET = 8; const EPOCH_ORIGIN_MS = 0; let _previous_id; let _previous_time; function incrementClockSequence(bytes) { for ( let idx = RANDOM_OFFSET - 1, end = CLOCK_SEQUENCE_OFFSET - 1; idx > end; --idx ) { if (bytes[idx] === 0xFF) { bytes[idx] = 0; } else { ++bytes[idx]; return; } } throw new ClockSequenceOverflow('Exhausted clock sequence'); }; function reserveClockSequence(bytes) { bytes[CLOCK_SEQUENCE_OFFSET] &= 0b01111111; }; function restoreClockSequence(bytes) { for (let idx = TIME_OFFSET; idx < RANDOM_OFFSET; ++idx) { bytes[idx] = _previous_id.bytes[idx]; } }; class UlidMonotonic extends Ulid { static reset() { _previous_time = -1; _previous_id = this.MIN(); } //Constructors static generate({ time } = {}) { time = EpochConverter.toEpoch(EPOCH_ORIGIN_MS, time); let bytes = ByteArray.generateRandomFilled(); if (time <= _previous_time) { restoreClockSequence(bytes); incrementClockSequence(bytes); } else { setTime(time, bytes) reserveClockSequence(bytes); _previous_time = time; } return (_previous_id = new this(bytes)); } } UlidMonotonic.reset(); module.exports = { UlidMonotonic }; ================================================ FILE: src/id/ulid.js ================================================ const ByteArray = require('../common/byte-array'); const EpochConverter = require('../common/epoch-converter'); const { BaseId } = require('./base'); const TIME_OFFSET = 0; const EPOCH_ORIGIN_MS = 0; const UINT32_RADIX = Math.pow(2, 32); const UINT8_MAX = 0b11111111; function setTime(time, bytes) { const time_low = time % UINT32_RADIX; const time_high = (time - time_low) / UINT32_RADIX; let idx = TIME_OFFSET - 1; bytes[++idx] = (time_high >>> 8) & UINT8_MAX; bytes[++idx] = (time_high >>> 0) & UINT8_MAX; bytes[++idx] = (time_low >>> 24) & UINT8_MAX; bytes[++idx] = (time_low >>> 16) & UINT8_MAX; bytes[++idx] = (time_low >>> 8) & UINT8_MAX; bytes[++idx] = (time_low >>> 0) & UINT8_MAX; } class Ulid extends BaseId { //Constructors static generate({ time } = {}) { time = EpochConverter.toEpoch(EPOCH_ORIGIN_MS, time); let bytes = ByteArray.generateRandomFilled(); setTime(time, bytes); return new this(bytes); } static MIN() { return new this(ByteArray.generateZeroFilled()); } static MAX() { return new this(ByteArray.generateOneFilled()); } // Accessors get time() { let idx = TIME_OFFSET - 1; const time_high = 0 | (this.bytes[++idx] << 8) | (this.bytes[++idx] << 0); const time_low = 0 | (this.bytes[++idx] << 24) | (this.bytes[++idx] << 16) | (this.bytes[++idx] << 8) | (this.bytes[++idx] << 0); const epoch_ms = (time_high * UINT32_RADIX) + (time_low >>> 0); return EpochConverter.fromEpoch(EPOCH_ORIGIN_MS, epoch_ms); }; } module.exports = { Ulid, setTime }; ================================================ FILE: src/id/uuid-1.js ================================================ 'use strict'; const ByteArray = require('../common/byte-array'); const EpochConverter = require('../common/epoch-converter'); const Machine = require('../common/machine'); const { Uuid, setVariant, setVersion, } = require('./uuid'); const TIME_OFFSET = 0; const HIRES_TIME_OFFSET = 2; const CLOCK_SEQUENCE_OFFSET = 8; const NODE_OFFSET = 10; const CLOCK_SEQUENCE_RADIX = Math.pow(2, 14); const EPOCH_ORIGIN_MS = Date.parse('1582-10-15Z'); const HIRES_TIME_RADIX = Math.pow(2, 12); const TIME_LOW_MS_RADIX = Math.pow(2, 20); const UINT8_MAX = 0b11111111; let _clock_sequence; let _hires_time; let _previous_time; function incrementClockSequence() { _clock_sequence = (_clock_sequence + 1) % CLOCK_SEQUENCE_RADIX; } function setClockSequence(time, bytes) { if (_clock_sequence === null) { const random_bytes = ByteArray.generateRandomFilled(); _clock_sequence = ( 0 | random_bytes[CLOCK_SEQUENCE_OFFSET + 0] << 8 | random_bytes[CLOCK_SEQUENCE_OFFSET + 1] << 0 ) % CLOCK_SEQUENCE_RADIX; } else if (time < _previous_time) { incrementClockSequence(); } let idx = CLOCK_SEQUENCE_OFFSET - 1; bytes[++idx] = (_clock_sequence >>> 8) & UINT8_MAX; bytes[++idx] = (_clock_sequence >>> 0) & UINT8_MAX; } function setNode(node, bytes) { for (let idx = 0; idx < 6; ++idx) { bytes[NODE_OFFSET + idx] = node[idx]; } } function setTime(time, bytes) { _hires_time = time > _previous_time ? 0 : _hires_time + 1; if (_hires_time === HIRES_TIME_RADIX) { _hires_time = 0; incrementClockSequence(); } const time_low_ms = time % TIME_LOW_MS_RADIX; const time_low = time_low_ms * HIRES_TIME_RADIX + _hires_time; const time_high = (time - time_low_ms) / TIME_LOW_MS_RADIX; let idx = TIME_OFFSET - 1; bytes[++idx] = (time_low >>> 24) & UINT8_MAX; bytes[++idx] = (time_low >>> 16) & UINT8_MAX; bytes[++idx] = (time_low >>> 8) & UINT8_MAX; bytes[++idx] = (time_low >>> 0) & UINT8_MAX; bytes[++idx] = (time_high >>> 8) & UINT8_MAX; bytes[++idx] = (time_high >>> 0) & UINT8_MAX; bytes[++idx] = (time_high >>> 24) & UINT8_MAX; bytes[++idx] = (time_high >>> 16) & UINT8_MAX; } class Uuid1 extends Uuid { static get VARIANT() { return 1 } static get VERSION() { return 1 } static reset() { _clock_sequence = null; _hires_time = -1; _previous_time = -1; } //Constructors static generate({ node, time } = {}) { time = EpochConverter.toEpoch(EPOCH_ORIGIN_MS, time); let bytes = ByteArray.generateZeroFilled(); setTime(time, bytes); setClockSequence(time, bytes); setNode(node || Machine.mac_address, bytes); setVariant(this.VARIANT, bytes); setVersion(this.VERSION, bytes); if (time > _previous_time) { _previous_time = time; } return new this(bytes); } // Accessors get clock_sequence() { return ( 0 | this.bytes[CLOCK_SEQUENCE_OFFSET] << 8 | this.bytes[CLOCK_SEQUENCE_OFFSET + 1] ) & (CLOCK_SEQUENCE_RADIX - 1); } get hires_time() { return ( 0 | this.bytes[HIRES_TIME_OFFSET] << 8 | this.bytes[HIRES_TIME_OFFSET + 1] ) & (HIRES_TIME_RADIX - 1); } get node() { return this.bytes.slice(NODE_OFFSET); } get time() { let idx = TIME_OFFSET - 1; const time_low_ms = 0 | (this.bytes[++idx] << 12) | (this.bytes[++idx] << 4) | (this.bytes[++idx] >>> 4); ++idx; // Skip hi-res bits const time_high = 0 | (this.bytes[++idx] << 8) | (this.bytes[++idx] << 0) | ((this.bytes[++idx] & 0x0F) << 24) | (this.bytes[++idx] << 16); const epoch_ms = time_high * TIME_LOW_MS_RADIX + time_low_ms; return EpochConverter.fromEpoch(EPOCH_ORIGIN_MS, epoch_ms); }; } Uuid1.reset(); module.exports = { Uuid1 }; ================================================ FILE: src/id/uuid-4.js ================================================ 'use strict'; const ByteArray = require('../common/byte-array'); const { Uuid, setVariant, setVersion, } = require('./uuid'); class Uuid4 extends Uuid { static get VARIANT() { return 1 } static get VERSION() { return 4 } static generate() { let bytes = ByteArray.generateRandomFilled(); setVariant(this.VARIANT, bytes); setVersion(this.VERSION, bytes); return new this(bytes); } } module.exports = { Uuid4 }; ================================================ FILE: src/id/uuid-6.js ================================================ 'use strict'; const ByteArray = require('../common/byte-array'); const EpochConverter = require('../common/epoch-converter'); const Machine = require('../common/machine'); const { Uuid, setVariant, setVersion, } = require('./uuid'); const TIME_OFFSET = 0; const HIRES_TIME_OFFSET = 6; const CLOCK_SEQUENCE_OFFSET = 8; const NODE_OFFSET = 10; const CLOCK_SEQUENCE_RADIX = Math.pow(2, 14); const EPOCH_ORIGIN_MS = Date.parse('1582-10-15Z'); const HIRES_TIME_RADIX = Math.pow(2, 12); const UINT32_RADIX = Math.pow(2, 32); const UINT8_MAX = 0b11111111; let _clock_sequence; let _hires_time; let _previous_time; function incrementClockSequence() { _clock_sequence = (_clock_sequence + 1) % CLOCK_SEQUENCE_RADIX; } function setClockSequence(time, bytes) { if (_clock_sequence === null) { const random_bytes = ByteArray.generateRandomFilled(); _clock_sequence = ( 0 | random_bytes[CLOCK_SEQUENCE_OFFSET + 0] << 8 | random_bytes[CLOCK_SEQUENCE_OFFSET + 1] << 0 ) % CLOCK_SEQUENCE_RADIX; } else if (time < _previous_time) { incrementClockSequence(); } let idx = CLOCK_SEQUENCE_OFFSET - 1; bytes[++idx] = (_clock_sequence >>> 8) & UINT8_MAX; bytes[++idx] = (_clock_sequence >>> 0) & UINT8_MAX; } function setNode(node, bytes) { for (let idx = 0; idx < 6; ++idx) { bytes[NODE_OFFSET + idx] = node[idx]; } } function setTime(time, bytes) { _hires_time = time > _previous_time ? 0 : _hires_time + 1; if (_hires_time === HIRES_TIME_RADIX) { _hires_time = 0; incrementClockSequence(); } const time_low = time % UINT32_RADIX; const time_high = (time - time_low) / UINT32_RADIX; let idx = TIME_OFFSET - 1; bytes[++idx] = (time_high >>> 8) & UINT8_MAX; bytes[++idx] = (time_high >>> 0) & UINT8_MAX; bytes[++idx] = (time_low >>> 24) & UINT8_MAX; bytes[++idx] = (time_low >>> 16) & UINT8_MAX; bytes[++idx] = (time_low >>> 8) & UINT8_MAX; bytes[++idx] = (time_low >>> 0) & UINT8_MAX; bytes[++idx] = (_hires_time >>> 8) & UINT8_MAX; bytes[++idx] = (_hires_time >>> 0) & UINT8_MAX; } class Uuid6 extends Uuid { static get VARIANT() { return 1 } static get VERSION() { return 6 } static reset() { _clock_sequence = null; _hires_time = -1; _previous_time = -1; } //Constructors static generate({ node, time } = {}) { time = EpochConverter.toEpoch(EPOCH_ORIGIN_MS, time); let bytes = ByteArray.generateZeroFilled(); setTime(time, bytes); setClockSequence(time, bytes); setNode(node || Machine.mac_address, bytes); setVariant(this.VARIANT, bytes); setVersion(this.VERSION, bytes); if (time > _previous_time) { _previous_time = time; } return new this(bytes); } // Accessors get clock_sequence() { return ( 0 | this.bytes[CLOCK_SEQUENCE_OFFSET] << 8 | this.bytes[CLOCK_SEQUENCE_OFFSET + 1] ) & (CLOCK_SEQUENCE_RADIX - 1); } get hires_time() { return ( 0 | this.bytes[HIRES_TIME_OFFSET] << 8 | this.bytes[HIRES_TIME_OFFSET + 1] ) & (HIRES_TIME_RADIX - 1); } get node() { return this.bytes.slice(NODE_OFFSET); } get time() { let idx = TIME_OFFSET - 1; const time_high = 0 | (this.bytes[++idx] << 8) | (this.bytes[++idx] << 0); const time_low = 0 | (this.bytes[++idx] << 24) | (this.bytes[++idx] << 16) | (this.bytes[++idx] << 8) | (this.bytes[++idx] << 0); const epoch_ms = (time_high * UINT32_RADIX) + (time_low >>> 0); return EpochConverter.fromEpoch(EPOCH_ORIGIN_MS, epoch_ms); }; } Uuid6.reset(); module.exports = { Uuid6 }; ================================================ FILE: src/id/uuid-nil.js ================================================ 'use strict'; const ByteArray = require('../common/byte-array'); const { Uuid, setVariant, setVersion, } = require('./uuid'); class UuidNil extends Uuid { static get VARIANT() { return 0 } static get VERSION() { return 0 } static generate() { let bytes = ByteArray.generateZeroFilled(); setVariant(this.VARIANT, bytes); setVersion(this.VERSION, bytes); return new this(bytes); } static MAX() { let bytes = ByteArray.generateZeroFilled(); setVariant(this.VARIANT, bytes); setVersion(this.VERSION, bytes); return new this(bytes); } } module.exports = { UuidNil }; ================================================ FILE: src/id/uuid.js ================================================ 'use strict'; const { BaseId } = require('./base'); const ByteArray = require('../common/byte-array'); const VARIANT_BYTE = 8; const VERSION_BYTE = 6; function setVariant(variant, bytes) { bytes[VARIANT_BYTE] &= 0b11111111 >>> (variant + 1); bytes[VARIANT_BYTE] |= ((0b111 << (3 - variant)) & 0b111) << 5; }; function setVersion(version, bytes) { bytes[VERSION_BYTE] &= 0b00001111; bytes[VERSION_BYTE] |= version << 4; }; class Uuid extends BaseId { static MIN() { let bytes = ByteArray.generateZeroFilled(); setVariant(this.VARIANT, bytes); setVersion(this.VERSION, bytes); return new this(bytes); } static MAX() { let bytes = ByteArray.generateOneFilled(); setVariant(this.VARIANT, bytes); setVersion(this.VERSION, bytes); return new this(bytes); } get variant() { const bits = this.bytes[VARIANT_BYTE] >>> 5; return ( bits === 0b111 ? 3 : (bits & 0b110) === 0b110 ? 2 : (bits & 0b100) === 0b100 ? 1 : 0 ); } get version() { return this.bytes[VERSION_BYTE] >>> 4; } } module.exports = { Uuid, setVersion, setVariant, }; ================================================ FILE: test/coder/crockford32.js ================================================ 'use strict'; const { expect } = require('chai'); const { ALPHABET, assertDecode, assertEncode, describeNamespace, makeBytes, makeString, } = require('./shared'); const { InvalidEncoding } = require('common/exception'); const described_namespace = require('coder/crockford32'); const encoding_any = '' + makeString(1, '01234567') + makeString(25, ALPHABET.CROCKFORD32); const encoding_max = '7' + makeString(25, 'Z'); const encoding_min = makeString(26, '0'); describe(describeNamespace(described_namespace, encoding_any), function() { assertDecode({ described_namespace, encoding_any, encoding_max, encoding_min, }); describe('.decode', function() { const subject = described_namespace.decode.bind(described_namespace); it('requires a 26-character Crockford32 string', function() { [ subject, subject.bind(null, makeBytes(26)), subject.bind(null, encoding_any.slice(0, -1)), subject.bind(null, encoding_any + makeString(1, ALPHABET.CROCKFORD32)), subject.bind(null, makeString(25, ALPHABET.ASCII) + '!'), ].forEach(expectation => expect(subject).to.throw(InvalidEncoding)); expect(subject.bind(null, encoding_any)).not.to.throw(); }); }); describe('.decodeTrusted extended', function() { const subject = described_namespace.decodeTrusted.bind(described_namespace); it('ignores case', function() { const encoding = makeString(1, '01234567') + makeString(25, ALPHABET.CROCKFORD32 + ALPHABET.CROCKFORD32.toLowerCase()); expect(subject(encoding)).to.deep.equal(subject(encoding.toUpperCase())); expect(subject(encoding)).to.deep.equal(subject(encoding.toLowerCase())); }); it('converts visually similar characters', function() { const encoding = encoding_any.slice(0, -1); const conversions = [ ['i', '1'], ['I', '1'], ['l', '1'], ['L', '1'], ['o', '0'], ['O', '0'], ['u', 'V'], ['U', 'V'], ]; conversions.forEach(([character, replacement]) => { expect(subject(encoding + character), `Failed to convert ${character}`) .to.deep.equal(subject(encoding + replacement)); }); }); }); assertEncode({ described_namespace, encoding_any, encoding_max, encoding_min, }); }); ================================================ FILE: test/coder/hex.js ================================================ 'use strict'; const { expect } = require('chai'); const { ALPHABET, assertDecode, assertEncode, describeNamespace, makeBytes, makeString, } = require('./shared'); const { InvalidEncoding } = require('common/exception'); const described_namespace = require('coder/hex'); const encoding_any = makeString(32, ALPHABET.HEX); const encoding_max = makeString(32, 'F'); const encoding_min = makeString(32, '0'); describe(describeNamespace(described_namespace, encoding_any), function() { assertDecode({ described_namespace, encoding_any, encoding_max, encoding_min, }); describe('.decode', function() { const subject = described_namespace.decode.bind(described_namespace); it('requires a 32-character hex string', function() { [ subject, subject.bind(null, makeBytes(32)), subject.bind(null, encoding_any.slice(0, -1)), subject.bind(null, encoding_any + makeString(1, ALPHABET.HEX)), subject.bind(null, makeString(31, ALPHABET.ASCII) + '\0'), ].forEach(expectation => expect(subject).to.throw(InvalidEncoding)); expect(subject.bind(null, encoding_any)).not.to.throw(); }); }); describe('.decodeTrusted extended', function() { const subject = described_namespace.decodeTrusted.bind(described_namespace); it('ignores case', function() { const encoding = makeString(32, ALPHABET.HEX + ALPHABET.HEX.toLowerCase()); expect(subject(encoding)).to.deep.equal(subject(encoding.toUpperCase())); expect(subject(encoding)).to.deep.equal(subject(encoding.toLowerCase())); }); }); assertEncode({ described_namespace, encoding_any, encoding_max, encoding_min, }); }); ================================================ FILE: test/coder/shared.js ================================================ 'use strict'; const { expect } = require('chai'); const ByteArray = require('common/byte-array'); const { InvalidBytes, InvalidEncoding, } = require('common/exception'); // Constants const BYTES = Object.freeze({ ANY: ByteArray.generateRandomFilled(), MAX: ByteArray.generateOneFilled(), MIN: ByteArray.generateZeroFilled(), }); const ALPHABET = Object.freeze({ ASCII: Array.from({ length: 128 }, (v, k) => String.fromCharCode(k)).join(''), CROCKFORD32: '0123456789ABCDEFGHJKMNPQRSTVWXYZ', HEX: '0123456789ABCDEF', }); //Helpers function describeNamespace(described_namespace, encoding_any) { return described_namespace.constructor.name + ` (with random encoding ${encoding_any})`; } function makeBytes(length) { return Uint8Array.from({length}); } function makeString(length, alphabet) { const generator = () => randomChar(alphabet); return Array.from({length}, generator).join(''); } function randomChar(alphabet) { const random_idx = Math.floor(alphabet.length * Math.random()); return alphabet.charAt(random_idx); } //Assertions function assertDecode({ described_namespace, encoding_any, encoding_max, encoding_min, } = {}) { describe('.decodeTrusted', function() { const subject = described_namespace.decodeTrusted.bind(described_namespace); it(`decodes ${encoding_min} to all 0-bits`, function() { expect(subject(encoding_min)).to.deep.equal(BYTES.MIN); }); it(`decodes ${encoding_max} to all 1-bits`, function() { expect(subject(encoding_max)).to.deep.equal(BYTES.MAX); }); it('inverts encode', function() { expect(subject(described_namespace.encode(BYTES.ANY))) .to.deep.equal(BYTES.ANY); }); }); } function assertEncode({ described_namespace, encoding_any, encoding_max, encoding_min, } = {}) { describe('.encode', function() { const subject = described_namespace.encode.bind(described_namespace); it('requires a 16-byte Uint8Array', function() { [ subject, subject.bind(null, makeString(16, '\0')), subject.bind(null, makeBytes(15)), subject.bind(null, makeBytes(17)), ].forEach((expectation) => expect(subject) .to.throw(InvalidBytes, 'Requires a 16-byte Uint8Array')); expect(subject.bind(null, BYTES.ANY)).not.to.throw(); }); }); describe('.encodeTrusted', function() { const subject = described_namespace.encodeTrusted.bind(described_namespace); it(`encodes all 0-bits to ${encoding_min}`, function() { expect(subject(BYTES.MIN)).to.equal(encoding_min); }); it(`encodes all 1-bits to ${encoding_max}`, function() { expect(subject(BYTES.MAX)).to.equal(encoding_max); }); it('inverts decode', function() { expect(subject(described_namespace.decode(encoding_any))) .to.equal(encoding_any); }); }); } module.exports = { ALPHABET, assertDecode, assertEncode, describeNamespace, makeBytes, makeString, }; ================================================ FILE: test/coder/uuid.js ================================================ 'use strict'; const { expect } = require('chai'); const { ALPHABET, assertDecode, assertEncode, describeNamespace, makeBytes, makeString, } = require('./shared'); const { InvalidEncoding } = require('common/exception'); const described_namespace = require('coder/uuid'); function makeUuid(alphabet) { return [8, 4, 4, 4, 12].map(len => makeString(len, alphabet)).join('-'); } const encoding_any = makeUuid(ALPHABET.HEX); const encoding_max = makeUuid('F'); const encoding_min = makeUuid('0'); describe(describeNamespace(described_namespace, encoding_any), function() { assertDecode({ described_namespace, encoding_any, encoding_max, encoding_min, }); describe('.decode', function() { const subject = described_namespace.decode.bind(described_namespace); it('requires a 32-character hex string (excluding hyphens)', function() { [ subject, subject.bind(null, makeBytes(32)), subject.bind(null, encoding_any.slice(0, -1)), subject.bind(null, encoding_any + makeString(1, ALPHABET.HEX)), subject.bind(null, makeString(31, ALPHABET.ASCII) + '\0'), ].forEach(expectation => expect(subject).to.throw(InvalidEncoding)); expect(subject.bind(null, encoding_any)).not.to.throw(); }); }); describe('.decodeTrusted extended', function() { const subject = described_namespace.decodeTrusted.bind(described_namespace); it('accepts without hyphens', function() { expect(subject(encoding_any.replace(/-/g, ''))) .to.deep.equal(subject(encoding_any)); }); it('accepts hyphens in even groups of 4', function() { const encoding = encoding_any .replace(/-/g, '') .split(/(.{4})/) .filter(Boolean) .join('-'); expect(subject(encoding)).to.deep.equal(subject(encoding_any)); }); it('ignores case', function() { const encoding = makeUuid(ALPHABET.HEX + ALPHABET.HEX.toLowerCase()); expect(subject(encoding)).to.deep.equal(subject(encoding.toUpperCase())); expect(subject(encoding)).to.deep.equal(subject(encoding.toLowerCase())); }); }); assertEncode({ described_namespace, encoding_any, encoding_max, encoding_min, }); }); ================================================ FILE: test/common/byte-array.js ================================================ 'use strict'; const { expect } = require('chai'); const described_namespace = require('common/byte-array'); function assertByteArray(subject) { it('returns a Uint8Array', function() { expect(subject()).to.be.a('Uint8Array'); }); it('returns 16 bytes', function() { expect(subject()).to.have.lengthOf(16); }); } describe(described_namespace.constructor.name, function() { describe('.compare', function() { const subject = described_namespace.compare; [ ['lhs < rhs', [1], [9], -1], ['lhs = rhs', [5], [5], 0], ['lhs > rhs', [9], [1], 1], ['non-leading lhs < rhs', [5, 9], [5, 3], 1], ['non-leading lhs = rhs', [5, 7], [5, 7], 0], ['non-leading lhs > rhs', [5, 3], [5, 9], -1], ].forEach(([label, lhs, rhs, result]) => { it(`resolves ${label} to ${result}`, function() { expect(subject(lhs, rhs)).to.equal(result); }); }); }); describe('.generateOneFilled', function() { const subject = described_namespace.generateOneFilled; assertByteArray(subject); it('has only one bits', function() { expect(subject().every((val) => val === 0xFF)).to.be.true; }); }); describe('.generateRandomFilled', function() { const subject = described_namespace.generateRandomFilled; assertByteArray(subject); it('has mixed bits', function() { // NOTE: given the nature of random and the bit entropy, // we're guarding against extreme misfortune this.retries(1); const bytes = subject(); expect(bytes.some((val) => val !== 0xFF)).to.be.true; expect(bytes.some((val) => val !== 0)).to.be.true; }); it('almost always has different bits', function() { // NOTE: given the nature of random and the bit entropy, // we're guarding against extreme misfortune this.retries(1); expect(subject()).not.to.deep.equal(subject()); }); it('every call allocates distinct memory', function() { // NOTE: given the nature of random and the bit entropy, // we're guarding against extreme misfortune this.retries(1); const byte_arrays = Array.from({ length: 1000 }) .map(subject) .map(x => x.toString()); expect(new Set(byte_arrays)).to.have.lengthOf(1000); }); }); describe('.generateZeroFilled', function() { const subject = described_namespace.generateZeroFilled; assertByteArray(subject); it('has only zero bits', function() { expect(subject().every((val) => val === 0)).to.be.true; }); }); }); ================================================ FILE: test/common/epoch-converter.js ================================================ 'use strict'; const { expect } = require('chai'); const { InvalidEpoch } = require('common/exception'); const described_namespace = require('common/epoch-converter'); describe(described_namespace.constructor.name, function() { const epoch_origin_ms = Date.parse('1955-11-05Z'); const min_time = 0 + epoch_origin_ms; const max_time = Math.pow(2, 48) - 1 + epoch_origin_ms; const now = Date.now(); describe('.fromEpoch', function() { const subject = (time) => described_namespace.fromEpoch(epoch_origin_ms, time); it('returns a Date', function() { expect(subject(0)).to.be.a('Date'); }); it('returns the time after adjusting for the origin', function() { [ ['min', 0, min_time], ['origin', -epoch_origin_ms, 0], ['now', now, now + epoch_origin_ms], ['max', Math.pow(2, 48) - 1, max_time], ].forEach(([label, epoch_ms, time]) => { expect(subject(epoch_ms).getTime(), label).to.equal(time); }); }); }); describe('.toEpoch', function() { const subject = (time) => described_namespace.toEpoch(epoch_origin_ms, time); it('accepts a Date', function() { [ ['start of epoch time', new Date(min_time), 0], ['origin of epoch time', new Date(0), -epoch_origin_ms], ['current time', new Date(now + epoch_origin_ms), now], ['end of epoch time', new Date(max_time), Math.pow(2, 48) - 1], ].forEach(([label, value, epoch]) => { expect(() => subject(value), label).not.to.throw(); expect(subject(value), label).to.equal(epoch); }); }); it('accepts milliseconds', function() { [ ['start of epoch time', min_time, 0], ['origin of epoch time', 0, -epoch_origin_ms], ['current time', now + epoch_origin_ms, now], ['end of epoch time', max_time, Math.pow(2, 48) - 1], ].forEach(([label, value, epoch]) => { expect(() => subject(value), label).not.to.throw(); expect(subject(value), label).to.equal(epoch); }); }); it('defaults to now for null and undefined', function() { [ ['null', null], ['undefined', void(null)], ].forEach(([label, value]) => { const now = Date.now() - epoch_origin_ms; expect(subject(value), label).to.be.within(now, now + 1); }); }); it('rejects other falsey values', function() { [ ['false', false], ['empty string', ''], ].forEach(([label, value]) => { expect(() => subject(value), label).to.throw(InvalidEpoch); }); }); it('rejects other Date-like values', function() { [ ['date string', '2018-01-10'], ['duck type', { getTime: (() => {}) }], ].forEach(([label, value]) => { expect(() => subject(value), label).to.throw(InvalidEpoch); }); }); it('rejects pre/post-epoch values', function() { [ ['date before epoch time', new Date(min_time - 1)], ['ms before epoch time', min_time - 1], ['date after epoch time', new Date(max_time + 1)], ['ms after epoch time', max_time + 1], ].forEach(([label, value]) => { expect(() => subject(value), label).to.throw(InvalidEpoch); }); }); }); }); ================================================ FILE: test/common/fake-machine.js ================================================ 'use strict'; const { expect } = require('chai'); const described_singleton = require('common/fake-machine'); describe(described_singleton.constructor.name, function() { beforeEach(() => described_singleton.reset()); after(() => described_singleton.reset()); describe('.mac_address', function() { const subject = () => described_singleton.mac_address; it('returns a 6 byte address', function() { expect(subject()).to.be.a('Uint8Array'); expect(subject()).to.have.length(6); }); it('sets the multicast bit', function() { expect(subject()[0]).to.satisfy((num) => (num & 0b00000001)) }); it('returns the same value every time', function() { expect(subject()).to.deep.equal(subject()); }); it('returns a new value after a reset', function() { const original = subject(); described_singleton.reset(); expect(subject()).not.to.deep.equal(original); }); }); }); ================================================ FILE: test/common/machine.js ================================================ 'use strict'; const { expect } = require('chai'); const Os = require('os') || {}; const Sinon = require('sinon'); const ByteArray = require('common/byte-array'); const FakeMachine = require('common/fake-machine'); const described_singleton = require('common/machine'); describe(described_singleton.constructor.name, function() { beforeEach(() => described_singleton.reset()); after(() => described_singleton.reset()); describe('.mac_address', function() { const subject = () => described_singleton.mac_address; function assertFaked() { it('returns a 6 byte address', function() { expect(subject()).to.be.a('Uint8Array'); expect(subject()).to.have.length(6); }); it('sets the multicast bit', function() { expect(subject()[0]).to.satisfy((num) => (num & 0b00000001)) }); it('returns the same value every time', function() { expect(subject()).to.deep.equal(subject()); }); it('returns a new value after a reset', function() { const original = subject(); described_singleton.reset(); expect(subject()).not.to.deep.equal(original); }); } let stubbed_os; before(() => (stubbed_os = Sinon.stub(Os, 'networkInterfaces'))); beforeEach(() => stubbed_os.reset()); after(() => stubbed_os.restore()); it('detects a hardware mac address', function() { stubbed_os.callThrough(); let is_supported = false; try { if (ByteArray.compare(subject(), FakeMachine.mac_address)) { is_supported = true; } } catch (_err) {} if (! is_supported) { this.skip(); } }); context('when the network interfaces are unavailable', function() { beforeEach(() => stubbed_os.returns([])); assertFaked(); }); context('when the network interfaces are available', function() { let interfaces; beforeEach(() => stubbed_os.returns(interfaces)); context('when the interface is internal', function() { before(() => (interfaces = { lo: [{ address: '127.0.0.1', netmask: '255.0.0.0', family: 'IPv4', mac: '00:00:00:00:00:00', internal: true, cidr: '127.0.0.1/8' }] })); assertFaked(); }); context('when the interface is missing a mac', function() { before(() => (interfaces = { wierd0: [{ address: '192.168.1.108', netmask: '255.255.255.0', family: 'IPv4', mac: '', internal: false, cidr: '192.168.1.108/24' }] })); assertFaked(); }); context('when the interface is external and valid', function() { before(() => (interfaces = { eth0: [{ address: '192.168.1.108', netmask: '255.255.255.0', family: 'IPv4', mac: '01:02:03:0a:0b:0c', internal: false, cidr: '192.168.1.108/24' }] })); it('returns a 6 byte address', function() { expect(subject()).to.be.a('Uint8Array'); expect(subject()).to.have.length(6); }); it('returns the same value every time', function() { expect(subject()).to.deep.equal(subject()); }); it('return the same value after a reset', function() { const original = subject(); described_singleton.reset(); expect(subject()).to.deep.equal(original); }); }); }); }); }); ================================================ FILE: test/factory/id.js ================================================ 'use strict'; const { expect } = require('chai'); const { IdFactory: described_class } = require('factory/id'); const id_class = class { static generate() { return new this(`id_${Date.now()}`) } static MIN() { return new this('\x00') } static MAX() { return new this('\xFF') } constructor(value) { this._bytes = value } get bytes() { return this._bytes } }; const factory = new described_class({ id: id_class, canonical_coder: { encode: (bytes) => `canonical ${bytes}`, encodeTrusted: (bytes) => `canonical ${bytes}`, decode: (str) => str.replace(/^canonical(?:_distrusted)? /, ''), decodeTrusted: (str) => str.replace(/^canonical /, ''), isValidEncoding: (str) => /^canonical /.test(str), }, raw_coder: { encode: (bytes) => `raw ${bytes}`, encodeTrusted: (bytes) => `raw ${bytes}`, decode: (str) => str.replace(/^raw(?:_distrusted)? /, ''), decodeTrusted: (str) => str.replace(/^raw /, ''), isValidEncoding: (str) => /^raw /.test(str), }, }); function assertDecoder(method, encoding) { describe(`#${method}`, function() { const subject = () => factory[method](encoding); it('returns an id', function() { expect(subject()).to.be.an.instanceOf(id_class); }); assertInjectsInstanceMethod('toCanonical', subject); assertInjectsInstanceMethod('toRaw', subject); }); } function assertEncoder(method, pattern) { describe(`#${method}`, function() { const subject = () => factory[method](factory.generate()); it('encodes the bytes of the id', function() { expect(subject()).to.match(pattern); }); }); } function assertGenerator(method) { describe(`#${method}`, function() { const subject = () => factory[method](); it(`returns an id`, function() { expect(subject()).to.be.an.instanceOf(id_class); }); it('always returns a different object', function() { expect(subject()).not.to.equal(subject()); }); assertInjectsInstanceMethod('toCanonical', subject); assertInjectsInstanceMethod('toRaw', subject); }); } function assertInjectsInstanceMethod(injected_method, generator) { describe(injected_method, function() { it(`is injected into the instance`, function() { const id = generator(); expect(() => id[injected_method]()).not.to.throw(); expect(id[injected_method]()).to.equal(factory[injected_method](id)); }); it('is only injected into the instance', function() { const id = generator(); expect(new id_class()[injected_method]).to.be.undefined; }); }); } function assertVerifier(method, validEncoding) { const conditions = [ ['canonical', factory.generate().toCanonical()], ['raw', factory.generate().toRaw()], ['other', 'some random string'], ]; describe(`#${method}`, function() { const subject = str => factory[method](str); const { true: trueConditions, false: falseConditions, } = conditions.reduce( (partitions, condition) => { const [name] = condition; partitions[validEncoding === name].push(condition); return partitions; }, { true: [], false: [] }, ); it('detects valid encodings', function() { trueConditions.forEach(([name, encoding]) => expect(subject(encoding), name).to.be.true); }); it('rejects invalid encodings', function() { falseConditions.forEach(([name, encoding]) => expect(subject(encoding), name).to.be.false); }); }); } describe(described_class.name, function() { describe('#construct', function() { const subject = () => factory.construct('some bytes'); it('returns an id', function() { expect(subject()).to.be.an.instanceOf(id_class); }); it('directly stores the bytes', function() { expect(subject()).to.have.property('bytes', 'some bytes'); }); }); describe('#name', function() { const subject = () => factory.name; it('returns the name of the id generated by the factory', function() { expect(subject()).to.equal(id_class.name); }); }); describe('#type', function() { const subject = () => factory.type; it('returns the class of the id generated by the factory', function() { expect(subject()).to.equal(id_class); }); it('provides support for the instanceof operator', function() { expect(factory.generate()).to.be.an.instanceOf(subject()) }); }); assertGenerator('generate'); assertGenerator('MIN'); assertGenerator('MAX'); assertDecoder('fromCanonical', 'canonical_distrusted some_id'); assertDecoder('fromCanonicalTrusted', 'canonical some_id'); assertDecoder('fromRaw', 'raw_distrusted some_id'); assertDecoder('fromRawTrusted', 'raw some_id'); assertEncoder('toCanonical', /^canonical id_\d+$/); assertEncoder('toRaw', /^raw id_\d+$/); assertVerifier('isCanonical', 'canonical'); assertVerifier('isRaw', 'raw'); }); ================================================ FILE: test/factory/versioned-id.js ================================================ 'use strict'; const { expect } = require('chai'); const { UnsupportedVersion } = require('common/exception'); const { VersionedIdFactory: described_class } = require('factory/versioned-id'); const buildIdClass = (version) => class { static get VERSION() { return version } static generate() { return new this(`id_${Date.now()} vrsn:${this.VERSION}`) } static MIN() { return new this(`\x00 vrsn:${this.VERSION}`) } static MAX() { return new this(`\xFF vrsn:${this.VERSION}`) } constructor(value) { this._bytes = value } get bytes() { return this._bytes } get version() { return /vrsn:(\w+)/.exec(this._bytes)[1] } }; const version = 'VER'; const abstract_id_class = buildIdClass(undefined); const versioned_id_class = buildIdClass(version); const versioned_id_classes = [ buildIdClass('OTHER1'), versioned_id_class, buildIdClass('OTHER2'), ]; const factory = new described_class({ abstract_id: abstract_id_class, versioned_ids: versioned_id_classes, canonical_coder: { encode: (bytes) => `canonical ${bytes}`, encodeTrusted: (bytes) => `canonical ${bytes}`, decode: (str) => str.replace(/^canonical(?:_distrusted)? /, ''), decodeTrusted: (str) => str.replace(/^canonical /, ''), }, raw_coder: { encode: (bytes) => `raw ${bytes}`, encodeTrusted: (bytes) => `raw ${bytes}`, decode: (str) => str.replace(/^raw(?:_distrusted)? /, ''), decodeTrusted: (str) => str.replace(/^raw /, ''), }, }); function assertDecoder(method, encoding) { describe(`#${method}`, function() { const subject = () => factory[method](encoding); it('returns a versioned id', function() { expect(subject()).to.be.an.instanceOf(versioned_id_class); }); it('returns an abstract id when the version is unsupported', function() { expect(factory[method](encoding.replace(version, 'IDUNNO'))) .to.be.an.instanceOf(abstract_id_class); }); assertInjectsInstanceMethod('toCanonical', subject); assertInjectsInstanceMethod('toRaw', subject); }); } function assertEncoder(method, pattern) { describe(`#${method}`, function() { const subject = () => factory[method](factory.generate({ version })); it('encodes the bytes of the id', function() { expect(subject()).to.match(pattern); }); }); } function assertGenerator(method) { describe(`#${method}`, function() { const subject = () => factory[method]({ version }); it(`returns a versioned id`, function() { expect(subject()).to.be.an.instanceOf(versioned_id_class); }); it('always returns a different object', function() { expect(subject()).not.to.equal(subject()); }); it('throws without a version', function() { expect(() => factory[method]()).to.throw(UnsupportedVersion); }); assertInjectsInstanceMethod('toCanonical', subject); assertInjectsInstanceMethod('toRaw', subject); }); } function assertInjectsInstanceMethod(injected_method, generator) { describe(injected_method, function() { it(`is injected into the instance`, function() { const id = generator(); expect(() => id[injected_method]()).not.to.throw(); expect(id[injected_method]()).to.equal(factory[injected_method](id)); }); it('is only injected into the instance', function() { const id = generator(); expect(new versioned_id_class()[injected_method]).to.be.undefined; }); }); } describe(described_class.name, function() { describe('#construct', function() { const subject = () => factory.construct(`some bytes vrsn:${version}`); it('returns a versioned id', function() { expect(subject()).to.be.an.instanceOf(versioned_id_class); }); it('directly stores the bytes', function() { expect(subject()).to.have.property('bytes', `some bytes vrsn:${version}`); }); it('returns an abstract id when the version is unsupported', function() { expect(factory.construct('some bytes vrsn:IDUNNO')) .to.be.an.instanceOf(abstract_id_class); }); }); describe('#name', function() { const subject = () => factory.name; it('returns the name of the abstract id for the factory', function() { expect(subject()).to.equal(abstract_id_class.name); }); }); describe('#versioned_ids', function() { const subject = () => factory.versioned_ids; it('returns all the versioned id classes the factory can generate', function() { expect(subject().map(id => id.name)) .to.have.members(versioned_id_classes.map(id => id.name)); }); }); assertGenerator('generate'); assertGenerator('MIN'); assertGenerator('MAX'); assertDecoder('fromCanonical', `canonical_distrusted some_id vrsn:${version}`); assertDecoder('fromCanonicalTrusted', `canonical some_id vrsn:${version}`); assertDecoder('fromRaw', `raw_distrusted some_id vrsn:${version}`); assertDecoder('fromRawTrusted', `raw some_id vrsn:${version}`); assertEncoder('toCanonical', new RegExp(`^canonical id_\\d+ vrsn:${version}$`)); assertEncoder('toRaw', new RegExp(`^raw id_\\d+ vrsn:${version}$`)); }); ================================================ FILE: test/id/shared.js ================================================ 'use strict'; const { expect } = require('chai'); const ByteArray = require('common/byte-array'); const Machine = require('common/machine'); const extractId = ([_, id]) => id; const extractLabel = ([label, _]) => label; function assertAccessorBytes(described_class) { describe('#bytes', function() { const subject = (bytes) => new described_class(bytes).bytes; it('returns the bytes given to the constructor', function() { [ ByteArray.generateZeroFilled(), ByteArray.generateRandomFilled(), ByteArray.generateOneFilled(), ].forEach((bytes) => expect(subject(bytes)).to.deep.equal(bytes)); }); }); } function assertAccessorNode(described_class) { describe('#node', function() { const subject = (node) => described_class.generate({ node }).node; it('returns the mac address by default', function() { expect(subject()).to.deep.equal(Machine.mac_address); }); it('returns the supplied node', function() { const node = Uint8Array.of(1, 2, 3, 4, 5, 6); expect(subject(node)).to.deep.equal(node); }); }); } function assertAccessorTime(described_class, labeled_times) { describe('#time', function() { const subject = (time) => described_class.generate({ time }).time; it('returns the time given to generate', function() { labeled_times.forEach(([label, time]) => { expect(subject(time), label).to.deep.equal(time) }); }); }); } function assertCompareDemonstratesTotalOrder(labeled_ids) { describe('#compare', function() { const diagnose = (lhs, rhs) => `(${lhs}).compare(${rhs})`; labeled_ids.forEach(([lhs_label, lhs_id], lhs_idx) => { const subject = (other) => lhs_id.compare(other.clone()); const prev_ids = labeled_ids.filter((_, idx) => (idx < lhs_idx)); const next_ids = labeled_ids.filter((_, idx) => (idx > lhs_idx)); describe(`given ${lhs_label}`, function() { if (lhs_idx === labeled_ids.length - 1) { it('has no subsequent ids', function() { expect(next_ids, `${lhs_label} should be the last id`).to.be.empty; }); } else { it('returns -1 for all subsequent ids', function() { next_ids.forEach(([label, id]) => { expect(subject(id), diagnose(lhs_label, label)).to.equal(-1); }); }); } it('returns 0 for itself', function() { const [label, id] = labeled_ids[lhs_idx]; expect(subject(id), label).to.equal(0); }); if (lhs_idx === 0) { it('has no previous ids', function() { expect(prev_ids, `${lhs_label} should be the first id`).to.be.empty; }); } else { it('returns 1 for all previous ids', function() { prev_ids.forEach(([label, id]) => { expect(subject(id), diagnose(lhs_label, label)).to.equal(1); }); }); } }); }); }); } function assertDebuggable(described_class) { describe('when cast as a string', function() { const subject = () => '' + new described_class(); it(`mentions the type ${described_class.name}`, function() { expect(subject()).to.contain.string(described_class.name); }); }); } function assertEqualDemonstratesSameness(labeled_ids) { describe('#equal', function() { const diagnose = (lhs, rhs) => `(${lhs}).equal(${rhs})`; labeled_ids.forEach(([lhs_label, lhs_id]) => { const subject = (other) => lhs_id.equal(other.clone()); describe(`given ${lhs_label}`, function() { it('returns true for itself', function() { expect(subject(lhs_id)).to.be.true; }); it('returns false for all others', function() { labeled_ids .filter((pair) => extractId(pair) !== lhs_id) .forEach(([label, id]) => { expect(subject(id), diagnose(lhs_label, label)).to.be.false }); }); }); }); }); } function assertGenerateBasics(described_class) { describe('.generate', function() { const subject = () => described_class.generate(); it(`returns a new ${described_class.name}`, function() { expect(subject()).to.be.an.instanceOf(described_class); }); it(`returns an id with different bytes each time`, function() { // NOTE: given the nature of random and the bit entropy, // we're guarding against extreme misfortune this.retries(2); expect(subject().bytes).not.to.deep.equal(subject().bytes); }); }); } function assertUuidVariantVersion(described_class, variant, version) { const assertVariant = (subject) => it(`is variant ${variant}`, function() { expect(subject().variant).to.eql(variant); }); const assertVersion = (subject) => it(`is version ${version}`, function() { expect(subject().version).to.eql(version); }); describe('.generate variant/version', function() { const subject = () => described_class.generate(); assertVariant(subject); assertVersion(subject); }); describe('.MIN variant/version', function() { const subject = () => described_class.MIN(); assertVariant(subject); assertVersion(subject); }); describe('.MAX variant/version', function() { const subject = () => described_class.MAX(); assertVariant(subject); assertVersion(subject); }); } module.exports = { assertAccessorBytes, assertAccessorNode, assertAccessorTime, assertCompareDemonstratesTotalOrder, assertDebuggable, assertEqualDemonstratesSameness, assertGenerateBasics, assertUuidVariantVersion, }; ================================================ FILE: test/id/ulid-monotonic.js ================================================ 'use strict'; const { expect } = require('chai'); const { assertAccessorBytes, assertCompareDemonstratesTotalOrder, assertDebuggable, assertEqualDemonstratesSameness, assertGenerateBasics, } = require('./shared'); const ByteArray = require('common/byte-array'); const { ClockSequenceOverflow, InvalidEpoch, } = require('common/exception'); const { UlidMonotonic: described_class } = require('id/ulid-monotonic'); const MAX_TIME = new Date(Math.pow(2, 48) - 1); const MIN_TIME = new Date(0); describe(described_class.name, function() { beforeEach(() => described_class.reset()); after(() => described_class.reset()); assertDebuggable(described_class); assertGenerateBasics(described_class); describe('.generate extended', function() { const subject = (time) => described_class.generate({ time }); it('accepts epoch values', function() { [ ['start of epoch', MIN_TIME], ['end of epoch', MAX_TIME], ].forEach(([label, value]) => { expect(() => subject(value), label).not.to.throw(InvalidEpoch); }); }); it('rejects pre/post-epoch values', function() { [ ['prior to 1970', MIN_TIME - 1], ['after late 10889', MAX_TIME + 1], ].forEach(([label, value]) => { expect(() => subject(value), label).to.throw(InvalidEpoch); }); }); it('throws when the clock sequence overflows', function() { const overflow = 0x10001; let sequence = 0; subject(Date.now() + 24 * 60 * 60 * 1000); expect(() => { for (; sequence <= overflow; ++sequence) { subject(); } }).to.throw(ClockSequenceOverflow); expect(sequence).to.be.above(overflow >> 1); expect(sequence).to.be.below(overflow); }); }); describe('.MIN', function() { const subject = () => described_class.MIN(); it('has all 0-bits', function() { expect(subject().bytes).to.deep.equal(ByteArray.generateZeroFilled()); }); it('has the least allowed time', function() { expect(subject().time).to.deep.equal(MIN_TIME); }); it('ignores monotonicity', function() { subject(); expect(subject().bytes).to.deep.equal(ByteArray.generateZeroFilled()); }); }); describe('.MAX', function() { const subject = () => described_class.MAX(); it('has all 1-bits', function() { expect(subject().bytes).to.deep.equal(ByteArray.generateOneFilled()); }); it('has the greatest allowed time', function() { expect(subject().time).to.deep.equal(MAX_TIME); }); it('ignores monotonicity', function() { subject(); expect(subject().bytes).to.deep.equal(ByteArray.generateOneFilled()); }); }); assertAccessorBytes(described_class); describe('#time', function() { const subject = (time) => described_class.generate({ time }).time; describe('given a future time', function() { it('returns the time given to generate', function() { [ ['min', MIN_TIME], ['now', new Date()], ['max', MAX_TIME], ].forEach(([label, time]) => { expect(subject(time), label).to.deep.equal(time); }); }); }); describe('given a past time', function() { let most_recent_time; beforeEach(() => (most_recent_time = subject(MAX_TIME))); it('returns the same time as the most recent id', function() { [ ['min', MIN_TIME], ['now', new Date()], ['max', MAX_TIME], ].forEach(([label, time]) => { expect(subject(time), label).to.deep.equal(most_recent_time); }); }); }); }); assertCompareDemonstratesTotalOrder([ ['the min id', described_class.MIN()], ['a min time id', described_class.generate({ time: MIN_TIME })], ['a recent id', described_class.generate({ time: new Date })], ['a max time id', described_class.generate({ time: MAX_TIME })], ['an anachronistic id', described_class.generate({ time: new Date })], ['the max id', described_class.MAX()], ]); assertEqualDemonstratesSameness([ ['the min id', described_class.MIN()], ['a min time id', described_class.generate({ time: MIN_TIME })], ['a recent id', described_class.generate({ time: new Date() })], ['a max time id', described_class.generate({ time: MAX_TIME })], ['an anachronistic id', described_class.generate({ time: new Date() })], ['the max id', described_class.MAX()], ]); }); ================================================ FILE: test/id/ulid.js ================================================ 'use strict'; const { expect } = require('chai'); const { assertAccessorBytes, assertAccessorTime, assertCompareDemonstratesTotalOrder, assertDebuggable, assertEqualDemonstratesSameness, assertGenerateBasics, } = require('./shared'); const ByteArray = require('common/byte-array'); const { InvalidEpoch } = require('common/exception'); const { Ulid: described_class } = require('id/ulid');; const MAX_TIME = new Date(Math.pow(2, 48) - 1); const MIN_TIME = new Date(0); describe(described_class.name, function() { assertDebuggable(described_class); assertGenerateBasics(described_class); describe('.generate extended', function() { const subject = (time) => described_class.generate({ time }); it('accepts epoch values', function() { [ ['start of epoch', MIN_TIME], ['end of epoch', MAX_TIME], ].forEach(([label, value]) => { expect(() => subject(value), label).not.to.throw(InvalidEpoch); }); }); it('rejects pre/post-epoch values', function() { [ ['prior to 1970', MIN_TIME - 1], ['after late 10889', MAX_TIME + 1], ].forEach(([label, value]) => { expect(() => subject(value), label).to.throw(InvalidEpoch); }); }); }); describe('.MIN', function() { const subject = () => described_class.MIN(); it('has all 0-bits', function() { expect(subject().bytes).to.deep.equal(ByteArray.generateZeroFilled()); }); it('has the least allowed time', function() { expect(subject().time).to.deep.equal(MIN_TIME); }); }); describe('.MAX', function() { const subject = () => described_class.MAX(); it('has all 1-bits', function() { expect(subject().bytes).to.deep.equal(ByteArray.generateOneFilled()); }); it('has the greatest allowed time', function() { expect(subject().time).to.deep.equal(MAX_TIME); }); }); assertAccessorBytes(described_class); assertAccessorTime(described_class, [ ['min', MIN_TIME], ['now', new Date()], ['max', MAX_TIME], ]); assertCompareDemonstratesTotalOrder([ ['the min id', described_class.MIN()], ['a min time id', described_class.generate({ time: MIN_TIME })], ['a recent id', described_class.generate({ time: new Date })], ['a max time id', described_class.generate({ time: MAX_TIME })], ['the max id', described_class.MAX()], ]); assertEqualDemonstratesSameness([ ['the min id', described_class.MIN()], ['a min time id', described_class.generate({ time: MIN_TIME })], ['a recent id', described_class.generate({ time: new Date })], ['a max time id', described_class.generate({ time: MAX_TIME })], ['the max id', described_class.MAX()], ]); }); ================================================ FILE: test/id/uuid-1.js ================================================ 'use strict'; const { expect } = require('chai'); const { assertAccessorBytes, assertAccessorNode, assertAccessorTime, assertCompareDemonstratesTotalOrder, assertDebuggable, assertEqualDemonstratesSameness, assertGenerateBasics, assertUuidVariantVersion, } = require('./shared'); const ByteArray = require('common/byte-array'); const { InvalidEpoch } = require('common/exception'); const { Uuid1: described_class } = require('id/uuid-1'); const ORIGIN = Date.parse('1582-10-15Z'); const MAX_TIME = new Date(Math.pow(2, 48) - 1 + ORIGIN); const MIN_TIME = new Date(ORIGIN); describe(described_class.name, function() { beforeEach(() => described_class.reset()); after(() => described_class.reset()); assertDebuggable(described_class); assertGenerateBasics(described_class); describe('.generate extended', function() { const CLOCK_MAX = 0x3FFF; const HIRES_MAX = 0x0FFF; const subject = (time) => described_class.generate({ time }); it('accepts epoch values', function() { [ ['start of epoch', MIN_TIME], ['end of epoch', MAX_TIME], ].forEach(([label, value]) => { expect(() => subject(value), label).not.to.throw(InvalidEpoch); }); }); it('rejects pre/post-epoch values', function() { [ ['prior to Gregorian calendar', MIN_TIME - 1], ['after late 10502', MAX_TIME + 1], ].forEach(([label, value]) => { expect(() => subject(value), label).to.throw(InvalidEpoch); }); }); context('when given a time in the past', function() { it('increments the hires timer', function() { const previous_id = subject(); expect(subject(MIN_TIME).hires_time) .to.equal(previous_id.hires_time + 1); }); it('increments the clock sequence', function() { // For the minor chance that the clock sequence starts at the top this.retries(1); const previous_id = subject(); expect(subject(MIN_TIME).clock_sequence) .to.equal(previous_id.clock_sequence + 1); }); context('when the clock sequence overflows', function() { function clockOverflow(time) { for (let count = 0; count < CLOCK_MAX; ++count) { subject(time); } } it('resets the clock sequence', function() { subject(); clockOverflow(MIN_TIME); expect(subject(MIN_TIME).hires_time).to.equal(0); }); }); }); context('when given the same time', function() { const time = Date.now(); it('increments the hires timer', function() { const previous_id = subject(time); expect(subject(time).hires_time).to.equal(previous_id.hires_time + 1); }); context('when the hires timer overflows', function() { function hiresOverflow(time) { for (let count = 0; count < HIRES_MAX; ++count) { subject(time); } } it('resets the hires timer', function() { subject(); hiresOverflow(time); expect(subject(time).hires_time).to.equal(0); }); it('increments the clock sequence', function() { // For the minor chance that the clock sequence starts at the top this.retries(1); const previous_id = subject(time); hiresOverflow(time); expect(subject(time).clock_sequence) .to.equal(previous_id.clock_sequence + 1); }); }); }); context('when given a time in the future', function() { it('resets the hires timer', function() { subject(); expect(subject(MAX_TIME).hires_time).to.equal(0); }); it('retains the same clock sequence', function() { const previous_id = subject(); expect(subject(MAX_TIME).clock_sequence) .to.equal(previous_id.clock_sequence); }); }); }); describe('.MIN', function() { const subject = () => described_class.MIN(); it('has all 0-bits other than variant/version', function() { const expected = ByteArray.generateZeroFilled(); expected.set([0b00010000], 6); expected.set([0b10000000], 8); expect(subject().bytes).to.deep.equal(expected); }); }); describe('.MAX', function() { const subject = () => described_class.MAX(); it('has all 1-bits other than variant/version', function() { const expected = ByteArray.generateOneFilled(); expected.set([0b00011111], 6); expected.set([0b10111111], 8); expect(subject().bytes).to.deep.equal(expected); }); }); assertUuidVariantVersion(described_class, 1, 1); assertAccessorBytes(described_class); assertAccessorNode(described_class); assertAccessorTime(described_class, [ ['min', MIN_TIME], ['origin', new Date(0)], ['now', new Date()], ['max', MAX_TIME], ]); assertCompareDemonstratesTotalOrder([ ['the min id', described_class.MIN()], ['a random id', described_class.generate()], ['the max id', described_class.MAX()], ]); assertEqualDemonstratesSameness([ ['the min id', described_class.MIN()], ['a random id', described_class.generate()], ['the max id', described_class.MAX()], ]); }); ================================================ FILE: test/id/uuid-4.js ================================================ 'use strict'; const { expect } = require('chai'); const { assertAccessorBytes, assertCompareDemonstratesTotalOrder, assertDebuggable, assertEqualDemonstratesSameness, assertGenerateBasics, assertUuidVariantVersion, } = require('./shared'); const ByteArray = require('common/byte-array'); const { Uuid4: described_class } = require('id/uuid-4'); describe(described_class.name, function() { assertDebuggable(described_class); assertGenerateBasics(described_class); describe('.MIN', function() { const subject = () => described_class.MIN(); it('has all 0-bits other than variant/version', function() { const expected = ByteArray.generateZeroFilled(); expected.set([0b01000000], 6); expected.set([0b10000000], 8); expect(subject().bytes).to.deep.equal(expected); }); }); describe('.MAX', function() { const subject = () => described_class.MAX(); it('has all 1-bits other than variant/version', function() { const expected = ByteArray.generateOneFilled(); expected.set([0b01001111], 6); expected.set([0b10111111], 8); expect(subject().bytes).to.deep.equal(expected); }); }); assertUuidVariantVersion(described_class, 1, 4); assertAccessorBytes(described_class); assertCompareDemonstratesTotalOrder([ ['the min id', described_class.MIN()], ['a random id', described_class.generate()], ['the max id', described_class.MAX()], ]); assertEqualDemonstratesSameness([ ['the min id', described_class.MIN()], ['a random id', described_class.generate()], ['the max id', described_class.MAX()], ]); }); ================================================ FILE: test/id/uuid-6.js ================================================ 'use strict'; const { expect } = require('chai'); const { assertAccessorBytes, assertAccessorNode, assertAccessorTime, assertCompareDemonstratesTotalOrder, assertDebuggable, assertEqualDemonstratesSameness, assertGenerateBasics, assertUuidVariantVersion, } = require('./shared'); const ByteArray = require('common/byte-array'); const { InvalidEpoch } = require('common/exception'); const { Uuid6: described_class } = require('id/uuid-6'); const ORIGIN = Date.parse('1582-10-15Z'); const MAX_TIME = new Date(Math.pow(2, 48) - 1 + ORIGIN); const MIN_TIME = new Date(ORIGIN); describe(described_class.name, function() { beforeEach(() => described_class.reset()); after(() => described_class.reset()); assertDebuggable(described_class); assertGenerateBasics(described_class); describe('.generate extended', function() { const CLOCK_MAX = 0x3FFF; const HIRES_MAX = 0x0FFF; const subject = (time) => described_class.generate({ time }); it('accepts epoch values', function() { [ ['start of epoch', MIN_TIME], ['end of epoch', MAX_TIME], ].forEach(([label, value]) => { expect(() => subject(value), label).not.to.throw(InvalidEpoch); }); }); it('rejects pre/post-epoch values', function() { [ ['prior to Gregorian calendar', MIN_TIME - 1], ['after late 10502', MAX_TIME + 1], ].forEach(([label, value]) => { expect(() => subject(value), label).to.throw(InvalidEpoch); }); }); context('when given a time in the past', function() { it('increments the hires timer', function() { const previous_id = subject(); expect(subject(MIN_TIME).hires_time) .to.equal(previous_id.hires_time + 1); }); it('increments the clock sequence', function() { // For the minor chance that the clock sequence starts at the top this.retries(1); const previous_id = subject(); expect(subject(MIN_TIME).clock_sequence) .to.equal(previous_id.clock_sequence + 1); }); context('when the clock sequence overflows', function() { function clockOverflow(time) { for (let count = 0; count < CLOCK_MAX; ++count) { subject(time); } } it('resets the clock sequence', function() { subject(); clockOverflow(MIN_TIME); expect(subject(MIN_TIME).hires_time).to.equal(0); }); }); }); context('when given the same time', function() { const time = Date.now(); it('increments the hires timer', function() { const previous_id = subject(time); expect(subject(time).hires_time).to.equal(previous_id.hires_time + 1); }); context('when the hires timer overflows', function() { function hiresOverflow(time) { for (let count = 0; count < HIRES_MAX; ++count) { subject(time); } } it('resets the hires timer', function() { subject(); hiresOverflow(time); expect(subject(time).hires_time).to.equal(0); }); it('increments the clock sequence', function() { // For the minor chance that the clock sequence starts at the top this.retries(1); const previous_id = subject(time); hiresOverflow(time); expect(subject(time).clock_sequence) .to.equal(previous_id.clock_sequence + 1); }); }); }); context('when given a time in the future', function() { it('resets the hires timer', function() { subject(); expect(subject(MAX_TIME).hires_time).to.equal(0); }); it('retains the same clock sequence', function() { const previous_id = subject(); expect(subject(MAX_TIME).clock_sequence) .to.equal(previous_id.clock_sequence); }); }); }); describe('.MIN', function() { const subject = () => described_class.MIN(); it('has all 0-bits other than variant/version', function() { const expected = ByteArray.generateZeroFilled(); expected.set([0b01100000], 6); expected.set([0b10000000], 8); expect(subject().bytes).to.deep.equal(expected); }); }); describe('.MAX', function() { const subject = () => described_class.MAX(); it('has all 1-bits other than variant/version', function() { const expected = ByteArray.generateOneFilled(); expected.set([0b01101111], 6); expected.set([0b10111111], 8); expect(subject().bytes).to.deep.equal(expected); }); }); assertUuidVariantVersion(described_class, 1, 6); assertAccessorBytes(described_class); assertAccessorNode(described_class); assertAccessorTime(described_class, [ ['min', MIN_TIME], ['origin', new Date(0)], ['now', new Date()], ['max', MAX_TIME], ]); assertCompareDemonstratesTotalOrder([ ['the min id', described_class.MIN()], ['a random id', described_class.generate()], ['the max id', described_class.MAX()], ]); assertEqualDemonstratesSameness([ ['the min id', described_class.MIN()], ['a random id', described_class.generate()], ['the max id', described_class.MAX()], ]); }); ================================================ FILE: test/id/uuid-nil.js ================================================ 'use strict'; const { expect } = require('chai'); const { assertAccessorBytes, assertDebuggable, assertUuidVariantVersion, } = require('./shared'); const ByteArray = require('common/byte-array'); const { UuidNil: described_class } = require('id/uuid-nil'); describe(described_class.name, function() { assertDebuggable(described_class); describe('.generate', function() { const subject = () => described_class.generate(); it(`returns a new ${described_class.name}`, function() { expect(subject()).to.be.an.instanceOf(described_class); }); }); describe('.MIN', function() { const subject = () => described_class.MIN(); it('has all 0-bits', function() { const expected = ByteArray.generateZeroFilled(); expect(subject().bytes).to.deep.equal(expected); }); }); describe('.MAX', function() { const subject = () => described_class.MAX(); it('has all 0-bits', function() { const expected = ByteArray.generateZeroFilled(); expect(subject().bytes).to.deep.equal(expected); }); }); assertUuidVariantVersion(described_class, 0, 0); assertAccessorBytes(described_class); describe('#compare', function() { const diagnose = (lhs, rhs) => `(${lhs}).compare(${rhs})`; const labeled_ids = [ ['the min id', described_class.MIN()], ['a random id', described_class.generate()], ['another random id', described_class.generate()], ['the max id', described_class.MAX()], ]; labeled_ids.forEach(([lhs_label, lhs_id], lhs_idx) => { const subject = (other) => lhs_id.compare(other.clone()); describe(`given ${lhs_label}`, function() { it('returns 0 for all ids', function() { labeled_ids.forEach(([label, id]) => { expect(subject(id), diagnose(lhs_label, label)).to.equal(0); }); }); }); }); }); describe('#equal', function() { const diagnose = (lhs, rhs) => `(${lhs}).equal(${rhs})`; const labeled_ids = [ ['the min id', described_class.MIN()], ['a random id', described_class.generate()], ['another random id', described_class.generate()], ['the max id', described_class.MAX()], ]; labeled_ids.forEach(([lhs_label, lhs_id]) => { const subject = (other) => lhs_id.equal(other.clone()); describe(`given ${lhs_label}`, function() { it('returns true for all ids', function() { labeled_ids.forEach(([label, id]) => { expect(subject(id), diagnose(lhs_label, label)).to.be.true; }); }); }); }); }); }); ================================================ FILE: test/id128.js ================================================ 'use strict'; const { expect } = require('chai'); const Id128 = require('../'); function assertDebuggable(id_name, generator) { describe('when cast as a string', function() { const subject = () => '' + generator(); it(`mentions the type ${id_name}`, function() { expect(subject()).to.contain.string(id_name); }); }); } function assertValidId128(id_name, factory, id_class, generator_args = {}) { describe('new', function() { it('is disabled', function() { expect(() => new factory).to.throw(TypeError); }); }); describe('.generate', function() { const subject = () => factory.generate(generator_args); it(`returns a ${id_name}`, function() { expect(subject()).to.be.an.instanceOf(id_class); expect(subject()).to.be.an.instanceOf(factory.type); }); it('generates 128-bit id', function() { expect(subject().bytes).to.have.lengthOf(16); }); assertDebuggable(id_name, subject); }); describe('.MIN', function() { const subject = () => factory.MIN(generator_args); it(`returns a ${id_name}`, function() { expect(subject()).to.be.an.instanceOf(id_class); }); it('generates 128-bit id', function() { expect(subject().bytes).to.have.lengthOf(16); }); assertDebuggable(id_name, subject); }); describe('.MAX', function() { const subject = () => factory.MAX(generator_args); it(`returns a ${id_name}`, function() { expect(subject()).to.be.an.instanceOf(id_class); }); it('generates 128-bit id', function() { expect(subject().bytes).to.have.lengthOf(16); }); assertDebuggable(id_name, subject); }); describe('canonical', function() { const id = factory.generate(generator_args); it('encodes to a string', function() { expect(factory.toCanonical(id)).to.be.a('string'); }); it(`decodes to a ${id_name}`, function() { expect(factory.fromCanonical(factory.toCanonical(id))) .to.be.an.instanceOf(id_class); }); it('converts symmetrically', function() { [ ['generated', id], ['min', factory.MIN(generator_args)], ['max', factory.MAX(generator_args)], ].forEach(([label, test_id]) => { expect(factory.fromCanonical(factory.toCanonical(test_id)), label) .to.deep.equal(test_id); }); }); describe('when decoded', function() { assertDebuggable(id_name, () => factory.fromCanonical(id.toCanonical())); }); }); describe('raw', function() { const id = factory.generate(generator_args); it('encodes to a string', function() { expect(factory.toRaw(id)).to.be.a('string'); }); it(`decodes to a ${id_name}`, function() { expect(factory.fromRaw(factory.toRaw(id))) .to.be.an.instanceOf(id_class); }); it('converts symmetrically', function() { [ ['generated', id], ['min', factory.MIN(generator_args)], ['max', factory.MAX(generator_args)], ].forEach(([label, test_id]) => { expect(factory.fromRaw(factory.toRaw(test_id)), label) .to.deep.equal(test_id); }); }); describe('when decoded', function() { assertDebuggable(id_name, () => factory.fromRaw(id.toRaw())); }); }); } [ 'Ulid', 'UlidMonotonic', 'Uuid1', 'Uuid4', 'UuidNil', ].forEach((id_name) => describe(`${id_name} Factory`, function() { assertValidId128( id_name, Id128[id_name], Id128[id_name].type, ); })); [ ['Uuid1', { version: 1 }], ['Uuid4', { version: 4 }], ['Uuid6', { version: 6 }], ['UuidNil', { version: 0 }], ].forEach(([id_name, generator_args]) => { describe(`Uuid Factory generating ${id_name}`, function() { assertValidId128( id_name, Id128.Uuid, Id128[id_name].type, generator_args, ); }); }); const all_ids = [ 'Ulid', 'UlidMonotonic', 'Uuid1', 'Uuid4', 'UuidNil', ].map((id_name) => Id128[id_name].generate()) describe('idCompare', function() { const subject = Id128.idCompare it('compares the same id of any type', function() { all_ids.forEach((id) => expect(subject(id, id), id.name).to.equal(0)); }); it('works with ids of any type', function() { all_ids.forEach((lhs) => { all_ids.forEach((rhs) => { expect(() => subject(lhs, rhs), `${lhs.name} and ${rhs.name}`) .not.to.throw(); }); }); }); }) describe('idEqual', function() { const subject = Id128.idEqual it('equates the same id of any type', function() { all_ids.forEach((id) => expect(subject(id, id), id.name).to.equal(true)); }); it('works with ids of any type', function() { all_ids.forEach((lhs) => { all_ids.forEach((rhs) => { expect(() => subject(lhs, rhs), `${lhs.name} and ${rhs.name}`) .not.to.throw(); }); }); }); }) ================================================ FILE: types/test.ts ================================================ import { idCompare, idEqual, Exception, Ulid, UlidMonotonic, Uuid, Uuid1, Uuid4, Uuid6, UuidNil, } from 'id128'; const bytes = new Uint8Array(16); const encoded_id = "encoded-id-data"; const node = new Uint8Array([]); const now = new Date(); const ulid = Ulid.MIN(); const ulid_monotonic = UlidMonotonic.MIN(); const uuid = Uuid.MIN({ version: 0 }); const uuid_1 = Uuid1.MIN(); const uuid_4 = Uuid4.MIN(); const uuid_6 = Uuid6.MIN(); const uuid_nil = UuidNil.MIN(); Ulid.MAX(); // $ExpectType Ulid Ulid.MIN(); // $ExpectType Ulid Ulid.construct(bytes); // $ExpectType Ulid Ulid.fromCanonical(encoded_id); // $ExpectType Ulid Ulid.fromCanonicalTrusted(encoded_id); // $ExpectType Ulid Ulid.fromRaw(encoded_id); // $ExpectType Ulid Ulid.fromRawTrusted(encoded_id); // $ExpectType Ulid Ulid.generate(); // $ExpectType Ulid Ulid.generate({ time: now }); // $ExpectType Ulid Ulid.generate({}); // $ExpectType Ulid Ulid.isCanonical(encoded_id); // $ExpectType boolean Ulid.isRaw(encoded_id); // $ExpectType boolean Ulid.name; // $ExpectType string Ulid.type; // $ExpectType ConstructorOf Ulid.toCanonical(ulid); // $ExpectType string Ulid.toRaw(ulid); // $ExpectType string UlidMonotonic.MAX(); // $ExpectType UlidMonotonic UlidMonotonic.MIN(); // $ExpectType UlidMonotonic UlidMonotonic.construct(bytes); // $ExpectType UlidMonotonic UlidMonotonic.fromCanonical(encoded_id); // $ExpectType UlidMonotonic UlidMonotonic.fromCanonicalTrusted(encoded_id); // $ExpectType UlidMonotonic UlidMonotonic.fromRaw(encoded_id); // $ExpectType UlidMonotonic UlidMonotonic.fromRawTrusted(encoded_id); // $ExpectType UlidMonotonic UlidMonotonic.generate(); // $ExpectType UlidMonotonic UlidMonotonic.generate({ time: now }); // $ExpectType UlidMonotonic UlidMonotonic.generate({}); // $ExpectType UlidMonotonic UlidMonotonic.isCanonical(encoded_id); // $ExpectType boolean UlidMonotonic.isRaw(encoded_id); // $ExpectType boolean UlidMonotonic.name; // $ExpectType string UlidMonotonic.type; // $ExpectType ConstructorOf UlidMonotonic.toCanonical(ulid_monotonic); // $ExpectType string UlidMonotonic.toRaw(ulid_monotonic); // $ExpectType string Uuid.MAX({ version: 0 }); // $ExpectType Uuid Uuid.MAX({ version: 1 }); // $ExpectType Uuid Uuid.MAX({ version: 4 }); // $ExpectType Uuid Uuid.MAX({ version: 6 }); // $ExpectType Uuid Uuid.MIN({ version: 0 }); // $ExpectType Uuid Uuid.MIN({ version: 1 }); // $ExpectType Uuid Uuid.MIN({ version: 4 }); // $ExpectType Uuid Uuid.MIN({ version: 6 }); // $ExpectType Uuid Uuid.fromCanonical(encoded_id); // $ExpectType Uuid Uuid.fromCanonicalTrusted(encoded_id); // $ExpectType Uuid Uuid.fromRaw(encoded_id); // $ExpectType Uuid Uuid.fromRawTrusted(encoded_id); // $ExpectType Uuid Uuid.generate({ node, time: now, version: 1 }); // $ExpectType Uuid Uuid.generate({ node, time: now, version: 6 }); // $ExpectType Uuid Uuid.generate({ version: 0 }); // $ExpectType Uuid Uuid.generate({ version: 4 }); // $ExpectType Uuid Uuid.isCanonical(encoded_id); // $ExpectType boolean Uuid.isRaw(encoded_id); // $ExpectType boolean Uuid.name; // $ExpectType string Uuid.type; // $ExpectType ConstructorOf Uuid.toCanonical(uuid); // $ExpectType string Uuid.toCanonical(uuid_1); // $ExpectType string Uuid.toCanonical(uuid_4); // $ExpectType string Uuid.toCanonical(uuid_6); // $ExpectType string Uuid.toCanonical(uuid_nil); // $ExpectType string Uuid.toRaw(uuid); // $ExpectType string Uuid.toRaw(uuid_1); // $ExpectType string Uuid.toRaw(uuid_4); // $ExpectType string Uuid.toRaw(uuid_6); // $ExpectType string Uuid.toRaw(uuid_nil); // $ExpectType string Uuid.versioned_ids; // $ExpectType IdFactory[] Uuid1.MAX(); // $ExpectType Uuid1 Uuid1.MIN(); // $ExpectType Uuid1 Uuid1.construct(bytes); // $ExpectType Uuid1 Uuid1.fromCanonical(encoded_id); // $ExpectType Uuid1 Uuid1.fromCanonicalTrusted(encoded_id); // $ExpectType Uuid1 Uuid1.fromRaw(encoded_id); // $ExpectType Uuid1 Uuid1.fromRawTrusted(encoded_id); // $ExpectType Uuid1 Uuid1.generate(); // $ExpectType Uuid1 Uuid1.generate({ node, time: now }); // $ExpectType Uuid1 Uuid1.generate({}); // $ExpectType Uuid1 Uuid1.isCanonical(encoded_id); // $ExpectType boolean Uuid1.isRaw(encoded_id); // $ExpectType boolean Uuid1.name; // $ExpectType string Uuid1.type; // $ExpectType ConstructorOf Uuid1.toCanonical(uuid_1); // $ExpectType string Uuid1.toRaw(uuid_1); // $ExpectType string Uuid4.MAX(); // $ExpectType Uuid4 Uuid4.MIN(); // $ExpectType Uuid4 Uuid4.construct(bytes); // $ExpectType Uuid4 Uuid4.fromCanonical(encoded_id); // $ExpectType Uuid4 Uuid4.fromCanonicalTrusted(encoded_id); // $ExpectType Uuid4 Uuid4.fromRaw(encoded_id); // $ExpectType Uuid4 Uuid4.fromRawTrusted(encoded_id); // $ExpectType Uuid4 Uuid4.generate(); // $ExpectType Uuid4 Uuid4.generate({}); // $ExpectType Uuid4 Uuid4.isCanonical(encoded_id); // $ExpectType boolean Uuid4.isRaw(encoded_id); // $ExpectType boolean Uuid4.name; // $ExpectType string Uuid4.type; // $ExpectType ConstructorOf Uuid4.toCanonical(uuid_4); // $ExpectType string Uuid4.toRaw(uuid_4); // $ExpectType string Uuid6.MAX(); // $ExpectType Uuid6 Uuid6.MIN(); // $ExpectType Uuid6 Uuid6.construct(bytes); // $ExpectType Uuid6 Uuid6.fromCanonical(encoded_id); // $ExpectType Uuid6 Uuid6.fromCanonicalTrusted(encoded_id); // $ExpectType Uuid6 Uuid6.fromRaw(encoded_id); // $ExpectType Uuid6 Uuid6.fromRawTrusted(encoded_id); // $ExpectType Uuid6 Uuid6.generate(); // $ExpectType Uuid6 Uuid6.generate({ node, time: now }); // $ExpectType Uuid6 Uuid6.generate({}); // $ExpectType Uuid6 Uuid6.isCanonical(encoded_id); // $ExpectType boolean Uuid6.isRaw(encoded_id); // $ExpectType boolean Uuid6.name; // $ExpectType string Uuid6.type; // $ExpectType ConstructorOf Uuid6.toCanonical(uuid_6); // $ExpectType string Uuid6.toRaw(uuid_6); // $ExpectType string UuidNil.MAX(); // $ExpectType UuidNil UuidNil.MIN(); // $ExpectType UuidNil UuidNil.construct(bytes); // $ExpectType UuidNil UuidNil.fromCanonical(encoded_id); // $ExpectType UuidNil UuidNil.fromCanonicalTrusted(encoded_id); // $ExpectType UuidNil UuidNil.fromRaw(encoded_id); // $ExpectType UuidNil UuidNil.fromRawTrusted(encoded_id); // $ExpectType UuidNil UuidNil.generate(); // $ExpectType UuidNil UuidNil.generate({}); // $ExpectType UuidNil UuidNil.isCanonical(encoded_id); // $ExpectType boolean UuidNil.isRaw(encoded_id); // $ExpectType boolean UuidNil.name; // $ExpectType string UuidNil.type; // $ExpectType ConstructorOf UuidNil.toCanonical(uuid_nil); // $ExpectType string UuidNil.toRaw(uuid_nil); // $ExpectType string ulid + ''; // $ExpectType string ulid.bytes; // $ExpectType Uint8Array ulid.clone(); // $ExpectType Ulid ulid.compare(ulid); // $ExpectType number ulid.equal(ulid); // $ExpectType boolean ulid.time; // $ExpectType Date ulid.toCanonical(); // $ExpectType string ulid.toRaw(); // $ExpectType string ulid instanceof Ulid.type; // $ExpectType boolean ulid_monotonic + ''; // $ExpectType string ulid_monotonic.bytes; // $ExpectType Uint8Array ulid_monotonic.clone(); // $ExpectType UlidMonotonic ulid_monotonic.compare(ulid_monotonic); // $ExpectType number ulid_monotonic.equal(ulid_monotonic); // $ExpectType boolean ulid_monotonic.time; // $ExpectType Date ulid_monotonic.toCanonical(); // $ExpectType string ulid_monotonic.toRaw(); // $ExpectType string ulid_monotonic instanceof UlidMonotonic.type; // $ExpectType boolean uuid_1 + ''; // $ExpectType string uuid_1.bytes; // $ExpectType Uint8Array uuid_1.clock_sequence; // $ExpectType number uuid_1.clone(); // $ExpectType Uuid1 uuid_1.compare(uuid_1); // $ExpectType number uuid_1.equal(uuid_1); // $ExpectType boolean uuid_1.hires_time; // $ExpectType number uuid_1.node; // $ExpectType Uint8Array uuid_1.time; // $ExpectType Date uuid_1.toCanonical(); // $ExpectType string uuid_1.toRaw(); // $ExpectType string uuid_1.variant; // $ExpectType number uuid_1.version; // $ExpectType number uuid_1 instanceof Uuid1.type; // $ExpectType boolean uuid_4 + ''; // $ExpectType string uuid_4.bytes; // $ExpectType Uint8Array uuid_4.clone(); // $ExpectType Uuid4 uuid_4.compare(uuid_4); // $ExpectType number uuid_4.equal(uuid_4); // $ExpectType boolean uuid_4.toCanonical(); // $ExpectType string uuid_4.toRaw(); // $ExpectType string uuid_4.variant; // $ExpectType number uuid_4.version; // $ExpectType number uuid_4 instanceof Uuid4.type; // $ExpectType boolean uuid_6 + ''; // $ExpectType string uuid_6.bytes; // $ExpectType Uint8Array uuid_6.clock_sequence; // $ExpectType number uuid_6.clone(); // $ExpectType Uuid6 uuid_6.compare(uuid_6); // $ExpectType number uuid_6.equal(uuid_6); // $ExpectType boolean uuid_6.hires_time; // $ExpectType number uuid_6.node; // $ExpectType Uint8Array uuid_6.time; // $ExpectType Date uuid_6.toCanonical(); // $ExpectType string uuid_6.toRaw(); // $ExpectType string uuid_6.variant; // $ExpectType number uuid_6.version; // $ExpectType number uuid_6 instanceof Uuid6.type; // $ExpectType boolean uuid_nil + ''; // $ExpectType string uuid_nil.bytes; // $ExpectType Uint8Array uuid_nil.clone(); // $ExpectType UuidNil uuid_nil.compare(uuid_nil); // $ExpectType number uuid_nil.equal(uuid_nil); // $ExpectType boolean uuid_nil.toCanonical(); // $ExpectType string uuid_nil.toRaw(); // $ExpectType string uuid_nil.variant; // $ExpectType number uuid_nil.version; // $ExpectType number uuid_nil instanceof UuidNil.type; // $ExpectType boolean idCompare(ulid, uuid_nil); // $expectType number idEqual(ulid, uuid_nil); // $expectType boolean new Error() instanceof Exception.Id128Error; // $ExpectType boolean new Error() instanceof Exception.ClockSequenceOverflow; // $ExpectType boolean new Error() instanceof Exception.InvalidBytes; // $ExpectType boolean new Error() instanceof Exception.InvalidEncoding; // $ExpectType boolean new Error() instanceof Exception.InvalidEpoch; // $ExpectType boolean new Error() instanceof Exception.UnsupportedVersion; // $ExpectType boolean ================================================ FILE: types/tsconfig.json ================================================ { "compilerOptions": { "lib": ["es6"], "module": "commonjs", "noEmit": true, "noImplicitAny": true, "noImplicitThis": true, "strictBindCallApply": true, "strictFunctionTypes": true, "strictNullChecks": true, "baseUrl": ".", "paths": { "id128": ["."] } } } ================================================ FILE: types/tslint.json ================================================ { "extends": "dtslint/dtslint.json", "rules": { "indent": [true, "tabs", 2], "no-empty-interface": false } } ================================================ FILE: utils.js ================================================ const { Ulid } = require('./src/id/ulid'); const { UlidMonotonic } = require('./src/id/ulid-monotonic'); const { Uuid } = require('./src/id/uuid'); const { Uuid1 } = require('./src/id/uuid-1'); const { Uuid4 } = require('./src/id/uuid-4'); const { Uuid6 } = require('./src/id/uuid-6'); const { UuidNil } = require('./src/id/uuid-nil'); const Crockford32Coder = require('./src/coder/crockford32'); const HexCoder = require('./src/coder/hex'); const UuidCoder = require('./src/coder/uuid'); const { IdFactory } = require('./src/factory/id'); const { VersionedIdFactory } = require('./src/factory/versioned-id'); const Exception = require('./src/common/exception'); module.exports = { Coder: { Crockford32: Crockford32Coder, Hex: HexCoder, Uuid: UuidCoder, }, Exception, Id: { Ulid, UlidMonotonic, Uuid, Uuid1, Uuid4, Uuid6, UuidNil, }, IdFactory, VersionedIdFactory, };