diff --git a/impl/ts/src/uxid.ts b/impl/ts/src/uxid.ts index 3b80f5b..9e4029c 100644 --- a/impl/ts/src/uxid.ts +++ b/impl/ts/src/uxid.ts @@ -1,47 +1,92 @@ -const CROCKFORD_ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" -const INVALID_REGEX = new RegExp(`[^${CROCKFORD_ENCODING}]`) -const TIME_MAX = Math.pow(2, 48) - 1 -const TIME_LEN = 10 +const CrockfordEncoding = [ + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", + "G", "H", "J", "K", "M", "N", "P", "Q", "R", "S", "T", "V", "W", "X", "Y", "Z" +] class UXID { - encoded: string; - time: number; + encoded: string + prefix: string + randSize: number + randEncoded: string + time: number + timeEncoded: string - static decode(encoded: string): UXID { - if (!encoded || encoded === "") { - throw "input is required" - } + static generate(prefix?: string, size?: string): string { + let uxid = new UXID(prefix, size) + return uxid.encoded + } + + constructor(prefix?: string, size?: string) { + this.prefix = prefix + this.time = (new Date).valueOf() + + switch (size) { + case "xs": + case "xsmall": + this.randSize = 0; + break; + + case "s": + case "small": + this.randSize = 2; + break; + + case "m": + case "medium": + this.randSize = 5; + break; + + case "l": + case "large": + this.randSize = 7; + break; - if (encoded.match(INVALID_REGEX)) { - throw `expected input to be a Base32 encoded string, got: '${encoded}'` + default: + this.randSize = 10; } - let uxid = new UXID(encoded) - uxid.decode() - return uxid + this.encode() } - static generate(): string { - return "UXID" - } + encode(): void { + this.encodeTime() + this.encodeRand() + + let binEncoded = this.timeEncoded + this.randEncoded - constructor(encoded?: string, time?: number) { - this.encoded = encoded - this.time = time + if (this.prefix) { + this.encoded = this.prefix + "_" + binEncoded + } else { + this.encoded = binEncoded + } } - decode(): void { - this.decodeTime() + encodeTime(): void { + let timeArray = new Uint8Array(6) + let timeView = new DataView(timeArray.buffer, 0, 6) + let timeChars = new Array(10) + + timeView.setUint16(0, (this.time / 4294967296.0) | 0); + timeView.setUint32(2, this.time | 0); + + timeChars[0] = CrockfordEncoding[(timeArray[0]&224)>>5] + timeChars[1] = CrockfordEncoding[timeArray[0]&31] + timeChars[2] = CrockfordEncoding[(timeArray[1]&248)>>3] + timeChars[3] = CrockfordEncoding[((timeArray[1]&7)<<2)|((timeArray[2]&192)>>6)] + timeChars[4] = CrockfordEncoding[(timeArray[2]&62)>>1] + timeChars[5] = CrockfordEncoding[((timeArray[2]&1)<<4)|((timeArray[3]&240)>>4)] + timeChars[6] = CrockfordEncoding[((timeArray[3]&15)<<1)|((timeArray[4]&128)>>7)] + timeChars[7] = CrockfordEncoding[(timeArray[4]&124)>>2] + timeChars[8] = CrockfordEncoding[((timeArray[4]&3)<<3)|((timeArray[5]&224)>>5)] + timeChars[9] = CrockfordEncoding[timeArray[5]&31] + + this.timeEncoded = timeChars.join("") } - decodeTime(): void { - this.time = this.encoded - .substr(0, TIME_LEN) - .split("") - .reverse() - .reduce((carry, char, index) => { - return (carry += CROCKFORD_ENCODING.indexOf(char) * Math.pow(32, index)) - }, 0) + encodeRand(): string { + let randEncoded = "rrrrrrrrrrrrrrrr" + this.randEncoded = randEncoded + return this.randEncoded } } diff --git a/impl/ts/test/spec/0001-setup-api.test.ts b/impl/ts/test/spec/0001-setup-api.test.ts deleted file mode 100644 index c3f685c..0000000 --- a/impl/ts/test/spec/0001-setup-api.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -// AUTOGENERATED FILE - DO NOT EDIT - -import UXID from "../../src/uxid" - - -describe("0001 setup api: api", () => { - - - test("UXID decode exists", () => { - const result = UXID.decode("01E9VB3RWNAR89HSKMS84K9HCS"); - expect(result).toBeDefined() - }); - -}); - - -describe("0001 setup api: decoder", () => { - - test("returns a UXID with input", () => { - const input_string = "01E9VB3RWNAR89HSKMS84K9HCS"; - - const uxid = UXID.decode(input_string); - expect(uxid).toBeInstanceOf(UXID) - expect(uxid.encoded).toBe("01E9VB3RWNAR89HSKMS84K9HCS"); - - - }); - - test("rejects blank strings", () => { - const input_string = ""; - - expect(() => { - UXID.decode(input_string); - }).toThrow("input is required") - - }); - -}); diff --git a/impl/ts/test/spec/0101-basic-decoding.test.ts b/impl/ts/test/spec/0101-basic-decoding.test.ts deleted file mode 100644 index a657634..0000000 --- a/impl/ts/test/spec/0101-basic-decoding.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -// AUTOGENERATED FILE - DO NOT EDIT - -import UXID from "../../src/uxid" - - -describe("0101 basic decoding: api", () => { - -}); - - -describe("0101 basic decoding: decoder", () => { - - test("accepts a ULID", () => { - const input_string = "01E9VB3RWNAR89HSKMS84K9HCS"; - - const uxid = UXID.decode(input_string); - expect(uxid).toBeInstanceOf(UXID) - - expect(uxid.time).toBe(1591129269141); - - }); - - test("accepts the maximum allowed timestamp", () => { - const input_string = "7ZZZZZZZZZZZZZZZZZZZZZZZZZ"; - - const uxid = UXID.decode(input_string); - expect(uxid).toBeInstanceOf(UXID) - - expect(uxid.time).toBe(281474976710655); - - }); - - test("rejects malformed strings", () => { - const input_string = "this is not a UXID"; - - expect(() => { - UXID.decode(input_string); - }).toThrow("expected input to be a Base32 encoded string, got: 'this is not a UXID'") - - }); - -}); diff --git a/impl/ts/test/uxid.test.ts b/impl/ts/test/uxid.test.ts index 38bb0ca..b9bda9f 100644 --- a/impl/ts/test/uxid.test.ts +++ b/impl/ts/test/uxid.test.ts @@ -1,6 +1,38 @@ "use strict"; -var UXID = require("../src/uxid").UXID +import UXID from "../src/uxid" test("UXID", () => { expect(UXID).not.toBeNull() }) + +test("UXID.generate()", () => { + const uxid = UXID.generate() + + expect(uxid).not.toBeNull() + expect(uxid.length).toBe(26) +}) + +test("UXID.generate(\"cus\")", () => { + const uxid = UXID.generate("cus") + + expect(uxid).not.toBeNull() + expect(uxid.length).toBe(30) +}) + +/* +test("UXID.generate(\"cus\", \"xs\")", () => { + const uxid = UXID.generate("cus", "xs") + console.log(uxid) + + expect(uxid).not.toBeNull() + expect(uxid.length).toBe(14) +}) + +test("UXID.generate(\"cus\", \"xl\")", () => { + const uxid = UXID.generate("cus", "xl") + console.log(uxid) + + expect(uxid).not.toBeNull() + expect(uxid.length).toBe(30) +}) +*/