@cero-base/cero 1.18.2 → 2.0.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 +38 -644
- package/package.json +16 -12
- package/src/build/index.js +21 -50
- package/src/build/internal.js +121 -0
- package/src/build/schemas.js +2 -8
- package/src/extensions/handle-sync.js +3 -10
- package/src/extensions/index.js +6 -0
- package/src/extensions/profile-sync.js +0 -5
- package/src/handle/index.js +255 -300
- package/src/index.js +65 -112
- package/src/lib/bluetooth.js +25 -56
- package/src/lib/constants.js +0 -15
- package/src/lib/operators.js +24 -74
- package/src/lib/peek.js +4 -8
- package/src/lib/refs.js +46 -0
- package/src/lib/spec.js +2 -3
- package/src/local/index.js +4 -5
- package/src/rpc/client.js +27 -39
- package/src/rpc/index.js +3 -3
- package/src/rpc/server.js +29 -40
- package/types/build/index.d.ts +19 -7
- package/types/build/internal.d.ts +78 -0
- package/types/build/schemas.d.ts +3 -3
- package/types/extensions/handle-sync.d.ts +4 -9
- package/types/extensions/index.d.ts +24 -2
- package/types/extensions/profile-sync.d.ts +2 -6
- package/types/handle/index.d.ts +218 -254
- package/types/index.d.ts +78 -102
- package/types/lib/bluetooth.d.ts +24 -46
- package/types/lib/constants.d.ts +5 -16
- package/types/lib/operators.d.ts +49 -77
- package/types/lib/peek.d.ts +3 -4
- package/types/lib/refs.d.ts +36 -0
- package/types/lib/spec.d.ts +5 -1
- package/types/local/index.d.ts +24 -23
- package/types/rpc/client.d.ts +107 -127
- package/types/rpc/index.d.ts +7 -2
- package/types/rpc/server.d.ts +62 -76
- package/src/build/builtins.js +0 -174
- package/src/lib/internal.js +0 -9
- package/src/lib/utils.js +0 -67
- package/types/build/builtins.d.ts +0 -100
- package/types/lib/internal.d.ts +0 -24
- package/types/lib/utils.d.ts +0 -55
package/src/rpc/server.js
CHANGED
|
@@ -12,7 +12,6 @@ import { put, set, get, del, count, watch, changes, call } from '../lib/operator
|
|
|
12
12
|
*
|
|
13
13
|
* @typedef {object} ServerOpts
|
|
14
14
|
* @property {string} storage Directory passed to `cero()` for the local store.
|
|
15
|
-
* @property {object} spec Compiled cero spec (schema + rpc + handles).
|
|
16
15
|
* @property {string} [name] Optional display name forwarded to `cero()`.
|
|
17
16
|
* @property {Array<{ host: string, port: number }>} [bootstrap] Custom DHT bootstrap.
|
|
18
17
|
* @property {boolean} [isMobile]
|
|
@@ -28,19 +27,19 @@ import { put, set, get, del, count, watch, changes, call } from '../lib/operator
|
|
|
28
27
|
*/
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
|
-
* IPC-side RPC server for cero. Bridges an `hrpc` channel to a live
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* 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.
|
|
35
33
|
*/
|
|
36
34
|
export class Server extends RPCServer {
|
|
37
35
|
/**
|
|
38
36
|
* @param {any} ipc Framed IPC stream (must be writable).
|
|
37
|
+
* @param {object} spec
|
|
39
38
|
* @param {Partial<ServerOpts>} [opts]
|
|
40
39
|
*/
|
|
41
|
-
constructor(ipc, { storage,
|
|
42
|
-
if (!storage) throw CeroError.REQUIRED('storage')
|
|
40
|
+
constructor(ipc, spec, { storage, ...opts } = {}) {
|
|
43
41
|
if (!spec) throw CeroError.REQUIRED('spec')
|
|
42
|
+
if (!storage) throw CeroError.REQUIRED('storage')
|
|
44
43
|
super(ipc, spec)
|
|
45
44
|
if (spec.local?.schema && !spec.local.codec) bindCodec(spec.local)
|
|
46
45
|
this.storage = storage
|
|
@@ -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 () => {
|
|
@@ -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
|
|
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
|
|
|
@@ -175,17 +174,13 @@ export class Server extends RPCServer {
|
|
|
175
174
|
;({ ref: r, codec } = this._refOf(handle, ref, local))
|
|
176
175
|
live = watch(r, codec.decodeQuery(query))
|
|
177
176
|
} catch (err) {
|
|
178
|
-
//
|
|
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.
|
|
177
|
+
// forward the error over the wire by destroying the response stream with it
|
|
181
178
|
stream.once('error', () => {})
|
|
182
179
|
stream.writeStream.destroy(err)
|
|
183
180
|
stream.destroy()
|
|
184
181
|
return
|
|
185
182
|
}
|
|
186
|
-
// keep-latest under
|
|
187
|
-
// un-drained wire holds one pending snapshot (newest wins) instead of
|
|
188
|
-
// queueing every intermediate result set — encode only what ships
|
|
183
|
+
// keep-latest under backpressure: snapshots are idempotent
|
|
189
184
|
let pending = null
|
|
190
185
|
let blocked = false
|
|
191
186
|
const onData = (snap) => {
|
|
@@ -212,8 +207,7 @@ export class Server extends RPCServer {
|
|
|
212
207
|
if (!set) this._watchStreams.set(handle, (set = new Set()))
|
|
213
208
|
set.add(stream)
|
|
214
209
|
live.on('data', onData)
|
|
215
|
-
// channel teardown destroys the stream with CHANNEL_CLOSED
|
|
216
|
-
// below does the cleanup; the error itself must not crash the server
|
|
210
|
+
// channel teardown destroys the stream with CHANNEL_CLOSED; 'close' cleans up
|
|
217
211
|
stream.on('error', safetyCatch)
|
|
218
212
|
stream.on('close', () => {
|
|
219
213
|
live.off('data', onData)
|
|
@@ -237,8 +231,7 @@ export class Server extends RPCServer {
|
|
|
237
231
|
let set = this._watchStreams.get(handle)
|
|
238
232
|
if (!set) this._watchStreams.set(handle, (set = new Set()))
|
|
239
233
|
set.add(stream)
|
|
240
|
-
//
|
|
241
|
-
// on wire backpressure instead; the cursor folds everything missed
|
|
234
|
+
// deltas are not idempotent: hold the iteration on backpressure instead
|
|
242
235
|
const pump = async () => {
|
|
243
236
|
for await (const batch of live) {
|
|
244
237
|
const ok = stream.write({
|
|
@@ -282,13 +275,10 @@ export class Server extends RPCServer {
|
|
|
282
275
|
return { ok }
|
|
283
276
|
})
|
|
284
277
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return { epoch }
|
|
290
|
-
})
|
|
291
|
-
}
|
|
278
|
+
this.rpc.onRotate(async ({ handle }) => {
|
|
279
|
+
const { epoch } = await this._resolve(handle).store.rotate()
|
|
280
|
+
return { epoch }
|
|
281
|
+
})
|
|
292
282
|
|
|
293
283
|
this.rpc.onJoin(async ({ parent, ref, invite }) => {
|
|
294
284
|
if (this._resolve(parent) !== this.me) throw CeroError.UNSUPPORTED('nested handles')
|
|
@@ -306,7 +296,7 @@ export class Server extends RPCServer {
|
|
|
306
296
|
const info = parent.spec.meta.refs?.[ref] || parent.spec.handles?.[ref]
|
|
307
297
|
if (!info) throw CeroError.UNKNOWN('handle type', ref)
|
|
308
298
|
const wire = parent.spec.codec.decodeCreate(data) || {}
|
|
309
|
-
// routes
|
|
299
|
+
// routes are functions and cannot cross the wire
|
|
310
300
|
const opts = { name: wire.name, role: wire.role, accept: wire.noAccept ? false : undefined }
|
|
311
301
|
const child = await this.me._create(ref, opts)
|
|
312
302
|
const id = child.id
|
|
@@ -358,9 +348,7 @@ export class Server extends RPCServer {
|
|
|
358
348
|
}
|
|
359
349
|
|
|
360
350
|
/**
|
|
361
|
-
* Resolve a `{ handle, ref }` pair to its `Ref` and codec.
|
|
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.
|
|
351
|
+
* Resolve a `{ handle, ref }` pair to its `Ref` and codec.
|
|
364
352
|
*
|
|
365
353
|
* @param {string} id
|
|
366
354
|
* @param {string} name
|
|
@@ -370,7 +358,7 @@ export class Server extends RPCServer {
|
|
|
370
358
|
_refOf(id, name, local) {
|
|
371
359
|
if (local) {
|
|
372
360
|
const info = this.spec.meta.local?.refs?.[name]
|
|
373
|
-
if (!info || info.
|
|
361
|
+
if (!info || info.internal) throw CeroError.UNKNOWN('local ref', name)
|
|
374
362
|
const r = this.me.local?.[name]
|
|
375
363
|
if (!r) throw CeroError.UNKNOWN('local ref', name)
|
|
376
364
|
return { ref: r, codec: this.spec.local.codec }
|
|
@@ -409,11 +397,12 @@ export class Server extends RPCServer {
|
|
|
409
397
|
* Construct a `Server`, wait for it to be ready, and return it.
|
|
410
398
|
*
|
|
411
399
|
* @param {any} ipc
|
|
400
|
+
* @param {object} spec
|
|
412
401
|
* @param {ServerOpts} opts
|
|
413
402
|
* @returns {Promise<Server>}
|
|
414
403
|
*/
|
|
415
|
-
export async function serve(ipc, opts) {
|
|
416
|
-
const server = new Server(ipc, opts)
|
|
404
|
+
export async function serve(ipc, spec, opts) {
|
|
405
|
+
const server = new Server(ipc, spec, opts)
|
|
417
406
|
await server.ready()
|
|
418
407
|
return server
|
|
419
408
|
}
|
package/types/build/index.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
extensions?: boolean;
|
|
4
|
-
}): Promise<void>;
|
|
5
|
-
export { getHyperdbType } from "./builtins.js";
|
|
6
|
-
export type Schema = import("@cero-base/core/schema").Schema;
|
|
7
|
-
export type SchemaDefs = import("@cero-base/core/schema").SchemaDefs;
|
|
1
|
+
export type Schema = import('@cero-base/core/schema').Schema;
|
|
2
|
+
export type SchemaDefs = import('@cero-base/core/schema').SchemaDefs;
|
|
8
3
|
export type SchemaInput = Schema | (SchemaDefs & {
|
|
9
4
|
local?: SchemaDefs;
|
|
10
5
|
});
|
|
@@ -14,3 +9,20 @@ export type BuildOpts = {
|
|
|
14
9
|
*/
|
|
15
10
|
ns?: string;
|
|
16
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {import('@cero-base/core/schema').Schema} Schema
|
|
14
|
+
* @typedef {import('@cero-base/core/schema').SchemaDefs} SchemaDefs
|
|
15
|
+
* @typedef {Schema | (SchemaDefs & { local?: SchemaDefs })} SchemaInput
|
|
16
|
+
*
|
|
17
|
+
* @typedef {object} BuildOpts
|
|
18
|
+
* @property {string} [ns] Namespace prefix for emitted schema ids. Defaults to `'cero'`.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Compile a cero schema into wire-level artifacts and write them to disk.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} specDir Output directory.
|
|
24
|
+
* @param {SchemaInput} schema Either a `schema(...)` wrapper or its raw defs object.
|
|
25
|
+
* @param {BuildOpts} [opts]
|
|
26
|
+
* @returns {Promise<void>}
|
|
27
|
+
*/
|
|
28
|
+
export declare function build(specDir: string, schema: SchemaInput, { ns, extensions }?: BuildOpts): Promise<void>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export declare const defs: {
|
|
2
|
+
main: {
|
|
3
|
+
members: {
|
|
4
|
+
type: string;
|
|
5
|
+
};
|
|
6
|
+
devices: {
|
|
7
|
+
type: string;
|
|
8
|
+
};
|
|
9
|
+
invites: {
|
|
10
|
+
type: string;
|
|
11
|
+
};
|
|
12
|
+
handles: {
|
|
13
|
+
type: string;
|
|
14
|
+
};
|
|
15
|
+
files: {
|
|
16
|
+
type: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
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
|
+
environment: {
|
|
32
|
+
type: string;
|
|
33
|
+
kind: string;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export declare function fields(map: any): {
|
|
38
|
+
name: string;
|
|
39
|
+
type: any;
|
|
40
|
+
required: boolean;
|
|
41
|
+
}[];
|
|
42
|
+
export declare function types(scope: any, extend?: {}): {
|
|
43
|
+
name: string;
|
|
44
|
+
compact: boolean;
|
|
45
|
+
fields: {
|
|
46
|
+
name: string;
|
|
47
|
+
type: any;
|
|
48
|
+
required: boolean;
|
|
49
|
+
}[];
|
|
50
|
+
}[];
|
|
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): {
|
|
61
|
+
name: string;
|
|
62
|
+
schema: string;
|
|
63
|
+
key: string[];
|
|
64
|
+
}[];
|
|
65
|
+
export declare function dispatches(ns: any): {
|
|
66
|
+
name: string;
|
|
67
|
+
requestType: string;
|
|
68
|
+
}[];
|
|
69
|
+
export declare function commands(ns: any): {
|
|
70
|
+
name: string | boolean;
|
|
71
|
+
request: {
|
|
72
|
+
name: string;
|
|
73
|
+
};
|
|
74
|
+
response: {
|
|
75
|
+
name: string;
|
|
76
|
+
stream: string | true;
|
|
77
|
+
};
|
|
78
|
+
}[];
|
package/types/build/schemas.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const main: {
|
|
1
|
+
export declare const main: {
|
|
2
2
|
'del-by-id': {
|
|
3
3
|
id: import("@cero-base/core").Prim;
|
|
4
4
|
};
|
|
@@ -80,7 +80,7 @@ export const main: {
|
|
|
80
80
|
stamp: import("@cero-base/core").Prim;
|
|
81
81
|
};
|
|
82
82
|
};
|
|
83
|
-
export const local: {
|
|
83
|
+
export declare const local: {
|
|
84
84
|
master: {
|
|
85
85
|
seed: import("@cero-base/core").Prim;
|
|
86
86
|
};
|
|
@@ -98,7 +98,7 @@ export const local: {
|
|
|
98
98
|
channel: import("@cero-base/core").Prim;
|
|
99
99
|
};
|
|
100
100
|
};
|
|
101
|
-
export const rpc: {
|
|
101
|
+
export declare const rpc: {
|
|
102
102
|
'req-empty': {
|
|
103
103
|
ok: import("@cero-base/core").Prim;
|
|
104
104
|
};
|
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mirror a child handle's `profile` (name + avatar) onto its row in the parent's
|
|
3
|
-
*
|
|
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
|
*/
|
|
12
|
-
export function handleSync({ fields }?: {
|
|
7
|
+
export declare function handleSync({ fields }?: {
|
|
13
8
|
fields?: Record<string, any>;
|
|
14
9
|
}): {
|
|
15
10
|
name: string;
|
|
16
11
|
bundled: boolean;
|
|
17
12
|
schema: {
|
|
18
13
|
handles: {
|
|
19
|
-
kind:
|
|
14
|
+
kind: 'extend';
|
|
20
15
|
fields: Record<string, import("@cero-base/core").Prim>;
|
|
21
16
|
};
|
|
22
17
|
};
|
|
@@ -1,2 +1,24 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from './profile-sync.js';
|
|
2
|
+
export * from './handle-sync.js';
|
|
3
|
+
export declare const registry: ({
|
|
4
|
+
name: string;
|
|
5
|
+
bundled: boolean;
|
|
6
|
+
schema: {
|
|
7
|
+
profile: import("@cero-base/core").TypeDef;
|
|
8
|
+
members: {
|
|
9
|
+
kind: 'extend';
|
|
10
|
+
fields: Record<string, import("@cero-base/core").Prim>;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
setup(me: any): void;
|
|
14
|
+
} | {
|
|
15
|
+
name: string;
|
|
16
|
+
bundled: boolean;
|
|
17
|
+
schema: {
|
|
18
|
+
handles: {
|
|
19
|
+
kind: 'extend';
|
|
20
|
+
fields: Record<string, import("@cero-base/core").Prim>;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
setup(me: any): void;
|
|
24
|
+
})[];
|
|
@@ -1,13 +1,9 @@
|
|
|
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
|
*/
|
|
10
|
-
export function profileSync({ fields }?: {
|
|
6
|
+
export declare function profileSync({ fields }?: {
|
|
11
7
|
fields?: Record<string, any>;
|
|
12
8
|
}): {
|
|
13
9
|
name: string;
|
|
@@ -15,7 +11,7 @@ export function profileSync({ fields }?: {
|
|
|
15
11
|
schema: {
|
|
16
12
|
profile: import("@cero-base/core").TypeDef;
|
|
17
13
|
members: {
|
|
18
|
-
kind:
|
|
14
|
+
kind: 'extend';
|
|
19
15
|
fields: Record<string, import("@cero-base/core").Prim>;
|
|
20
16
|
};
|
|
21
17
|
};
|