@cero-base/cero 0.4.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/LICENSE +201 -0
- package/README.md +287 -0
- package/package.json +79 -0
- package/src/CLAUDE.md +3 -0
- package/src/builder.js +276 -0
- package/src/handle/CLAUDE.md +3 -0
- package/src/handle/index.js +600 -0
- package/src/index.js +170 -0
- package/src/lib/CLAUDE.md +3 -0
- package/src/lib/builtins.js +410 -0
- package/src/lib/operators.js +128 -0
- package/src/lib/peek.js +36 -0
- package/src/lib/spec.js +6 -0
- package/src/lib/utils.js +41 -0
- package/src/local/index.js +49 -0
- package/src/rpc/CLAUDE.md +3 -0
- package/src/rpc/client.js +337 -0
- package/src/rpc/index.js +8 -0
- package/src/rpc/server.js +290 -0
- package/types/builder.d.ts +32 -0
- package/types/handle/index.d.ts +353 -0
- package/types/index.d.ts +102 -0
- package/types/lib/builtins.d.ts +141 -0
- package/types/lib/operators.d.ts +29 -0
- package/types/lib/peek.d.ts +10 -0
- package/types/lib/spec.d.ts +1 -0
- package/types/lib/utils.d.ts +44 -0
- package/types/local/index.d.ts +33 -0
- package/types/rpc/client.d.ts +125 -0
- package/types/rpc/index.d.ts +2 -0
- package/types/rpc/server.d.ts +154 -0
package/src/builder.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { join } from 'path'
|
|
2
|
+
import { promises as fs } from 'fs'
|
|
3
|
+
|
|
4
|
+
import Hyperschema from 'hyperschema'
|
|
5
|
+
import HyperdbBuilder from 'hyperdb/builder'
|
|
6
|
+
import Hyperdispatch from 'hyperdispatch'
|
|
7
|
+
import HRPCBuilder from 'hrpc'
|
|
8
|
+
|
|
9
|
+
import { CeroError } from '@cero-base/core/errors'
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
BUILTINS,
|
|
13
|
+
LOCAL_BUILTINS,
|
|
14
|
+
hyperdbType,
|
|
15
|
+
builtinTypes,
|
|
16
|
+
builtinCollections,
|
|
17
|
+
builtinDispatches,
|
|
18
|
+
localBuiltinTypes,
|
|
19
|
+
localBuiltinCollections,
|
|
20
|
+
rpcTypes,
|
|
21
|
+
rpcCommands
|
|
22
|
+
} from './lib/builtins.js'
|
|
23
|
+
|
|
24
|
+
const NS = 'cero'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {import('@cero-base/core/schema').Schema} Schema
|
|
28
|
+
* @typedef {import('@cero-base/core/schema').SchemaDefs} SchemaDefs
|
|
29
|
+
* @typedef {Schema | (SchemaDefs & { local?: SchemaDefs })} SchemaInput
|
|
30
|
+
*
|
|
31
|
+
* @typedef {object} BuildOpts
|
|
32
|
+
* @property {string} [ns] Namespace prefix for emitted schema ids. Defaults to `'cero'`.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Compile a cero schema into wire-level artifacts and write them to disk.
|
|
37
|
+
*
|
|
38
|
+
* Emits a `main/` tree (schema + hyperdb + dispatch + rpc), a `local/` tree
|
|
39
|
+
* for per-device data, one `handles/<name>/` tree per child handle type, and
|
|
40
|
+
* an `index.js` that re-exports a ready-to-use `spec` object.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} specDir Output directory.
|
|
43
|
+
* @param {SchemaInput} schema Either a `schema(...)` wrapper or its raw defs object.
|
|
44
|
+
* @param {BuildOpts} [opts]
|
|
45
|
+
* @returns {Promise<void>}
|
|
46
|
+
*/
|
|
47
|
+
export async function build(specDir, schema, { ns = NS } = {}) {
|
|
48
|
+
const defs = /** @type {SchemaDefs & { local?: SchemaDefs }} */ (schema?.defs || schema)
|
|
49
|
+
if (!defs || typeof defs !== 'object') throw CeroError.REQUIRED('schema')
|
|
50
|
+
|
|
51
|
+
const main = compile(splitMain(defs), ns, BUILTINS)
|
|
52
|
+
const local = defs.local
|
|
53
|
+
? compile(defs.local, ns, LOCAL_BUILTINS)
|
|
54
|
+
: compile({}, ns, LOCAL_BUILTINS)
|
|
55
|
+
const handles = {}
|
|
56
|
+
for (const [name, child] of Object.entries(splitHandles(defs))) {
|
|
57
|
+
handles[name] = compile(child, ns)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
emitMain(join(specDir, 'main'), ns, main, { rpc: true })
|
|
61
|
+
emitLocal(join(specDir, 'local'), ns, local)
|
|
62
|
+
for (const [name, handle] of Object.entries(handles)) {
|
|
63
|
+
emitMain(join(specDir, 'handles', name), ns, handle, { rpc: false })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const meta = {
|
|
67
|
+
...main.meta,
|
|
68
|
+
local: local.meta,
|
|
69
|
+
handles: Object.fromEntries(
|
|
70
|
+
Object.entries(handles).map(([n, h]) => [n, { ...h.meta, type: n }])
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Hang a `type: <name>` marker on each handle ref in main so the runtime knows which type to open.
|
|
75
|
+
for (const [name] of Object.entries(handles)) {
|
|
76
|
+
if (meta.refs[name]) meta.refs[name] = { kind: 'handle', type: name, schema: `@${ns}/handle` }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await fs.writeFile(join(specDir, 'index.js'), wireModule(meta, Object.keys(handles)), 'utf-8')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function splitMain(defs) {
|
|
83
|
+
const out = {}
|
|
84
|
+
for (const [k, v] of Object.entries(defs)) {
|
|
85
|
+
if (k === 'local') continue
|
|
86
|
+
if (isPlainHandle(v)) {
|
|
87
|
+
out[k] = { kind: 'handle', type: k }
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
out[k] = v
|
|
91
|
+
}
|
|
92
|
+
return out
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function splitHandles(defs) {
|
|
96
|
+
const out = {}
|
|
97
|
+
for (const [k, v] of Object.entries(defs)) {
|
|
98
|
+
if (k === 'local') continue
|
|
99
|
+
if (isPlainHandle(v)) out[k] = v
|
|
100
|
+
}
|
|
101
|
+
return out
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isPlainHandle(v) {
|
|
105
|
+
return v && typeof v === 'object' && !v.kind && !v.prim
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function compile(root, ns, builtins = BUILTINS) {
|
|
109
|
+
const ctx = { types: [], collections: [], dispatches: [], meta: { ns, refs: {} }, ns }
|
|
110
|
+
|
|
111
|
+
for (const b of builtins) {
|
|
112
|
+
ctx.meta.refs[b.name] = {
|
|
113
|
+
kind: b.kind || 'collection',
|
|
114
|
+
path: [b.name],
|
|
115
|
+
builtin: true,
|
|
116
|
+
verb: b.verb,
|
|
117
|
+
schema: `@${ns}/${b.type}`
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const [name, node] of Object.entries(root)) {
|
|
122
|
+
if (node.kind === 'handle') {
|
|
123
|
+
ctx.meta.refs[name] = { kind: 'handle', type: node.type || name, schema: `@${ns}/handle` }
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
register(name, node, ctx)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
types: ctx.types,
|
|
131
|
+
collections: ctx.collections,
|
|
132
|
+
dispatches: ctx.dispatches,
|
|
133
|
+
meta: ctx.meta
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function register(name, node, ctx) {
|
|
138
|
+
const fqn = `@${ctx.ns}/${name}`
|
|
139
|
+
if (node.kind === 'action') {
|
|
140
|
+
ctx.types.push({ name, compact: false, fields: fieldsFor(node.fields) })
|
|
141
|
+
ctx.dispatches.push({ name, requestType: fqn })
|
|
142
|
+
ctx.meta.refs[name] = { kind: 'action', path: [name], schema: fqn }
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
if (node.kind === 'single') {
|
|
146
|
+
ctx.types.push({ name, compact: false, fields: fieldsFor(node.fields) })
|
|
147
|
+
ctx.collections.push({ name, schema: fqn, key: [] })
|
|
148
|
+
ctx.dispatches.push({ name: `set-${name}`, requestType: fqn })
|
|
149
|
+
ctx.meta.refs[name] = { kind: 'single', path: [name], schema: fqn }
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
if (node.kind === 'collection') {
|
|
153
|
+
ctx.types.push({
|
|
154
|
+
name,
|
|
155
|
+
compact: false,
|
|
156
|
+
fields: [
|
|
157
|
+
{ name: 'id', type: 'string', required: true },
|
|
158
|
+
{ name: 'memberId', type: 'string', required: false },
|
|
159
|
+
{ name: 'index', type: 'uint', required: false },
|
|
160
|
+
{ name: 'createdAt', type: 'int', required: false },
|
|
161
|
+
{ name: 'updatedAt', type: 'int', required: false },
|
|
162
|
+
...fieldsFor(node.fields)
|
|
163
|
+
]
|
|
164
|
+
})
|
|
165
|
+
ctx.collections.push({ name, schema: fqn, key: ['id'] })
|
|
166
|
+
ctx.dispatches.push({ name: `add-${name}`, requestType: fqn })
|
|
167
|
+
ctx.dispatches.push({ name: `set-${name}`, requestType: fqn })
|
|
168
|
+
ctx.dispatches.push({ name: `del-${name}`, requestType: `@${ctx.ns}/del-by-id` })
|
|
169
|
+
ctx.meta.refs[name] = { kind: 'collection', path: [name], schema: fqn }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function fieldsFor(fields) {
|
|
174
|
+
return Object.entries(fields).map(([name, marker]) => ({
|
|
175
|
+
name,
|
|
176
|
+
type: hyperdbType(marker.prim),
|
|
177
|
+
required: false
|
|
178
|
+
}))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function emitMain(dir, ns, { types, collections, dispatches }, { rpc }) {
|
|
182
|
+
const schemaDir = join(dir, 'schema')
|
|
183
|
+
const dbDir = join(dir, 'db')
|
|
184
|
+
const dispatchDir = join(dir, 'dispatch')
|
|
185
|
+
const rpcDir = join(dir, 'rpc')
|
|
186
|
+
|
|
187
|
+
const s = Hyperschema.from(schemaDir)
|
|
188
|
+
const sns = s.namespace(ns)
|
|
189
|
+
for (const desc of builtinTypes()) sns.register(desc)
|
|
190
|
+
for (const desc of types) sns.register(desc)
|
|
191
|
+
if (rpc) for (const desc of rpcTypes()) sns.register(desc)
|
|
192
|
+
Hyperschema.toDisk(s, schemaDir, { esm: true })
|
|
193
|
+
|
|
194
|
+
const db = HyperdbBuilder.from(schemaDir, dbDir)
|
|
195
|
+
const dns = db.namespace(ns)
|
|
196
|
+
for (const desc of builtinCollections(ns)) dns.collections.register(desc)
|
|
197
|
+
for (const desc of collections) dns.collections.register(desc)
|
|
198
|
+
HyperdbBuilder.toDisk(db, dbDir, { esm: true })
|
|
199
|
+
|
|
200
|
+
const d = Hyperdispatch.from(schemaDir, dispatchDir)
|
|
201
|
+
const xns = d.namespace(ns)
|
|
202
|
+
for (const desc of builtinDispatches(ns)) xns.register(desc)
|
|
203
|
+
for (const desc of dispatches) xns.register(desc)
|
|
204
|
+
Hyperdispatch.toDisk(d, dispatchDir, { esm: true })
|
|
205
|
+
|
|
206
|
+
if (rpc) {
|
|
207
|
+
const r = HRPCBuilder.from(schemaDir, rpcDir)
|
|
208
|
+
const rns = r.namespace(ns)
|
|
209
|
+
for (const desc of rpcCommands(ns)) rns.register(desc)
|
|
210
|
+
HRPCBuilder.toDisk(r, rpcDir, { esm: true })
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function emitLocal(dir, ns, { types, collections }) {
|
|
215
|
+
const schemaDir = join(dir, 'schema')
|
|
216
|
+
const dbDir = join(dir, 'db')
|
|
217
|
+
|
|
218
|
+
const s = Hyperschema.from(schemaDir)
|
|
219
|
+
const sns = s.namespace(ns)
|
|
220
|
+
for (const desc of localBuiltinTypes()) sns.register(desc)
|
|
221
|
+
for (const desc of types) sns.register(desc)
|
|
222
|
+
Hyperschema.toDisk(s, schemaDir, { esm: true })
|
|
223
|
+
|
|
224
|
+
const db = HyperdbBuilder.from(schemaDir, dbDir)
|
|
225
|
+
const dns = db.namespace(ns)
|
|
226
|
+
for (const desc of localBuiltinCollections(ns)) dns.collections.register(desc)
|
|
227
|
+
for (const desc of collections) dns.collections.register(desc)
|
|
228
|
+
HyperdbBuilder.toDisk(db, dbDir, { esm: true })
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const IMPORTS = {
|
|
232
|
+
database: (n) => `import ${n}Database from './handles/${n}/db/index.js'`,
|
|
233
|
+
dispatch: (n) => `import * as ${n}Dispatch from './handles/${n}/dispatch/index.js'`,
|
|
234
|
+
schema: (n) => `import * as ${n}Schema from './handles/${n}/schema/index.js'`
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const cap = (s) => s[0].toUpperCase() + s.slice(1)
|
|
238
|
+
|
|
239
|
+
function handleImports(names, kinds) {
|
|
240
|
+
return names.flatMap((n) => kinds.map((k) => IMPORTS[k](n))).join('\n')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function handleEntries(names, kinds) {
|
|
244
|
+
return names
|
|
245
|
+
.map((n) => {
|
|
246
|
+
const parts = kinds.map((k) => `${k}: ${n}${cap(k)}`).join(', ')
|
|
247
|
+
return ` ${n}: { ${parts}, meta: meta.handles.${n} }`
|
|
248
|
+
})
|
|
249
|
+
.join(',\n')
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function wireModule(meta, names) {
|
|
253
|
+
const kinds = ['database', 'dispatch', 'schema']
|
|
254
|
+
return `// autogenerated by cero/build
|
|
255
|
+
import database from './main/db/index.js'
|
|
256
|
+
import * as dispatch from './main/dispatch/index.js'
|
|
257
|
+
import * as schema from './main/schema/index.js'
|
|
258
|
+
import rpc from './main/rpc/index.js'
|
|
259
|
+
import localDatabase from './local/db/index.js'
|
|
260
|
+
${handleImports(names, kinds)}
|
|
261
|
+
|
|
262
|
+
export const meta = ${JSON.stringify(meta, null, 2)}
|
|
263
|
+
|
|
264
|
+
export const spec = {
|
|
265
|
+
database,
|
|
266
|
+
dispatch,
|
|
267
|
+
schema,
|
|
268
|
+
rpc,
|
|
269
|
+
local: { database: localDatabase },
|
|
270
|
+
meta,
|
|
271
|
+
handles: {
|
|
272
|
+
${handleEntries(names, kinds)}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
`
|
|
276
|
+
}
|