@mblaney/holster 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/node.js.yml +31 -0
- package/LICENSE.md +55 -0
- package/README.md +24 -0
- package/build/holster.js +4 -0
- package/build/holster.js.map +7 -0
- package/examples/index.html +33 -0
- package/examples/index.js +49 -0
- package/examples/user.html +31 -0
- package/examples/user.js +88 -0
- package/examples/wire.html +27 -0
- package/examples/wire.js +55 -0
- package/package.json +44 -0
- package/src/array.js +34 -0
- package/src/buffer.js +83 -0
- package/src/dup.js +23 -0
- package/src/get.js +19 -0
- package/src/ham.js +151 -0
- package/src/holster.js +502 -0
- package/src/index.js +21 -0
- package/src/radisk.js +438 -0
- package/src/radix.js +106 -0
- package/src/sea-utils.js +65 -0
- package/src/sea.js +224 -0
- package/src/store.js +225 -0
- package/src/user.js +330 -0
- package/src/utils.js +124 -0
- package/src/wire.js +290 -0
- package/test/holster.get.test.js +60 -0
- package/test/holster.lex.test.js +196 -0
- package/test/holster.off.test.js +53 -0
- package/test/holster.on.test.js +168 -0
- package/test/holster.put.test.js +278 -0
- package/test/holster.secure.test.js +210 -0
- package/test/holster.user.get.test.js +86 -0
- package/test/holster.user.lex.test.js +148 -0
- package/test/holster.user.off.test.js +72 -0
- package/test/holster.user.on.test.js +185 -0
- package/test/holster.user.put.test.js +381 -0
- package/test/kitty/! +9 -0
- package/test/names.js +225 -0
- package/test/radisk.test.js +191 -0
- package/test/radix.test.js +117 -0
- package/test/sea.test.js +284 -0
- package/test/store.test.js +316 -0
- package/test/user.test.js +173 -0
- package/test/wire.test.js +159 -0
package/src/holster.js
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import * as utils from "./utils.js"
|
|
2
|
+
import Wire from "./wire.js"
|
|
3
|
+
import User from "./user.js"
|
|
4
|
+
import SEA from "./sea.js"
|
|
5
|
+
|
|
6
|
+
const Holster = opt => {
|
|
7
|
+
const wire = Wire(opt)
|
|
8
|
+
const user = User(null, wire)
|
|
9
|
+
// Map callbacks since the user's callback is not passed to wire.on.
|
|
10
|
+
const map = new Map()
|
|
11
|
+
// Allow concurrent calls to the api by storing each context.
|
|
12
|
+
const allctx = new Map()
|
|
13
|
+
|
|
14
|
+
const ok = data => {
|
|
15
|
+
return (
|
|
16
|
+
data === null ||
|
|
17
|
+
data === true ||
|
|
18
|
+
data === false ||
|
|
19
|
+
typeof data === "string" ||
|
|
20
|
+
utils.rel.is(data) ||
|
|
21
|
+
utils.num.is(data)
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// check returns true if data is ok to add to a graph, an error string if
|
|
26
|
+
// the data can't be converted, and the keys on the data object otherwise.
|
|
27
|
+
const check = data => {
|
|
28
|
+
if (ok(data)) return true
|
|
29
|
+
|
|
30
|
+
if (utils.obj.is(data)) {
|
|
31
|
+
const keys = []
|
|
32
|
+
for (const [key, value] of Object.entries(data)) {
|
|
33
|
+
if (key === "_") {
|
|
34
|
+
return "error underscore cannot be used as an item name"
|
|
35
|
+
}
|
|
36
|
+
if (utils.obj.is(value) || ok(value)) {
|
|
37
|
+
keys.push(key)
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
return `error {${key}:${value}} cannot be converted to graph`
|
|
41
|
+
}
|
|
42
|
+
if (keys.length !== 0) return keys
|
|
43
|
+
}
|
|
44
|
+
return `error ${data} cannot be converted to a graph`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const api = ctxid => {
|
|
48
|
+
const get = (lex, soul, ack) => {
|
|
49
|
+
wire.get(utils.obj.put(lex, "#", soul), async msg => {
|
|
50
|
+
if (msg.err) console.log(msg.err)
|
|
51
|
+
if (msg.put && msg.put[soul]) {
|
|
52
|
+
delete msg.put[soul]._
|
|
53
|
+
delete msg.put[soul][utils.userPublicKey]
|
|
54
|
+
// Resolve any rels on the node before returning to the user.
|
|
55
|
+
for (const key of Object.keys(msg.put[soul])) {
|
|
56
|
+
const id = utils.rel.is(msg.put[soul][key])
|
|
57
|
+
if (id) {
|
|
58
|
+
const data = await new Promise(res => {
|
|
59
|
+
const _ctxid = utils.text.random()
|
|
60
|
+
allctx.set(_ctxid, {chain: [{item: null, soul: id}]})
|
|
61
|
+
api(_ctxid).next(null, res)
|
|
62
|
+
})
|
|
63
|
+
msg.put[soul][key] = data
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
ack(msg.put[soul])
|
|
67
|
+
} else {
|
|
68
|
+
// No data callback.
|
|
69
|
+
ack(null)
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const graph = async (soul, data, cb) => {
|
|
75
|
+
if (!cb) cb = console.log
|
|
76
|
+
|
|
77
|
+
if (!user.is) {
|
|
78
|
+
if (opt.secure) {
|
|
79
|
+
cb(`error putting data on ${soul}: user required in secure mode`)
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return utils.graph(soul, data)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!user.is) {
|
|
87
|
+
cb(`error putting data on ${soul}: user not authenticated`)
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const signed = await SEA.sign(data, user.is)
|
|
92
|
+
return utils.graph(soul, signed.m, signed.s, user.is.pub)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const done = data => {
|
|
96
|
+
const ctx = allctx.get(ctxid)
|
|
97
|
+
if (ctx && typeof ctx.cb !== "undefined") ctx.cb(data)
|
|
98
|
+
else if (data) console.log(data)
|
|
99
|
+
// A context updated by "on" should only be removed by "off".
|
|
100
|
+
if (ctx && !ctx.on) allctx.delete(ctxid)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const resolve = (request, cb) => {
|
|
104
|
+
const get = request && typeof request.get !== "undefined"
|
|
105
|
+
const put = request && typeof request.put !== "undefined"
|
|
106
|
+
const on = request && typeof request.on !== "undefined"
|
|
107
|
+
const off = request && typeof request.off !== "undefined"
|
|
108
|
+
|
|
109
|
+
let found = false
|
|
110
|
+
const ctx = allctx.get(ctxid)
|
|
111
|
+
for (var i = 1; i < ctx.chain.length; i++) {
|
|
112
|
+
if (ctx.chain[i].soul !== null) continue
|
|
113
|
+
|
|
114
|
+
found = true
|
|
115
|
+
break
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (found) {
|
|
119
|
+
// Found a soul that needs resolving, need the previous context
|
|
120
|
+
// (ie the parent node) to find a soul relation for it.
|
|
121
|
+
const {item, soul} = ctx.chain[i - 1]
|
|
122
|
+
wire.get({"#": soul, ".": item}, async msg => {
|
|
123
|
+
if (msg.err) {
|
|
124
|
+
console.log(`error getting ${item} on ${soul}: ${msg.err}`)
|
|
125
|
+
if (cb) cb(null)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const node = msg.put && msg.put[soul]
|
|
130
|
+
if (node && typeof node[item] !== "undefined") {
|
|
131
|
+
let id = utils.rel.is(node[item])
|
|
132
|
+
if (id) {
|
|
133
|
+
ctx.chain[i].soul = id
|
|
134
|
+
// Not sure why the map needs to be set rather than just ctx?
|
|
135
|
+
allctx.set(ctxid, {chain: ctx.chain, cb: ctx.cb})
|
|
136
|
+
// Call api again using the updated context.
|
|
137
|
+
if (get) api(ctxid).next(null, request.get, cb)
|
|
138
|
+
else if (put) api(ctxid).put(request.put, cb)
|
|
139
|
+
else if (on) api(ctxid).on(request.on, cb)
|
|
140
|
+
else if (off) api(ctxid).off(cb)
|
|
141
|
+
} else if (get) {
|
|
142
|
+
// Request was not for a node, return a property on the current
|
|
143
|
+
// soul.
|
|
144
|
+
cb(node[item])
|
|
145
|
+
} else if (put) {
|
|
146
|
+
// Request was chained before put, so rel doesn't exist yet.
|
|
147
|
+
id = utils.text.random()
|
|
148
|
+
const rel = {[item]: utils.rel.ify(id)}
|
|
149
|
+
const g = await graph(soul, rel, cb)
|
|
150
|
+
if (g === null) return
|
|
151
|
+
|
|
152
|
+
wire.put(g, err => {
|
|
153
|
+
if (err) {
|
|
154
|
+
cb(`error putting ${item} on ${soul}: ${err}`)
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
ctx.chain[i].soul = id
|
|
159
|
+
api(ctxid).put(request.put, cb)
|
|
160
|
+
})
|
|
161
|
+
} else if (on) {
|
|
162
|
+
console.log(`error resolving on for ${item} on ${soul}`)
|
|
163
|
+
cb(null)
|
|
164
|
+
} else if (off) {
|
|
165
|
+
console.log(`error resolving off for ${item} on ${soul}`)
|
|
166
|
+
if (cb) cb(null)
|
|
167
|
+
}
|
|
168
|
+
} else if (put) {
|
|
169
|
+
cb(`error ${item} not found on ${soul}`)
|
|
170
|
+
} else {
|
|
171
|
+
console.log(`error ${item} not found on ${soul}`)
|
|
172
|
+
if (cb) cb(null)
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
// Callback has been passed to next soul lookup or called above, so
|
|
176
|
+
// return false as the calling code should not continue.
|
|
177
|
+
return false
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (get && ctx.chain[ctx.chain.length - 1].item !== null) {
|
|
181
|
+
// The context has been resolved but it does not include the requested
|
|
182
|
+
// node, which requires one more lookup.
|
|
183
|
+
ctx.chain.push({item: null, soul: null})
|
|
184
|
+
api(ctxid).next(null, request.get, cb)
|
|
185
|
+
return false
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Return the last context, ie the soul required by the calling code.
|
|
189
|
+
return ctx.chain[ctx.chain.length - 1]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
get: (key, lex, cb) => {
|
|
194
|
+
if (typeof lex === "function") {
|
|
195
|
+
cb = lex
|
|
196
|
+
lex = null
|
|
197
|
+
}
|
|
198
|
+
if (key === null || key === "" || key === "_") {
|
|
199
|
+
if (cb) cb(null)
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// lex requires a callback as it's not included in the chain below.
|
|
204
|
+
if (lex && !cb) {
|
|
205
|
+
console.log("lex requires a callback function")
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Top level keys are added to a root node so their values don't need
|
|
210
|
+
// to be objects, the user's public key is used as the root node when
|
|
211
|
+
// user.ctx is set.
|
|
212
|
+
const root = user.ctx ? user.ctx : "root"
|
|
213
|
+
|
|
214
|
+
ctxid = utils.text.random()
|
|
215
|
+
allctx.set(ctxid, {chain: [{item: key, soul: root}], cb: cb})
|
|
216
|
+
if (!cb) return api(ctxid)
|
|
217
|
+
|
|
218
|
+
// When there's a callback need to resolve the context first.
|
|
219
|
+
const {soul} = resolve({get: lex}, done)
|
|
220
|
+
if (soul) get(lex, soul, done)
|
|
221
|
+
},
|
|
222
|
+
next: (key, lex, cb) => {
|
|
223
|
+
const ack = data => {
|
|
224
|
+
cb ? cb(data) : done(data)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (typeof lex === "function") {
|
|
228
|
+
cb = lex
|
|
229
|
+
lex = null
|
|
230
|
+
}
|
|
231
|
+
if (!ctxid) {
|
|
232
|
+
console.log("please provide a key using get(key)")
|
|
233
|
+
ack(null)
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (key === "" || key === "_") {
|
|
238
|
+
ack(null)
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// lex requires a callback as it's not included in the chain below.
|
|
243
|
+
if (lex && !cb) {
|
|
244
|
+
console.log("lex requires a callback function")
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const ctx = allctx.get(ctxid)
|
|
249
|
+
// ctx already removed by another chained callback is ok?
|
|
250
|
+
if (!ctx) return
|
|
251
|
+
|
|
252
|
+
if (cb && typeof ctx.cb === "undefined") {
|
|
253
|
+
// This (and ack) allows nested objects to set their own callbacks.
|
|
254
|
+
ctx.cb = cb
|
|
255
|
+
cb = null
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Push the key to the context as it needs a soul lookup.
|
|
259
|
+
// (null is used to call the api with updated context)
|
|
260
|
+
if (key !== null) ctx.chain.push({item: key, soul: null})
|
|
261
|
+
if (!ctx.cb) return api(ctxid)
|
|
262
|
+
|
|
263
|
+
// When there's a callback need to resolve the context first.
|
|
264
|
+
const {soul} = resolve({get: lex}, ack)
|
|
265
|
+
if (soul) get(lex, soul, ack)
|
|
266
|
+
},
|
|
267
|
+
put: (data, cb) => {
|
|
268
|
+
const ack = err => {
|
|
269
|
+
cb ? cb(err) : done(err)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!ctxid) {
|
|
273
|
+
ack("please provide a key using get(key)")
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const ctx = allctx.get(ctxid)
|
|
278
|
+
// ctx already removed by another chained callback is ok?
|
|
279
|
+
if (!ctx) return
|
|
280
|
+
|
|
281
|
+
if (!ctx.cb) {
|
|
282
|
+
if (!cb) return
|
|
283
|
+
|
|
284
|
+
// This (and ack) allows nested objects to set their own callbacks.
|
|
285
|
+
ctx.cb = cb
|
|
286
|
+
cb = null
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const result = check(data)
|
|
290
|
+
if (typeof result === "string") {
|
|
291
|
+
// All strings returned from check are errors, cannot continue.
|
|
292
|
+
ack(result)
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Resolve the current context before putting data.
|
|
297
|
+
const {item, soul} = resolve({put: data}, ack)
|
|
298
|
+
if (!soul) return
|
|
299
|
+
|
|
300
|
+
if (result === true) {
|
|
301
|
+
// When result is true data is a property to put on the current soul.
|
|
302
|
+
// Need to check if item is a rel and also set the node to null. (This
|
|
303
|
+
// applies for any update from a rel to a property, not just null.)
|
|
304
|
+
wire.get({"#": soul, ".": item}, async msg => {
|
|
305
|
+
if (msg.err) {
|
|
306
|
+
console.log(`error getting ${soul}: ${msg.err}`)
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const current = msg.put && msg.put[soul] && msg.put[soul][item]
|
|
311
|
+
const id = utils.rel.is(current)
|
|
312
|
+
if (!id) {
|
|
313
|
+
// Not a rel, can just put the data.
|
|
314
|
+
const g = await graph(soul, {[item]: data}, ack)
|
|
315
|
+
if (g === null) return
|
|
316
|
+
|
|
317
|
+
wire.put(g, ack)
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
wire.get({"#": id}, async msg => {
|
|
322
|
+
if (msg.err) {
|
|
323
|
+
console.log(`error getting ${id}: ${msg.err}`)
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (!msg.put || !msg.put[id]) {
|
|
328
|
+
console.log(`error ${id} not found`)
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
delete msg.put[id]._
|
|
333
|
+
// null each of the properties on the node before putting data.
|
|
334
|
+
for (const key of Object.keys(msg.put[id])) {
|
|
335
|
+
if (key === utils.userPublicKey) continue
|
|
336
|
+
|
|
337
|
+
const err = await new Promise(res => {
|
|
338
|
+
const _ctxid = utils.text.random()
|
|
339
|
+
allctx.set(_ctxid, {chain: [{item: key, soul: id}]})
|
|
340
|
+
api(_ctxid).put(null, res)
|
|
341
|
+
})
|
|
342
|
+
if (err) {
|
|
343
|
+
ack(err)
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const g = await graph(soul, {[item]: data}, ack)
|
|
348
|
+
if (g === null) return
|
|
349
|
+
|
|
350
|
+
wire.put(g, ack)
|
|
351
|
+
})
|
|
352
|
+
})
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Otherwise put the data using the keys returned in result.
|
|
357
|
+
// Need to check if a rel has already been added on the current node.
|
|
358
|
+
wire.get({"#": soul, ".": item}, async msg => {
|
|
359
|
+
if (msg.err) {
|
|
360
|
+
ack(`error getting ${soul}.${item}: ${msg.err}`)
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const current = msg.put && msg.put[soul] && msg.put[soul][item]
|
|
365
|
+
const id = utils.rel.is(current)
|
|
366
|
+
if (!id) {
|
|
367
|
+
// The current rel doesn't exist, so add it first.
|
|
368
|
+
const rel = {[item]: utils.rel.ify(utils.text.random())}
|
|
369
|
+
const g = await graph(soul, rel, ack)
|
|
370
|
+
if (g === null) return
|
|
371
|
+
|
|
372
|
+
wire.put(g, err => {
|
|
373
|
+
if (err) {
|
|
374
|
+
ack(`error putting ${item} on ${soul}: ${err}`)
|
|
375
|
+
} else {
|
|
376
|
+
const _ctxid = utils.text.random()
|
|
377
|
+
const chain = [{item: item, soul: soul}]
|
|
378
|
+
// Pass the previous context's callback on here.
|
|
379
|
+
allctx.set(_ctxid, {chain: chain, cb: ctx.cb})
|
|
380
|
+
api(_ctxid).put(data)
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let put = false
|
|
387
|
+
const update = {}
|
|
388
|
+
for (const key of result) {
|
|
389
|
+
const err = await new Promise(res => {
|
|
390
|
+
if (utils.obj.is(data[key])) {
|
|
391
|
+
// Use the current rel as the context for nested objects.
|
|
392
|
+
const _ctxid = utils.text.random()
|
|
393
|
+
allctx.set(_ctxid, {chain: [{item: key, soul: id}]})
|
|
394
|
+
api(_ctxid).put(data[key], res)
|
|
395
|
+
} else {
|
|
396
|
+
put = true
|
|
397
|
+
// Group other properties into one update.
|
|
398
|
+
update[key] = data[key]
|
|
399
|
+
res(null)
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
if (err) {
|
|
403
|
+
ack(err)
|
|
404
|
+
return
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (put) {
|
|
408
|
+
const g = await graph(id, update, ack)
|
|
409
|
+
if (g === null) return
|
|
410
|
+
|
|
411
|
+
wire.put(g, ack)
|
|
412
|
+
} else {
|
|
413
|
+
// Callback on behalf of nested objects.
|
|
414
|
+
ack(null)
|
|
415
|
+
}
|
|
416
|
+
})
|
|
417
|
+
},
|
|
418
|
+
on: (lex, cb) => {
|
|
419
|
+
if (typeof lex === "function") {
|
|
420
|
+
cb = lex
|
|
421
|
+
lex = null
|
|
422
|
+
}
|
|
423
|
+
if (!cb) return
|
|
424
|
+
|
|
425
|
+
if (!ctxid) {
|
|
426
|
+
console.log("please provide a key using get(key)")
|
|
427
|
+
cb(null)
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Resolve the current context before adding event listener.
|
|
432
|
+
const {item, soul} = resolve({on: lex}, cb)
|
|
433
|
+
if (!soul) return
|
|
434
|
+
|
|
435
|
+
// Flag that this context is set from on and shouldn't be removed.
|
|
436
|
+
allctx.set(ctxid, {chain: [{item: item, soul: soul}], on: true})
|
|
437
|
+
// Map the user's callback because it can also be passed to off,
|
|
438
|
+
// so need a reference to it to compare them.
|
|
439
|
+
map.set(cb, () => api(ctxid).next(null, cb))
|
|
440
|
+
// Check if item is a rel and add event listener for the node.
|
|
441
|
+
wire.get({"#": soul, ".": item}, msg => {
|
|
442
|
+
if (msg.err) {
|
|
443
|
+
console.log(`error getting ${soul}.${item}: ${msg.err}`)
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const current = msg.put && msg.put[soul] && msg.put[soul][item]
|
|
448
|
+
const id = utils.rel.is(current)
|
|
449
|
+
if (id) {
|
|
450
|
+
if (lex) lex = utils.obj.put(lex, "#", id)
|
|
451
|
+
else lex = {"#": id, ".": null}
|
|
452
|
+
} else {
|
|
453
|
+
if (lex) lex = utils.obj.put(lex, "#", soul)
|
|
454
|
+
else lex = {"#": soul, ".": item}
|
|
455
|
+
}
|
|
456
|
+
wire.on(lex, map.get(cb))
|
|
457
|
+
})
|
|
458
|
+
},
|
|
459
|
+
off: cb => {
|
|
460
|
+
if (!ctxid) {
|
|
461
|
+
console.log("please provide a key using get(key)")
|
|
462
|
+
if (cb) cb(null)
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Resolve the current context before removing event listener.
|
|
467
|
+
const {item, soul} = resolve({off: true}, cb)
|
|
468
|
+
if (!soul) return
|
|
469
|
+
|
|
470
|
+
// Check if item is a rel and remove event listener for the node.
|
|
471
|
+
wire.get({"#": soul, ".": item}, msg => {
|
|
472
|
+
if (msg.err) {
|
|
473
|
+
console.log(`error getting ${soul}.${item}: ${msg.err}`)
|
|
474
|
+
return
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const current = msg.put && msg.put[soul] && msg.put[soul][item]
|
|
478
|
+
const id = utils.rel.is(current)
|
|
479
|
+
if (id) wire.off({"#": id}, map.get(cb))
|
|
480
|
+
else wire.off({"#": soul}, map.get(cb))
|
|
481
|
+
map.delete(cb)
|
|
482
|
+
allctx.delete(ctxid)
|
|
483
|
+
})
|
|
484
|
+
},
|
|
485
|
+
user: pub => {
|
|
486
|
+
// Return the combined Holster and User APIs. Passing in a public key
|
|
487
|
+
// will set the user context to that key, otherwise user.ctx is set when
|
|
488
|
+
// a user logs in so that their public key will be used as the root node
|
|
489
|
+
// in get(), also put() will sign all graph updates for verification.
|
|
490
|
+
if (pub) user.ctx = "~" + pub
|
|
491
|
+
return Object.assign(user, api())
|
|
492
|
+
},
|
|
493
|
+
// Allow the wire spec to be used via holster.
|
|
494
|
+
wire: wire,
|
|
495
|
+
// Allow SEA functions to be used via holster.
|
|
496
|
+
SEA: SEA,
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return api()
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export default Holster
|
package/src/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import express from "express"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import {fileURLToPath} from "url"
|
|
4
|
+
import Holster from "./holster.js"
|
|
5
|
+
|
|
6
|
+
// TODO: pass in opt for websocket url, etc.
|
|
7
|
+
const holster = Holster()
|
|
8
|
+
|
|
9
|
+
// Note that express is only used here to serve the examples folder. It is not
|
|
10
|
+
// required to run Holster itself.
|
|
11
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
12
|
+
|
|
13
|
+
const app = express()
|
|
14
|
+
|
|
15
|
+
app.use(express.static(path.join(dirname, "..")))
|
|
16
|
+
|
|
17
|
+
app.get("/", (req, res) => {
|
|
18
|
+
res.redirect("/examples/index.html")
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
app.listen(3000)
|