@mblaney/holster 1.0.35 → 2.0.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.
Files changed (72) hide show
  1. package/build/holster.js +3 -3
  2. package/build/holster.js.map +3 -3
  3. package/dist/holster.js +4 -0
  4. package/dist/holster.js.map +7 -0
  5. package/package.json +22 -5
  6. package/src/dup.js +4 -5
  7. package/src/get.js +10 -19
  8. package/src/ham.js +22 -49
  9. package/src/holster.js +172 -58
  10. package/src/index.js +1 -1
  11. package/src/radisk.js +89 -86
  12. package/src/sea-utils.js +1 -1
  13. package/src/sea.js +0 -52
  14. package/src/store.js +8 -8
  15. package/src/user.js +0 -4
  16. package/src/wire.js +209 -79
  17. package/ts/src/array.ts +78 -0
  18. package/ts/src/buffer.ts +441 -0
  19. package/ts/src/dup.ts +38 -0
  20. package/ts/src/get.ts +83 -0
  21. package/ts/src/ham.ts +258 -0
  22. package/ts/src/holster.ts +968 -0
  23. package/ts/src/index.ts +35 -0
  24. package/ts/src/radisk.ts +591 -0
  25. package/ts/src/radix.ts +157 -0
  26. package/ts/src/schemas.ts +515 -0
  27. package/ts/src/sea-utils.ts +103 -0
  28. package/ts/src/sea.ts +388 -0
  29. package/ts/src/store.ts +321 -0
  30. package/ts/src/user.ts +377 -0
  31. package/ts/src/utils.ts +232 -0
  32. package/ts/src/wire.ts +961 -0
  33. package/.dockerignore +0 -9
  34. package/.github/workflows/node.js.yml +0 -31
  35. package/.github/workflows/npm-publish.yml +0 -33
  36. package/Dockerfile +0 -20
  37. package/examples/index.html +0 -33
  38. package/examples/index.js +0 -49
  39. package/examples/user.html +0 -31
  40. package/examples/user.js +0 -93
  41. package/examples/wire.html +0 -27
  42. package/examples/wire.js +0 -55
  43. package/test/holster.get.test.js +0 -94
  44. package/test/holster.lex.test.js +0 -196
  45. package/test/holster.off.test.js +0 -53
  46. package/test/holster.on.test.js +0 -264
  47. package/test/holster.put.test.js +0 -358
  48. package/test/holster.secure.test.js +0 -202
  49. package/test/holster.user.get.test.js +0 -114
  50. package/test/holster.user.lex.test.js +0 -149
  51. package/test/holster.user.off.test.js +0 -73
  52. package/test/holster.user.on.test.js +0 -300
  53. package/test/holster.user.put.test.js +0 -463
  54. package/test/kitty/! +0 -9
  55. package/test/names.js +0 -225
  56. package/test/radisk.cache.test.js +0 -213
  57. package/test/radisk.test.js +0 -331
  58. package/test/radix.test.js +0 -117
  59. package/test/sea.test.js +0 -284
  60. package/test/split.test.js +0 -317
  61. package/test/store.test.js +0 -316
  62. package/test/system/basic.test.js +0 -29
  63. package/test/system/chained-on.test.js +0 -41
  64. package/test/system/concurrent-listeners.test.js +0 -125
  65. package/test/system/immediate-read.test.js +0 -30
  66. package/test/system/listener-before-data.test.js +0 -51
  67. package/test/system/promise-wrapper.test.js +0 -47
  68. package/test/system/user-chained-on.test.js +0 -60
  69. package/test/system/user-concurrent-listeners.test.js +0 -142
  70. package/test/system/user-listener-before-data.test.js +0 -66
  71. package/test/user.test.js +0 -173
  72. package/test/wire.test.js +0 -158
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Radix - Radix tree implementation for efficient key-value storage
3
+ * Supports prefix-based key compression
4
+ */
5
+
6
+ import * as utils from "./utils.ts"
7
+ import type { EncodedValue, RadixFunction } from "./schemas.ts"
8
+
9
+ // ASCII character for group separator
10
+ const group = String.fromCharCode(29)
11
+ // ASCII character for record separator
12
+ const record = String.fromCharCode(30)
13
+
14
+ // Local type for internal radix tree structure (different from schemas.RadixValue)
15
+ // This represents the internal node structure with dynamic keys
16
+ interface RadixValue {
17
+ [key: string]: RadixTree | EncodedValue | undefined
18
+ }
19
+
20
+ // RadixTree is the internal tree structure (also known as RadixNode in schemas)
21
+ type RadixTree = Record<string, RadixValue>
22
+ // Alias for compatibility with schema types
23
+ type RadixNode = RadixTree
24
+
25
+ // Type guard to check if a value is a RadixTree (object)
26
+ const isRadixTree = (value: unknown): value is RadixTree => {
27
+ return typeof value === "object" && value !== null && !Array.isArray(value)
28
+ }
29
+
30
+ /**
31
+ * Create a new Radix tree
32
+ */
33
+ const Radix = (): RadixFunction => {
34
+ const radix = ((
35
+ keys?: string,
36
+ value?: EncodedValue,
37
+ tree?: RadixTree
38
+ ): RadixNode | EncodedValue | undefined | Record<string, EncodedValue> => {
39
+ if (!tree) {
40
+ if (!(radix as Record<string, unknown>)[group]) (radix as Record<string, unknown>)[group] = {}
41
+ tree = (radix as Record<string, RadixTree>)[group]!
42
+ }
43
+ if (!keys) return tree
44
+
45
+ let i = 0
46
+ let tmp: Record<string, RadixValue> = {}
47
+ let key = keys[i]!
48
+ const max = keys.length - 1
49
+ const noValue = typeof value === "undefined"
50
+
51
+ // Find a matching value using the shortest string from keys
52
+ let found: RadixValue | undefined = tree![key]
53
+ while (!found && i < max) {
54
+ key += keys[++i]
55
+ found = tree![key]
56
+ }
57
+
58
+ if (!found) {
59
+ // If not found from the provided keys try matching with an existing key
60
+ const result = utils.obj.map(tree!, (hasValue: RadixValue, hasKey: string) => {
61
+ let j = 0
62
+ let matchingKey = ""
63
+ while (hasKey[j] === keys[j]) {
64
+ matchingKey += hasKey[j]
65
+ j++
66
+ }
67
+ if (matchingKey) {
68
+ if (noValue) {
69
+ // matchingKey has to be as long as the original keys when reading
70
+ if (j <= max) return undefined
71
+
72
+ tmp[hasKey.slice(j)] = hasValue
73
+ return hasValue
74
+ }
75
+
76
+ const replace: RadixTree = {
77
+ [hasKey.slice(j)]: hasValue,
78
+ [keys.slice(j)]: { [record]: value! },
79
+ }
80
+ tree![matchingKey] = { [group]: replace }
81
+ delete tree![hasKey]
82
+ return true
83
+ }
84
+ return undefined
85
+ })
86
+
87
+ if (!result) {
88
+ if (noValue) return undefined
89
+
90
+ if (!tree![key]) tree![key] = {}
91
+ tree![key]![record] = value!
92
+ } else if (noValue) {
93
+ return tmp
94
+ }
95
+ } else if (i === max) {
96
+ // If no value use the key provided to return a whole group or record
97
+ if (noValue) {
98
+ // If an individual record isn't found then return the whole group
99
+ return typeof found[record] === "undefined" ? found[group] : found[record]
100
+ }
101
+ // Otherwise create a new record at the provided key for value
102
+ found[record] = value!
103
+ } else {
104
+ // Found at a shorter key, try again
105
+ if (!found[group] && !noValue) found[group] = {}
106
+ const nextTree = found[group]
107
+ if (isRadixTree(nextTree)) {
108
+ return radix(keys.slice(++i), value, nextTree as RadixTree) as RadixNode
109
+ }
110
+ }
111
+
112
+ return undefined
113
+ }) as RadixFunction
114
+
115
+ return radix
116
+ }
117
+
118
+ /**
119
+ * Map over all values in a radix tree
120
+ */
121
+ Radix.map = function map(
122
+ radix: RadixFunction | RadixNode,
123
+ cb: (value: EncodedValue, fullKey: string, key: string, pre: string[]) => unknown,
124
+ opt?: boolean,
125
+ pre: string[] = []
126
+ ): unknown {
127
+ const tree = ((radix as Record<string, RadixTree>)[group] || radix) as RadixTree
128
+ const keys = Object.keys(tree).sort()
129
+
130
+ for (let i = 0; i < keys.length; i++) {
131
+ const key = keys[i]!
132
+ const found = tree[key]
133
+ if (!found) continue
134
+
135
+ const recordValue = found[record]
136
+
137
+ if (typeof recordValue !== "undefined") {
138
+ const result = cb(recordValue as EncodedValue, pre.join("") + key, key, pre)
139
+ if (typeof result !== "undefined") return result
140
+ } else if (opt) {
141
+ cb(undefined as never, pre.join(""), key, pre)
142
+ }
143
+
144
+ const groupValue = found[group]
145
+ if (isRadixTree(groupValue)) {
146
+ pre.push(key)
147
+ const result = map(groupValue, cb, opt, pre)
148
+ if (typeof result !== "undefined") return result
149
+ pre.pop()
150
+ }
151
+ }
152
+
153
+ return undefined
154
+ }
155
+
156
+ export default Radix
157
+
@@ -0,0 +1,515 @@
1
+ import { z } from "zod"
2
+
3
+ // ============================================================================
4
+ // Core Primitive Types
5
+ // ============================================================================
6
+
7
+ /**
8
+ * Soul identifier - unique identifier for graph nodes
9
+ * Examples: "root", "~pubkey", "~@username", random UUIDs
10
+ */
11
+ export const SoulSchema = z.string().min(1)
12
+ export type Soul = z.infer<typeof SoulSchema>
13
+
14
+ /**
15
+ * Timestamp for state vector (HAM conflict resolution)
16
+ */
17
+ export const TimestampSchema = z.number().int().nonnegative()
18
+ export type Timestamp = z.infer<typeof TimestampSchema>
19
+
20
+ /**
21
+ * Base64 encoded string (for signatures, encrypted data, etc)
22
+ */
23
+ export const Base64Schema = z.string()
24
+ export type Base64String = z.infer<typeof Base64Schema>
25
+
26
+ // ============================================================================
27
+ // Graph Data Structures
28
+ // ============================================================================
29
+
30
+ /**
31
+ * Relation - reference to another node in the graph
32
+ * Format: {"#": "soul-id"}
33
+ */
34
+ export const RelationSchema = z.object({
35
+ "#": SoulSchema,
36
+ })
37
+ export type Relation = z.infer<typeof RelationSchema>
38
+
39
+ /**
40
+ * Valid graph values - primitives, null, relations, or nested objects
41
+ * Note: GraphValue is recursive to allow nested objects (verified in src/holster.js check() function)
42
+ */
43
+ export const GraphValueSchema: z.ZodType<GraphValue> = z.lazy(() =>
44
+ z.union([
45
+ z.string(),
46
+ z.number(),
47
+ z.boolean(),
48
+ z.null(),
49
+ RelationSchema,
50
+ z.record(z.string(), GraphValueSchema),
51
+ ])
52
+ )
53
+
54
+ export type GraphValue =
55
+ | string
56
+ | number
57
+ | boolean
58
+ | null
59
+ | Relation
60
+ | { [key: string]: GraphValue }
61
+
62
+ /**
63
+ * State vector - tracks timestamps for each property
64
+ */
65
+ export const StateVectorSchema = z.record(z.string(), TimestampSchema)
66
+ export type StateVector = z.infer<typeof StateVectorSchema>
67
+
68
+ /**
69
+ * Signature map - maps timestamps or property names to signatures
70
+ */
71
+ export const SignatureMapSchema = z.record(z.string(), Base64Schema)
72
+ export type SignatureMap = z.infer<typeof SignatureMapSchema>
73
+
74
+ /**
75
+ * Graph metadata attached to each node
76
+ */
77
+ export const GraphMetadataSchema = z.object({
78
+ "#": SoulSchema,
79
+ ">": StateVectorSchema,
80
+ s: SignatureMapSchema.optional(),
81
+ })
82
+ export type GraphMetadata = z.infer<typeof GraphMetadataSchema>
83
+
84
+ /**
85
+ * User metadata keys
86
+ */
87
+ export const USER_PUBLIC_KEY = "_holster_user_public_key"
88
+ export const USER_SIGNATURE = "_holster_user_signature"
89
+
90
+ /**
91
+ * Graph node - contains metadata and properties
92
+ * Note: Using manual type to properly handle the index signature with special _ key
93
+ */
94
+ export const GraphNodeSchema = z.object({
95
+ _: GraphMetadataSchema,
96
+ }).and(z.record(z.string(), GraphValueSchema))
97
+
98
+ export type GraphNode = {
99
+ _: GraphMetadata
100
+ [key: string]: GraphValue | GraphMetadata
101
+ }
102
+
103
+ /**
104
+ * Graph - collection of nodes indexed by soul
105
+ */
106
+ export const GraphSchema = z.record(SoulSchema, GraphNodeSchema)
107
+ export type Graph = z.infer<typeof GraphSchema>
108
+
109
+ // ============================================================================
110
+ // Query/Lex Structures
111
+ // ============================================================================
112
+
113
+ /**
114
+ * Prefix filter for lex queries
115
+ */
116
+ export const PrefixFilterSchema = z.object({
117
+ "*": z.string(),
118
+ })
119
+ export type PrefixFilter = z.infer<typeof PrefixFilterSchema>
120
+
121
+ /**
122
+ * Range filter for lex queries
123
+ */
124
+ export const RangeFilterSchema = z.object({
125
+ ">": z.string().optional(),
126
+ "<": z.string().optional(),
127
+ })
128
+ export type RangeFilter = z.infer<typeof RangeFilterSchema>
129
+
130
+ /**
131
+ * Lex filter - supports various query patterns
132
+ */
133
+ export const LexFilterSchema = z.union([
134
+ z.string(),
135
+ z.array(z.string()),
136
+ PrefixFilterSchema,
137
+ RangeFilterSchema,
138
+ z.null(),
139
+ z.undefined(),
140
+ ])
141
+ export type LexFilter = z.infer<typeof LexFilterSchema>
142
+
143
+ // LexFilter with optional "." property for nested queries
144
+ export type LexWithDot =
145
+ | LexFilter
146
+ | { ".": LexFilter }
147
+
148
+ /**
149
+ * Lex query object
150
+ */
151
+ export const LexSchema = z.object({
152
+ "#": SoulSchema,
153
+ ".": LexFilterSchema.optional(),
154
+ })
155
+ export type Lex = z.infer<typeof LexSchema>
156
+
157
+ // ============================================================================
158
+ // Wire Protocol Messages
159
+ // ============================================================================
160
+
161
+ /**
162
+ * Wire message ID (for deduplication)
163
+ */
164
+ export const MessageIdSchema = z.string()
165
+ export type MessageId = z.infer<typeof MessageIdSchema>
166
+
167
+ /**
168
+ * Wire message - protocol for network communication
169
+ */
170
+ export const WireMessageSchema = z.object({
171
+ "#": MessageIdSchema.optional(),
172
+ "@": MessageIdSchema.optional(),
173
+ get: LexSchema.optional(),
174
+ put: GraphSchema.optional(),
175
+ err: z.string().optional(),
176
+ throttle: z.number().optional(),
177
+ })
178
+ export type WireMessage = z.infer<typeof WireMessageSchema>
179
+
180
+ /**
181
+ * Wire options for get/put operations
182
+ */
183
+ export const WireOptionsSchema = z
184
+ .object({
185
+ secure: z.boolean().optional(),
186
+ fast: z.boolean().optional(),
187
+ put: z.boolean().optional(),
188
+ })
189
+ .optional()
190
+ export type WireOptions = z.infer<typeof WireOptionsSchema>
191
+
192
+ // ============================================================================
193
+ // Crypto/Security Structures
194
+ // ============================================================================
195
+
196
+ /**
197
+ * ECDSA + ECDH key pair for user
198
+ */
199
+ export const UserPairSchema = z.object({
200
+ pub: z.string(), // Public key for signing (ECDSA)
201
+ priv: z.string(), // Private key for signing (ECDSA)
202
+ epub: z.string(), // Public key for encryption (ECDH)
203
+ epriv: z.string(), // Private key for encryption (ECDH)
204
+ })
205
+ export type UserPair = z.infer<typeof UserPairSchema>
206
+
207
+ /**
208
+ * User identity (authenticated user state)
209
+ */
210
+ export const UserIdentitySchema = z.object({
211
+ username: z.string(),
212
+ pub: z.string(),
213
+ epub: z.string(),
214
+ priv: z.string(),
215
+ epriv: z.string(),
216
+ })
217
+ export type UserIdentity = z.infer<typeof UserIdentitySchema>
218
+
219
+ /**
220
+ * Encrypted data structure
221
+ */
222
+ export const EncryptedDataSchema = z.object({
223
+ ct: Base64Schema, // Ciphertext
224
+ iv: Base64Schema, // Initialization vector
225
+ s: Base64Schema, // Salt
226
+ })
227
+ export type EncryptedData = z.infer<typeof EncryptedDataSchema>
228
+
229
+ /**
230
+ * Signed data structure
231
+ */
232
+ export const SignedDataSchema = z.object({
233
+ m: z.union([z.string(), z.record(z.string(), z.unknown())]), // Message
234
+ s: Base64Schema, // Signature
235
+ })
236
+ export type SignedData = z.infer<typeof SignedDataSchema>
237
+
238
+ /**
239
+ * User authentication data
240
+ */
241
+ export const UserAuthSchema = z.object({
242
+ enc: EncryptedDataSchema,
243
+ salt: z.string(),
244
+ })
245
+ export type UserAuth = z.infer<typeof UserAuthSchema>
246
+
247
+ /**
248
+ * JWK (JSON Web Key) format
249
+ */
250
+ export const JWKSchema = z.object({
251
+ kty: z.string(),
252
+ crv: z.string().optional(),
253
+ x: z.string().optional(),
254
+ y: z.string().optional(),
255
+ d: z.string().optional(),
256
+ k: z.string().optional(),
257
+ ext: z.boolean(),
258
+ key_ops: z.array(z.string()).optional(),
259
+ alg: z.string().optional(),
260
+ })
261
+ export type JWK = z.infer<typeof JWKSchema>
262
+
263
+ // ============================================================================
264
+ // Storage Structures
265
+ // ============================================================================
266
+
267
+ /**
268
+ * Encoded value for storage: [value, timestamp] or [value, timestamp, signature]
269
+ */
270
+ export const EncodedValueSchema = z.union([
271
+ z.tuple([GraphValueSchema, TimestampSchema]),
272
+ z.tuple([GraphValueSchema, TimestampSchema, Base64Schema]),
273
+ ])
274
+
275
+ // Manual type definition for better tuple handling
276
+ export type EncodedValue =
277
+ | [GraphValue, Timestamp]
278
+ | [GraphValue, Timestamp, Base64String]
279
+
280
+ /**
281
+ * Store interface callbacks
282
+ */
283
+ export const StoreCallbackSchema = z.function()
284
+
285
+ /**
286
+ * File system interface for storage
287
+ */
288
+ export const FileSystemInterfaceSchema = z.object({
289
+ get: z.function(),
290
+ put: z.function(),
291
+ list: z.function(),
292
+ })
293
+
294
+ // FileSystemInterface: Flexible to support both string-based and EncodedValue storage
295
+ // In-memory stores use strings, disk stores use EncodedValue/RadixNode
296
+ export interface FileSystemInterface {
297
+ get: (file: string, cb: (err?: string | null, data?: string | EncodedValue | Record<string, unknown>) => void) => void
298
+ put: (file: string, data: string | EncodedValue | Record<string, unknown>, cb: (err?: string | null) => void) => void
299
+ list: (cb: (file?: string) => void) => void
300
+ }
301
+
302
+ /**
303
+ * Store options
304
+ */
305
+ export const StoreOptionsSchema = z.object({
306
+ file: z.string().optional(),
307
+ store: FileSystemInterfaceSchema.optional(),
308
+ indexedDB: z.boolean().optional(),
309
+ secure: z.boolean().optional(),
310
+ })
311
+ export type StoreOptions = z.infer<typeof StoreOptionsSchema>
312
+
313
+ /**
314
+ * Radisk options
315
+ */
316
+ export const RadiskOptionsSchema = z.object({
317
+ log: z.function().optional(),
318
+ batch: z.number().optional(),
319
+ write: z.number().optional(),
320
+ size: z.number().optional(),
321
+ memoryLimit: z.number().optional(),
322
+ cache: z.boolean().optional(),
323
+ store: FileSystemInterfaceSchema.optional(),
324
+ })
325
+
326
+ export interface RadiskOptions {
327
+ log?: (...args: unknown[]) => void
328
+ batch?: number
329
+ write?: number
330
+ size?: number
331
+ memoryLimit?: number
332
+ cache?: boolean
333
+ store?: FileSystemInterface
334
+ }
335
+
336
+ // ============================================================================
337
+ // HAM (Conflict Resolution) Structures
338
+ // ============================================================================
339
+
340
+ /**
341
+ * HAM comparison result
342
+ */
343
+ export const HamResultSchema = z.object({
344
+ incoming: z.boolean().optional(),
345
+ current: z.boolean().optional(),
346
+ historical: z.boolean().optional(),
347
+ state: z.boolean().optional(),
348
+ })
349
+ export type HamResult = z.infer<typeof HamResultSchema>
350
+
351
+ /**
352
+ * HAM mix result - result of merging changes into graph
353
+ */
354
+ export const HamMixResultSchema = z.object({
355
+ now: GraphSchema,
356
+ defer: GraphSchema,
357
+ wait: z.number(),
358
+ listeners: z.array(z.function()),
359
+ })
360
+ export type HamMixResult = z.infer<typeof HamMixResultSchema>
361
+
362
+ /**
363
+ * Listen callback map for event listeners
364
+ */
365
+ export const ListenerSchema = z.object({
366
+ ".": LexFilterSchema.optional(),
367
+ cb: z.function(),
368
+ })
369
+ export type Listener = z.infer<typeof ListenerSchema>
370
+
371
+ export const ListenMapSchema = z.record(SoulSchema, z.array(ListenerSchema))
372
+ export type ListenMap = z.infer<typeof ListenMapSchema>
373
+
374
+ // ============================================================================
375
+ // Holster API Structures
376
+ // ============================================================================
377
+
378
+ /**
379
+ * Context chain for chained API calls
380
+ */
381
+ export const ChainItemSchema = z.object({
382
+ item: z.string().nullable(),
383
+ soul: z.string().nullable(),
384
+ })
385
+ export type ChainItem = z.infer<typeof ChainItemSchema>
386
+
387
+ export const ApiContextSchema = z.object({
388
+ chain: z.array(ChainItemSchema),
389
+ cb: z.function().optional(),
390
+ on: z.boolean().optional(),
391
+ user: UserIdentitySchema.nullable().optional(),
392
+ })
393
+ export type ApiContext = z.infer<typeof ApiContextSchema>
394
+
395
+ /**
396
+ * Holster options
397
+ */
398
+ export const HolsterOptionsSchema = z
399
+ .object({
400
+ peers: z.union([z.string(), z.array(z.string())]).optional(),
401
+ secure: z.boolean().optional(),
402
+ port: z.number().optional(),
403
+ server: z.any().optional(),
404
+ wss: z.any().optional(),
405
+ maxConnections: z.number().optional(),
406
+ maxMessageSize: z.number().optional(),
407
+ maxQueueLength: z.number().optional(),
408
+ })
409
+ .passthrough()
410
+ export type HolsterOptions = z.infer<typeof HolsterOptionsSchema>
411
+
412
+ // ============================================================================
413
+ // Utility Type Guards
414
+ // ============================================================================
415
+
416
+ /**
417
+ * Check if value is a relation
418
+ */
419
+ export function isRelation(value: unknown): value is Relation {
420
+ return RelationSchema.safeParse(value).success
421
+ }
422
+
423
+ /**
424
+ * Check if value is a valid graph value
425
+ */
426
+ export function isGraphValue(value: unknown): value is GraphValue {
427
+ return GraphValueSchema.safeParse(value).success
428
+ }
429
+
430
+ /**
431
+ * Check if object is a valid graph node
432
+ */
433
+ export function isGraphNode(value: unknown): value is GraphNode {
434
+ return GraphNodeSchema.safeParse(value).success
435
+ }
436
+
437
+ /**
438
+ * Check if object is a valid lex query
439
+ */
440
+ export function isLex(value: unknown): value is Lex {
441
+ return LexSchema.safeParse(value).success
442
+ }
443
+
444
+ // ============================================================================
445
+ // Radix Tree Structures
446
+ // ============================================================================
447
+
448
+ /**
449
+ * Radix tree node (recursive structure)
450
+ */
451
+ export type RadixNode = {
452
+ [key: string]: RadixValue | undefined
453
+ }
454
+
455
+ // RadixValue can be GraphValue (raw), EncodedValue (tuple), or RadixNode (nested tree)
456
+ // This matches src/radix.js behavior - radix stores ANY value type
457
+ export type RadixValue = GraphValue | EncodedValue | RadixNode
458
+
459
+ /**
460
+ * Radix function type - Single Source of Truth for Radix tree operations
461
+ * Supports both synchronous get/put and async callback forms
462
+ */
463
+ export type RadixFunction = {
464
+ // Main call signature: synchronous get/put operations
465
+ (keys?: string, value?: GraphValue | EncodedValue, tree?: RadixNode):
466
+ RadixNode | EncodedValue | undefined | Record<string, EncodedValue>
467
+
468
+ // Callback form: async operations
469
+ (key?: string, value?: RadixValue, cb?: (err?: string) => void): void
470
+
471
+ // Static map method for iterating over radix tree values
472
+ map: (
473
+ radix: RadixFunction | RadixNode,
474
+ cb: (value: EncodedValue, fullKey: string, key: string, pre: string[]) => unknown,
475
+ opt?: boolean,
476
+ pre?: string[]
477
+ ) => unknown
478
+
479
+ // Dynamic properties for internal state (e.g., group separator key)
480
+ [key: string]: unknown
481
+ }
482
+
483
+ // ============================================================================
484
+ // Dup (Duplicate Tracking) Structures
485
+ // ============================================================================
486
+
487
+ /**
488
+ * Dup store for tracking message IDs
489
+ */
490
+ export const DupStoreSchema = z.record(z.string(), z.number())
491
+ export type DupStore = z.infer<typeof DupStoreSchema>
492
+
493
+ export const DupSchema = z.object({
494
+ store: DupStoreSchema,
495
+ check: z.function(),
496
+ track: z.function(),
497
+ expiry: z.any().optional(),
498
+ })
499
+ export type Dup = z.infer<typeof DupSchema> & {
500
+ check: (id: string) => string | false
501
+ track: (id: string) => string
502
+ }
503
+
504
+ // ============================================================================
505
+ // Re-exports from modules
506
+ // ============================================================================
507
+
508
+ // Export RadiskInterface from radisk module for test usage
509
+ export type { RadiskInterface } from "./radisk.ts"
510
+
511
+ // Export other interfaces for test usage
512
+ export type { WireAPI as WireInterface } from "./wire.ts"
513
+ export type { UserInterface } from "./user.ts"
514
+ export type { StoreInterface } from "./store.ts"
515
+