@cero-base/cero 0.5.2 → 0.7.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.
@@ -11,6 +11,8 @@ import { Pairing } from '@cero-base/core/pairing'
11
11
  import { toId } from '@cero-base/core/utils'
12
12
  import { CeroError } from '@cero-base/core/errors'
13
13
 
14
+ import { NS, TIMEOUT } from '../lib/constants.js'
15
+
14
16
  import { attachRefs } from '../lib/utils.js'
15
17
 
16
18
  export { Ref } from '../lib/utils.js'
@@ -64,8 +66,6 @@ export { Ref } from '../lib/utils.js'
64
66
  *
65
67
  * @typedef {object} RecoverOpts
66
68
  * @property {number} [timeout]
67
- * @property {string | null} [name]
68
- * @property {boolean} [isMobile]
69
69
  *
70
70
  * @typedef {object} HandleExtra
71
71
  * @property {string | null} [name] Display name; set on child handles by the owner flow.
@@ -137,16 +137,9 @@ export class Handle extends ReadyResource {
137
137
  await this.pair.ready()
138
138
  }
139
139
  attachRefs(this, this.store.refs)
140
- if (!this.children) return
141
-
142
- this._offUpdate = this.store.onUpdate(() => {
143
- if (this.closing || this.closed) return
144
- for (const c of this.children) this._syncMember(c).catch(this._onerror)
145
- })
146
140
  }
147
141
 
