@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,92 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Name Encoding - 5-bit encoding for TNID names
|
|
3
|
+
// Valid characters: 0-4, a-z (31 chars + null terminator = 32 = 2^5)
|
|
4
|
+
// =============================================================================
|
|
5
|
+
const NAME_CHAR_TO_VALUE = {
|
|
6
|
+
"0": 1,
|
|
7
|
+
"1": 2,
|
|
8
|
+
"2": 3,
|
|
9
|
+
"3": 4,
|
|
10
|
+
"4": 5,
|
|
11
|
+
a: 6,
|
|
12
|
+
b: 7,
|
|
13
|
+
c: 8,
|
|
14
|
+
d: 9,
|
|
15
|
+
e: 10,
|
|
16
|
+
f: 11,
|
|
17
|
+
g: 12,
|
|
18
|
+
h: 13,
|
|
19
|
+
i: 14,
|
|
20
|
+
j: 15,
|
|
21
|
+
k: 16,
|
|
22
|
+
l: 17,
|
|
23
|
+
m: 18,
|
|
24
|
+
n: 19,
|
|
25
|
+
o: 20,
|
|
26
|
+
p: 21,
|
|
27
|
+
q: 22,
|
|
28
|
+
r: 23,
|
|
29
|
+
s: 24,
|
|
30
|
+
t: 25,
|
|
31
|
+
u: 26,
|
|
32
|
+
v: 27,
|
|
33
|
+
w: 28,
|
|
34
|
+
x: 29,
|
|
35
|
+
y: 30,
|
|
36
|
+
z: 31,
|
|
37
|
+
};
|
|
38
|
+
const NAME_VALUE_TO_CHAR = {};
|
|
39
|
+
for (const [char, value] of Object.entries(NAME_CHAR_TO_VALUE)) {
|
|
40
|
+
NAME_VALUE_TO_CHAR[value] = char;
|
|
41
|
+
}
|
|
42
|
+
const VALID_NAME_CHARS = new Set("01234abcdefghijklmnopqrstuvwxyz".split(""));
|
|
43
|
+
/**
|
|
44
|
+
* Encode a name into 20 bits (4 chars * 5 bits each).
|
|
45
|
+
* Null-pads on the right (least significant bits).
|
|
46
|
+
*/
|
|
47
|
+
export function encodeName(name) {
|
|
48
|
+
let result = 0;
|
|
49
|
+
for (let i = 0; i < 4; i++) {
|
|
50
|
+
result <<= 5;
|
|
51
|
+
if (i < name.length) {
|
|
52
|
+
const value = NAME_CHAR_TO_VALUE[name[i]];
|
|
53
|
+
if (value === undefined) {
|
|
54
|
+
throw new Error(`Invalid name character: ${name[i]}`);
|
|
55
|
+
}
|
|
56
|
+
result |= value;
|
|
57
|
+
}
|
|
58
|
+
// else: null (0) is already the default
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Decode 20 bits back to a name string.
|
|
64
|
+
*/
|
|
65
|
+
export function decodeName(encoded) {
|
|
66
|
+
let result = "";
|
|
67
|
+
for (let i = 0; i < 4; i++) {
|
|
68
|
+
const shift = (3 - i) * 5;
|
|
69
|
+
const value = (encoded >> shift) & 0x1f;
|
|
70
|
+
if (value === 0)
|
|
71
|
+
break; // null terminator
|
|
72
|
+
const char = NAME_VALUE_TO_CHAR[value];
|
|
73
|
+
if (!char) {
|
|
74
|
+
throw new Error(`Invalid encoded name value: ${value}`);
|
|
75
|
+
}
|
|
76
|
+
result += char;
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Validate a name at runtime.
|
|
82
|
+
* Must be 1-4 characters, each being 0-4 or a-z.
|
|
83
|
+
*/
|
|
84
|
+
export function isValidNameRuntime(name) {
|
|
85
|
+
if (name.length < 1 || name.length > 4)
|
|
86
|
+
return false;
|
|
87
|
+
for (const char of name) {
|
|
88
|
+
if (!VALID_NAME_CHARS.has(char))
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
package/esm/package.json
ADDED
package/esm/types.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** Valid characters for TNID names (5-bit encoding): 0-4, a-z */
|
|
2
|
+
export type NameChar = "0" | "1" | "2" | "3" | "4" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z";
|
|
3
|
+
/** Recursive validation - check one character at a time to avoid type explosion */
|
|
4
|
+
type ValidateNameChars<S extends string> = S extends "" ? true : S extends `${infer First}${infer Rest}` ? First extends NameChar ? ValidateNameChars<Rest> : false : false;
|
|
5
|
+
/** Check string length is 1-4 using tuple length trick */
|
|
6
|
+
type StringLength<S extends string, Acc extends unknown[] = []> = S extends "" ? Acc["length"] : S extends `${infer _}${infer Rest}` ? StringLength<Rest, [...Acc, unknown]> : never;
|
|
7
|
+
type ValidLength = 1 | 2 | 3 | 4;
|
|
8
|
+
/**
|
|
9
|
+
* Validate a TNID name at compile time.
|
|
10
|
+
* Must be 1-4 characters, each being a valid NameChar.
|
|
11
|
+
*/
|
|
12
|
+
export type ValidateName<S extends string> = ValidateNameChars<S> extends true ? StringLength<S> extends ValidLength ? S : never : never;
|
|
13
|
+
/**
|
|
14
|
+
* A TNID value branded with its name. At runtime this is just a string.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* // Define a NamedTnid and matching type
|
|
19
|
+
* const UserId = Tnid("user");
|
|
20
|
+
* type UserId = TnidType<typeof UserId>;
|
|
21
|
+
*
|
|
22
|
+
* // Generate IDs
|
|
23
|
+
* const id: UserId = UserId.new_v0(); // time-sortable
|
|
24
|
+
* const id2: UserId = UserId.new_v1(); // high-entropy random
|
|
25
|
+
*
|
|
26
|
+
* // Parse from strings
|
|
27
|
+
* const parsed = UserId.parse("user.abc..."); // validates name matches
|
|
28
|
+
* const fromUuid = UserId.parseUuidString("d6157329-4640-8e30-...");
|
|
29
|
+
*
|
|
30
|
+
* // DynamicTnid - runtime name validation
|
|
31
|
+
* DynamicTnid.new_v0("item"); // create with runtime name
|
|
32
|
+
* DynamicTnid.new_v1("item"); // create with runtime name
|
|
33
|
+
* DynamicTnid.parse("post.xyz..."); // parse any TNID
|
|
34
|
+
* DynamicTnid.getName(id); // "user"
|
|
35
|
+
* DynamicTnid.getVariant(id); // "v0" | "v1" | "v2" | "v3"
|
|
36
|
+
*
|
|
37
|
+
* // UuidLike - wrapper for any UUID hex string
|
|
38
|
+
* UuidLike.fromTnid(id); // TNID to UUID string
|
|
39
|
+
* UuidLike.parse("d6157329-..."); // parse any UUID (format only)
|
|
40
|
+
* UuidLike.toTnid(uuid); // UUID to DynamicTnid (validates)
|
|
41
|
+
*
|
|
42
|
+
* // Type safety: different names are incompatible
|
|
43
|
+
* const PostId = Tnid("post");
|
|
44
|
+
* type PostId = TnidType<typeof PostId>;
|
|
45
|
+
* const postId: PostId = PostId.new_v0();
|
|
46
|
+
* // id = postId; // Compile error!
|
|
47
|
+
*
|
|
48
|
+
* // DynamicTnid type accepts any TNID
|
|
49
|
+
* function log(id: DynamicTnid) { console.log(DynamicTnid.getName(id)); }
|
|
50
|
+
* log(id); // works
|
|
51
|
+
* log(postId); // works
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export type TnidValue<Name extends string> = string & {
|
|
55
|
+
tnid: Name;
|
|
56
|
+
};
|
|
57
|
+
/** TNID variant: v0=time-ordered, v1=random, v2/v3=reserved */
|
|
58
|
+
export type TnidVariant = "v0" | "v1" | "v2" | "v3";
|
|
59
|
+
/** Case for UUID hex string formatting. */
|
|
60
|
+
export type Case = "lower" | "upper";
|
|
61
|
+
export interface NamedTnid<Name extends string> {
|
|
62
|
+
/** The TNID name */
|
|
63
|
+
readonly name: Name;
|
|
64
|
+
/** Generate a new time-sortable TNID (variant 0) */
|
|
65
|
+
new_v0(): TnidValue<Name>;
|
|
66
|
+
/** Generate a new random TNID (variant 1) */
|
|
67
|
+
new_v1(): TnidValue<Name>;
|
|
68
|
+
/** Construct a V0 TNID from specific parts (for deterministic testing) */
|
|
69
|
+
v0_from_parts(timestampMs: bigint, randomBits: bigint): TnidValue<Name>;
|
|
70
|
+
/** Construct a V1 TNID from specific parts (for deterministic testing) */
|
|
71
|
+
v1_from_parts(randomBits: bigint): TnidValue<Name>;
|
|
72
|
+
/**
|
|
73
|
+
* Parse and validate a TNID string.
|
|
74
|
+
* @throws Error if the string is invalid or the name doesn't match
|
|
75
|
+
*/
|
|
76
|
+
parse(s: string): TnidValue<Name>;
|
|
77
|
+
/**
|
|
78
|
+
* Parse a UUID hex string into a TNID.
|
|
79
|
+
* Validates that it's a valid UUIDv8 TNID and the name matches.
|
|
80
|
+
* @throws Error if the UUID is invalid or the name doesn't match
|
|
81
|
+
*/
|
|
82
|
+
parseUuidString(uuid: string): TnidValue<Name>;
|
|
83
|
+
/** Get the name encoded as a 5-character hex string. */
|
|
84
|
+
nameHex(): string;
|
|
85
|
+
/** Get the variant of a TNID. */
|
|
86
|
+
variant(id: TnidValue<Name>): TnidVariant;
|
|
87
|
+
/** Convert a TNID to UUID hex string format. */
|
|
88
|
+
toUuidString(id: TnidValue<Name>, caseFormat?: Case): string;
|
|
89
|
+
}
|
|
90
|
+
/** Extract the `TnidValue` type from a NamedTnid. */
|
|
91
|
+
export type TnidType<T> = T extends NamedTnid<infer N> ? TnidValue<N> : never;
|
|
92
|
+
export {};
|
|
93
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/esm/types.js
ADDED
package/esm/uuid.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TnidVariant } from "./types.js";
|
|
2
|
+
export declare const UUID_REGEX: RegExp;
|
|
3
|
+
/** Convert a 128-bit value to UUID hex string format */
|
|
4
|
+
export declare function valueToUuidString(value: bigint, upperCase?: boolean): string;
|
|
5
|
+
/** Parse a UUID hex string to a 128-bit value */
|
|
6
|
+
export declare function parseUuidStringToValue(uuid: string): bigint;
|
|
7
|
+
/** Extract name bits from a 128-bit TNID value */
|
|
8
|
+
export declare function extractNameBitsFromValue(value: bigint): number;
|
|
9
|
+
/** Extract TNID variant from a 128-bit value */
|
|
10
|
+
export declare function extractVariantFromValue(value: bigint): TnidVariant;
|
|
11
|
+
/** Validate that a 128-bit value has correct UUIDv8 version and variant bits */
|
|
12
|
+
export declare function validateUuidBits(value: bigint): boolean;
|
|
13
|
+
/** Convert bytes to TNID string */
|
|
14
|
+
export declare function bytesToTnidString(bytes: Uint8Array, name: string): string;
|
|
15
|
+
/** Convert 128-bit value to TNID string */
|
|
16
|
+
export declare function valueToTnidString(value: bigint): string;
|
|
17
|
+
//# sourceMappingURL=uuid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK9C,eAAO,MAAM,UAAU,QAC4C,CAAC;AAEpE,wDAAwD;AACxD,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,OAAe,GACzB,MAAM,CAMR;AAED,iDAAiD;AACjD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM3D;AAED,kDAAkD;AAClD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,gDAAgD;AAChD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAIlE;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAUvD;AAED,mCAAmC;AACnC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGzE;AAED,2CAA2C;AAC3C,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKvD"}
|
package/esm/uuid.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// UUID String Utilities
|
|
3
|
+
// =============================================================================
|
|
4
|
+
import { decodeName } from "./name_encoding.js";
|
|
5
|
+
import { encodeData } from "./data_encoding.js";
|
|
6
|
+
import { valueToBytes } from "./bits.js";
|
|
7
|
+
export const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
8
|
+
/** Convert a 128-bit value to UUID hex string format */
|
|
9
|
+
export function valueToUuidString(value, upperCase = false) {
|
|
10
|
+
const hex = value.toString(16).padStart(32, "0");
|
|
11
|
+
const uuid = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
12
|
+
return upperCase ? uuid.toUpperCase() : uuid.toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
/** Parse a UUID hex string to a 128-bit value */
|
|
15
|
+
export function parseUuidStringToValue(uuid) {
|
|
16
|
+
if (!UUID_REGEX.test(uuid)) {
|
|
17
|
+
throw new Error(`Invalid UUID format: ${uuid}`);
|
|
18
|
+
}
|
|
19
|
+
const hex = uuid.replace(/-/g, "");
|
|
20
|
+
return BigInt("0x" + hex);
|
|
21
|
+
}
|
|
22
|
+
/** Extract name bits from a 128-bit TNID value */
|
|
23
|
+
export function extractNameBitsFromValue(value) {
|
|
24
|
+
return Number((value >> 108n) & 0xfffffn);
|
|
25
|
+
}
|
|
26
|
+
/** Extract TNID variant from a 128-bit value */
|
|
27
|
+
export function extractVariantFromValue(value) {
|
|
28
|
+
const variantBits = Number((value >> 60n) & 3n);
|
|
29
|
+
const variants = ["v0", "v1", "v2", "v3"];
|
|
30
|
+
return variants[variantBits];
|
|
31
|
+
}
|
|
32
|
+
/** Validate that a 128-bit value has correct UUIDv8 version and variant bits */
|
|
33
|
+
export function validateUuidBits(value) {
|
|
34
|
+
// Check UUID version (bits 76-79 should be 0x8)
|
|
35
|
+
const version = Number((value >> 76n) & 0xfn);
|
|
36
|
+
if (version !== 8)
|
|
37
|
+
return false;
|
|
38
|
+
// Check UUID variant (bits 62-63 should be 0b10)
|
|
39
|
+
const variant = Number((value >> 62n) & 3n);
|
|
40
|
+
if (variant !== 0b10)
|
|
41
|
+
return false;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
/** Convert bytes to TNID string */
|
|
45
|
+
export function bytesToTnidString(bytes, name) {
|
|
46
|
+
const dataEncoded = encodeData(bytes);
|
|
47
|
+
return `${name}.${dataEncoded}`;
|
|
48
|
+
}
|
|
49
|
+
/** Convert 128-bit value to TNID string */
|
|
50
|
+
export function valueToTnidString(value) {
|
|
51
|
+
const nameBits = extractNameBitsFromValue(value);
|
|
52
|
+
const name = decodeName(nameBits);
|
|
53
|
+
const bytes = valueToBytes(value);
|
|
54
|
+
return bytesToTnidString(bytes, name);
|
|
55
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DynamicTnid } from "./dynamic.js";
|
|
2
|
+
/** A UUID hex string (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) that may or may not be a valid TNID. */
|
|
3
|
+
export type UuidLike = string & {
|
|
4
|
+
__uuidlike: true;
|
|
5
|
+
};
|
|
6
|
+
/** Wrapper for UUID hex strings that may or may not be valid TNIDs. */
|
|
7
|
+
export declare const UuidLike: {
|
|
8
|
+
/** Create from a TNID (always valid). */
|
|
9
|
+
readonly fromTnid: (id: DynamicTnid) => UuidLike;
|
|
10
|
+
/** Parse UUID hex string (format validation only, not TNID validation). */
|
|
11
|
+
readonly parse: (s: string) => UuidLike;
|
|
12
|
+
/** Try to convert to DynamicTnid (validates TNID structure). */
|
|
13
|
+
readonly toTnid: (uuid: UuidLike) => DynamicTnid;
|
|
14
|
+
/** Format as uppercase UUID hex string. */
|
|
15
|
+
readonly toUpperCase: (uuid: UuidLike) => UuidLike;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=uuidlike.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uuidlike.d.ts","sourceRoot":"","sources":["../src/uuidlike.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,oGAAoG;AACpG,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,UAAU,EAAE,IAAI,CAAA;CAAE,CAAC;AAWrD,uEAAuE;AACvE,eAAO,MAAM,QAAQ;IACnB,yCAAyC;4BAC5B,WAAW,KAAG,QAAQ;IAInC,2EAA2E;wBAClE,MAAM,KAAG,QAAQ;IAQ1B,gEAAgE;4BACnD,QAAQ,KAAG,WAAW;IAanC,2CAA2C;iCACzB,QAAQ,KAAG,QAAQ;CAG7B,CAAC"}
|
package/esm/uuidlike.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// UuidLike - Wrapper for UUID hex strings
|
|
3
|
+
// =============================================================================
|
|
4
|
+
import { decodeName, isValidNameRuntime } from "./name_encoding.js";
|
|
5
|
+
import { UUID_REGEX, parseUuidStringToValue, validateUuidBits, extractNameBitsFromValue, valueToTnidString, } from "./uuid.js";
|
|
6
|
+
import { toUuidStringImpl } from "./dynamic.js";
|
|
7
|
+
/** Wrapper for UUID hex strings that may or may not be valid TNIDs. */
|
|
8
|
+
export const UuidLike = {
|
|
9
|
+
/** Create from a TNID (always valid). */
|
|
10
|
+
fromTnid(id) {
|
|
11
|
+
return toUuidStringImpl(id, false);
|
|
12
|
+
},
|
|
13
|
+
/** Parse UUID hex string (format validation only, not TNID validation). */
|
|
14
|
+
parse(s) {
|
|
15
|
+
// Validate format only
|
|
16
|
+
if (!UUID_REGEX.test(s)) {
|
|
17
|
+
throw new Error(`Invalid UUID format: ${s}`);
|
|
18
|
+
}
|
|
19
|
+
return s.toLowerCase();
|
|
20
|
+
},
|
|
21
|
+
/** Try to convert to DynamicTnid (validates TNID structure). */
|
|
22
|
+
toTnid(uuid) {
|
|
23
|
+
const value = parseUuidStringToValue(uuid);
|
|
24
|
+
if (!validateUuidBits(value)) {
|
|
25
|
+
throw new Error("Invalid TNID: not a valid UUIDv8");
|
|
26
|
+
}
|
|
27
|
+
const nameBits = extractNameBitsFromValue(value);
|
|
28
|
+
const name = decodeName(nameBits);
|
|
29
|
+
if (!isValidNameRuntime(name)) {
|
|
30
|
+
throw new Error("Invalid TNID: invalid name encoding");
|
|
31
|
+
}
|
|
32
|
+
return valueToTnidString(value);
|
|
33
|
+
},
|
|
34
|
+
/** Format as uppercase UUID hex string. */
|
|
35
|
+
toUpperCase(uuid) {
|
|
36
|
+
return uuid.toUpperCase();
|
|
37
|
+
},
|
|
38
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tnid/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Type-safe, named, unique identifiers (TNIDs) - UUID-compatible IDs with embedded type names",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"uuid",
|
|
7
|
+
"id",
|
|
8
|
+
"identifier",
|
|
9
|
+
"tnid",
|
|
10
|
+
"typed",
|
|
11
|
+
"type-safe"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/tnid/tnid-typescript.git"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/tnid/tnid-typescript/issues"
|
|
20
|
+
},
|
|
21
|
+
"main": "./script/index.js",
|
|
22
|
+
"module": "./esm/index.js",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"import": "./esm/index.js",
|
|
26
|
+
"require": "./script/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"scripts": {},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@deno/shim-crypto": "~0.3.1"
|
|
35
|
+
},
|
|
36
|
+
"_generatedBy": "dnt@dev"
|
|
37
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { crypto, type Crypto, type SubtleCrypto, type AlgorithmIdentifier, type Algorithm, type RsaOaepParams, type BufferSource, type AesCtrParams, type AesCbcParams, type AesGcmParams, type CryptoKey, type KeyAlgorithm, type KeyType, type KeyUsage, type EcdhKeyDeriveParams, type HkdfParams, type HashAlgorithmIdentifier, type Pbkdf2Params, type AesDerivedKeyParams, type HmacImportParams, type JsonWebKey, type RsaOtherPrimesInfo, type KeyFormat, type RsaHashedKeyGenParams, type RsaKeyGenParams, type BigInteger, type EcKeyGenParams, type NamedCurve, type CryptoKeyPair, type AesKeyGenParams, type HmacKeyGenParams, type RsaHashedImportParams, type EcKeyImportParams, type AesKeyAlgorithm, type RsaPssParams, type EcdsaParams } from "@deno/shim-crypto";
|
|
2
|
+
export declare const dntGlobalThis: Omit<typeof globalThis, "crypto"> & {
|
|
3
|
+
crypto: import("@deno/shim-crypto").Crypto;
|
|
4
|
+
};
|
|
5
|
+
//# sourceMappingURL=_dnt.shims.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_dnt.shims.d.ts","sourceRoot":"","sources":["../src/_dnt.shims.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,mBAAmB,EAAE,KAAK,UAAU,EAAE,KAAK,uBAAuB,EAAE,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,kBAAkB,EAAE,KAAK,SAAS,EAAE,KAAK,qBAAqB,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,KAAK,iBAAiB,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKrvB,eAAO,MAAM,aAAa;;CAA2C,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dntGlobalThis = exports.crypto = void 0;
|
|
4
|
+
const shim_crypto_1 = require("@deno/shim-crypto");
|
|
5
|
+
var shim_crypto_2 = require("@deno/shim-crypto");
|
|
6
|
+
Object.defineProperty(exports, "crypto", { enumerable: true, get: function () { return shim_crypto_2.crypto; } });
|
|
7
|
+
const dntGlobals = {
|
|
8
|
+
crypto: shim_crypto_1.crypto,
|
|
9
|
+
};
|
|
10
|
+
exports.dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
|
|
11
|
+
function createMergeProxy(baseObj, extObj) {
|
|
12
|
+
return new Proxy(baseObj, {
|
|
13
|
+
get(_target, prop, _receiver) {
|
|
14
|
+
if (prop in extObj) {
|
|
15
|
+
return extObj[prop];
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return baseObj[prop];
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
set(_target, prop, value) {
|
|
22
|
+
if (prop in extObj) {
|
|
23
|
+
delete extObj[prop];
|
|
24
|
+
}
|
|
25
|
+
baseObj[prop] = value;
|
|
26
|
+
return true;
|
|
27
|
+
},
|
|
28
|
+
deleteProperty(_target, prop) {
|
|
29
|
+
let success = false;
|
|
30
|
+
if (prop in extObj) {
|
|
31
|
+
delete extObj[prop];
|
|
32
|
+
success = true;
|
|
33
|
+
}
|
|
34
|
+
if (prop in baseObj) {
|
|
35
|
+
delete baseObj[prop];
|
|
36
|
+
success = true;
|
|
37
|
+
}
|
|
38
|
+
return success;
|
|
39
|
+
},
|
|
40
|
+
ownKeys(_target) {
|
|
41
|
+
const baseKeys = Reflect.ownKeys(baseObj);
|
|
42
|
+
const extKeys = Reflect.ownKeys(extObj);
|
|
43
|
+
const extKeysSet = new Set(extKeys);
|
|
44
|
+
return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
|
|
45
|
+
},
|
|
46
|
+
defineProperty(_target, prop, desc) {
|
|
47
|
+
if (prop in extObj) {
|
|
48
|
+
delete extObj[prop];
|
|
49
|
+
}
|
|
50
|
+
Reflect.defineProperty(baseObj, prop, desc);
|
|
51
|
+
return true;
|
|
52
|
+
},
|
|
53
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
54
|
+
if (prop in extObj) {
|
|
55
|
+
return Reflect.getOwnPropertyDescriptor(extObj, prop);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
return Reflect.getOwnPropertyDescriptor(baseObj, prop);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
has(_target, prop) {
|
|
62
|
+
return prop in extObj || prop in baseObj;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
package/script/bits.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const UUID_V8_MASK = 604472133179351442128896n;
|
|
2
|
+
export declare const V1_RANDOM_MASK = 324518552525041477071872066584575n;
|
|
3
|
+
export declare const V0_RANDOM_MASK = 144115188075855871n;
|
|
4
|
+
/** Place name bits in their correct position (bits 108-127) */
|
|
5
|
+
export declare function nameMask(nameBits: number): bigint;
|
|
6
|
+
/** Place UUID version/variant and TNID variant in their correct positions */
|
|
7
|
+
export declare function uuidAndVariantMask(tnidVariant: bigint): bigint;
|
|
8
|
+
/** Scatter 43-bit timestamp into its three positions within the 128-bit ID */
|
|
9
|
+
export declare function millisMask(millisSinceEpoch: bigint): bigint;
|
|
10
|
+
/** Build a 128-bit TNID value using mask-based OR operations */
|
|
11
|
+
export declare function buildTnidValue(nameBits: number, payloadMask: bigint, tnidVariant: bigint): bigint;
|
|
12
|
+
/** Convert 128-bit value to byte array */
|
|
13
|
+
export declare function valueToBytes(value: bigint): Uint8Array;
|
|
14
|
+
/** Generate a V0 (time-ordered) TNID as bytes */
|
|
15
|
+
export declare function generateV0(nameBits: number, timestampMs?: bigint, randomBits?: bigint): Uint8Array;
|
|
16
|
+
/** Generate a V1 (high-entropy random) TNID as bytes */
|
|
17
|
+
export declare function generateV1(nameBits: number, randomBits?: bigint): Uint8Array;
|
|
18
|
+
//# sourceMappingURL=bits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bits.d.ts","sourceRoot":"","sources":["../src/bits.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,YAAY,4BAA0C,CAAC;AAGpE,eAAO,MAAM,cAAc,qCAA0C,CAAC;AAGtE,eAAO,MAAM,cAAc,sBAA0C,CAAC;AAOtE,+DAA+D;AAC/D,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAmB3D;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAQtD;AAED,iDAAiD;AACjD,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,MAAM,GAClB,UAAU,CAmBZ;AAED,wDAAwD;AACxD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,UAAU,CAe5E"}
|
package/script/bits.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Bit Manipulation and TNID Generation
|
|
4
|
+
// Mask-based approach matching Rust implementation
|
|
5
|
+
// =============================================================================
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.V0_RANDOM_MASK = exports.V1_RANDOM_MASK = exports.UUID_V8_MASK = void 0;
|
|
41
|
+
exports.nameMask = nameMask;
|
|
42
|
+
exports.uuidAndVariantMask = uuidAndVariantMask;
|
|
43
|
+
exports.millisMask = millisMask;
|
|
44
|
+
exports.buildTnidValue = buildTnidValue;
|
|
45
|
+
exports.valueToBytes = valueToBytes;
|
|
46
|
+
exports.generateV0 = generateV0;
|
|
47
|
+
exports.generateV1 = generateV1;
|
|
48
|
+
// Masks for UUID version 8 and variant bits
|
|
49
|
+
const dntShim = __importStar(require("./_dnt.shims.js"));
|
|
50
|
+
exports.UUID_V8_MASK = 0x00000000000080008000000000000000n;
|
|
51
|
+
// V1: 100 random bits scattered across the UUID
|
|
52
|
+
exports.V1_RANDOM_MASK = 0x00000fffffff0fff0fffffffffffffffn;
|
|
53
|
+
// V0: 57 random bits in the lower portion
|
|
54
|
+
exports.V0_RANDOM_MASK = 0x000000000000000001ffffffffffffffn;
|
|
55
|
+
// V0 timestamp masks (for extracting and placing 43-bit timestamp)
|
|
56
|
+
const TIMESTAMP_FIRST_28_MASK = 0x000007ffffff8000n;
|
|
57
|
+
const TIMESTAMP_SECOND_12_MASK = 0x0000000000007ff8n;
|
|
58
|
+
const TIMESTAMP_LAST_3_MASK = 0x0000000000000007n;
|
|
59
|
+
/** Place name bits in their correct position (bits 108-127) */
|
|
60
|
+
function nameMask(nameBits) {
|
|
61
|
+
return BigInt(nameBits) << 108n;
|
|
62
|
+
}
|
|
63
|
+
/** Place UUID version/variant and TNID variant in their correct positions */
|
|
64
|
+
function uuidAndVariantMask(tnidVariant) {
|
|
65
|
+
return exports.UUID_V8_MASK | ((tnidVariant & 3n) << 60n);
|
|
66
|
+
}
|
|
67
|
+
/** Scatter 43-bit timestamp into its three positions within the 128-bit ID */
|
|
68
|
+
function millisMask(millisSinceEpoch) {
|
|
69
|
+
let mask = 0n;
|
|
70
|
+
// First 28 bits of timestamp → bits 80-107 of UUID
|
|
71
|
+
const first28LeadingZeros = 64n - 43n; // = 21, so mask starts at bit 42
|
|
72
|
+
mask |= (millisSinceEpoch & TIMESTAMP_FIRST_28_MASK) <<
|
|
73
|
+
(first28LeadingZeros + 64n - 20n);
|
|
74
|
+
// Middle 12 bits of timestamp → bits 64-75 of UUID (after version nibble)
|
|
75
|
+
const second12LeadingZeros = 64n - 15n; // = 49
|
|
76
|
+
mask |= (millisSinceEpoch & TIMESTAMP_SECOND_12_MASK) <<
|
|
77
|
+
(second12LeadingZeros + 64n - 52n);
|
|
78
|
+
// Last 3 bits of timestamp → bits 57-59 of UUID (after variant bits)
|
|
79
|
+
const last3LeadingZeros = 64n - 3n; // = 61
|
|
80
|
+
mask |= (millisSinceEpoch & TIMESTAMP_LAST_3_MASK) <<
|
|
81
|
+
(last3LeadingZeros + 64n - 68n);
|
|
82
|
+
return mask;
|
|
83
|
+
}
|
|
84
|
+
/** Build a 128-bit TNID value using mask-based OR operations */
|
|
85
|
+
function buildTnidValue(nameBits, payloadMask, tnidVariant) {
|
|
86
|
+
return nameMask(nameBits) | uuidAndVariantMask(tnidVariant) | payloadMask;
|
|
87
|
+
}
|
|
88
|
+
/** Convert 128-bit value to byte array */
|
|
89
|
+
function valueToBytes(value) {
|
|
90
|
+
const bytes = new Uint8Array(16);
|
|
91
|
+
let v = value;
|
|
92
|
+
for (let i = 15; i >= 0; i--) {
|
|
93
|
+
bytes[i] = Number(v & 0xffn);
|
|
94
|
+
v >>= 8n;
|
|
95
|
+
}
|
|
96
|
+
return bytes;
|
|
97
|
+
}
|
|
98
|
+
/** Generate a V0 (time-ordered) TNID as bytes */
|
|
99
|
+
function generateV0(nameBits, timestampMs, randomBits) {
|
|
100
|
+
const timestamp = timestampMs !== undefined
|
|
101
|
+
? timestampMs & ((1n << 43n) - 1n)
|
|
102
|
+
: BigInt(Date.now()) & ((1n << 43n) - 1n);
|
|
103
|
+
let random;
|
|
104
|
+
if (randomBits !== undefined) {
|
|
105
|
+
random = randomBits;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const randomBytes = dntShim.crypto.getRandomValues(new Uint8Array(8));
|
|
109
|
+
random = 0n;
|
|
110
|
+
for (const byte of randomBytes) {
|
|
111
|
+
random = (random << 8n) | BigInt(byte);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const payloadMask = millisMask(timestamp) | (random & exports.V0_RANDOM_MASK);
|
|
115
|
+
const value = buildTnidValue(nameBits, payloadMask, 0n);
|
|
116
|
+
return valueToBytes(value);
|
|
117
|
+
}
|
|
118
|
+
/** Generate a V1 (high-entropy random) TNID as bytes */
|
|
119
|
+
function generateV1(nameBits, randomBits) {
|
|
120
|
+
let random;
|
|
121
|
+
if (randomBits !== undefined) {
|
|
122
|
+
random = randomBits;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const randomBytes = dntShim.crypto.getRandomValues(new Uint8Array(16));
|
|
126
|
+
random = 0n;
|
|
127
|
+
for (const byte of randomBytes) {
|
|
128
|
+
random = (random << 8n) | BigInt(byte);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const payloadMask = random & exports.V1_RANDOM_MASK;
|
|
132
|
+
const value = buildTnidValue(nameBits, payloadMask, 1n);
|
|
133
|
+
return valueToBytes(value);
|
|
134
|
+
}
|
|
@@ -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"}
|