@cero-base/cero 1.19.0 → 2.1.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.
Files changed (41) hide show
  1. package/README.md +38 -643
  2. package/package.json +9 -6
  3. package/src/build/index.js +63 -61
  4. package/src/build/internal.js +123 -0
  5. package/src/build/schemas.js +6 -11
  6. package/src/extensions/handle-sync.js +3 -11
  7. package/src/extensions/index.js +86 -0
  8. package/src/extensions/profile-sync.js +15 -16
  9. package/src/handle/index.js +290 -284
  10. package/src/index.js +24 -81
  11. package/src/lib/bluetooth.js +25 -56
  12. package/src/lib/constants.js +0 -15
  13. package/src/lib/operators.js +35 -134
  14. package/src/lib/peek.js +4 -8
  15. package/src/lib/refs.js +10 -8
  16. package/src/lib/spec.js +2 -3
  17. package/src/local/index.js +2 -3
  18. package/src/rpc/client.js +54 -77
  19. package/src/rpc/index.js +3 -3
  20. package/src/rpc/server.js +40 -43
  21. package/types/build/index.d.ts +11 -9
  22. package/types/build/{builtins.d.ts → internal.d.ts} +20 -38
  23. package/types/build/schemas.d.ts +4 -3
  24. package/types/extensions/handle-sync.d.ts +2 -8
  25. package/types/extensions/index.d.ts +102 -0
  26. package/types/extensions/profile-sync.d.ts +0 -5
  27. package/types/handle/index.d.ts +90 -108
  28. package/types/index.d.ts +21 -17
  29. package/types/lib/bluetooth.d.ts +8 -32
  30. package/types/lib/constants.d.ts +0 -11
  31. package/types/lib/operators.d.ts +30 -83
  32. package/types/lib/peek.d.ts +2 -3
  33. package/types/lib/refs.d.ts +5 -5
  34. package/types/lib/spec.d.ts +2 -3
  35. package/types/local/index.d.ts +2 -3
  36. package/types/rpc/client.d.ts +29 -26
  37. package/types/rpc/index.d.ts +3 -3
  38. package/types/rpc/server.d.ts +7 -10
  39. package/src/build/builtins.js +0 -174
  40. package/src/lib/internal.js +0 -9
  41. package/types/lib/internal.d.ts +0 -24
package/src/rpc/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * RPC barrel — re-exports the `Server`/`Client` classes and their
3
- * `serve()`/`connect()` helpers so consumers can spin up either side
4
- * of the cero IPC bridge from a single import.
2
+ * RPC barrel — re-exports the `Server`/`Client` classes and their `serve()`/`connect()`
3
+ * helpers so consumers can spin up either side of the cero IPC bridge from a single
4
+ * import.
5
5
  */
6
6
 
7
7
  export { Server, serve } from './server.js'
package/src/rpc/server.js CHANGED
@@ -5,7 +5,7 @@ import { CeroError } from '@cero-base/core/errors'
5
5
  import { encodeId } from '@cero-base/core/blobs/codec'
6
6
 
7
7
  import { cero, restore } from '../index.js'
8
- import { put, set, get, del, count, watch, changes, call } from '../lib/operators.js'
8
+ import { put, set, get, del, watch, changes, call } from '../lib/operators.js'
9
9
 
10
10
  /**
11
11
  * @typedef {import('@cero-base/core/rpc').RPCServer} BaseRPCServer
@@ -27,10 +27,9 @@ import { put, set, get, del, count, watch, changes, call } from '../lib/operator
27
27
  */
28
28
 
29
29
  /**
30
- * IPC-side RPC server for cero. Bridges an `hrpc` channel to a live
31
- * `Handle` tree: lazy-initializes the root via `cero()` on the first
32
- * `init` call, then exposes data ops, pairing, and handle lifecycle
33
- * over the wire using the spec-bound codec.
30
+ * IPC-side RPC server for cero. Bridges an `hrpc` channel to a live `Handle` tree:
31
+ * lazy-initializes the root via `cero()` on the first `init` call, then exposes data ops,
32
+ * pairing, and handle lifecycle.
34
33
  */
