@tnid/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +509 -0
  3. package/esm/_dnt.shims.d.ts +5 -0
  4. package/esm/_dnt.shims.d.ts.map +1 -0
  5. package/esm/_dnt.shims.js +61 -0
  6. package/esm/bits.d.ts +18 -0
  7. package/esm/bits.d.ts.map +1 -0
  8. package/esm/bits.js +91 -0
  9. package/esm/data_encoding.d.ts +16 -0
  10. package/esm/data_encoding.d.ts.map +1 -0
  11. package/esm/data_encoding.js +103 -0
  12. package/esm/dynamic.d.ts +36 -0
  13. package/esm/dynamic.d.ts.map +1 -0
  14. package/esm/dynamic.js +173 -0
  15. package/esm/factory.d.ts +27 -0
  16. package/esm/factory.d.ts.map +1 -0
  17. package/esm/factory.js +107 -0
  18. package/esm/index.d.ts +5 -0
  19. package/esm/index.d.ts.map +1 -0
  20. package/esm/index.js +9 -0
  21. package/esm/name_encoding.d.ts +15 -0
  22. package/esm/name_encoding.d.ts.map +1 -0
  23. package/esm/name_encoding.js +92 -0
  24. package/esm/package.json +3 -0
  25. package/esm/types.d.ts +93 -0
  26. package/esm/types.d.ts.map +1 -0
  27. package/esm/types.js +4 -0
  28. package/esm/uuid.d.ts +17 -0
  29. package/esm/uuid.d.ts.map +1 -0
  30. package/esm/uuid.js +55 -0
  31. package/esm/uuidlike.d.ts +17 -0
  32. package/esm/uuidlike.d.ts.map +1 -0
  33. package/esm/uuidlike.js +38 -0
  34. package/package.json +37 -0
  35. package/script/_dnt.shims.d.ts +5 -0
  36. package/script/_dnt.shims.d.ts.map +1 -0
  37. package/script/_dnt.shims.js +65 -0
  38. package/script/bits.d.ts +18 -0
  39. package/script/bits.d.ts.map +1 -0
  40. package/script/bits.js +134 -0
  41. package/script/data_encoding.d.ts +16 -0
  42. package/script/data_encoding.d.ts.map +1 -0
  43. package/script/data_encoding.js +108 -0
  44. package/script/dynamic.d.ts +36 -0
  45. package/script/dynamic.d.ts.map +1 -0
  46. package/script/dynamic.js +176 -0
  47. package/script/factory.d.ts +27 -0
  48. package/script/factory.d.ts.map +1 -0
  49. package/script/factory.js +110 -0
  50. package/script/index.d.ts +5 -0
  51. package/script/index.d.ts.map +1 -0
  52. package/script/index.js +15 -0
  53. package/script/name_encoding.d.ts +15 -0
  54. package/script/name_encoding.d.ts.map +1 -0
  55. package/script/name_encoding.js +97 -0
  56. package/script/package.json +3 -0
  57. package/script/types.d.ts +93 -0
  58. package/script/types.d.ts.map +1 -0
  59. package/script/types.js +5 -0
  60. package/script/uuid.d.ts +17 -0
  61. package/script/uuid.d.ts.map +1 -0
  62. package/script/uuid.js +65 -0
  63. package/script/uuidlike.d.ts +17 -0
  64. package/script/uuidlike.d.ts.map +1 -0
  65. package/script/uuidlike.js +41 -0
