@cero-base/cero 0.7.0 → 0.8.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/README.md CHANGED
@@ -140,16 +140,18 @@ const { data: n } = await cero.count(room.messages)
140
140
  const { data: n } = await cero.count(room.messages, { gt: 'm-2025' })
141
141
  ```
142
142
 
143
- ### `cero.watch(ref, q?)`
143
+ ### `cero.watch(ref, q?, opts?)`
144
144
 
145
- A Readable stream that re-emits the latest snapshot on every change. Always emits an initial snapshot.
145
+ A Readable stream that re-emits the latest snapshot on every change. Always emits an initial snapshot. The stream is **tied to the ref's handle** — closing the handle destroys it, so a watch never outlives its store and you don't track cleanup. Pass `{ signal }` to bind it to a finer scope instead.
146
146
 
147
147
  ```js
148
148
  const stream = cero.watch(room.messages, { limit: 50, reverse: true })
149
149
  stream.on('data', ({ data, total, size }) => {
150
150
  /* render */
151
151
  })
152
- // stream.destroy() to stop
152
+ // stops on room.close(), on stream.destroy(), or on `signal` abort:
153
+ const ac = new AbortController()
154
+ cero.watch(room.messages, null, { signal: ac.signal }) // ac.abort() ⇒ destroyed
153
155
  ```
154
156
 
155
157
  ### `cero.call(actionRef, data)`
@@ -173,9 +175,9 @@ await cero.open(me.room, { id: existingRoomId }) // load an existing room by id
173
175
 
174
176
  Returns the child handle, ready to operate on (`cero.put(joined.messages, …)` etc.).
175
177
 
176
- ### `cero.before(ref, fn)` / `cero.after(ref, fn)`
178
+ ### `cero.before(ref, fn, opts?)` / `cero.after(ref, fn, opts?)`
177
179
 
178
- Hook into writes to a ref. `before` runs **in-path** before the write commits — return `false` to cancel, or mutate `ctx.row`. `after` is a non-blocking **event** that fires once the write has committed. Both return an unsubscribe fn — call it to stop early, or ignore it for a subscription that should live as long as the handle (it's dropped when the handle closes).
180
+ Hook into writes to a ref. `before` runs **in-path** before the write commits — return `false` to cancel, or mutate `ctx.row`. `after` is a non-blocking **event** that fires once the write has committed. Both return an unsubscribe fn — call it to stop early, ignore it for a subscription that lives as long as the handle, or pass `{ signal }` to unsubscribe when an `AbortSignal` fires (e.g. `{ signal: me.signal }` to stop on close).
179
181
 
180
182
  ```js
