@helipod/id-codec 0.1.0
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/dist/index.d.ts +281 -0
- package/dist/index.js +475 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { SerializedKeyRange } from '@helipod/index-key-codec';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Crockford Base32 — case-insensitive, omits the ambiguous letters I, L, O, U.
|
|
5
|
+
* Used to render binary document ids as compact, human-safe, copy-pasteable strings.
|
|
6
|
+
*/
|
|
7
|
+
declare const CROCKFORD_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
8
|
+
declare class Base32Error extends Error {
|
|
9
|
+
name: string;
|
|
10
|
+
}
|
|
11
|
+
declare function base32Encode(bytes: Uint8Array): string;
|
|
12
|
+
declare function base32Decode(text: string): Uint8Array;
|
|
13
|
+
declare function isValidBase32(text: string): boolean;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Fletcher-16 checksum. Appended to each document id so a corrupted or fabricated id is
|
|
17
|
+
* rejected client-side without a database round-trip.
|
|
18
|
+
*/
|
|
19
|
+
declare function fletcher16(bytes: Uint8Array): number;
|
|
20
|
+
declare function verifyFletcher16(bytes: Uint8Array, checksum: number): boolean;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* LEB128 unsigned varint over uint32 (1–5 bytes). Encodes the table number that
|
|
24
|
+
* prefixes a document id, so small system table numbers stay compact.
|
|
25
|
+
*/
|
|
26
|
+
declare class VarintError extends Error {
|
|
27
|
+
name: string;
|
|
28
|
+
}
|
|
29
|
+
declare function varintEncode(value: number): Uint8Array;
|
|
30
|
+
interface VarintDecodeResult {
|
|
31
|
+
value: number;
|
|
32
|
+
bytesRead: number;
|
|
33
|
+
}
|
|
34
|
+
declare function varintDecode(bytes: Uint8Array, offset?: number): VarintDecodeResult;
|
|
35
|
+
declare function varintEncodedLength(value: number): number;
|
|
36
|
+
|
|
37
|
+
type InternalId = Uint8Array;
|
|
38
|
+
declare const INTERNAL_ID_BYTES = 16;
|
|
39
|
+
interface InternalDocumentId {
|
|
40
|
+
tableNumber: number;
|
|
41
|
+
internalId: InternalId;
|
|
42
|
+
}
|
|
43
|
+
declare const docIdBrand: unique symbol;
|
|
44
|
+
type DocumentId = string & {
|
|
45
|
+
readonly [docIdBrand]: "DocumentId";
|
|
46
|
+
};
|
|
47
|
+
declare class DocumentIdError extends Error {
|
|
48
|
+
name: string;
|
|
49
|
+
}
|
|
50
|
+
declare function internalIdToHex(internalId: InternalId): string;
|
|
51
|
+
declare function generateInternalId(): InternalId;
|
|
52
|
+
declare function newDocumentId(tableNumber: number): InternalDocumentId;
|
|
53
|
+
declare function encodeDocumentId(tableNumber: number, internalId: InternalId): DocumentId;
|
|
54
|
+
declare function encodeInternalDocumentId(id: InternalDocumentId): DocumentId;
|
|
55
|
+
/** Mint a full encoded document id CLIENT-SIDE (same shape and entropy as the engine's own
|
|
56
|
+
* minting — 16 random bytes). The engine validates at insert; see client-supplied ids spec. */
|
|
57
|
+
declare function mintEncodedDocumentId(tableNumber: number): DocumentId;
|
|
58
|
+
declare function decodeDocumentId(encoded: string): InternalDocumentId;
|
|
59
|
+
declare function tryDecodeDocumentId(encoded: string): InternalDocumentId | null;
|
|
60
|
+
declare function isValidDocumentId(encoded: string, expectedTableNumber?: number): boolean;
|
|
61
|
+
/** The encoded length in characters for ids of a given table number (31–37). */
|
|
62
|
+
declare function getEncodedLength(tableNumber: number): number;
|
|
63
|
+
declare function documentIdKey(id: InternalDocumentId): string;
|
|
64
|
+
declare function parseDocumentIdKey(key: string): InternalDocumentId;
|
|
65
|
+
declare function documentIdsEqual(a: InternalDocumentId, b: InternalDocumentId): boolean;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Stable string identifiers the storage layer uses to name a table's document space and
|
|
69
|
+
* its indexes. Derived from the table *number* (not name) so renames never touch storage.
|
|
70
|
+
*/
|
|
71
|
+
declare function encodeStorageTableId(tableNumber: number): string;
|
|
72
|
+
declare function decodeStorageTableId(tableId: string): number;
|
|
73
|
+
declare function encodeStorageIndexId(tableNumber: number, indexName: string): string;
|
|
74
|
+
declare function decodeStorageIndexId(indexId: string): {
|
|
75
|
+
tableNumber: number;
|
|
76
|
+
indexName: string;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Shared point-range conversion helpers — the canonical home for `keyToPointRange`/
|
|
81
|
+
* `docKeyToPointRange`, byte-for-byte identical to the versions that originally shipped in
|
|
82
|
+
* `ee/packages/fleet/src/ranges.ts` (Tier 3 Slice 1/2). Extracted here (Tier 3 Slice 8, Task 8.1) so
|
|
83
|
+
* `ee/packages/objectstore-substrate`'s replica reactive-tailer wiring can reuse them without taking
|
|
84
|
+
* a dependency on `@helipod/fleet` (an EE package objectstore-substrate must not import — the
|
|
85
|
+
* object-store substrate and the Postgres fleet are siblings, not a hierarchy).
|
|
86
|
+
*
|
|
87
|
+
* Homed in `@helipod/id-codec`, NOT `@helipod/index-key-codec`, despite `SerializedKeyRange`
|
|
88
|
+
* itself living in the latter: these functions bridge a STORAGE id (`decodeStorageIndexId`,
|
|
89
|
+
* `encodeStorageTableId` — this package's own `storage-id.ts`) to the engine's KEYSPACE id
|
|
90
|
+
* (`indexKeyspaceId`/`tableKeyspaceId` — `@helipod/index-key-codec`'s `keyspace.ts`), so they
|
|
91
|
+
* inherently need both id spaces. `@helipod/id-codec` already depends on
|
|
92
|
+
* `@helipod/index-key-codec` (see `jump-hash.ts`'s `encodeIndexKey` import), so this direction adds
|
|
93
|
+
* no new edge; the reverse — moving these into `index-key-codec` and having IT depend on
|
|
94
|
+
* `id-codec`'s storage-id helpers — would create an import cycle (`id-codec -> index-key-codec ->
|
|
95
|
+
* id-codec`). `id-codec` is therefore the only cycle-free canonical home for this bridge.
|
|
96
|
+
*
|
|
97
|
+
* `ee/packages/fleet/src/ranges.ts` re-exports both functions from here verbatim — fleet's public
|
|
98
|
+
* API and existing tests are unaffected by the move.
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Convert a single written `(indexId, key)` pair into the sync handler's point range
|
|
103
|
+
* `[key, keySuccessor(key))` — the exact same half-open encoding `RangeSet.addKey` uses for a
|
|
104
|
+
* point read/write, so a follower's derived write range overlaps a subscription's recorded read
|
|
105
|
+
* range under `rangesOverlap`.
|
|
106
|
+
*
|
|
107
|
+
* `indexId` here is the storage `index_id` — the STORAGE index id produced by
|
|
108
|
+
* `encodeStorageIndexId`, format `"<tableNumber>/<indexName>"` (e.g. `"10001/by_creation"`). That
|
|
109
|
+
* is NOT the same string as the engine's keyspace id (`indexKeyspaceId`'s `"index:<table>:<name>"`
|
|
110
|
+
* / `tableKeyspaceId`'s `"table:<table>"`), which is what `SerializedKeyRange.keyspace` — and
|
|
111
|
+
* `rangesOverlap` — actually compare on. So the storage id must be decoded back into its parts and
|
|
112
|
+
* the keyspace REBUILT with the engine's own helper; feeding the raw storage id straight through
|
|
113
|
+
* silently produces ranges that can never overlap anything.
|
|
114
|
+
*/
|
|
115
|
+
declare function keyToPointRange(indexId: string, key: Uint8Array): SerializedKeyRange;
|
|
116
|
+
/**
|
|
117
|
+
* Convert a single written `(table_id, internal_id)` pair — e.g. from a `DerivedInvalidation`'s (or
|
|
118
|
+
* `AppliedInvalidation`'s) `writtenDocs` — into the DOCUMENT-keyspace point range a bare
|
|
119
|
+
* `ctx.db.get(id)` read records: `[internalId, keySuccessor(internalId))` under
|
|
120
|
+
* `tableKeyspaceId(table)`. Unlike `keyToPointRange` above, no decode/recompose round trip is
|
|
121
|
+
* needed here — `table_id` is already `encodeStorageTableId(tableNumber)`, exactly the string
|
|
122
|
+
* `tableKeyspaceId` expects; it is NOT a storage *index* id, so there is no separate "storage id"
|
|
123
|
+
* vs "engine keyspace id" split to bridge for the table half. This must match
|
|
124
|
+
* `single-writer-transactor.ts`'s `docKeyspace()` (`tableKeyspaceId(encodeStorageTableId(...))`)
|
|
125
|
+
* plus its `RangeSet.addKey(docKeyspace(id), id.internalId)` byte-for-byte, or a follower never
|
|
126
|
+
* invalidates a subscription whose only read was a point `get`.
|
|
127
|
+
*/
|
|
128
|
+
declare function docKeyToPointRange(tableId: string, internalId: Uint8Array): SerializedKeyRange;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Sharding primitives — scale-seam #1. A conversation (or any partition) maps to a shard
|
|
132
|
+
* key derived from a document field; a shard router maps that key to the single-writer
|
|
133
|
+
* shard that owns it. At **Tier 0 there is exactly one shard (`"default"`)** and the
|
|
134
|
+
* router always returns it, but the seam exists in the interfaces from day one so Tier 2
|
|
135
|
+
* per-conversation sharding is a config swap, not an engine rewrite.
|
|
136
|
+
*/
|
|
137
|
+
type ShardId = string;
|
|
138
|
+
type ShardKey = string;
|
|
139
|
+
declare const DEFAULT_SHARD: ShardId;
|
|
140
|
+
interface ShardKeyResolverInput {
|
|
141
|
+
table: string;
|
|
142
|
+
document: Record<string, unknown>;
|
|
143
|
+
}
|
|
144
|
+
/** Extracts the shard key from a document, or null if the table isn't sharded. */
|
|
145
|
+
interface ShardKeyResolver {
|
|
146
|
+
resolve(input: ShardKeyResolverInput): ShardKey | null;
|
|
147
|
+
}
|
|
148
|
+
/** Tier 0: nothing is sharded — every document lives in the default shard. */
|
|
149
|
+
declare class DefaultShardKeyResolver implements ShardKeyResolver {
|
|
150
|
+
resolve(): ShardKey | null;
|
|
151
|
+
}
|
|
152
|
+
/** Resolves the shard key from a configured field per table (e.g. `messages.conversationId`). */
|
|
153
|
+
declare class FieldShardKeyResolver implements ShardKeyResolver {
|
|
154
|
+
private readonly fieldByTable;
|
|
155
|
+
constructor(fieldByTable: ReadonlyMap<string, string>);
|
|
156
|
+
resolve({ table, document }: ShardKeyResolverInput): ShardKey | null;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Routes shard keys and clients to shards/nodes. Tier 2 implements consistent hashing
|
|
160
|
+
* (document→committer) and rendezvous hashing (client→sync node); `TStub` is the
|
|
161
|
+
* per-shard handle (e.g. a Durable Object stub).
|
|
162
|
+
*/
|
|
163
|
+
interface ShardRouter<TStub = unknown> {
|
|
164
|
+
getShardForKey(shardKey: ShardKey | null): ShardId;
|
|
165
|
+
getShardForDocument(table: string, shardKey: ShardKey | null): ShardId;
|
|
166
|
+
getSyncNodeId(clientId: string): string;
|
|
167
|
+
resolveStub?(shardId: ShardId): TStub;
|
|
168
|
+
}
|
|
169
|
+
/** Tier 0 router: one shard, one local sync node. */
|
|
170
|
+
declare class SimpleShardRouter implements ShardRouter {
|
|
171
|
+
getShardForKey(_shardKey: ShardKey | null): ShardId;
|
|
172
|
+
getShardForDocument(_table: string, _shardKey: ShardKey | null): ShardId;
|
|
173
|
+
getSyncNodeId(_clientId: string): string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Map a 64-bit `key` to a bucket in `[0, buckets)`. The reference ~10-line algorithm: walk a
|
|
178
|
+
* pseudo-random sequence of "jump" points, keeping the last one that lands below `buckets`.
|
|
179
|
+
* Deterministic and allocation-free; `buckets` must be ≥ 1.
|
|
180
|
+
*/
|
|
181
|
+
declare function jumpConsistentHash(key: bigint, buckets: number): number;
|
|
182
|
+
/**
|
|
183
|
+
* Resolve a shard-key VALUE (a scalar: string/number/bigint/boolean/null/bytes) to its shard id
|
|
184
|
+
* under `numShards`. Canonicalizes via the engine's own index-key encoding so routing is stable
|
|
185
|
+
* across the arg → route and document → route paths. Slot 0 → `"default"`, slot k → `"s"+k`.
|
|
186
|
+
*/
|
|
187
|
+
declare function shardIdForKeyValue(value: unknown, numShards: number): ShardId;
|
|
188
|
+
/**
|
|
189
|
+
* The canonical ordered shard-id list for `numShards` shards: `["default", "s1", …, "s{N-1}"]`.
|
|
190
|
+
* The array INDEX is the slot number — slot 0 is `"default"`, slot k is `"s"+k` — matching
|
|
191
|
+
* `shardIdForKeyValue`'s slot→id mapping exactly. This is the ONE source of truth for the
|
|
192
|
+
* slot↔shardId contract the fleet's per-shard commit pool (`NodePgClient`'s `commitPool.shards`),
|
|
193
|
+
* per-slot advisory locks (`tryAcquireShardLock(slot)`), and lease acquire-all loop all share, so
|
|
194
|
+
* nobody hand-rolls (and risks disagreeing on) the numbering. `numShards` must be ≥ 1.
|
|
195
|
+
*/
|
|
196
|
+
declare function shardIdList(numShards: number): ShardId[];
|
|
197
|
+
/**
|
|
198
|
+
* The B2a `ShardRouter`: jump-consistent-hash routing over `numShards` shards. Single-node, so
|
|
199
|
+
* every client resolves to one local sync node. `getShardForKey`/`getShardForDocument` take the
|
|
200
|
+
* already-extracted shard-key value (a string, per the seam); `shardIdForKeyValue` is the
|
|
201
|
+
* value-typed entry point the executor and kernel call directly with the raw arg/field value.
|
|
202
|
+
*/
|
|
203
|
+
declare class JumpShardRouter implements ShardRouter {
|
|
204
|
+
private readonly numShards;
|
|
205
|
+
constructor(numShards: number);
|
|
206
|
+
getShardForKey(shardKey: ShardKey | null): ShardId;
|
|
207
|
+
getShardForDocument(_table: string, shardKey: ShardKey | null): ShardId;
|
|
208
|
+
getSyncNodeId(_clientId: string): string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The table registry assigns each table a stable numeric id (embedded in document ids).
|
|
213
|
+
* System tables (`_`-prefixed) get numbers 1–9999; user tables start at 10001, leaving a
|
|
214
|
+
* reserved gap. The DocStore-backed durable registry arrives in M2; this in-memory one is
|
|
215
|
+
* the canonical interface + the Tier 0 / test implementation.
|
|
216
|
+
*/
|
|
217
|
+
type TableVisibility = "user" | "system";
|
|
218
|
+
type TableState = "active" | "creating" | "deleting";
|
|
219
|
+
interface TableInfo {
|
|
220
|
+
name: string;
|
|
221
|
+
tableNumber: number;
|
|
222
|
+
visibility: TableVisibility;
|
|
223
|
+
state: TableState;
|
|
224
|
+
/** The document field used as the shard key (seam #1), or null. */
|
|
225
|
+
shardKey: string | null;
|
|
226
|
+
}
|
|
227
|
+
interface AllocateOptions {
|
|
228
|
+
visibility?: TableVisibility;
|
|
229
|
+
shardKey?: string | null;
|
|
230
|
+
}
|
|
231
|
+
interface TableRegistry {
|
|
232
|
+
getByName(name: string): TableInfo | undefined;
|
|
233
|
+
getByNumber(tableNumber: number): TableInfo | undefined;
|
|
234
|
+
allocate(name: string, options?: AllocateOptions): TableInfo;
|
|
235
|
+
list(): TableInfo[];
|
|
236
|
+
}
|
|
237
|
+
interface PreassignOptions {
|
|
238
|
+
visibility?: TableVisibility;
|
|
239
|
+
shardKey?: string | null;
|
|
240
|
+
state?: TableState;
|
|
241
|
+
}
|
|
242
|
+
declare const SYSTEM_TABLE_NUMBER_MIN = 1;
|
|
243
|
+
declare const SYSTEM_TABLE_NUMBER_MAX = 9999;
|
|
244
|
+
declare const USER_TABLE_NUMBER_START = 10001;
|
|
245
|
+
/**
|
|
246
|
+
* Reserved table numbers for built-in app-namespace system tables. These MUST stay stable
|
|
247
|
+
* forever: a persisted `Id<"_storage">` encodes this number, so it must always decode back to
|
|
248
|
+
* the same table. Registry seeding pins them with `preassign(name, number)` before any other
|
|
249
|
+
* system table is auto-allocated, keeping their numbers deterministic across runs regardless of
|
|
250
|
+
* registration order. `_storage` (the file-storage feature's document table) is 20.
|
|
251
|
+
*/
|
|
252
|
+
declare const STORAGE_TABLE_NUMBER = 20;
|
|
253
|
+
declare function isSystemTableName(name: string): boolean;
|
|
254
|
+
declare function getFullTableName(name: string, componentPath: string): string;
|
|
255
|
+
declare function parseFullTableName(fullName: string): {
|
|
256
|
+
componentPath: string;
|
|
257
|
+
name: string;
|
|
258
|
+
};
|
|
259
|
+
declare class MemoryTableRegistry implements TableRegistry {
|
|
260
|
+
private readonly byName;
|
|
261
|
+
private readonly byNumber;
|
|
262
|
+
private nextUser;
|
|
263
|
+
private nextSystem;
|
|
264
|
+
getByName(name: string): TableInfo | undefined;
|
|
265
|
+
getByNumber(tableNumber: number): TableInfo | undefined;
|
|
266
|
+
/** Idempotent: allocating an existing name returns the existing entry. */
|
|
267
|
+
allocate(name: string, options?: AllocateOptions): TableInfo;
|
|
268
|
+
list(): TableInfo[];
|
|
269
|
+
/**
|
|
270
|
+
* Pre-register a table at a KNOWN (already-live) number, so a later `allocate(name)` for the
|
|
271
|
+
* same name returns this number instead of minting a fresh one — used to seed a fresh registry
|
|
272
|
+
* with a running deploy's table numbers before composing a new schema, so existing tables
|
|
273
|
+
* (app AND component) never renumber; only genuinely-new tables get numbers above the seeded
|
|
274
|
+
* max. Idempotent/no-op if `name` is already known (first registration wins, matching
|
|
275
|
+
* `allocate`'s idempotency). Bumps the relevant `next*` counter so future `allocate` calls
|
|
276
|
+
* never collide with a preassigned number.
|
|
277
|
+
*/
|
|
278
|
+
preassign(name: string, tableNumber: number, options?: PreassignOptions): TableInfo;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export { type AllocateOptions, Base32Error, CROCKFORD_ALPHABET, DEFAULT_SHARD, DefaultShardKeyResolver, type DocumentId, DocumentIdError, FieldShardKeyResolver, INTERNAL_ID_BYTES, type InternalDocumentId, type InternalId, JumpShardRouter, MemoryTableRegistry, STORAGE_TABLE_NUMBER, SYSTEM_TABLE_NUMBER_MAX, SYSTEM_TABLE_NUMBER_MIN, type ShardId, type ShardKey, type ShardKeyResolver, type ShardKeyResolverInput, type ShardRouter, SimpleShardRouter, type TableInfo, type TableRegistry, type TableState, type TableVisibility, USER_TABLE_NUMBER_START, type VarintDecodeResult, VarintError, base32Decode, base32Encode, decodeDocumentId, decodeStorageIndexId, decodeStorageTableId, docKeyToPointRange, documentIdKey, documentIdsEqual, encodeDocumentId, encodeInternalDocumentId, encodeStorageIndexId, encodeStorageTableId, fletcher16, generateInternalId, getEncodedLength, getFullTableName, internalIdToHex, isSystemTableName, isValidBase32, isValidDocumentId, jumpConsistentHash, keyToPointRange, mintEncodedDocumentId, newDocumentId, parseDocumentIdKey, parseFullTableName, shardIdForKeyValue, shardIdList, tryDecodeDocumentId, varintDecode, varintEncode, varintEncodedLength, verifyFletcher16 };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
// src/base32.ts
|
|
2
|
+
var CROCKFORD_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
3
|
+
var DECODE = (() => {
|
|
4
|
+
const map = {};
|
|
5
|
+
for (let i = 0; i < CROCKFORD_ALPHABET.length; i++) {
|
|
6
|
+
const ch = CROCKFORD_ALPHABET[i];
|
|
7
|
+
map[ch] = i;
|
|
8
|
+
map[ch.toUpperCase()] = i;
|
|
9
|
+
}
|
|
10
|
+
map["i"] = map["I"] = 1;
|
|
11
|
+
map["l"] = map["L"] = 1;
|
|
12
|
+
map["o"] = map["O"] = 0;
|
|
13
|
+
return map;
|
|
14
|
+
})();
|
|
15
|
+
var Base32Error = class extends Error {
|
|
16
|
+
name = "Base32Error";
|
|
17
|
+
};
|
|
18
|
+
function base32Encode(bytes) {
|
|
19
|
+
let bits = 0;
|
|
20
|
+
let value = 0;
|
|
21
|
+
let out = "";
|
|
22
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
23
|
+
value = value << 8 | bytes[i];
|
|
24
|
+
bits += 8;
|
|
25
|
+
while (bits >= 5) {
|
|
26
|
+
out += CROCKFORD_ALPHABET[value >>> bits - 5 & 31];
|
|
27
|
+
bits -= 5;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (bits > 0) out += CROCKFORD_ALPHABET[value << 5 - bits & 31];
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function base32Decode(text) {
|
|
34
|
+
let bits = 0;
|
|
35
|
+
let value = 0;
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const ch of text) {
|
|
38
|
+
const v = DECODE[ch];
|
|
39
|
+
if (v === void 0) throw new Base32Error(`invalid base32 character: ${JSON.stringify(ch)}`);
|
|
40
|
+
value = value << 5 | v;
|
|
41
|
+
bits += 5;
|
|
42
|
+
if (bits >= 8) {
|
|
43
|
+
out.push(value >>> bits - 8 & 255);
|
|
44
|
+
bits -= 8;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (bits > 0 && (value & (1 << bits) - 1) !== 0) {
|
|
48
|
+
throw new Base32Error("non-canonical base32 padding bits");
|
|
49
|
+
}
|
|
50
|
+
return Uint8Array.from(out);
|
|
51
|
+
}
|
|
52
|
+
function isValidBase32(text) {
|
|
53
|
+
for (const ch of text) if (DECODE[ch] === void 0) return false;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/checksum.ts
|
|
58
|
+
function fletcher16(bytes) {
|
|
59
|
+
let sum1 = 0;
|
|
60
|
+
let sum2 = 0;
|
|
61
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
62
|
+
sum1 = (sum1 + bytes[i]) % 255;
|
|
63
|
+
sum2 = (sum2 + sum1) % 255;
|
|
64
|
+
}
|
|
65
|
+
return sum2 << 8 | sum1;
|
|
66
|
+
}
|
|
67
|
+
function verifyFletcher16(bytes, checksum) {
|
|
68
|
+
return fletcher16(bytes) === checksum;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/varint.ts
|
|
72
|
+
var VarintError = class extends Error {
|
|
73
|
+
name = "VarintError";
|
|
74
|
+
};
|
|
75
|
+
var UINT32_MAX = 4294967295;
|
|
76
|
+
function varintEncode(value) {
|
|
77
|
+
if (!Number.isInteger(value) || value < 0 || value > UINT32_MAX) {
|
|
78
|
+
throw new VarintError(`varint value out of uint32 range: ${value}`);
|
|
79
|
+
}
|
|
80
|
+
const out = [];
|
|
81
|
+
let v = value;
|
|
82
|
+
do {
|
|
83
|
+
let byte = v & 127;
|
|
84
|
+
v = Math.floor(v / 128);
|
|
85
|
+
if (v > 0) byte |= 128;
|
|
86
|
+
out.push(byte);
|
|
87
|
+
} while (v > 0);
|
|
88
|
+
return Uint8Array.from(out);
|
|
89
|
+
}
|
|
90
|
+
function varintDecode(bytes, offset = 0) {
|
|
91
|
+
let result = 0;
|
|
92
|
+
let shift = 0;
|
|
93
|
+
let pos = offset;
|
|
94
|
+
while (true) {
|
|
95
|
+
if (pos >= bytes.length) throw new VarintError("varint truncated");
|
|
96
|
+
const byte = bytes[pos];
|
|
97
|
+
result += (byte & 127) * 2 ** shift;
|
|
98
|
+
pos += 1;
|
|
99
|
+
if ((byte & 128) === 0) break;
|
|
100
|
+
shift += 7;
|
|
101
|
+
if (shift > 28) throw new VarintError("varint exceeds uint32");
|
|
102
|
+
}
|
|
103
|
+
if (result > UINT32_MAX) throw new VarintError("varint exceeds uint32");
|
|
104
|
+
return { value: result, bytesRead: pos - offset };
|
|
105
|
+
}
|
|
106
|
+
function varintEncodedLength(value) {
|
|
107
|
+
return varintEncode(value).length;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/document-id.ts
|
|
111
|
+
var INTERNAL_ID_BYTES = 16;
|
|
112
|
+
var CHECKSUM_BYTES = 2;
|
|
113
|
+
var DocumentIdError = class extends Error {
|
|
114
|
+
name = "DocumentIdError";
|
|
115
|
+
};
|
|
116
|
+
function concatBytes(...parts) {
|
|
117
|
+
const total = parts.reduce((n, p) => n + p.length, 0);
|
|
118
|
+
const out = new Uint8Array(total);
|
|
119
|
+
let offset = 0;
|
|
120
|
+
for (const p of parts) {
|
|
121
|
+
out.set(p, offset);
|
|
122
|
+
offset += p.length;
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
function internalIdToHex(internalId) {
|
|
127
|
+
let hex = "";
|
|
128
|
+
for (let i = 0; i < internalId.length; i++) hex += internalId[i].toString(16).padStart(2, "0");
|
|
129
|
+
return hex;
|
|
130
|
+
}
|
|
131
|
+
function hexToInternalId(hex) {
|
|
132
|
+
if (hex.length % 2 !== 0) throw new DocumentIdError("odd-length hex");
|
|
133
|
+
const out = new Uint8Array(hex.length / 2);
|
|
134
|
+
for (let i = 0; i < out.length; i++) {
|
|
135
|
+
const byte = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
136
|
+
if (Number.isNaN(byte)) throw new DocumentIdError("invalid hex");
|
|
137
|
+
out[i] = byte;
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
function generateInternalId() {
|
|
142
|
+
const bytes = new Uint8Array(INTERNAL_ID_BYTES);
|
|
143
|
+
crypto.getRandomValues(bytes);
|
|
144
|
+
return bytes;
|
|
145
|
+
}
|
|
146
|
+
function newDocumentId(tableNumber) {
|
|
147
|
+
return { tableNumber, internalId: generateInternalId() };
|
|
148
|
+
}
|
|
149
|
+
function encodeDocumentId(tableNumber, internalId) {
|
|
150
|
+
if (internalId.length !== INTERNAL_ID_BYTES) {
|
|
151
|
+
throw new DocumentIdError(`internalId must be ${INTERNAL_ID_BYTES} bytes, got ${internalId.length}`);
|
|
152
|
+
}
|
|
153
|
+
const prefix = concatBytes(varintEncode(tableNumber), internalId);
|
|
154
|
+
const checksum = fletcher16(prefix);
|
|
155
|
+
const checksumBytes = new Uint8Array([checksum >>> 8 & 255, checksum & 255]);
|
|
156
|
+
return base32Encode(concatBytes(prefix, checksumBytes));
|
|
157
|
+
}
|
|
158
|
+
function encodeInternalDocumentId(id) {
|
|
159
|
+
return encodeDocumentId(id.tableNumber, id.internalId);
|
|
160
|
+
}
|
|
161
|
+
function mintEncodedDocumentId(tableNumber) {
|
|
162
|
+
return encodeInternalDocumentId(newDocumentId(tableNumber));
|
|
163
|
+
}
|
|
164
|
+
function decodeDocumentId(encoded) {
|
|
165
|
+
let bytes;
|
|
166
|
+
try {
|
|
167
|
+
bytes = base32Decode(encoded);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
throw new DocumentIdError(e instanceof Base32Error ? e.message : "invalid base32");
|
|
170
|
+
}
|
|
171
|
+
let tableNumber;
|
|
172
|
+
let bytesRead;
|
|
173
|
+
try {
|
|
174
|
+
({ value: tableNumber, bytesRead } = varintDecode(bytes, 0));
|
|
175
|
+
} catch {
|
|
176
|
+
throw new DocumentIdError("invalid table number");
|
|
177
|
+
}
|
|
178
|
+
const internalIdEnd = bytesRead + INTERNAL_ID_BYTES;
|
|
179
|
+
if (bytes.length !== internalIdEnd + CHECKSUM_BYTES) {
|
|
180
|
+
throw new DocumentIdError("incorrect id length");
|
|
181
|
+
}
|
|
182
|
+
const internalId = bytes.slice(bytesRead, internalIdEnd);
|
|
183
|
+
const checksum = bytes[internalIdEnd] << 8 | bytes[internalIdEnd + 1];
|
|
184
|
+
const prefix = bytes.slice(0, internalIdEnd);
|
|
185
|
+
if (!verifyFletcher16(prefix, checksum)) throw new DocumentIdError("checksum mismatch");
|
|
186
|
+
return { tableNumber, internalId };
|
|
187
|
+
}
|
|
188
|
+
function tryDecodeDocumentId(encoded) {
|
|
189
|
+
try {
|
|
190
|
+
return decodeDocumentId(encoded);
|
|
191
|
+
} catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function isValidDocumentId(encoded, expectedTableNumber) {
|
|
196
|
+
const decoded = tryDecodeDocumentId(encoded);
|
|
197
|
+
if (decoded === null) return false;
|
|
198
|
+
return expectedTableNumber === void 0 || decoded.tableNumber === expectedTableNumber;
|
|
199
|
+
}
|
|
200
|
+
function getEncodedLength(tableNumber) {
|
|
201
|
+
const byteLen = varintEncodedLength(tableNumber) + INTERNAL_ID_BYTES + CHECKSUM_BYTES;
|
|
202
|
+
return Math.ceil(byteLen * 8 / 5);
|
|
203
|
+
}
|
|
204
|
+
function documentIdKey(id) {
|
|
205
|
+
return `${id.tableNumber}:${internalIdToHex(id.internalId)}`;
|
|
206
|
+
}
|
|
207
|
+
function parseDocumentIdKey(key) {
|
|
208
|
+
const sep = key.indexOf(":");
|
|
209
|
+
if (sep < 0) throw new DocumentIdError("invalid document id key");
|
|
210
|
+
const tableNumber = Number.parseInt(key.slice(0, sep), 10);
|
|
211
|
+
if (!Number.isInteger(tableNumber)) throw new DocumentIdError("invalid table number in key");
|
|
212
|
+
return { tableNumber, internalId: hexToInternalId(key.slice(sep + 1)) };
|
|
213
|
+
}
|
|
214
|
+
function documentIdsEqual(a, b) {
|
|
215
|
+
if (a.tableNumber !== b.tableNumber || a.internalId.length !== b.internalId.length) return false;
|
|
216
|
+
for (let i = 0; i < a.internalId.length; i++) if (a.internalId[i] !== b.internalId[i]) return false;
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/storage-id.ts
|
|
221
|
+
function encodeStorageTableId(tableNumber) {
|
|
222
|
+
return String(tableNumber);
|
|
223
|
+
}
|
|
224
|
+
function decodeStorageTableId(tableId) {
|
|
225
|
+
const n = Number.parseInt(tableId, 10);
|
|
226
|
+
if (!Number.isInteger(n) || String(n) !== tableId) throw new Error(`invalid storage table id: ${tableId}`);
|
|
227
|
+
return n;
|
|
228
|
+
}
|
|
229
|
+
function encodeStorageIndexId(tableNumber, indexName) {
|
|
230
|
+
return `${tableNumber}/${indexName}`;
|
|
231
|
+
}
|
|
232
|
+
function decodeStorageIndexId(indexId) {
|
|
233
|
+
const sep = indexId.indexOf("/");
|
|
234
|
+
if (sep < 0) throw new Error(`invalid storage index id: ${indexId}`);
|
|
235
|
+
return {
|
|
236
|
+
tableNumber: decodeStorageTableId(indexId.slice(0, sep)),
|
|
237
|
+
indexName: indexId.slice(sep + 1)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/point-range.ts
|
|
242
|
+
import {
|
|
243
|
+
keySuccessor,
|
|
244
|
+
serializeKeyRange,
|
|
245
|
+
indexKeyspaceId,
|
|
246
|
+
tableKeyspaceId
|
|
247
|
+
} from "@helipod/index-key-codec";
|
|
248
|
+
function keyToPointRange(indexId, key) {
|
|
249
|
+
const { tableNumber, indexName } = decodeStorageIndexId(indexId);
|
|
250
|
+
const keyspace = indexKeyspaceId(encodeStorageTableId(tableNumber), indexName);
|
|
251
|
+
return serializeKeyRange({ keyspace, start: key, end: keySuccessor(key) });
|
|
252
|
+
}
|
|
253
|
+
function docKeyToPointRange(tableId, internalId) {
|
|
254
|
+
const keyspace = tableKeyspaceId(tableId);
|
|
255
|
+
return serializeKeyRange({ keyspace, start: internalId, end: keySuccessor(internalId) });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/shard.ts
|
|
259
|
+
var DEFAULT_SHARD = "default";
|
|
260
|
+
var DefaultShardKeyResolver = class {
|
|
261
|
+
resolve() {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
var FieldShardKeyResolver = class {
|
|
266
|
+
constructor(fieldByTable) {
|
|
267
|
+
this.fieldByTable = fieldByTable;
|
|
268
|
+
}
|
|
269
|
+
fieldByTable;
|
|
270
|
+
resolve({ table, document }) {
|
|
271
|
+
const field = this.fieldByTable.get(table);
|
|
272
|
+
if (field === void 0) return null;
|
|
273
|
+
const value = document[field];
|
|
274
|
+
return value === void 0 || value === null ? null : String(value);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var SimpleShardRouter = class {
|
|
278
|
+
getShardForKey(_shardKey) {
|
|
279
|
+
return DEFAULT_SHARD;
|
|
280
|
+
}
|
|
281
|
+
getShardForDocument(_table, _shardKey) {
|
|
282
|
+
return DEFAULT_SHARD;
|
|
283
|
+
}
|
|
284
|
+
getSyncNodeId(_clientId) {
|
|
285
|
+
return "local";
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// src/jump-hash.ts
|
|
290
|
+
import { encodeIndexKey } from "@helipod/index-key-codec";
|
|
291
|
+
var U64_MASK = 0xffffffffffffffffn;
|
|
292
|
+
var LCG_MULT = 2862933555777941757n;
|
|
293
|
+
function jumpConsistentHash(key, buckets) {
|
|
294
|
+
if (buckets < 1) throw new RangeError(`buckets must be >= 1, got ${buckets}`);
|
|
295
|
+
let k = BigInt.asUintN(64, key);
|
|
296
|
+
let b = -1n;
|
|
297
|
+
let j = 0n;
|
|
298
|
+
const n = BigInt(buckets);
|
|
299
|
+
while (j < n) {
|
|
300
|
+
b = j;
|
|
301
|
+
k = k * LCG_MULT + 1n & U64_MASK;
|
|
302
|
+
const denom = Number(k >> 33n) + 1;
|
|
303
|
+
j = BigInt(Math.floor((Number(b) + 1) * (2147483648 / denom)));
|
|
304
|
+
}
|
|
305
|
+
return Number(b);
|
|
306
|
+
}
|
|
307
|
+
function fnv1a64(bytes) {
|
|
308
|
+
let h = 0xcbf29ce484222325n;
|
|
309
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
310
|
+
h ^= BigInt(bytes[i]);
|
|
311
|
+
h = h * 0x100000001b3n & U64_MASK;
|
|
312
|
+
}
|
|
313
|
+
return h;
|
|
314
|
+
}
|
|
315
|
+
function shardIdForKeyValue(value, numShards) {
|
|
316
|
+
const bytes = encodeIndexKey([value]);
|
|
317
|
+
const slot = jumpConsistentHash(fnv1a64(bytes), Math.max(1, numShards));
|
|
318
|
+
return slot === 0 ? DEFAULT_SHARD : `s${slot}`;
|
|
319
|
+
}
|
|
320
|
+
function shardIdList(numShards) {
|
|
321
|
+
if (numShards < 1) throw new RangeError(`numShards must be >= 1, got ${numShards}`);
|
|
322
|
+
const ids = [DEFAULT_SHARD];
|
|
323
|
+
for (let slot = 1; slot < numShards; slot++) ids.push(`s${slot}`);
|
|
324
|
+
return ids;
|
|
325
|
+
}
|
|
326
|
+
var JumpShardRouter = class {
|
|
327
|
+
constructor(numShards) {
|
|
328
|
+
this.numShards = numShards;
|
|
329
|
+
if (numShards < 1) throw new RangeError(`numShards must be >= 1, got ${numShards}`);
|
|
330
|
+
}
|
|
331
|
+
numShards;
|
|
332
|
+
getShardForKey(shardKey) {
|
|
333
|
+
return shardKey === null ? DEFAULT_SHARD : shardIdForKeyValue(shardKey, this.numShards);
|
|
334
|
+
}
|
|
335
|
+
getShardForDocument(_table, shardKey) {
|
|
336
|
+
return this.getShardForKey(shardKey);
|
|
337
|
+
}
|
|
338
|
+
getSyncNodeId(_clientId) {
|
|
339
|
+
return "local";
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// src/table-registry.ts
|
|
344
|
+
var SYSTEM_TABLE_NUMBER_MIN = 1;
|
|
345
|
+
var SYSTEM_TABLE_NUMBER_MAX = 9999;
|
|
346
|
+
var USER_TABLE_NUMBER_START = 10001;
|
|
347
|
+
var STORAGE_TABLE_NUMBER = 20;
|
|
348
|
+
function isSystemTableName(name) {
|
|
349
|
+
return name.startsWith("_");
|
|
350
|
+
}
|
|
351
|
+
function getFullTableName(name, componentPath) {
|
|
352
|
+
return componentPath ? `${componentPath}/${name}` : name;
|
|
353
|
+
}
|
|
354
|
+
function parseFullTableName(fullName) {
|
|
355
|
+
const i = fullName.lastIndexOf("/");
|
|
356
|
+
return i < 0 ? { componentPath: "", name: fullName } : { componentPath: fullName.slice(0, i), name: fullName.slice(i + 1) };
|
|
357
|
+
}
|
|
358
|
+
var MemoryTableRegistry = class {
|
|
359
|
+
byName = /* @__PURE__ */ new Map();
|
|
360
|
+
byNumber = /* @__PURE__ */ new Map();
|
|
361
|
+
nextUser = USER_TABLE_NUMBER_START;
|
|
362
|
+
nextSystem = SYSTEM_TABLE_NUMBER_MIN;
|
|
363
|
+
getByName(name) {
|
|
364
|
+
return this.byName.get(name);
|
|
365
|
+
}
|
|
366
|
+
getByNumber(tableNumber) {
|
|
367
|
+
return this.byNumber.get(tableNumber);
|
|
368
|
+
}
|
|
369
|
+
/** Idempotent: allocating an existing name returns the existing entry. */
|
|
370
|
+
allocate(name, options = {}) {
|
|
371
|
+
const existing = this.byName.get(name);
|
|
372
|
+
if (existing) return existing;
|
|
373
|
+
const visibility = options.visibility ?? (isSystemTableName(name) ? "system" : "user");
|
|
374
|
+
let tableNumber;
|
|
375
|
+
if (visibility === "system") {
|
|
376
|
+
if (this.nextSystem > SYSTEM_TABLE_NUMBER_MAX) throw new Error("exhausted system table numbers");
|
|
377
|
+
tableNumber = this.nextSystem++;
|
|
378
|
+
} else {
|
|
379
|
+
tableNumber = this.nextUser++;
|
|
380
|
+
}
|
|
381
|
+
const info = {
|
|
382
|
+
name,
|
|
383
|
+
tableNumber,
|
|
384
|
+
visibility,
|
|
385
|
+
state: "active",
|
|
386
|
+
shardKey: options.shardKey ?? null
|
|
387
|
+
};
|
|
388
|
+
this.byName.set(name, info);
|
|
389
|
+
this.byNumber.set(tableNumber, info);
|
|
390
|
+
return info;
|
|
391
|
+
}
|
|
392
|
+
list() {
|
|
393
|
+
return [...this.byName.values()];
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Pre-register a table at a KNOWN (already-live) number, so a later `allocate(name)` for the
|
|
397
|
+
* same name returns this number instead of minting a fresh one — used to seed a fresh registry
|
|
398
|
+
* with a running deploy's table numbers before composing a new schema, so existing tables
|
|
399
|
+
* (app AND component) never renumber; only genuinely-new tables get numbers above the seeded
|
|
400
|
+
* max. Idempotent/no-op if `name` is already known (first registration wins, matching
|
|
401
|
+
* `allocate`'s idempotency). Bumps the relevant `next*` counter so future `allocate` calls
|
|
402
|
+
* never collide with a preassigned number.
|
|
403
|
+
*/
|
|
404
|
+
preassign(name, tableNumber, options = {}) {
|
|
405
|
+
const existing = this.byName.get(name);
|
|
406
|
+
if (existing) return existing;
|
|
407
|
+
const visibility = options.visibility ?? (isSystemTableName(name) ? "system" : "user");
|
|
408
|
+
const info = {
|
|
409
|
+
name,
|
|
410
|
+
tableNumber,
|
|
411
|
+
visibility,
|
|
412
|
+
state: options.state ?? "active",
|
|
413
|
+
shardKey: options.shardKey ?? null
|
|
414
|
+
};
|
|
415
|
+
this.byName.set(name, info);
|
|
416
|
+
this.byNumber.set(tableNumber, info);
|
|
417
|
+
if (visibility === "system") {
|
|
418
|
+
this.nextSystem = Math.max(this.nextSystem, tableNumber + 1);
|
|
419
|
+
} else {
|
|
420
|
+
this.nextUser = Math.max(this.nextUser, tableNumber + 1);
|
|
421
|
+
}
|
|
422
|
+
return info;
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
export {
|
|
426
|
+
Base32Error,
|
|
427
|
+
CROCKFORD_ALPHABET,
|
|
428
|
+
DEFAULT_SHARD,
|
|
429
|
+
DefaultShardKeyResolver,
|
|
430
|
+
DocumentIdError,
|
|
431
|
+
FieldShardKeyResolver,
|
|
432
|
+
INTERNAL_ID_BYTES,
|
|
433
|
+
JumpShardRouter,
|
|
434
|
+
MemoryTableRegistry,
|
|
435
|
+
STORAGE_TABLE_NUMBER,
|
|
436
|
+
SYSTEM_TABLE_NUMBER_MAX,
|
|
437
|
+
SYSTEM_TABLE_NUMBER_MIN,
|
|
438
|
+
SimpleShardRouter,
|
|
439
|
+
USER_TABLE_NUMBER_START,
|
|
440
|
+
VarintError,
|
|
441
|
+
base32Decode,
|
|
442
|
+
base32Encode,
|
|
443
|
+
decodeDocumentId,
|
|
444
|
+
decodeStorageIndexId,
|
|
445
|
+
decodeStorageTableId,
|
|
446
|
+
docKeyToPointRange,
|
|
447
|
+
documentIdKey,
|
|
448
|
+
documentIdsEqual,
|
|
449
|
+
encodeDocumentId,
|
|
450
|
+
encodeInternalDocumentId,
|
|
451
|
+
encodeStorageIndexId,
|
|
452
|
+
encodeStorageTableId,
|
|
453
|
+
fletcher16,
|
|
454
|
+
generateInternalId,
|
|
455
|
+
getEncodedLength,
|
|
456
|
+
getFullTableName,
|
|
457
|
+
internalIdToHex,
|
|
458
|
+
isSystemTableName,
|
|
459
|
+
isValidBase32,
|
|
460
|
+
isValidDocumentId,
|
|
461
|
+
jumpConsistentHash,
|
|
462
|
+
keyToPointRange,
|
|
463
|
+
mintEncodedDocumentId,
|
|
464
|
+
newDocumentId,
|
|
465
|
+
parseDocumentIdKey,
|
|
466
|
+
parseFullTableName,
|
|
467
|
+
shardIdForKeyValue,
|
|
468
|
+
shardIdList,
|
|
469
|
+
tryDecodeDocumentId,
|
|
470
|
+
varintDecode,
|
|
471
|
+
varintEncode,
|
|
472
|
+
varintEncodedLength,
|
|
473
|
+
verifyFletcher16
|
|
474
|
+
};
|
|
475
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/base32.ts","../src/checksum.ts","../src/varint.ts","../src/document-id.ts","../src/storage-id.ts","../src/point-range.ts","../src/shard.ts","../src/jump-hash.ts","../src/table-registry.ts"],"sourcesContent":["/**\n * Crockford Base32 — case-insensitive, omits the ambiguous letters I, L, O, U.\n * Used to render binary document ids as compact, human-safe, copy-pasteable strings.\n */\nexport const CROCKFORD_ALPHABET = \"0123456789abcdefghjkmnpqrstvwxyz\"; // omits i l o u\n\nconst DECODE: Record<string, number> = (() => {\n const map: Record<string, number> = {};\n for (let i = 0; i < CROCKFORD_ALPHABET.length; i++) {\n const ch = CROCKFORD_ALPHABET[i]!;\n map[ch] = i;\n map[ch.toUpperCase()] = i;\n }\n // Crockford normalization of look-alikes.\n map[\"i\"] = map[\"I\"] = 1;\n map[\"l\"] = map[\"L\"] = 1;\n map[\"o\"] = map[\"O\"] = 0;\n return map;\n})();\n\nexport class Base32Error extends Error {\n override name = \"Base32Error\";\n}\n\nexport function base32Encode(bytes: Uint8Array): string {\n let bits = 0;\n let value = 0;\n let out = \"\";\n for (let i = 0; i < bytes.length; i++) {\n value = (value << 8) | bytes[i]!;\n bits += 8;\n while (bits >= 5) {\n out += CROCKFORD_ALPHABET[(value >>> (bits - 5)) & 0x1f];\n bits -= 5;\n }\n }\n if (bits > 0) out += CROCKFORD_ALPHABET[(value << (5 - bits)) & 0x1f];\n return out;\n}\n\nexport function base32Decode(text: string): Uint8Array {\n let bits = 0;\n let value = 0;\n const out: number[] = [];\n for (const ch of text) {\n const v = DECODE[ch];\n if (v === undefined) throw new Base32Error(`invalid base32 character: ${JSON.stringify(ch)}`);\n value = (value << 5) | v;\n bits += 5;\n if (bits >= 8) {\n out.push((value >>> (bits - 8)) & 0xff);\n bits -= 8;\n }\n }\n // Leftover bits must be zero padding (canonical form), else the string is malformed.\n if (bits > 0 && (value & ((1 << bits) - 1)) !== 0) {\n throw new Base32Error(\"non-canonical base32 padding bits\");\n }\n return Uint8Array.from(out);\n}\n\nexport function isValidBase32(text: string): boolean {\n for (const ch of text) if (DECODE[ch] === undefined) return false;\n return true;\n}\n","/**\n * Fletcher-16 checksum. Appended to each document id so a corrupted or fabricated id is\n * rejected client-side without a database round-trip.\n */\nexport function fletcher16(bytes: Uint8Array): number {\n let sum1 = 0;\n let sum2 = 0;\n for (let i = 0; i < bytes.length; i++) {\n sum1 = (sum1 + bytes[i]!) % 255;\n sum2 = (sum2 + sum1) % 255;\n }\n return (sum2 << 8) | sum1;\n}\n\nexport function verifyFletcher16(bytes: Uint8Array, checksum: number): boolean {\n return fletcher16(bytes) === checksum;\n}\n","/**\n * LEB128 unsigned varint over uint32 (1–5 bytes). Encodes the table number that\n * prefixes a document id, so small system table numbers stay compact.\n */\nexport class VarintError extends Error {\n override name = \"VarintError\";\n}\n\nconst UINT32_MAX = 0xffffffff;\n\nexport function varintEncode(value: number): Uint8Array {\n if (!Number.isInteger(value) || value < 0 || value > UINT32_MAX) {\n throw new VarintError(`varint value out of uint32 range: ${value}`);\n }\n const out: number[] = [];\n let v = value;\n do {\n let byte = v & 0x7f;\n v = Math.floor(v / 128);\n if (v > 0) byte |= 0x80;\n out.push(byte);\n } while (v > 0);\n return Uint8Array.from(out);\n}\n\nexport interface VarintDecodeResult {\n value: number;\n bytesRead: number;\n}\n\nexport function varintDecode(bytes: Uint8Array, offset = 0): VarintDecodeResult {\n let result = 0;\n let shift = 0;\n let pos = offset;\n while (true) {\n if (pos >= bytes.length) throw new VarintError(\"varint truncated\");\n const byte = bytes[pos]!;\n result += (byte & 0x7f) * 2 ** shift;\n pos += 1;\n if ((byte & 0x80) === 0) break;\n shift += 7;\n if (shift > 28) throw new VarintError(\"varint exceeds uint32\");\n }\n if (result > UINT32_MAX) throw new VarintError(\"varint exceeds uint32\");\n return { value: result, bytesRead: pos - offset };\n}\n\nexport function varintEncodedLength(value: number): number {\n return varintEncode(value).length;\n}\n","/**\n * Document identity. Internally a document is `(tableNumber, internalId)` where\n * `internalId` is 16 random bytes. The developer-facing `DocumentId` string is:\n *\n * base32( varint(tableNumber) ++ internalId(16) ++ fletcher16(prefix)[2] )\n *\n * giving a compact (31–37 char), self-validating, order-irrelevant id. The table number\n * (not the name) is embedded, so renaming a table never rewrites its ids.\n */\nimport { base32Encode, base32Decode, Base32Error } from \"./base32\";\nimport { fletcher16, verifyFletcher16 } from \"./checksum\";\nimport { varintEncode, varintDecode, varintEncodedLength } from \"./varint\";\n\nexport type InternalId = Uint8Array;\n\nexport const INTERNAL_ID_BYTES = 16;\nconst CHECKSUM_BYTES = 2;\n\nexport interface InternalDocumentId {\n tableNumber: number;\n internalId: InternalId;\n}\n\ndeclare const docIdBrand: unique symbol;\nexport type DocumentId = string & { readonly [docIdBrand]: \"DocumentId\" };\n\nexport class DocumentIdError extends Error {\n override name = \"DocumentIdError\";\n}\n\nfunction concatBytes(...parts: Uint8Array[]): Uint8Array {\n const total = parts.reduce((n, p) => n + p.length, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const p of parts) {\n out.set(p, offset);\n offset += p.length;\n }\n return out;\n}\n\nexport function internalIdToHex(internalId: InternalId): string {\n let hex = \"\";\n for (let i = 0; i < internalId.length; i++) hex += internalId[i]!.toString(16).padStart(2, \"0\");\n return hex;\n}\n\nfunction hexToInternalId(hex: string): InternalId {\n if (hex.length % 2 !== 0) throw new DocumentIdError(\"odd-length hex\");\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) {\n const byte = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n if (Number.isNaN(byte)) throw new DocumentIdError(\"invalid hex\");\n out[i] = byte;\n }\n return out;\n}\n\nexport function generateInternalId(): InternalId {\n const bytes = new Uint8Array(INTERNAL_ID_BYTES);\n crypto.getRandomValues(bytes);\n return bytes;\n}\n\nexport function newDocumentId(tableNumber: number): InternalDocumentId {\n return { tableNumber, internalId: generateInternalId() };\n}\n\nexport function encodeDocumentId(tableNumber: number, internalId: InternalId): DocumentId {\n if (internalId.length !== INTERNAL_ID_BYTES) {\n throw new DocumentIdError(`internalId must be ${INTERNAL_ID_BYTES} bytes, got ${internalId.length}`);\n }\n const prefix = concatBytes(varintEncode(tableNumber), internalId);\n const checksum = fletcher16(prefix);\n const checksumBytes = new Uint8Array([(checksum >>> 8) & 0xff, checksum & 0xff]);\n return base32Encode(concatBytes(prefix, checksumBytes)) as DocumentId;\n}\n\nexport function encodeInternalDocumentId(id: InternalDocumentId): DocumentId {\n return encodeDocumentId(id.tableNumber, id.internalId);\n}\n\n/** Mint a full encoded document id CLIENT-SIDE (same shape and entropy as the engine's own\n * minting — 16 random bytes). The engine validates at insert; see client-supplied ids spec. */\nexport function mintEncodedDocumentId(tableNumber: number): DocumentId {\n return encodeInternalDocumentId(newDocumentId(tableNumber));\n}\n\nexport function decodeDocumentId(encoded: string): InternalDocumentId {\n let bytes: Uint8Array;\n try {\n bytes = base32Decode(encoded);\n } catch (e) {\n throw new DocumentIdError(e instanceof Base32Error ? e.message : \"invalid base32\");\n }\n let tableNumber: number;\n let bytesRead: number;\n try {\n ({ value: tableNumber, bytesRead } = varintDecode(bytes, 0));\n } catch {\n throw new DocumentIdError(\"invalid table number\");\n }\n const internalIdEnd = bytesRead + INTERNAL_ID_BYTES;\n if (bytes.length !== internalIdEnd + CHECKSUM_BYTES) {\n throw new DocumentIdError(\"incorrect id length\");\n }\n const internalId = bytes.slice(bytesRead, internalIdEnd);\n const checksum = (bytes[internalIdEnd]! << 8) | bytes[internalIdEnd + 1]!;\n const prefix = bytes.slice(0, internalIdEnd);\n if (!verifyFletcher16(prefix, checksum)) throw new DocumentIdError(\"checksum mismatch\");\n return { tableNumber, internalId };\n}\n\nexport function tryDecodeDocumentId(encoded: string): InternalDocumentId | null {\n try {\n return decodeDocumentId(encoded);\n } catch {\n return null;\n }\n}\n\nexport function isValidDocumentId(encoded: string, expectedTableNumber?: number): boolean {\n const decoded = tryDecodeDocumentId(encoded);\n if (decoded === null) return false;\n return expectedTableNumber === undefined || decoded.tableNumber === expectedTableNumber;\n}\n\n/** The encoded length in characters for ids of a given table number (31–37). */\nexport function getEncodedLength(tableNumber: number): number {\n const byteLen = varintEncodedLength(tableNumber) + INTERNAL_ID_BYTES + CHECKSUM_BYTES;\n return Math.ceil((byteLen * 8) / 5);\n}\n\n/* --- stable Map/Set keys for InternalDocumentId --- */\n\nexport function documentIdKey(id: InternalDocumentId): string {\n return `${id.tableNumber}:${internalIdToHex(id.internalId)}`;\n}\n\nexport function parseDocumentIdKey(key: string): InternalDocumentId {\n const sep = key.indexOf(\":\");\n if (sep < 0) throw new DocumentIdError(\"invalid document id key\");\n const tableNumber = Number.parseInt(key.slice(0, sep), 10);\n if (!Number.isInteger(tableNumber)) throw new DocumentIdError(\"invalid table number in key\");\n return { tableNumber, internalId: hexToInternalId(key.slice(sep + 1)) };\n}\n\nexport function documentIdsEqual(a: InternalDocumentId, b: InternalDocumentId): boolean {\n if (a.tableNumber !== b.tableNumber || a.internalId.length !== b.internalId.length) return false;\n for (let i = 0; i < a.internalId.length; i++) if (a.internalId[i] !== b.internalId[i]) return false;\n return true;\n}\n","/**\n * Stable string identifiers the storage layer uses to name a table's document space and\n * its indexes. Derived from the table *number* (not name) so renames never touch storage.\n */\nexport function encodeStorageTableId(tableNumber: number): string {\n return String(tableNumber);\n}\n\nexport function decodeStorageTableId(tableId: string): number {\n const n = Number.parseInt(tableId, 10);\n if (!Number.isInteger(n) || String(n) !== tableId) throw new Error(`invalid storage table id: ${tableId}`);\n return n;\n}\n\nexport function encodeStorageIndexId(tableNumber: number, indexName: string): string {\n return `${tableNumber}/${indexName}`;\n}\n\nexport function decodeStorageIndexId(indexId: string): { tableNumber: number; indexName: string } {\n const sep = indexId.indexOf(\"/\");\n if (sep < 0) throw new Error(`invalid storage index id: ${indexId}`);\n return {\n tableNumber: decodeStorageTableId(indexId.slice(0, sep)),\n indexName: indexId.slice(sep + 1),\n };\n}\n","/**\n * Shared point-range conversion helpers — the canonical home for `keyToPointRange`/\n * `docKeyToPointRange`, byte-for-byte identical to the versions that originally shipped in\n * `ee/packages/fleet/src/ranges.ts` (Tier 3 Slice 1/2). Extracted here (Tier 3 Slice 8, Task 8.1) so\n * `ee/packages/objectstore-substrate`'s replica reactive-tailer wiring can reuse them without taking\n * a dependency on `@helipod/fleet` (an EE package objectstore-substrate must not import — the\n * object-store substrate and the Postgres fleet are siblings, not a hierarchy).\n *\n * Homed in `@helipod/id-codec`, NOT `@helipod/index-key-codec`, despite `SerializedKeyRange`\n * itself living in the latter: these functions bridge a STORAGE id (`decodeStorageIndexId`,\n * `encodeStorageTableId` — this package's own `storage-id.ts`) to the engine's KEYSPACE id\n * (`indexKeyspaceId`/`tableKeyspaceId` — `@helipod/index-key-codec`'s `keyspace.ts`), so they\n * inherently need both id spaces. `@helipod/id-codec` already depends on\n * `@helipod/index-key-codec` (see `jump-hash.ts`'s `encodeIndexKey` import), so this direction adds\n * no new edge; the reverse — moving these into `index-key-codec` and having IT depend on\n * `id-codec`'s storage-id helpers — would create an import cycle (`id-codec -> index-key-codec ->\n * id-codec`). `id-codec` is therefore the only cycle-free canonical home for this bridge.\n *\n * `ee/packages/fleet/src/ranges.ts` re-exports both functions from here verbatim — fleet's public\n * API and existing tests are unaffected by the move.\n */\nimport {\n keySuccessor,\n serializeKeyRange,\n indexKeyspaceId,\n tableKeyspaceId,\n type SerializedKeyRange,\n} from \"@helipod/index-key-codec\";\nimport { decodeStorageIndexId, encodeStorageTableId } from \"./storage-id\";\n\n/**\n * Convert a single written `(indexId, key)` pair into the sync handler's point range\n * `[key, keySuccessor(key))` — the exact same half-open encoding `RangeSet.addKey` uses for a\n * point read/write, so a follower's derived write range overlaps a subscription's recorded read\n * range under `rangesOverlap`.\n *\n * `indexId` here is the storage `index_id` — the STORAGE index id produced by\n * `encodeStorageIndexId`, format `\"<tableNumber>/<indexName>\"` (e.g. `\"10001/by_creation\"`). That\n * is NOT the same string as the engine's keyspace id (`indexKeyspaceId`'s `\"index:<table>:<name>\"`\n * / `tableKeyspaceId`'s `\"table:<table>\"`), which is what `SerializedKeyRange.keyspace` — and\n * `rangesOverlap` — actually compare on. So the storage id must be decoded back into its parts and\n * the keyspace REBUILT with the engine's own helper; feeding the raw storage id straight through\n * silently produces ranges that can never overlap anything.\n */\nexport function keyToPointRange(indexId: string, key: Uint8Array): SerializedKeyRange {\n const { tableNumber, indexName } = decodeStorageIndexId(indexId);\n const keyspace = indexKeyspaceId(encodeStorageTableId(tableNumber), indexName);\n return serializeKeyRange({ keyspace, start: key, end: keySuccessor(key) });\n}\n\n/**\n * Convert a single written `(table_id, internal_id)` pair — e.g. from a `DerivedInvalidation`'s (or\n * `AppliedInvalidation`'s) `writtenDocs` — into the DOCUMENT-keyspace point range a bare\n * `ctx.db.get(id)` read records: `[internalId, keySuccessor(internalId))` under\n * `tableKeyspaceId(table)`. Unlike `keyToPointRange` above, no decode/recompose round trip is\n * needed here — `table_id` is already `encodeStorageTableId(tableNumber)`, exactly the string\n * `tableKeyspaceId` expects; it is NOT a storage *index* id, so there is no separate \"storage id\"\n * vs \"engine keyspace id\" split to bridge for the table half. This must match\n * `single-writer-transactor.ts`'s `docKeyspace()` (`tableKeyspaceId(encodeStorageTableId(...))`)\n * plus its `RangeSet.addKey(docKeyspace(id), id.internalId)` byte-for-byte, or a follower never\n * invalidates a subscription whose only read was a point `get`.\n */\nexport function docKeyToPointRange(tableId: string, internalId: Uint8Array): SerializedKeyRange {\n const keyspace = tableKeyspaceId(tableId);\n return serializeKeyRange({ keyspace, start: internalId, end: keySuccessor(internalId) });\n}\n","/**\n * Sharding primitives — scale-seam #1. A conversation (or any partition) maps to a shard\n * key derived from a document field; a shard router maps that key to the single-writer\n * shard that owns it. At **Tier 0 there is exactly one shard (`\"default\"`)** and the\n * router always returns it, but the seam exists in the interfaces from day one so Tier 2\n * per-conversation sharding is a config swap, not an engine rewrite.\n */\nexport type ShardId = string;\nexport type ShardKey = string;\n\nexport const DEFAULT_SHARD: ShardId = \"default\";\n\nexport interface ShardKeyResolverInput {\n table: string;\n document: Record<string, unknown>;\n}\n\n/** Extracts the shard key from a document, or null if the table isn't sharded. */\nexport interface ShardKeyResolver {\n resolve(input: ShardKeyResolverInput): ShardKey | null;\n}\n\n/** Tier 0: nothing is sharded — every document lives in the default shard. */\nexport class DefaultShardKeyResolver implements ShardKeyResolver {\n resolve(): ShardKey | null {\n return null;\n }\n}\n\n/** Resolves the shard key from a configured field per table (e.g. `messages.conversationId`). */\nexport class FieldShardKeyResolver implements ShardKeyResolver {\n constructor(private readonly fieldByTable: ReadonlyMap<string, string>) {}\n\n resolve({ table, document }: ShardKeyResolverInput): ShardKey | null {\n const field = this.fieldByTable.get(table);\n if (field === undefined) return null;\n const value = document[field];\n return value === undefined || value === null ? null : String(value);\n }\n}\n\n/**\n * Routes shard keys and clients to shards/nodes. Tier 2 implements consistent hashing\n * (document→committer) and rendezvous hashing (client→sync node); `TStub` is the\n * per-shard handle (e.g. a Durable Object stub).\n */\nexport interface ShardRouter<TStub = unknown> {\n getShardForKey(shardKey: ShardKey | null): ShardId;\n getShardForDocument(table: string, shardKey: ShardKey | null): ShardId;\n getSyncNodeId(clientId: string): string;\n resolveStub?(shardId: ShardId): TStub;\n}\n\n/** Tier 0 router: one shard, one local sync node. */\nexport class SimpleShardRouter implements ShardRouter {\n getShardForKey(_shardKey: ShardKey | null): ShardId {\n return DEFAULT_SHARD;\n }\n getShardForDocument(_table: string, _shardKey: ShardKey | null): ShardId {\n return DEFAULT_SHARD;\n }\n getSyncNodeId(_clientId: string): string {\n return \"local\";\n }\n}\n","/**\n * Jump consistent hash (Lamping & Veach, 2014) + the concrete `ShardRouter` built on it.\n *\n * The routing seam (`./shard.ts`) declares WHAT a router does; this file is HOW B2a does it:\n * a shard-key value is canonicalized to bytes the way the engine already canonicalizes index\n * keys (`encodeIndexKey` from `@helipod/index-key-codec` — never a hand-rolled encoding, so\n * a `1` routes the same whether it arrives as an arg or is read back off a document), hashed to\n * a 64-bit key, and mapped to a bucket in `[0, numShards)` by jump consistent hash. Slot 0 is\n * the reserved `\"default\"` shard (unsharded tables + no-`shardBy` mutations); slot k>0 is `\"s\"+k`.\n *\n * Jump consistent hash is chosen for its movement-minimality: growing the bucket count from n to\n * n' only ever moves a key to one of the NEW buckets `[n, n')`, never reshuffles a key among the\n * old buckets — the property B5's offline resharding tool leans on.\n */\nimport { encodeIndexKey, type IndexableValue } from \"@helipod/index-key-codec\";\nimport { DEFAULT_SHARD, type ShardId, type ShardKey } from \"./shard\";\nimport type { ShardRouter } from \"./shard\";\n\nconst U64_MASK = 0xffffffffffffffffn;\n/** 64-bit LCG multiplier from the reference jump-hash implementation. */\nconst LCG_MULT = 2862933555777941757n;\n\n/**\n * Map a 64-bit `key` to a bucket in `[0, buckets)`. The reference ~10-line algorithm: walk a\n * pseudo-random sequence of \"jump\" points, keeping the last one that lands below `buckets`.\n * Deterministic and allocation-free; `buckets` must be ≥ 1.\n */\nexport function jumpConsistentHash(key: bigint, buckets: number): number {\n if (buckets < 1) throw new RangeError(`buckets must be >= 1, got ${buckets}`);\n let k = BigInt.asUintN(64, key);\n let b = -1n;\n let j = 0n;\n const n = BigInt(buckets);\n while (j < n) {\n b = j;\n k = (k * LCG_MULT + 1n) & U64_MASK;\n // j = floor((b + 1) * (2^31 / ((k >> 33) + 1)))\n const denom = Number(k >> 33n) + 1;\n j = BigInt(Math.floor((Number(b) + 1) * (0x80000000 / denom)));\n }\n return Number(b);\n}\n\n/** FNV-1a 64-bit hash of a byte string → a well-mixed 64-bit key for `jumpConsistentHash`. */\nfunction fnv1a64(bytes: Uint8Array): bigint {\n let h = 0xcbf29ce484222325n;\n for (let i = 0; i < bytes.length; i++) {\n h ^= BigInt(bytes[i]!);\n h = (h * 0x100000001b3n) & U64_MASK;\n }\n return h;\n}\n\n/**\n * Resolve a shard-key VALUE (a scalar: string/number/bigint/boolean/null/bytes) to its shard id\n * under `numShards`. Canonicalizes via the engine's own index-key encoding so routing is stable\n * across the arg → route and document → route paths. Slot 0 → `\"default\"`, slot k → `\"s\"+k`.\n */\nexport function shardIdForKeyValue(value: unknown, numShards: number): ShardId {\n const bytes = encodeIndexKey([value as IndexableValue]);\n const slot = jumpConsistentHash(fnv1a64(bytes), Math.max(1, numShards));\n return slot === 0 ? DEFAULT_SHARD : `s${slot}`;\n}\n\n/**\n * The canonical ordered shard-id list for `numShards` shards: `[\"default\", \"s1\", …, \"s{N-1}\"]`.\n * The array INDEX is the slot number — slot 0 is `\"default\"`, slot k is `\"s\"+k` — matching\n * `shardIdForKeyValue`'s slot→id mapping exactly. This is the ONE source of truth for the\n * slot↔shardId contract the fleet's per-shard commit pool (`NodePgClient`'s `commitPool.shards`),\n * per-slot advisory locks (`tryAcquireShardLock(slot)`), and lease acquire-all loop all share, so\n * nobody hand-rolls (and risks disagreeing on) the numbering. `numShards` must be ≥ 1.\n */\nexport function shardIdList(numShards: number): ShardId[] {\n if (numShards < 1) throw new RangeError(`numShards must be >= 1, got ${numShards}`);\n const ids: ShardId[] = [DEFAULT_SHARD];\n for (let slot = 1; slot < numShards; slot++) ids.push(`s${slot}`);\n return ids;\n}\n\n/**\n * The B2a `ShardRouter`: jump-consistent-hash routing over `numShards` shards. Single-node, so\n * every client resolves to one local sync node. `getShardForKey`/`getShardForDocument` take the\n * already-extracted shard-key value (a string, per the seam); `shardIdForKeyValue` is the\n * value-typed entry point the executor and kernel call directly with the raw arg/field value.\n */\nexport class JumpShardRouter implements ShardRouter {\n constructor(private readonly numShards: number) {\n if (numShards < 1) throw new RangeError(`numShards must be >= 1, got ${numShards}`);\n }\n\n getShardForKey(shardKey: ShardKey | null): ShardId {\n return shardKey === null ? DEFAULT_SHARD : shardIdForKeyValue(shardKey, this.numShards);\n }\n\n getShardForDocument(_table: string, shardKey: ShardKey | null): ShardId {\n return this.getShardForKey(shardKey);\n }\n\n getSyncNodeId(_clientId: string): string {\n return \"local\";\n }\n}\n","/**\n * The table registry assigns each table a stable numeric id (embedded in document ids).\n * System tables (`_`-prefixed) get numbers 1–9999; user tables start at 10001, leaving a\n * reserved gap. The DocStore-backed durable registry arrives in M2; this in-memory one is\n * the canonical interface + the Tier 0 / test implementation.\n */\nexport type TableVisibility = \"user\" | \"system\";\nexport type TableState = \"active\" | \"creating\" | \"deleting\";\n\nexport interface TableInfo {\n name: string;\n tableNumber: number;\n visibility: TableVisibility;\n state: TableState;\n /** The document field used as the shard key (seam #1), or null. */\n shardKey: string | null;\n}\n\nexport interface AllocateOptions {\n visibility?: TableVisibility;\n shardKey?: string | null;\n}\n\nexport interface TableRegistry {\n getByName(name: string): TableInfo | undefined;\n getByNumber(tableNumber: number): TableInfo | undefined;\n allocate(name: string, options?: AllocateOptions): TableInfo;\n list(): TableInfo[];\n}\n\nexport interface PreassignOptions {\n visibility?: TableVisibility;\n shardKey?: string | null;\n state?: TableState;\n}\n\nexport const SYSTEM_TABLE_NUMBER_MIN = 1;\nexport const SYSTEM_TABLE_NUMBER_MAX = 9999;\nexport const USER_TABLE_NUMBER_START = 10001;\n\n/**\n * Reserved table numbers for built-in app-namespace system tables. These MUST stay stable\n * forever: a persisted `Id<\"_storage\">` encodes this number, so it must always decode back to\n * the same table. Registry seeding pins them with `preassign(name, number)` before any other\n * system table is auto-allocated, keeping their numbers deterministic across runs regardless of\n * registration order. `_storage` (the file-storage feature's document table) is 20.\n */\nexport const STORAGE_TABLE_NUMBER = 20;\n\nexport function isSystemTableName(name: string): boolean {\n return name.startsWith(\"_\");\n}\n\nexport function getFullTableName(name: string, componentPath: string): string {\n return componentPath ? `${componentPath}/${name}` : name;\n}\n\nexport function parseFullTableName(fullName: string): { componentPath: string; name: string } {\n const i = fullName.lastIndexOf(\"/\");\n return i < 0 ? { componentPath: \"\", name: fullName } : { componentPath: fullName.slice(0, i), name: fullName.slice(i + 1) };\n}\n\nexport class MemoryTableRegistry implements TableRegistry {\n private readonly byName = new Map<string, TableInfo>();\n private readonly byNumber = new Map<number, TableInfo>();\n private nextUser = USER_TABLE_NUMBER_START;\n private nextSystem = SYSTEM_TABLE_NUMBER_MIN;\n\n getByName(name: string): TableInfo | undefined {\n return this.byName.get(name);\n }\n\n getByNumber(tableNumber: number): TableInfo | undefined {\n return this.byNumber.get(tableNumber);\n }\n\n /** Idempotent: allocating an existing name returns the existing entry. */\n allocate(name: string, options: AllocateOptions = {}): TableInfo {\n const existing = this.byName.get(name);\n if (existing) return existing;\n\n const visibility = options.visibility ?? (isSystemTableName(name) ? \"system\" : \"user\");\n let tableNumber: number;\n if (visibility === \"system\") {\n if (this.nextSystem > SYSTEM_TABLE_NUMBER_MAX) throw new Error(\"exhausted system table numbers\");\n tableNumber = this.nextSystem++;\n } else {\n tableNumber = this.nextUser++;\n }\n\n const info: TableInfo = {\n name,\n tableNumber,\n visibility,\n state: \"active\",\n shardKey: options.shardKey ?? null,\n };\n this.byName.set(name, info);\n this.byNumber.set(tableNumber, info);\n return info;\n }\n\n list(): TableInfo[] {\n return [...this.byName.values()];\n }\n\n /**\n * Pre-register a table at a KNOWN (already-live) number, so a later `allocate(name)` for the\n * same name returns this number instead of minting a fresh one — used to seed a fresh registry\n * with a running deploy's table numbers before composing a new schema, so existing tables\n * (app AND component) never renumber; only genuinely-new tables get numbers above the seeded\n * max. Idempotent/no-op if `name` is already known (first registration wins, matching\n * `allocate`'s idempotency). Bumps the relevant `next*` counter so future `allocate` calls\n * never collide with a preassigned number.\n */\n preassign(name: string, tableNumber: number, options: PreassignOptions = {}): TableInfo {\n const existing = this.byName.get(name);\n if (existing) return existing;\n\n const visibility = options.visibility ?? (isSystemTableName(name) ? \"system\" : \"user\");\n const info: TableInfo = {\n name,\n tableNumber,\n visibility,\n state: options.state ?? \"active\",\n shardKey: options.shardKey ?? null,\n };\n this.byName.set(name, info);\n this.byNumber.set(tableNumber, info);\n if (visibility === \"system\") {\n this.nextSystem = Math.max(this.nextSystem, tableNumber + 1);\n } else {\n this.nextUser = Math.max(this.nextUser, tableNumber + 1);\n }\n return info;\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@helipod/id-codec",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@helipod/index-key-codec": "0.1.0"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"clean": "rm -rf dist .turbo"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"tsup": "^8.3.5",
|
|
30
|
+
"typescript": "^5.7.2",
|
|
31
|
+
"vitest": "^2.1.8"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/helipod-sh/helipod.git",
|
|
39
|
+
"directory": "packages/id-codec"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/helipod-sh/helipod"
|
|
42
|
+
}
|