@thi.ng/ksuid
Advanced tools
Comparing version 0.2.6 to 0.3.0
@@ -6,2 +6,27 @@ # Change Log | ||
# [0.3.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/ksuid@0.2.6...@thi.ng/ksuid@0.3.0) (2021-08-07) | ||
### Code Refactoring | ||
* **ksuid:** extract IKSUID, update impls, docs ([1276c94](https://github.com/thi-ng/umbrella/commit/1276c940d6e7b584d90eb871261ff6a28352de4f)) | ||
### Features | ||
* **ksuid:** pkg restructure, add 64bit version ([9c40b20](https://github.com/thi-ng/umbrella/commit/9c40b2053afb9067723bfb0377e5e3ea2a38c52a)) | ||
### BREAKING CHANGES | ||
* **ksuid:** Rename KSUID => KSUID32 / defKSUID32() | ||
- update readme | ||
- update tests | ||
- update pkg meta | ||
## [0.2.6](https://github.com/thi-ng/umbrella/compare/@thi.ng/ksuid@0.2.5...@thi.ng/ksuid@0.2.6) (2021-08-04) | ||
@@ -8,0 +33,0 @@ |
@@ -1,96 +0,5 @@ | ||
import { BaseN } from "@thi.ng/base-n"; | ||
import { IRandom } from "@thi.ng/random"; | ||
export interface KSUIDOpts { | ||
/** | ||
* {@link @this.ng/base-n#BaseN} instance for string encoding the generated | ||
* binary IDs. | ||
* | ||
* @defaultValue BASE62 | ||
*/ | ||
base: BaseN; | ||
/** | ||
* Optional PRNG instance for sourcing random values (for development/debug | ||
* purposes only). | ||
* | ||
* @defaultValue window.crypto | ||
*/ | ||
rnd: IRandom; | ||
/** | ||
* Number of bytes for random payload. | ||
* | ||
* @defaultValue 16 | ||
*/ | ||
bytes: number; | ||
/** | ||
* Time offset in seconds, relative to standard Unix epoch. This is used to | ||
* extend the time headroom of IDs into the future. | ||
* | ||
* @remarks | ||
* The default value is approx. 2020-09-13, meaning this is the T0 epoch for | ||
* all IDs (providing an additional ~50 year lifespan compared to the | ||
* standard 1970-01-01 epoch) | ||
* | ||
* @defaultValue 1_600_000_000 | ||
*/ | ||
epoch: number; | ||
} | ||
export declare class KSUID { | ||
/** | ||
* Returns the byte size of a single ID, based on the KSUID's configuration. | ||
* The default config (payload 16 bytes) will result in 20-byte IDs (27 | ||
* chars base62 encoded). | ||
*/ | ||
readonly size: number; | ||
protected base: BaseN; | ||
protected rnd?: IRandom; | ||
protected epoch: number; | ||
protected pad: (x: any) => string; | ||
constructor(opts?: Partial<KSUIDOpts>); | ||
/** | ||
* Returns a new baseN encoded ID string. | ||
*/ | ||
next(): string; | ||
/** | ||
* Returns a new ID as byte array. | ||
*/ | ||
nextBinary(): Uint8Array; | ||
/** | ||
* Returns a new baseN encoded ID string for given `epoch` (default: current | ||
* time) and with all random payload bytes set to 0. | ||
* | ||
* @param epoch | ||
*/ | ||
timeOnly(epoch?: number): string; | ||
/** | ||
* Binary version of {@link KSUI.timeOnly}, but returns byte array. The | ||
* first 4 bytes will contain the timestamp. | ||
* | ||
* @param epoch | ||
*/ | ||
timeOnlyBinary(epoch?: number): Uint8Array; | ||
/** | ||
* Returns baseN encoded version of given binary ID (generated via | ||
* `.nextBinary()`). | ||
*/ | ||
format(buf: Uint8Array): string; | ||
/** | ||
* Takes a KSUID string (assumed to be generated with the same config as | ||
* this instance) and parses it into an object of: `{ epoch, id }`, where | ||
* `epoch` is the Unix epoch of the ID and `id` the random bytes. | ||
* | ||
* @remarks | ||
* This operation requires `bigint` support by the host environment. | ||
* | ||
* @param id | ||
*/ | ||
parse(id: string): { | ||
epoch: number; | ||
id: Uint8Array; | ||
}; | ||
} | ||
/** | ||
* | ||
* @param opts | ||
*/ | ||
export declare const defKSUID: (opts?: Partial<KSUIDOpts> | undefined) => KSUID; | ||
export * from "./api"; | ||
export * from "./aksuid"; | ||
export * from "./ksuid32"; | ||
export * from "./ksuid64"; | ||
//# sourceMappingURL=index.d.ts.map |
88
index.js
@@ -1,84 +0,4 @@ | ||
import { assert } from "@thi.ng/api"; | ||
import { BASE62 } from "@thi.ng/base-n"; | ||
import { randomBytes, randomBytesFrom } from "@thi.ng/random"; | ||
import { padLeft } from "@thi.ng/strings"; | ||
export class KSUID { | ||
constructor(opts) { | ||
opts = Object.assign({ base: BASE62, epoch: 1600000000, bytes: 16 }, opts); | ||
this.base = opts.base; | ||
this.rnd = opts.rnd; | ||
this.epoch = opts.epoch; | ||
this.size = 4 + opts.bytes; | ||
this.pad = padLeft(this.base.size(2 ** (this.size * 8) - 1), this.base.base[0]); | ||
} | ||
/** | ||
* Returns a new baseN encoded ID string. | ||
*/ | ||
next() { | ||
return this.format(this.nextBinary()); | ||
} | ||
/** | ||
* Returns a new ID as byte array. | ||
*/ | ||
nextBinary() { | ||
const buf = this.timeOnlyBinary(); | ||
return this.rnd | ||
? randomBytesFrom(this.rnd, buf, 4) | ||
: randomBytes(buf, 4); | ||
} | ||
/** | ||
* Returns a new baseN encoded ID string for given `epoch` (default: current | ||
* time) and with all random payload bytes set to 0. | ||
* | ||
* @param epoch | ||
*/ | ||
timeOnly(epoch) { | ||
return this.format(this.timeOnlyBinary(epoch)); | ||
} | ||
/** | ||
* Binary version of {@link KSUI.timeOnly}, but returns byte array. The | ||
* first 4 bytes will contain the timestamp. | ||
* | ||
* @param epoch | ||
*/ | ||
timeOnlyBinary(epoch = Date.now()) { | ||
const buf = new Uint8Array(this.size); | ||
let t = epoch / 1000 - this.epoch; | ||
assert(t >= 0, "configured base epoch must be in the past"); | ||
buf.set([t >>> 24, (t >>> 16) & 0xff, (t >>> 8) & 0xff, t & 0xff]); | ||
return buf; | ||
} | ||
/** | ||
* Returns baseN encoded version of given binary ID (generated via | ||
* `.nextBinary()`). | ||
*/ | ||
format(buf) { | ||
assert(buf.length == this.size, `illegal KSUID size, expected ${this.size} bytes`); | ||
return this.pad(this.base.encodeBytes(buf)); | ||
} | ||
/** | ||
* Takes a KSUID string (assumed to be generated with the same config as | ||
* this instance) and parses it into an object of: `{ epoch, id }`, where | ||
* `epoch` is the Unix epoch of the ID and `id` the random bytes. | ||
* | ||
* @remarks | ||
* This operation requires `bigint` support by the host environment. | ||
* | ||
* @param id | ||
*/ | ||
parse(id) { | ||
const buf = new Uint8Array(this.size); | ||
this.base.decodeBytes(id, buf); | ||
return { | ||
epoch: (((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]) + | ||
this.epoch) * | ||
1000, | ||
id: buf.slice(4), | ||
}; | ||
} | ||
} | ||
/** | ||
* | ||
* @param opts | ||
*/ | ||
export const defKSUID = (opts) => new KSUID(opts); | ||
export * from "./api"; | ||
export * from "./aksuid"; | ||
export * from "./ksuid32"; | ||
export * from "./ksuid64"; |
@@ -10,9 +10,9 @@ 'use strict'; | ||
class KSUID { | ||
constructor(opts) { | ||
opts = Object.assign({ base: baseN.BASE62, epoch: 1600000000, bytes: 16 }, opts); | ||
this.base = opts.base; | ||
class AKSUID { | ||
constructor(epochSize, opts) { | ||
this.epochSize = epochSize; | ||
this.base = opts.base || baseN.BASE62; | ||
this.rnd = opts.rnd; | ||
this.epoch = opts.epoch; | ||
this.size = 4 + opts.bytes; | ||
this.size = this.epochSize + opts.bytes; | ||
this.pad = strings.padLeft(this.base.size(2 ** (this.size * 8) - 1), this.base.base[0]); | ||
@@ -26,4 +26,4 @@ } | ||
return this.rnd | ||
? random.randomBytesFrom(this.rnd, buf, 4) | ||
: random.randomBytes(buf, 4); | ||
? random.randomBytesFrom(this.rnd, buf, this.epochSize) | ||
: random.randomBytes(buf, this.epochSize); | ||
} | ||
@@ -33,13 +33,33 @@ timeOnly(epoch) { | ||
} | ||
format(buf) { | ||
this.ensureSize(buf); | ||
return this.pad(this.base.encodeBytes(buf)); | ||
} | ||
ensureSize(buf) { | ||
api.assert(buf.length == this.size, `illegal KSUID size, expected ${this.size} bytes`); | ||
return buf; | ||
} | ||
ensureTime(t) { | ||
api.assert(t >= 0, "configured base epoch must be in the past"); | ||
return t; | ||
} | ||
u32(buf, i = 0) { | ||
return (((buf[i] << 24) | | ||
(buf[i + 1] << 16) | | ||
(buf[i + 2] << 8) | | ||
buf[i + 3]) >>> | ||
0); | ||
} | ||
} | ||
class KSUID32 extends AKSUID { | ||
constructor(opts) { | ||
super(4, Object.assign({ epoch: 1600000000, bytes: 16 }, opts)); | ||
} | ||
timeOnlyBinary(epoch = Date.now()) { | ||
const buf = new Uint8Array(this.size); | ||
let t = epoch / 1000 - this.epoch; | ||
api.assert(t >= 0, "configured base epoch must be in the past"); | ||
buf.set([t >>> 24, (t >>> 16) & 0xff, (t >>> 8) & 0xff, t & 0xff]); | ||
const t = this.ensureTime((epoch / 1000 - this.epoch) | 0); | ||
buf.set([t >>> 24, (t >> 16) & 0xff, (t >> 8) & 0xff, t & 0xff]); | ||
return buf; | ||
} | ||
format(buf) { | ||
api.assert(buf.length == this.size, `illegal KSUID size, expected ${this.size} bytes`); | ||
return this.pad(this.base.encodeBytes(buf)); | ||
} | ||
parse(id) { | ||
@@ -49,5 +69,3 @@ const buf = new Uint8Array(this.size); | ||
return { | ||
epoch: (((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]) + | ||
this.epoch) * | ||
1000, | ||
epoch: (this.u32(buf) + this.epoch) * 1000, | ||
id: buf.slice(4), | ||
@@ -57,5 +75,40 @@ }; | ||
} | ||
const defKSUID = (opts) => new KSUID(opts); | ||
const defKSUID32 = (opts) => new KSUID32(opts); | ||
exports.KSUID = KSUID; | ||
exports.defKSUID = defKSUID; | ||
class KSUID64 extends AKSUID { | ||
constructor(opts) { | ||
super(8, Object.assign({ epoch: 1600000000000, bytes: 12 }, opts)); | ||
} | ||
timeOnlyBinary(epoch = Date.now()) { | ||
const buf = new Uint8Array(this.size); | ||
const t = this.ensureTime(epoch - this.epoch); | ||
const h = (t / 4294967296) >>> 0; | ||
const l = (t & 4294967295) >>> 0; | ||
buf.set([ | ||
h >>> 24, | ||
(h >> 16) & 0xff, | ||
(h >> 8) & 0xff, | ||
h & 0xff, | ||
l >>> 24, | ||
(l >> 16) & 0xff, | ||
(l >> 8) & 0xff, | ||
l & 0xff, | ||
]); | ||
return buf; | ||
} | ||
parse(id) { | ||
const buf = new Uint8Array(this.size); | ||
this.base.decodeBytes(id, buf); | ||
return { | ||
epoch: this.u32(buf) * 4294967296 + this.u32(buf, 4) + this.epoch, | ||
id: buf.slice(8), | ||
}; | ||
} | ||
} | ||
const defKSUID64 = (opts) => new KSUID64(opts); | ||
exports.AKSUID = AKSUID; | ||
exports.KSUID32 = KSUID32; | ||
exports.KSUID64 = KSUID64; | ||
exports.defKSUID32 = defKSUID32; | ||
exports.defKSUID64 = defKSUID64; |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@thi.ng/api"),require("@thi.ng/base-n"),require("@thi.ng/random"),require("@thi.ng/strings")):"function"==typeof define&&define.amd?define(["exports","@thi.ng/api","@thi.ng/base-n","@thi.ng/random","@thi.ng/strings"],t):t(((e="undefined"!=typeof globalThis?globalThis:e||self).thi=e.thi||{},e.thi.ng=e.thi.ng||{},e.thi.ng.ksuid={}),e.thi.ng.api,e.thi.ng.baseN,e.thi.ng.random,e.thi.ng.strings)}(this,(function(e,t,i,s,n){"use strict";class r{constructor(e){e=Object.assign({base:i.BASE62,epoch:16e8,bytes:16},e),this.base=e.base,this.rnd=e.rnd,this.epoch=e.epoch,this.size=4+e.bytes,this.pad=n.padLeft(this.base.size(2**(8*this.size)-1),this.base.base[0])}next(){return this.format(this.nextBinary())}nextBinary(){const e=this.timeOnlyBinary();return this.rnd?s.randomBytesFrom(this.rnd,e,4):s.randomBytes(e,4)}timeOnly(e){return this.format(this.timeOnlyBinary(e))}timeOnlyBinary(e=Date.now()){const i=new Uint8Array(this.size);let s=e/1e3-this.epoch;return t.assert(s>=0,"configured base epoch must be in the past"),i.set([s>>>24,s>>>16&255,s>>>8&255,255&s]),i}format(e){return t.assert(e.length==this.size,`illegal KSUID size, expected ${this.size} bytes`),this.pad(this.base.encodeBytes(e))}parse(e){const t=new Uint8Array(this.size);return this.base.decodeBytes(e,t),{epoch:1e3*((t[0]<<24|t[1]<<16|t[2]<<8|t[3])+this.epoch),id:t.slice(4)}}}e.KSUID=r,e.defKSUID=e=>new r(e),Object.defineProperty(e,"__esModule",{value:!0})})); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@thi.ng/api"),require("@thi.ng/base-n"),require("@thi.ng/random"),require("@thi.ng/strings")):"function"==typeof define&&define.amd?define(["exports","@thi.ng/api","@thi.ng/base-n","@thi.ng/random","@thi.ng/strings"],t):t(((e="undefined"!=typeof globalThis?globalThis:e||self).thi=e.thi||{},e.thi.ng=e.thi.ng||{},e.thi.ng.ksuid={}),e.thi.ng.api,e.thi.ng.baseN,e.thi.ng.random,e.thi.ng.strings)}(this,(function(e,t,s,i,n){"use strict";class r{constructor(e,t){this.epochSize=e,this.base=t.base||s.BASE62,this.rnd=t.rnd,this.epoch=t.epoch,this.size=this.epochSize+t.bytes,this.pad=n.padLeft(this.base.size(2**(8*this.size)-1),this.base.base[0])}next(){return this.format(this.nextBinary())}nextBinary(){const e=this.timeOnlyBinary();return this.rnd?i.randomBytesFrom(this.rnd,e,this.epochSize):i.randomBytes(e,this.epochSize)}timeOnly(e){return this.format(this.timeOnlyBinary(e))}format(e){return this.ensureSize(e),this.pad(this.base.encodeBytes(e))}ensureSize(e){return t.assert(e.length==this.size,`illegal KSUID size, expected ${this.size} bytes`),e}ensureTime(e){return t.assert(e>=0,"configured base epoch must be in the past"),e}u32(e,t=0){return(e[t]<<24|e[t+1]<<16|e[t+2]<<8|e[t+3])>>>0}}class h extends r{constructor(e){super(4,Object.assign({epoch:16e8,bytes:16},e))}timeOnlyBinary(e=Date.now()){const t=new Uint8Array(this.size),s=this.ensureTime(e/1e3-this.epoch|0);return t.set([s>>>24,s>>16&255,s>>8&255,255&s]),t}parse(e){const t=new Uint8Array(this.size);return this.base.decodeBytes(e,t),{epoch:1e3*(this.u32(t)+this.epoch),id:t.slice(4)}}}class o extends r{constructor(e){super(8,Object.assign({epoch:16e11,bytes:12},e))}timeOnlyBinary(e=Date.now()){const t=new Uint8Array(this.size),s=this.ensureTime(e-this.epoch),i=s/4294967296>>>0,n=(4294967295&s)>>>0;return t.set([i>>>24,i>>16&255,i>>8&255,255&i,n>>>24,n>>16&255,n>>8&255,255&n]),t}parse(e){const t=new Uint8Array(this.size);return this.base.decodeBytes(e,t),{epoch:4294967296*this.u32(t)+this.u32(t,4)+this.epoch,id:t.slice(8)}}}e.AKSUID=r,e.KSUID32=h,e.KSUID64=o,e.defKSUID32=e=>new h(e),e.defKSUID64=e=>new o(e),Object.defineProperty(e,"__esModule",{value:!0})})); |
{ | ||
"name": "@thi.ng/ksuid", | ||
"version": "0.2.6", | ||
"description": "Configurable K-sortable unique identifiers, binary & base-N encoded", | ||
"version": "0.3.0", | ||
"description": "Configurable sortable unique IDs, binary & base-N encoded, 32/64bit time resolution", | ||
"module": "./index.js", | ||
@@ -53,2 +53,4 @@ "main": "./lib/index.js", | ||
"keywords": [ | ||
"32bit", | ||
"64bit", | ||
"base62", | ||
@@ -65,2 +67,3 @@ "bigint", | ||
"id", | ||
"millisecond", | ||
"random", | ||
@@ -81,6 +84,5 @@ "sort", | ||
], | ||
"status": "alpha", | ||
"year": 2020 | ||
}, | ||
"gitHead": "5a289330f80e3c253c3b434655825c5dcfaebfd2" | ||
"gitHead": "0c3f02d2670775eefda522bcee8789f1a686b1fc" | ||
} |
@@ -24,3 +24,3 @@ <!-- This file is generated - DO NOT EDIT! --> | ||
Configurable K-sortable unique identifiers, binary & base-N encoded. | ||
Configurable sortable unique IDs, binary & base-N encoded, 32/64bit time resolution. | ||
@@ -30,15 +30,17 @@ Idea based on [segmentio/ksuid](https://github.com/segmentio/ksuid), though with | ||
- Configurable bit size (default: 128bits) | ||
- Configurable bit size (default: 160 bits) | ||
- Base-N encoding scheme (default: base62, see | ||
[@thi.ng/base-n](https://github.com/thi-ng/umbrella/tree/develop/packages/base-n) | ||
for alternatives) | ||
- Timestamp resolution (seconds [32 bits], milliseconds [64 bits]) | ||
- Epoch start time offset | ||
- Time-only base ID generation (optional) | ||
- KSUID parsing / decomposition | ||
- Configurable RNG source (default: `window.crypto` or `Math.random`) | ||
- Configurable RNG source (default: `window.crypto`, `Math.random` fallback) | ||
KSUIDs generated w/ this package consist of the lower 32bits of an Unix epoch | ||
(potentially time shifted to free up bits for future timestamps) and N bits of a | ||
random payload (from a configurable source). IDs can be generated as byte arrays | ||
or base-N encoded strings. For the latter, the JS runtime MUST support | ||
KSUIDs generated w/ this package are composed from a 32 bit or 64 bit Unix epoch | ||
(by default time shifted to free up bits for future timestamps) and N additional | ||
bits of a random payload (from a configurable source). IDs can be generated as | ||
byte arrays or base-N encoded strings. For the latter, the JS runtime MUST | ||
support | ||
[`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt). | ||
@@ -50,3 +52,3 @@ | ||
**ALPHA** - bleeding edge / work-in-progress | ||
**STABLE** - used in production | ||
@@ -74,3 +76,3 @@ [Search or submit any issues for this package](https://github.com/thi-ng/umbrella/issues?q=%5Bksuid%5D+in%3Atitle) | ||
Package sizes (gzipped, pre-treeshake): ESM: 563 bytes / CJS: 620 bytes / UMD: 743 bytes | ||
Package sizes (gzipped, pre-treeshake): ESM: 730 bytes / CJS: 798 bytes / UMD: 907 bytes | ||
@@ -89,6 +91,8 @@ ## Dependencies | ||
```ts | ||
import { defKSUID } from "@thi.ng/ksuid"; | ||
import { defKSUID32, defKSUID64 } from "@thi.ng/ksuid"; | ||
// init w/ defaults | ||
const id = defKSUID(); | ||
// init 32bit epoch (resolution: seconds) w/ defaults | ||
const id = defKSUID32(); | ||
// init 64bit epoch (resolution: milliseconds), same API | ||
const id = defKSUID64(); | ||
@@ -130,3 +134,3 @@ id.next(); | ||
// no time shift, 64bit random | ||
const id36 = defKSUID({ base: BASE36, epoch: 0, bytes: 8 }); | ||
const id36 = defKSUID32({ base: BASE36, epoch: 0, bytes: 8 }); | ||
// '2VOUKH4K59AG0RXR4XH' | ||
@@ -133,0 +137,0 @@ ``` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
43998
18
377
179
1