181
183
  const off = cero.before(room.messages, (ctx) => {
@@ -207,9 +209,10 @@ function myExtension() {
207
209
  return {
208
210
  schema: { members: cero.t.extend({ status: cero.t.string }) }, // merged into your schema
209
211
  setup(me) {
210
- me.on('handle', (room, opts) => {
212
+ const onHandle = (room, opts) => {
211
213
  /* opts carries the open args (e.g. opts.name on create); me.children is the set of open rooms */
212
- })
214
+ }
215
+ me.on('handle', onHandle, { signal: me.signal }) // dropped on me.close()
213
216
  }
214
217
  }
215
218
  }
@@ -229,12 +232,26 @@ Build and the running app are separate processes, so `cero.use()` runs in both
229
232
 
230
233
  ### Cleanup
231
234
 
232
- `setup(me)` may return a disposer; cero calls it on `me.close()`. You rarely need it`before`/`after` and `me.on(...)` registered on `me` or its rooms are torn down automatically when the handle closes. Return a disposer **only** for things the handle's close won't clean up, like a timer or an external connection:
235
+ You rarely write teardown — a handle's subscriptions follow the same "dies with the node" model as DOM events:
236
+
237
+ - **`cero.watch(ref)` streams** are destroyed automatically when the ref's handle closes.
238
+ - **`me.on(...)`, `before`, `after`** take `{ signal }` to drop themselves when an `AbortSignal` fires.
239
+ - **`me.signal`** is an `AbortSignal` that aborts on `me.close()`. Pass it to tie a subscription to the handle's life, or use your own `AbortController` for a finer scope (drop it before the handle closes):
240
+
241
+ ```js
242
+ setup(me) {
243
+ me.on('handle', onHandle, { signal: me.signal }) // dropped when me closes
244
+ cero.after(me.profile, onProfile, { signal: me.signal })
245
+ }
246
+ ```
247
+
248
+ For anything the close cascade won't reach — a timer, an external connection, a stream you made yourself — hand it to **`me.own(resource)`** (destroyed on close) or return a disposer from `setup`:
233
249
 
234
250
  ```js
235
251
  setup(me) {
236
252
  const timer = setInterval(() => ping(me), 30_000)
237
- return () => clearInterval(timer)
253
+ me.own({ destroy: () => clearInterval(timer) }) // tied to me.close()
254
+ // …or: return () => clearInterval(timer)
238
255
  }
239
256
  ```
240
257
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cero-base/cero",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
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",
@@ -74,7 +74,7 @@
74
74
  "test": "ls test/*.test.js | xargs -P2 -n1 brittle-node"
75
75
  },
76
76
  "dependencies": {
77
- "@cero-base/core": "^0.7.0",
77
+ "@cero-base/core": "^0.8.0",
78
78
  "b4a": "^1.8.1",
79
79
  "bare-crypto": "^1.13.7",
80
80
  "bare-fs": "^4.7.1",
package/src/CLAUDE.md ADDED
@@ -0,0 +1,3 @@
1
+ <claude-mem-context>
2
+
3
+ </claude-mem-context>
@@ -0,0 +1,3 @@
1
+ <claude-mem-context>
2
+
3
+ </claude-mem-context>
@@ -16,17 +16,16 @@ export function handleSync({ fields = { avatar: t.string } } = {}) {
16
16
  return {
17
17
  schema: { handles: t.extend(fields) },
18
18
  setup(me) {
19
- me.on('handle', (child, opts) => {
19
+ const onHandle = (child, opts) => {
20
20
  if (!child.profile) return
21
- if (opts.name) set(child.profile, { name: opts.name })
22
- const sub = watch(child.profile)
23
- sub.on('data', ({ data }) => {
21
+ if (opts.name) set(child.profile, { name: opts.name }).catch(me._onerror)
22
+ watch(child.profile).on('data', ({ data }) => {
24
23
  if (!data?.name) return
25
24
  child.name = data.name
26
- set(me.handles, { id: child.id, ...data }, { upsert: false })
25
+ set(me.handles, { id: child.id, ...data }, { upsert: false }).catch(me._onerror)
27
26
  })
28
- child.once('close', () => sub.destroy())
29
- })
27
+ }
28
+ me.on('handle', onHandle, { signal: me.signal })
30
29
  }
31
30
  }
32
31
  }
@@ -1,2 +1,2 @@
1
- export { profileSync } from './profile-sync.js'
2
- export { handleSync } from './handle-sync.js'
1
+ export * from './profile-sync.js'
2
+ export * from './handle-sync.js'
@@ -2,11 +2,11 @@ import { t } from '../lib/spec.js'
2
2
  import { get, set, after } from '../lib/operators.js'
3
3
 
4
4
  /**
5
- * Mirror your `profile` onto your `member` row in every room. Declares a
6
- * `profile` single (`name`, `avatar`, plus any extra `fields`) and mirrors them
7
- * onto the `member` builtin, then publishes when you open/join a room and whenever
8
- * your profile changes. An app may declare its own richer `profile` instead —
9
- * the app schema wins.
5
+ * Mirror your `profile` onto your `member` row in every handle you're in.
6
+ * Declares a `profile` single (`name`, `avatar`, plus any extra `fields`) and
7
+ * mirrors them onto the `member` builtin, then publishes when you open/join a
8
+ * handle and whenever your profile changes. An app may declare its own richer
9
+ * `profile` instead — the app schema wins.
10
10
  *
11
11
  * @param {{ fields?: Record<string, any> }} [opts]
12
12
  */
@@ -17,14 +17,20 @@ export function profileSync({ fields = { avatar: t.string } } = {}) {
17
17
  members: t.extend(fields)
18
18
  },
19
19
  setup(me) {
20
- const publish = async (room) => {
21
- const { data: member } = await get(room.members, me.identity.id)
22
- if (!member) return
20
+ const publish = (child, profile) =>
21
+ set(child.members, { id: me.identity.id, ...profile }, { upsert: false }).catch(me._onerror)
22
+
23
+ const onHandle = async (child) => {
23
24
  const { data: profile } = await get(me.profile)
24
- if (profile) await set(room.members, { id: me.identity.id, ...profile })
25
+ if (profile) publish(child, profile)
26
+ }
27
+
28
+ const onProfile = (ctx) => {
29
+ if (ctx.row) me.children.forEach((child) => publish(child, ctx.row))
25
30
  }
26
- me.on('handle', publish)
27
- after(me.profile, () => me.children.forEach(publish))
31
+
32
+ me.on('handle', onHandle, { signal: me.signal })
33
+ after(me.profile, onProfile, { signal: me.signal })
28
34
  }
29
35
  }
30
36
  }
@@ -13,7 +13,7 @@ import { CeroError } from '@cero-base/core/errors'
13
13
 
14
14
  import { NS, TIMEOUT } from '../lib/constants.js'
15
15
 
16
- import { attachRefs } from '../lib/utils.js'
16
+ import { attachRefs, onAbort } from '../lib/utils.js'
17
17
 
18
18
  export { Ref } from '../lib/utils.js'
19
19
 
@@ -110,6 +110,7 @@ export class Handle extends ReadyResource {
110
110
  this._opts = opts.opts || {}
111
111
  this._onerror = this._opts.onerror || safetyCatch
112
112
  this.children = parent ? null : new Set()
113
+ this._owned = new Set()
113
114
 
114
115
  this.store = new Database({
115
116
  store: store,
@@ -140,6 +141,8 @@ export class Handle extends ReadyResource {
140
141
  }
141
142
 
142
143
  async _close() {
144
+ for (const r of [...this._owned]) r.destroy?.()
145
+ this._owned.clear()
143
146
  if (this.children) {
144
147
  for (const c of [...this.children]) await c.close()
145
148
  this.children.clear()
@@ -161,6 +164,56 @@ export class Handle extends ReadyResource {
161
164
  if (this._storage) await this._storage.close()
162
165
  }
163
166
 
167
+ /**
168
+ * Tie a destroyable resource (a `watch` stream, a timer, any `{ destroy }`)
169
+ * to this handle's lifecycle — it's destroyed automatically on close, so
170
+ * callers don't track cleanup. De-registers itself if destroyed earlier.
171
+ *
172
+ * @template {{ destroy?: Function, once?: Function }} T
173
+ * @param {T} resource
174
+ * @returns {T}
175
+ */
176
+ own(resource) {
177
+ if (this.closing || this.closed) {
178
+ resource.destroy?.()
179
+ return resource
180
+ }
181
+ this._owned.add(resource)
182
+ resource.once?.('close', () => this._owned.delete(resource))
183
+ return resource
184
+ }
185
+
186
+ /**
187
+ * An `AbortSignal` that fires when this handle closes. Pass it as
188
+ * `{ signal }` to `on`/`after`/`before`/`watch` to drop a subscription on
189
+ * close — or use your own `AbortController` for a finer scope.
190
+ *
191
+ * @returns {AbortSignal}
192
+ */
193
+ get signal() {
194
+ if (!this._ac) {
195
+ this._ac = new AbortController()
196
+ if (this.closed) this._ac.abort()
197
+ else this.once('close', () => this._ac.abort())
198
+ }
199
+ return this._ac.signal
200
+ }
201
+
202
+ /**
203
+ * `EventEmitter.on` plus an optional `{ signal }` that removes the listener
204
+ * when the signal aborts — e.g. `me.on('handle', fn, { signal: me.signal })`.
205
+ *
206
+ * @param {string} event
207
+ * @param {(...args: any[]) => void} fn
208
+ * @param {{ signal?: AbortSignal }} [opts]
209
+ * @returns {this}
210
+ */
211
+ on(event, fn, opts) {
212
+ super.on(event, fn)
213
+ onAbort(opts?.signal, () => super.off(event, fn))
214
+ return this
215
+ }
216
+
164
217
  /** Canonical id — identity id for the root handle, store key for children. */
165
218
  get id() {
166
219
  if (!this.parent) return this.identity.id
@@ -0,0 +1,3 @@
1
+ <claude-mem-context>
2
+
3
+ </claude-mem-context>
@@ -1,5 +1,7 @@
1
1
  import { Readable } from 'streamx'
2
2
 
3
+ import { onAbort } from './utils.js'
4
+
3
5
  /**
4
6
  * @typedef {import('./utils.js').Ref} Ref
5
7
  * @typedef {{ data: any }} SingleResult
@@ -61,35 +63,42 @@ const WRITES = { single: ['set'], collection: ['put', 'set', 'del'] }
61
63
  /**
62
64
  * Intercept writes to `ref` before they commit — `fn(ctx)` runs in-path
63
65
  * (awaited). Return `false` to cancel the write, or mutate `ctx.row`.
64
- * Returns an unsubscribe fn.
66
+ * Returns an unsubscribe fn; pass `{ signal }` to unsubscribe on abort.
65
67
  *
66
68
  * @param {Ref} ref
67
69
  * @param {(ctx: { op: string, name: string, row: any }) => any} fn
70
+ * @param {{ signal?: AbortSignal }} [opts]
68
71
  * @returns {() => void}
69
72
  */
70
- export const before = (ref, fn) => {
73
+ export const before = (ref, fn, opts) => {
71
74
  const db = ref.handle.store
72
75
  const ops = WRITES[ref.kind] || ['set']
73
76
  const offs = ops.map((op) =>
74
77
  db.before(op, (ctx) => (ctx.name === ref.name ? fn(ctx) : undefined))
75
78
  )
76
- return () => offs.forEach((off) => off())
79
+ const off = () => offs.forEach((unsub) => unsub())
80
+ onAbort(opts?.signal, off)
81
+ return off
77
82
  }
78
83
 
79
84
  /**
80
85
  * Subscribe to writes on `ref` — fires after each committed write,
81
- * non-blocking (observe only). Returns an unsubscribe fn.
86
+ * non-blocking (observe only). Returns an unsubscribe fn; pass `{ signal }`
87
+ * to unsubscribe on abort.
82
88
  *
83
89
  * @param {Ref} ref
84
90
  * @param {(ctx: { op: string, name: string, row: any }) => void} fn
91
+ * @param {{ signal?: AbortSignal }} [opts]
85
92
  * @returns {() => void}
86
93
  */
87
- export const after = (ref, fn) => {
94
+ export const after = (ref, fn, opts) => {
88
95
  const db = ref.handle.store
89
96
  const ops = WRITES[ref.kind] || ['set']
90
97
  const handler = (ctx) => ctx.name === ref.name && fn(ctx)
91
98
  for (const op of ops) db.on(`after:${op}`, handler)
92
- return () => ops.forEach((op) => db.off(`after:${op}`, handler))
99
+ const off = () => ops.forEach((op) => db.off(`after:${op}`, handler))
100
+ onAbort(opts?.signal, off)
101
+ return off
93
102
  }
94
103
 
95
104
  // get/watch on a handle-kind ref list its rows from the `handles` collection
@@ -118,16 +127,28 @@ export const get = async (ref, q) => {
118
127
  return normalize(all, ref.name)
119
128
  }
120
129
 
130
+ // Tie a fresh watch stream to its handle's lifecycle (destroyed on close) and
131
+ // to an optional `{ signal }` (destroyed on abort). Local refs have no
132
+ // close-cascade, so they keep managing their own streams.
133
+ const bindStream = (owner, stream, opts) => {
134
+ onAbort(opts?.signal, () => stream.destroy())
135
+ return owner.own ? owner.own(stream) : stream
136
+ }
137
+
121
138
  /**
122
139
  * Live snapshot stream on `ref` — re-emits the latest `get()` result on
123
- * every underlying mutation. Destroy the stream to stop watching.
140
+ * every underlying mutation. Tied to `ref.handle`'s lifecycle: closing the
141
+ * handle destroys it. Pass `{ signal }` to bind it to a finer scope, or
142
+ * destroy the stream directly to stop watching sooner.
124
143
  *
125
144
  * @param {Ref} ref
126
145
  * @param {Record<string, any>} [q]
146
+ * @param {{ signal?: AbortSignal }} [opts]
127
147
  * @returns {import('streamx').Readable}
128
148
  */
129
- export const watch = (ref, q) => {
130
- if (ref.kind !== 'handle') return ref.handle.store.watch(ref.name, q)
149
+ export const watch = (ref, q, opts) => {
150
+ const owner = ref.handle
151
+ if (ref.kind !== 'handle') return bindStream(owner, owner.store.watch(ref.name, q), opts)
131
152
  const source = parentStore(ref).watch('handles', q)
132
153
  const out = new Readable({
133
154
  destroy(cb) {
@@ -138,7 +159,7 @@ export const watch = (ref, q) => {
138
159
  source.on('data', (snap) => out.push(normalize(snap?.data, ref.name)))
139
160
  source.on('end', () => out.push(null))
140
161
  source.on('error', (err) => out.destroy(err))
141
- return out
162
+ return bindStream(owner, out, opts)
142
163
  }
143
164
 
144
165
  // Universal signal instantiator. Dispatch on the second arg:
package/src/lib/utils.js CHANGED
@@ -39,3 +39,16 @@ export function attachRefs(target, refs) {
39
39
  target[name] = new Ref(target, name, info.kind, info.schema)
40
40
  }
41
41
  }
42
+
43
+ /**
44
+ * Run `cb` when `signal` aborts — or immediately if it already has. No-op
45
+ * without a signal. The listener removes itself on fire.
46
+ *
47
+ * @param {AbortSignal} [signal]
48
+ * @param {() => void} cb
49
+ */
50
+ export function onAbort(signal, cb) {
51
+ if (!signal) return
52
+ if (signal.aborted) return cb()
53
+ signal.addEventListener('abort', cb, { once: true })
54
+ }
@@ -1,153 +1,153 @@
1
1
  export const main: {
2
2
  'del-by-id': {
3
- id: import("@cero-base/core/schema").Prim;
3
+ id: import("@cero-base/core").Prim;
4
4
  };
5
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;
6
+ master: import("@cero-base/core").Prim;
7
+ writer: import("@cero-base/core").Prim;
8
+ sig: import("@cero-base/core").Prim;
9
+ isIndexer: import("@cero-base/core").Prim;
10
10
  };
11
11
  counter: {
12
- name: import("@cero-base/core/schema").Prim;
13
- value: import("@cero-base/core/schema").Prim;
12
+ name: import("@cero-base/core").Prim;
13
+ value: import("@cero-base/core").Prim;
14
14
  };
15
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;
16
+ id: import("@cero-base/core").Prim;
17
+ key: import("@cero-base/core").Prim;
18
+ role: import("@cero-base/core").Prim;
19
+ name: import("@cero-base/core").Prim;
20
+ createdAt: import("@cero-base/core").Prim;
21
+ updatedAt: import("@cero-base/core").Prim;
22
+ sig: import("@cero-base/core").Prim;
23
+ index: import("@cero-base/core").Prim;
24
24
  };
25
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;
26
+ id: import("@cero-base/core").Prim;
27
+ memberId: import("@cero-base/core").Prim;
28
+ name: import("@cero-base/core").Prim;
29
+ isMobile: import("@cero-base/core").Prim;
30
+ createdAt: import("@cero-base/core").Prim;
31
+ updatedAt: import("@cero-base/core").Prim;
32
+ index: import("@cero-base/core").Prim;
33
33
  };
34
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;
35
+ id: import("@cero-base/core").Prim;
36
+ invite: import("@cero-base/core").Prim;
37
+ publicKey: import("@cero-base/core").Prim;
38
+ data: import("@cero-base/core").Prim;
39
+ sig: import("@cero-base/core").Prim;
40
+ role: import("@cero-base/core").Prim;
41
+ expires: import("@cero-base/core").Prim;
42
+ createdAt: import("@cero-base/core").Prim;
43
+ index: import("@cero-base/core").Prim;
44
44
  };
45
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;
46
+ id: import("@cero-base/core").Prim;
47
+ type: import("@cero-base/core").Prim;
48
+ key: import("@cero-base/core").Prim;
49
+ encryptionKey: import("@cero-base/core").Prim;
50
+ name: import("@cero-base/core").Prim;
51
+ createdAt: import("@cero-base/core").Prim;
52
+ updatedAt: import("@cero-base/core").Prim;
53
+ index: import("@cero-base/core").Prim;
54
54
  };
55
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;
56
+ identity: import("@cero-base/core").Prim;
57
+ writer: import("@cero-base/core").Prim;
58
+ sig: import("@cero-base/core").Prim;
59
59
  };
60
60
  };
61
61
  export const local: {
62
62
  master: {
63
- seed: import("@cero-base/core/schema").Prim;
63
+ seed: import("@cero-base/core").Prim;
64
64
  };
65
65
  keypair: {
66
- publicKey: import("@cero-base/core/schema").Prim;
67
- secretKey: import("@cero-base/core/schema").Prim;
66
+ publicKey: import("@cero-base/core").Prim;
67
+ secretKey: import("@cero-base/core").Prim;
68
68
  };
69
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;
70
+ id: import("@cero-base/core").Prim;
71
+ publicKey: import("@cero-base/core").Prim;
72
+ secretKey: import("@cero-base/core").Prim;
73
+ encryptionKey: import("@cero-base/core").Prim;
74
74
  };
75
75
  };
76
76
  export const rpc: {
77
77
  'req-empty': {
78
- ok: import("@cero-base/core/schema").Prim;
78
+ ok: import("@cero-base/core").Prim;
79
79
  };
80
80
  'req-restore': {
81
- phrase: import("@cero-base/core/schema").Prim;
81
+ phrase: import("@cero-base/core").Prim;
82
82
  };
83
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;
84
+ handle: import("@cero-base/core").Prim;
85
+ ref: import("@cero-base/core").Prim;
86
+ data: import("@cero-base/core").Prim;
87
+ local: import("@cero-base/core").Prim;
88
88
  };
89
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;
90
+ handle: import("@cero-base/core").Prim;
91
+ ref: import("@cero-base/core").Prim;
92
+ id: import("@cero-base/core").Prim;
93
+ local: import("@cero-base/core").Prim;
94
94
  };
95
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;
96
+ handle: import("@cero-base/core").Prim;
97
+ ref: import("@cero-base/core").Prim;
98
+ query: import("@cero-base/core").Prim;
99
+ local: import("@cero-base/core").Prim;
100
100
  };
101
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;
102
+ handle: import("@cero-base/core").Prim;
103
+ op: import("@cero-base/core").Prim;
104
+ data: import("@cero-base/core").Prim;
105
105
  };
106
106
  'req-invite': {
107
- handle: import("@cero-base/core/schema").Prim;
108
- role: import("@cero-base/core/schema").Prim;
107
+ handle: import("@cero-base/core").Prim;
108
+ role: import("@cero-base/core").Prim;
109
109
  };
110
110
  'req-revoke': {
111
- handle: import("@cero-base/core/schema").Prim;
112
- invite: import("@cero-base/core/schema").Prim;
111
+ handle: import("@cero-base/core").Prim;
112
+ invite: import("@cero-base/core").Prim;
113
113
  };
114
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;
115
+ parent: import("@cero-base/core").Prim;
116
+ ref: import("@cero-base/core").Prim;
117
+ invite: import("@cero-base/core").Prim;
118
118
  };
119
119
  'req-open': {
120
- parent: import("@cero-base/core/schema").Prim;
121
- row: import("@cero-base/core/schema").Prim;
120
+ parent: import("@cero-base/core").Prim;
121
+ row: import("@cero-base/core").Prim;
122
122
  };
123
123
  'req-handle': {
124
- handle: import("@cero-base/core/schema").Prim;
124
+ handle: import("@cero-base/core").Prim;
125
125
  };
126
126
  'res-data': {
127
- data: import("@cero-base/core/schema").Prim;
127
+ data: import("@cero-base/core").Prim;
128
128
  };
129
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;
130
+ data: import("@cero-base/core").Prim;
131
+ total: import("@cero-base/core").Prim;
132
+ size: import("@cero-base/core").Prim;
133
133
  };
134
134
  'res-count': {
135
- count: import("@cero-base/core/schema").Prim;
135
+ count: import("@cero-base/core").Prim;
136
136
  };
137
137
  'res-invite': {
138
- invite: import("@cero-base/core/schema").Prim;
138
+ invite: import("@cero-base/core").Prim;
139
139
  };
140
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;
141
+ id: import("@cero-base/core").Prim;
142
+ type: import("@cero-base/core").Prim;
143
+ name: import("@cero-base/core").Prim;
144
144
  };
145
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;
146
+ id: import("@cero-base/core").Prim;
147
+ deviceId: import("@cero-base/core").Prim;
148
+ phrase: import("@cero-base/core").Prim;
149
149
  };
150
150
  'res-ok': {
151
- ok: import("@cero-base/core/schema").Prim;
151
+ ok: import("@cero-base/core").Prim;
152
152
  };
153
153
  };