148
142
  async _close() {
149
- this._offUpdate?.()
150
143
  if (this.children) {
151
144
  for (const c of [...this.children]) await c.close()
152
145
  this.children.clear()
@@ -214,9 +207,9 @@ export class Handle extends ReadyResource {
214
207
  * @param {RecoverOpts} [opts]
215
208
  * @returns {Promise<void>}
216
209
  */
217
- async recover({ timeout = 30000, name = null, isMobile = false } = {}) {
210
+ async recover({ timeout = TIMEOUT } = {}) {
218
211
  if (this.store.writable) return
219
- await this.store.claim({ name, isMobile })
212
+ await this.store.claim()
220
213
  await this.store.whenWritable({ timeout })
221
214
  await this.store.bee.update()
222
215
  }
@@ -281,9 +274,7 @@ export class Handle extends ReadyResource {
281
274
  await this.store.call('add-writer', {
282
275
  sig,
283
276
  master: this.identity.publicKey,
284
- writer: writerKey,
285
- name: name || null,
286
- isMobile: false
277
+ writer: writerKey
287
278
  })
288
279
  await this.store.call('add-member', member)
289
280
  })
@@ -316,7 +307,7 @@ export class Handle extends ReadyResource {
316
307
  new Handle({
317
308
  parent: this,
318
309
  spec: pickHandle(this.spec, type),
319
- namespace: `cero/handle/${type}/${writer.id}`,
310
+ namespace: `${NS}/handle/${type}/${writer.id}`,
320
311
  routes,
321
312
  keyPair: writer
322
313
  })
@@ -332,9 +323,7 @@ export class Handle extends ReadyResource {
332
323
  await child.store.call('add-writer', {
333
324
  master: this.identity.publicKey,
334
325
  writer: writerKey,
335
- sig: this.identity.sign(writerKey),
336
- name,
337
- isMobile: false
326
+ sig: this.identity.sign(writerKey)
338
327
  })
339
328
  await child.store.call('add-member', {
340
329
  id: this.identity.id,
@@ -344,7 +333,6 @@ export class Handle extends ReadyResource {
344
333
  createdAt: ts,
345
334
  updatedAt: ts
346
335
  })
347
- if (name && child.profile) await child.store.set('profile', { name })
348
336
  await this.store.call('add-handle', {
349
337
  id,
350
338
  type,
@@ -357,7 +345,7 @@ export class Handle extends ReadyResource {
357
345
 
358
346
  if (accept !== false) this._wireAccept(child, { role })
359
347
  this.children.add(child)
360
- await this._syncMember(child)
348
+ this.emit('handle', child, { name, role })
361
349
  return child
362
350
  }
363
351
 
@@ -372,7 +360,7 @@ export class Handle extends ReadyResource {
372
360
  * @returns {Promise<Handle>}
373
361
  */
374
362
  async _join(invite, type, { routes, timeout } = {}) {
375
- const deadline = timeout || 30000
363
+ const deadline = timeout || TIMEOUT
376
364
 
377
365
  // Idempotent: if the invite targets a handle we already have, return it —
378
366
  // without pairing, so there's no waiting on a peer to confirm.
@@ -389,7 +377,7 @@ export class Handle extends ReadyResource {
389
377
  await Handle.join(invite, {
390
378
  parent: this,
391
379
  spec: pickHandle(this.spec, type),
392
- namespace: `cero/handle/${type}/${randomNs()}`,
380
+ namespace: `${NS}/handle/${type}/${randomNs()}`,
393
381
  routes,
394
382
  timeout
395
383
  })
@@ -400,20 +388,19 @@ export class Handle extends ReadyResource {
400
388
  const id = toId(child.store.key)
401
389
  await this._saveKeyPair(id, child.store.keyPair)
402
390
 
403
- const name = child.profile ? await waitForProfileName(child, deadline) : null
404
391
  const ts = Date.now()
405
392
  await this.store.call('add-handle', {
406
393
  id,
407
394
  type,
408
395
  key: child.store.key,
409
396
  encryptionKey: child.store.encryptionKey,
410
- name,
397
+ name: null,
411
398
  createdAt: ts,
412
399
  updatedAt: ts
413
400
  })
414
401
  this._wireAccept(child)
415
402
  this.children.add(child)
416
- await this._syncMember(child)
403
+ this.emit('handle', child, {})
417
404
  return child
418
405
  }
419
406
 
@@ -439,7 +426,7 @@ export class Handle extends ReadyResource {
439
426
  const child = new Handle({
440
427
  parent: this,
441
428
  spec: pickHandle(this.spec, type),
442
- namespace: `cero/handle/${type}/${id}`,
429
+ namespace: `${NS}/handle/${type}/${id}`,
443
430
  key: data.key,
444
431
  encryptionKey: data.encryptionKey,
445
432
  keyPair: /** @type {KeyPair} */ (writer)
@@ -456,6 +443,7 @@ export class Handle extends ReadyResource {
456
443
  }
457
444
  this._wireAccept(child)
458
445
  this.children.add(child)
446
+ this.emit('handle', child, {})
459
447
  return child
460
448
  }
461
449
 
@@ -503,7 +491,7 @@ export class Handle extends ReadyResource {
503
491
  */
504
492
  static async join(
505
493
  invite,
506
- { parent, network, identity, store, spec, namespace, routes, timeout = 30000 } = {}
494
+ { parent, network, identity, store, spec, namespace, routes, timeout = TIMEOUT } = {}
507
495
  ) {
508
496
  const net = network || parent?.network
509
497
  const id = identity || parent?.identity
@@ -538,20 +526,6 @@ export class Handle extends ReadyResource {
538
526
  })
539
527
  }
540
528
 
541
- async _syncMember(child) {
542
- if (!child.store.writable) return
543
- const { data: profile } = await this.store.get('profile')
544
- if (!profile) return
545
- const { data: existing } = await child.store.get('members', this.identity.id)
546
- if (!existing) return
547
- await child.store.call('set-member', {
548
- ...existing,
549
- ...profile,
550
- id: existing.id,
551
- updatedAt: Date.now()
552
- })
553
- }
554
-
555
529
  /**
556
530
  * @param {Handle} child
557
531
  * @param {{ role?: string }} [opts]
@@ -601,13 +575,3 @@ function pickHandle(spec, type) {
601
575
  function randomNs() {
602
576
  return z32.encode(Identity.randomBytes(8))
603
577
  }
604
-
605
- async function waitForProfileName(child, timeout) {
606
- const deadline = Date.now() + timeout
607
- while (Date.now() < deadline) {
608
- const { data } = await child.store.get('profile')
609
- if (data?.name) return data.name
610
- await new Promise((r) => setTimeout(r, 100))
611
- }
612
- return null
613
- }
package/src/index.js CHANGED
@@ -8,12 +8,14 @@ import { CeroError } from '@cero-base/core/errors'
8
8
 
9
9
  import { Handle, Ref } from './handle/index.js'
10
10
  import { Local } from './local/index.js'
11
- import { put, set, get, del, count, watch, call, open } from './lib/operators.js'
11
+ import { put, set, get, del, count, watch, call, open, before, after } from './lib/operators.js'
12
12
  import { peek } from './lib/peek.js'
13
13
  import { t, schema } from './lib/spec.js'
14
+ import { FLUSH } from './lib/constants.js'
15
+ import { internal } from './lib/internal.js'
14
16
 
15
17
  export { Handle, Ref, Local }
16
- export { put, set, get, del, count, watch, call, open } from './lib/operators.js'
18
+ export { put, set, get, del, count, watch, call, open, before, after } from './lib/operators.js'
17
19
  export { peek } from './lib/peek.js'
18
20
  export { t, schema } from './lib/spec.js'
19
21
 
@@ -65,7 +67,7 @@ export async function cero(dir, spec, opts = {}) {
65
67
  const network = new Network({ bootstrap: opts.bootstrap })
66
68
  await network.ready()
67
69
  const discovery = network.join(identity.topic)
68
- await Promise.race([discovery.flush(), new Promise((r) => setTimeout(r, 500))])
70
+ await Promise.race([discovery.flush(), new Promise((r) => setTimeout(r, FLUSH))])
69
71
 
70
72
  const me = new Handle({
71
73
  storage,
@@ -109,12 +111,12 @@ export async function cero(dir, spec, opts = {}) {
109
111
  })
110
112
  }
111
113
  }
112
- if (opts.recovery)
113
- await me.recover({
114
- timeout: opts.recoveryTimeout,
115
- name: opts.name || null,
116
- isMobile: opts.isMobile === true
117
- })
114
+ if (opts.recovery) await me.recover({ timeout: opts.recoveryTimeout })
115
+
116
+ for (const ext of internal.extensions) {
117
+ const off = await ext.setup?.(me)
118
+ if (typeof off === 'function') me.once('close', off)
119
+ }
118
120
 
119
121
  return me
120
122
  }
@@ -154,7 +156,26 @@ export async function restore(me, phrase) {
154
156
  })
155
157
  }
156
158
 
157
- Object.assign(cero, { put, set, get, del, count, watch, call, open, peek, restore, t, schema })
159
+ Object.assign(cero, {
160
+ t,
161
+ put,
162
+ set,
163
+ get,
164
+ del,
165
+ count,
166
+ watch,
167
+ call,
168
+ open,
169
+ before,
170
+ after,
171
+ peek,
172
+ restore,
173
+ schema
174
+ })
175
+ cero._internal = internal
176
+ // A bare function is shorthand for a behavior-only extension: `{ setup: fn }`.
177
+ cero.use = (...exts) =>
178
+ internal.extensions.push(...exts.map((e) => (typeof e === 'function' ? { setup: e } : e)))
158
179
 
159
180
  async function resolveIdentity(opts, local) {
160
181
  if (opts.identity) return opts.identity
@@ -0,0 +1,20 @@
1
+ // Shared cero constants.
2
+
3
+ export const NS = 'cero'
4
+ export const COUNTERS = 'counters'
5
+
6
+ // Writer-admission / pairing timeout (ms), and the initial discovery-flush wait (ms).
7
+ export const TIMEOUT = 30000
8
+ export const FLUSH = 500
9
+
10
+ // schema-DSL primitive → HyperDB type (used by the builder).
11
+ export const DB_TYPE = {
12
+ string: 'string',
13
+ uint: 'uint',
14
+ int: 'int',
15
+ bool: 'bool',
16
+ bytes: 'buffer',
17
+ json: 'json',
18
+ fixed32: 'fixed32',
19
+ fixed64: 'fixed64'
20
+ }
@@ -0,0 +1,3 @@
1
+ // Process-wide registry for extensions registered via `cero.use()`.
2
+ // `build()` folds in each extension's schema; `cero()` runs each setup.
3
+ export const internal = { extensions: [] }
@@ -18,13 +18,15 @@ export const put = (ref, row) => ref.handle.store.put(ref.name, row)
18
18
 
19
19
  /**
20
20
  * Upsert a row on `ref` — merges with the existing row and preserves
21
- * `createdAt`.
21
+ * `createdAt`. Pass `{ upsert: false }` to update-only: a missing row is left
22
+ * untouched instead of created (atomic — never resurrects a deleted row).
22
23
  *
23
24
  * @param {Ref} ref
24
25
  * @param {Record<string, any>} row
26
+ * @param {{ upsert?: boolean }} [opts]
25
27
  * @returns {Promise<SingleResult>}
26
28
  */
27
- export const set = (ref, row) => ref.handle.store.set(ref.name, row)
29
+ export const set = (ref, row, opts) => ref.handle.store.set(ref.name, row, opts)
28
30
 
29
31
  /**
30
32
  * Delete a row by id (collection refs), or wipe the row (single refs).
@@ -53,6 +55,43 @@ export const count = (ref, q) => ref.handle.store.count(ref.name, q)
53
55
  */
54
56
  export const call = (ref, d) => ref.handle.store.call(ref.name, d)
55
57
 
58
+ // Write ops per ref kind, for `before`/`after` subscriptions.
59
+ const WRITES = { single: ['set'], collection: ['put', 'set', 'del'] }
60
+
61
+ /**
62
+ * Intercept writes to `ref` before they commit — `fn(ctx)` runs in-path
63
+ * (awaited). Return `false` to cancel the write, or mutate `ctx.row`.
64
+ * Returns an unsubscribe fn.
65
+ *
66
+ * @param {Ref} ref
67
+ * @param {(ctx: { op: string, name: string, row: any }) => any} fn
68
+ * @returns {() => void}
69
+ */
70
+ export const before = (ref, fn) => {
71
+ const db = ref.handle.store
72
+ const ops = WRITES[ref.kind] || ['set']
73
+ const offs = ops.map((op) =>
74
+ db.before(op, (ctx) => (ctx.name === ref.name ? fn(ctx) : undefined))
75
+ )
76
+ return () => offs.forEach((off) => off())
77
+ }
78
+
79
+ /**
80
+ * Subscribe to writes on `ref` — fires after each committed write,
81
+ * non-blocking (observe only). Returns an unsubscribe fn.
82
+ *
83
+ * @param {Ref} ref
84
+ * @param {(ctx: { op: string, name: string, row: any }) => void} fn
85
+ * @returns {() => void}
86
+ */
87
+ export const after = (ref, fn) => {
88
+ const db = ref.handle.store
89
+ const ops = WRITES[ref.kind] || ['set']
90
+ const handler = (ctx) => ctx.name === ref.name && fn(ctx)
91
+ for (const op of ops) db.on(`after:${op}`, handler)
92
+ return () => ops.forEach((op) => db.off(`after:${op}`, handler))
93
+ }
94
+
56
95
  // get/watch on a handle-kind ref list its rows from the `handles` collection
57
96
  // filtered by type (handle types are stored there with their { id, key,
58
97
  // encryptionKey, name }). Data-kind refs go straight to the store.
@@ -0,0 +1,88 @@
1
+ export namespace refs {
2
+ namespace main {
3
+ namespace members {
4
+ let type: string;
5
+ }
6
+ namespace devices {
7
+ let type_1: string;
8
+ export { type_1 as type };
9
+ }
10
+ namespace invites {
11
+ let type_2: string;
12
+ export { type_2 as type };
13
+ }
14
+ namespace handles {
15
+ let type_3: string;
16
+ export { type_3 as type };
17
+ }
18
+ }
19
+ let local: {
20
+ master: {
21
+ type: string;
22
+ kind: string;
23
+ };
24
+ keypair: {
25
+ type: string;
26
+ kind: string;
27
+ };
28
+ 'handle-keypairs': {
29
+ type: string;
30
+ };
31
+ };
32
+ }
33
+ export function hyperdbType(prim: any): any;
34
+ export function builtinRefs(ns: any, scope: any): {
35
+ [k: string]: {
36
+ schema: string;
37
+ verb: any;
38
+ kind: any;
39
+ path: string[];
40
+ builtin: boolean;
41
+ };
42
+ };
43
+ export function builtinTypes(scope: any, extend: any): {
44
+ name: string;
45
+ compact: boolean;
46
+ fields: {
47
+ name: string;
48
+ type: any;
49
+ required: boolean;
50
+ }[];
51
+ }[];
52
+ export function rpcTypes(): {
53
+ name: string;
54
+ compact: boolean;
55
+ fields: {
56
+ name: string;
57
+ type: any;
58
+ required: boolean;
59
+ }[];
60
+ }[];
61
+ export function builtinCollections(ns: any, scope: any): {
62
+ name: string;
63
+ schema: string;
64
+ key: string[];
65
+ }[];
66
+ export function builtinDispatches(ns: any): {
67
+ name: string;
68
+ requestType: string;
69
+ }[];
70
+ export function rpcCommands(ns: any): ({
71
+ name: string;
72
+ request: {
73
+ name: string;
74
+ };
75
+ response: {
76
+ name: string;
77
+ stream?: undefined;
78
+ };
79
+ } | {
80
+ name: string;
81
+ request: {
82
+ name: string;
83
+ };
84
+ response: {
85
+ name: string;
86
+ stream: boolean;
87
+ };
88
+ })[];
@@ -0,0 +1,153 @@
1
+ export const main: {
2
+ 'del-by-id': {
3
+ id: import("@cero-base/core/schema").Prim;
4
+ };
5
+ writer: {
6
+ master: import("@cero-base/core/schema").Prim;
7
+ writer: import("@cero-base/core/schema").Prim;
8
+ sig: import("@cero-base/core/schema").Prim;
9
+ isIndexer: import("@cero-base/core/schema").Prim;
10
+ };
11
+ counter: {
12
+ name: import("@cero-base/core/schema").Prim;
13
+ value: import("@cero-base/core/schema").Prim;
14
+ };
15
+ member: {
16
+ id: import("@cero-base/core/schema").Prim;
17
+ key: import("@cero-base/core/schema").Prim;
18
+ role: import("@cero-base/core/schema").Prim;
19
+ name: import("@cero-base/core/schema").Prim;
20
+ createdAt: import("@cero-base/core/schema").Prim;
21
+ updatedAt: import("@cero-base/core/schema").Prim;
22
+ sig: import("@cero-base/core/schema").Prim;
23
+ index: import("@cero-base/core/schema").Prim;
24
+ };
25
+ device: {
26
+ id: import("@cero-base/core/schema").Prim;
27
+ memberId: import("@cero-base/core/schema").Prim;
28
+ name: import("@cero-base/core/schema").Prim;
29
+ isMobile: import("@cero-base/core/schema").Prim;
30
+ createdAt: import("@cero-base/core/schema").Prim;
31
+ updatedAt: import("@cero-base/core/schema").Prim;
32
+ index: import("@cero-base/core/schema").Prim;
33
+ };
34
+ invite: {
35
+ id: import("@cero-base/core/schema").Prim;
36
+ invite: import("@cero-base/core/schema").Prim;
37
+ publicKey: import("@cero-base/core/schema").Prim;
38
+ data: import("@cero-base/core/schema").Prim;
39
+ sig: import("@cero-base/core/schema").Prim;
40
+ role: import("@cero-base/core/schema").Prim;
41
+ expires: import("@cero-base/core/schema").Prim;
42
+ createdAt: import("@cero-base/core/schema").Prim;
43
+ index: import("@cero-base/core/schema").Prim;
44
+ };
45
+ handle: {
46
+ id: import("@cero-base/core/schema").Prim;
47
+ type: import("@cero-base/core/schema").Prim;
48
+ key: import("@cero-base/core/schema").Prim;
49
+ encryptionKey: import("@cero-base/core/schema").Prim;
50
+ name: import("@cero-base/core/schema").Prim;
51
+ createdAt: import("@cero-base/core/schema").Prim;
52
+ updatedAt: import("@cero-base/core/schema").Prim;
53
+ index: import("@cero-base/core/schema").Prim;
54
+ };
55
+ claim: {
56
+ identity: import("@cero-base/core/schema").Prim;
57
+ writer: import("@cero-base/core/schema").Prim;
58
+ sig: import("@cero-base/core/schema").Prim;
59
+ };
60
+ };
61
+ export const local: {
62
+ master: {
63
+ seed: import("@cero-base/core/schema").Prim;
64
+ };
65
+ keypair: {
66
+ publicKey: import("@cero-base/core/schema").Prim;
67
+ secretKey: import("@cero-base/core/schema").Prim;
68
+ };
69
+ 'handle-keypair': {
70
+ id: import("@cero-base/core/schema").Prim;
71
+ publicKey: import("@cero-base/core/schema").Prim;
72
+ secretKey: import("@cero-base/core/schema").Prim;
73
+ encryptionKey: import("@cero-base/core/schema").Prim;
74
+ };
75
+ };
76
+ export const rpc: {
77
+ 'req-empty': {
78
+ ok: import("@cero-base/core/schema").Prim;
79
+ };
80
+ 'req-restore': {
81
+ phrase: import("@cero-base/core/schema").Prim;
82
+ };
83
+ 'req-row': {
84
+ handle: import("@cero-base/core/schema").Prim;
85
+ ref: import("@cero-base/core/schema").Prim;
86
+ data: import("@cero-base/core/schema").Prim;
87
+ local: import("@cero-base/core/schema").Prim;
88
+ };
89
+ 'req-id': {
90
+ handle: import("@cero-base/core/schema").Prim;
91
+ ref: import("@cero-base/core/schema").Prim;
92
+ id: import("@cero-base/core/schema").Prim;
93
+ local: import("@cero-base/core/schema").Prim;
94
+ };
95
+ 'req-query': {
96
+ handle: import("@cero-base/core/schema").Prim;
97
+ ref: import("@cero-base/core/schema").Prim;
98
+ query: import("@cero-base/core/schema").Prim;
99
+ local: import("@cero-base/core/schema").Prim;
100
+ };
101
+ 'req-call': {
102
+ handle: import("@cero-base/core/schema").Prim;
103
+ op: import("@cero-base/core/schema").Prim;
104
+ data: import("@cero-base/core/schema").Prim;
105
+ };
106
+ 'req-invite': {
107
+ handle: import("@cero-base/core/schema").Prim;
108
+ role: import("@cero-base/core/schema").Prim;
109
+ };
110
+ 'req-revoke': {
111
+ handle: import("@cero-base/core/schema").Prim;
112
+ invite: import("@cero-base/core/schema").Prim;
113
+ };
114
+ 'req-join': {
115
+ parent: import("@cero-base/core/schema").Prim;
116
+ ref: import("@cero-base/core/schema").Prim;
117
+ invite: import("@cero-base/core/schema").Prim;
118
+ };
119
+ 'req-open': {
120
+ parent: import("@cero-base/core/schema").Prim;
121
+ row: import("@cero-base/core/schema").Prim;
122
+ };
123
+ 'req-handle': {
124
+ handle: import("@cero-base/core/schema").Prim;
125
+ };
126
+ 'res-data': {
127
+ data: import("@cero-base/core/schema").Prim;
128
+ };
129
+ 'res-rows': {
130
+ data: import("@cero-base/core/schema").Prim;
131
+ total: import("@cero-base/core/schema").Prim;
132
+ size: import("@cero-base/core/schema").Prim;
133
+ };
134
+ 'res-count': {
135
+ count: import("@cero-base/core/schema").Prim;
136
+ };
137
+ 'res-invite': {
138
+ invite: import("@cero-base/core/schema").Prim;
139
+ };
140
+ 'res-handle': {
141
+ id: import("@cero-base/core/schema").Prim;
142
+ type: import("@cero-base/core/schema").Prim;
143
+ name: import("@cero-base/core/schema").Prim;
144
+ };
145
+ 'res-identity': {
146
+ id: import("@cero-base/core/schema").Prim;
147
+ deviceId: import("@cero-base/core/schema").Prim;
148
+ phrase: import("@cero-base/core/schema").Prim;
149
+ };
150
+ 'res-ok': {
151
+ ok: import("@cero-base/core/schema").Prim;
152
+ };
153
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Mirror a child handle's `profile` (name + avatar) onto its row in the parent's
3
+ * `handles` list — so a handle list shows names + avatars without opening each
4
+ * one. Adds the synced `fields` to the `handle` builtin.
5
+ *
6
+ * On create it seeds the handle's `profile.name` from the open `{ name }`, then
7
+ * reflects the (app-owned) `profile` onto the row. Requires your handle types to
8
+ * declare a `profile` single — handles without one are left untouched.
9
+ *
10
+ * @param {{ fields?: Record<string, any> }} [opts]
11
+ */
12
+ export function handleSync({ fields }?: {
13
+ fields?: Record<string, any>;
14
+ }): {
15
+ schema: {
16
+ handles: {
17
+ kind: "extend";
18
+ fields: Record<string, import("@cero-base/core/schema").Prim>;
19
+ };
20
+ };
21
+ setup(me: any): void;
22
+ };
@@ -0,0 +1,2 @@
1
+ export { profileSync } from "./profile-sync.js";
2
+ export { handleSync } from "./handle-sync.js";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Mirror your `profile` onto your `member` row in every room. Declares a
3
+ * `profile` single (`name`, `avatar`, plus any extra `fields`) and mirrors them
4
+ * onto the `member` builtin, then publishes when you open/join a room and whenever
5
+ * your profile changes. An app may declare its own richer `profile` instead —
6
+ * the app schema wins.
7
+ *
8
+ * @param {{ fields?: Record<string, any> }} [opts]
9
+ */
10
+ export function profileSync({ fields }?: {
11
+ fields?: Record<string, any>;
12
+ }): {
13
+ schema: {
14
+ profile: import("@cero-base/core/schema").TypeDef;
15
+ members: {
16
+ kind: "extend";
17
+ fields: Record<string, import("@cero-base/core/schema").Prim>;
18
+ };
19
+ };
20
+ setup(me: any): void;
21
+ };
@@ -48,8 +48,6 @@ export { Ref } from "../lib/utils.js";
48
48
  *
49
49
  * @typedef {object} RecoverOpts
50
50
  * @property {number} [timeout]
51
- * @property {string | null} [name]
52
- * @property {boolean} [isMobile]
53
51
  *
54
52
  * @typedef {object} HandleExtra
55
53
  * @property {string | null} [name] Display name; set on child handles by the owner flow.
@@ -91,7 +89,6 @@ export class Handle extends ReadyResource {
91
89
  store: Database;
92
90
  pair: Pairing;
93
91
  _wantsPair: boolean;
94
- _offUpdate: () => void;
95
92
  /** Canonical id — identity id for the root handle, store key for children. */
96
93
  get id(): any;
97
94
  /** This device's id + name. `null` on child handles. */
@@ -126,7 +123,7 @@ export class Handle extends ReadyResource {
126
123
  * @param {RecoverOpts} [opts]
127
124
  * @returns {Promise<void>}
128
125
  */
129
- recover({ timeout, name, isMobile }?: RecoverOpts): Promise<void>;
126
+ recover({ timeout }?: RecoverOpts): Promise<void>;
130
127
  /**
131
128
  * Mint a pairing invite for this handle.
132
129
  *
@@ -205,7 +202,6 @@ export class Handle extends ReadyResource {
205
202
  * @returns {Promise<void>}
206
203
  */
207
204
  resume(): Promise<void>;
208
- _syncMember(child: any): Promise<void>;
209
205
  /**
210
206
  * @param {Handle} child
211
207
  * @param {{ role?: string }} [opts]
@@ -326,8 +322,6 @@ export type AcceptOpts = {
326
322
  };
327
323
  export type RecoverOpts = {
328
324
  timeout?: number;
329
- name?: string | null;
330
- isMobile?: boolean;
331
325
  };
332
326
  export type HandleExtra = {
333
327
  /**