@mblaney/holster 1.0.35 → 1.0.36

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 (67) 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 +23 -5
  6. package/src/get.js +6 -18
  7. package/src/ham.js +17 -47
  8. package/src/holster.js +106 -28
  9. package/src/sea.js +0 -52
  10. package/src/store.js +7 -7
  11. package/src/wire.js +7 -1
  12. package/ts/src/array.ts +78 -0
  13. package/ts/src/buffer.ts +441 -0
  14. package/ts/src/dup.ts +49 -0
  15. package/ts/src/get.ts +83 -0
  16. package/ts/src/ham.ts +254 -0
  17. package/ts/src/holster.ts +931 -0
  18. package/ts/src/index.ts +35 -0
  19. package/ts/src/radisk.ts +585 -0
  20. package/ts/src/radix.ts +157 -0
  21. package/ts/src/schemas.ts +519 -0
  22. package/ts/src/sea-utils.ts +103 -0
  23. package/ts/src/sea.ts +388 -0
  24. package/ts/src/store.ts +321 -0
  25. package/ts/src/user.ts +381 -0
  26. package/ts/src/utils.ts +232 -0
  27. package/ts/src/wire.ts +829 -0
  28. package/.dockerignore +0 -9
  29. package/.github/workflows/node.js.yml +0 -31
  30. package/.github/workflows/npm-publish.yml +0 -33
  31. package/Dockerfile +0 -20
  32. package/examples/index.html +0 -33
  33. package/examples/index.js +0 -49
  34. package/examples/user.html +0 -31
  35. package/examples/user.js +0 -93
  36. package/examples/wire.html +0 -27
  37. package/examples/wire.js +0 -55
  38. package/test/holster.get.test.js +0 -94
  39. package/test/holster.lex.test.js +0 -196
  40. package/test/holster.off.test.js +0 -53
  41. package/test/holster.on.test.js +0 -264
  42. package/test/holster.put.test.js +0 -358
  43. package/test/holster.secure.test.js +0 -202
  44. package/test/holster.user.get.test.js +0 -114
  45. package/test/holster.user.lex.test.js +0 -149
  46. package/test/holster.user.off.test.js +0 -73
  47. package/test/holster.user.on.test.js +0 -300
  48. package/test/holster.user.put.test.js +0 -463
  49. package/test/kitty/! +0 -9
  50. package/test/names.js +0 -225
  51. package/test/radisk.cache.test.js +0 -213
  52. package/test/radisk.test.js +0 -331
  53. package/test/radix.test.js +0 -117
  54. package/test/sea.test.js +0 -284
  55. package/test/split.test.js +0 -317
  56. package/test/store.test.js +0 -316
  57. package/test/system/basic.test.js +0 -29
  58. package/test/system/chained-on.test.js +0 -41
  59. package/test/system/concurrent-listeners.test.js +0 -125
  60. package/test/system/immediate-read.test.js +0 -30
  61. package/test/system/listener-before-data.test.js +0 -51
  62. package/test/system/promise-wrapper.test.js +0 -47
  63. package/test/system/user-chained-on.test.js +0 -60
  64. package/test/system/user-concurrent-listeners.test.js +0 -142
  65. package/test/system/user-listener-before-data.test.js +0 -66
  66. package/test/user.test.js +0 -173
  67. package/test/wire.test.js +0 -158
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Utility functions for Holster
3
+ * Provides helpers for type checking, graph manipulation, and pattern matching
4
+ */
5
+
6
+ import type { Soul, Relation, Graph, GraphNode, LexFilter, GraphValue } from "./schemas.ts"
7
+
8
+ // ============================================================================
9
+ // Number Utilities
10
+ // ============================================================================
11
+
12
+ export const num = {
13
+ /**
14
+ * Check if value is a valid number
15
+ */
16
+ is: (n: unknown): n is number => {
17
+ if (n instanceof Array) return false
18
+ if (typeof n === "number") return !isNaN(n)
19
+ if (typeof n === "string") {
20
+ const parsed = parseFloat(n)
21
+ return !isNaN(parsed) && isFinite(parsed)
22
+ }
23
+ return false
24
+ },
25
+ }
26
+
27
+ // ============================================================================
28
+ // Object Utilities
29
+ // ============================================================================
30
+
31
+ type PlainObject = Record<string, unknown>
32
+
33
+ export const obj = {
34
+ /**
35
+ * Check if value is a plain object
36
+ */
37
+ is: (o: unknown): o is PlainObject => {
38
+ if (!o || typeof o !== "object") return false
39
+ const toString = Object.prototype.toString.call(o)
40
+ const match = toString.match(/^\[object (\w+)\]$/)
41
+ return (
42
+ (o as Record<string, unknown>).constructor === Object ||
43
+ (match !== null && match[1] === "Object")
44
+ )
45
+ },
46
+
47
+ /**
48
+ * Map over object keys with early return support
49
+ */
50
+ map: <T, R>(
51
+ list: Record<string, T>,
52
+ cb: (value: T, key: string, o?: Record<string, unknown>) => R | undefined,
53
+ o?: Record<string, unknown>
54
+ ): R | undefined => {
55
+ const keys = Object.keys(list)
56
+ for (let i = 0; i < keys.length; i++) {
57
+ const key = keys[i]!
58
+ const result = cb(list[key]!, key, o)
59
+ if (typeof result !== "undefined") return result
60
+ }
61
+ return undefined
62
+ },
63
+
64
+ /**
65
+ * Set a property on an object
66
+ */
67
+ put: (o: Record<string, unknown> | undefined, key: string, value: unknown): Record<string, unknown> => {
68
+ const target = o || {}
69
+ target[key] = value
70
+ return target
71
+ },
72
+
73
+ /**
74
+ * Delete a property from an object
75
+ */
76
+ del: <T>(o: Record<string, T> | undefined, key: string): Record<string, T> | undefined => {
77
+ if (!o) return undefined
78
+ o[key] = null as T
79
+ delete o[key]
80
+ return o
81
+ },
82
+ }
83
+
84
+ // ============================================================================
85
+ // Relation Utilities
86
+ // ============================================================================
87
+
88
+ const map_soul = (soul: unknown, key: string, o: { id: string | false }): void => {
89
+ // If id is already defined AND we're still looping through the object,
90
+ // then it is considered invalid
91
+ if (o.id) {
92
+ o.id = false
93
+ return
94
+ }
95
+
96
+ if (key === "#" && typeof soul === "string") {
97
+ o.id = soul
98
+ return
99
+ }
100
+
101
+ // If there exists anything else on the object that isn't the soul,
102
+ // then it is considered invalid
103
+ o.id = false
104
+ }
105
+
106
+ export const rel = {
107
+ /**
108
+ * Check if value is a soul relation and return the soul ID
109
+ */
110
+ is: (value: unknown): string | false => {
111
+ if (
112
+ value &&
113
+ typeof value === "object" &&
114
+ "#" in value &&
115
+ !("_" in value) &&
116
+ obj.is(value)
117
+ ) {
118
+ const o: { id: string | false } = { id: "" }
119
+ obj.map(value as Record<string, unknown>, map_soul as (value: unknown, key: string, o?: Record<string, unknown>) => void, o)
120
+ if (o.id) return o.id
121
+ }
122
+ return false
123
+ },
124
+
125
+ /**
126
+ * Convert a soul into a relation and return it
127
+ */
128
+ ify: (soul: Soul): Relation => obj.put({}, "#", soul) as Relation,
129
+ }
130
+
131
+ // ============================================================================
132
+ // User Metadata Keys
133
+ // ============================================================================
134
+
135
+ export const userSignature = "_holster_user_signature"
136
+ export const userPublicKey = "_holster_user_public_key"
137
+
138
+ // ============================================================================
139
+ // Graph Construction
140
+ // ============================================================================
141
+
142
+ /**
143
+ * Create a graph from soul and data with optional signature metadata
144
+ */
145
+ export const graph = (
146
+ soul: Soul,
147
+ data: Record<string, GraphValue>,
148
+ sig?: string,
149
+ pub?: string,
150
+ timestamp?: number
151
+ ): Graph => {
152
+ const ts = timestamp || Date.now()
153
+ const stateVector: Record<string, number> = {}
154
+ const node: GraphNode = {
155
+ _: { "#": soul, ">": stateVector },
156
+ }
157
+
158
+ for (const [key, value] of Object.entries(data)) {
159
+ if (key !== "_" && key !== userPublicKey && key !== userSignature) {
160
+ node[key] = value
161
+ stateVector[key] = ts
162
+ }
163
+ }
164
+
165
+ if (sig && pub && timestamp) {
166
+ if (!node._["s"]) {
167
+ node._["s"] = {}
168
+ }
169
+ node._["s"][timestamp.toString()] = sig
170
+ node[userPublicKey] = pub
171
+ stateVector[userPublicKey] = timestamp
172
+ }
173
+
174
+ return { [soul]: node }
175
+ }
176
+
177
+ // ============================================================================
178
+ // Pattern Matching
179
+ // ============================================================================
180
+
181
+ /**
182
+ * Match a lex filter against a key
183
+ */
184
+ export const match = (lex: LexFilter, key?: string): boolean => {
185
+ // Null is used to match listeners on souls, which don't provide a key
186
+ if (typeof key === "undefined") return lex === null
187
+
188
+ if (typeof lex === "undefined") return true
189
+
190
+ if (typeof lex === "string") return lex === key
191
+
192
+ // Array support: check if key is in the array
193
+ if (Array.isArray(lex)) return lex.includes(key)
194
+
195
+ if (!obj.is(lex) || !key) return false
196
+
197
+ // Prefix match
198
+ if ("*" in lex) {
199
+ const prefix = lex["*"] as string
200
+ return key.slice(0, prefix.length) === prefix
201
+ }
202
+
203
+ // Range match
204
+ const gt = ">" in lex ? (lex[">"] as string) : undefined
205
+ const lt = "<" in lex ? (lex["<"] as string) : undefined
206
+
207
+ if (gt && lt) return key >= gt && key <= lt
208
+ if (gt) return key >= gt
209
+ if (lt) return key <= lt
210
+
211
+ return false
212
+ }
213
+
214
+ // ============================================================================
215
+ // Text Utilities
216
+ // ============================================================================
217
+
218
+ export const text = {
219
+ /**
220
+ * Generate a random string of specified length
221
+ */
222
+ random: (length?: number): string => {
223
+ let s = ""
224
+ const c = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
225
+ const len = length || 24
226
+ for (let i = 0; i < len; i++) {
227
+ s += c.charAt(Math.floor(Math.random() * c.length))
228
+ }
229
+ return s
230
+ },
231
+ }
232
+