@cero-base/core 1.8.1 → 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/core",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "description": "cero p2p primitives — identity, storage, network, database, blobs, rpc, pairing.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -69,7 +69,8 @@ export class Pairing extends ReadyResource {
69
69
  host = true,
70
70
  inviteEncoding = null,
71
71
  joinerEncoding = null,
72
- onerror = safetyCatch
72
+ onerror = safetyCatch,
73
+ onconsume = null
73
74
  } = {}) {
74
75
  super()
75
76
  if (!network) throw CeroError.REQUIRED('network')
@@ -82,6 +83,9 @@ export class Pairing extends ReadyResource {
82
83
  this.inviteEncoding = inviteEncoding
83
84
  this.joinerEncoding = joinerEncoding
84
85
  this._onerror = onerror
86
+ // Fired with an invite's hex id when this instance stops serving it
87
+ // (single-use settled, or expired) — lets the owner drop its persisted row.
88
+ this.onconsume = onconsume
85
89
 
86
90
  this._blind = null
87
91
  this._member = null
@@ -168,12 +172,68 @@ export class Pairing extends ReadyResource {
168
172
  seed: blind.seed,
169
173
  publicKey: blind.publicKey,
170
174
  invite,
171
- reuse
175
+ reuse,
176
+ // minted here and possibly not yet persisted/replicated — syncRows must
177
+ // never drop it for being absent from the store
178
+ local: true
172
179
  })
173
180
 
174
181
  return invite.toString()
175
182
  }
176
183
 
184
+ /**
185
+ * Look up the in-memory record for a minted invite by its string form.
186
+ *
187
+ * @param {string} inviteStr
188
+ * @returns {{ id: Uint8Array, seed: Uint8Array, publicKey: Uint8Array, invite: import('./invite.js').Invite, reuse: boolean } | null}
189
+ */
190
+ recordOf(inviteStr) {
191
+ for (const record of this._invites.values()) {
192
+ if (record.invite.toString() === inviteStr) return record
193
+ }
194
+ return null
195
+ }
196
+
197
+ /**
198
+ * Reconcile the served-invite set with persisted rows (the room's `invites`
199
+ * collection). Adds unknown rows so ANY member replica can serve them across
200
+ * restarts; drops replicated records whose row disappeared (revoked or
201
+ * consumed elsewhere). Locally-minted records not yet visible as rows are
202
+ * kept.
203
+ *
204
+ * @param {Array<{ id: string, invite: Uint8Array, publicKey: Uint8Array, seed: Uint8Array, reuse?: boolean }>} rows
205
+ */
206
+ syncRows(rows) {
207
+ const seen = new Set()
208
+ for (const row of rows) {
209
+ if (!row?.id || !row.invite || !row.publicKey || !row.seed) continue
210
+ seen.add(row.id)
211
+ const existing = this._invites.get(row.id)
212
+ if (existing) {
213
+ existing.local = false
214
+ continue
215
+ }
216
+ let invite
217
+ try {
218
+ invite = Invite.parse(b4a.toString(row.invite))
219
+ } catch {
220
+ continue
221
+ }
222
+ if (invite.expired) continue
223
+ this._invites.set(row.id, {
224
+ id: b4a.from(row.id, 'hex'),
225
+ seed: row.seed,
226
+ publicKey: row.publicKey,
227
+ invite,
228
+ reuse: !!row.reuse,
229
+ local: false
230
+ })
231
+ }
232
+ for (const [id, record] of this._invites) {
233
+ if (!seen.has(id) && !record.local) this._invites.delete(id)
234
+ }
235
+ }
236
+
177
237
  /**
178
238
  * Forget a previously-minted invite. New candidates carrying it will be
179
239
  * dropped silently.
@@ -273,6 +333,7 @@ export class Pairing extends ReadyResource {
273
333
  if (!record) return
274
334
  if (record.invite.expired) {
275
335
  this._invites.delete(id)
336
+ this.onconsume?.(id)
276
337
  return
277
338
  }
278
339
 
@@ -292,7 +353,10 @@ export class Pairing extends ReadyResource {
292
353
  seed: record.seed,
293
354
  userData,
294
355
  onsettle: () => {
295
- if (!record.reuse) this._invites.delete(id)
356
+ if (!record.reuse) {
357
+ this._invites.delete(id)
358
+ this.onconsume?.(id)
359
+ }
296
360
  }
297
361
  })
298
362
 
@@ -57,7 +57,7 @@ export class Pairing extends ReadyResource {
57
57
  */
58
58
  static isInvite(str: unknown): boolean;
59
59
  /** @param {PairingOpts} [opts] */
60
- constructor({ network, identity, topic, host, inviteEncoding, joinerEncoding, onerror }?: PairingOpts);
60
+ constructor({ network, identity, topic, host, inviteEncoding, joinerEncoding, onerror, onconsume }?: PairingOpts);
61
61
  network: import("../index.js").Network;
62
62
  identity: import("../index.js").Identity;
63
63
  topic: Uint8Array<ArrayBufferLike>;
@@ -65,6 +65,7 @@ export class Pairing extends ReadyResource {
65
65
  inviteEncoding: any;
66
66
  joinerEncoding: any;
67
67
  _onerror: (err: Error) => void;
68
+ onconsume: any;
68
69
  _blind: any;
69
70
  _member: any;
70
71
  _invites: Map<any, any>;
@@ -76,6 +77,35 @@ export class Pairing extends ReadyResource {
76
77
  * @returns {Promise<string>}
77
78
  */
78
79
  createInvite({ role, expiresIn, data, reuse }?: CreateInviteOpts): Promise<string>;
80
+ /**
81
+ * Look up the in-memory record for a minted invite by its string form.
82
+ *
83
+ * @param {string} inviteStr
84
+ * @returns {{ id: Uint8Array, seed: Uint8Array, publicKey: Uint8Array, invite: import('./invite.js').Invite, reuse: boolean } | null}
85
+ */
86
+ recordOf(inviteStr: string): {
87
+ id: Uint8Array;
88
+ seed: Uint8Array;
89
+ publicKey: Uint8Array;
90
+ invite: import("./invite.js").Invite;
91
+ reuse: boolean;
92
+ } | null;
93
+ /**
94
+ * Reconcile the served-invite set with persisted rows (the room's `invites`
95
+ * collection). Adds unknown rows so ANY member replica can serve them across
96
+ * restarts; drops replicated records whose row disappeared (revoked or
97
+ * consumed elsewhere). Locally-minted records not yet visible as rows are
98
+ * kept.
99
+ *
100
+ * @param {Array<{ id: string, invite: Uint8Array, publicKey: Uint8Array, seed: Uint8Array, reuse?: boolean }>} rows
101
+ */
102
+ syncRows(rows: Array<{
103
+ id: string;
104
+ invite: Uint8Array;
105
+ publicKey: Uint8Array;
106
+ seed: Uint8Array;
107
+ reuse?: boolean;
108
+ }>): void;
79
109
  /**
80
110
  * Forget a previously-minted invite. New candidates carrying it will be
81
111
  * dropped silently.