@cero-base/cero 0.8.2 → 0.8.3

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": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "The ideal p2p API — everything is a handle, handles contain refs, refs contain rows.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -78,7 +78,7 @@
78
78
  "test": "ls test/*.test.js | xargs -P1 -n1 brittle-node"
79
79
  },
80
80
  "dependencies": {
81
- "@cero-base/core": "^0.8.2",
81
+ "@cero-base/core": "^0.8.3",
82
82
  "b4a": "^1.8.1",
83
83
  "bare-abort-controller": "^1.1.2",
84
84
  "bare-crypto": "^1.13.7",
@@ -111,6 +111,7 @@ export class Handle extends ReadyResource {
111
111
  this._opts = opts.opts || {}
112
112
  this._onerror = this._opts.onerror || safetyCatch
113
113
  this.children = parent ? null : new Set()
114
+ this._loading = parent ? null : new Map()
114
115
  this._owned = new Set()
115
116
 
116
117
  this.store = new Database({
@@ -459,9 +460,9 @@ export class Handle extends ReadyResource {
459
460
  }
460
461
 
461
462
  /**
462
- * Re-open an existing child handle by id. Reuses the stored writer
463
- * keypair if available; otherwise generates a fresh one and claims
464
- * writer capability.
463
+ * Get an open child by id, or re-open it. Concurrent calls for the same id
464
+ * share one in-flight load, so the child is built — and `handle` emitted —
465
+ * exactly once.
465
466
  *
466
467
  * @param {string} type
467
468
  * @param {string} id
@@ -469,6 +470,26 @@ export class Handle extends ReadyResource {
469
470
  */
470
471
  async _load(type, id) {
471
472
  for (const c of this.children) if (c.id === id) return c
473
+ const existing = this._loading.get(id)
474
+ if (existing) return existing
475
+ const loading = this._reopen(type, id)
476
+ this._loading.set(id, loading)
477
+ try {
478
+ return await loading
479
+ } finally {
480
+ this._loading.delete(id)
481
+ }
482
+ }
483
+
484
+ /**
485
+ * Reconstruct a child handle by id. Reuses the stored writer keypair if
486
+ * available; otherwise generates a fresh one and claims writer capability.
487
+ *
488
+ * @param {string} type
489
+ * @param {string} id
490
+ * @returns {Promise<Handle>}
491
+ */
492
+ async _reopen(type, id) {
472
493
  const { data } = await this.store.get('handles', id)
473
494
  if (!data) throw CeroError.UNKNOWN('handle', id)
474
495
  if (data.type !== type) throw new Error(`handle ${id} is type ${data.type}, not ${type}`)
package/src/index.js CHANGED
@@ -113,9 +113,14 @@ export async function cero(dir, spec, opts = {}) {
113
113
  }
114
114
  if (opts.recovery) await me.recover({ timeout: opts.recoveryTimeout })
115
115
 
116
- for (const ext of internal.extensions) {
117
- const off = await ext.setup?.(me)
118
- if (typeof off === 'function') me.once('close', off)
116
+ try {
117
+ for (const ext of internal.extensions) {
118
+ const off = await ext.setup?.(me)
119
+ if (typeof off === 'function') me.once('close', off)
120
+ }
121
+ } catch (err) {
122
+ await me.close().catch(() => {}) // a failed setup must not leak the half-built instance
123
+ throw err
119
124
  }
120
125
 
121
126
  return me
@@ -86,6 +86,7 @@ export class Handle extends ReadyResource {
86
86
  _opts: any;
87
87
  _onerror: any;
88
88
  children: Set<any>;
89
+ _loading: Map<any, any>;
89
90
  _owned: Set<any>;
90
91
  store: Database;
91
92
  pair: Pairing;
@@ -215,15 +216,24 @@ export class Handle extends ReadyResource {
215
216
  */
216
217
  _join(invite: string, type: string, { routes, timeout }?: JoinChildOpts): Promise<Handle>;
217
218
  /**
218
- * Re-open an existing child handle by id. Reuses the stored writer
219
- * keypair if available; otherwise generates a fresh one and claims
220
- * writer capability.
219
+ * Get an open child by id, or re-open it. Concurrent calls for the same id
220
+ * share one in-flight load, so the child is built — and `handle` emitted —
221
+ * exactly once.
221
222
  *
222
223
  * @param {string} type
223
224
  * @param {string} id
224
225
  * @returns {Promise<Handle>}
225
226
  */
226
227
  _load(type: string, id: string): Promise<Handle>;
228
+ /**
229
+ * Reconstruct a child handle by id. Reuses the stored writer keypair if
230
+ * available; otherwise generates a fresh one and claims writer capability.
231
+ *
232
+ * @param {string} type
233
+ * @param {string} id
234
+ * @returns {Promise<Handle>}
235
+ */
236
+ _reopen(type: string, id: string): Promise<Handle>;
227
237
  /**
228
238
  * Pause networking + storage. Idempotent; no-op on child handles.
229
239
  *