@cero-base/cero 0.7.0 → 0.8.1
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 +26 -9
- package/package.json +3 -3
- package/src/CLAUDE.md +3 -0
- package/src/extensions/CLAUDE.md +3 -0
- package/src/extensions/handle-sync.js +6 -7
- package/src/extensions/index.js +2 -2
- package/src/extensions/profile-sync.js +17 -11
- package/src/handle/index.js +54 -1
- package/src/lib/CLAUDE.md +3 -0
- package/src/lib/operators.js +31 -10
- package/src/lib/utils.js +13 -0
- package/types/build/schemas.d.ts +89 -89
- package/types/extensions/handle-sync.d.ts +1 -1
- package/types/extensions/index.d.ts +2 -2
- package/types/extensions/profile-sync.d.ts +7 -7
- package/types/handle/index.d.ts +35 -0
- package/types/lib/operators.d.ts +9 -3
- package/types/lib/utils.d.ts +8 -0
- package/types/rpc/client.d.ts +1 -1
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()
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.8.1",
|
|
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",
|
|
@@ -71,10 +71,10 @@
|
|
|
71
71
|
"build:types": "rm -rf types && tsc -p .",
|
|
72
72
|
"pretest": "npm run build:test",
|
|
73
73
|
"prepublishOnly": "npm run build:types",
|
|
74
|
-
"test": "ls test/*.test.js | xargs -
|
|
74
|
+
"test": "ls test/*.test.js | xargs -P1 -n1 brittle-node"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@cero-base/core": "^0.
|
|
77
|
+
"@cero-base/core": "^0.8.1",
|
|
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
|
@@ -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
|
-
|
|
19
|
+
const onHandle = (child, opts) => {
|
|
20
20
|
if (!child.profile) return
|
|
21
|
-
if (opts.name) set(child.profile, { name: opts.name })
|
|
22
|
-
|
|
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
|
-
|
|
29
|
-
})
|
|
27
|
+
}
|
|
28
|
+
me.on('handle', onHandle, { signal: me.signal })
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
31
|
}
|
package/src/extensions/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
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
|
|
6
|
-
* `profile` single (`name`, `avatar`, plus any extra `fields`) and
|
|
7
|
-
* onto the `member` builtin, then publishes when you open/join a
|
|
8
|
-
* your profile changes. An app may declare its own richer
|
|
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 =
|
|
21
|
-
|
|
22
|
-
|
|
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)
|
|
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
|
-
|
|
27
|
-
|
|
31
|
+
|
|
32
|
+
me.on('handle', onHandle, { signal: me.signal })
|
|
33
|
+
after(me.profile, onProfile, { signal: me.signal })
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
}
|
package/src/handle/index.js
CHANGED
|
@@ -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
|
package/src/lib/operators.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
+
}
|
package/types/build/schemas.d.ts
CHANGED
|
@@ -1,153 +1,153 @@
|
|
|
1
1
|
export const main: {
|
|
2
2
|
'del-by-id': {
|
|
3
|
-
id: import("@cero-base/core
|
|
3
|
+
id: import("@cero-base/core").Prim;
|
|
4
4
|
};
|
|
5
5
|
writer: {
|
|
6
|
-
master: import("@cero-base/core
|
|
7
|
-
writer: import("@cero-base/core
|
|
8
|
-
sig: import("@cero-base/core
|
|
9
|
-
isIndexer: import("@cero-base/core
|
|
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
|
|
13
|
-
value: import("@cero-base/core
|
|
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
|
|
17
|
-
key: import("@cero-base/core
|
|
18
|
-
role: import("@cero-base/core
|
|
19
|
-
name: import("@cero-base/core
|
|
20
|
-
createdAt: import("@cero-base/core
|
|
21
|
-
updatedAt: import("@cero-base/core
|
|
22
|
-
sig: import("@cero-base/core
|
|
23
|
-
index: import("@cero-base/core
|
|
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
|
|
27
|
-
memberId: import("@cero-base/core
|
|
28
|
-
name: import("@cero-base/core
|
|
29
|
-
isMobile: import("@cero-base/core
|
|
30
|
-
createdAt: import("@cero-base/core
|
|
31
|
-
updatedAt: import("@cero-base/core
|
|
32
|
-
index: import("@cero-base/core
|
|
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
|
|
36
|
-
invite: import("@cero-base/core
|
|
37
|
-
publicKey: import("@cero-base/core
|
|
38
|
-
data: import("@cero-base/core
|
|
39
|
-
sig: import("@cero-base/core
|
|
40
|
-
role: import("@cero-base/core
|
|
41
|
-
expires: import("@cero-base/core
|
|
42
|
-
createdAt: import("@cero-base/core
|
|
43
|
-
index: import("@cero-base/core
|
|
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
|
|
47
|
-
type: import("@cero-base/core
|
|
48
|
-
key: import("@cero-base/core
|
|
49
|
-
encryptionKey: import("@cero-base/core
|
|
50
|
-
name: import("@cero-base/core
|
|
51
|
-
createdAt: import("@cero-base/core
|
|
52
|
-
updatedAt: import("@cero-base/core
|
|
53
|
-
index: import("@cero-base/core
|
|
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
|
|
57
|
-
writer: import("@cero-base/core
|
|
58
|
-
sig: import("@cero-base/core
|
|
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
|
|
63
|
+
seed: import("@cero-base/core").Prim;
|
|
64
64
|
};
|
|
65
65
|
keypair: {
|
|
66
|
-
publicKey: import("@cero-base/core
|
|
67
|
-
secretKey: import("@cero-base/core
|
|
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
|
|
71
|
-
publicKey: import("@cero-base/core
|
|
72
|
-
secretKey: import("@cero-base/core
|
|
73
|
-
encryptionKey: import("@cero-base/core
|
|
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
|
|
78
|
+
ok: import("@cero-base/core").Prim;
|
|
79
79
|
};
|
|
80
80
|
'req-restore': {
|
|
81
|
-
phrase: import("@cero-base/core
|
|
81
|
+
phrase: import("@cero-base/core").Prim;
|
|
82
82
|
};
|
|
83
83
|
'req-row': {
|
|
84
|
-
handle: import("@cero-base/core
|
|
85
|
-
ref: import("@cero-base/core
|
|
86
|
-
data: import("@cero-base/core
|
|
87
|
-
local: import("@cero-base/core
|
|
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
|
|
91
|
-
ref: import("@cero-base/core
|
|
92
|
-
id: import("@cero-base/core
|
|
93
|
-
local: import("@cero-base/core
|
|
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
|
|
97
|
-
ref: import("@cero-base/core
|
|
98
|
-
query: import("@cero-base/core
|
|
99
|
-
local: import("@cero-base/core
|
|
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
|
|
103
|
-
op: import("@cero-base/core
|
|
104
|
-
data: import("@cero-base/core
|
|
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
|
|
108
|
-
role: import("@cero-base/core
|
|
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
|
|
112
|
-
invite: import("@cero-base/core
|
|
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
|
|
116
|
-
ref: import("@cero-base/core
|
|
117
|
-
invite: import("@cero-base/core
|
|
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
|
|
121
|
-
row: import("@cero-base/core
|
|
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
|
|
124
|
+
handle: import("@cero-base/core").Prim;
|
|
125
125
|
};
|
|
126
126
|
'res-data': {
|
|
127
|
-
data: import("@cero-base/core
|
|
127
|
+
data: import("@cero-base/core").Prim;
|
|
128
128
|
};
|
|
129
129
|
'res-rows': {
|
|
130
|
-
data: import("@cero-base/core
|
|
131
|
-
total: import("@cero-base/core
|
|
132
|
-
size: import("@cero-base/core
|
|
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
|
|
135
|
+
count: import("@cero-base/core").Prim;
|
|
136
136
|
};
|
|
137
137
|
'res-invite': {
|
|
138
|
-
invite: import("@cero-base/core
|
|
138
|
+
invite: import("@cero-base/core").Prim;
|
|
139
139
|
};
|
|
140
140
|
'res-handle': {
|
|
141
|
-
id: import("@cero-base/core
|
|
142
|
-
type: import("@cero-base/core
|
|
143
|
-
name: import("@cero-base/core
|
|
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
|
|
147
|
-
deviceId: import("@cero-base/core
|
|
148
|
-
phrase: import("@cero-base/core
|
|
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
|
|
151
|
+
ok: import("@cero-base/core").Prim;
|
|
152
152
|
};
|
|
153
153
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
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
|
|
3
|
-
* `profile` single (`name`, `avatar`, plus any extra `fields`) and
|
|
4
|
-
* onto the `member` builtin, then publishes when you open/join a
|
|
5
|
-
* your profile changes. An app may declare its own richer
|
|
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
|
|
14
|
+
profile: import("@cero-base/core").TypeDef;
|
|
15
15
|
members: {
|
|
16
16
|
kind: "extend";
|
|
17
|
-
fields: Record<string, import("@cero-base/core
|
|
17
|
+
fields: Record<string, import("@cero-base/core").Prim>;
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
20
|
setup(me: any): void;
|
package/types/handle/index.d.ts
CHANGED
|
@@ -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. */
|
package/types/lib/operators.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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;
|
package/types/lib/utils.d.ts
CHANGED
|
@@ -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
|
package/types/rpc/client.d.ts
CHANGED
|
@@ -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
|
|
114
|
+
spec: import("@cero-base/core").Spec;
|
|
115
115
|
store: this;
|
|
116
116
|
_local: boolean;
|
|
117
117
|
/** Underlying RPC channel borrowed from the parent. */
|