@cero-base/cero 1.7.1 → 1.8.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 +8 -8
- package/package.json +7 -7
- package/src/build/schemas.js +1 -1
- package/src/handle/index.js +31 -3
- package/src/rpc/client.js +2 -2
- package/src/rpc/server.js +2 -2
- package/types/build/schemas.d.ts +1 -1
- package/types/handle/index.d.ts +10 -1
package/README.md
CHANGED
|
@@ -521,14 +521,14 @@ The same `spec/index.js` is imported on both sides. `@cero-base/cero/build` emit
|
|
|
521
521
|
|
|
522
522
|
Everything the local API offers, plus lifecycle methods on returned handles:
|
|
523
523
|
|
|
524
|
-
| Operator / method
|
|
525
|
-
|
|
|
526
|
-
| `cero.put` / `set` / `get` / `del` / `count` / `watch` / `call`
|
|
527
|
-
| `cero.open(ref, …)` — create, join, load by id
|
|
528
|
-
| `room.invite({ role, expiresIn,
|
|
529
|
-
| `room.revoke(invite)` — invalidate an outstanding invite; `true` if it existed
|
|
530
|
-
| `room.close()` — release the handle on the server
|
|
531
|
-
| `room.leave()` — remove yourself from the room and from your handles list
|
|
524
|
+
| Operator / method | Works over RPC |
|
|
525
|
+
| -------------------------------------------------------------------------------------------------------------- | -------------- |
|
|
526
|
+
| `cero.put` / `set` / `get` / `del` / `count` / `watch` / `call` | ✓ |
|
|
527
|
+
| `cero.open(ref, …)` — create, join, load by id | ✓ |
|
|
528
|
+
| `room.invite({ role, expiresIn, reuse })` — mint an invite (`reuse: true` keeps it alive after the first join) | ✓ |
|
|
529
|
+
| `room.revoke(invite)` — invalidate an outstanding invite; `true` if it existed | ✓ |
|
|
530
|
+
| `room.close()` — release the handle on the server | ✓ |
|
|
531
|
+
| `room.leave()` — remove yourself from the room and from your handles list | ✓ |
|
|
532
532
|
|
|
533
533
|
`cero.watch(ref)` returns a Readable on both sides; snapshots flow as a server-streamed RPC.
|
|
534
534
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cero-base/cero",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "The ideal p2p API — everything is a handle, handles contain refs, refs contain rows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -89,27 +89,27 @@
|
|
|
89
89
|
"build:types": "rm -rf types && tsc -p .",
|
|
90
90
|
"pretest": "npm run build:test",
|
|
91
91
|
"prepublishOnly": "npm run build:types",
|
|
92
|
-
"test": "npm run test:
|
|
92
|
+
"test": "npm run test:bare",
|
|
93
93
|
"pretest:bare": "npm run build:test",
|
|
94
94
|
"test:bare": "ls test/*.test.js | xargs -P1 -n1 brittle-bare test/bare.js",
|
|
95
95
|
"test:node": "ls test/*.test.js | xargs -P1 -n1 brittle-node"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@cero-base/core": "^1.
|
|
98
|
+
"@cero-base/core": "^1.8.1",
|
|
99
99
|
"b4a": "^1.8.1",
|
|
100
100
|
"bare-abort-controller": "^1.1.2",
|
|
101
101
|
"bare-crypto": "^1.15.3",
|
|
102
102
|
"bare-fs": "^4.7.4",
|
|
103
103
|
"bare-path": "^3.1.1",
|
|
104
104
|
"compact-encoding": "^3.3.0",
|
|
105
|
-
"corestore": "^7.
|
|
105
|
+
"corestore": "^7.12.0",
|
|
106
106
|
"hrpc": "^4.3.0",
|
|
107
|
-
"hypercore": "^11.
|
|
107
|
+
"hypercore": "^11.35.1",
|
|
108
108
|
"hypercore-crypto": "^3.7.0",
|
|
109
109
|
"hypercore-storage": "^3.2.0",
|
|
110
|
-
"hyperdb": "^6.
|
|
110
|
+
"hyperdb": "^6.8.0",
|
|
111
111
|
"hyperdispatch": "^1.6.0",
|
|
112
|
-
"hyperschema": "^1.
|
|
112
|
+
"hyperschema": "^1.22.0",
|
|
113
113
|
"ready-resource": "^1.2.0",
|
|
114
114
|
"safety-catch": "^1.0.3",
|
|
115
115
|
"streamx": "^2.28.0",
|
package/src/build/schemas.js
CHANGED
package/src/handle/index.js
CHANGED
|
@@ -122,6 +122,7 @@ export class Handle extends ReadyResource {
|
|
|
122
122
|
this._onerror = this._opts.onerror || safetyCatch
|
|
123
123
|
this.children = parent ? null : new Set()
|
|
124
124
|
this._loading = parent ? null : new Map()
|
|
125
|
+
this._joining = parent ? null : new Map()
|
|
125
126
|
this._coreKeys = parent ? null : new Map()
|
|
126
127
|
this._fileServer = null
|
|
127
128
|
this._blobs = null
|
|
@@ -630,7 +631,33 @@ export class Handle extends ReadyResource {
|
|
|
630
631
|
* @param {JoinChildOpts} [opts]
|
|
631
632
|
* @returns {Promise<Handle>}
|
|
632
633
|
*/
|
|
633
|
-
async _join(invite, type,
|
|
634
|
+
async _join(invite, type, opts = {}) {
|
|
635
|
+
// Coalesce concurrent joins of the same room (a retry racing a pending
|
|
636
|
+
// join, a double tap): the pairing layer allows one candidate per invite,
|
|
637
|
+
// so the second attempt must attach to the in-flight join, not start
|
|
638
|
+
// another handshake.
|
|
639
|
+
const target = Pairing.inviteTopic(invite)
|
|
640
|
+
const key = target && `${type}/${b4a.toString(target, 'hex')}`
|
|
641
|
+
if (!key) return this._pair(invite, type, opts, target)
|
|
642
|
+
const pending = this._joining.get(key)
|
|
643
|
+
if (pending) return pending
|
|
644
|
+
const joining = this._pair(invite, type, opts, target)
|
|
645
|
+
this._joining.set(key, joining)
|
|
646
|
+
try {
|
|
647
|
+
return await joining
|
|
648
|
+
} finally {
|
|
649
|
+
this._joining.delete(key)
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* @param {string} invite
|
|
655
|
+
* @param {string} type
|
|
656
|
+
* @param {JoinChildOpts} [opts]
|
|
657
|
+
* @param {Uint8Array | null} [target]
|
|
658
|
+
* @returns {Promise<Handle>}
|
|
659
|
+
*/
|
|
660
|
+
async _pair(invite, type, { routes, timeout } = {}, target = null) {
|
|
634
661
|
const deadline = timeout || TIMEOUT
|
|
635
662
|
|
|
636
663
|
// Idempotent: if the invite targets a handle we already have AND we are
|
|
@@ -639,7 +666,6 @@ export class Handle extends ReadyResource {
|
|
|
639
666
|
// removed), the stored writer is revoked and the old session can never
|
|
640
667
|
// become writable again — holding a fresh invite is exactly the
|
|
641
668
|
// re-admission path, so fall through to a real pairing instead.
|
|
642
|
-
const target = Pairing.inviteTopic(invite)
|
|
643
669
|
if (target) {
|
|
644
670
|
const { data: joined } = await this.store.get('handles')
|
|
645
671
|
const existing = joined.find(
|
|
@@ -812,7 +838,9 @@ export class Handle extends ReadyResource {
|
|
|
812
838
|
if (!spec) throw CeroError.REQUIRED('spec')
|
|
813
839
|
|
|
814
840
|
const writer = Identity.randomKeyPair()
|
|
815
|
-
|
|
841
|
+
// join-only: no member listener, so concurrent joins never collide on the
|
|
842
|
+
// identity-derived default topic ('Active member already exist')
|
|
843
|
+
const pair = new Pairing({ network: net, identity: id, host: false })
|
|
816
844
|
await pair.ready()
|
|
817
845
|
|
|
818
846
|
let key, encryptionKey, additional
|
package/src/rpc/client.js
CHANGED
|
@@ -385,12 +385,12 @@ const operators = {
|
|
|
385
385
|
* @param {{ role?: string }} [opts]
|
|
386
386
|
* @returns {Promise<string>}
|
|
387
387
|
*/
|
|
388
|
-
async invite({ role, expiresIn,
|
|
388
|
+
async invite({ role, expiresIn, reuse } = {}) {
|
|
389
389
|
const { invite } = await this.rpc.invite({
|
|
390
390
|
handle: this.id,
|
|
391
391
|
role: role || '',
|
|
392
392
|
expiresIn: expiresIn || 0,
|
|
393
|
-
|
|
393
|
+
reuse: reuse === true
|
|
394
394
|
})
|
|
395
395
|
return invite
|
|
396
396
|
},
|
package/src/rpc/server.js
CHANGED
|
@@ -267,12 +267,12 @@ export class Server extends RPCServer {
|
|
|
267
267
|
|
|
268
268
|
/** Register invite/revoke/join RPC handlers. */
|
|
269
269
|
_wirePairing() {
|
|
270
|
-
this.rpc.onInvite(async ({ handle, role, expiresIn,
|
|
270
|
+
this.rpc.onInvite(async ({ handle, role, expiresIn, reuse }) => {
|
|
271
271
|
const h = this._resolve(handle)
|
|
272
272
|
const invite = await h.invite({
|
|
273
273
|
role: role || undefined,
|
|
274
274
|
expiresIn: expiresIn || undefined,
|
|
275
|
-
|
|
275
|
+
reuse: reuse === true
|
|
276
276
|
})
|
|
277
277
|
return { invite }
|
|
278
278
|
})
|
package/types/build/schemas.d.ts
CHANGED
|
@@ -129,7 +129,7 @@ export const rpc: {
|
|
|
129
129
|
handle: import("@cero-base/core").Prim;
|
|
130
130
|
role: import("@cero-base/core").Prim;
|
|
131
131
|
expiresIn: import("@cero-base/core").Prim;
|
|
132
|
-
|
|
132
|
+
reuse: import("@cero-base/core").Prim;
|
|
133
133
|
};
|
|
134
134
|
'req-revoke': {
|
|
135
135
|
handle: import("@cero-base/core").Prim;
|
package/types/handle/index.d.ts
CHANGED
|
@@ -91,6 +91,7 @@ export class Handle extends ReadyResource {
|
|
|
91
91
|
_onerror: any;
|
|
92
92
|
children: Set<any>;
|
|
93
93
|
_loading: Map<any, any>;
|
|
94
|
+
_joining: Map<any, any>;
|
|
94
95
|
_coreKeys: Map<any, any>;
|
|
95
96
|
_fileServer: FileServer;
|
|
96
97
|
_blobs: Blobs;
|
|
@@ -277,7 +278,15 @@ export class Handle extends ReadyResource {
|
|
|
277
278
|
* @param {JoinChildOpts} [opts]
|
|
278
279
|
* @returns {Promise<Handle>}
|
|
279
280
|
*/
|
|
280
|
-
_join(invite: string, type: string,
|
|
281
|
+
_join(invite: string, type: string, opts?: JoinChildOpts): Promise<Handle>;
|
|
282
|
+
/**
|
|
283
|
+
* @param {string} invite
|
|
284
|
+
* @param {string} type
|
|
285
|
+
* @param {JoinChildOpts} [opts]
|
|
286
|
+
* @param {Uint8Array | null} [target]
|
|
287
|
+
* @returns {Promise<Handle>}
|
|
288
|
+
*/
|
|
289
|
+
_pair(invite: string, type: string, { routes, timeout }?: JoinChildOpts, target?: Uint8Array | null): Promise<Handle>;
|
|
281
290
|
/**
|
|
282
291
|
* Get an open child by id, or re-open it. Concurrent calls for the same id
|
|
283
292
|
* share one in-flight load, so the child is built — and `handle` emitted —
|