@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/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cero-base/cero",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "The ideal p2p API — everything is a handle, handles contain refs, refs contain rows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": ">=
|
|
8
|
+
"node": ">=22"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
"src",
|
|
19
19
|
"types",
|
|
20
20
|
"README.md",
|
|
21
|
-
"LICENSE"
|
|
22
|
-
"!**/CLAUDE.md"
|
|
21
|
+
"LICENSE"
|
|
23
22
|
],
|
|
24
23
|
"publishConfig": {
|
|
25
24
|
"access": "public"
|
|
@@ -82,31 +81,36 @@
|
|
|
82
81
|
"events": {
|
|
83
82
|
"bare": "bare-events",
|
|
84
83
|
"default": "events"
|
|
84
|
+
},
|
|
85
|
+
"process": {
|
|
86
|
+
"bare": "bare-process",
|
|
87
|
+
"default": "process"
|
|
85
88
|
}
|
|
86
89
|
},
|
|
87
90
|
"scripts": {
|
|
88
|
-
"build:test": "rm -rf test/
|
|
91
|
+
"build:test": "rm -rf test/fixtures/spec && node test/fixtures/build.js",
|
|
89
92
|
"build:types": "rm -rf types && tsc -p .",
|
|
90
93
|
"pretest": "npm run build:test",
|
|
91
94
|
"prepublishOnly": "npm run build:types",
|
|
92
95
|
"test": "npm run test:bare",
|
|
93
96
|
"pretest:bare": "npm run build:test",
|
|
94
|
-
"test:bare": "
|
|
95
|
-
"test:node": "
|
|
97
|
+
"test:bare": "find test -name '*.test.js' | sort | xargs -P1 -n1 brittle-bare",
|
|
98
|
+
"test:node": "find test -name '*.test.js' | sort | xargs -P1 -n1 brittle-node"
|
|
96
99
|
},
|
|
97
100
|
"dependencies": {
|
|
98
|
-
"@cero-base/core": "^
|
|
101
|
+
"@cero-base/core": "^2.0.0",
|
|
99
102
|
"b4a": "^1.8.1",
|
|
100
103
|
"bare-abort-controller": "^1.1.2",
|
|
101
104
|
"bare-crypto": "^1.15.3",
|
|
102
105
|
"bare-fs": "^4.8.1",
|
|
103
106
|
"bare-path": "^3.1.2",
|
|
104
107
|
"ble-swarm": "^2.3.0",
|
|
105
|
-
"compact-encoding": "^3.
|
|
106
|
-
"corestore": "^7.12.
|
|
108
|
+
"compact-encoding": "^3.4.0",
|
|
109
|
+
"corestore": "^7.12.3",
|
|
107
110
|
"hrpc": "^4.3.1",
|
|
108
|
-
"hypercore": "^11.35.
|
|
111
|
+
"hypercore": "^11.35.4",
|
|
109
112
|
"hypercore-crypto": "^3.7.0",
|
|
113
|
+
"hypercore-id-encoding": "^1.3.0",
|
|
110
114
|
"hypercore-storage": "^3.2.1",
|
|
111
115
|
"hyperdb": "^6.9.0",
|
|
112
116
|
"hyperdispatch": "^1.6.0",
|
|
@@ -124,7 +128,7 @@
|
|
|
124
128
|
"bare-process": "^4.5.1",
|
|
125
129
|
"bare-url": "^2.5.4",
|
|
126
130
|
"brittle": "^4.1.1",
|
|
127
|
-
"typescript": "^
|
|
131
|
+
"typescript": "^7.0.2"
|
|
128
132
|
},
|
|
129
133
|
"license": "Apache-2.0"
|
|
130
134
|
}
|
package/src/build/index.js
CHANGED
|
@@ -9,18 +9,8 @@ import HRPCBuilder from 'hrpc'
|
|
|
9
9
|
import { CeroError } from '@cero-base/core/errors'
|
|
10
10
|
|
|
11
11
|
import { NS } from '../lib/constants.js'
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
14
|
-
refs,
|
|
15
|
-
builtinRefs,
|
|
16
|
-
builtinTypes,
|
|
17
|
-
builtinCollections,
|
|
18
|
-
builtinDispatches,
|
|
19
|
-
rotateDispatch,
|
|
20
|
-
rpcTypes,
|
|
21
|
-
rpcCommands,
|
|
22
|
-
getHyperdbType
|
|
23
|
-
} from './builtins.js'
|
|
12
|
+
import { registry } from '../extensions/index.js'
|
|
13
|
+
import * as internal from './internal.js'
|
|
24
14
|
|
|
25
15
|
/**
|
|
26
16
|
* @typedef {import('@cero-base/core/schema').Schema} Schema
|
|
@@ -34,29 +24,22 @@ import {
|
|
|
34
24
|
/**
|
|
35
25
|
* Compile a cero schema into wire-level artifacts and write them to disk.
|
|
36
26
|
*
|
|
37
|
-
* Emits a `main/` tree (schema + hyperdb + dispatch + rpc), a `local/` tree
|
|
38
|
-
* for per-device data, one `handles/<name>/` tree per child handle type, and
|
|
39
|
-
* an `index.js` that re-exports a ready-to-use `spec` object.
|
|
40
|
-
*
|
|
41
27
|
* @param {string} specDir Output directory.
|
|
42
28
|
* @param {SchemaInput} schema Either a `schema(...)` wrapper or its raw defs object.
|
|
43
29
|
* @param {BuildOpts} [opts]
|
|
44
30
|
* @returns {Promise<void>}
|
|
45
31
|
*/
|
|
46
|
-
export { getHyperdbType } from './builtins.js'
|
|
47
|
-
|
|
48
32
|
export async function build(specDir, schema, { ns = NS, extensions = true } = {}) {
|
|
49
33
|
const raw = /** @type {SchemaDefs & { local?: SchemaDefs }} */ (schema?.defs || schema)
|
|
50
34
|
if (!raw || typeof raw !== 'object') throw CeroError.REQUIRED('schema')
|
|
51
35
|
|
|
52
|
-
//
|
|
53
|
-
// name, merged across scopes. App schema first, then each `cero.use()` extension.
|
|
36
|
+
// t.extend entries by internal type: app schema first, then each extension
|
|
54
37
|
const extend = {}
|
|
55
38
|
const defs = {}
|
|
56
39
|
const collect = (entries, fromExt = false) => {
|
|
57
40
|
for (const [k, v] of Object.entries(entries)) {
|
|
58
41
|
if (v && v.kind === 'extend') {
|
|
59
|
-
const type =
|
|
42
|
+
const type = internal.defs.main[k]?.type
|
|
60
43
|
if (!type) throw CeroError.INVALID(`'${k}' is not an extendable builtin`)
|
|
61
44
|
// app schema wins on field conflicts, extensions only add new fields
|
|
62
45
|
extend[type] = fromExt ? { ...v.fields, ...extend[type] } : { ...extend[type], ...v.fields }
|
|
@@ -66,7 +49,7 @@ export async function build(specDir, schema, { ns = NS, extensions = true } = {}
|
|
|
66
49
|
}
|
|
67
50
|
}
|
|
68
51
|
collect(raw)
|
|
69
|
-
for (const ext of
|
|
52
|
+
for (const ext of registry) {
|
|
70
53
|
if (ext.bundled && extensions === false) continue
|
|
71
54
|
if (ext.schema) collect(ext.schema, true)
|
|
72
55
|
}
|
|
@@ -93,7 +76,7 @@ export async function build(specDir, schema, { ns = NS, extensions = true } = {}
|
|
|
93
76
|
)
|
|
94
77
|
}
|
|
95
78
|
|
|
96
|
-
//
|
|
79
|
+
// the type marker tells the runtime which handle type to open
|
|
97
80
|
for (const [name] of Object.entries(handles)) {
|
|
98
81
|
if (meta.refs[name]) meta.refs[name] = { kind: 'handle', type: name, schema: `@${ns}/handle` }
|
|
99
82
|
}
|
|
@@ -138,7 +121,7 @@ function compile(root, ns, scope = 'main') {
|
|
|
138
121
|
scope
|
|
139
122
|
}
|
|
140
123
|
|
|
141
|
-
Object.assign(ctx.meta.refs,
|
|
124
|
+
Object.assign(ctx.meta.refs, internal.refs(ns, scope))
|
|
142
125
|
|
|
143
126
|
for (const [name, node] of Object.entries(root)) {
|
|
144
127
|
if (node.kind === 'handle') {
|
|
@@ -166,13 +149,13 @@ function fileFieldNames(fields) {
|
|
|
166
149
|
function register(name, node, ctx) {
|
|
167
150
|
const fqn = `@${ctx.ns}/${name}`
|
|
168
151
|
if (node.kind === 'action') {
|
|
169
|
-
ctx.types.push({ name, compact: false, fields:
|
|
152
|
+
ctx.types.push({ name, compact: false, fields: internal.fields(node.fields) })
|
|
170
153
|
ctx.dispatches.push({ name, requestType: fqn })
|
|
171
154
|
ctx.meta.refs[name] = { kind: 'action', path: [name], schema: fqn }
|
|
172
155
|
return
|
|
173
156
|
}
|
|
174
157
|
if (node.kind === 'single') {
|
|
175
|
-
ctx.types.push({ name, compact: false, fields:
|
|
158
|
+
ctx.types.push({ name, compact: false, fields: internal.fields(node.fields) })
|
|
176
159
|
ctx.collections.push({ name, schema: fqn, key: [] })
|
|
177
160
|
ctx.dispatches.push({ name: `set-${name}`, requestType: fqn })
|
|
178
161
|
ctx.dispatches.push({ name: `del-${name}`, requestType: `@${ctx.ns}/del-by-id` })
|
|
@@ -192,7 +175,7 @@ function register(name, node, ctx) {
|
|
|
192
175
|
{ name: 'index', type: 'uint', required: false },
|
|
193
176
|
{ name: 'createdAt', type: 'int', required: false },
|
|
194
177
|
{ name: 'updatedAt', type: 'int', required: false },
|
|
195
|
-
...
|
|
178
|
+
...internal.fields(node.fields)
|
|
196
179
|
]
|
|
197
180
|
})
|
|
198
181
|
ctx.collections.push({ name, schema: fqn, key: ['id'] })
|
|
@@ -215,10 +198,7 @@ function register(name, node, ctx) {
|
|
|
215
198
|
}
|
|
216
199
|
ctx.meta.refs[name].indexes = node.indexes
|
|
217
200
|
}
|
|
218
|
-
// implicit
|
|
219
|
-
// reverse/limit down to hyperdb instead of scanning the whole collection.
|
|
220
|
-
// main scope only: local (Storage) rows never get `index` stamped, and an
|
|
221
|
-
// undefined index key crashes the encoder on insert.
|
|
201
|
+
// implicit index on the auto-increment `index`, so reverse and limit push down; main scope only
|
|
222
202
|
if (ctx.scope === 'main' && !node.indexes?.index) {
|
|
223
203
|
ctx.indexes.push({ name: `${name}-index`, collection: fqn, key: ['index'] })
|
|
224
204
|
refEntry.orderIndex = true
|
|
@@ -226,17 +206,7 @@ function register(name, node, ctx) {
|
|
|
226
206
|
}
|
|
227
207
|
}
|
|
228
208
|
|
|
229
|
-
|
|
230
|
-
return Object.entries(fields).map(([name, marker]) => ({
|
|
231
|
-
name,
|
|
232
|
-
type: getHyperdbType(marker.prim),
|
|
233
|
-
required: marker.required === true
|
|
234
|
-
}))
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// The contract version is the highest of the three auto-bumped artifact
|
|
238
|
-
// versions — any schema/db/dispatch change advances it, so a peer can tell
|
|
239
|
-
// whether an op predates or postdates its own build.
|
|
209
|
+
// the contract version is the highest of the schema, db and dispatch versions
|
|
240
210
|
async function contractVersion(mainDir) {
|
|
241
211
|
const read = async (rel) => {
|
|
242
212
|
const j = JSON.parse(await fs.readFile(join(mainDir, rel), 'utf-8'))
|
|
@@ -258,29 +228,30 @@ function emitMain(dir, ns, { types, collections, dispatches, indexes = [] }, { r
|
|
|
258
228
|
|
|
259
229
|
const s = Hyperschema.from(schemaDir)
|
|
260
230
|
const sns = s.namespace(ns)
|
|
261
|
-
for (const desc of
|
|
231
|
+
for (const desc of internal.types('main', extend)) sns.register(desc)
|
|
262
232
|
for (const desc of types) sns.register(desc)
|
|
263
|
-
if (rpc) for (const desc of
|
|
233
|
+
if (rpc) for (const desc of internal.types('rpc')) sns.register(desc)
|
|
264
234
|
Hyperschema.toDisk(s, schemaDir, { esm: true })
|
|
265
235
|
|
|
266
236
|
const db = HyperdbBuilder.from(schemaDir, dbDir)
|
|
267
237
|
const dns = db.namespace(ns)
|
|
268
|
-
for (const desc of
|
|
238
|
+
for (const desc of internal.collections(ns, 'main')) dns.collections.register(desc)
|
|
269
239
|
for (const desc of collections) dns.collections.register(desc)
|
|
270
240
|
for (const desc of indexes) dns.indexes.register(desc)
|
|
271
241
|
HyperdbBuilder.toDisk(db, dbDir, { esm: true })
|
|
272
242
|
|
|
273
243
|
const d = Hyperdispatch.from(schemaDir, dispatchDir)
|
|
274
244
|
const xns = d.namespace(ns)
|
|
275
|
-
for (const desc of
|
|
245
|
+
for (const desc of internal.dispatches(ns)) xns.register(desc)
|
|
276
246
|
for (const desc of dispatches) xns.register(desc)
|
|
277
|
-
|
|
247
|
+
// after the app's dispatches: hyperdispatch numbers routes positionally and persists them
|
|
248
|
+
xns.register({ name: 'rotate-key', requestType: `@${ns}/epoch` })
|
|
278
249
|
Hyperdispatch.toDisk(d, dispatchDir, { esm: true })
|
|
279
250
|
|
|
280
251
|
if (rpc) {
|
|
281
252
|
const r = HRPCBuilder.from(schemaDir, rpcDir)
|
|
282
253
|
const rns = r.namespace(ns)
|
|
283
|
-
for (const desc of
|
|
254
|
+
for (const desc of internal.commands(ns)) rns.register(desc)
|
|
284
255
|
HRPCBuilder.toDisk(r, rpcDir, { esm: true })
|
|
285
256
|
}
|
|
286
257
|
}
|
|
@@ -291,13 +262,13 @@ function emitLocal(dir, ns, { types, collections, indexes = [] }) {
|
|
|
291
262
|
|
|
292
263
|
const s = Hyperschema.from(schemaDir)
|
|
293
264
|
const sns = s.namespace(ns)
|
|
294
|
-
for (const desc of
|
|
265
|
+
for (const desc of internal.types('local')) sns.register(desc)
|
|
295
266
|
for (const desc of types) sns.register(desc)
|
|
296
267
|
Hyperschema.toDisk(s, schemaDir, { esm: true })
|
|
297
268
|
|
|
298
269
|
const db = HyperdbBuilder.from(schemaDir, dbDir)
|
|
299
270
|
const dns = db.namespace(ns)
|
|
300
|
-
for (const desc of
|
|
271
|
+
for (const desc of internal.collections(ns, 'local')) dns.collections.register(desc)
|
|
301
272
|
for (const desc of collections) dns.collections.register(desc)
|
|
302
273
|
for (const desc of indexes) dns.indexes.register(desc)
|
|
303
274
|
HyperdbBuilder.toDisk(db, dbDir, { esm: true })
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { CeroError } from '@cero-base/core/errors'
|
|
2
|
+
import { COUNTERS, EPOCHS } from '../lib/constants.js'
|
|
3
|
+
import * as schemas from './schemas.js'
|
|
4
|
+
|
|
5
|
+
// internal refs by scope; kind defaults to collection
|
|
6
|
+
export const defs = {
|
|
7
|
+
main: {
|
|
8
|
+
members: { type: 'member' },
|
|
9
|
+
devices: { type: 'device' },
|
|
10
|
+
invites: { type: 'invite' },
|
|
11
|
+
handles: { type: 'handle' },
|
|
12
|
+
files: { type: 'file' }
|
|
13
|
+
},
|
|
14
|
+
local: {
|
|
15
|
+
master: { type: 'master', kind: 'single' },
|
|
16
|
+
keypair: { type: 'keypair', kind: 'single' },
|
|
17
|
+
'handle-keypairs': { type: 'handle-keypair' },
|
|
18
|
+
environment: { type: 'environment', kind: 'single' }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// a DSL primitive is its hyperdb column type, except bytes (a buffer) and file (an id string)
|
|
23
|
+
const COLUMN = { bytes: 'buffer', file: 'string' }
|
|
24
|
+
|
|
25
|
+
export function fields(map) {
|
|
26
|
+
return Object.entries(map).map(([name, m]) => ({
|
|
27
|
+
name,
|
|
28
|
+
type: COLUMN[m.prim] || m.prim,
|
|
29
|
+
required: m.required === true
|
|
30
|
+
}))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// a base field cannot be redeclared
|
|
34
|
+
function merge(type, base, extra) {
|
|
35
|
+
if (!extra) return base
|
|
36
|
+
const dup = Object.keys(extra).find((k) => k in base)
|
|
37
|
+
if (dup) throw CeroError.INVALID(`'${dup}' is a base field of '${type}' and cannot be redeclared`)
|
|
38
|
+
return { ...base, ...extra }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function types(scope, extend = {}) {
|
|
42
|
+
return Object.entries(schemas[scope]).map(([name, base]) => ({
|
|
43
|
+
name,
|
|
44
|
+
compact: false,
|
|
45
|
+
fields: fields(merge(name, base, extend[name]))
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function refs(ns, scope) {
|
|
50
|
+
return Object.fromEntries(
|
|
51
|
+
Object.entries(defs[scope]).map(([name, def]) => [
|
|
52
|
+
name,
|
|
53
|
+
{
|
|
54
|
+
kind: def.kind || 'collection',
|
|
55
|
+
path: [name],
|
|
56
|
+
internal: true,
|
|
57
|
+
...(scope === 'main' && { verb: def.type }),
|
|
58
|
+
schema: `@${ns}/${def.type}`
|
|
59
|
+
}
|
|
60
|
+
])
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function collections(ns, scope) {
|
|
65
|
+
const out = Object.entries(defs[scope]).map(([name, def]) => ({
|
|
66
|
+
name,
|
|
67
|
+
schema: `@${ns}/${def.type}`,
|
|
68
|
+
key: def.kind === 'single' ? [] : ['id']
|
|
69
|
+
}))
|
|
70
|
+
if (scope === 'main') {
|
|
71
|
+
out.push({ name: COUNTERS, schema: `@${ns}/counter`, key: ['name'] })
|
|
72
|
+
out.push({ name: EPOCHS, schema: `@${ns}/epoch`, key: ['epoch'] })
|
|
73
|
+
}
|
|
74
|
+
return out
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function dispatches(ns) {
|
|
78
|
+
return [
|
|
79
|
+
{ name: 'add-writer', requestType: `@${ns}/writer` },
|
|
80
|
+
{ name: 'del-writer', requestType: `@${ns}/writer` },
|
|
81
|
+
{ name: 'claim-writer', requestType: `@${ns}/claim` },
|
|
82
|
+
...Object.values(defs.main).flatMap(({ type }) => [
|
|
83
|
+
{ name: `add-${type}`, requestType: `@${ns}/${type}` },
|
|
84
|
+
{ name: `set-${type}`, requestType: `@${ns}/${type}` },
|
|
85
|
+
{ name: `del-${type}`, requestType: `@${ns}/del-by-id` }
|
|
86
|
+
])
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// hrpc commands: name, request type, response type, streaming response
|
|
91
|
+
const COMMANDS = [
|
|
92
|
+
['init', 'req-empty', 'res-identity'],
|
|
93
|
+
['restore', 'req-restore', 'res-identity'],
|
|
94
|
+
['seed', 'req-empty', 'res-seed'],
|
|
95
|
+
['add-row', 'req-row', 'res-data'],
|
|
96
|
+
['add-file', 'req-add-file', 'res-data'],
|
|
97
|
+
['add-handle', 'req-row', 'res-handle'],
|
|
98
|
+
['set', 'req-row', 'res-data'],
|
|
99
|
+
['get', 'req-query', 'res-rows'],
|
|
100
|
+
['get-one', 'req-id', 'res-data'],
|
|
101
|
+
['del', 'req-id', 'res-ok'],
|
|
102
|
+
['count', 'req-query', 'res-count'],
|
|
103
|
+
['watch', 'req-query', 'res-rows', true],
|
|
104
|
+
['call', 'req-call', 'res-data'],
|
|
105
|
+
['invite', 'req-invite', 'res-invite'],
|
|
106
|
+
['revoke', 'req-revoke', 'res-ok'],
|
|
107
|
+
['join', 'req-join', 'res-handle'],
|
|
108
|
+
['open-handle', 'req-open', 'res-handle'],
|
|
109
|
+
['close-handle', 'req-handle', 'res-ok'],
|
|
110
|
+
['leave', 'req-handle', 'res-ok'],
|
|
111
|
+
['changes', 'req-query', 'res-changes', true],
|
|
112
|
+
['rotate', 'req-handle', 'res-epoch']
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
export function commands(ns) {
|
|
116
|
+
return COMMANDS.map(([name, req, res, stream]) => ({
|
|
117
|
+
name,
|
|
118
|
+
request: { name: `@${ns}/${req}` },
|
|
119
|
+
response: { name: `@${ns}/${res}`, ...(stream && { stream }) }
|
|
120
|
+
}))
|
|
121
|
+
}
|
package/src/build/schemas.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// the build wiring (builtins.js) so apps can extend them with `t.extend`.
|
|
3
|
-
// Field order is the wire order; do not reorder.
|
|
1
|
+
// field order is the wire format: append only, never reorder
|
|
4
2
|
import { t } from '../lib/spec.js'
|
|
5
3
|
|
|
6
4
|
const { string, bytes, int, uint, bool, required } = t
|
|
@@ -15,11 +13,7 @@ export const main = {
|
|
|
15
13
|
sig: required(bytes),
|
|
16
14
|
isIndexer: bool,
|
|
17
15
|
ts: int,
|
|
18
|
-
//
|
|
19
|
-
// the same transaction, and a refused grant discards this admission with
|
|
20
|
-
// it — so `role` is unread since 77e5ed0 and stays only for older peers.
|
|
21
|
-
// Absent on a device you add for yourself (recovery), where the device
|
|
22
|
-
// binds to the master identity.
|
|
16
|
+
// the paired add-member decides the rank in the same transaction; `role` here is unread
|
|
23
17
|
memberId: string,
|
|
24
18
|
role: string
|
|
25
19
|
},
|
|
@@ -2,13 +2,8 @@ import { t } from '../lib/spec.js'
|
|
|
2
2
|
import { get, set, watch } from '../lib/operators.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Mirror a child handle's `profile` (name + avatar) onto its row in the parent's
|
|
6
|
-
*
|
|
7
|
-
* one. Adds the synced `fields` to the `handle` builtin.
|
|
8
|
-
*
|
|
9
|
-
* On create it seeds the handle's `profile.name` from the open `{ name }`, then
|
|
10
|
-
* reflects the (app-owned) `profile` onto the row. Requires your handle types to
|
|
11
|
-
* declare a `profile` single — handles without one are left untouched.
|
|
5
|
+
* Mirror a child handle's `profile` (name + avatar) onto its row in the parent's `handles`
|
|
6
|
+
* list — so a handle list shows names + avatars without opening each one.
|
|
12
7
|
*
|
|
13
8
|
* @param {{ fields?: Record<string, any> }} [opts]
|
|
14
9
|
*/
|
|
@@ -19,9 +14,7 @@ export function handleSync({ fields = { avatar: t.string } } = {}) {
|
|
|
19
14
|
schema: { handles: t.extend(fields) },
|
|
20
15
|
setup(me) {
|
|
21
16
|
const keys = ['name', ...Object.keys(fields)]
|
|
22
|
-
//
|
|
23
|
-
// re-emits the watch snapshot, and an unconditional set is a new op in
|
|
24
|
-
// the replicated log forever
|
|
17
|
+
// an unconditional set on every open is a new op in the log forever
|
|
25
18
|
const reflect = async (child, data) => {
|
|
26
19
|
child.name = data.name
|
|
27
20
|
const { data: row } = await get(me.handles, child.id)
|
package/src/extensions/index.js
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
import { profileSync } from './profile-sync.js'
|
|
2
|
+
import { handleSync } from './handle-sync.js'
|
|
3
|
+
|
|
1
4
|
export * from './profile-sync.js'
|
|
2
5
|
export * from './handle-sync.js'
|
|
6
|
+
|
|
7
|
+
// process-wide: build() folds in each schema, cero() runs each setup
|
|
8
|
+
export const registry = [profileSync(), handleSync()]
|
|
@@ -3,10 +3,6 @@ import { get, set, after } from '../lib/operators.js'
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
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
6
|
*
|
|
11
7
|
* @param {{ fields?: Record<string, any> }} [opts]
|
|
12
8
|
*/
|
|
@@ -23,7 +19,6 @@ export function profileSync({ fields = { avatar: t.string } } = {}) {
|
|
|
23
19
|
const publish = (child, profile) =>
|
|
24
20
|
set(child.members, { id: me.identity.id, ...profile }, { upsert: false }).catch(me._onerror)
|
|
25
21
|
|
|
26
|
-
// skip the broadcast when the member row already mirrors the profile —
|
|
27
22
|
// an unconditional set on every open is a room-wide op forever
|
|
28
23
|
const onHandle = async (child) => {
|
|
29
24
|
const { data: profile } = await get(me.profile)
|