@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.
- package/LICENSE +21 -0
- package/README.md +509 -0
- package/esm/_dnt.shims.d.ts +5 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +61 -0
- package/esm/bits.d.ts +18 -0
- package/esm/bits.d.ts.map +1 -0
- package/esm/bits.js +91 -0
- package/esm/data_encoding.d.ts +16 -0
- package/esm/data_encoding.d.ts.map +1 -0
- package/esm/data_encoding.js +103 -0
- package/esm/dynamic.d.ts +36 -0
- package/esm/dynamic.d.ts.map +1 -0
- package/esm/dynamic.js +173 -0
- package/esm/factory.d.ts +27 -0
- package/esm/factory.d.ts.map +1 -0
- package/esm/factory.js +107 -0
- package/esm/index.d.ts +5 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +9 -0
- package/esm/name_encoding.d.ts +15 -0
- package/esm/name_encoding.d.ts.map +1 -0
- package/esm/name_encoding.js +92 -0
- package/esm/package.json +3 -0
- package/esm/types.d.ts +93 -0
- package/esm/types.d.ts.map +1 -0
- package/esm/types.js +4 -0
- package/esm/uuid.d.ts +17 -0
- package/esm/uuid.d.ts.map +1 -0
- package/esm/uuid.js +55 -0
- package/esm/uuidlike.d.ts +17 -0
- package/esm/uuidlike.d.ts.map +1 -0
- package/esm/uuidlike.js +38 -0
- package/package.json +37 -0
- package/script/_dnt.shims.d.ts +5 -0
- package/script/_dnt.shims.d.ts.map +1 -0
- package/script/_dnt.shims.js +65 -0
- package/script/bits.d.ts +18 -0
- package/script/bits.d.ts.map +1 -0
- package/script/bits.js +134 -0
- package/script/data_encoding.d.ts +16 -0
- package/script/data_encoding.d.ts.map +1 -0
- package/script/data_encoding.js +108 -0
- package/script/dynamic.d.ts +36 -0
- package/script/dynamic.d.ts.map +1 -0
- package/script/dynamic.js +176 -0
- package/script/factory.d.ts +27 -0
- package/script/factory.d.ts.map +1 -0
- package/script/factory.js +110 -0
- package/script/index.d.ts +5 -0
- package/script/index.d.ts.map +1 -0
- package/script/index.js +15 -0
- package/script/name_encoding.d.ts +15 -0
- package/script/name_encoding.d.ts.map +1 -0
- package/script/name_encoding.js +97 -0
- package/script/package.json +3 -0
- package/script/types.d.ts +93 -0
- package/script/types.d.ts.map +1 -0
- package/script/types.js +5 -0
- package/script/uuid.d.ts +17 -0
- package/script/uuid.d.ts.map +1 -0
- package/script/uuid.js +65 -0
- package/script/uuidlike.d.ts +17 -0
- package/script/uuidlike.d.ts.map +1 -0
- package/script/uuidlike.js +41 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Data Encoding - 6-bit encoding for the data portion
|
|
4
|
+
// 64 characters in order: - 0-9 A-Z _ a-z
|
|
5
|
+
// =============================================================================
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.encodeData = encodeData;
|
|
8
|
+
exports.decodeData = decodeData;
|
|
9
|
+
exports.dataBitsToBytes = dataBitsToBytes;
|
|
10
|
+
const DATA_CHAR_TO_VALUE = {};
|
|
11
|
+
const DATA_VALUE_TO_CHAR = [];
|
|
12
|
+
const DATA_ENCODING_ORDER = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
|
|
13
|
+
for (let i = 0; i < DATA_ENCODING_ORDER.length; i++) {
|
|
14
|
+
const char = DATA_ENCODING_ORDER[i];
|
|
15
|
+
DATA_CHAR_TO_VALUE[char] = i;
|
|
16
|
+
DATA_VALUE_TO_CHAR[i] = char;
|
|
17
|
+
}
|
|
18
|
+
// The data portion is 102 bits encoded as 17 6-bit characters (102 = 17 * 6)
|
|
19
|
+
//
|
|
20
|
+
// 128-bit layout (MSB = bit 0):
|
|
21
|
+
// Bits 0-19: Name (20 bits) - NOT in data encoding
|
|
22
|
+
// Bits 20-47: Payload A (28 bits)
|
|
23
|
+
// Bits 48-51: UUID version (4 bits) - NOT in data encoding
|
|
24
|
+
// Bits 52-63: Payload B (12 bits)
|
|
25
|
+
// Bits 64-65: UUID variant (2 bits) - NOT in data encoding
|
|
26
|
+
// Bits 66-67: TNID variant (2 bits)
|
|
27
|
+
// Bits 68-127: Payload C (60 bits)
|
|
28
|
+
//
|
|
29
|
+
// Data bits = PayloadA(28) + PayloadB(12) + TNIDVariant(2) + PayloadC(60) = 102 bits
|
|
30
|
+
/**
|
|
31
|
+
* Encode a 16-byte TNID value as a 17-character data string.
|
|
32
|
+
*/
|
|
33
|
+
function encodeData(bytes) {
|
|
34
|
+
// Convert bytes to BigInt (MSB first)
|
|
35
|
+
let value = 0n;
|
|
36
|
+
for (const byte of bytes) {
|
|
37
|
+
value = (value << 8n) | BigInt(byte);
|
|
38
|
+
}
|
|
39
|
+
// Extract the parts (positions from LSB = 127 - MSB_position)
|
|
40
|
+
// MSB bits 20-47 = LSB bits 80-107 (28 bits)
|
|
41
|
+
const payloadA = (value >> 80n) & ((1n << 28n) - 1n);
|
|
42
|
+
// MSB bits 52-63 = LSB bits 64-75 (12 bits)
|
|
43
|
+
const payloadB = (value >> 64n) & ((1n << 12n) - 1n);
|
|
44
|
+
// MSB bits 66-67 = LSB bits 60-61 (2 bits)
|
|
45
|
+
const tnidVariant = (value >> 60n) & 3n;
|
|
46
|
+
// MSB bits 68-127 = LSB bits 0-59 (60 bits)
|
|
47
|
+
const payloadC = value & ((1n << 60n) - 1n);
|
|
48
|
+
// Combine into 102 data bits: A(28) + B(12) + variant(2) + C(60)
|
|
49
|
+
const dataBits = (payloadA << 74n) | (payloadB << 62n) |
|
|
50
|
+
(tnidVariant << 60n) | payloadC;
|
|
51
|
+
// Encode as 17 6-bit characters
|
|
52
|
+
let result = "";
|
|
53
|
+
for (let i = 16; i >= 0; i--) {
|
|
54
|
+
const charValue = Number((dataBits >> BigInt(i * 6)) & 0x3fn);
|
|
55
|
+
result += DATA_VALUE_TO_CHAR[charValue];
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Decode a 17-character data string to data bits and TNID variant.
|
|
61
|
+
*/
|
|
62
|
+
function decodeData(encoded) {
|
|
63
|
+
if (encoded.length !== 17) {
|
|
64
|
+
throw new Error(`Invalid data length: expected 17, got ${encoded.length}`);
|
|
65
|
+
}
|
|
66
|
+
let dataBits = 0n;
|
|
67
|
+
for (let i = 0; i < 17; i++) {
|
|
68
|
+
const char = encoded[i];
|
|
69
|
+
const value = DATA_CHAR_TO_VALUE[char];
|
|
70
|
+
if (value === undefined) {
|
|
71
|
+
throw new Error(`Invalid data character: ${char}`);
|
|
72
|
+
}
|
|
73
|
+
dataBits = (dataBits << 6n) | BigInt(value);
|
|
74
|
+
}
|
|
75
|
+
// Extract TNID variant (bits 60-61 from the right of the 102-bit value)
|
|
76
|
+
const tnidVariant = Number((dataBits >> 60n) & 3n);
|
|
77
|
+
return { dataBits, tnidVariant };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Convert data bits back to a 16-byte array.
|
|
81
|
+
*/
|
|
82
|
+
function dataBitsToBytes(dataBits, nameBits) {
|
|
83
|
+
// Reconstruct the full 128-bit value from 102 data bits
|
|
84
|
+
// Data bits layout: payloadA(28) + payloadB(12) + tnid_var(2) + payloadC(60) = 102 bits
|
|
85
|
+
//
|
|
86
|
+
// 128-bit layout:
|
|
87
|
+
// name(20) + payloadA(28) + uuid_ver(4) + payloadB(12) + uuid_var(2) + tnid_var(2) + payloadC(60)
|
|
88
|
+
// Extract from dataBits (102 bits total):
|
|
89
|
+
const payloadA = (dataBits >> 74n) & ((1n << 28n) - 1n); // bits 74-101
|
|
90
|
+
const payloadB = (dataBits >> 62n) & ((1n << 12n) - 1n); // bits 62-73
|
|
91
|
+
const tnidVariant = (dataBits >> 60n) & 3n; // bits 60-61
|
|
92
|
+
const payloadC = dataBits & ((1n << 60n) - 1n); // bits 0-59
|
|
93
|
+
// Reconstruct 128-bit value
|
|
94
|
+
let value = BigInt(nameBits);
|
|
95
|
+
value = (value << 28n) | payloadA;
|
|
96
|
+
value = (value << 4n) | 0x8n; // UUID version 8
|
|
97
|
+
value = (value << 12n) | payloadB;
|
|
98
|
+
value = (value << 2n) | 2n; // UUID variant
|
|
99
|
+
value = (value << 2n) | tnidVariant;
|
|
100
|
+
value = (value << 60n) | payloadC;
|
|
101
|
+
// Convert to bytes
|
|
102
|
+
const bytes = new Uint8Array(16);
|
|
103
|
+
for (let i = 15; i >= 0; i--) {
|
|
104
|
+
bytes[i] = Number(value & 0xffn);
|
|
105
|
+
value >>= 8n;
|
|
106
|
+
}
|
|
107
|
+
return bytes;
|
|
108
|
+
}
|
|
@@ -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"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// DynamicTnid - Runtime name validation
|
|
4
|
+
// =============================================================================
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DynamicTnid = void 0;
|
|
7
|
+
exports.getTnidVariantImpl = getTnidVariantImpl;
|
|
8
|
+
exports.toUuidStringImpl = toUuidStringImpl;
|
|
9
|
+
const name_encoding_js_1 = require("./name_encoding.js");
|
|
10
|
+
const data_encoding_js_1 = require("./data_encoding.js");
|
|
11
|
+
const bits_js_1 = require("./bits.js");
|
|
12
|
+
const uuid_js_1 = require("./uuid.js");
|
|
13
|
+
// -----------------------------------------------------------------------------
|
|
14
|
+
// Internal Helper Functions
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
function getTnidVariantImpl(id) {
|
|
17
|
+
const dotIndex = id.indexOf(".");
|
|
18
|
+
if (dotIndex === -1) {
|
|
19
|
+
throw new Error("Invalid TNID: missing separator");
|
|
20
|
+
}
|
|
21
|
+
const dataEncoded = id.substring(dotIndex + 1);
|
|
22
|
+
const { tnidVariant } = (0, data_encoding_js_1.decodeData)(dataEncoded);
|
|
23
|
+
const variants = ["v0", "v1", "v2", "v3"];
|
|
24
|
+
return variants[tnidVariant];
|
|
25
|
+
}
|
|
26
|
+
function getTnidNameImpl(id) {
|
|
27
|
+
const dotIndex = id.indexOf(".");
|
|
28
|
+
if (dotIndex === -1) {
|
|
29
|
+
throw new Error("Invalid TNID: missing separator");
|
|
30
|
+
}
|
|
31
|
+
return id.substring(0, dotIndex);
|
|
32
|
+
}
|
|
33
|
+
function getNameHexImpl(id) {
|
|
34
|
+
const name = getTnidNameImpl(id);
|
|
35
|
+
const nameBits = (0, name_encoding_js_1.encodeName)(name);
|
|
36
|
+
// Format as 5 hex characters (20 bits)
|
|
37
|
+
return nameBits.toString(16).padStart(5, "0");
|
|
38
|
+
}
|
|
39
|
+
function toUuidStringImpl(id, upperCase = false) {
|
|
40
|
+
const dotIndex = id.indexOf(".");
|
|
41
|
+
if (dotIndex === -1) {
|
|
42
|
+
throw new Error("Invalid TNID: missing separator");
|
|
43
|
+
}
|
|
44
|
+
const name = id.substring(0, dotIndex);
|
|
45
|
+
const dataEncoded = id.substring(dotIndex + 1);
|
|
46
|
+
const nameBits = (0, name_encoding_js_1.encodeName)(name);
|
|
47
|
+
const { dataBits } = (0, data_encoding_js_1.decodeData)(dataEncoded);
|
|
48
|
+
const bytes = (0, data_encoding_js_1.dataBitsToBytes)(dataBits, nameBits);
|
|
49
|
+
let value = 0n;
|
|
50
|
+
for (const byte of bytes) {
|
|
51
|
+
value = (value << 8n) | BigInt(byte);
|
|
52
|
+
}
|
|
53
|
+
return (0, uuid_js_1.valueToUuidString)(value, upperCase);
|
|
54
|
+
}
|
|
55
|
+
function parseDynamicTnidImpl(s) {
|
|
56
|
+
const dotIndex = s.indexOf(".");
|
|
57
|
+
if (dotIndex === -1) {
|
|
58
|
+
throw new Error("Invalid TNID string: missing '.' separator");
|
|
59
|
+
}
|
|
60
|
+
const name = s.substring(0, dotIndex);
|
|
61
|
+
const dataEncoded = s.substring(dotIndex + 1);
|
|
62
|
+
if (!(0, name_encoding_js_1.isValidNameRuntime)(name)) {
|
|
63
|
+
throw new Error(`Invalid TNID name: "${name}"`);
|
|
64
|
+
}
|
|
65
|
+
// Validate data portion
|
|
66
|
+
const nameBits = (0, name_encoding_js_1.encodeName)(name);
|
|
67
|
+
const { dataBits } = (0, data_encoding_js_1.decodeData)(dataEncoded);
|
|
68
|
+
// Verify we can reconstruct it (validates the encoding)
|
|
69
|
+
const reconstructed = (0, data_encoding_js_1.dataBitsToBytes)(dataBits, nameBits);
|
|
70
|
+
const reencoded = (0, data_encoding_js_1.encodeData)(reconstructed);
|
|
71
|
+
if (reencoded !== dataEncoded) {
|
|
72
|
+
throw new Error("Invalid TNID data encoding");
|
|
73
|
+
}
|
|
74
|
+
return s;
|
|
75
|
+
}
|
|
76
|
+
function parseDynamicUuidStringImpl(uuid) {
|
|
77
|
+
const value = (0, uuid_js_1.parseUuidStringToValue)(uuid);
|
|
78
|
+
if (!(0, uuid_js_1.validateUuidBits)(value)) {
|
|
79
|
+
throw new Error("Invalid TNID: not a valid UUIDv8");
|
|
80
|
+
}
|
|
81
|
+
const nameBits = (0, uuid_js_1.extractNameBitsFromValue)(value);
|
|
82
|
+
const name = (0, name_encoding_js_1.decodeName)(nameBits);
|
|
83
|
+
if (!(0, name_encoding_js_1.isValidNameRuntime)(name)) {
|
|
84
|
+
throw new Error(`Invalid TNID: invalid name encoding`);
|
|
85
|
+
}
|
|
86
|
+
return (0, uuid_js_1.valueToTnidString)(value);
|
|
87
|
+
}
|
|
88
|
+
// -----------------------------------------------------------------------------
|
|
89
|
+
// DynamicTnid Namespace
|
|
90
|
+
// -----------------------------------------------------------------------------
|
|
91
|
+
/** Static methods for working with any TNID regardless of name. */
|
|
92
|
+
exports.DynamicTnid = {
|
|
93
|
+
/** Generate a new time-sortable TNID (variant 0) with runtime name validation. */
|
|
94
|
+
new_v0(name) {
|
|
95
|
+
if (!(0, name_encoding_js_1.isValidNameRuntime)(name)) {
|
|
96
|
+
throw new Error(`Invalid TNID name: "${name}". Must be 1-4 characters of: 0-4, a-z`);
|
|
97
|
+
}
|
|
98
|
+
const nameBits = (0, name_encoding_js_1.encodeName)(name);
|
|
99
|
+
const bytes = (0, bits_js_1.generateV0)(nameBits);
|
|
100
|
+
const dataEncoded = (0, data_encoding_js_1.encodeData)(bytes);
|
|
101
|
+
return `${name}.${dataEncoded}`;
|
|
102
|
+
},
|
|
103
|
+
/** Alias for new_v0. */
|
|
104
|
+
new_time_ordered(name) {
|
|
105
|
+
return exports.DynamicTnid.new_v0(name);
|
|
106
|
+
},
|
|
107
|
+
/** Generate a new time-sortable TNID with a specific timestamp. */
|
|
108
|
+
new_v0_with_time(name, time) {
|
|
109
|
+
if (!(0, name_encoding_js_1.isValidNameRuntime)(name)) {
|
|
110
|
+
throw new Error(`Invalid TNID name: "${name}". Must be 1-4 characters of: 0-4, a-z`);
|
|
111
|
+
}
|
|
112
|
+
const nameBits = (0, name_encoding_js_1.encodeName)(name);
|
|
113
|
+
const timestampMs = BigInt(time.getTime());
|
|
114
|
+
const bytes = (0, bits_js_1.generateV0)(nameBits, timestampMs);
|
|
115
|
+
const dataEncoded = (0, data_encoding_js_1.encodeData)(bytes);
|
|
116
|
+
return `${name}.${dataEncoded}`;
|
|
117
|
+
},
|
|
118
|
+
/** Generate a new time-sortable TNID with explicit timestamp and random components. */
|
|
119
|
+
new_v0_with_parts(name, epochMillis, random) {
|
|
120
|
+
if (!(0, name_encoding_js_1.isValidNameRuntime)(name)) {
|
|
121
|
+
throw new Error(`Invalid TNID name: "${name}". Must be 1-4 characters of: 0-4, a-z`);
|
|
122
|
+
}
|
|
123
|
+
const nameBits = (0, name_encoding_js_1.encodeName)(name);
|
|
124
|
+
const bytes = (0, bits_js_1.generateV0)(nameBits, epochMillis, random);
|
|
125
|
+
const dataEncoded = (0, data_encoding_js_1.encodeData)(bytes);
|
|
126
|
+
return `${name}.${dataEncoded}`;
|
|
127
|
+
},
|
|
128
|
+
/** Generate a new high-entropy TNID (variant 1) with runtime name validation. */
|
|
129
|
+
new_v1(name) {
|
|
130
|
+
if (!(0, name_encoding_js_1.isValidNameRuntime)(name)) {
|
|
131
|
+
throw new Error(`Invalid TNID name: "${name}". Must be 1-4 characters of: 0-4, a-z`);
|
|
132
|
+
}
|
|
133
|
+
const nameBits = (0, name_encoding_js_1.encodeName)(name);
|
|
134
|
+
const bytes = (0, bits_js_1.generateV1)(nameBits);
|
|
135
|
+
const dataEncoded = (0, data_encoding_js_1.encodeData)(bytes);
|
|
136
|
+
return `${name}.${dataEncoded}`;
|
|
137
|
+
},
|
|
138
|
+
/** Alias for new_v1. */
|
|
139
|
+
new_high_entropy(name) {
|
|
140
|
+
return exports.DynamicTnid.new_v1(name);
|
|
141
|
+
},
|
|
142
|
+
/** Generate a new high-entropy TNID with explicit random bits. */
|
|
143
|
+
new_v1_with_random(name, randomBits) {
|
|
144
|
+
if (!(0, name_encoding_js_1.isValidNameRuntime)(name)) {
|
|
145
|
+
throw new Error(`Invalid TNID name: "${name}". Must be 1-4 characters of: 0-4, a-z`);
|
|
146
|
+
}
|
|
147
|
+
const nameBits = (0, name_encoding_js_1.encodeName)(name);
|
|
148
|
+
const bytes = (0, bits_js_1.generateV1)(nameBits, randomBits);
|
|
149
|
+
const dataEncoded = (0, data_encoding_js_1.encodeData)(bytes);
|
|
150
|
+
return `${name}.${dataEncoded}`;
|
|
151
|
+
},
|
|
152
|
+
/** Parse any valid TNID string. */
|
|
153
|
+
parse(s) {
|
|
154
|
+
return parseDynamicTnidImpl(s);
|
|
155
|
+
},
|
|
156
|
+
/** Parse a UUID hex string into a DynamicTnid (validates TNID structure). */
|
|
157
|
+
parse_uuid_string(uuid) {
|
|
158
|
+
return parseDynamicUuidStringImpl(uuid);
|
|
159
|
+
},
|
|
160
|
+
/** Get the name from a TNID. */
|
|
161
|
+
getName(id) {
|
|
162
|
+
return getTnidNameImpl(id);
|
|
163
|
+
},
|
|
164
|
+
/** Get the name encoded as a 5-character hex string. */
|
|
165
|
+
getNameHex(id) {
|
|
166
|
+
return getNameHexImpl(id);
|
|
167
|
+
},
|
|
168
|
+
/** Get the variant of a TNID. */
|
|
169
|
+
getVariant(id) {
|
|
170
|
+
return getTnidVariantImpl(id);
|
|
171
|
+
},
|
|
172
|
+
/** Convert to UUID hex string format. */
|
|
173
|
+
toUuidString(id, caseFormat = "lower") {
|
|
174
|
+
return toUuidStringImpl(id, caseFormat === "upper");
|
|
175
|
+
},
|
|
176
|
+
};
|
|
@@ -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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Tnid Function
|
|
4
|
+
// =============================================================================
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Tnid = Tnid;
|
|
7
|
+
const name_encoding_js_1 = require("./name_encoding.js");
|
|
8
|
+
const data_encoding_js_1 = require("./data_encoding.js");
|
|
9
|
+
const bits_js_1 = require("./bits.js");
|
|
10
|
+
const uuid_js_1 = require("./uuid.js");
|
|
11
|
+
const dynamic_js_1 = require("./dynamic.js");
|
|
12
|
+
/**
|
|
13
|
+
* Create a NamedTnid for the given name.
|
|
14
|
+
*
|
|
15
|
+
* The name is validated at **compile time** - only 1-4 characters using `0-4` and `a-z`.
|
|
16
|
+
* Invalid names will produce a TypeScript error.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const UserId = Tnid("user");
|
|
21
|
+
* type UserId = TnidType<typeof UserId>;
|
|
22
|
+
*
|
|
23
|
+
* const id = UserId.new_v0(); // Generate new ID
|
|
24
|
+
* const parsed = UserId.parse(str); // Parse existing ID
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* // These produce compile errors:
|
|
30
|
+
* Tnid("users") // Too long (max 4 chars)
|
|
31
|
+
* Tnid("User") // Uppercase not allowed
|
|
32
|
+
* Tnid("a-b") // Hyphen not allowed
|
|
33
|
+
* Tnid("5") // Only digits 0-4 allowed
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
function Tnid(name) {
|
|
37
|
+
// Runtime validation (belt and suspenders)
|
|
38
|
+
if (!(0, name_encoding_js_1.isValidNameRuntime)(name)) {
|
|
39
|
+
throw new Error(`Invalid TNID name: "${name}". Must be 1-4 characters of: 0-4, a-z`);
|
|
40
|
+
}
|
|
41
|
+
const nameBits = (0, name_encoding_js_1.encodeName)(name);
|
|
42
|
+
const tnid = {
|
|
43
|
+
name: name,
|
|
44
|
+
new_v0() {
|
|
45
|
+
const bytes = (0, bits_js_1.generateV0)(nameBits);
|
|
46
|
+
const dataEncoded = (0, data_encoding_js_1.encodeData)(bytes);
|
|
47
|
+
return `${name}.${dataEncoded}`;
|
|
48
|
+
},
|
|
49
|
+
new_v1() {
|
|
50
|
+
const bytes = (0, bits_js_1.generateV1)(nameBits);
|
|
51
|
+
const dataEncoded = (0, data_encoding_js_1.encodeData)(bytes);
|
|
52
|
+
return `${name}.${dataEncoded}`;
|
|
53
|
+
},
|
|
54
|
+
v0_from_parts(timestampMs, randomBits) {
|
|
55
|
+
const bytes = (0, bits_js_1.generateV0)(nameBits, timestampMs, randomBits);
|
|
56
|
+
const dataEncoded = (0, data_encoding_js_1.encodeData)(bytes);
|
|
57
|
+
return `${name}.${dataEncoded}`;
|
|
58
|
+
},
|
|
59
|
+
v1_from_parts(randomBits) {
|
|
60
|
+
const bytes = (0, bits_js_1.generateV1)(nameBits, randomBits);
|
|
61
|
+
const dataEncoded = (0, data_encoding_js_1.encodeData)(bytes);
|
|
62
|
+
return `${name}.${dataEncoded}`;
|
|
63
|
+
},
|
|
64
|
+
parse(s) {
|
|
65
|
+
const dotIndex = s.indexOf(".");
|
|
66
|
+
if (dotIndex === -1) {
|
|
67
|
+
throw new Error(`Invalid TNID string: missing '.' separator`);
|
|
68
|
+
}
|
|
69
|
+
const parsedName = s.substring(0, dotIndex);
|
|
70
|
+
const dataEncoded = s.substring(dotIndex + 1);
|
|
71
|
+
if (parsedName !== name) {
|
|
72
|
+
throw new Error(`TNID name mismatch: expected "${name}", got "${parsedName}"`);
|
|
73
|
+
}
|
|
74
|
+
if (!(0, name_encoding_js_1.isValidNameRuntime)(parsedName)) {
|
|
75
|
+
throw new Error(`Invalid TNID name in string: "${parsedName}"`);
|
|
76
|
+
}
|
|
77
|
+
// Validate data portion
|
|
78
|
+
const { dataBits } = (0, data_encoding_js_1.decodeData)(dataEncoded);
|
|
79
|
+
// Verify we can reconstruct it (validates the encoding)
|
|
80
|
+
const reconstructed = (0, data_encoding_js_1.dataBitsToBytes)(dataBits, nameBits);
|
|
81
|
+
const reencoded = (0, data_encoding_js_1.encodeData)(reconstructed);
|
|
82
|
+
if (reencoded !== dataEncoded) {
|
|
83
|
+
throw new Error(`Invalid TNID data encoding`);
|
|
84
|
+
}
|
|
85
|
+
return s;
|
|
86
|
+
},
|
|
87
|
+
parseUuidString(uuid) {
|
|
88
|
+
const value = (0, uuid_js_1.parseUuidStringToValue)(uuid);
|
|
89
|
+
if (!(0, uuid_js_1.validateUuidBits)(value)) {
|
|
90
|
+
throw new Error("Invalid TNID: not a valid UUIDv8");
|
|
91
|
+
}
|
|
92
|
+
const uuidNameBits = (0, uuid_js_1.extractNameBitsFromValue)(value);
|
|
93
|
+
if (uuidNameBits !== nameBits) {
|
|
94
|
+
const foundName = (0, name_encoding_js_1.decodeName)(uuidNameBits);
|
|
95
|
+
throw new Error(`TNID name mismatch: expected "${name}", got "${foundName}"`);
|
|
96
|
+
}
|
|
97
|
+
return (0, uuid_js_1.valueToTnidString)(value);
|
|
98
|
+
},
|
|
99
|
+
nameHex() {
|
|
100
|
+
return nameBits.toString(16).padStart(5, "0");
|
|
101
|
+
},
|
|
102
|
+
variant(id) {
|
|
103
|
+
return (0, dynamic_js_1.getTnidVariantImpl)(id);
|
|
104
|
+
},
|
|
105
|
+
toUuidString(id, caseFormat = "lower") {
|
|
106
|
+
return (0, dynamic_js_1.toUuidStringImpl)(id, caseFormat === "upper");
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
return tnid;
|
|
110
|
+
}
|
|
@@ -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/script/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// @tnid/core - Public API
|
|
4
|
+
// =============================================================================
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Tnid = exports.UuidLike = exports.DynamicTnid = void 0;
|
|
7
|
+
// DynamicTnid (type + namespace merged in dynamic.ts)
|
|
8
|
+
var dynamic_js_1 = require("./dynamic.js");
|
|
9
|
+
Object.defineProperty(exports, "DynamicTnid", { enumerable: true, get: function () { return dynamic_js_1.DynamicTnid; } });
|
|
10
|
+
// UuidLike (type + namespace merged in uuidlike.ts)
|
|
11
|
+
var uuidlike_js_1 = require("./uuidlike.js");
|
|
12
|
+
Object.defineProperty(exports, "UuidLike", { enumerable: true, get: function () { return uuidlike_js_1.UuidLike; } });
|
|
13
|
+
// Tnid function
|
|
14
|
+
var factory_js_1 = require("./factory.js");
|
|
15
|
+
Object.defineProperty(exports, "Tnid", { enumerable: true, get: function () { return factory_js_1.Tnid; } });
|
|
@@ -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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Name Encoding - 5-bit encoding for TNID names
|
|
4
|
+
// Valid characters: 0-4, a-z (31 chars + null terminator = 32 = 2^5)
|
|
5
|
+
// =============================================================================
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.encodeName = encodeName;
|
|
8
|
+
exports.decodeName = decodeName;
|
|
9
|
+
exports.isValidNameRuntime = isValidNameRuntime;
|
|
10
|
+
const NAME_CHAR_TO_VALUE = {
|
|
11
|
+
"0": 1,
|
|
12
|
+
"1": 2,
|
|
13
|
+
"2": 3,
|
|
14
|
+
"3": 4,
|
|
15
|
+
"4": 5,
|
|
16
|
+
a: 6,
|
|
17
|
+
b: 7,
|
|
18
|
+
c: 8,
|
|
19
|
+
d: 9,
|
|
20
|
+
e: 10,
|
|
21
|
+
f: 11,
|
|
22
|
+
g: 12,
|
|
23
|
+
h: 13,
|
|
24
|
+
i: 14,
|
|
25
|
+
j: 15,
|
|
26
|
+
k: 16,
|
|
27
|
+
l: 17,
|
|
28
|
+
m: 18,
|
|
29
|
+
n: 19,
|
|
30
|
+
o: 20,
|
|
31
|
+
p: 21,
|
|
32
|
+
q: 22,
|
|
33
|
+
r: 23,
|
|
34
|
+
s: 24,
|
|
35
|
+
t: 25,
|
|
36
|
+
u: 26,
|
|
37
|
+
v: 27,
|
|
38
|
+
w: 28,
|
|
39
|
+
x: 29,
|
|
40
|
+
y: 30,
|
|
41
|
+
z: 31,
|
|
42
|
+
};
|
|
43
|
+
const NAME_VALUE_TO_CHAR = {};
|
|
44
|
+
for (const [char, value] of Object.entries(NAME_CHAR_TO_VALUE)) {
|
|
45
|
+
NAME_VALUE_TO_CHAR[value] = char;
|
|
46
|
+
}
|
|
47
|
+
const VALID_NAME_CHARS = new Set("01234abcdefghijklmnopqrstuvwxyz".split(""));
|
|
48
|
+
/**
|
|
49
|
+
* Encode a name into 20 bits (4 chars * 5 bits each).
|
|
50
|
+
* Null-pads on the right (least significant bits).
|
|
51
|
+
*/
|
|
52
|
+
function encodeName(name) {
|
|
53
|
+
let result = 0;
|
|
54
|
+
for (let i = 0; i < 4; i++) {
|
|
55
|
+
result <<= 5;
|
|
56
|
+
if (i < name.length) {
|
|
57
|
+
const value = NAME_CHAR_TO_VALUE[name[i]];
|
|
58
|
+
if (value === undefined) {
|
|
59
|
+
throw new Error(`Invalid name character: ${name[i]}`);
|
|
60
|
+
}
|
|
61
|
+
result |= value;
|
|
62
|
+
}
|
|
63
|
+
// else: null (0) is already the default
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Decode 20 bits back to a name string.
|
|
69
|
+
*/
|
|
70
|
+
function decodeName(encoded) {
|
|
71
|
+
let result = "";
|
|
72
|
+
for (let i = 0; i < 4; i++) {
|
|
73
|
+
const shift = (3 - i) * 5;
|
|
74
|
+
const value = (encoded >> shift) & 0x1f;
|
|
75
|
+
if (value === 0)
|
|
76
|
+
break; // null terminator
|
|
77
|
+
const char = NAME_VALUE_TO_CHAR[value];
|
|
78
|
+
if (!char) {
|
|
79
|
+
throw new Error(`Invalid encoded name value: ${value}`);
|
|
80
|
+
}
|
|
81
|
+
result += char;
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Validate a name at runtime.
|
|
87
|
+
* Must be 1-4 characters, each being 0-4 or a-z.
|
|
88
|
+
*/
|
|
89
|
+
function isValidNameRuntime(name) {
|
|
90
|
+
if (name.length < 1 || name.length > 4)
|
|
91
|
+
return false;
|
|
92
|
+
for (const char of name) {
|
|
93
|
+
if (!VALID_NAME_CHARS.has(char))
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
}
|