@cero-base/core 0.2.0 → 0.4.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.
Files changed (52) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +180 -136
  3. package/package.json +123 -28
  4. package/src/blobs/index.js +297 -0
  5. package/src/database/CLAUDE.md +3 -0
  6. package/src/database/bootstrap.js +76 -0
  7. package/src/database/dispatch.js +156 -0
  8. package/src/database/index.js +572 -0
  9. package/src/identity/CLAUDE.md +3 -0
  10. package/src/identity/index.js +232 -0
  11. package/src/index.js +20 -1
  12. package/src/lib/CLAUDE.md +3 -0
  13. package/src/lib/constants.js +24 -4
  14. package/src/lib/errors.js +150 -0
  15. package/src/lib/schema.js +58 -440
  16. package/src/lib/spec/index.js +353 -0
  17. package/src/lib/spec/schema.json +284 -0
  18. package/src/lib/utils.js +54 -49
  19. package/src/network/discovery.js +80 -0
  20. package/src/network/index.js +231 -0
  21. package/src/pairing/index.js +482 -0
  22. package/src/pairing/invite.js +199 -0
  23. package/src/rpc/client.js +45 -0
  24. package/src/rpc/index.js +141 -0
  25. package/src/rpc/server.js +45 -0
  26. package/src/storage/index.js +261 -0
  27. package/types/blobs/index.d.ts +169 -0
  28. package/types/database/bootstrap.d.ts +17 -0
  29. package/types/database/dispatch.d.ts +8 -0
  30. package/types/database/index.d.ts +329 -0
  31. package/types/identity/index.d.ts +160 -0
  32. package/types/index.d.ts +11 -0
  33. package/types/lib/constants.d.ts +13 -0
  34. package/types/lib/errors.d.ts +110 -0
  35. package/types/lib/schema.d.ts +53 -0
  36. package/types/lib/spec/index.d.ts +95 -0
  37. package/types/lib/utils.d.ts +39 -0
  38. package/types/network/discovery.d.ts +44 -0
  39. package/types/network/index.d.ts +115 -0
  40. package/types/pairing/index.d.ts +194 -0
  41. package/types/pairing/invite.d.ts +157 -0
  42. package/types/rpc/client.d.ts +18 -0
  43. package/types/rpc/index.d.ts +67 -0
  44. package/types/rpc/server.d.ts +18 -0
  45. package/types/storage/index.d.ts +163 -0
  46. package/src/lib/base.js +0 -84
  47. package/src/lib/batch.js +0 -98
  48. package/src/lib/builder.js +0 -24
  49. package/src/lib/collection.js +0 -252
  50. package/src/lib/crypto.js +0 -6
  51. package/src/lib/index.js +0 -6
  52. package/src/lib/room.js +0 -145
