@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael Edlinger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
# TNID Typescript
|
|
2
|
+
|
|
3
|
+
Type-safe, named, unique identifiers (TNIDs) for TypeScript.
|
|
4
|
+
|
|
5
|
+
TNIDs are UUID-compatible identifiers with embedded type names, providing
|
|
6
|
+
compile-time type safety and human-readable prefixes.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# npm
|
|
12
|
+
npm install @tnid/core
|
|
13
|
+
|
|
14
|
+
# pnpm
|
|
15
|
+
pnpm add @tnid/core
|
|
16
|
+
|
|
17
|
+
# bun
|
|
18
|
+
bun add @tnid/core
|
|
19
|
+
|
|
20
|
+
# deno
|
|
21
|
+
deno add npm:@tnid/core
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { DynamicTnid, Tnid, TnidType, UuidLike } from "@tnid/core";
|
|
28
|
+
|
|
29
|
+
// Create a typed NamedTnid (compile-time name validation)
|
|
30
|
+
const UserId = Tnid("user");
|
|
31
|
+
type UserId = TnidType<typeof UserId>;
|
|
32
|
+
|
|
33
|
+
// Generate IDs
|
|
34
|
+
const id: UserId = UserId.new_v0(); // time-ordered
|
|
35
|
+
const id2: UserId = UserId.new_v1(); // high-entropy random
|
|
36
|
+
|
|
37
|
+
// IDs look like: "user.Br2flcNDfF6LYICnT"
|
|
38
|
+
console.log(id);
|
|
39
|
+
|
|
40
|
+
// Parse existing IDs
|
|
41
|
+
const parsed: UserId = UserId.parse("user.Br2flcNDfF6LYICnT");
|
|
42
|
+
|
|
43
|
+
// Convert to/from UUID format
|
|
44
|
+
const uuid: string = UserId.toUuidString(id); // "d6157329-4640-8e30-..."
|
|
45
|
+
const fromUuid: UserId = UserId.parseUuidString(uuid);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- **Compile-time type safety**: Different TNID types are incompatible at the
|
|
51
|
+
type level
|
|
52
|
+
- **Human-readable prefixes**: IDs like `user.Br2flcNDfF6LYICnT` are
|
|
53
|
+
self-documenting
|
|
54
|
+
- **UUID compatible**: Store in any UUID column, use with existing UUID tooling
|
|
55
|
+
- **Time-ordered (V0)**: Sortable by creation time, like UUIDv7
|
|
56
|
+
- **High-entropy (V1)**: Maximum randomness, like UUIDv4
|
|
57
|
+
- **String-backed**: At runtime, TNIDs are their string representation (e.g.
|
|
58
|
+
`user.Br2flcNDfF6LYICnT`). Use as Map keys, in JSON, or compare with `===`
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const id = UserId.new_v0();
|
|
62
|
+
|
|
63
|
+
// Use as Map key
|
|
64
|
+
const cache = new Map<UserId, User>();
|
|
65
|
+
cache.set(id, user);
|
|
66
|
+
|
|
67
|
+
// JSON serialization works naturally
|
|
68
|
+
JSON.stringify({ userId: id }); // {"userId":"user.Br2flcNDfF6LYICnT"}
|
|
69
|
+
|
|
70
|
+
// String comparison
|
|
71
|
+
id === otherUserId; // true/false
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## API Reference
|
|
77
|
+
|
|
78
|
+
### Exports
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import {
|
|
82
|
+
Case, // "lower" | "upper"
|
|
83
|
+
DynamicTnid, // Runtime TNID operations (type + namespace)
|
|
84
|
+
Tnid, // Factory creator function
|
|
85
|
+
NamedTnid, // Named TNID interface
|
|
86
|
+
TnidType, // Type helper to extract ID type from factory
|
|
87
|
+
// Types only:
|
|
88
|
+
TnidValue, // Branded string type
|
|
89
|
+
TnidVariant, // "v0" | "v1" | "v2" | "v3"
|
|
90
|
+
UuidLike, // UUID string operations (type + namespace)
|
|
91
|
+
} from "@tnid/core";
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### `Tnid(name)`
|
|
97
|
+
|
|
98
|
+
Creates a factory for TNIDs with a specific name. The name is validated at
|
|
99
|
+
**compile time**.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const UserId = Tnid("user");
|
|
103
|
+
const PostId = Tnid("post");
|
|
104
|
+
const ItemId = Tnid("item");
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### Name Rules
|
|
108
|
+
|
|
109
|
+
- **Length**: 1-4 characters
|
|
110
|
+
- **Valid characters**: `0-4` and `a-z` (31 characters total)
|
|
111
|
+
- **Case**: lowercase only
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Valid names
|
|
115
|
+
Tnid("user"); // ✓
|
|
116
|
+
Tnid("a"); // ✓
|
|
117
|
+
Tnid("1234"); // ✓
|
|
118
|
+
Tnid("a1b2"); // ✓
|
|
119
|
+
|
|
120
|
+
// Compile errors
|
|
121
|
+
Tnid("users"); // ✗ too long (max 4)
|
|
122
|
+
Tnid("User"); // ✗ uppercase not allowed
|
|
123
|
+
Tnid("a-b"); // ✗ hyphen not allowed
|
|
124
|
+
Tnid("5"); // ✗ only digits 0-4
|
|
125
|
+
Tnid(""); // ✗ empty not allowed
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### `NamedTnid<Name>` (returned by `Tnid()`)
|
|
131
|
+
|
|
132
|
+
The object returned by `Tnid(name)` has these methods:
|
|
133
|
+
|
|
134
|
+
#### Properties
|
|
135
|
+
|
|
136
|
+
| Property | Type | Description |
|
|
137
|
+
| -------- | ------ | ------------------------------------------ |
|
|
138
|
+
| `name` | `Name` | The TNID name this creates IDs for |
|
|
139
|
+
|
|
140
|
+
#### Generation Methods
|
|
141
|
+
|
|
142
|
+
| Method | Returns | Description |
|
|
143
|
+
| ---------------------------------------- | ----------------- | ------------------------------------------------- |
|
|
144
|
+
| `new_v0()` | `TnidValue<Name>` | Generate a time-ordered ID (like UUIDv7) |
|
|
145
|
+
| `new_v1()` | `TnidValue<Name>` | Generate a high-entropy random ID (like UUIDv4) |
|
|
146
|
+
| `v0_from_parts(timestampMs, randomBits)` | `TnidValue<Name>` | Create V0 from explicit parts (for testing) |
|
|
147
|
+
| `v1_from_parts(randomBits)` | `TnidValue<Name>` | Create V1 from explicit random bits (for testing) |
|
|
148
|
+
|
|
149
|
+
#### Parsing Methods
|
|
150
|
+
|
|
151
|
+
| Method | Returns | Throws | Description |
|
|
152
|
+
| ----------------------- | ----------------- | -------------------------------- | ----------------------- |
|
|
153
|
+
| `parse(s)` | `TnidValue<Name>` | If invalid or name mismatch | Parse a TNID string |
|
|
154
|
+
| `parseUuidString(uuid)` | `TnidValue<Name>` | If invalid UUID or name mismatch | Parse a UUID hex string |
|
|
155
|
+
|
|
156
|
+
#### Utility Methods
|
|
157
|
+
|
|
158
|
+
| Method | Returns | Description |
|
|
159
|
+
| ------------------------- | ------------- | -------------------------------------- |
|
|
160
|
+
| `variant(id)` | `TnidVariant` | Get the variant (`"v0"`, `"v1"`, etc.) |
|
|
161
|
+
| `toUuidString(id, case?)` | `string` | Convert to UUID hex format |
|
|
162
|
+
| `nameHex()` | `string` | Get the name as a 5-char hex string |
|
|
163
|
+
|
|
164
|
+
#### Example
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const UserId = Tnid("user");
|
|
168
|
+
type UserId = TnidType<typeof UserId>;
|
|
169
|
+
|
|
170
|
+
// Generation
|
|
171
|
+
const id: UserId = UserId.new_v0(); // "user.Br2flcNDfF6LYICnT"
|
|
172
|
+
const id2: UserId = UserId.new_v1(); // "user.EUBcUw4T9x3KNOll-"
|
|
173
|
+
|
|
174
|
+
// Parsing
|
|
175
|
+
const parsed: UserId = UserId.parse("user.Br2flcNDfF6LYICnT");
|
|
176
|
+
const fromUuid: UserId = UserId.parseUuidString(
|
|
177
|
+
"d6157329-4640-8e30-8012-345678901234",
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Utilities
|
|
181
|
+
UserId.variant(id); // "v0"
|
|
182
|
+
UserId.toUuidString(id); // "d6157329-4640-8e30-8012-..."
|
|
183
|
+
UserId.toUuidString(id, "upper"); // "D6157329-4640-8E30-8012-..."
|
|
184
|
+
UserId.nameHex(); // "d6157"
|
|
185
|
+
|
|
186
|
+
// Property
|
|
187
|
+
UserId.name; // "user"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### `TnidType<Factory>`
|
|
193
|
+
|
|
194
|
+
Type helper to extract the `TnidValue` type from a `NamedTnid`.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const UserId = Tnid("user");
|
|
198
|
+
type UserId = TnidType<typeof UserId>; // TnidValue<"user">
|
|
199
|
+
|
|
200
|
+
const PostId = Tnid("post");
|
|
201
|
+
type PostId = TnidType<typeof PostId>; // TnidValue<"post">
|
|
202
|
+
|
|
203
|
+
// Use in function signatures
|
|
204
|
+
function getUser(id: UserId): User { ... }
|
|
205
|
+
function getPost(id: PostId): Post { ... }
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### `DynamicTnid`
|
|
211
|
+
|
|
212
|
+
For working with TNIDs when the name isn't known at compile time. This is both a
|
|
213
|
+
**type** and a **namespace**.
|
|
214
|
+
|
|
215
|
+
#### As a Type
|
|
216
|
+
|
|
217
|
+
`DynamicTnid` accepts any TNID regardless of name:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
function logAnyId(id: DynamicTnid) {
|
|
221
|
+
console.log(DynamicTnid.getName(id), id);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const userId: UserId = UserId.new_v0();
|
|
225
|
+
const postId: PostId = PostId.new_v0();
|
|
226
|
+
|
|
227
|
+
logAnyId(userId); // works
|
|
228
|
+
logAnyId(postId); // works
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### Namespace Methods
|
|
232
|
+
|
|
233
|
+
##### Generation
|
|
234
|
+
|
|
235
|
+
| Method | Returns | Description |
|
|
236
|
+
| ---------------------------------------------- | ------------- | ------------------------------------------ |
|
|
237
|
+
| `new_v0(name)` | `DynamicTnid` | Generate time-ordered ID with runtime name |
|
|
238
|
+
| `new_v1(name)` | `DynamicTnid` | Generate high-entropy ID with runtime name |
|
|
239
|
+
| `new_time_ordered(name)` | `DynamicTnid` | Alias for `new_v0` |
|
|
240
|
+
| `new_high_entropy(name)` | `DynamicTnid` | Alias for `new_v1` |
|
|
241
|
+
| `new_v0_with_time(name, date)` | `DynamicTnid` | V0 with specific timestamp |
|
|
242
|
+
| `new_v0_with_parts(name, epochMillis, random)` | `DynamicTnid` | V0 with explicit parts |
|
|
243
|
+
| `new_v1_with_random(name, randomBits)` | `DynamicTnid` | V1 with explicit random bits |
|
|
244
|
+
|
|
245
|
+
##### Parsing
|
|
246
|
+
|
|
247
|
+
| Method | Returns | Throws | Description |
|
|
248
|
+
| ------------------------- | ------------- | ---------- | --------------------- |
|
|
249
|
+
| `parse(s)` | `DynamicTnid` | If invalid | Parse any TNID string |
|
|
250
|
+
| `parse_uuid_string(uuid)` | `DynamicTnid` | If invalid | Parse UUID to TNID |
|
|
251
|
+
|
|
252
|
+
##### Inspection
|
|
253
|
+
|
|
254
|
+
| Method | Returns | Description |
|
|
255
|
+
| ------------------------- | ------------- | ------------------------ |
|
|
256
|
+
| `getName(id)` | `string` | Extract the name portion |
|
|
257
|
+
| `getNameHex(id)` | `string` | Get name as 5-char hex |
|
|
258
|
+
| `getVariant(id)` | `TnidVariant` | Get the variant |
|
|
259
|
+
| `toUuidString(id, case?)` | `string` | Convert to UUID hex |
|
|
260
|
+
|
|
261
|
+
#### Example
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// Runtime name - useful for dynamic/generic code
|
|
265
|
+
const entityType = "user"; // from config, API, etc.
|
|
266
|
+
const id = DynamicTnid.new_v0(entityType);
|
|
267
|
+
|
|
268
|
+
// Parse any TNID without knowing its type
|
|
269
|
+
const unknown = DynamicTnid.parse("post.EUBcUw4T9x3KNOll-");
|
|
270
|
+
console.log(DynamicTnid.getName(unknown)); // "post"
|
|
271
|
+
console.log(DynamicTnid.getVariant(unknown)); // "v1"
|
|
272
|
+
|
|
273
|
+
// Create with specific timestamp
|
|
274
|
+
const backdated = DynamicTnid.new_v0_with_time("log", new Date("2024-01-01"));
|
|
275
|
+
|
|
276
|
+
// Convert to UUID
|
|
277
|
+
const uuid = DynamicTnid.toUuidString(id);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
### `UuidLike`
|
|
283
|
+
|
|
284
|
+
For working with UUID hex strings that may or may not be valid TNIDs. This is
|
|
285
|
+
both a **type** and a **namespace**.
|
|
286
|
+
|
|
287
|
+
#### As a Type
|
|
288
|
+
|
|
289
|
+
`UuidLike` represents any valid UUID hex string (format:
|
|
290
|
+
`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`):
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
function storeInDatabase(uuid: UuidLike) { ... }
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
#### Namespace Methods
|
|
297
|
+
|
|
298
|
+
| Method | Returns | Throws | Description |
|
|
299
|
+
| ------------------- | ------------- | ------------------- | ----------------------------------- |
|
|
300
|
+
| `fromTnid(id)` | `UuidLike` | - | Convert TNID to UUID string |
|
|
301
|
+
| `parse(s)` | `UuidLike` | If invalid format | Parse any UUID string (format only) |
|
|
302
|
+
| `toTnid(uuid)` | `DynamicTnid` | If not a valid TNID | Convert UUID to TNID |
|
|
303
|
+
| `toUpperCase(uuid)` | `UuidLike` | - | Convert to uppercase |
|
|
304
|
+
|
|
305
|
+
#### Example
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
const UserId = Tnid("user");
|
|
309
|
+
const id = UserId.new_v0();
|
|
310
|
+
|
|
311
|
+
// TNID to UUID
|
|
312
|
+
const uuid: UuidLike = UuidLike.fromTnid(id);
|
|
313
|
+
// "d6157329-4640-8e30-8012-345678901234"
|
|
314
|
+
|
|
315
|
+
// Parse any UUID (doesn't validate TNID structure)
|
|
316
|
+
const anyUuid = UuidLike.parse("550e8400-e29b-41d4-a716-446655440000");
|
|
317
|
+
|
|
318
|
+
// Convert back to TNID (validates it's a valid TNID)
|
|
319
|
+
try {
|
|
320
|
+
const tnid = UuidLike.toTnid(uuid); // works
|
|
321
|
+
const fail = UuidLike.toTnid(anyUuid); // throws - not a TNID
|
|
322
|
+
} catch (e) {
|
|
323
|
+
console.log("Not a valid TNID");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Case conversion
|
|
327
|
+
const upper = UuidLike.toUpperCase(uuid);
|
|
328
|
+
// "D6157329-4640-8E30-8012-345678901234"
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Variants
|
|
334
|
+
|
|
335
|
+
### V0 (Time-Ordered)
|
|
336
|
+
|
|
337
|
+
- 43 bits: millisecond timestamp
|
|
338
|
+
- 57 bits: random
|
|
339
|
+
- **Use case**: When you need chronological sorting (logs, events, feeds)
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
const id = UserId.new_v0();
|
|
343
|
+
// IDs created later sort after earlier ones
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### V1 (High-Entropy)
|
|
347
|
+
|
|
348
|
+
- 100 bits: random
|
|
349
|
+
- **Use case**: When you need maximum uniqueness/unpredictability
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
const id = UserId.new_v1();
|
|
353
|
+
// Maximum entropy, no time component
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Type Safety
|
|
359
|
+
|
|
360
|
+
Different TNID types are completely incompatible at compile time:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
const UserId = Tnid("user");
|
|
364
|
+
const PostId = Tnid("post");
|
|
365
|
+
type UserId = TnidType<typeof UserId>;
|
|
366
|
+
type PostId = TnidType<typeof PostId>;
|
|
367
|
+
|
|
368
|
+
const userId: UserId = UserId.new_v0();
|
|
369
|
+
const postId: PostId = PostId.new_v0();
|
|
370
|
+
|
|
371
|
+
// Compile errors - types don't match
|
|
372
|
+
const wrong1: UserId = postId; // ✗
|
|
373
|
+
const wrong2: PostId = userId; // ✗
|
|
374
|
+
function getUser(id: UserId) { ... }
|
|
375
|
+
getUser(postId); // ✗
|
|
376
|
+
|
|
377
|
+
// DynamicTnid accepts any TNID
|
|
378
|
+
const dynamic: DynamicTnid = userId; // ✓
|
|
379
|
+
const dynamic2: DynamicTnid = postId; // ✓
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Preventing Forgery
|
|
383
|
+
|
|
384
|
+
Plain strings cannot be assigned to TNID types:
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
const UserId = Tnid("user");
|
|
388
|
+
type UserId = TnidType<typeof UserId>;
|
|
389
|
+
|
|
390
|
+
// Compile errors - plain strings not allowed
|
|
391
|
+
const fake1: UserId = "user.Br2flcNDfF6LYICnT"; // ✗
|
|
392
|
+
const fake2: UserId = someString; // ✗
|
|
393
|
+
|
|
394
|
+
// Must use parse() or new_*() to get a valid TNID
|
|
395
|
+
const valid: UserId = UserId.parse("user.Br2flcNDfF6LYICnT"); // ✓
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## UUID Compatibility
|
|
401
|
+
|
|
402
|
+
TNIDs are valid UUIDv8 identifiers. You can:
|
|
403
|
+
|
|
404
|
+
- Store in UUID database columns
|
|
405
|
+
- Use with UUID-aware tools and libraries
|
|
406
|
+
- Convert freely between TNID and UUID formats
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
const UserId = Tnid("user");
|
|
410
|
+
const id = UserId.new_v0();
|
|
411
|
+
|
|
412
|
+
// To UUID
|
|
413
|
+
const uuid = UserId.toUuidString(id);
|
|
414
|
+
// "d6157329-4640-8e30-8012-345678901234"
|
|
415
|
+
|
|
416
|
+
// From UUID
|
|
417
|
+
const back = UserId.parseUuidString(uuid);
|
|
418
|
+
// back === id
|
|
419
|
+
|
|
420
|
+
// Store in database as UUID
|
|
421
|
+
await db.query("INSERT INTO users (id) VALUES ($1)", [uuid]);
|
|
422
|
+
|
|
423
|
+
// Retrieve and convert back
|
|
424
|
+
const row = await db.query("SELECT id FROM users WHERE ...");
|
|
425
|
+
const userId = UserId.parseUuidString(row.id);
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Common Patterns
|
|
431
|
+
|
|
432
|
+
### Define ID types for your domain
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
// ids.ts
|
|
436
|
+
import { Tnid, TnidType } from "@tnid/core";
|
|
437
|
+
|
|
438
|
+
export const UserId = Tnid("user");
|
|
439
|
+
export type UserId = TnidType<typeof UserId>;
|
|
440
|
+
|
|
441
|
+
export const PostId = Tnid("post");
|
|
442
|
+
export type PostId = TnidType<typeof PostId>;
|
|
443
|
+
|
|
444
|
+
export const OrgId = Tnid("org");
|
|
445
|
+
export type OrgId = TnidType<typeof OrgId>;
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Use in function signatures
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
import { PostId, UserId } from "./ids";
|
|
452
|
+
|
|
453
|
+
interface Post {
|
|
454
|
+
id: PostId;
|
|
455
|
+
authorId: UserId;
|
|
456
|
+
title: string;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function createPost(authorId: UserId, title: string): Post {
|
|
460
|
+
return {
|
|
461
|
+
id: PostId.new_v0(),
|
|
462
|
+
authorId,
|
|
463
|
+
title,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Generic functions with DynamicTnid
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { DynamicTnid } from "@tnid/core";
|
|
472
|
+
|
|
473
|
+
function logEntity(id: DynamicTnid) {
|
|
474
|
+
const name = DynamicTnid.getName(id);
|
|
475
|
+
const variant = DynamicTnid.getVariant(id);
|
|
476
|
+
console.log(`[${name}:${variant}] ${id}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Works with any TNID
|
|
480
|
+
logEntity(userId); // "[user:v0] user.Br2..."
|
|
481
|
+
logEntity(postId); // "[post:v1] post.EU..."
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Parsing from external sources
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
import { UserId } from "./ids";
|
|
488
|
+
|
|
489
|
+
// From API request
|
|
490
|
+
app.get("/users/:id", (req, res) => {
|
|
491
|
+
try {
|
|
492
|
+
const id = UserId.parse(req.params.id);
|
|
493
|
+
const user = await getUser(id);
|
|
494
|
+
res.json(user);
|
|
495
|
+
} catch {
|
|
496
|
+
res.status(400).json({ error: "Invalid user ID" });
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// From database UUID column
|
|
501
|
+
const row = await db.query("SELECT id FROM users WHERE email = $1", [email]);
|
|
502
|
+
const userId = UserId.parseUuidString(row.id);
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## License
|
|
508
|
+
|
|
509
|
+
MIT
|
|
@@ -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,61 @@
|
|
|
1
|
+
import { crypto } from "@deno/shim-crypto";
|
|
2
|
+
export { crypto } from "@deno/shim-crypto";
|
|
3
|
+
const dntGlobals = {
|
|
4
|
+
crypto,
|
|
5
|
+
};
|
|
6
|
+
export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
|
|
7
|
+
function createMergeProxy(baseObj, extObj) {
|
|
8
|
+
return new Proxy(baseObj, {
|
|
9
|
+
get(_target, prop, _receiver) {
|
|
10
|
+
if (prop in extObj) {
|
|
11
|
+
return extObj[prop];
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
return baseObj[prop];
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
set(_target, prop, value) {
|
|
18
|
+
if (prop in extObj) {
|
|
19
|
+
delete extObj[prop];
|
|
20
|
+
}
|
|
21
|
+
baseObj[prop] = value;
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
deleteProperty(_target, prop) {
|
|
25
|
+
let success = false;
|
|
26
|
+
if (prop in extObj) {
|
|
27
|
+
delete extObj[prop];
|
|
28
|
+
success = true;
|
|
29
|
+
}
|
|
30
|
+
if (prop in baseObj) {
|
|
31
|
+
delete baseObj[prop];
|
|
32
|
+
success = true;
|
|
33
|
+
}
|
|
34
|
+
return success;
|
|
35
|
+
},
|
|
36
|
+
ownKeys(_target) {
|
|
37
|
+
const baseKeys = Reflect.ownKeys(baseObj);
|
|
38
|
+
const extKeys = Reflect.ownKeys(extObj);
|
|
39
|
+
const extKeysSet = new Set(extKeys);
|
|
40
|
+
return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
|
|
41
|
+
},
|
|
42
|
+
defineProperty(_target, prop, desc) {
|
|
43
|
+
if (prop in extObj) {
|
|
44
|
+
delete extObj[prop];
|
|
45
|
+
}
|
|
46
|
+
Reflect.defineProperty(baseObj, prop, desc);
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
50
|
+
if (prop in extObj) {
|
|
51
|
+
return Reflect.getOwnPropertyDescriptor(extObj, prop);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
return Reflect.getOwnPropertyDescriptor(baseObj, prop);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
has(_target, prop) {
|
|
58
|
+
return prop in extObj || prop in baseObj;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
package/esm/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"}
|