@mblaney/holster 2.0.2 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -6
- package/build/holster.js +3 -3
- package/build/holster.js.map +3 -3
- package/dist/holster.js +3 -3
- package/dist/holster.js.map +3 -3
- package/package.json +2 -2
- package/src/ham.js +10 -3
- package/src/holster.js +36 -25
- package/src/radisk.js +26 -12
- package/src/user.js +92 -101
- package/src/wire.js +300 -104
- package/ts/src/array.ts +10 -13
- package/ts/src/buffer.ts +40 -20
- package/ts/src/get.ts +5 -6
- package/ts/src/ham.ts +47 -28
- package/ts/src/holster.ts +277 -132
- package/ts/src/index.ts +14 -15
- package/ts/src/radisk.ts +122 -37
- package/ts/src/radix.ts +50 -34
- package/ts/src/schemas.ts +44 -24
- package/ts/src/sea-utils.ts +7 -11
- package/ts/src/sea.ts +51 -54
- package/ts/src/store.ts +48 -42
- package/ts/src/user.ts +136 -113
- package/ts/src/utils.ts +36 -11
- package/ts/src/wire.ts +455 -232
package/ts/src/index.ts
CHANGED
|
@@ -1,35 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Holster TypeScript Entry Point
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This is the main entry point for the TypeScript version of Holster.
|
|
5
5
|
* It exports all the main modules and types.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// Main API
|
|
9
|
-
export {
|
|
9
|
+
export {default as Holster, type HolsterAPI} from "./holster.ts"
|
|
10
10
|
|
|
11
11
|
// Core modules
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
12
|
+
export {default as Wire, type WireAPI} from "./wire.ts"
|
|
13
|
+
export {default as User, type UserInterface} from "./user.ts"
|
|
14
|
+
export {default as SEA} from "./sea.ts"
|
|
15
|
+
export {default as Store, type StoreInterface} from "./store.ts"
|
|
16
16
|
|
|
17
17
|
// Data structure modules
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
22
|
-
export {
|
|
18
|
+
export {default as Ham} from "./ham.ts"
|
|
19
|
+
export {default as Get} from "./get.ts"
|
|
20
|
+
export {default as Radisk} from "./radisk.ts"
|
|
21
|
+
export {default as Radix} from "./radix.ts"
|
|
22
|
+
export {default as Dup, type DupInterface} from "./dup.ts"
|
|
23
23
|
|
|
24
24
|
// Utilities
|
|
25
25
|
export * as utils from "./utils.ts"
|
|
26
26
|
export * as seaUtils from "./sea-utils.ts"
|
|
27
|
-
export {
|
|
28
|
-
export {
|
|
27
|
+
export {default as SafeBuffer} from "./buffer.ts"
|
|
28
|
+
export {default as SeaArray} from "./array.ts"
|
|
29
29
|
|
|
30
30
|
// Types and Schemas
|
|
31
31
|
export * from "./schemas.ts"
|
|
32
32
|
|
|
33
33
|
// Default export
|
|
34
|
-
export {
|
|
35
|
-
|
|
34
|
+
export {default} from "./holster.ts"
|
package/ts/src/radisk.ts
CHANGED
|
@@ -4,9 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import Radix from "./radix.ts"
|
|
7
|
-
import type {
|
|
7
|
+
import type {RadixFunction} from "./schemas.ts"
|
|
8
8
|
import * as utils from "./utils.ts"
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
EncodedValue,
|
|
11
|
+
RadiskOptions,
|
|
12
|
+
GraphValue,
|
|
13
|
+
Relation,
|
|
14
|
+
} from "./schemas.ts"
|
|
10
15
|
|
|
11
16
|
// ASCII character for end of text
|
|
12
17
|
const etx = String.fromCharCode(3)
|
|
@@ -18,8 +23,18 @@ const unit = String.fromCharCode(31)
|
|
|
18
23
|
export interface RadiskInterface extends RadixFunction {
|
|
19
24
|
// Override call signature to support both get (with callback) and put (with value and optional callback)
|
|
20
25
|
// Accepts GraphValue (raw values) OR EncodedValue (tuples) - see src/radisk.js and tests
|
|
21
|
-
(
|
|
22
|
-
|
|
26
|
+
(
|
|
27
|
+
key?: string,
|
|
28
|
+
value?:
|
|
29
|
+
| GraphValue
|
|
30
|
+
| EncodedValue
|
|
31
|
+
| ((
|
|
32
|
+
err?: string | null,
|
|
33
|
+
value?: EncodedValue | Record<string, EncodedValue>,
|
|
34
|
+
) => void),
|
|
35
|
+
cb?: (err?: string | null) => void,
|
|
36
|
+
): void
|
|
37
|
+
|
|
23
38
|
batch: RadixFunction & {
|
|
24
39
|
acks: Array<(err?: string | null) => void>
|
|
25
40
|
ed: number
|
|
@@ -32,9 +47,22 @@ export interface RadiskInterface extends RadixFunction {
|
|
|
32
47
|
more?: boolean
|
|
33
48
|
}
|
|
34
49
|
save: (rad: RadixFunction, cb: (err?: string | null) => void) => void
|
|
35
|
-
write: (
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
write: (
|
|
51
|
+
file: string,
|
|
52
|
+
rad: RadixFunction,
|
|
53
|
+
cb: (err?: string | null) => void,
|
|
54
|
+
) => void
|
|
55
|
+
read: (
|
|
56
|
+
key: string,
|
|
57
|
+
cb: (
|
|
58
|
+
err?: string | null,
|
|
59
|
+
value?: EncodedValue | Record<string, EncodedValue>,
|
|
60
|
+
) => void,
|
|
61
|
+
) => void
|
|
62
|
+
parse: (
|
|
63
|
+
file: string,
|
|
64
|
+
cb: (err?: string | null, disk?: RadixFunction) => void,
|
|
65
|
+
) => void
|
|
38
66
|
}
|
|
39
67
|
|
|
40
68
|
/**
|
|
@@ -48,7 +76,12 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
48
76
|
|
|
49
77
|
const pendingReads = new Map<
|
|
50
78
|
string,
|
|
51
|
-
Array<
|
|
79
|
+
Array<
|
|
80
|
+
(
|
|
81
|
+
err?: string | null,
|
|
82
|
+
result?: EncodedValue | Record<string, EncodedValue>,
|
|
83
|
+
) => void
|
|
84
|
+
>
|
|
52
85
|
>()
|
|
53
86
|
|
|
54
87
|
let lastMemoryCheck = 0
|
|
@@ -67,22 +100,27 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
67
100
|
}
|
|
68
101
|
|
|
69
102
|
if (!options.store) {
|
|
70
|
-
throw new Error(
|
|
103
|
+
throw new Error(
|
|
104
|
+
"Radisk needs `store` interface with `{get: fn, put: fn, list: fn}`",
|
|
105
|
+
)
|
|
71
106
|
}
|
|
72
107
|
if (!options.store.get) {
|
|
73
108
|
throw new Error("Radisk needs `store.get` interface with `(file, cb)`")
|
|
74
109
|
}
|
|
75
110
|
if (!options.store.put) {
|
|
76
|
-
throw new Error(
|
|
111
|
+
throw new Error(
|
|
112
|
+
"Radisk needs `store.put` interface with `(file, data, cb)`",
|
|
113
|
+
)
|
|
77
114
|
}
|
|
78
115
|
if (!options.store.list) {
|
|
79
|
-
throw new Error(
|
|
116
|
+
throw new Error(
|
|
117
|
+
"Radisk needs a streaming `store.list` interface with `(cb)`",
|
|
118
|
+
)
|
|
80
119
|
}
|
|
81
120
|
|
|
82
121
|
// After validation, store is guaranteed to be defined
|
|
83
122
|
const store = options.store
|
|
84
123
|
|
|
85
|
-
|
|
86
124
|
const checkMemoryUsage = (): void => {
|
|
87
125
|
if (typeof process === "undefined" || !process.memoryUsage) return
|
|
88
126
|
|
|
@@ -98,21 +136,31 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
98
136
|
if (heapUsageRatio > HEAP_CLEANUP_THRESHOLD) {
|
|
99
137
|
const cacheSize = cache.size
|
|
100
138
|
console.log(
|
|
101
|
-
`[RADISK-MEMORY] High memory usage: ${Math.round(heapUsageRatio * 100)}% of heap limit. Clearing ${cacheSize} cached files
|
|
139
|
+
`[RADISK-MEMORY] High memory usage: ${Math.round(heapUsageRatio * 100)}% of heap limit. Clearing ${cacheSize} cached files.`,
|
|
102
140
|
)
|
|
103
141
|
cache.clear()
|
|
104
|
-
if ((global as {
|
|
105
|
-
(global as {
|
|
142
|
+
if ((global as {gc?: () => void}).gc) {
|
|
143
|
+
;(global as {gc: () => void}).gc()
|
|
106
144
|
}
|
|
107
145
|
lastMemoryCheck = now + MEMORY_CHECK_INTERVAL
|
|
108
146
|
} else if (heapUsageRatio > HEAP_WARNING_THRESHOLD) {
|
|
109
147
|
console.log(
|
|
110
|
-
`[RADISK-MEMORY] Memory warning: ${Math.round(heapUsageRatio * 100)}% of heap limit used. Cache size: ${cache.size} files
|
|
148
|
+
`[RADISK-MEMORY] Memory warning: ${Math.round(heapUsageRatio * 100)}% of heap limit used. Cache size: ${cache.size} files.`,
|
|
111
149
|
)
|
|
112
150
|
}
|
|
113
151
|
}
|
|
114
152
|
|
|
115
|
-
const radisk = ((
|
|
153
|
+
const radisk = ((
|
|
154
|
+
key?: string,
|
|
155
|
+
value?:
|
|
156
|
+
| GraphValue
|
|
157
|
+
| EncodedValue
|
|
158
|
+
| ((
|
|
159
|
+
err?: string | null,
|
|
160
|
+
value?: EncodedValue | Record<string, EncodedValue>,
|
|
161
|
+
) => void),
|
|
162
|
+
cb?: (err?: string | null) => void,
|
|
163
|
+
) => {
|
|
116
164
|
if (typeof key !== "string") return undefined
|
|
117
165
|
|
|
118
166
|
if (typeof value === "function") {
|
|
@@ -261,7 +309,7 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
261
309
|
done: false,
|
|
262
310
|
count: 0,
|
|
263
311
|
sub: undefined as RadixFunction | undefined,
|
|
264
|
-
halfKey: "",
|
|
312
|
+
halfKey: "", // soul ID at the ~options.size/2 mark for a balanced split
|
|
265
313
|
halfOffset: 0, // write.text.length before that entry
|
|
266
314
|
each: (
|
|
267
315
|
value: EncodedValue,
|
|
@@ -289,8 +337,7 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
289
337
|
) {
|
|
290
338
|
const fullKey = pre.join("") + k
|
|
291
339
|
const endIdx = fullKey.indexOf(enq)
|
|
292
|
-
write.halfKey =
|
|
293
|
-
endIdx === -1 ? fullKey : fullKey.substring(0, endIdx)
|
|
340
|
+
write.halfKey = endIdx === -1 ? fullKey : fullKey.substring(0, endIdx)
|
|
294
341
|
write.halfOffset = write.text.length
|
|
295
342
|
}
|
|
296
343
|
|
|
@@ -342,20 +389,35 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
342
389
|
const soul = endIdx === -1 ? key : key.substring(0, endIdx)
|
|
343
390
|
|
|
344
391
|
const findFile = (files: string[]): string | null => {
|
|
345
|
-
let lo = 0,
|
|
392
|
+
let lo = 0,
|
|
393
|
+
hi = files.length - 1,
|
|
394
|
+
result: string | null = null
|
|
346
395
|
while (lo <= hi) {
|
|
347
396
|
const mid = (lo + hi) >> 1
|
|
348
397
|
const f = files[mid]!
|
|
349
|
-
if (f <= soul) {
|
|
350
|
-
|
|
398
|
+
if (f <= soul) {
|
|
399
|
+
result = f
|
|
400
|
+
lo = mid + 1
|
|
401
|
+
} else {
|
|
402
|
+
hi = mid - 1
|
|
403
|
+
}
|
|
351
404
|
}
|
|
352
405
|
return result
|
|
353
406
|
}
|
|
354
407
|
|
|
355
408
|
const read = (file: string | null): void => {
|
|
356
|
-
if (!file) {
|
|
409
|
+
if (!file) {
|
|
410
|
+
cb("no file found", undefined)
|
|
411
|
+
return
|
|
412
|
+
}
|
|
357
413
|
if (options.cache && cache.has(file)) {
|
|
358
|
-
return cb(
|
|
414
|
+
return cb(
|
|
415
|
+
undefined,
|
|
416
|
+
cache.get(file)!(key) as
|
|
417
|
+
| EncodedValue
|
|
418
|
+
| Record<string, EncodedValue>
|
|
419
|
+
| undefined,
|
|
420
|
+
)
|
|
359
421
|
}
|
|
360
422
|
radisk.parse(file, (err, disk) => {
|
|
361
423
|
if (err) options.log(err)
|
|
@@ -363,17 +425,32 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
363
425
|
cache.set(file, disk)
|
|
364
426
|
checkMemoryUsage()
|
|
365
427
|
}
|
|
366
|
-
cb(
|
|
428
|
+
cb(
|
|
429
|
+
err,
|
|
430
|
+
disk
|
|
431
|
+
? (disk(key) as
|
|
432
|
+
| EncodedValue
|
|
433
|
+
| Record<string, EncodedValue>
|
|
434
|
+
| undefined)
|
|
435
|
+
: undefined,
|
|
436
|
+
)
|
|
367
437
|
})
|
|
368
438
|
}
|
|
369
439
|
|
|
370
440
|
const now = Date.now()
|
|
371
|
-
if (
|
|
441
|
+
if (
|
|
442
|
+
options.cache &&
|
|
443
|
+
fileListCache &&
|
|
444
|
+
now - fileListCacheTime < FILE_LIST_CACHE_TTL
|
|
445
|
+
) {
|
|
372
446
|
read(findFile(fileListCache))
|
|
373
447
|
} else {
|
|
374
448
|
const files: string[] = []
|
|
375
449
|
store.list((file?: string) => {
|
|
376
|
-
if (file) {
|
|
450
|
+
if (file) {
|
|
451
|
+
files.push(file)
|
|
452
|
+
return
|
|
453
|
+
}
|
|
377
454
|
files.sort()
|
|
378
455
|
if (options.cache) {
|
|
379
456
|
fileListCache = files
|
|
@@ -387,7 +464,10 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
387
464
|
radisk.parse = (file, cb) => {
|
|
388
465
|
const parse = {
|
|
389
466
|
disk: Radix(),
|
|
390
|
-
read: (
|
|
467
|
+
read: (
|
|
468
|
+
err?: string | null,
|
|
469
|
+
data?: string | EncodedValue | Record<string, unknown>,
|
|
470
|
+
): void => {
|
|
391
471
|
if (err) return cb(err)
|
|
392
472
|
if (!data) return cb(null, parse.disk)
|
|
393
473
|
|
|
@@ -398,7 +478,12 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
398
478
|
let key: string | undefined
|
|
399
479
|
let value: EncodedValue | undefined
|
|
400
480
|
const tmpVal = tmp[1]
|
|
401
|
-
const i =
|
|
481
|
+
const i =
|
|
482
|
+
typeof tmpVal === "number"
|
|
483
|
+
? tmpVal
|
|
484
|
+
: typeof tmpVal === "string"
|
|
485
|
+
? parseInt(tmpVal, 10)
|
|
486
|
+
: 0
|
|
402
487
|
tmp = parse.split(tmp[2]) || ("" as never)
|
|
403
488
|
if (tmp[0] === "#") {
|
|
404
489
|
const tmpKey = tmp[1]
|
|
@@ -425,16 +510,14 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
425
510
|
}
|
|
426
511
|
cb(null, parse.disk)
|
|
427
512
|
},
|
|
428
|
-
split: (
|
|
429
|
-
data: string
|
|
430
|
-
): [string, unknown, string] | undefined => {
|
|
513
|
+
split: (data: string): [string, unknown, string] | undefined => {
|
|
431
514
|
if (!data) return undefined
|
|
432
515
|
|
|
433
516
|
const i = data.indexOf(unit)
|
|
434
517
|
if (i === -1) return undefined
|
|
435
518
|
|
|
436
519
|
const a = data.slice(0, i)
|
|
437
|
-
const o: {
|
|
520
|
+
const o: {i?: number} = {}
|
|
438
521
|
return [a, Radisk.decode(data.slice(i), o), data.slice(i + o.i!)]
|
|
439
522
|
},
|
|
440
523
|
}
|
|
@@ -452,7 +535,7 @@ Radisk.encode = (input: EncodedValue | GraphValue): string => {
|
|
|
452
535
|
let state = ""
|
|
453
536
|
let sig = ""
|
|
454
537
|
let data: GraphValue
|
|
455
|
-
|
|
538
|
+
|
|
456
539
|
if (Array.isArray(input) && (input.length === 2 || input.length === 3)) {
|
|
457
540
|
// input is EncodedValue - extract the GraphValue and metadata
|
|
458
541
|
state = etx + input[1]
|
|
@@ -492,7 +575,10 @@ Radisk.encode = (input: EncodedValue | GraphValue): string => {
|
|
|
492
575
|
/**
|
|
493
576
|
* Decode value from storage
|
|
494
577
|
*/
|
|
495
|
-
Radisk.decode = (
|
|
578
|
+
Radisk.decode = (
|
|
579
|
+
data: string,
|
|
580
|
+
obj?: {i?: number},
|
|
581
|
+
): EncodedValue | GraphValue | Relation | null | undefined => {
|
|
496
582
|
let i = -1
|
|
497
583
|
let n = 0
|
|
498
584
|
let current: string | undefined = undefined
|
|
@@ -574,4 +660,3 @@ Radisk.decode = (data: string, obj?: { i?: number }): EncodedValue | GraphValue
|
|
|
574
660
|
}
|
|
575
661
|
|
|
576
662
|
export default Radisk
|
|
577
|
-
|
package/ts/src/radix.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import * as utils from "./utils.ts"
|
|
7
|
-
import type {
|
|
7
|
+
import type {EncodedValue, RadixFunction} from "./schemas.ts"
|
|
8
8
|
|
|
9
9
|
// ASCII character for group separator
|
|
10
10
|
const group = String.fromCharCode(29)
|
|
@@ -34,10 +34,11 @@ const Radix = (): RadixFunction => {
|
|
|
34
34
|
const radix = ((
|
|
35
35
|
keys?: string,
|
|
36
36
|
value?: EncodedValue,
|
|
37
|
-
tree?: RadixTree
|
|
37
|
+
tree?: RadixTree,
|
|
38
38
|
): RadixNode | EncodedValue | undefined | Record<string, EncodedValue> => {
|
|
39
39
|
if (!tree) {
|
|
40
|
-
if (!(radix as Record<string, unknown>)[group])
|
|
40
|
+
if (!(radix as Record<string, unknown>)[group])
|
|
41
|
+
(radix as Record<string, unknown>)[group] = {}
|
|
41
42
|
tree = (radix as Record<string, RadixTree>)[group]!
|
|
42
43
|
}
|
|
43
44
|
if (!keys) return tree
|
|
@@ -57,32 +58,35 @@ const Radix = (): RadixFunction => {
|
|
|
57
58
|
|
|
58
59
|
if (!found) {
|
|
59
60
|
// If not found from the provided keys try matching with an existing key
|
|
60
|
-
const result = utils.obj.map(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
matchingKey
|
|
65
|
-
j
|
|
66
|
-
|
|
67
|
-
|
|
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
|
|
61
|
+
const result = utils.obj.map(
|
|
62
|
+
tree!,
|
|
63
|
+
(hasValue: RadixValue, hasKey: string) => {
|
|
64
|
+
let j = 0
|
|
65
|
+
let matchingKey = ""
|
|
66
|
+
while (hasKey[j] === keys[j]) {
|
|
67
|
+
matchingKey += hasKey[j]
|
|
68
|
+
j++
|
|
74
69
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
if (matchingKey) {
|
|
71
|
+
if (noValue) {
|
|
72
|
+
// matchingKey has to be as long as the original keys when reading
|
|
73
|
+
if (j <= max) return undefined
|
|
74
|
+
|
|
75
|
+
tmp[hasKey.slice(j)] = hasValue
|
|
76
|
+
return hasValue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const replace: RadixTree = {
|
|
80
|
+
[hasKey.slice(j)]: hasValue,
|
|
81
|
+
[keys.slice(j)]: {[record]: value!},
|
|
82
|
+
}
|
|
83
|
+
tree![matchingKey] = {[group]: replace}
|
|
84
|
+
delete tree![hasKey]
|
|
85
|
+
return true
|
|
79
86
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
return undefined
|
|
85
|
-
})
|
|
87
|
+
return undefined
|
|
88
|
+
},
|
|
89
|
+
)
|
|
86
90
|
|
|
87
91
|
if (!result) {
|
|
88
92
|
if (noValue) return undefined
|
|
@@ -96,7 +100,9 @@ const Radix = (): RadixFunction => {
|
|
|
96
100
|
// If no value use the key provided to return a whole group or record
|
|
97
101
|
if (noValue) {
|
|
98
102
|
// If an individual record isn't found then return the whole group
|
|
99
|
-
return typeof found[record] === "undefined"
|
|
103
|
+
return typeof found[record] === "undefined"
|
|
104
|
+
? found[group]
|
|
105
|
+
: found[record]
|
|
100
106
|
}
|
|
101
107
|
// Otherwise create a new record at the provided key for value
|
|
102
108
|
found[record] = value!
|
|
@@ -120,22 +126,33 @@ const Radix = (): RadixFunction => {
|
|
|
120
126
|
*/
|
|
121
127
|
Radix.map = function map(
|
|
122
128
|
radix: RadixFunction | RadixNode,
|
|
123
|
-
cb: (
|
|
129
|
+
cb: (
|
|
130
|
+
value: EncodedValue,
|
|
131
|
+
fullKey: string,
|
|
132
|
+
key: string,
|
|
133
|
+
pre: string[],
|
|
134
|
+
) => unknown,
|
|
124
135
|
opt?: boolean,
|
|
125
|
-
pre: string[] = []
|
|
136
|
+
pre: string[] = [],
|
|
126
137
|
): unknown {
|
|
127
|
-
const tree = ((radix as Record<string, RadixTree>)[group] ||
|
|
138
|
+
const tree = ((radix as Record<string, RadixTree>)[group] ||
|
|
139
|
+
radix) as RadixTree
|
|
128
140
|
const keys = Object.keys(tree).sort()
|
|
129
141
|
|
|
130
142
|
for (let i = 0; i < keys.length; i++) {
|
|
131
143
|
const key = keys[i]!
|
|
132
144
|
const found = tree[key]
|
|
133
145
|
if (!found) continue
|
|
134
|
-
|
|
146
|
+
|
|
135
147
|
const recordValue = found[record]
|
|
136
148
|
|
|
137
149
|
if (typeof recordValue !== "undefined") {
|
|
138
|
-
const result = cb(
|
|
150
|
+
const result = cb(
|
|
151
|
+
recordValue as EncodedValue,
|
|
152
|
+
pre.join("") + key,
|
|
153
|
+
key,
|
|
154
|
+
pre,
|
|
155
|
+
)
|
|
139
156
|
if (typeof result !== "undefined") return result
|
|
140
157
|
} else if (opt) {
|
|
141
158
|
cb(undefined as never, pre.join(""), key, pre)
|
|
@@ -154,4 +171,3 @@ Radix.map = function map(
|
|
|
154
171
|
}
|
|
155
172
|
|
|
156
173
|
export default Radix
|
|
157
|
-
|
package/ts/src/schemas.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {z} from "zod"
|
|
2
2
|
|
|
3
3
|
// ============================================================================
|
|
4
4
|
// Core Primitive Types
|
|
@@ -48,7 +48,7 @@ export const GraphValueSchema: z.ZodType<GraphValue> = z.lazy(() =>
|
|
|
48
48
|
z.null(),
|
|
49
49
|
RelationSchema,
|
|
50
50
|
z.record(z.string(), GraphValueSchema),
|
|
51
|
-
])
|
|
51
|
+
]),
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
export type GraphValue =
|
|
@@ -57,7 +57,7 @@ export type GraphValue =
|
|
|
57
57
|
| boolean
|
|
58
58
|
| null
|
|
59
59
|
| Relation
|
|
60
|
-
| {
|
|
60
|
+
| {[key: string]: GraphValue}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* State vector - tracks timestamps for each property
|
|
@@ -91,9 +91,11 @@ export const USER_SIGNATURE = "_holster_user_signature"
|
|
|
91
91
|
* Graph node - contains metadata and properties
|
|
92
92
|
* Note: Using manual type to properly handle the index signature with special _ key
|
|
93
93
|
*/
|
|
94
|
-
export const GraphNodeSchema = z
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
export const GraphNodeSchema = z
|
|
95
|
+
.object({
|
|
96
|
+
_: GraphMetadataSchema,
|
|
97
|
+
})
|
|
98
|
+
.and(z.record(z.string(), GraphValueSchema))
|
|
97
99
|
|
|
98
100
|
export type GraphNode = {
|
|
99
101
|
_: GraphMetadata
|
|
@@ -141,9 +143,7 @@ export const LexFilterSchema = z.union([
|
|
|
141
143
|
export type LexFilter = z.infer<typeof LexFilterSchema>
|
|
142
144
|
|
|
143
145
|
// LexFilter with optional "." property for nested queries
|
|
144
|
-
export type LexWithDot =
|
|
145
|
-
| LexFilter
|
|
146
|
-
| { ".": LexFilter }
|
|
146
|
+
export type LexWithDot = LexFilter | {".": LexFilter}
|
|
147
147
|
|
|
148
148
|
/**
|
|
149
149
|
* Lex query object
|
|
@@ -294,8 +294,18 @@ export const FileSystemInterfaceSchema = z.object({
|
|
|
294
294
|
// FileSystemInterface: Flexible to support both string-based and EncodedValue storage
|
|
295
295
|
// In-memory stores use strings, disk stores use EncodedValue/RadixNode
|
|
296
296
|
export interface FileSystemInterface {
|
|
297
|
-
get: (
|
|
298
|
-
|
|
297
|
+
get: (
|
|
298
|
+
file: string,
|
|
299
|
+
cb: (
|
|
300
|
+
err?: string | null,
|
|
301
|
+
data?: string | EncodedValue | Record<string, unknown>,
|
|
302
|
+
) => void,
|
|
303
|
+
) => void
|
|
304
|
+
put: (
|
|
305
|
+
file: string,
|
|
306
|
+
data: string | EncodedValue | Record<string, unknown>,
|
|
307
|
+
cb: (err?: string | null) => void,
|
|
308
|
+
) => void
|
|
299
309
|
list: (cb: (file?: string) => void) => void
|
|
300
310
|
}
|
|
301
311
|
|
|
@@ -405,6 +415,9 @@ export const HolsterOptionsSchema = z
|
|
|
405
415
|
maxConnections: z.number().optional(),
|
|
406
416
|
maxMessageSize: z.number().optional(),
|
|
407
417
|
maxQueueLength: z.number().optional(),
|
|
418
|
+
userLimit: z.boolean().optional(),
|
|
419
|
+
defaultLimit: z.number().optional(),
|
|
420
|
+
maxGraphSize: z.number().optional(),
|
|
408
421
|
})
|
|
409
422
|
.passthrough()
|
|
410
423
|
export type HolsterOptions = z.infer<typeof HolsterOptionsSchema>
|
|
@@ -462,20 +475,28 @@ export type RadixValue = GraphValue | EncodedValue | RadixNode
|
|
|
462
475
|
*/
|
|
463
476
|
export type RadixFunction = {
|
|
464
477
|
// Main call signature: synchronous get/put operations
|
|
465
|
-
(
|
|
466
|
-
|
|
467
|
-
|
|
478
|
+
(
|
|
479
|
+
keys?: string,
|
|
480
|
+
value?: GraphValue | EncodedValue,
|
|
481
|
+
tree?: RadixNode,
|
|
482
|
+
): RadixNode | EncodedValue | undefined | Record<string, EncodedValue>
|
|
483
|
+
|
|
468
484
|
// Callback form: async operations
|
|
469
485
|
(key?: string, value?: RadixValue, cb?: (err?: string) => void): void
|
|
470
|
-
|
|
486
|
+
|
|
471
487
|
// Static map method for iterating over radix tree values
|
|
472
488
|
map: (
|
|
473
|
-
radix: RadixFunction | RadixNode,
|
|
474
|
-
cb: (
|
|
489
|
+
radix: RadixFunction | RadixNode,
|
|
490
|
+
cb: (
|
|
491
|
+
value: EncodedValue,
|
|
492
|
+
fullKey: string,
|
|
493
|
+
key: string,
|
|
494
|
+
pre: string[],
|
|
495
|
+
) => unknown,
|
|
475
496
|
opt?: boolean,
|
|
476
|
-
pre?: string[]
|
|
497
|
+
pre?: string[],
|
|
477
498
|
) => unknown
|
|
478
|
-
|
|
499
|
+
|
|
479
500
|
// Dynamic properties for internal state (e.g., group separator key)
|
|
480
501
|
[key: string]: unknown
|
|
481
502
|
}
|
|
@@ -506,10 +527,9 @@ export type Dup = z.infer<typeof DupSchema> & {
|
|
|
506
527
|
// ============================================================================
|
|
507
528
|
|
|
508
529
|
// Export RadiskInterface from radisk module for test usage
|
|
509
|
-
export type {
|
|
530
|
+
export type {RadiskInterface} from "./radisk.ts"
|
|
510
531
|
|
|
511
532
|
// Export other interfaces for test usage
|
|
512
|
-
export type {
|
|
513
|
-
export type {
|
|
514
|
-
export type {
|
|
515
|
-
|
|
533
|
+
export type {WireAPI as WireInterface} from "./wire.ts"
|
|
534
|
+
export type {UserInterface} from "./user.ts"
|
|
535
|
+
export type {StoreInterface} from "./store.ts"
|
package/ts/src/sea-utils.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import SafeBuffer from "./buffer.ts"
|
|
7
|
-
import type {
|
|
7
|
+
import type {JWK} from "./schemas.ts"
|
|
8
8
|
|
|
9
9
|
const isNode = typeof process !== "undefined" && process.versions?.node != null
|
|
10
10
|
|
|
@@ -64,23 +64,20 @@ export const jwk = (pub: string, priv?: string): JWK => {
|
|
|
64
64
|
*/
|
|
65
65
|
export const sha256 = async (data: unknown): Promise<SeaArray> => {
|
|
66
66
|
const hash = await subtle.digest(
|
|
67
|
-
{
|
|
68
|
-
new TextEncoder().encode(stringify(data))
|
|
67
|
+
{name: "SHA-256"},
|
|
68
|
+
new TextEncoder().encode(stringify(data)),
|
|
69
69
|
)
|
|
70
70
|
return SafeBuffer.from(new Uint8Array(hash))
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Derive AES key from password and salt
|
|
74
|
+
* Derive AES key from password and salt
|
|
75
75
|
*/
|
|
76
|
-
export const aeskey = async (
|
|
77
|
-
key: string,
|
|
78
|
-
salt: SeaArray
|
|
79
|
-
) => {
|
|
76
|
+
export const aeskey = async (key: string, salt: SeaArray) => {
|
|
80
77
|
const combined = key + salt.toString("utf8")
|
|
81
78
|
const hashResult = await sha256(combined)
|
|
82
79
|
const jwkKey = keyToJwk(hashResult)
|
|
83
|
-
return await subtle.importKey("jwk", jwkKey, {
|
|
80
|
+
return await subtle.importKey("jwk", jwkKey, {name: "AES-GCM"}, false, [
|
|
84
81
|
"encrypt",
|
|
85
82
|
"decrypt",
|
|
86
83
|
])
|
|
@@ -95,9 +92,8 @@ const keyToJwk = (key: SeaArray): JWK => {
|
|
|
95
92
|
.replace(/\+/g, "-")
|
|
96
93
|
.replace(/\//g, "_")
|
|
97
94
|
.replace(/=/g, "")
|
|
98
|
-
return {
|
|
95
|
+
return {kty: "oct", k: k, ext: false, alg: "A256GCM"}
|
|
99
96
|
}
|
|
100
97
|
|
|
101
98
|
// Re-export SeaArray type for convenience
|
|
102
99
|
import type SeaArray from "./array.ts"
|
|
103
|
-
|