@@ -15,7 +15,7 @@ export function handleSync({ fields }?: {
15
15
  schema: {
16
16
  handles: {
17
17
  kind: "extend";
18
- fields: Record<string, import("@cero-base/core/schema").Prim>;
18
+ fields: Record<string, import("@cero-base/core").Prim>;
19
19
  };
20
20
  };
21
21
  setup(me: any): void;
@@ -1,2 +1,2 @@
1
- export { profileSync } from "./profile-sync.js";
2
- export { handleSync } from "./handle-sync.js";
1
+ export * from "./profile-sync.js";
2
+ export * from "./handle-sync.js";
@@ -1,9 +1,9 @@
1
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.
2
+ * Mirror your `profile` onto your `member` row in every handle you're in.
3
+ * Declares a `profile` single (`name`, `avatar`, plus any extra `fields`) and
4
+ * mirrors them onto the `member` builtin, then publishes when you open/join a
5
+ * handle and whenever your profile changes. An app may declare its own richer
6
+ * `profile` instead — the app schema wins.
7
7
  *
8
8
  * @param {{ fields?: Record<string, any> }} [opts]
9
9
  */
@@ -11,10 +11,10 @@ export function profileSync({ fields }?: {
11
11
  fields?: Record<string, any>;
12
12
  }): {
13
13
  schema: {
14
- profile: import("@cero-base/core/schema").TypeDef;
14
+ profile: import("@cero-base/core").TypeDef;
15
15
  members: {
16
16
  kind: "extend";
17
- fields: Record<string, import("@cero-base/core/schema").Prim>;
17
+ fields: Record<string, import("@cero-base/core").Prim>;
18
18
  };
19
19
  };
20
20
  setup(me: any): void;
@@ -86,9 +86,44 @@ export class Handle extends ReadyResource {
86
86
  _opts: any;
87
87
  _onerror: any;
88
88
  children: Set<any>;
89
+ _owned: Set<any>;
89
90
  store: Database;
90
91
  pair: Pairing;
91
92
  _wantsPair: boolean;
93
+ /**
94
+ * Tie a destroyable resource (a `watch` stream, a timer, any `{ destroy }`)
95
+ * to this handle's lifecycle — it's destroyed automatically on close, so
96
+ * callers don't track cleanup. De-registers itself if destroyed earlier.
97
+ *
98
+ * @template {{ destroy?: Function, once?: Function }} T
99
+ * @param {T} resource
100
+ * @returns {T}
101
+ */
102
+ own<T extends {
103
+ destroy?: Function;
104
+ once?: Function;
105
+ }>(resource: T): T;
106
+ /**
107
+ * An `AbortSignal` that fires when this handle closes. Pass it as
108
+ * `{ signal }` to `on`/`after`/`before`/`watch` to drop a subscription on
109
+ * close — or use your own `AbortController` for a finer scope.
110
+ *
111
+ * @returns {AbortSignal}
112
+ */
113
+ get signal(): AbortSignal;
114
+ _ac: AbortController;
115
+ /**
116
+ * `EventEmitter.on` plus an optional `{ signal }` that removes the listener
117
+ * when the signal aborts — e.g. `me.on('handle', fn, { signal: me.signal })`.
118
+ *
119
+ * @param {string} event
120
+ * @param {(...args: any[]) => void} fn
121
+ * @param {{ signal?: AbortSignal }} [opts]
122
+ * @returns {this}
123
+ */
124
+ on(event: string, fn: (...args: any[]) => void, opts?: {
125
+ signal?: AbortSignal;
126
+ }): this;
92
127
  /** Canonical id — identity id for the root handle, store key for children. */
93
128
  get id(): any;
94
129
  /** This device's id + name. `null` on child handles. */
@@ -11,14 +11,20 @@ export function before(ref: Ref, fn: (ctx: {
11
11
  op: string;
12
12
  name: string;
13
13
  row: any;
14
- }) => any): () => void;
14
+ }) => any, opts?: {
15
+ signal?: AbortSignal;
16
+ }): () => void;
15
17
  export function after(ref: Ref, fn: (ctx: {
16
18
  op: string;
17
19
  name: string;
18
20
  row: any;
19
- }) => void): () => void;
21
+ }) => void, opts?: {
22
+ signal?: AbortSignal;
23
+ }): () => void;
20
24
  export function get(ref: Ref, q?: string | Record<string, any>): Promise<SingleResult | ListResult | GetByIdResult>;