@@ -0,0 +1,156 @@
1
+ import { SINGLE, COLLECTION, ACTION, COUNTERS } from '../lib/constants.js'
2
+ import { toId, toKey } from '../lib/utils.js'
3
+ import { Identity } from '../identity/index.js'
4
+
5
+ // Built-in collections wired into every cero spec.
6
+ export const BUILTINS = [
7
+ { name: 'members', verb: 'member' },
8
+ { name: 'devices', verb: 'device' },
9
+ { name: 'invites', verb: 'invite' },
10
+ { name: 'handles', verb: 'handle' }
11
+ ]
12
+
13
+ export function makeDispatcher(spec, ns, routes) {
14
+ const dispatcher = new spec.dispatch.Router()
15
+
16
+ const countersCol = `@${ns}/${COUNTERS}`
17
+ const member = (view, id) => view.get(`@${ns}/members`, { id })
18
+ const device = (view, id) => view.get(`@${ns}/devices`, { id })
19
+
20
+ async function insert(view, name, col, op) {
21
+ if (name !== COUNTERS) {
22
+ const existing = op.id != null ? await view.get(col, op) : null
23
+ if (existing && existing.index != null) {
24
+ op.index = existing.index
25
+ } else {
26
+ const counter = (await view.get(countersCol, { name })) ?? { name, value: 0 }
27
+ op.index = counter.value + 1
28
+ await view.insert(countersCol, { name, value: op.index })
29
+ }
30
+ }
31
+ await view.insert(col, op)
32
+ }
33
+
34
+ const upsert = (name, col, kind) => async (op, ctx) => {
35
+ const d = ctx.key && (await device(ctx.view, toId(ctx.key)))
36
+ op.memberId = d?.memberId || null
37
+ if (kind === COLLECTION) await insert(ctx.view, name, col, op)
38
+ else await ctx.view.insert(col, op)
39
+ }
40
+ const remove = (col) => async (op, ctx) => ctx.view.delete(col, op)
41
+ const add = (verb, fn) => dispatcher.add(`@${ns}/${verb}`, fn)
42
+
43
+ add('add-writer', async (op, ctx) => {
44
+ if (!Identity.verify(op.master, op.writer, op.sig)) return
45
+ await ctx.host.addWriter(op.writer, { isIndexer: op.isIndexer !== false })
46
+ const ts = Date.now()
47
+ await insert(ctx.view, 'devices', `@${ns}/devices`, {
48
+ id: toId(op.writer),
49
+ memberId: toId(op.master),
50
+ name: op.name || null,
51
+ isMobile: op.isMobile === true,
52
+ createdAt: ts,
53
+ updatedAt: ts
54
+ })
55
+ })
56
+
57
+ add('del-writer', async (op, ctx) => {
58
+ if (!Identity.verify(op.master, op.writer, op.sig)) return
59
+ await ctx.host.removeWriter(op.writer)
60
+ await ctx.view.delete(`@${ns}/devices`, { id: toId(op.writer) })
61
+ })
62
+
63
+ add('claim-writer', async (op, ctx) => {
64
+ if (!Identity.verify(op.identity, op.writer, op.sig)) return
65
+ const memberId = toId(op.identity)
66
+ if (!(await member(ctx.view, memberId))) return
67
+ await ctx.host.addWriter(op.writer, { isIndexer: true })
68
+ const ts = Date.now()
69
+ await insert(ctx.view, 'devices', `@${ns}/devices`, {
70
+ id: toId(op.writer),
71
+ memberId,
72
+ name: op.name || null,
73
+ isMobile: op.isMobile === true,
74
+ createdAt: ts,
75
+ updatedAt: ts
76
+ })
77
+ })
78
+
79
+ add('add-member', async (op, ctx) => {
80
+ await insert(ctx.view, 'members', `@${ns}/members`, op)
81
+ const deviceId = toId(op.key)
82
+ const existingDevice = await device(ctx.view, deviceId)
83
+ const ts = Date.now()
84
+ await insert(ctx.view, 'devices', `@${ns}/devices`, {
85
+ id: deviceId,
86
+ memberId: op.id,
87
+ name: existingDevice?.name ?? null,
88
+ isMobile: existingDevice?.isMobile ?? false,
89
+ createdAt: existingDevice?.createdAt ?? ts,
90
+ updatedAt: ts
91
+ })
92
+ })
93
+
94
+ add('set-member', async (op, ctx) => {
95
+ const existing = await member(ctx.view, op.id)
96
+ if (!existing) return
97
+ await insert(ctx.view, 'members', `@${ns}/members`, { ...existing, ...op })
98
+ })
99
+
100
+ add('del-member', async (op, ctx) => {
101
+ const existing = await member(ctx.view, op.id)
102
+ if (!existing) return
103
+ if (existing.key) await ctx.host.removeWriter(existing.key)
104
+ await ctx.view.delete(`@${ns}/members`, op)
105
+ })
106
+
107
+ add('del-device', async (op, ctx) => {
108
+ const existing = await device(ctx.view, op.id)
109
+ if (!existing) return
110
+ await ctx.host.removeWriter(toKey(existing.id))
111
+ await ctx.view.delete(`@${ns}/devices`, op)
112
+ })
113
+
114
+ for (const b of BUILTINS) {
115
+ const col = `@${ns}/${b.name}`
116
+ if (b.verb === 'member') continue
117
+ if (b.verb === 'device') {
118
+ add(`add-${b.verb}`, async (op, ctx) => {
119
+ await insert(ctx.view, b.name, col, { ...op, memberId: op.memberId ?? null })
120
+ })
121
+ add(`set-${b.verb}`, async (op, ctx) => {
122
+ const existing = await device(ctx.view, op.id)
123
+ await insert(ctx.view, b.name, col, { ...op, memberId: existing?.memberId ?? null })
124
+ })
125
+ continue
126
+ }
127
+ add(`add-${b.verb}`, upsert(b.name, col, COLLECTION))
128
+ add(`set-${b.verb}`, upsert(b.name, col, COLLECTION))
129
+ add(`del-${b.verb}`, remove(col))
130
+ }
131
+
132
+ for (const [name, info] of Object.entries(spec.meta?.refs || {})) {
133
+ if (info.builtin) continue
134
+ if (info.kind === 'handle') continue
135
+ if (info.kind === ACTION) {
136
+ add(name, routes[name] || (async () => {}))
137
+ continue
138
+ }
139
+ const col = `@${ns}/${name}`
140
+ const kind = info.kind === SINGLE ? SINGLE : COLLECTION
141
+ add(`set-${name}`, upsert(name, col, kind))
142
+ if (info.kind === SINGLE) continue
143
+ add(`add-${name}`, upsert(name, col, COLLECTION))
144
+ add(`del-${name}`, remove(col))
145
+ }
146
+
147
+ return {
148
+ dispatcher,
149
+ async apply(nodes, view, host) {
150
+ for (const node of nodes) {
151
+ await dispatcher.dispatch(node.value, { view, host, key: node.key })
152
+ }
153
+ await view.flush()
154
+ }
155
+ }
156
+ }