35
34
  export class Server extends RPCServer {
36
35
  /**
@@ -52,14 +51,6 @@ export class Server extends RPCServer {
52
51
  this._wireInit()
53
52
  }
54
53
 
55
- /** End every watch stream bound to a handle (e.g. when it closes or leaves). */
56
- _endWatches(handle) {
57
- const set = this._watchStreams.get(handle)
58
- if (!set) return
59
- for (const s of set) s.destroy()
60
- this._watchStreams.delete(handle)
61
- }
62
-
63
54
  /**
64
55
  * Root cero id (null until `init` has run).
65
56
  *
@@ -87,6 +78,14 @@ export class Server extends RPCServer {
87
78
  await super._close()
88
79
  }
89
80
 
81
+ /** End every watch stream bound to a handle (e.g. when it closes or leaves). */
82
+ _endWatches(handle) {
83
+ const set = this._watchStreams.get(handle)
84
+ if (!set) return
85
+ for (const s of set) s.destroy()
86
+ this._watchStreams.delete(handle)
87
+ }
88
+
90
89
  /** Wire the `init` handler that lazily constructs the root cero handle. */
91
90
  _wireInit() {
92
91
  this.rpc.onInit(async () => {
@@ -113,7 +112,7 @@ export class Server extends RPCServer {
113
112
  })
114
113
  }
115
114
 
116
- /** Register the row-level RPC handlers (put/set/get/del/count/watch/call). */
115
+ /** Register the row-level RPC handlers (put/set/get/del/watch/call). */
117
116
  _wireData() {
118
117
  this.rpc.onAddFile(async ({ handle, data, name, type }) => {
119
118
  const h = this._resolve(handle)
@@ -147,7 +146,7 @@ export class Server extends RPCServer {
147
146
  r.kind === 'single'
148
147
  ? codec.encodeRow(r.schema, result.data)
149
148
  : codec.encodeRows(r.schema, result.data)
150
- // total is int on the wire: -1 encodes "skipped" (null), see T2.1 lazy total
149
+ // total is int on the wire: -1 encodes null
151
150
  return { data, total: result.total ?? -1, size: result.size ?? 0 }
152
151
  })
153
152
 
@@ -162,12 +161,6 @@ export class Server extends RPCServer {
162
161
  return {}
163
162
  })
164
163
 
165
- this.rpc.onCount(async ({ handle, ref, query, local }) => {
166
- const { ref: r, codec } = this._refOf(handle, ref, local)
167
- const { data } = await count(r, codec.decodeQuery(query))
168
- return { count: data }
169
- })
170
-
171
164
  this.rpc.onWatch((stream) => {
172
165
  const { handle, ref, query, local } = stream.data
173
166
  let r, codec, live
@@ -175,17 +168,13 @@ export class Server extends RPCServer {
175
168
  ;({ ref: r, codec } = this._refOf(handle, ref, local))
176
169
  live = watch(r, codec.decodeQuery(query))
177
170
  } catch (err) {
178
- // Forward the error to the client over the wire by destroying the
179
- // underlying response stream with it (emits a CLOSE|ERROR frame). The
180
- // local RPCStream's 'error' is swallowed so it doesn't crash the server.
171
+ // forward the error over the wire by destroying the response stream with it
181
172
  stream.once('error', () => {})
182
173
  stream.writeStream.destroy(err)
183
174
  stream.destroy()
184
175
  return
185
176
  }
186
- // keep-latest under IPC backpressure: snapshots are idempotent, so an
187
- // un-drained wire holds one pending snapshot (newest wins) instead of
188
- // queueing every intermediate result set — encode only what ships
177
+ // keep-latest under backpressure: snapshots are idempotent
189
178
  let pending = null
190
179
  let blocked = false
191
180
  const onData = (snap) => {
@@ -212,8 +201,7 @@ export class Server extends RPCServer {
212
201
  if (!set) this._watchStreams.set(handle, (set = new Set()))
213
202
  set.add(stream)
214
203
  live.on('data', onData)
215
- // channel teardown destroys the stream with CHANNEL_CLOSED 'close'
216
- // below does the cleanup; the error itself must not crash the server
204
+ // channel teardown destroys the stream with CHANNEL_CLOSED; 'close' cleans up
217
205
  stream.on('error', safetyCatch)
218
206
  stream.on('close', () => {
219
207
  live.off('data', onData)
@@ -237,8 +225,7 @@ export class Server extends RPCServer {
237
225
  let set = this._watchStreams.get(handle)
238
226
  if (!set) this._watchStreams.set(handle, (set = new Set()))
239
227
  set.add(stream)
240
- // no keep-latest: delta batches are not idempotent hold the iteration
241
- // on wire backpressure instead; the cursor folds everything missed
228
+ // deltas are not idempotent: hold the iteration on backpressure instead
242
229
  const pump = async () => {
243
230
  for await (const batch of live) {
244
231
  const ok = stream.write({
@@ -282,13 +269,25 @@ export class Server extends RPCServer {
282
269
  return { ok }
283
270
  })
284
271
 
285
- // specs built before rotation have no rotate command
286
- if (typeof this.rpc.onRotate === 'function') {
287
- this.rpc.onRotate(async ({ handle }) => {
288
- const { epoch } = await this._resolve(handle).store.rotate()
289
- return { epoch }
290
- })
291
- }
272
+ this.rpc.onRotate(async ({ handle }) => {
273
+ const { epoch } = await this._resolve(handle).store.rotate()
274
+ return { epoch }
275
+ })
276
+
277
+ this.rpc.onSetActive(({ handle, active }) => {
278
+ this._resolve(handle).setActive(active)
279
+ return {}
280
+ })
281
+
282
+ this.rpc.onSuspend(async () => {
283
+ await this.me.suspend()
284
+ return {}
285
+ })
286
+
287
+ this.rpc.onResume(async () => {
288
+ await this.me.resume()
289
+ return {}
290
+ })
292
291
 
293
292
  this.rpc.onJoin(async ({ parent, ref, invite }) => {
294
293
  if (this._resolve(parent) !== this.me) throw CeroError.UNSUPPORTED('nested handles')
@@ -306,7 +305,7 @@ export class Server extends RPCServer {
306
305
  const info = parent.spec.meta.refs?.[ref] || parent.spec.handles?.[ref]
307
306
  if (!info) throw CeroError.UNKNOWN('handle type', ref)
308
307
  const wire = parent.spec.codec.decodeCreate(data) || {}
309
- // routes can't cross the wire (functions) both sides register them via define()
308
+ // routes are functions and cannot cross the wire
310
309
  const opts = { name: wire.name, role: wire.role, accept: wire.noAccept ? false : undefined }
311
310
  const child = await this.me._create(ref, opts)
312
311
  const id = child.id
@@ -358,9 +357,7 @@ export class Server extends RPCServer {
358
357
  }
359
358
 
360
359
  /**
361
- * Resolve a `{ handle, ref }` pair to its `Ref` and codec. When `local` is
362
- * set, the ref is resolved against the root's per-device `local` store and
363
- * paired with the codec bound from the local schema.
360
+ * Resolve a `{ handle, ref }` pair to its `Ref` and codec.
364
361
  *
365
362
  * @param {string} id
366
363
  * @param {string} name
@@ -370,7 +367,7 @@ export class Server extends RPCServer {
370
367
  _refOf(id, name, local) {
371
368
  if (local) {
372
369
  const info = this.spec.meta.local?.refs?.[name]
373
- if (!info || info.builtin) throw CeroError.UNKNOWN('local ref', name)
370
+ if (!info || info.internal) throw CeroError.UNKNOWN('local ref', name)
374
371
  const r = this.me.local?.[name]
375
372
  if (!r) throw CeroError.UNKNOWN('local ref', name)
376
373
  return { ref: r, codec: this.spec.local.codec }
@@ -8,6 +8,14 @@ export type BuildOpts = {
8
8
  * Namespace prefix for emitted schema ids. Defaults to `'cero'`.
9
9
  */
10
10
  ns?: string;
11
+ /**
12
+ * The extensions to fold in. A module specifier, relative to `specDir`, is imported here for its `extensions` export and written into the spec, so every process runs the same list. A list is folded in only. The bundled two by default, `[]` for none.
13
+ */
14
+ extensions?: string | import('../extensions/index.js').Extension[];
15
+ /**
16
+ * A module specifier, relative to `specDir`, written into the spec for its `operators` export, so every process binds the same map.
17
+ */
18
+ operators?: string;
11
19
  };
12
20
  /**
13
21
  * @typedef {import('@cero-base/core/schema').Schema} Schema
@@ -16,21 +24,15 @@ export type BuildOpts = {
16
24
  *
17
25
  * @typedef {object} BuildOpts
18
26
  * @property {string} [ns] Namespace prefix for emitted schema ids. Defaults to `'cero'`.
27
+ * @property {string | import('../extensions/index.js').Extension[]} [extensions] The extensions to fold in. A module specifier, relative to `specDir`, is imported here for its `extensions` export and written into the spec, so every process runs the same list. A list is folded in only. The bundled two by default, `[]` for none.
28
+ * @property {string} [operators] A module specifier, relative to `specDir`, written into the spec for its `operators` export, so every process binds the same map.
19
29
  */
20
30
  /**
21
31
  * Compile a cero schema into wire-level artifacts and write them to disk.
22
32
  *
23
- * Emits a `main/` tree (schema + hyperdb + dispatch + rpc), a `local/` tree
24
- * for per-device data, one `handles/<name>/` tree per child handle type, and
25
- * an `index.js` that re-exports a ready-to-use `spec` object.
26
- *
27
33
  * @param {string} specDir Output directory.
28
34
  * @param {SchemaInput} schema Either a `schema(...)` wrapper or its raw defs object.
29
35
  * @param {BuildOpts} [opts]
30
36
  * @returns {Promise<void>}
31
37
  */
32
- export { getHyperdbType } from './builtins.js';
33
- export declare function build(specDir: any, schema: any, { ns, extensions }?: {
34
- extensions?: boolean;
35
- ns?: string;
36
- }): Promise<void>;
38
+ export declare function build(specDir: string, schema: SchemaInput, { ns, extensions, operators }?: BuildOpts): Promise<void>;
@@ -1,4 +1,4 @@
1
- export declare const refs: {
1
+ export declare const defs: {
2
2
  main: {
3
3
  members: {
4
4
  type: string;
@@ -34,26 +34,12 @@ export declare const refs: {
34
34
  };
35
35
  };
36
36
  };
37
- export declare function getHyperdbType(prim: any): any;
38
- export declare function builtinRefs(ns: any, scope: any): {
39
- [k: string]: {
40
- kind: any;
41
- path: string[];
42
- builtin: boolean;
43
- verb: any;
44
- schema: string;
45
- };
46
- };
47
- export declare function builtinTypes(scope: any, extend: any): {
37
+ export declare function fields(map: any): {
48
38
  name: string;
49
- compact: boolean;
50
- fields: {
51
- name: string;
52
- type: any;
53
- required: boolean;
54
- }[];
39
+ type: any;
40
+ required: boolean;
55
41
  }[];
56
- export declare function rpcTypes(): {
42
+ export declare function types(scope: any, extend?: {}): {
57
43
  name: string;
58
44
  compact: boolean;
59
45
  fields: {
@@ -62,35 +48,31 @@ export declare function rpcTypes(): {
62
48
  required: boolean;
63
49
  }[];
64
50
  }[];
65
- export declare function builtinCollections(ns: any, scope: any): {
51
+ export declare function refs(ns: any, scope: any): {
52
+ [k: string]: {
53
+ kind: any;
54
+ path: string[];
55
+ internal: boolean;
56
+ verb: any;
57
+ schema: string;
58
+ };
59
+ };
60
+ export declare function collections(ns: any, scope: any): {
66
61
  name: string;
67
62
  schema: string;
68
63
  key: string[];
69
64
  }[];
70
- export declare function builtinDispatches(ns: any): {
65
+ export declare function dispatches(ns: any): {
71
66
  name: string;
72
67
  requestType: string;
73
68
  }[];
74
- export declare function rotateDispatch(ns: any): {
75
- name: string;
76
- requestType: string;
77
- };
78
- export declare function rpcCommands(ns: any): ({
79
- name: string;
69
+ export declare function commands(ns: any): {
70
+ name: string | boolean;
80
71
  request: {
81
72
  name: string;
82
73
  };
83
74
  response: {
84
75
  name: string;
85
- stream?: undefined;
76
+ stream: string | true;
86
77
  };
87
- } | {
88
- name: string;
89
- request: {
90
- name: string;
91
- };
92
- response: {
93
- name: string;
94
- stream: boolean;
95
- };
96
- })[];
78
+ }[];
@@ -151,6 +151,10 @@ export declare const rpc: {
151
151
  'req-handle': {
152
152
  handle: import("@cero-base/core").Prim;
153
153
  };
154
+ 'req-set-active': {
155
+ handle: import("@cero-base/core").Prim;
156
+ active: import("@cero-base/core").Prim;
157
+ };
154
158
  'res-data': {
155
159
  data: import("@cero-base/core").Prim;
156
160
  };
@@ -163,9 +167,6 @@ export declare const rpc: {
163
167
  changes: import("@cero-base/core").Prim;
164
168
  reset: import("@cero-base/core").Prim;
165
169
  };
166
- 'res-count': {
167
- count: import("@cero-base/core").Prim;
168
- };
169
170
  'res-invite': {
170
171
  invite: import("@cero-base/core").Prim;
171
172
  };
@@ -1,11 +1,6 @@
1
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.
2
+ * Mirror a child handle's `profile` (name + avatar) onto its row in the parent's `handles`
3
+ * list — so a handle list shows names + avatars without opening each one.
9
4
  *
10
5
  * @param {{ fields?: Record<string, any> }} [opts]
11
6
  */
@@ -13,7 +8,6 @@ export declare function handleSync({ fields }?: {
13
8
  fields?: Record<string, any>;
14
9
  }): {
15
10
  name: string;
16
- bundled: boolean;
17
11
  schema: {
18
12
  handles: {
19
13
  kind: 'extend';
@@ -1,2 +1,104 @@
1
+ import { t, schema } from '../lib/spec.js';
2
+ import * as operators from '../lib/operators.js';
1
3
  export * from './profile-sync.js';
2
4
  export * from './handle-sync.js';
5
+ export { t, schema };
6
+ export declare const put: typeof operators.put, set: typeof operators.set, get: typeof operators.get, del: typeof operators.del, watch: typeof operators.watch, changes: typeof operators.changes, call: typeof operators.call, open: typeof operators.open, rotate: typeof operators.rotate, before: typeof operators.before, after: typeof operators.after;
7
+ export declare const cero: {
8
+ t: {
9
+ string: import("@cero-base/core").Prim;
10
+ uint: import("@cero-base/core").Prim;
11
+ int: import("@cero-base/core").Prim;
12
+ bool: import("@cero-base/core").Prim;
13
+ bytes: import("@cero-base/core").Prim;
14
+ json: import("@cero-base/core").Prim;
15
+ fixed32: import("@cero-base/core").Prim;
16
+ fixed64: import("@cero-base/core").Prim;
17
+ file: import("@cero-base/core").Prim;
18
+ required: (marker: import("@cero-base/core").Prim) => import("@cero-base/core").Prim;
19
+ single(fields: Record<string, import("@cero-base/core").Prim>): import("@cero-base/core").TypeDef;
20
+ collection(fields: Record<string, import("@cero-base/core").Prim>, opts?: {
21
+ indexes?: Record<string, string[]>;
22
+ own?: boolean;
23
+ }): import("@cero-base/core").TypeDef;
24
+ action(fields: Record<string, import("@cero-base/core").Prim>): import("@cero-base/core").TypeDef;
25
+ extend(fields: Record<string, import("@cero-base/core").Prim>): {
26
+ kind: 'extend';
27
+ fields: Record<string, import("@cero-base/core").Prim>;
28
+ };
29
+ };
30
+ schema: typeof schema;
31
+ put: typeof operators.put;
32
+ set: typeof operators.set;
33
+ get: typeof operators.get;
34
+ del: typeof operators.del;
35
+ watch: typeof operators.watch;
36
+ changes: typeof operators.changes;
37
+ call: typeof operators.call;
38
+ open: typeof operators.open;
39
+ rotate: typeof operators.rotate;
40
+ before: typeof operators.before;
41
+ after: typeof operators.after;
42
+ };
43
+ export type Extension = {
44
+ /**
45
+ * Refs to add, or `t.extend` on a builtin, nested by handle type like the app schema.
46
+ */
47
+ schema?: Record<string, any>;
48
+ /**
49
+ * Runs once the root is ready; a returned function runs on close.
50
+ */
51
+ setup?: (me: any) => any;
52
+ };
53
+ /**
54
+ * @typedef {object} Extension
55
+ * @property {Record<string, any>} [schema] Refs to add, or `t.extend` on a builtin, nested by handle type like the app schema.
56
+ * @property {(me: any) => any} [setup] Runs once the root is ready; a returned function runs on close.
57
+ */
58
+ /** The two every app gets unless its build names a list. */
59
+ export declare const bundled: ({
60
+ name: string;
61
+ schema: {
62
+ profile: import("@cero-base/core").TypeDef;
63
+ members: {
64
+ kind: 'extend';
65
+ fields: Record<string, import("@cero-base/core").Prim>;
66
+ };
67
+ };
68
+ setup(me: any): void;
69
+ } | {
70
+ name: string;
71
+ schema: {
72
+ handles: {
73
+ kind: 'extend';
74
+ fields: Record<string, import("@cero-base/core").Prim>;
75
+ };
76
+ };
77
+ setup(me: any): void;
78
+ })[];
79
+ /**
80
+ * The extensions a spec carries, else the bundled two. A bare function is `{ setup }`.
81
+ *
82
+ * @param {any} spec
83
+ * @param {Array<any>} [override]
84
+ * @returns {Extension[]}
85
+ */
86
+ export declare function extensionsOf(spec: any, override?: Array<any>): Extension[];
87
+ /**
88
+ * The operators a spec carries: functions taking the handle first, keyed by namespace, a
89
+ * key naming a handle type holding that type's namespaces.
90
+ *
91
+ * @param {any} spec
92
+ * @param {Record<string, any>} [override]
93
+ * @returns {Record<string, any>}
94
+ */
95
+ export declare function operatorsOf(spec: any, override?: Record<string, any>): Record<string, any>;
96
+ /**
97
+ * Put the operators for `handle` on it: the root when `type` is null, else a child of `type`.
98
+ *
99
+ * @param {any} handle
100
+ * @param {string | null} type
101
+ * @param {Record<string, any>} operators
102
+ * @returns {any} handle
103
+ */
104
+ export declare function bind(handle: any, type: string | null, operators: Record<string, any>): any;
@@ -1,9 +1,5 @@
1
1
  /**
2
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
3
  *
8
4
  * @param {{ fields?: Record<string, any> }} [opts]
9
5
  */
@@ -11,7 +7,6 @@ export declare function profileSync({ fields }?: {
11
7
  fields?: Record<string, any>;
12
8
  }): {
13
9
  name: string;
14
- bundled: boolean;
15
10
  schema: {
16
11
  profile: import("@cero-base/core").TypeDef;
17
12
  members: {