package/esm/bits.js ADDED
@@ -0,0 +1,91 @@
1
+ // =============================================================================
2
+ // Bit Manipulation and TNID Generation
3
+ // Mask-based approach matching Rust implementation
4
+ // =============================================================================
5
+ // Masks for UUID version 8 and variant bits
6
+ import * as dntShim from "./_dnt.shims.js";
7
+ export const UUID_V8_MASK = 0x00000000000080008000000000000000n;
8
+ // V1: 100 random bits scattered across the UUID
9
+ export const V1_RANDOM_MASK = 0x00000fffffff0fff0fffffffffffffffn;
10
+ // V0: 57 random bits in the lower portion
11
+ export const V0_RANDOM_MASK = 0x000000000000000001ffffffffffffffn;
12
+ // V0 timestamp masks (for extracting and placing 43-bit timestamp)
13
+ const TIMESTAMP_FIRST_28_MASK = 0x000007ffffff8000n;
14
+ const TIMESTAMP_SECOND_12_MASK = 0x0000000000007ff8n;
15
+ const TIMESTAMP_LAST_3_MASK = 0x0000000000000007n;
16
+ /** Place name bits in their correct position (bits 108-127) */
17
+ export function nameMask(nameBits) {
18
+ return BigInt(nameBits) << 108n;
19
+ }
20
+ /** Place UUID version/variant and TNID variant in their correct positions */
21
+ export function uuidAndVariantMask(tnidVariant) {
22
+ return UUID_V8_MASK | ((tnidVariant & 3n) << 60n);
23
+ }
24
+ /** Scatter 43-bit timestamp into its three positions within the 128-bit ID */
25
+ export function millisMask(millisSinceEpoch) {
26
+ let mask = 0n;
27
+ // First 28 bits of timestamp → bits 80-107 of UUID
28
+ const first28LeadingZeros = 64n - 43n; // = 21, so mask starts at bit 42
29
+ mask |= (millisSinceEpoch & TIMESTAMP_FIRST_28_MASK) <<
30
+ (first28LeadingZeros + 64n - 20n);
31
+ // Middle 12 bits of timestamp → bits 64-75 of UUID (after version nibble)
32
+ const second12LeadingZeros = 64n - 15n; // = 49
33
+ mask |= (millisSinceEpoch & TIMESTAMP_SECOND_12_MASK) <<
34
+ (second12LeadingZeros + 64n - 52n);
35
+ // Last 3 bits of timestamp → bits 57-59 of UUID (after variant bits)
36
+ const last3LeadingZeros = 64n - 3n; // = 61
37
+ mask |= (millisSinceEpoch & TIMESTAMP_LAST_3_MASK) <<
38
+ (last3LeadingZeros + 64n - 68n);
39
+ return mask;
40
+ }
41
+ /** Build a 128-bit TNID value using mask-based OR operations */
42
+ export function buildTnidValue(nameBits, payloadMask, tnidVariant) {
43
+ return nameMask(nameBits) | uuidAndVariantMask(tnidVariant) | payloadMask;
44
+ }
45
+ /** Convert 128-bit value to byte array */
46
+ export function valueToBytes(value) {
47
+ const bytes = new Uint8Array(16);
48
+ let v = value;
49
+ for (let i = 15; i >= 0; i--) {
50
+ bytes[i] = Number(v & 0xffn);
51
+ v >>= 8n;
52
+ }
53
+ return bytes;
54
+ }
55
+ /** Generate a V0 (time-ordered) TNID as bytes */
56
+ export function generateV0(nameBits, timestampMs, randomBits) {
57
+ const timestamp = timestampMs !== undefined
58
+ ? timestampMs & ((1n << 43n) - 1n)
59
+ : BigInt(Date.now()) & ((1n << 43n) - 1n);
60
+ let random;
61
+ if (randomBits !== undefined) {
62
+ random = randomBits;
63
+ }
64
+ else {
65
+ const randomBytes = dntShim.crypto.getRandomValues(new Uint8Array(8));
66
+ random = 0n;
67
+ for (const byte of randomBytes) {
68
+ random = (random << 8n) | BigInt(byte);
69
+ }
70
+ }
71
+ const payloadMask = millisMask(timestamp) | (random & V0_RANDOM_MASK);
72
+ const value = buildTnidValue(nameBits, payloadMask, 0n);
73
+ return valueToBytes(value);
74
+ }
75
+ /** Generate a V1 (high-entropy random) TNID as bytes */
76
+ export function generateV1(nameBits, randomBits) {
77
+ let random;
78
+ if (randomBits !== undefined) {
79
+ random = randomBits;
80
+ }
81
+ else {
82
+ const randomBytes = dntShim.crypto.getRandomValues(new Uint8Array(16));
83
+ random = 0n;
84
+ for (const byte of randomBytes) {
85
+ random = (random << 8n) | BigInt(byte);
86
+ }
87
+ }
88
+ const payloadMask = random & V1_RANDOM_MASK;
89
+ const value = buildTnidValue(nameBits, payloadMask, 1n);
90
+ return valueToBytes(value);
91
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Encode a 16-byte TNID value as a 17-character data string.
3
+ */
4
+ export declare function encodeData(bytes: Uint8Array): string;
5
+ /**
6
+ * Decode a 17-character data string to data bits and TNID variant.
7
+ */
8
+ export declare function decodeData(encoded: string): {
9
+ dataBits: bigint;
10
+ tnidVariant: number;
11
+ };
12
+ /**
13
+ * Convert data bits back to a 16-byte array.
14
+ */
15
+ export declare function dataBitsToBytes(dataBits: bigint, nameBits: number): Uint8Array;
16
+ //# sourceMappingURL=data_encoding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data_encoding.d.ts","sourceRoot":"","sources":["../src/data_encoding.ts"],"names":[],"mappings":"AA6BA;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAgCpD;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,GACd;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAmB3C;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CA8B9E"}
@@ -0,0 +1,103 @@
1
+ // =============================================================================
2
+ // Data Encoding - 6-bit encoding for the data portion
3
+ // 64 characters in order: - 0-9 A-Z _ a-z
4
+ // =============================================================================
5
+ const DATA_CHAR_TO_VALUE = {};
6
+ const DATA_VALUE_TO_CHAR = [];
7
+ const DATA_ENCODING_ORDER = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
8
+ for (let i = 0; i < DATA_ENCODING_ORDER.length; i++) {
9
+ const char = DATA_ENCODING_ORDER[i];
10
+ DATA_CHAR_TO_VALUE[char] = i;
11
+ DATA_VALUE_TO_CHAR[i] = char;
12
+ }
13
+ // The data portion is 102 bits encoded as 17 6-bit characters (102 = 17 * 6)
14
+ //
15
+ // 128-bit layout (MSB = bit 0):
16
+ // Bits 0-19: Name (20 bits) - NOT in data encoding
17
+ // Bits 20-47: Payload A (28 bits)
18
+ // Bits 48-51: UUID version (4 bits) - NOT in data encoding
19
+ // Bits 52-63: Payload B (12 bits)
20
+ // Bits 64-65: UUID variant (2 bits) - NOT in data encoding
21
+ // Bits 66-67: TNID variant (2 bits)
22
+ // Bits 68-127: Payload C (60 bits)
23
+ //
24
+ // Data bits = PayloadA(28) + PayloadB(12) + TNIDVariant(2) + PayloadC(60) = 102 bits
25
+ /**
26
+ * Encode a 16-byte TNID value as a 17-character data string.
27
+ */
28
+ export function encodeData(bytes) {
29
+ // Convert bytes to BigInt (MSB first)
30
+ let value = 0n;
31
+ for (const byte of bytes) {
32
+ value = (value << 8n) | BigInt(byte);
33
+ }
34
+ // Extract the parts (positions from LSB = 127 - MSB_position)
35
+ // MSB bits 20-47 = LSB bits 80-107 (28 bits)
36
+ const payloadA = (value >> 80n) & ((1n << 28n) - 1n);
37
+ // MSB bits 52-63 = LSB bits 64-75 (12 bits)
38
+ const payloadB = (value >> 64n) & ((1n << 12n) - 1n);
39
+ // MSB bits 66-67 = LSB bits 60-61 (2 bits)
40
+ const tnidVariant = (value >> 60n) & 3n;
41
+ // MSB bits 68-127 = LSB bits 0-59 (60 bits)
42
+ const payloadC = value & ((1n << 60n) - 1n);
43
+ // Combine into 102 data bits: A(28) + B(12) + variant(2) + C(60)
44
+ const dataBits = (payloadA << 74n) | (payloadB << 62n) |
45
+ (tnidVariant << 60n) | payloadC;
46
+ // Encode as 17 6-bit characters
47
+ let result = "";
48
+ for (let i = 16; i >= 0; i--) {
49
+ const charValue = Number((dataBits >> BigInt(i * 6)) & 0x3fn);
50
+ result += DATA_VALUE_TO_CHAR[charValue];
51
+ }
52
+ return result;
53
+ }
54
+ /**
55
+ * Decode a 17-character data string to data bits and TNID variant.
56
+ */
57
+ export function decodeData(encoded) {
58
+ if (encoded.length !== 17) {
59
+ throw new Error(`Invalid data length: expected 17, got ${encoded.length}`);
60
+ }
61
+ let dataBits = 0n;
62
+ for (let i = 0; i < 17; i++) {
63
+ const char = encoded[i];
64
+ const value = DATA_CHAR_TO_VALUE[char];
65
+ if (value === undefined) {
66
+ throw new Error(`Invalid data character: ${char}`);
67
+ }
68
+ dataBits = (dataBits << 6n) | BigInt(value);
69
+ }
70
+ // Extract TNID variant (bits 60-61 from the right of the 102-bit value)
71
+ const tnidVariant = Number((dataBits >> 60n) & 3n);
72
+ return { dataBits, tnidVariant };
73
+ }
74
+ /**
75
+ * Convert data bits back to a 16-byte array.
76
+ */
77
+ export function dataBitsToBytes(dataBits, nameBits) {
78
+ // Reconstruct the full 128-bit value from 102 data bits
79
+ // Data bits layout: payloadA(28) + payloadB(12) + tnid_var(2) + payloadC(60) = 102 bits
80
+ //
81
+ // 128-bit layout:
82
+ // name(20) + payloadA(28) + uuid_ver(4) + payloadB(12) + uuid_var(2) + tnid_var(2) + payloadC(60)
83
+ // Extract from dataBits (102 bits total):
84
+ const payloadA = (dataBits >> 74n) & ((1n << 28n) - 1n); // bits 74-101
85
+ const payloadB = (dataBits >> 62n) & ((1n << 12n) - 1n); // bits 62-73
86
+ const tnidVariant = (dataBits >> 60n) & 3n; // bits 60-61
87
+ const payloadC = dataBits & ((1n << 60n) - 1n); // bits 0-59
88
+ // Reconstruct 128-bit value
89
+ let value = BigInt(nameBits);
90
+ value = (value << 28n) | payloadA;
91
+ value = (value << 4n) | 0x8n; // UUID version 8
92
+ value = (value << 12n) | payloadB;
93
+ value = (value << 2n) | 2n; // UUID variant
94
+ value = (value << 2n) | tnidVariant;
95
+ value = (value << 60n) | payloadC;
96
+ // Convert to bytes
97
+ const bytes = new Uint8Array(16);
98
+ for (let i = 15; i >= 0; i--) {
99
+ bytes[i] = Number(value & 0xffn);
100
+ value >>= 8n;
101
+ }
102
+ return bytes;
103
+ }
@@ -0,0 +1,36 @@
1
+ import type { Case, TnidValue, TnidVariant } from "./types.js";
2
+ /** A TNID that can hold any name. Use for generic functions. */
3
+ export type DynamicTnid = TnidValue<string>;
4
+ declare function getTnidVariantImpl(id: DynamicTnid): TnidVariant;
5
+ declare function toUuidStringImpl(id: DynamicTnid, upperCase?: boolean): string;
6
+ /** Static methods for working with any TNID regardless of name. */
7
+ export declare const DynamicTnid: {
8
+ /** Generate a new time-sortable TNID (variant 0) with runtime name validation. */
9
+ readonly new_v0: (name: string) => DynamicTnid;
10
+ /** Alias for new_v0. */
11
+ readonly new_time_ordered: (name: string) => DynamicTnid;
12
+ /** Generate a new time-sortable TNID with a specific timestamp. */
13
+ readonly new_v0_with_time: (name: string, time: Date) => DynamicTnid;
14
+ /** Generate a new time-sortable TNID with explicit timestamp and random components. */
15
+ readonly new_v0_with_parts: (name: string, epochMillis: bigint, random: bigint) => DynamicTnid;
16
+ /** Generate a new high-entropy TNID (variant 1) with runtime name validation. */
17
+ readonly new_v1: (name: string) => DynamicTnid;
18
+ /** Alias for new_v1. */
19
+ readonly new_high_entropy: (name: string) => DynamicTnid;
20
+ /** Generate a new high-entropy TNID with explicit random bits. */
21
+ readonly new_v1_with_random: (name: string, randomBits: bigint) => DynamicTnid;
22
+ /** Parse any valid TNID string. */
23
+ readonly parse: (s: string) => DynamicTnid;
24
+ /** Parse a UUID hex string into a DynamicTnid (validates TNID structure). */
25
+ readonly parse_uuid_string: (uuid: string) => DynamicTnid;
26
+ /** Get the name from a TNID. */
27
+ readonly getName: (id: DynamicTnid) => string;
28
+ /** Get the name encoded as a 5-character hex string. */
29
+ readonly getNameHex: (id: DynamicTnid) => string;
30
+ /** Get the variant of a TNID. */
31
+ readonly getVariant: (id: DynamicTnid) => TnidVariant;
32
+ /** Convert to UUID hex string format. */
33
+ readonly toUuidString: (id: DynamicTnid, caseFormat?: Case) => string;
34
+ };
35
+ export { getTnidVariantImpl, toUuidStringImpl, };
36
+ //# sourceMappingURL=dynamic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic.d.ts","sourceRoot":"","sources":["../src/dynamic.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE/D,gEAAgE;AAChE,MAAM,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;AAgB5C,iBAAS,kBAAkB,CAAC,EAAE,EAAE,WAAW,GAAG,WAAW,CASxD;AAiBD,iBAAS,gBAAgB,CAAC,EAAE,EAAE,WAAW,EAAE,SAAS,GAAE,OAAe,GAAG,MAAM,CAmB7E;AAmDD,mEAAmE;AACnE,eAAO,MAAM,WAAW;IACtB,kFAAkF;4BACrE,MAAM,KAAG,WAAW;IAYjC,wBAAwB;sCACD,MAAM,KAAG,WAAW;IAI3C,mEAAmE;sCAC5C,MAAM,QAAQ,IAAI,KAAG,WAAW;IAavD,uFAAuF;uCAE/E,MAAM,eACC,MAAM,UACX,MAAM,KACb,WAAW;IAYd,iFAAiF;4BACpE,MAAM,KAAG,WAAW;IAYjC,wBAAwB;sCACD,MAAM,KAAG,WAAW;IAI3C,kEAAkE;wCACzC,MAAM,cAAc,MAAM,KAAG,WAAW;IAYjE,mCAAmC;wBAC1B,MAAM,KAAG,WAAW;IAI7B,6EAA6E;uCACrD,MAAM,KAAG,WAAW;IAI5C,gCAAgC;2BACpB,WAAW,KAAG,MAAM;IAIhC,wDAAwD;8BACzC,WAAW,KAAG,MAAM;IAInC,iCAAiC;8BAClB,WAAW,KAAG,WAAW;IAIxC,yCAAyC;gCACxB,WAAW,eAAc,IAAI,KAAa,MAAM;CAGzD,CAAC;AAGX,OAAO,EACL,kBAAkB,EAClB,gBAAgB,GACjB,CAAC"}
package/esm/dynamic.js ADDED
@@ -0,0 +1,173 @@
1
+ // =============================================================================
2
+ // DynamicTnid - Runtime name validation
3
+ // =============================================================================
4
+ import { encodeName, decodeName, isValidNameRuntime } from "./name_encoding.js";
5
+ import { encodeData, decodeData, dataBitsToBytes } from "./data_encoding.js";
6
+ import { generateV0, generateV1 } from "./bits.js";
7
+ import { parseUuidStringToValue, validateUuidBits, extractNameBitsFromValue, valueToTnidString, valueToUuidString, } from "./uuid.js";
8
+ // -----------------------------------------------------------------------------
9
+ // Internal Helper Functions
10
+ // -----------------------------------------------------------------------------
11
+ function getTnidVariantImpl(id) {
12
+ const dotIndex = id.indexOf(".");
13
+ if (dotIndex === -1) {
14
+ throw new Error("Invalid TNID: missing separator");
15
+ }
16
+ const dataEncoded = id.substring(dotIndex + 1);
17
+ const { tnidVariant } = decodeData(dataEncoded);
18
+ const variants = ["v0", "v1", "v2", "v3"];
19
+ return variants[tnidVariant];
20
+ }
21
+ function getTnidNameImpl(id) {
22
+ const dotIndex = id.indexOf(".");
23
+ if (dotIndex === -1) {
24
+ throw new Error("Invalid TNID: missing separator");
25
+ }
26
+ return id.substring(0, dotIndex);
27
+ }
28
+ function getNameHexImpl(id) {
29
+ const name = getTnidNameImpl(id);
30
+ const nameBits = encodeName(name);
31
+ // Format as 5 hex characters (20 bits)
32
+ return nameBits.toString(16).padStart(5, "0");
33
+ }
34
+ function toUuidStringImpl(id, upperCase = false) {
35
+ const dotIndex = id.indexOf(".");
36
+ if (dotIndex === -1) {
37
+ throw new Error("Invalid TNID: missing separator");
38
+ }
39
+ const name = id.substring(0, dotIndex);
40
+ const dataEncoded = id.substring(dotIndex + 1);
41
+ const nameBits = encodeName(name);
42
+ const { dataBits } = decodeData(dataEncoded);
43
+ const bytes = dataBitsToBytes(dataBits, nameBits);
44
+ let value = 0n;
45
+ for (const byte of bytes) {
46
+ value = (value << 8n) | BigInt(byte);
47
+ }
48
+ return valueToUuidString(value, upperCase);
49
+ }
50
+ function parseDynamicTnidImpl(s) {
51
+ const dotIndex = s.indexOf(".");
52
+ if (dotIndex === -1) {
53
+ throw new Error("Invalid TNID string: missing '.' separator");
54
+ }
55
+ const name = s.substring(0, dotIndex);
56
+ const dataEncoded = s.substring(dotIndex + 1);
57
+ if (!isValidNameRuntime(name)) {
58
+ throw new Error(`Invalid TNID name: "${name}"`);
59
+ }
60
+ // Validate data portion
61
+ const nameBits = encodeName(name);
62
+ const { dataBits } = decodeData(dataEncoded);
63
+ // Verify we can reconstruct it (validates the encoding)
64
+ const reconstructed = dataBitsToBytes(dataBits, nameBits);
65
+ const reencoded = encodeData(reconstructed);
66
+ if (reencoded !== dataEncoded) {
67
+ throw new Error("Invalid TNID data encoding");
68
+ }
69
+ return s;
70
+ }
71
+ function parseDynamicUuidStringImpl(uuid) {
72
+ const value = parseUuidStringToValue(uuid);
73
+ if (!validateUuidBits(value)) {
74
+ throw new Error("Invalid TNID: not a valid UUIDv8");
75
+ }
76
+ const nameBits = extractNameBitsFromValue(value);
77
+ const name = decodeName(nameBits);
78
+ if (!isValidNameRuntime(name)) {
79
+ throw new Error(`Invalid TNID: invalid name encoding`);
80
+ }
81
+ return valueToTnidString(value);
82
+ }
83
+ // -----------------------------------------------------------------------------
84
+ // DynamicTnid Namespace
85
+ // -----------------------------------------------------------------------------
86
+ /** Static methods for working with any TNID regardless of name. */
87
+ export const DynamicTnid = {
88
+ /** Generate a new time-sortable TNID (variant 0) with runtime name validation. */
89
+ new_v0(name) {
90
+ if (!isValidNameRuntime(name)) {
91
+ throw new Error(`Invalid TNID name: "${name}". Must be 1-4 characters of: 0-4, a-z`);
92
+ }
93
+ const nameBits = encodeName(name);
94
+ const bytes = generateV0(nameBits);
95
+ const dataEncoded = encodeData(bytes);
96
+ return `${name}.${dataEncoded}`;
97
+ },
98
+ /** Alias for new_v0. */
99
+ new_time_ordered(name) {
100
+ return DynamicTnid.new_v0(name);
101
+ },
102
+ /** Generate a new time-sortable TNID with a specific timestamp. */
103
+ new_v0_with_time(name, time) {
104
+ if (!isValidNameRuntime(name)) {
105
+ throw new Error(`Invalid TNID name: "${name}". Must be 1-4 characters of: 0-4, a-z`);
106
+ }
107
+ const nameBits = encodeName(name);
108
+ const timestampMs = BigInt(time.getTime());
109
+ const bytes = generateV0(nameBits, timestampMs);
110
+ const dataEncoded = encodeData(bytes);
111
+ return `${name}.${dataEncoded}`;
112
+ },
113
+ /** Generate a new time-sortable TNID with explicit timestamp and random components. */
114
+ new_v0_with_parts(name, epochMillis, random) {
115
+ if (!isValidNameRuntime(name)) {
116
+ throw new Error(`Invalid TNID name: "${name}". Must be 1-4 characters of: 0-4, a-z`);
117
+ }
118
+ const nameBits = encodeName(name);
119
+ const bytes = generateV0(nameBits, epochMillis, random);
120
+ const dataEncoded = encodeData(bytes);
121
+ return `${name}.${dataEncoded}`;
122
+ },
123
+ /** Generate a new high-entropy TNID (variant 1) with runtime name validation. */
124
+ new_v1(name) {
125
+ if (!isValidNameRuntime(name)) {
126
+ throw new Error(`Invalid TNID name: "${name}". Must be 1-4 characters of: 0-4, a-z`);
127
+ }
128
+ const nameBits = encodeName(name);
129
+ const bytes = generateV1(nameBits);
130
+ const dataEncoded = encodeData(bytes);
131
+ return `${name}.${dataEncoded}`;
132
+ },
133
+ /** Alias for new_v1. */
134
+ new_high_entropy(name) {
135
+ return DynamicTnid.new_v1(name);
136
+ },
137
+ /** Generate a new high-entropy TNID with explicit random bits. */
138
+ new_v1_with_random(name, randomBits) {
139
+ if (!isValidNameRuntime(name)) {
140
+ throw new Error(`Invalid TNID name: "${name}". Must be 1-4 characters of: 0-4, a-z`);
141
+ }
142
+ const nameBits = encodeName(name);
143
+ const bytes = generateV1(nameBits, randomBits);
144
+ const dataEncoded = encodeData(bytes);
145
+ return `${name}.${dataEncoded}`;
146
+ },
147
+ /** Parse any valid TNID string. */
148
+ parse(s) {
149
+ return parseDynamicTnidImpl(s);
150
+ },
151
+ /** Parse a UUID hex string into a DynamicTnid (validates TNID structure). */
152
+ parse_uuid_string(uuid) {
153
+ return parseDynamicUuidStringImpl(uuid);
154
+ },
155
+ /** Get the name from a TNID. */
156
+ getName(id) {
157
+ return getTnidNameImpl(id);
158
+ },
159
+ /** Get the name encoded as a 5-character hex string. */
160
+ getNameHex(id) {
161
+ return getNameHexImpl(id);
162
+ },
163
+ /** Get the variant of a TNID. */
164
+ getVariant(id) {
165
+ return getTnidVariantImpl(id);
166
+ },
167
+ /** Convert to UUID hex string format. */
168
+ toUuidString(id, caseFormat = "lower") {
169
+ return toUuidStringImpl(id, caseFormat === "upper");
170
+ },
171
+ };
172
+ // Export helper functions for use by factory.ts
173
+ export { getTnidVariantImpl, toUuidStringImpl, };
@@ -0,0 +1,27 @@
1
+ import type { NamedTnid, ValidateName } from "./types.js";
2
+ /**
3
+ * Create a NamedTnid for the given name.
4
+ *
5
+ * The name is validated at **compile time** - only 1-4 characters using `0-4` and `a-z`.
6
+ * Invalid names will produce a TypeScript error.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const UserId = Tnid("user");
11
+ * type UserId = TnidType<typeof UserId>;
12
+ *
13
+ * const id = UserId.new_v0(); // Generate new ID
14
+ * const parsed = UserId.parse(str); // Parse existing ID
15
+ * ```
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * // These produce compile errors:
20
+ * Tnid("users") // Too long (max 4 chars)
21
+ * Tnid("User") // Uppercase not allowed
22
+ * Tnid("a-b") // Hyphen not allowed
23
+ * Tnid("5") // Only digits 0-4 allowed
24
+ * ```
25
+ */
26
+ export declare function Tnid<const Name extends string>(name: ValidateName<Name>): NamedTnid<Name>;
27
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,SAAS,EAGT,YAAY,EACb,MAAM,YAAY,CAAC;AAapB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,IAAI,CAAC,KAAK,CAAC,IAAI,SAAS,MAAM,EAC5C,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,GACvB,SAAS,CAAC,IAAI,CAAC,CAsGjB"}
package/esm/factory.js ADDED
@@ -0,0 +1,107 @@
1
+ // =============================================================================
2
+ // Tnid Function
3
+ // =============================================================================
4
+ import { encodeName, decodeName, isValidNameRuntime } from "./name_encoding.js";
5
+ import { encodeData, decodeData, dataBitsToBytes } from "./data_encoding.js";
6
+ import { generateV0, generateV1 } from "./bits.js";
7
+ import { parseUuidStringToValue, validateUuidBits, extractNameBitsFromValue, valueToTnidString, } from "./uuid.js";
8
+ import { getTnidVariantImpl, toUuidStringImpl } from "./dynamic.js";
9
+ /**
10
+ * Create a NamedTnid for the given name.
11
+ *
12
+ * The name is validated at **compile time** - only 1-4 characters using `0-4` and `a-z`.
13
+ * Invalid names will produce a TypeScript error.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const UserId = Tnid("user");
18
+ * type UserId = TnidType<typeof UserId>;
19
+ *
20
+ * const id = UserId.new_v0(); // Generate new ID
21
+ * const parsed = UserId.parse(str); // Parse existing ID
22
+ * ```
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * // These produce compile errors:
27
+ * Tnid("users") // Too long (max 4 chars)
28
+ * Tnid("User") // Uppercase not allowed
29
+ * Tnid("a-b") // Hyphen not allowed
30
+ * Tnid("5") // Only digits 0-4 allowed
31
+ * ```
32
+ */
33
+ export function Tnid(name) {
34
+ // Runtime validation (belt and suspenders)
35
+ if (!isValidNameRuntime(name)) {
36
+ throw new Error(`Invalid TNID name: "${name}". Must be 1-4 characters of: 0-4, a-z`);
37
+ }
38
+ const nameBits = encodeName(name);
39
+ const tnid = {
40
+ name: name,
41
+ new_v0() {
42
+ const bytes = generateV0(nameBits);
43
+ const dataEncoded = encodeData(bytes);
44
+ return `${name}.${dataEncoded}`;
45
+ },
46
+ new_v1() {
47
+ const bytes = generateV1(nameBits);
48
+ const dataEncoded = encodeData(bytes);
49
+ return `${name}.${dataEncoded}`;
50
+ },
51
+ v0_from_parts(timestampMs, randomBits) {
52
+ const bytes = generateV0(nameBits, timestampMs, randomBits);
53
+ const dataEncoded = encodeData(bytes);
54
+ return `${name}.${dataEncoded}`;
55
+ },
56
+ v1_from_parts(randomBits) {
57
+ const bytes = generateV1(nameBits, randomBits);
58
+ const dataEncoded = encodeData(bytes);
59
+ return `${name}.${dataEncoded}`;
60
+ },
61
+ parse(s) {
62
+ const dotIndex = s.indexOf(".");
63
+ if (dotIndex === -1) {
64
+ throw new Error(`Invalid TNID string: missing '.' separator`);
65
+ }
66
+ const parsedName = s.substring(0, dotIndex);
67
+ const dataEncoded = s.substring(dotIndex + 1);
68
+ if (parsedName !== name) {
69
+ throw new Error(`TNID name mismatch: expected "${name}", got "${parsedName}"`);
70
+ }
71
+ if (!isValidNameRuntime(parsedName)) {
72
+ throw new Error(`Invalid TNID name in string: "${parsedName}"`);
73
+ }
74
+ // Validate data portion
75
+ const { dataBits } = decodeData(dataEncoded);
76
+ // Verify we can reconstruct it (validates the encoding)
77
+ const reconstructed = dataBitsToBytes(dataBits, nameBits);
78
+ const reencoded = encodeData(reconstructed);
79
+ if (reencoded !== dataEncoded) {
80
+ throw new Error(`Invalid TNID data encoding`);
81
+ }
82
+ return s;
83
+ },
84
+ parseUuidString(uuid) {
85
+ const value = parseUuidStringToValue(uuid);
86
+ if (!validateUuidBits(value)) {
87
+ throw new Error("Invalid TNID: not a valid UUIDv8");
88
+ }
89
+ const uuidNameBits = extractNameBitsFromValue(value);
90
+ if (uuidNameBits !== nameBits) {
91
+ const foundName = decodeName(uuidNameBits);
92
+ throw new Error(`TNID name mismatch: expected "${name}", got "${foundName}"`);
93
+ }
94
+ return valueToTnidString(value);
95
+ },
96
+ nameHex() {
97
+ return nameBits.toString(16).padStart(5, "0");
98
+ },
99
+ variant(id) {
100
+ return getTnidVariantImpl(id);
101
+ },
102
+ toUuidString(id, caseFormat = "lower") {
103
+ return toUuidStringImpl(id, caseFormat === "upper");
104
+ },
105
+ };
106
+ return tnid;
107
+ }
package/esm/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export type { TnidValue, TnidVariant, Case, NamedTnid, TnidType, } from "./types.js";
2
+ export { DynamicTnid } from "./dynamic.js";
3
+ export { UuidLike } from "./uuidlike.js";
4
+ export { Tnid } from "./factory.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,YAAY,EACV,SAAS,EACT,WAAW,EACX,IAAI,EACJ,SAAS,EACT,QAAQ,GACT,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC"}
package/esm/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // =============================================================================
2
+ // @tnid/core - Public API
3
+ // =============================================================================
4
+ // DynamicTnid (type + namespace merged in dynamic.ts)
5
+ export { DynamicTnid } from "./dynamic.js";
6
+ // UuidLike (type + namespace merged in uuidlike.ts)
7
+ export { UuidLike } from "./uuidlike.js";
8
+ // Tnid function
9
+ export { Tnid } from "./factory.js";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Encode a name into 20 bits (4 chars * 5 bits each).
3
+ * Null-pads on the right (least significant bits).
4
+ */
5
+ export declare function encodeName(name: string): number;
6
+ /**
7
+ * Decode 20 bits back to a name string.
8
+ */
9
+ export declare function decodeName(encoded: number): string;
10
+ /**
11
+ * Validate a name at runtime.
12
+ * Must be 1-4 characters, each being 0-4 or a-z.
13
+ */
14
+ export declare function isValidNameRuntime(name: string): boolean;
15
+ //# sourceMappingURL=name_encoding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"name_encoding.d.ts","sourceRoot":"","sources":["../src/name_encoding.ts"],"names":[],"mappings":"AA8CA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAc/C;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAalD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMxD"}