@cero-base/cero 1.8.0 → 1.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cero-base/cero",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
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,
@@ -95,7 +95,7 @@
95
95
  "test:node": "ls test/*.test.js | xargs -P1 -n1 brittle-node"
96
96
  },
97
97
  "dependencies": {
98
- "@cero-base/core": "^1.8.0",
98
+ "@cero-base/core": "^1.9.0",
99
99
  "b4a": "^1.8.1",
100
100
  "bare-abort-controller": "^1.1.2",
101
101
  "bare-crypto": "^1.15.3",
@@ -48,7 +48,9 @@ export const main = {
48
48
  role: required(string),
49
49
  expires: int,
50
50
  createdAt: int,
51
- index: uint
51
+ index: uint,
52
+ seed: bytes,
53
+ reuse: bool
52
54
  },
53
55
  handle: {
54
56
  id: required(string),
@@ -154,9 +154,18 @@ export class Handle extends ReadyResource {
154
154
  network: this.network,
155
155
  identity: this.identity,
156
156
  topic: this.store.key,
157
- onerror: this._onerror
157
+ onerror: this._onerror,
158
+ onconsume: (id) => {
159
+ this.store.call('del-invite', { id }).catch(safetyCatch)
160
+ }
158
161
  })
159
162
  await this.pair.ready()
163
+ // Serve invites persisted by any member — and keep the served set in
164
+ // step as rows replicate in (minted elsewhere) or disappear (revoked
165
+ // or consumed elsewhere).
166
+ await this._syncInvites().catch(safetyCatch)
167
+ this._invitesSync = () => this._syncInvites().catch(safetyCatch)
168
+ this.store.on('update', this._invitesSync)
160
169
  }
161
170
  attachRefs(this, this.store.refs)
162
171
  this.root._coreKeys.set(b4a.toString(this.store.key, 'hex'), this.store.encryptionKey)
@@ -179,6 +188,10 @@ export class Handle extends ReadyResource {
179
188
  for (const c of [...this.children]) await c.close()
180
189
  this.children.clear()
181
190
  }
191
+ if (this._invitesSync) {
192
+ this.store.off('update', this._invitesSync)
193
+ this._invitesSync = null
194
+ }
182
195
  if (this.pair) await this.pair.close()
183
196
 
184
197
  if (this.parent) {
@@ -455,12 +468,37 @@ export class Handle extends ReadyResource {
455
468
  * @param {{ role?: string, expiresIn?: number, data?: any }} [opts]
456
469
  * @returns {Promise<string>} Z32-encoded invite string.
457
470
  */
458
- invite(opts) {
471
+ async invite(opts) {
459
472
  if (!this.pair)
460
473
  throw CeroError.INVALID(
461
474
  'invite() is not available on the root handle — open a child handle first'
462
475
  )
463
- return this.pair.createInvite(opts)
476
+ const str = await this.pair.createInvite(opts)
477
+ const record = this.pair.recordOf(str)
478
+ if (record) {
479
+ // Persist so every member replica serves this invite across restarts —
480
+ // the in-memory record keeps working this session even if the write
481
+ // fails.
482
+ await this.store
483
+ .call('add-invite', {
484
+ id: b4a.toString(record.id, 'hex'),
485
+ invite: b4a.from(str),
486
+ publicKey: record.publicKey,
487
+ seed: record.seed,
488
+ role: record.invite.role || '',
489
+ expires: record.invite.expires || 0,
490
+ createdAt: Date.now(),
491
+ reuse: !!record.reuse
492
+ })
493
+ .catch(safetyCatch)
494
+ }
495
+ return str
496
+ }
497
+
498
+ async _syncInvites() {
499
+ if (!this.pair) return
500
+ const { data } = await this.store.get('invites')
501
+ this.pair.syncRows(data || [])
464
502
  }
465
503
 
466
504
  /**
@@ -471,7 +509,13 @@ export class Handle extends ReadyResource {
471
509
  */
472
510
  revoke(invite) {
473
511
  if (!this.pair) throw CeroError.INVALID('revoke() is not available on the root handle')
474
- return this.pair.revoke(invite)
512
+ const record = this.pair.recordOf(invite)
513
+ const revoked = this.pair.revoke(invite)
514
+ if (revoked && record) {
515
+ // drop the persisted row so every other member stops serving it too
516
+ this.store.call('del-invite', { id: b4a.toString(record.id, 'hex') }).catch(safetyCatch)
517
+ }
518
+ return revoked
475
519
  }
476
520
 
477
521
  /**
@@ -42,6 +42,8 @@ export const main: {
42
42
  expires: import("@cero-base/core").Prim;
43
43
  createdAt: import("@cero-base/core").Prim;
44
44
  index: import("@cero-base/core").Prim;
45
+ seed: import("@cero-base/core").Prim;
46
+ reuse: import("@cero-base/core").Prim;
45
47
  };
46
48
  handle: {
47
49
  id: import("@cero-base/core").Prim;
@@ -100,6 +100,7 @@ export class Handle extends ReadyResource {
100
100
  store: Database;
101
101
  pair: Pairing;
102
102
  _wantsPair: boolean;
103
+ _invitesSync: () => Promise<void>;
103
104
  /**
104
105
  * Tie a destroyable resource (a `watch` stream, a timer, any `{ destroy }`)
105
106
  * to this handle's lifecycle — it's destroyed automatically on close, so
@@ -235,6 +236,7 @@ export class Handle extends ReadyResource {
235
236
  expiresIn?: number;
236
237
  data?: any;
237
238
  }): Promise<string>;
239
+ _syncInvites(): Promise<void>;
238
240
  /**
239
241
  * Revoke a previously-minted invite by its string form.
240
242
  *