@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.
- package/build/holster.js +3 -3
- package/build/holster.js.map +3 -3
- package/dist/holster.js +4 -0
- package/dist/holster.js.map +7 -0
- package/package.json +23 -5
- package/src/get.js +6 -18
- package/src/ham.js +17 -47
- package/src/holster.js +106 -28
- package/src/sea.js +0 -52
- package/src/store.js +7 -7
- package/src/wire.js +7 -1
- package/ts/src/array.ts +78 -0
- package/ts/src/buffer.ts +441 -0
- package/ts/src/dup.ts +49 -0
- package/ts/src/get.ts +83 -0
- package/ts/src/ham.ts +254 -0
- package/ts/src/holster.ts +931 -0
- package/ts/src/index.ts +35 -0
- package/ts/src/radisk.ts +585 -0
- package/ts/src/radix.ts +157 -0
- package/ts/src/schemas.ts +519 -0
- package/ts/src/sea-utils.ts +103 -0
- package/ts/src/sea.ts +388 -0
- package/ts/src/store.ts +321 -0
- package/ts/src/user.ts +381 -0
- package/ts/src/utils.ts +232 -0
- package/ts/src/wire.ts +829 -0
- package/.dockerignore +0 -9
- package/.github/workflows/node.js.yml +0 -31
- package/.github/workflows/npm-publish.yml +0 -33
- package/Dockerfile +0 -20
- package/examples/index.html +0 -33
- package/examples/index.js +0 -49
- package/examples/user.html +0 -31
- package/examples/user.js +0 -93
- package/examples/wire.html +0 -27
- package/examples/wire.js +0 -55
- package/test/holster.get.test.js +0 -94
- package/test/holster.lex.test.js +0 -196
- package/test/holster.off.test.js +0 -53
- package/test/holster.on.test.js +0 -264
- package/test/holster.put.test.js +0 -358
- package/test/holster.secure.test.js +0 -202
- package/test/holster.user.get.test.js +0 -114
- package/test/holster.user.lex.test.js +0 -149
- package/test/holster.user.off.test.js +0 -73
- package/test/holster.user.on.test.js +0 -300
- package/test/holster.user.put.test.js +0 -463
- package/test/kitty/! +0 -9
- package/test/names.js +0 -225
- package/test/radisk.cache.test.js +0 -213
- package/test/radisk.test.js +0 -331
- package/test/radix.test.js +0 -117
- package/test/sea.test.js +0 -284
- package/test/split.test.js +0 -317
- package/test/store.test.js +0 -316
- package/test/system/basic.test.js +0 -29
- package/test/system/chained-on.test.js +0 -41
- package/test/system/concurrent-listeners.test.js +0 -125
- package/test/system/immediate-read.test.js +0 -30
- package/test/system/listener-before-data.test.js +0 -51
- package/test/system/promise-wrapper.test.js +0 -47
- package/test/system/user-chained-on.test.js +0 -60
- package/test/system/user-concurrent-listeners.test.js +0 -142
- package/test/system/user-listener-before-data.test.js +0 -66
- package/test/user.test.js +0 -173
- package/test/wire.test.js +0 -158
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holster - Main API for graph database operations
|
|
3
|
+
* Provides chainable interface for get, put, on, off operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as utils from "./utils.ts"
|
|
7
|
+
import Wire, { type WireAPI } from "./wire.ts"
|
|
8
|
+
import User, { type UserInterface } from "./user.ts"
|
|
9
|
+
import SEA from "./sea.ts"
|
|
10
|
+
import type {
|
|
11
|
+
HolsterOptions,
|
|
12
|
+
ChainItem,
|
|
13
|
+
ApiContext,
|
|
14
|
+
Graph,
|
|
15
|
+
GraphValue,
|
|
16
|
+
Lex,
|
|
17
|
+
LexFilter,
|
|
18
|
+
LexWithDot,
|
|
19
|
+
UserIdentity,
|
|
20
|
+
WireOptions,
|
|
21
|
+
} from "./schemas.ts"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Holster API interface
|
|
25
|
+
*/
|
|
26
|
+
export interface HolsterAPI {
|
|
27
|
+
get: {
|
|
28
|
+
(key: string | null, lex?: LexWithDot, cb?: (data: unknown) => void, _opt?: WireOptions): HolsterAPI
|
|
29
|
+
(
|
|
30
|
+
key: string | null,
|
|
31
|
+
cb?: (data: unknown) => void,
|
|
32
|
+
_opt?: WireOptions
|
|
33
|
+
): HolsterAPI
|
|
34
|
+
}
|
|
35
|
+
next: {
|
|
36
|
+
(key: string | null, lex?: LexWithDot, cb?: (data: unknown) => void, _opt?: WireOptions): HolsterAPI
|
|
37
|
+
(
|
|
38
|
+
key: string | null,
|
|
39
|
+
cb?: (data: unknown) => void,
|
|
40
|
+
_opt?: WireOptions
|
|
41
|
+
): HolsterAPI
|
|
42
|
+
}
|
|
43
|
+
put: (
|
|
44
|
+
data: GraphValue | Record<string, GraphValue>,
|
|
45
|
+
set?: boolean | ((data?: string | null) => void),
|
|
46
|
+
cb?: (data?: string | null) => void
|
|
47
|
+
) => HolsterAPI | void
|
|
48
|
+
on: (
|
|
49
|
+
lex: LexWithDot | ((data: unknown) => void),
|
|
50
|
+
cb?: ((data: unknown) => void) | boolean,
|
|
51
|
+
_get?: boolean,
|
|
52
|
+
_opt?: WireOptions
|
|
53
|
+
) => void
|
|
54
|
+
off: (cb?: (data: unknown) => void) => HolsterAPI | void
|
|
55
|
+
user: () => UserInterface & HolsterAPI
|
|
56
|
+
wire: WireAPI
|
|
57
|
+
SEA: typeof SEA
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const Holster = (opt?: HolsterOptions | string | string[]): HolsterAPI => {
|
|
61
|
+
let options: HolsterOptions
|
|
62
|
+
if (typeof opt === "string") options = { peers: [opt] }
|
|
63
|
+
else if (opt instanceof Array) options = { peers: opt }
|
|
64
|
+
else if (!utils.obj.is(opt)) options = {}
|
|
65
|
+
else options = opt
|
|
66
|
+
|
|
67
|
+
const wire = Wire(options)
|
|
68
|
+
const user = User(options, wire as never)
|
|
69
|
+
const map = new Map<(data: unknown) => void, () => void>()
|
|
70
|
+
const allctx = new Map<string, ApiContext>()
|
|
71
|
+
|
|
72
|
+
const ok = (data: GraphValue): boolean => {
|
|
73
|
+
return (
|
|
74
|
+
data === null ||
|
|
75
|
+
data === true ||
|
|
76
|
+
data === false ||
|
|
77
|
+
typeof data === "string" ||
|
|
78
|
+
!!utils.rel.is(data) ||
|
|
79
|
+
utils.num.is(data)
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const check = (
|
|
84
|
+
data: GraphValue | Record<string, GraphValue>
|
|
85
|
+
): true | string | string[] => {
|
|
86
|
+
if (ok(data as GraphValue)) return true
|
|
87
|
+
|
|
88
|
+
if (utils.obj.is(data)) {
|
|
89
|
+
const keys: string[] = []
|
|
90
|
+
for (const [key, value] of Object.entries(data)) {
|
|
91
|
+
if (key === "_") {
|
|
92
|
+
return "error underscore cannot be used as a property name"
|
|
93
|
+
}
|
|
94
|
+
if (utils.obj.is(value) || ok(value as GraphValue)) {
|
|
95
|
+
keys.push(key)
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
if (typeof value === "undefined") {
|
|
99
|
+
return `error undefined ${key} cannot be converted to a graph`
|
|
100
|
+
}
|
|
101
|
+
const error = JSON.stringify({ [key]: value })
|
|
102
|
+
return `error ${error} cannot be converted to a graph`
|
|
103
|
+
}
|
|
104
|
+
if (keys.length !== 0) return keys
|
|
105
|
+
}
|
|
106
|
+
const error = JSON.stringify(data)
|
|
107
|
+
return `error ${error} cannot be converted to a graph`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const api = (initCtxid?: string): HolsterAPI => {
|
|
111
|
+
let ctxid = initCtxid
|
|
112
|
+
const get = (
|
|
113
|
+
lex: LexFilter,
|
|
114
|
+
soul: string,
|
|
115
|
+
ack: (data: unknown) => void,
|
|
116
|
+
_opt?: WireOptions
|
|
117
|
+
): void => {
|
|
118
|
+
wire.get(
|
|
119
|
+
utils.obj.put(lex as never, "#", soul) as Lex,
|
|
120
|
+
async msg => {
|
|
121
|
+
if (msg.err) console.log(msg.err)
|
|
122
|
+
if (msg.put && msg.put[soul]) {
|
|
123
|
+
delete (msg.put[soul] as { _?: unknown })._
|
|
124
|
+
delete (msg.put[soul] as Record<string, unknown>)[utils.userPublicKey]
|
|
125
|
+
delete (msg.put[soul] as Record<string, unknown>)[utils.userSignature]
|
|
126
|
+
for (const key of Object.keys(msg.put[soul]!)) {
|
|
127
|
+
const id = utils.rel.is(msg.put[soul]![key] as GraphValue)
|
|
128
|
+
if (id) {
|
|
129
|
+
const attemptRead = async (retries = 0): Promise<unknown> => {
|
|
130
|
+
const data = await new Promise<unknown>(res => {
|
|
131
|
+
const _ctxid = utils.text.random()
|
|
132
|
+
const ctx = allctx.get(ctxid!)
|
|
133
|
+
allctx.set(_ctxid, {
|
|
134
|
+
chain: [{ item: null, soul: id }],
|
|
135
|
+
user: ctx ? ctx.user : null,
|
|
136
|
+
})
|
|
137
|
+
api(_ctxid).next(null as never, res as never, _opt)
|
|
138
|
+
})
|
|
139
|
+
if (data !== null || retries >= 5) {
|
|
140
|
+
return data
|
|
141
|
+
}
|
|
142
|
+
await new Promise(resolve => setTimeout(resolve, 50))
|
|
143
|
+
return attemptRead(retries + 1)
|
|
144
|
+
}
|
|
145
|
+
msg.put[soul]![key] = (await attemptRead()) as GraphValue
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
ack(msg.put[soul])
|
|
149
|
+
} else {
|
|
150
|
+
ack(null)
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
_opt
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const graph = async (
|
|
158
|
+
soul: string,
|
|
159
|
+
data: Record<string, GraphValue>,
|
|
160
|
+
userctx?: UserIdentity | null,
|
|
161
|
+
cb?: (err?: string) => void
|
|
162
|
+
): Promise<Graph | null> => {
|
|
163
|
+
if (userctx) {
|
|
164
|
+
const timestamp = Date.now()
|
|
165
|
+
const sig = await SEA.signTimestamp(timestamp, userctx)
|
|
166
|
+
return utils.graph(soul, data, sig!, userctx.pub, timestamp)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (options.secure) {
|
|
170
|
+
if (!cb) cb = console.log
|
|
171
|
+
cb(`error putting data on ${soul}: user required in secure mode`)
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return utils.graph(soul, data)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const done = (ctxid: string) => {
|
|
179
|
+
return (data: unknown): void => {
|
|
180
|
+
const ctx = allctx.get(ctxid)
|
|
181
|
+
if (ctx && typeof ctx.cb !== "undefined") {
|
|
182
|
+
setTimeout(() => ctx.cb!(data as never), 1)
|
|
183
|
+
} else if (data) {
|
|
184
|
+
console.log("error no callback for data", data, "ctx", ctx)
|
|
185
|
+
}
|
|
186
|
+
if (ctx && !ctx.on) allctx.delete(ctxid)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const watchForRel = (
|
|
191
|
+
soul: string,
|
|
192
|
+
item: string,
|
|
193
|
+
ctx: ApiContext,
|
|
194
|
+
i: number,
|
|
195
|
+
request: { on?: LexFilter; _get?: boolean; _opt?: WireOptions },
|
|
196
|
+
cb: (data: unknown) => void
|
|
197
|
+
): void => {
|
|
198
|
+
const handler = (): void => {
|
|
199
|
+
// Bail out if the context was removed by off().
|
|
200
|
+
if (!allctx.has(ctxid!)) {
|
|
201
|
+
wire.off({ "#": soul, ".": item }, handler)
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
wire.get(
|
|
205
|
+
{ "#": soul, ".": item },
|
|
206
|
+
msg => {
|
|
207
|
+
const node = msg.put && msg.put[soul]
|
|
208
|
+
const id = utils.rel.is(node && node[item] as GraphValue)
|
|
209
|
+
if (!id) return
|
|
210
|
+
wire.off({ "#": soul, ".": item }, handler)
|
|
211
|
+
ctx.chain[i]!.soul = id
|
|
212
|
+
allctx.set(ctxid!, { ...ctx })
|
|
213
|
+
api(ctxid).on(request.on!, cb, request._get, request._opt)
|
|
214
|
+
},
|
|
215
|
+
{ ...request._opt, secure: (typeof ctx.user === "boolean" ? ctx.user : !!ctx.user) || options.secure }
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
wire.on({ "#": soul, ".": item }, handler, false, request._opt)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const resolve = (
|
|
222
|
+
request: {
|
|
223
|
+
get?: LexFilter
|
|
224
|
+
put?: GraphValue | Record<string, GraphValue>
|
|
225
|
+
on?: LexFilter
|
|
226
|
+
off?: boolean
|
|
227
|
+
_opt?: WireOptions
|
|
228
|
+
_get?: boolean
|
|
229
|
+
},
|
|
230
|
+
cb?: (data: unknown) => void
|
|
231
|
+
): ChainItem | false => {
|
|
232
|
+
if (!request) {
|
|
233
|
+
console.log("error resolve request parameter required")
|
|
234
|
+
return false
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const get = typeof request.get !== "undefined"
|
|
238
|
+
const put = typeof request.put !== "undefined"
|
|
239
|
+
const on = typeof request.on !== "undefined"
|
|
240
|
+
const off = typeof request.off !== "undefined"
|
|
241
|
+
|
|
242
|
+
let found = false
|
|
243
|
+
const ctx = allctx.get(ctxid!)!
|
|
244
|
+
for (let i = 1; i < ctx.chain.length; i++) {
|
|
245
|
+
if (ctx.chain[i]!.soul !== null) continue
|
|
246
|
+
found = true
|
|
247
|
+
break
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (found) {
|
|
251
|
+
let i = 1
|
|
252
|
+
while (i < ctx.chain.length && ctx.chain[i]!.soul !== null) i++
|
|
253
|
+
const { item, soul } = ctx.chain[i - 1]!
|
|
254
|
+
wire.get(
|
|
255
|
+
{ "#": soul!, ".": item! },
|
|
256
|
+
async msg => {
|
|
257
|
+
if (msg.err) {
|
|
258
|
+
console.log(`error getting ${item} on ${soul}: ${msg.err}`)
|
|
259
|
+
if (cb) cb(null)
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let node = msg.put && msg.put[soul!]
|
|
264
|
+
if (node && typeof node[item!] !== "undefined") {
|
|
265
|
+
let id = utils.rel.is(node[item!] as GraphValue)
|
|
266
|
+
if (id) {
|
|
267
|
+
ctx.chain[i]!.soul = id
|
|
268
|
+
allctx.set(ctxid!, { ...ctx })
|
|
269
|
+
if (get) {
|
|
270
|
+
api(ctxid).next(null as never, request.get as never, cb as never, request._opt)
|
|
271
|
+
} else if (put) {
|
|
272
|
+
api(ctxid).put(request.put!, cb as never)
|
|
273
|
+
} else if (on) {
|
|
274
|
+
api(ctxid).on(request.on!, cb!, request._get, request._opt)
|
|
275
|
+
} else if (off) {
|
|
276
|
+
api(ctxid).off(cb as never)
|
|
277
|
+
}
|
|
278
|
+
} else if (get) {
|
|
279
|
+
cb!(node[item!])
|
|
280
|
+
} else if (put) {
|
|
281
|
+
id = utils.text.random()
|
|
282
|
+
node[item!] = utils.rel.ify(id)
|
|
283
|
+
const g = await graph(soul!, node as never, ctx.user, cb as never)
|
|
284
|
+
if (g === null) return
|
|
285
|
+
|
|
286
|
+
wire.put(g, err => {
|
|
287
|
+
if (err) {
|
|
288
|
+
(cb as (err: string) => void)(`error putting ${item} on ${soul}: ${err}`)
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
if (id) {
|
|
292
|
+
ctx.chain[i]!.soul = id
|
|
293
|
+
}
|
|
294
|
+
api(ctxid).put(request.put!, cb as never)
|
|
295
|
+
})
|
|
296
|
+
} else if (on) {
|
|
297
|
+
// Item is not a rel yet — watch the parent soul for when it
|
|
298
|
+
// becomes one, then retry chain resolution.
|
|
299
|
+
watchForRel(soul!, item!, ctx, i, request, cb!)
|
|
300
|
+
if (request._get) cb!(null)
|
|
301
|
+
} else if (off) {
|
|
302
|
+
if (cb) cb(null)
|
|
303
|
+
}
|
|
304
|
+
} else if (put) {
|
|
305
|
+
const id = utils.text.random()
|
|
306
|
+
if (!node) node = {} as never
|
|
307
|
+
node[item!] = utils.rel.ify(id)
|
|
308
|
+
const g = await graph(soul!, node as never, ctx.user, cb as never)
|
|
309
|
+
if (g === null) return
|
|
310
|
+
|
|
311
|
+
wire.put(g, err => {
|
|
312
|
+
if (err) {
|
|
313
|
+
(cb as (err: string) => void)(`error putting ${item} on ${soul}: ${err}`)
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
ctx.chain[i]!.soul = id
|
|
317
|
+
api(ctxid).put(request.put!, cb as never)
|
|
318
|
+
})
|
|
319
|
+
} else {
|
|
320
|
+
if (on) {
|
|
321
|
+
// Node doesn't exist yet — watch for it to appear.
|
|
322
|
+
watchForRel(soul!, item!, ctx, i, request, cb!)
|
|
323
|
+
if (request._get) cb!(null)
|
|
324
|
+
} else {
|
|
325
|
+
if (cb) cb(null)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
{ ...request._opt, secure: (typeof ctx.user === "boolean" ? ctx.user : !!ctx.user) || options.secure }
|
|
330
|
+
)
|
|
331
|
+
return false
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (get && ctx.chain[ctx.chain.length - 1]!.item !== null) {
|
|
335
|
+
ctx.chain.push({ item: null, soul: null })
|
|
336
|
+
api(ctxid).next(null as never, request.get as never, cb as never, request._opt)
|
|
337
|
+
return false
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return ctx.chain[ctx.chain.length - 1]!
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
get: function (
|
|
345
|
+
this: HolsterAPI,
|
|
346
|
+
key: string | null,
|
|
347
|
+
lex?: LexFilter | ((data: unknown) => void),
|
|
348
|
+
cb?: ((data: unknown) => void) | WireOptions,
|
|
349
|
+
_opt?: WireOptions
|
|
350
|
+
): HolsterAPI {
|
|
351
|
+
let lexFilter: LexFilter | null | undefined
|
|
352
|
+
let callback: ((data: unknown) => void) | undefined
|
|
353
|
+
let opts: WireOptions | undefined
|
|
354
|
+
|
|
355
|
+
if (typeof lex === "function") {
|
|
356
|
+
opts = cb as WireOptions
|
|
357
|
+
callback = lex
|
|
358
|
+
lexFilter = null
|
|
359
|
+
} else {
|
|
360
|
+
lexFilter = lex
|
|
361
|
+
callback = cb as (data: unknown) => void
|
|
362
|
+
opts = _opt
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (key === null || key === "" || key === "_") {
|
|
366
|
+
console.log("error please provide a key")
|
|
367
|
+
if (callback) callback(null)
|
|
368
|
+
return this
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (lexFilter && typeof callback !== "function") {
|
|
372
|
+
console.log("error lex requires a callback function")
|
|
373
|
+
return this
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
ctxid = utils.text.random()
|
|
377
|
+
allctx.set(ctxid, {
|
|
378
|
+
chain: [{ item: String(key), soul: "root" }],
|
|
379
|
+
cb: callback,
|
|
380
|
+
})
|
|
381
|
+
if (!callback) return api(ctxid)
|
|
382
|
+
|
|
383
|
+
const _done = done(ctxid)
|
|
384
|
+
const result = resolve({ get: lexFilter, _opt: opts }, _done)
|
|
385
|
+
if (result) get(lexFilter as never, result.soul!, _done, opts)
|
|
386
|
+
return this
|
|
387
|
+
} as HolsterAPI["get"],
|
|
388
|
+
|
|
389
|
+
next: function (
|
|
390
|
+
this: HolsterAPI,
|
|
391
|
+
key: string | null,
|
|
392
|
+
lex?: LexFilter | ((data: unknown) => void),
|
|
393
|
+
cb?: ((data: unknown) => void) | WireOptions,
|
|
394
|
+
_opt?: WireOptions
|
|
395
|
+
): HolsterAPI {
|
|
396
|
+
let lexFilter: LexFilter | null | undefined
|
|
397
|
+
let callback: ((data: unknown) => void) | undefined
|
|
398
|
+
let opts: WireOptions | undefined
|
|
399
|
+
|
|
400
|
+
if (typeof lex === "function") {
|
|
401
|
+
opts = cb as WireOptions
|
|
402
|
+
callback = lex
|
|
403
|
+
lexFilter = null
|
|
404
|
+
} else {
|
|
405
|
+
lexFilter = lex
|
|
406
|
+
callback = cb as (data: unknown) => void
|
|
407
|
+
opts = _opt
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const ack = (ctxid: string) => {
|
|
411
|
+
return (data: unknown): void => {
|
|
412
|
+
if (callback) {
|
|
413
|
+
callback(data)
|
|
414
|
+
} else {
|
|
415
|
+
done(ctxid)(data)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (!ctxid) {
|
|
421
|
+
console.log("error please provide a key using get(key)")
|
|
422
|
+
if (callback) callback(null)
|
|
423
|
+
return this
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const _ack = ack(ctxid)
|
|
427
|
+
if (key === "" || key === "_") {
|
|
428
|
+
_ack(null)
|
|
429
|
+
return this
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (lexFilter && typeof callback !== "function") {
|
|
433
|
+
console.log("error lex requires a callback function")
|
|
434
|
+
return this
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const ctx = allctx.get(ctxid)
|
|
438
|
+
if (!ctx) return this
|
|
439
|
+
|
|
440
|
+
if (callback && typeof ctx.cb === "undefined") {
|
|
441
|
+
ctx.cb = callback
|
|
442
|
+
callback = undefined
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (key !== null) ctx.chain.push({ item: String(key), soul: null })
|
|
446
|
+
if (!ctx.cb) return api(ctxid)
|
|
447
|
+
|
|
448
|
+
const result = resolve({ get: lexFilter, _opt: opts }, _ack)
|
|
449
|
+
if (result) get(lexFilter as never, result.soul!, _ack, opts)
|
|
450
|
+
return this
|
|
451
|
+
} as HolsterAPI["next"],
|
|
452
|
+
|
|
453
|
+
put: function (
|
|
454
|
+
data: GraphValue | Record<string, GraphValue>,
|
|
455
|
+
set?: boolean | ((data?: string | null) => void),
|
|
456
|
+
cb?: (data?: string | null) => void
|
|
457
|
+
): HolsterAPI | void {
|
|
458
|
+
let isSet = false
|
|
459
|
+
let callback: ((data?: string | null) => void) | undefined
|
|
460
|
+
|
|
461
|
+
if (typeof set === "function") {
|
|
462
|
+
callback = set
|
|
463
|
+
isSet = false
|
|
464
|
+
} else {
|
|
465
|
+
isSet = set || false
|
|
466
|
+
callback = cb
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const ack = (ctxid: string) => {
|
|
470
|
+
return (data?: string | null): void => {
|
|
471
|
+
if (callback) {
|
|
472
|
+
callback(data)
|
|
473
|
+
} else {
|
|
474
|
+
done(ctxid)(data)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (!ctxid) {
|
|
480
|
+
if (callback) callback("error please provide a key using get(key)")
|
|
481
|
+
return
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const ctx = allctx.get(ctxid!)
|
|
485
|
+
if (!ctx) return
|
|
486
|
+
|
|
487
|
+
if (!ctx.cb) {
|
|
488
|
+
if (!callback) return
|
|
489
|
+
ctx.cb = callback as never
|
|
490
|
+
callback = undefined
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (isSet) data = { [utils.text.random()]: data } as Record<string, GraphValue>
|
|
494
|
+
|
|
495
|
+
const _ack = ack(ctxid)
|
|
496
|
+
const result = check(data)
|
|
497
|
+
if (typeof result === "string") {
|
|
498
|
+
_ack(result)
|
|
499
|
+
return
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const resolved = resolve({ put: data }, _ack as never)
|
|
503
|
+
if (!resolved) return
|
|
504
|
+
|
|
505
|
+
const { item, soul } = resolved
|
|
506
|
+
if (!soul) return
|
|
507
|
+
|
|
508
|
+
if (result === true) {
|
|
509
|
+
wire.get(
|
|
510
|
+
{ "#": soul, ".": item! },
|
|
511
|
+
async msg => {
|
|
512
|
+
if (msg.err) {
|
|
513
|
+
_ack(`error getting ${soul}: ${msg.err}`)
|
|
514
|
+
return
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
let node = msg.put && msg.put[soul]
|
|
518
|
+
const current = node && node[item!]
|
|
519
|
+
const id = utils.rel.is(current as GraphValue)
|
|
520
|
+
if (!id) {
|
|
521
|
+
if (!node) node = {} as never
|
|
522
|
+
node[item!] = (isSet ? data : data) as GraphValue
|
|
523
|
+
const g = await graph(soul, node as never, ctx.user, _ack as never)
|
|
524
|
+
if (g === null) return
|
|
525
|
+
|
|
526
|
+
wire.put(g, _ack as never)
|
|
527
|
+
return
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
wire.get({ "#": id }, async msg => {
|
|
531
|
+
if (msg.err) {
|
|
532
|
+
_ack(`error getting ${id}: ${msg.err}`)
|
|
533
|
+
return
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (!msg.put || !msg.put[id]) {
|
|
537
|
+
_ack(`error ${id} not found`)
|
|
538
|
+
return
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
for (const key of Object.keys(msg.put[id]!)) {
|
|
542
|
+
if (
|
|
543
|
+
key === "_" ||
|
|
544
|
+
key === utils.userPublicKey ||
|
|
545
|
+
key === utils.userSignature
|
|
546
|
+
) {
|
|
547
|
+
continue
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const err = await new Promise<string | null | undefined>(res => {
|
|
551
|
+
const _ctxid = utils.text.random()
|
|
552
|
+
const chain: ChainItem[] = [{ item: key, soul: id }]
|
|
553
|
+
allctx.set(_ctxid, { chain: chain, user: ctx.user })
|
|
554
|
+
api(_ctxid).put(null!, res as never)
|
|
555
|
+
})
|
|
556
|
+
if (err) {
|
|
557
|
+
_ack(err)
|
|
558
|
+
return
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (!node) node = {} as never
|
|
562
|
+
node[item!] = data as GraphValue
|
|
563
|
+
const g = await graph(soul, node as never, ctx.user, _ack as never)
|
|
564
|
+
if (g === null) return
|
|
565
|
+
|
|
566
|
+
wire.put(g, _ack as never)
|
|
567
|
+
})
|
|
568
|
+
},
|
|
569
|
+
{ secure: (typeof ctx.user === "boolean" ? ctx.user : !!ctx.user) || options.secure }
|
|
570
|
+
)
|
|
571
|
+
return
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const attemptRead = (retries = 0): void => {
|
|
575
|
+
wire.get(
|
|
576
|
+
{ "#": soul, ".": item! },
|
|
577
|
+
async msg => {
|
|
578
|
+
if (msg.err) {
|
|
579
|
+
_ack(`error getting ${soul}.${item}: ${msg.err}`)
|
|
580
|
+
return
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
let node = msg.put && msg.put[soul]
|
|
584
|
+
const current = node && node[item!]
|
|
585
|
+
|
|
586
|
+
if (!current && retries < 5) {
|
|
587
|
+
await new Promise(r => setTimeout(r, 50))
|
|
588
|
+
return attemptRead(retries + 1)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const id = utils.rel.is(current as GraphValue)
|
|
592
|
+
if (!id) {
|
|
593
|
+
if (!node) node = {} as never
|
|
594
|
+
node[item!] = utils.rel.ify(utils.text.random())
|
|
595
|
+
const g = await graph(soul, node as never, ctx.user, _ack as never)
|
|
596
|
+
if (g === null) return
|
|
597
|
+
|
|
598
|
+
wire.put(g, err => {
|
|
599
|
+
if (err) {
|
|
600
|
+
_ack(`error putting ${item} on ${soul}: ${err}`)
|
|
601
|
+
} else {
|
|
602
|
+
const _ctxid = utils.text.random()
|
|
603
|
+
const chain: ChainItem[] = [{ item: item!, soul: soul }]
|
|
604
|
+
allctx.set(_ctxid, {
|
|
605
|
+
chain: chain,
|
|
606
|
+
user: ctx.user,
|
|
607
|
+
cb: ctx.cb,
|
|
608
|
+
})
|
|
609
|
+
api(_ctxid).put(data)
|
|
610
|
+
}
|
|
611
|
+
})
|
|
612
|
+
return
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const update: string[] = []
|
|
616
|
+
for (const key of result as string[]) {
|
|
617
|
+
const err = await new Promise<string | null | undefined>(res => {
|
|
618
|
+
if (
|
|
619
|
+
utils.obj.is((data as Record<string, unknown>)[key]) &&
|
|
620
|
+
!utils.rel.is((data as Record<string, GraphValue>)[key])
|
|
621
|
+
) {
|
|
622
|
+
const _ctxid = utils.text.random()
|
|
623
|
+
const chain: ChainItem[] = [{ item: key, soul: id }]
|
|
624
|
+
allctx.set(_ctxid, { chain: chain, user: ctx.user })
|
|
625
|
+
api(_ctxid).put((data as Record<string, GraphValue>)[key]!, res as never)
|
|
626
|
+
} else {
|
|
627
|
+
update.push(key)
|
|
628
|
+
res(null)
|
|
629
|
+
}
|
|
630
|
+
})
|
|
631
|
+
if (err) {
|
|
632
|
+
_ack(err)
|
|
633
|
+
return
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (update.length === 0) {
|
|
638
|
+
_ack(null)
|
|
639
|
+
return
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Only write the primitive update keys. Rels created by the
|
|
643
|
+
// recursive puts above are already in the in-memory graph.
|
|
644
|
+
const updateNode: Record<string, GraphValue> = {}
|
|
645
|
+
update.forEach(key => {
|
|
646
|
+
updateNode[key] = (data as Record<string, GraphValue>)[key]!
|
|
647
|
+
})
|
|
648
|
+
const g = await graph(id, updateNode as never, ctx.user, _ack as never)
|
|
649
|
+
if (g === null) return
|
|
650
|
+
|
|
651
|
+
wire.put(g, _ack as never)
|
|
652
|
+
},
|
|
653
|
+
{ secure: (typeof ctx.user === "boolean" ? ctx.user : !!ctx.user) || options.secure }
|
|
654
|
+
)
|
|
655
|
+
}
|
|
656
|
+
attemptRead()
|
|
657
|
+
},
|
|
658
|
+
|
|
659
|
+
on: function (
|
|
660
|
+
lex: LexWithDot | ((data: unknown) => void),
|
|
661
|
+
cb?: ((data: unknown) => void) | boolean,
|
|
662
|
+
_get?: boolean,
|
|
663
|
+
_opt?: WireOptions
|
|
664
|
+
): void {
|
|
665
|
+
let lexFilter: LexFilter | undefined
|
|
666
|
+
let callback: (data: unknown) => void
|
|
667
|
+
let opts: WireOptions | undefined
|
|
668
|
+
|
|
669
|
+
if (typeof lex === "function") {
|
|
670
|
+
opts = _get as never
|
|
671
|
+
_get = cb as never
|
|
672
|
+
callback = lex
|
|
673
|
+
lexFilter = null
|
|
674
|
+
} else {
|
|
675
|
+
lexFilter = lex as LexFilter
|
|
676
|
+
callback = (typeof cb === "function" ? cb : undefined)!
|
|
677
|
+
opts = _opt
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (typeof callback !== "function") {
|
|
681
|
+
console.log("error on() requires a callback function")
|
|
682
|
+
return
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (!ctxid) {
|
|
686
|
+
console.log("error please provide a key using get(key)")
|
|
687
|
+
callback(null)
|
|
688
|
+
return
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const resolved = resolve({ on: lexFilter, _get: _get, _opt: opts }, callback)
|
|
692
|
+
if (!resolved) return
|
|
693
|
+
|
|
694
|
+
const { item, soul } = resolved
|
|
695
|
+
if (!soul) return
|
|
696
|
+
|
|
697
|
+
const ctx = allctx.get(ctxid)
|
|
698
|
+
|
|
699
|
+
allctx.set(ctxid, {
|
|
700
|
+
chain: [{ item: item, soul: soul }],
|
|
701
|
+
on: true,
|
|
702
|
+
user: ctx ? ctx.user : null,
|
|
703
|
+
})
|
|
704
|
+
|
|
705
|
+
// Shared retry state for this on() subscription.
|
|
706
|
+
let retryCount = 0
|
|
707
|
+
const maxRetries = 5
|
|
708
|
+
const retryDelay = 1000
|
|
709
|
+
let retryTimer: ReturnType<typeof setTimeout> | null = null
|
|
710
|
+
|
|
711
|
+
map.set(callback, () => {
|
|
712
|
+
// Bail out if off() has already cleaned up this context — the mapped
|
|
713
|
+
// callback can fire twice (once from wire.on and once from the _get
|
|
714
|
+
// immediate-read path) and the second call after async context cleanup
|
|
715
|
+
// would crash in resolve(). Matches the guard in watchForRel.
|
|
716
|
+
if (!allctx.has(ctxid!)) return
|
|
717
|
+
// Cancel any pending retry — the persistent listener firing means we
|
|
718
|
+
// have an update and should not call callback twice.
|
|
719
|
+
clearTimeout(retryTimer!)
|
|
720
|
+
retryTimer = null
|
|
721
|
+
wire.get(
|
|
722
|
+
{ "#": soul, ".": item! },
|
|
723
|
+
msg => {
|
|
724
|
+
const node = msg.put && msg.put[soul]
|
|
725
|
+
const current = node && node[item!]
|
|
726
|
+
const id = utils.rel.is(current as GraphValue)
|
|
727
|
+
if (id) {
|
|
728
|
+
const attemptRead = async (retries = 0): Promise<void> => {
|
|
729
|
+
const data = await new Promise<unknown>(res => {
|
|
730
|
+
const _ctxid = utils.text.random()
|
|
731
|
+
allctx.set(_ctxid, {
|
|
732
|
+
chain: [{ item: null, soul: id }],
|
|
733
|
+
user: ctx ? ctx.user : null,
|
|
734
|
+
})
|
|
735
|
+
api(_ctxid).next(null as never, res as never, (retries === 0 ? utils.obj.put(opts, "fast", true) : opts) as never)
|
|
736
|
+
})
|
|
737
|
+
if (data !== null || retries >= 5) {
|
|
738
|
+
retryCount = 0
|
|
739
|
+
callback(data)
|
|
740
|
+
} else {
|
|
741
|
+
await new Promise(resolve => setTimeout(resolve, 50))
|
|
742
|
+
attemptRead(retries + 1)
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
attemptRead()
|
|
746
|
+
} else {
|
|
747
|
+
const nodeExists = !!node
|
|
748
|
+
if (nodeExists) {
|
|
749
|
+
retryCount = 0
|
|
750
|
+
callback(current !== undefined ? current : null)
|
|
751
|
+
} else if (retryCount < maxRetries) {
|
|
752
|
+
// No data yet — keep polling until we get a response or
|
|
753
|
+
// exhaust retries. on() with _get can return null on a cache
|
|
754
|
+
// miss and never fire again if the data doesn't change while
|
|
755
|
+
// the listener is attached, so we must poll for initial load.
|
|
756
|
+
const secureOpt = { ...opts, secure: ctx ? ((typeof ctx.user === "boolean" ? ctx.user : !!ctx.user) || options.secure) : options.secure }
|
|
757
|
+
const retry = (): void => {
|
|
758
|
+
const delay = Math.min(retryDelay * Math.pow(2, retryCount), 30000)
|
|
759
|
+
retryCount++
|
|
760
|
+
retryTimer = setTimeout(() => {
|
|
761
|
+
wire.get(
|
|
762
|
+
{ "#": soul, ".": item! },
|
|
763
|
+
retryMsg => {
|
|
764
|
+
const retryNode = retryMsg.put && retryMsg.put[soul]
|
|
765
|
+
if (retryNode) {
|
|
766
|
+
retryCount = 0
|
|
767
|
+
retryTimer = null
|
|
768
|
+
const retryValue = retryNode[item!]
|
|
769
|
+
callback(retryValue !== undefined ? retryValue : null)
|
|
770
|
+
} else if (retryCount < maxRetries) {
|
|
771
|
+
retry()
|
|
772
|
+
} else {
|
|
773
|
+
retryTimer = null
|
|
774
|
+
callback(null)
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
secureOpt,
|
|
778
|
+
)
|
|
779
|
+
}, delay)
|
|
780
|
+
}
|
|
781
|
+
retry()
|
|
782
|
+
} else {
|
|
783
|
+
callback(null)
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
},
|
|
787
|
+
{ ...opts, secure: ctx ? ((typeof ctx.user === "boolean" ? ctx.user : !!ctx.user) || options.secure) : options.secure }
|
|
788
|
+
)
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
let initialLex: { "#": string; "."?: string | number } = lexFilter
|
|
792
|
+
? (utils.obj.put(lexFilter as never, "#", soul) as { "#": string; "."?: string | number })
|
|
793
|
+
: { "#": soul, ".": item! }
|
|
794
|
+
if (initialLex["."] != null && typeof initialLex["."] === "number") {
|
|
795
|
+
initialLex = { ...initialLex, ".": String(initialLex["."]) }
|
|
796
|
+
}
|
|
797
|
+
wire.on(initialLex as never, map.get(callback)!, false, opts)
|
|
798
|
+
|
|
799
|
+
wire.get(
|
|
800
|
+
{ "#": soul, ".": item! },
|
|
801
|
+
msg => {
|
|
802
|
+
if (msg.err) {
|
|
803
|
+
console.log(`error getting ${soul}.${item}: ${msg.err}`)
|
|
804
|
+
return
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const current = msg.put && msg.put[soul] && msg.put[soul]![item!]
|
|
808
|
+
const id = utils.rel.is(current as GraphValue)
|
|
809
|
+
if (id) {
|
|
810
|
+
wire.off(initialLex as never, map.get(callback)!)
|
|
811
|
+
const relLex = (lexFilter
|
|
812
|
+
? utils.obj.put(initialLex as never, "#", id)
|
|
813
|
+
: { "#": id, ".": null }) as Lex
|
|
814
|
+
wire.on(relLex, map.get(callback)!, false, opts)
|
|
815
|
+
if (_get) map.get(callback)!()
|
|
816
|
+
} else if (_get) {
|
|
817
|
+
// Not a rel, but _get was requested, so trigger callback.
|
|
818
|
+
map.get(callback)!()
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
{ ...opts, secure: ctx ? ((typeof ctx.user === "boolean" ? ctx.user : !!ctx.user) || options.secure) : options.secure }
|
|
822
|
+
)
|
|
823
|
+
},
|
|
824
|
+
|
|
825
|
+
off: function (cb?: (data: unknown) => void): HolsterAPI | void {
|
|
826
|
+
if (!ctxid) {
|
|
827
|
+
console.log("error please provide a key using get(key)")
|
|
828
|
+
if (cb) cb(null)
|
|
829
|
+
return
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const resolved = resolve({ off: true }, cb)
|
|
833
|
+
if (!resolved) return
|
|
834
|
+
|
|
835
|
+
const { item, soul } = resolved
|
|
836
|
+
if (!soul) return
|
|
837
|
+
|
|
838
|
+
wire.off({ "#": soul } as never, map.get(cb!)!)
|
|
839
|
+
|
|
840
|
+
wire.get({ "#": soul, ".": item! }, msg => {
|
|
841
|
+
if (msg.err) {
|
|
842
|
+
map.delete(cb!)
|
|
843
|
+
allctx.delete(ctxid!)
|
|
844
|
+
return
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const current = msg.put && msg.put[soul] && msg.put[soul]![item!]
|
|
848
|
+
const id = utils.rel.is(current as GraphValue)
|
|
849
|
+
if (id) wire.off({ "#": id } as never, map.get(cb!)!)
|
|
850
|
+
map.delete(cb!)
|
|
851
|
+
allctx.delete(ctxid!)
|
|
852
|
+
})
|
|
853
|
+
},
|
|
854
|
+
|
|
855
|
+
user: () => {
|
|
856
|
+
if (!(user as { get?: unknown }).get) {
|
|
857
|
+
Object.assign(user, api())
|
|
858
|
+
;(user as UserInterface & HolsterAPI).get = function (
|
|
859
|
+
this: HolsterAPI,
|
|
860
|
+
keys: string | string[],
|
|
861
|
+
lex?: LexFilter | ((data: unknown) => void),
|
|
862
|
+
cb?: ((data: unknown) => void) | WireOptions,
|
|
863
|
+
_opt?: WireOptions
|
|
864
|
+
): HolsterAPI {
|
|
865
|
+
let lexFilter: LexFilter | null | undefined
|
|
866
|
+
let callback: ((data: unknown) => void) | undefined
|
|
867
|
+
let opts: WireOptions | undefined
|
|
868
|
+
|
|
869
|
+
if (typeof lex === "function") {
|
|
870
|
+
opts = cb as WireOptions
|
|
871
|
+
callback = lex
|
|
872
|
+
lexFilter = null
|
|
873
|
+
} else {
|
|
874
|
+
lexFilter = lex
|
|
875
|
+
callback = cb as (data: unknown) => void
|
|
876
|
+
opts = _opt
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
let pub: string | null = null
|
|
880
|
+
let key: string | null = null
|
|
881
|
+
if (user.is) pub = user.is.pub
|
|
882
|
+
if (typeof keys === "string") {
|
|
883
|
+
key = keys
|
|
884
|
+
} else if (keys instanceof Array) {
|
|
885
|
+
if (keys.length === 2 && keys[0] && keys[1]) {
|
|
886
|
+
pub = keys[0]
|
|
887
|
+
key = keys[1]
|
|
888
|
+
} else if (keys.length === 1 && keys[0]) {
|
|
889
|
+
key = keys[0]
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if (!pub) {
|
|
893
|
+
console.log("error please log in or provide a public key")
|
|
894
|
+
if (callback) callback(null)
|
|
895
|
+
return this
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (key === null || key === "" || key === "_") {
|
|
899
|
+
console.log("error please provide a key")
|
|
900
|
+
if (callback) callback(null)
|
|
901
|
+
return this
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
if (lexFilter && !callback) {
|
|
905
|
+
console.log("error lex requires a callback function")
|
|
906
|
+
return this
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
ctxid = utils.text.random()
|
|
910
|
+
const chain: ChainItem[] = [{ item: String(key), soul: "~" + pub }]
|
|
911
|
+
allctx.set(ctxid, { chain: chain, user: user.is, cb: callback as never })
|
|
912
|
+
if (!callback) return api(ctxid)
|
|
913
|
+
|
|
914
|
+
const _done = done(ctxid)
|
|
915
|
+
const resolved = resolve({ get: lexFilter, _opt: opts }, _done)
|
|
916
|
+
if (resolved) get(lexFilter as never, resolved.soul!, _done, opts)
|
|
917
|
+
return this
|
|
918
|
+
} as never
|
|
919
|
+
}
|
|
920
|
+
return user as UserInterface & HolsterAPI
|
|
921
|
+
},
|
|
922
|
+
|
|
923
|
+
wire: wire,
|
|
924
|
+
SEA: SEA,
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return api()
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
export default Holster
|
|
931
|
+
|