@mblaney/holster 2.2.2 → 2.3.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/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 +1 -1
- package/src/holster.js +48 -10
- package/src/wire.js +9 -1
- package/ts/src/holster.ts +100 -22
- package/ts/src/user.ts +19 -7
- package/ts/src/wire.ts +9 -1
package/package.json
CHANGED
package/src/holster.js
CHANGED
|
@@ -84,6 +84,24 @@ const Holster = opt => {
|
|
|
84
84
|
return readRelTarget(id, ctxUser, _opt, retries + 1)
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// Wraps a not-yet-terminal chain object (api(ctxid), returned wherever no
|
|
88
|
+
// callback was given to resolve it yet) with a .then, so awaiting it
|
|
89
|
+
// resolves the same way a callback given right here would - mirrors
|
|
90
|
+
// readRelTarget's own api(ctxid).next(null, cb) idiom, which resolves the
|
|
91
|
+
// current chain without pushing a new key onto it. There's no separate
|
|
92
|
+
// error channel on this path (get/next's own callback only ever receives
|
|
93
|
+
// a value, never an error), so reject is simply never called - a
|
|
94
|
+
// rejection-less promise is still a valid one. Deliberately only applied
|
|
95
|
+
// at these specific "chain not yet resolved" return points, not to
|
|
96
|
+
// api()'s own bare object (see holster.user(), which Object.assigns that
|
|
97
|
+
// onto the long-lived user object) - that object has no chain to resolve
|
|
98
|
+
// at all, and giving user itself a .then would make a bare `await user`
|
|
99
|
+
// (or passing it to Promise.all, etc) silently try to resolve one.
|
|
100
|
+
const awaitable = chainApi => ({
|
|
101
|
+
...chainApi,
|
|
102
|
+
then: resolve => chainApi.next(null, resolve),
|
|
103
|
+
})
|
|
104
|
+
|
|
87
105
|
const api = ctxid => {
|
|
88
106
|
const get = (lex, soul, ack, _opt) => {
|
|
89
107
|
wire.get(
|
|
@@ -412,7 +430,7 @@ const Holster = opt => {
|
|
|
412
430
|
// to be objects.
|
|
413
431
|
ctxid = utils.text.random()
|
|
414
432
|
allctx.set(ctxid, {chain: [{item: String(key), soul: "root"}], cb: cb})
|
|
415
|
-
if (!cb) return api(ctxid)
|
|
433
|
+
if (!cb) return awaitable(api(ctxid))
|
|
416
434
|
|
|
417
435
|
const _done = done(ctxid)
|
|
418
436
|
// When there's a callback need to resolve the context first.
|
|
@@ -467,7 +485,7 @@ const Holster = opt => {
|
|
|
467
485
|
// Push the key to the context as it needs a soul lookup.
|
|
468
486
|
// (null is used to call the api with updated context)
|
|
469
487
|
if (key !== null) ctx.chain.push({item: String(key), soul: null})
|
|
470
|
-
if (!ctx.cb) return api(ctxid)
|
|
488
|
+
if (!ctx.cb) return awaitable(api(ctxid))
|
|
471
489
|
|
|
472
490
|
// When there's a callback need to resolve the context first.
|
|
473
491
|
const {soul} = resolve({get: lex, _opt: _opt}, _ack)
|
|
@@ -478,6 +496,25 @@ const Holster = opt => {
|
|
|
478
496
|
cb = set
|
|
479
497
|
set = false
|
|
480
498
|
}
|
|
499
|
+
// Unlike get/next (see awaitable above), put's own callback always
|
|
500
|
+
// receives an error channel - an error string, or undefined on
|
|
501
|
+
// success - so a promise here can reject, not just resolve.
|
|
502
|
+
if (!cb) {
|
|
503
|
+
const promise = new Promise((resolve, reject) => {
|
|
504
|
+
api(ctxid).put(data, set, err =>
|
|
505
|
+
err ? reject(new Error(err)) : resolve(),
|
|
506
|
+
)
|
|
507
|
+
})
|
|
508
|
+
// A caller using the old fire-and-forget idiom (put(data) with
|
|
509
|
+
// no callback, result never touched) shouldn't crash the
|
|
510
|
+
// process on an unhandled rejection if something genuinely
|
|
511
|
+
// fails now that this returns a promise - doesn't suppress the
|
|
512
|
+
// rejection for a caller that does await/catch it, since a
|
|
513
|
+
// promise can have more than one handler attached (same
|
|
514
|
+
// pattern as createRel's own promise above).
|
|
515
|
+
promise.catch(() => {})
|
|
516
|
+
return promise
|
|
517
|
+
}
|
|
481
518
|
const ack = ctxid => {
|
|
482
519
|
return data => {
|
|
483
520
|
if (cb) {
|
|
@@ -619,13 +656,14 @@ const Holster = opt => {
|
|
|
619
656
|
|
|
620
657
|
const _ctxid = utils.text.random()
|
|
621
658
|
const chain = [{item: item, soul: soul}]
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
659
|
+
allctx.set(_ctxid, {chain: chain, user: ctx.user})
|
|
660
|
+
// Pass on the previous ctx's own callback (if any)
|
|
661
|
+
// positionally rather than pre-registering it via
|
|
662
|
+
// allctx - otherwise it's invisible to put's own "no cb
|
|
663
|
+
// given" check above, which would then wrap this retry
|
|
664
|
+
// in a brand new promise competing with (and never
|
|
665
|
+
// actually calling) the real one.
|
|
666
|
+
api(_ctxid).put(data, ctx.cb)
|
|
629
667
|
})
|
|
630
668
|
return
|
|
631
669
|
}
|
|
@@ -952,7 +990,7 @@ const Holster = opt => {
|
|
|
952
990
|
ctxid = utils.text.random()
|
|
953
991
|
const chain = [{item: String(key), soul: "~" + pub}]
|
|
954
992
|
allctx.set(ctxid, {chain: chain, user: user.is, cb: cb})
|
|
955
|
-
if (!cb) return api(ctxid)
|
|
993
|
+
if (!cb) return awaitable(api(ctxid))
|
|
956
994
|
|
|
957
995
|
// When there's a callback need to resolve the context first.
|
|
958
996
|
const _done = done(ctxid)
|
package/src/wire.js
CHANGED
|
@@ -497,7 +497,15 @@ const Wire = opt => {
|
|
|
497
497
|
})
|
|
498
498
|
|
|
499
499
|
if (ack) {
|
|
500
|
-
|
|
500
|
+
// Put requests only need a local answer - skip the wire round-trip
|
|
501
|
+
// (matches the same check below, once store.get is reached instead
|
|
502
|
+
// of this fast graph path). Without this, check()'s own bare
|
|
503
|
+
// full-node lookup - always {put: true} - re-sends on the wire every
|
|
504
|
+
// time it's called once the soul's been through store.get once (see
|
|
505
|
+
// hasNode above), and since its own reply re-enters put() and calls
|
|
506
|
+
// check() again, this becomes a self-sustaining loop: a get every
|
|
507
|
+
// ~recentCheckTTL, forever, per soul, for every soul ever fetched.
|
|
508
|
+
if (!isNode && !_opt.put) {
|
|
501
509
|
// Client: send request on the wire to check for updates,
|
|
502
510
|
// since it may have missed puts while disconnected.
|
|
503
511
|
// Skip if the same lex was checked recently to avoid flooding.
|
package/ts/src/holster.ts
CHANGED
|
@@ -28,34 +28,54 @@ export interface HolsterAPI {
|
|
|
28
28
|
get: {
|
|
29
29
|
(
|
|
30
30
|
key: string | null,
|
|
31
|
-
lex
|
|
32
|
-
cb
|
|
31
|
+
lex: LexWithDot,
|
|
32
|
+
cb: (data: unknown) => void,
|
|
33
33
|
_opt?: WireOptions,
|
|
34
34
|
): HolsterAPI
|
|
35
35
|
(
|
|
36
36
|
key: string | null,
|
|
37
|
-
cb
|
|
37
|
+
cb: (data: unknown) => void,
|
|
38
38
|
_opt?: WireOptions,
|
|
39
39
|
): HolsterAPI
|
|
40
|
+
(
|
|
41
|
+
key: string | null,
|
|
42
|
+
lex?: LexWithDot,
|
|
43
|
+
_opt?: WireOptions,
|
|
44
|
+
): AwaitableHolsterAPI
|
|
40
45
|
}
|
|
41
46
|
next: {
|
|
42
47
|
(
|
|
43
48
|
key: string | null,
|
|
44
|
-
lex
|
|
45
|
-
cb
|
|
49
|
+
lex: LexWithDot,
|
|
50
|
+
cb: (data: unknown) => void,
|
|
46
51
|
_opt?: WireOptions,
|
|
47
52
|
): HolsterAPI
|
|
48
53
|
(
|
|
49
54
|
key: string | null,
|
|
50
|
-
cb
|
|
55
|
+
cb: (data: unknown) => void,
|
|
51
56
|
_opt?: WireOptions,
|
|
52
57
|
): HolsterAPI
|
|
58
|
+
(
|
|
59
|
+
key: string | null,
|
|
60
|
+
lex?: LexWithDot,
|
|
61
|
+
_opt?: WireOptions,
|
|
62
|
+
): AwaitableHolsterAPI
|
|
63
|
+
}
|
|
64
|
+
put: {
|
|
65
|
+
(
|
|
66
|
+
data: GraphValue | Record<string, GraphValue>,
|
|
67
|
+
set: boolean,
|
|
68
|
+
cb: (data?: string | null) => void,
|
|
69
|
+
): void
|
|
70
|
+
(
|
|
71
|
+
data: GraphValue | Record<string, GraphValue>,
|
|
72
|
+
cb: (data?: string | null) => void,
|
|
73
|
+
): void
|
|
74
|
+
(
|
|
75
|
+
data: GraphValue | Record<string, GraphValue>,
|
|
76
|
+
set?: boolean,
|
|
77
|
+
): Promise<void>
|
|
53
78
|
}
|
|
54
|
-
put: (
|
|
55
|
-
data: GraphValue | Record<string, GraphValue>,
|
|
56
|
-
set?: boolean | ((data?: string | null) => void),
|
|
57
|
-
cb?: (data?: string | null) => void,
|
|
58
|
-
) => HolsterAPI | void
|
|
59
79
|
on: {
|
|
60
80
|
(
|
|
61
81
|
lex: LexWithDot,
|
|
@@ -71,6 +91,23 @@ export interface HolsterAPI {
|
|
|
71
91
|
SEA: typeof SEA
|
|
72
92
|
}
|
|
73
93
|
|
|
94
|
+
// Deliberately not the built-in PromiseLike<T> - that also requires a
|
|
95
|
+
// return value and an onrejected param awaitable's own then() never uses
|
|
96
|
+
// (there's no separate error channel on this path, see awaitable's own
|
|
97
|
+
// comment). TS's Awaited<T> only needs a callable then(onfulfilled, ...)
|
|
98
|
+
// to recognize something as thenable - this minimal shape is enough for
|
|
99
|
+
// `await` to work and to correctly infer the resolved type as unknown.
|
|
100
|
+
interface Thenable {
|
|
101
|
+
then: (resolve: (data: unknown) => void) => void
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// A chain object (api(ctxid), returned wherever no callback was given to
|
|
105
|
+
// resolve it yet) with a .then added, so `await`-ing it resolves the same
|
|
106
|
+
// way a callback given right here would (see awaitable in holster.ts).
|
|
107
|
+
// Only the "no callback given" get/next overloads above return this -
|
|
108
|
+
// the "has a real callback" ones genuinely don't have a .then.
|
|
109
|
+
export type AwaitableHolsterAPI = HolsterAPI & Thenable
|
|
110
|
+
|
|
74
111
|
const Holster = (opt?: HolsterOptions | string | string[]): HolsterAPI => {
|
|
75
112
|
let options: HolsterOptions
|
|
76
113
|
if (typeof opt === "string") options = {peers: [opt]}
|
|
@@ -156,6 +193,25 @@ const Holster = (opt?: HolsterOptions | string | string[]): HolsterAPI => {
|
|
|
156
193
|
return readRelTarget(id, ctxUser, opt, retries + 1)
|
|
157
194
|
}
|
|
158
195
|
|
|
196
|
+
// Wraps a not-yet-terminal chain object (api(ctxid), returned wherever no
|
|
197
|
+
// callback was given to resolve it yet) with a .then, so awaiting it
|
|
198
|
+
// resolves the same way a callback given right here would - mirrors
|
|
199
|
+
// readRelTarget's own api(ctxid).next(null, cb) idiom, which resolves the
|
|
200
|
+
// current chain without pushing a new key onto it. There's no separate
|
|
201
|
+
// error channel on this path (get/next's own callback only ever receives
|
|
202
|
+
// a value, never an error), so reject is simply never called - a
|
|
203
|
+
// rejection-less promise is still a valid one. Deliberately only applied
|
|
204
|
+
// at these specific "chain not yet resolved" return points, not to
|
|
205
|
+
// api()'s own bare object (see holster.user(), which Object.assigns that
|
|
206
|
+
// onto the long-lived user object) - that object has no chain to resolve
|
|
207
|
+
// at all, and giving user itself a .then would make a bare `await user`
|
|
208
|
+
// (or passing it to Promise.all, etc) silently try to resolve one.
|
|
209
|
+
const awaitable = (chainApi: HolsterAPI): AwaitableHolsterAPI => ({
|
|
210
|
+
...chainApi,
|
|
211
|
+
then: (resolve: (data: unknown) => void) =>
|
|
212
|
+
chainApi.next(null, resolve as never),
|
|
213
|
+
})
|
|
214
|
+
|
|
159
215
|
const api = (initCtxid?: string): HolsterAPI => {
|
|
160
216
|
let ctxid = initCtxid
|
|
161
217
|
const get = (
|
|
@@ -559,7 +615,7 @@ const Holster = (opt?: HolsterOptions | string | string[]): HolsterAPI => {
|
|
|
559
615
|
chain: [{item: String(key), soul: "root"}],
|
|
560
616
|
cb: callback,
|
|
561
617
|
})
|
|
562
|
-
if (!callback) return api(ctxid)
|
|
618
|
+
if (!callback) return awaitable(api(ctxid))
|
|
563
619
|
|
|
564
620
|
const _done = done(ctxid)
|
|
565
621
|
const result = resolve({get: lexFilter, _opt: opts}, _done)
|
|
@@ -624,7 +680,7 @@ const Holster = (opt?: HolsterOptions | string | string[]): HolsterAPI => {
|
|
|
624
680
|
}
|
|
625
681
|
|
|
626
682
|
if (key !== null) ctx.chain.push({item: String(key), soul: null})
|
|
627
|
-
if (!ctx.cb) return api(ctxid)
|
|
683
|
+
if (!ctx.cb) return awaitable(api(ctxid))
|
|
628
684
|
|
|
629
685
|
const result = resolve({get: lexFilter, _opt: opts}, _ack)
|
|
630
686
|
if (result) get(lexFilter as never, result.soul!, _ack, opts)
|
|
@@ -635,7 +691,7 @@ const Holster = (opt?: HolsterOptions | string | string[]): HolsterAPI => {
|
|
|
635
691
|
data: GraphValue | Record<string, GraphValue>,
|
|
636
692
|
set?: boolean | ((data?: string | null) => void),
|
|
637
693
|
cb?: (data?: string | null) => void,
|
|
638
|
-
):
|
|
694
|
+
): void | Promise<void> {
|
|
639
695
|
let isSet = false
|
|
640
696
|
let callback: ((data?: string | null) => void) | undefined
|
|
641
697
|
|
|
@@ -647,6 +703,26 @@ const Holster = (opt?: HolsterOptions | string | string[]): HolsterAPI => {
|
|
|
647
703
|
callback = cb
|
|
648
704
|
}
|
|
649
705
|
|
|
706
|
+
// Unlike get/next (see awaitable above), put's own callback always
|
|
707
|
+
// receives an error channel - an error string, or undefined on
|
|
708
|
+
// success - so a promise here can reject, not just resolve.
|
|
709
|
+
if (!callback) {
|
|
710
|
+
const promise = new Promise<void>((promiseResolve, reject) => {
|
|
711
|
+
api(ctxid).put(data, isSet, err =>
|
|
712
|
+
err ? reject(new Error(err)) : promiseResolve(),
|
|
713
|
+
)
|
|
714
|
+
})
|
|
715
|
+
// A caller using the old fire-and-forget idiom (put(data) with
|
|
716
|
+
// no callback, result never touched) shouldn't crash the
|
|
717
|
+
// process on an unhandled rejection if something genuinely
|
|
718
|
+
// fails now that this returns a promise - doesn't suppress the
|
|
719
|
+
// rejection for a caller that does await/catch it, since a
|
|
720
|
+
// promise can have more than one handler attached (same
|
|
721
|
+
// pattern as createRel's own promise above).
|
|
722
|
+
promise.catch(() => {})
|
|
723
|
+
return promise
|
|
724
|
+
}
|
|
725
|
+
|
|
650
726
|
const ack = (ctxid: string) => {
|
|
651
727
|
return (data?: string | null): void => {
|
|
652
728
|
if (callback) {
|
|
@@ -797,12 +873,14 @@ const Holster = (opt?: HolsterOptions | string | string[]): HolsterAPI => {
|
|
|
797
873
|
|
|
798
874
|
const _ctxid = utils.text.random()
|
|
799
875
|
const chain: ChainItem[] = [{item: item!, soul: soul}]
|
|
800
|
-
allctx.set(_ctxid, {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
876
|
+
allctx.set(_ctxid, {chain: chain, user: ctx.user})
|
|
877
|
+
// Pass on the previous ctx's own callback (if any)
|
|
878
|
+
// positionally rather than pre-registering it via
|
|
879
|
+
// allctx - otherwise it's invisible to put's own "no cb
|
|
880
|
+
// given" check above, which would then wrap this retry
|
|
881
|
+
// in a brand new promise competing with (and never
|
|
882
|
+
// actually calling) the real one.
|
|
883
|
+
api(_ctxid).put(data, ctx.cb as never)
|
|
806
884
|
})
|
|
807
885
|
return
|
|
808
886
|
}
|
|
@@ -864,7 +942,7 @@ const Holster = (opt?: HolsterOptions | string | string[]): HolsterAPI => {
|
|
|
864
942
|
)
|
|
865
943
|
}
|
|
866
944
|
attemptRead()
|
|
867
|
-
},
|
|
945
|
+
} as HolsterAPI["put"],
|
|
868
946
|
|
|
869
947
|
on: function (
|
|
870
948
|
lex: LexWithDot | ((data: unknown) => void),
|
|
@@ -1199,7 +1277,7 @@ const Holster = (opt?: HolsterOptions | string | string[]): HolsterAPI => {
|
|
|
1199
1277
|
user: user.is,
|
|
1200
1278
|
cb: callback as never,
|
|
1201
1279
|
})
|
|
1202
|
-
if (!callback) return api(ctxid)
|
|
1280
|
+
if (!callback) return awaitable(api(ctxid))
|
|
1203
1281
|
|
|
1204
1282
|
const _done = done(ctxid)
|
|
1205
1283
|
const resolved = resolve({get: lexFilter, _opt: opts}, _done)
|
package/ts/src/user.ts
CHANGED
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
LexFilter,
|
|
13
13
|
WireOptions,
|
|
14
14
|
} from "./schemas.ts"
|
|
15
|
-
import type {HolsterAPI} from "./holster.ts"
|
|
15
|
+
import type {HolsterAPI, AwaitableHolsterAPI} from "./holster.ts"
|
|
16
16
|
|
|
17
17
|
type WireInterface = ReturnType<typeof Wire>
|
|
18
18
|
|
|
@@ -42,12 +42,24 @@ export interface UserInterface {
|
|
|
42
42
|
password: string,
|
|
43
43
|
cb?: (err?: string | null) => void,
|
|
44
44
|
) => void
|
|
45
|
-
get
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
get?: {
|
|
46
|
+
(
|
|
47
|
+
keys: string | string[],
|
|
48
|
+
lex: LexFilter,
|
|
49
|
+
cb: (data: unknown) => void,
|
|
50
|
+
_opt?: WireOptions,
|
|
51
|
+
): HolsterAPI
|
|
52
|
+
(
|
|
53
|
+
keys: string | string[],
|
|
54
|
+
cb: (data: unknown) => void,
|
|
55
|
+
_opt?: WireOptions,
|
|
56
|
+
): HolsterAPI
|
|
57
|
+
(
|
|
58
|
+
keys: string | string[],
|
|
59
|
+
lex?: LexFilter,
|
|
60
|
+
_opt?: WireOptions,
|
|
61
|
+
): AwaitableHolsterAPI
|
|
62
|
+
}
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
/**
|
package/ts/src/wire.ts
CHANGED
|
@@ -556,7 +556,15 @@ const Wire = (opt: HolsterOptions): WireAPI => {
|
|
|
556
556
|
})
|
|
557
557
|
|
|
558
558
|
if (ack) {
|
|
559
|
-
|
|
559
|
+
// Put requests only need a local answer - skip the wire round-trip
|
|
560
|
+
// (matches the same check below, once store.get is reached instead
|
|
561
|
+
// of this fast graph path). Without this, check()'s own bare
|
|
562
|
+
// full-node lookup - always {put: true} - re-sends on the wire every
|
|
563
|
+
// time it's called once the soul's been through store.get once (see
|
|
564
|
+
// hasNode above), and since its own reply re-enters put() and calls
|
|
565
|
+
// check() again, this becomes a self-sustaining loop: a get every
|
|
566
|
+
// ~recentCheckTTL, forever, per soul, for every soul ever fetched.
|
|
567
|
+
if (!isNode && !opts.put) {
|
|
560
568
|
// Client: send request on the wire to check for updates,
|
|
561
569
|
// since it may have missed puts while disconnected.
|
|
562
570
|
// Skip if the same lex was checked recently to avoid flooding.
|