21
- export function watch(ref: Ref, q?: Record<string, any>): any;
25
+ export function watch(ref: Ref, q?: Record<string, any>, opts?: {
26
+ signal?: AbortSignal;
27
+ }): any;
22
28
  export function open(ref: Ref, arg?: string | {
23
29
  invite?: string;
24
30
  id?: string;
@@ -7,6 +7,14 @@
7
7
  * @param {Record<string, RefInfo>} refs
8
8
  */
9
9
  export function attachRefs(target: any, refs: Record<string, RefInfo>): void;
10
+ /**
11
+ * Run `cb` when `signal` aborts — or immediately if it already has. No-op
12
+ * without a signal. The listener removes itself on fire.
13
+ *
14
+ * @param {AbortSignal} [signal]
15
+ * @param {() => void} cb
16
+ */
17
+ export function onAbort(signal?: AbortSignal, cb: () => void): void;
10
18
  /**
11
19
  * @typedef {'collection' | 'single' | 'action' | 'handle'} RefKind
12
20
  * @typedef {{ kind?: string, schema?: string }} RefInfo
@@ -111,7 +111,7 @@ declare class LocalRefs {
111
111
  /** @param {Client} client */
112
112
  constructor(client: Client);
113
113
  parent: Client;
114
- spec: import("@cero-base/core/rpc").Spec;
114
+ spec: import("@cero-base/core").Spec;
115
115
  store: this;
116
116
  _local: boolean;
117
117
  /** Underlying RPC channel borrowed from the parent. */