@cruba/crubal 0.1.0 → 0.3.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 (89) hide show
  1. package/README.md +30 -2
  2. package/dist/bin/crubal.js +70 -4
  3. package/dist/bin/crubal.js.map +1 -1
  4. package/dist/src/access.d.ts +126 -0
  5. package/dist/src/access.js +517 -0
  6. package/dist/src/access.js.map +1 -0
  7. package/dist/src/address.d.ts +29 -0
  8. package/dist/src/address.js +59 -0
  9. package/dist/src/address.js.map +1 -0
  10. package/dist/src/arrival.d.ts +55 -0
  11. package/dist/src/arrival.js +124 -0
  12. package/dist/src/arrival.js.map +1 -0
  13. package/dist/src/bundle.d.ts +66 -0
  14. package/dist/src/bundle.js +753 -0
  15. package/dist/src/bundle.js.map +1 -0
  16. package/dist/src/conditions.d.ts +7 -1
  17. package/dist/src/conditions.js +52 -11
  18. package/dist/src/conditions.js.map +1 -1
  19. package/dist/src/corpus.js +568 -34
  20. package/dist/src/corpus.js.map +1 -1
  21. package/dist/src/defs.d.ts +37 -2
  22. package/dist/src/defs.js +95 -14
  23. package/dist/src/defs.js.map +1 -1
  24. package/dist/src/dictionary.d.ts +15 -0
  25. package/dist/src/dictionary.js +72 -13
  26. package/dist/src/dictionary.js.map +1 -1
  27. package/dist/src/exchange.d.ts +204 -11
  28. package/dist/src/exchange.js +762 -45
  29. package/dist/src/exchange.js.map +1 -1
  30. package/dist/src/forms.d.ts +16 -0
  31. package/dist/src/forms.js +169 -0
  32. package/dist/src/forms.js.map +1 -0
  33. package/dist/src/index.d.ts +19 -6
  34. package/dist/src/index.js +18 -5
  35. package/dist/src/index.js.map +1 -1
  36. package/dist/src/lifecycle.d.ts +84 -0
  37. package/dist/src/lifecycle.js +415 -0
  38. package/dist/src/lifecycle.js.map +1 -0
  39. package/dist/src/log.d.ts +78 -0
  40. package/dist/src/log.js +506 -0
  41. package/dist/src/log.js.map +1 -0
  42. package/dist/src/migrate.d.ts +24 -1
  43. package/dist/src/migrate.js +104 -9
  44. package/dist/src/migrate.js.map +1 -1
  45. package/dist/src/model.d.ts +33 -1
  46. package/dist/src/model.js +158 -2
  47. package/dist/src/model.js.map +1 -1
  48. package/dist/src/packages.d.ts +44 -0
  49. package/dist/src/packages.js +137 -3
  50. package/dist/src/packages.js.map +1 -1
  51. package/dist/src/print-text.js +22 -0
  52. package/dist/src/print-text.js.map +1 -1
  53. package/dist/src/publish.d.ts +27 -0
  54. package/dist/src/publish.js +275 -0
  55. package/dist/src/publish.js.map +1 -0
  56. package/dist/src/read-text.d.ts +2 -0
  57. package/dist/src/read-text.js +72 -4
  58. package/dist/src/read-text.js.map +1 -1
  59. package/dist/src/records.d.ts +32 -0
  60. package/dist/src/records.js +104 -0
  61. package/dist/src/records.js.map +1 -0
  62. package/dist/src/review.d.ts +22 -0
  63. package/dist/src/review.js +198 -0
  64. package/dist/src/review.js.map +1 -0
  65. package/dist/src/rules.d.ts +38 -3
  66. package/dist/src/rules.js +184 -30
  67. package/dist/src/rules.js.map +1 -1
  68. package/dist/src/schedule.d.ts +24 -0
  69. package/dist/src/schedule.js +126 -0
  70. package/dist/src/schedule.js.map +1 -0
  71. package/dist/src/std-packages.d.ts +9 -0
  72. package/dist/src/std-packages.js +268 -0
  73. package/dist/src/std-packages.js.map +1 -0
  74. package/dist/src/store.d.ts +38 -7
  75. package/dist/src/store.js +166 -24
  76. package/dist/src/store.js.map +1 -1
  77. package/dist/src/tree.d.ts +3 -0
  78. package/dist/src/tree.js +36 -0
  79. package/dist/src/tree.js.map +1 -1
  80. package/dist/src/units-table.d.ts +1 -0
  81. package/dist/src/units-table.js +200 -0
  82. package/dist/src/units-table.js.map +1 -0
  83. package/dist/src/units.d.ts +42 -4
  84. package/dist/src/units.js +91 -30
  85. package/dist/src/units.js.map +1 -1
  86. package/dist/src/views.d.ts +116 -6
  87. package/dist/src/views.js +484 -74
  88. package/dist/src/views.js.map +1 -1
  89. package/package.json +5 -3
@@ -0,0 +1,753 @@
1
+ // bundle.ts: a package as one JSON document (docs/design/publishing.md; spec 4.6, 5.9, 6.10, 7.9, 10.6,
2
+ // 11.6, 12.7, 14.11). Writers turn what the text readers produced into the JSON forms; the printer turns
3
+ // the JSON forms back into the text form, so the text readers stay the one parser and a bundle is read by
4
+ // printing it and reading the text. The hash is over the canonical form without `hash` and `sig`; the
5
+ // signature is Ed25519 over the hash's bytes, as a log entry's is (8.5).
6
+ import { readInstructionBlocks } from "./lifecycle.js";
7
+ import { createPrivateKey, createPublicKey, sign as edSign, verify as edVerify } from "node:crypto";
8
+ import { readAccess } from "./access.js";
9
+ import { serialize } from "./canonical.js";
10
+ import { emptyDictionary, readDefinitions } from "./defs.js";
11
+ import { facetJson } from "./dictionary.js";
12
+ import { readRecipes } from "./migrate.js";
13
+ import { readPackages } from "./packages.js";
14
+ import { readRules } from "./rules.js";
15
+ import { readContracts, sha256, keyFor } from "./store.js";
16
+ import { readViews } from "./views.js";
17
+ const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
18
+ /** Read a package's text with every reader. */
19
+ export function parsePackage(source) {
20
+ const [info] = readPackages(source);
21
+ if (!info)
22
+ throw new Error("no package header");
23
+ const dict = readDefinitions(source);
24
+ const { perms, edges } = readAccess(source);
25
+ return { info, dict, rules: readRules(source), contracts: readContracts(source), views: readViews(source), perms, edges, recipes: readRecipes(source), instructions: readInstructionBlocks(source) };
26
+ }
27
+ // ─── Writers: the JSON forms ─────────────────────────────────────────────────
28
+ const PRECISION_CODE = { year: "y", month: "ym", day: "ymd", hour: "ymdh", minute: "ymdhm", second: "ymdhms", any: "any" };
29
+ export function kindJson(dict, k) {
30
+ const o = { kind: k.name, levels: [...k.levels] };
31
+ if (k.base)
32
+ o.base = k.base;
33
+ if (k.schema)
34
+ o.schema = k.schema;
35
+ const vary = {};
36
+ for (const level of k.levels) {
37
+ const keys = dict.vary.get(level);
38
+ if (keys?.size)
39
+ vary[level] = [...keys];
40
+ }
41
+ if (Object.keys(vary).length)
42
+ o.vary = vary;
43
+ if (k.follows)
44
+ o.follows = { ...k.follows };
45
+ const identify = {};
46
+ const match = {};
47
+ for (const level of k.levels) {
48
+ const i = dict.identify.get(level);
49
+ if (i?.length)
50
+ identify[level] = [...i];
51
+ const m = dict.match.get(level);
52
+ if (m?.length)
53
+ match[level] = m.map((g) => [...g]);
54
+ }
55
+ if (Object.keys(identify).length)
56
+ o.identify = identify;
57
+ if (Object.keys(match).length)
58
+ o.match = match;
59
+ const archive = dict.archive.get(k.name);
60
+ if (archive)
61
+ o.archive = archive;
62
+ return o;
63
+ }
64
+ export function classLineJson(l) {
65
+ const o = { facet: l.facet };
66
+ if (l.required)
67
+ o.required = true;
68
+ if (l.values)
69
+ o.values = [...l.values];
70
+ if (l.length !== undefined)
71
+ o.length = String(l.length);
72
+ if (l.pick !== undefined)
73
+ o.pick = l.pick;
74
+ if (l.range)
75
+ o.range = { min: l.range.min, max: l.range.max };
76
+ if (l.precision)
77
+ o.precision = l.precision;
78
+ if (l.level)
79
+ o.level = l.level;
80
+ if (l.when)
81
+ o.when = l.when;
82
+ if (l.frame)
83
+ o.frame = l.frame;
84
+ return o;
85
+ }
86
+ export function classJson(c) {
87
+ const o = { class: c.name };
88
+ if (c.label)
89
+ o.label = c.label;
90
+ if (c.describe)
91
+ o.describe = c.describe;
92
+ if (c.for.length)
93
+ o.for = [...c.for];
94
+ if (c.uses.length)
95
+ o.uses = [...c.uses];
96
+ const lines = [];
97
+ let section;
98
+ for (const l of c.lines) {
99
+ if (l.section !== section) {
100
+ section = l.section;
101
+ if (section)
102
+ lines.push({ section });
103
+ }
104
+ lines.push(classLineJson(l));
105
+ }
106
+ o.facets = lines;
107
+ if (c.parts.length)
108
+ o.parts = c.parts.map((p) => ({ slot: p.slot, wears: p.wears, pick: p.many ? "any" : "1", ...(p.fungible ? { fungible: true } : {}), ...(p.match ? { match: p.match } : {}) }));
109
+ if (c.state && c.state !== "active")
110
+ o.state = c.state;
111
+ if (c.version)
112
+ o.version = c.version;
113
+ return o;
114
+ }
115
+ export function ruleJson(r) {
116
+ const o = { rule: r.name };
117
+ if (r.for !== "everywhere")
118
+ o.for = r.for;
119
+ if (r.whenText)
120
+ o.when = r.whenText;
121
+ o.then = r.thenText;
122
+ o.strength = r.strength;
123
+ if (r.because)
124
+ o.because = r.because;
125
+ if (r.version)
126
+ o.version = r.version;
127
+ return o;
128
+ }
129
+ const mapJson = (m) => ({
130
+ ...(m.from.startsWith('"') ? { literal: JSON.parse(m.from) } : { from: m.from }),
131
+ to: m.to,
132
+ ...(m.values.length ? { values: m.values.map(([from, to]) => ({ from, to })) } : {}),
133
+ });
134
+ export function contractJson(c) {
135
+ const o = { contract: c.name, kind: c.kind };
136
+ for (const k of ["label", "describe", "for"])
137
+ if (c.parts[k] !== undefined)
138
+ o[k] = c.parts[k];
139
+ if (c.needs.length)
140
+ o.needs = c.needs.map(classLineJson);
141
+ if (c.gives.length)
142
+ o.gives = [...c.gives];
143
+ if (Object.keys(c.terms).length)
144
+ o.terms = { ...c.terms };
145
+ if (c.stores.length)
146
+ o.stores = c.stores.map(mapJson);
147
+ else {
148
+ if (c.writes.length)
149
+ o.writes = c.writes.map(mapJson);
150
+ if (c.reads.length)
151
+ o.reads = c.reads.map(mapJson);
152
+ }
153
+ if (c.stream.length)
154
+ o.stream = [...c.stream];
155
+ for (const k of ["when", "who", "from", "to", "mode", "undo", "ref", "prefix"])
156
+ if (c[k])
157
+ o[k] = c[k];
158
+ // origin is a part of the kinds that bring values in (7.2); the reader defaults it for the rest.
159
+ if (c.origin && ["receive", "form", "take"].includes(c.kind))
160
+ o.origin = c.origin;
161
+ if (c.lands !== "value")
162
+ o.lands = c.lands;
163
+ if (!c.absorb)
164
+ o.absorb = false;
165
+ if (c.ignore.length)
166
+ o.ignore = [...c.ignore];
167
+ if (c.keepRaw.length)
168
+ o["keep-raw"] = [...c.keepRaw];
169
+ for (const [k, v] of Object.entries(c.parts)) {
170
+ if (["label", "describe", "for", "needs", "gives", "terms", "writes", "reads", "stream", "when", "who", "from", "to", "mode", "undo", "ref", "origin", "prefix", "lands", "absorb", "ignore", "keep-raw", "kind"].includes(k))
171
+ continue;
172
+ if (k === "retry") {
173
+ const m = v.match(/^(\d+)\s+every\s+(\S+)$/);
174
+ o.retry = m ? { times: m[1], every: m[2] } : v;
175
+ }
176
+ else
177
+ o[k] = v;
178
+ }
179
+ return o;
180
+ }
181
+ export function recipeJson(r) {
182
+ const steps = r.steps.map((s) => {
183
+ const o = { facet: s.facet };
184
+ switch (s.verb) {
185
+ case "rename":
186
+ case "precision":
187
+ case "retype":
188
+ o[s.verb] = { from: s.from, to: s.to };
189
+ break;
190
+ case "merge":
191
+ o.merge = { from: [...s.from], to: s.to };
192
+ break;
193
+ case "convert":
194
+ o.convert = { from: s.from, to: s.to, rounding: s.rounding };
195
+ break;
196
+ case "split":
197
+ o.split = { value: s.value, when: s.when.map((w) => ({ if: w.cond, to: w.to })), ...(s.else !== undefined ? { else: s.else } : {}) };
198
+ break;
199
+ case "move":
200
+ o.move = { to: s.to };
201
+ break;
202
+ case "review":
203
+ o.review = s.cond;
204
+ break;
205
+ case "recompute":
206
+ o.recompute = true;
207
+ break;
208
+ case "fill":
209
+ o.fill = { value: s.value, ...(s.level ? { level: s.level } : {}), ...(s.when ? { when: s.when } : {}) };
210
+ break;
211
+ }
212
+ return o;
213
+ });
214
+ return { migration: { from: r.from, to: r.to }, steps };
215
+ }
216
+ export function shapeJson(s) {
217
+ return { shape: s.name, slots: s.slots.map((sl) => ({ ...(sl.from.startsWith('"') ? { template: JSON.parse(sl.from) } : { from: sl.from }), to: sl.to, ...(sl.required ? { required: true } : {}) })) };
218
+ }
219
+ export function filterJson(f) {
220
+ const o = { filter: f.name, facet: f.facet };
221
+ if (f.buckets)
222
+ o.buckets = f.buckets.map((b) => (b.range ? { name: b.name, range: { min: String(b.range[0]), max: String(b.range[1]) } } : { name: b.name, values: [...(b.values ?? [])] }));
223
+ if (f.dependent.length)
224
+ o.dependent = [...f.dependent];
225
+ return o;
226
+ }
227
+ export function viewJson(v) {
228
+ const select = {};
229
+ if (v.level)
230
+ select.level = v.level;
231
+ if (v.wears.length)
232
+ select.wears = [...v.wears];
233
+ if (v.when)
234
+ select.when = v.when;
235
+ if (v.list)
236
+ select.list = v.list;
237
+ const o = { view: v.name, select };
238
+ if (v.partition)
239
+ o.partition = v.partition;
240
+ if (v.of)
241
+ o.of = v.of;
242
+ if (v.pin.length)
243
+ o.pin = [...v.pin];
244
+ if (v.hide.length)
245
+ o.hide = [...v.hide];
246
+ if (v.order.length)
247
+ o.order = v.order.map((x) => ({ key: x.key, dir: x.dir }));
248
+ if (v.page !== 24)
249
+ o.page = String(v.page);
250
+ o.shape = v.shapes.length === 1 ? v.shapes[0] : [...v.shapes];
251
+ if (v.filters.length)
252
+ o.filters = [...v.filters];
253
+ if (v.index.length)
254
+ o.index = [...v.index];
255
+ if (v.show !== "public")
256
+ o.show = v.show;
257
+ if (v.fresh !== "write")
258
+ o.fresh = v.fresh;
259
+ if (v.version)
260
+ o.version = v.version;
261
+ return o;
262
+ }
263
+ /** The `role:` blocks, from the presets and menu items the access reader made of them (12.4). */
264
+ export function rolesJson(perms) {
265
+ const out = [];
266
+ for (const p of perms.presets.values()) {
267
+ const own = p.items.filter((i) => i.startsWith(`${p.name}.`));
268
+ if (!own.length || own.length !== p.items.length)
269
+ continue;
270
+ const may = {};
271
+ for (const i of own) {
272
+ const item = perms.menu.get(i);
273
+ if (item)
274
+ for (const [verb, targets] of Object.entries(item.verbs))
275
+ may[verb] = [...targets];
276
+ }
277
+ out.push({ role: p.name, audience: p.audience, may });
278
+ }
279
+ return out;
280
+ }
281
+ export function permissionsJson(perms) {
282
+ const roleItems = new Set(rolesJson(perms).flatMap((r) => Object.keys(r.may).map((v) => `${r.role}.${v}`)));
283
+ const menu = {};
284
+ for (const m of perms.menu.values())
285
+ if (!roleItems.has(m.name))
286
+ menu[m.name] = { verbs: Object.fromEntries(Object.entries(m.verbs).map(([v, t]) => [v, [...t]])), ...(m.on ? { on: m.on } : {}) };
287
+ const presets = {};
288
+ for (const p of perms.presets.values())
289
+ if (!roleItems.has(`${p.name}.${Object.keys(perms.menu.get(p.items[0] ?? "")?.verbs ?? {})[0]}`) && !p.items.every((i) => roleItems.has(i)))
290
+ presets[p.name] = { items: [...p.items], audience: p.audience };
291
+ const relations = {};
292
+ for (const [r, v] of perms.relations)
293
+ relations[r] = v.only ? { only: true } : {};
294
+ if (!perms.name && !Object.keys(menu).length && !Object.keys(relations).length)
295
+ return undefined;
296
+ return { permissions: perms.name ?? "", menu, presets, relations };
297
+ }
298
+ /** A utility block's JSON form (11.6). */
299
+ export function utilityJson(u) {
300
+ const o = { utility: u.name, version: u.version };
301
+ if (u.label)
302
+ o.label = u.label;
303
+ if (u.describe)
304
+ o.describe = u.describe;
305
+ if (u.serves.length)
306
+ o.serves = u.serves.map((s) => ({ as: s.as, ...(s.items ? { items: [...s.items] } : {}) }));
307
+ if (u.needs.length)
308
+ o.needs = u.needs.map((n) => ({ name: n.name, ...(n.version ? { version: n.version } : {}) }));
309
+ if (u.setup.length)
310
+ o.setup = u.setup.map((s) => ("connection" in s ? { connection: s.connection } : { setting: s.setting }));
311
+ return o;
312
+ }
313
+ /** The whole package as the JSON document, without `hash` and `sig`. */
314
+ export function bundleOf(p) {
315
+ const { info, dict } = p;
316
+ const o = { crubal: String(info.crubal), package: info.name, version: info.version, owner: info.owner, id: `${info.owner}/${info.name}` };
317
+ for (const k of ["label", "describe", "licence"])
318
+ if (info.header[k])
319
+ o[k] = info.header[k];
320
+ if (info.extends.length)
321
+ o.extends = info.extends.map((e) => ({ package: e.name, version: e.version }));
322
+ if (info.prefixes.size)
323
+ o.prefixes = Object.fromEntries([...info.prefixes].map(([k, v]) => [k, v.target]));
324
+ if (info.header.units) {
325
+ const [pkg = "", ver = ""] = info.header.units.split(/\s+\//).map((s) => s.trim());
326
+ o.units = { package: pkg, version: ver };
327
+ }
328
+ for (const k of ["confirm", "split", "recur"])
329
+ if (info.header[k])
330
+ o[k] = info.header[k];
331
+ if (dict.trust.length)
332
+ o.trust = [...dict.trust];
333
+ if (dict.facetTrust.size)
334
+ o["facet-trust"] = Object.fromEntries([...dict.facetTrust].map(([k, v]) => [k, [...v]]));
335
+ if (Object.keys(info.terms).length)
336
+ o.terms = { ...info.terms };
337
+ const members = {};
338
+ const facets = [...dict.facets.values()].filter((f) => !f.key.includes(":") || info.name === "core");
339
+ for (const f of facets)
340
+ if (f.version)
341
+ members[`facet:${f.key}`] = f.version;
342
+ for (const c of dict.classes.values())
343
+ if (c.version)
344
+ members[`class:${c.name}`] = c.version;
345
+ for (const r of p.rules)
346
+ if (r.version)
347
+ members[`rule:${r.name}`] = r.version;
348
+ for (const c of p.contracts)
349
+ if (c.parts.version)
350
+ members[`contract:${c.name}`] = c.parts.version;
351
+ for (const v of p.views.views.values())
352
+ if (v.version)
353
+ members[`view:${v.name}`] = v.version;
354
+ for (const u of info.utilities)
355
+ if (u.version)
356
+ members[`utility:${u.name}`] = u.version;
357
+ o.members = members;
358
+ if (dict.kinds.size)
359
+ o.kinds = [...dict.kinds.values()].map((k) => kindJson(dict, k));
360
+ if (facets.length)
361
+ o.facets = facets.map(facetJson);
362
+ if (dict.classes.size)
363
+ o.classes = [...dict.classes.values()].map(classJson);
364
+ if (p.rules.length)
365
+ o.rules = p.rules.map(ruleJson);
366
+ if (p.contracts.length)
367
+ o.contracts = p.contracts.map(contractJson);
368
+ if (p.views.shapes.size)
369
+ o.shapes = [...p.views.shapes.values()].map(shapeJson);
370
+ if (p.views.filters.size)
371
+ o.filters = [...p.views.filters.values()].map(filterJson);
372
+ if (p.views.views.size)
373
+ o.views = [...p.views.views.values()].map(viewJson);
374
+ const roles = rolesJson(p.perms);
375
+ if (roles.length)
376
+ o.roles = roles;
377
+ const perms = permissionsJson(p.perms);
378
+ if (perms)
379
+ o.permissions = perms;
380
+ if (p.perms.ownership.size)
381
+ o.ownership = Object.fromEntries([...p.perms.ownership].map(([k, v]) => [k, [...v]]));
382
+ if (p.perms.emergency)
383
+ o.emergency = { approvers: String(p.perms.emergency.approvers), longest: p.perms.emergency.longest };
384
+ if (p.recipes.length)
385
+ o.recipes = p.recipes.map(recipeJson);
386
+ if (p.instructions.size)
387
+ o.instructions = [...p.instructions].map(([issuer, list]) => ({ issuer, instructions: list.map((i) => ({ n: i.n, op: i.op, key: i.key, ...(i.to ? { to: i.to } : {}), ...(i.map.length ? { map: i.map.map(([a, b]) => ({ from: a, to: b })) } : {}) })) }));
388
+ if (info.utilities.length)
389
+ o.utilities = info.utilities.map(utilityJson);
390
+ return o;
391
+ }
392
+ // ─── Printer: the JSON forms back to the text form ───────────────────────────
393
+ /** Quote a value when the text form would otherwise misread it (2.5). */
394
+ export function q(s) {
395
+ if (s === "" || /^[&_\-\["{]|^\.\.\.$/.test(s) || /, |: | \/\/|[\n\r\t]/.test(s) || /^\s|\s$/.test(s) || /^-?\d/.test(s) && false)
396
+ return JSON.stringify(s);
397
+ return s;
398
+ }
399
+ const list = (xs) => (xs ?? []).map((x) => q(String(x))).join(", ");
400
+ const str = (v) => (v === undefined ? undefined : typeof v === "string" ? v : String(v));
401
+ function facetText(f, depth) {
402
+ const pad = " ".repeat(depth);
403
+ const out = [`${pad}facet: ${f.facet}`];
404
+ const line = (k, v) => { if (v !== undefined)
405
+ out.push(`${pad} ${k}: ${v}`); };
406
+ line("type", str(f.type));
407
+ for (const k of ["label", "ask", "describe"])
408
+ if (typeof f[k] === "string")
409
+ line(k, q(f[k]));
410
+ line("pick", str(f.pick));
411
+ if (f.ordered === true)
412
+ line("ordered", "yes");
413
+ line("state", str(f.state));
414
+ if (Array.isArray(f.qualifiers))
415
+ line("qualifiers", list(f.qualifiers));
416
+ if (Array.isArray(f.aliases))
417
+ line("aliases", list(f.aliases));
418
+ if (Array.isArray(f["same-as"]))
419
+ line("same-as", list(f["same-as"]));
420
+ line("agree-by", str(f["agree-by"]));
421
+ line("length", str(f.length));
422
+ line("unit", str(f.unit));
423
+ if (Array.isArray(f.accepts))
424
+ line("accepts", list(f.accepts));
425
+ if (isObj(f.range))
426
+ line("range", `${f.range.min}..${f.range.max}${f.range.step ? ` step ${f.range.step}` : ""}`);
427
+ line("places", str(f.places));
428
+ line("precision", str(f.precision));
429
+ line("zone", str(f.zone));
430
+ if (Array.isArray(f.words))
431
+ line("words", list(f.words));
432
+ if (Array.isArray(f.values)) {
433
+ const vals = f.values.map((v) => (isObj(v) ? [q(String(v.value)), ...(v.aliases ?? []).map((a) => `~${q(a)}`)].join(" ") : q(String(v))));
434
+ if (f.open === true)
435
+ vals.push("...");
436
+ line("values", vals.join(", "));
437
+ }
438
+ for (const k of ["fixed", "ranked", "scored", "required", "na", "unique", "transitions"])
439
+ if (f[k] === true)
440
+ line(k, "yes");
441
+ line("to", str(f.to));
442
+ if (Array.isArray(f.facets)) {
443
+ out.push(`${pad} facets:`);
444
+ for (const c of f.facets)
445
+ if (isObj(c))
446
+ out.push(...facetText(c, depth + 2));
447
+ }
448
+ line("identity", str(f.identity));
449
+ if (typeof f.when === "string")
450
+ line("when", f.when);
451
+ line("combine", str(f.combine));
452
+ line("version", str(f.version));
453
+ for (const [k, v] of Object.entries(f))
454
+ if (k.includes(":"))
455
+ line(k, str(v));
456
+ return out;
457
+ }
458
+ /** A class line's text from its settings (5.3), the shorthand marks in their order (4.5). */
459
+ export function lineText(l, type) {
460
+ let s = `${l.required === true ? "!" : ""}${l.level ? `${l.level}::` : ""}${l.facet}`;
461
+ if (Array.isArray(l.values))
462
+ s += `{${l.values.map((v) => q(String(v)).replace(/\|/g, "\\|")).join("|")}}`;
463
+ if (l.length !== undefined)
464
+ s += `$${l.length}`;
465
+ if (l.pick !== undefined)
466
+ s += l.pick === "any" ? "*" : `*${l.pick}`;
467
+ if (isObj(l.range))
468
+ s += ` ${l.range.min}..${l.range.max}`;
469
+ if (typeof l.precision === "string")
470
+ s += `@${type === "time" ? (l.precision === "second" ? "hms" : "hm") : (PRECISION_CODE[l.precision] ?? l.precision)}`;
471
+ if (typeof l.frame === "string")
472
+ s += ` frame: ${l.frame}`;
473
+ if (typeof l.when === "string")
474
+ s += ` when ${l.when}`;
475
+ return s;
476
+ }
477
+ const kv = (pad, k, v) => (v === undefined ? [] : [`${pad}${k}: ${Array.isArray(v) ? list(v) : q(String(v))}`]);
478
+ function kindText(k) {
479
+ const out = [`kind: ${k.kind}`, ...kv(" ", "levels", k.levels), ...kv(" ", "base", k.base)];
480
+ if (typeof k.schema === "string")
481
+ out.push(` schema: ${k.schema}`);
482
+ else if (isObj(k.schema))
483
+ out.push(" schema:", ...Object.entries(k.schema).map(([l, t]) => ` ${l}: ${t}`));
484
+ for (const block of ["vary", "identify"])
485
+ if (isObj(k[block]))
486
+ out.push(` ${block}:`, ...Object.entries(k[block]).map(([l, keys]) => ` ${l}: ${keys.join(", ")}`));
487
+ if (isObj(k.follows))
488
+ out.push(" follows:", ...Object.entries(k.follows).map(([a, b]) => ` ${a}: ${b}`));
489
+ if (isObj(k.match))
490
+ out.push(" match:", ...Object.entries(k.match).map(([l, groups]) => ` ${l}: ${groups.map((g) => g.join(" + ")).join("; ")}`));
491
+ out.push(...kv(" ", "archive", k.archive));
492
+ return out;
493
+ }
494
+ function classText(c) {
495
+ const out = [`class: ${c.class}`, ...kv(" ", "label", c.label), ...kv(" ", "describe", c.describe), ...kv(" ", "for", c.for), ...kv(" ", "uses", c.uses), ...kv(" ", "state", c.state), ...kv(" ", "version", c.version)];
496
+ if (Array.isArray(c.facets)) {
497
+ out.push(" facets:");
498
+ for (const l of c.facets)
499
+ if (isObj(l))
500
+ out.push(typeof l.section === "string" ? ` section: ${q(l.section)}` : ` ${lineText(l)}`);
501
+ }
502
+ if (Array.isArray(c.parts)) {
503
+ out.push(" parts:");
504
+ for (const p of c.parts)
505
+ if (isObj(p))
506
+ out.push(` ${p.slot}: ${p.wears}${p.pick === "any" ? " *" : ""}${p.fungible === true ? " fungible: yes" : ""}${p.match ? ` match: ${p.match}` : ""}`);
507
+ }
508
+ return out;
509
+ }
510
+ const mapText = (m) => [` ${typeof m.literal === "string" ? JSON.stringify(m.literal) : m.from} -> ${m.to}`, ...(m.values ?? []).map((v) => ` ${q(String(v.from))} -> ${q(String(v.to))}`)];
511
+ function contractText(c) {
512
+ const out = [`contract: ${c.contract}`, ...kv(" ", "kind", c.kind)];
513
+ for (const k of ["label", "describe", "for"])
514
+ out.push(...kv(" ", k, c[k]));
515
+ if (Array.isArray(c.needs))
516
+ out.push(" needs:", ...c.needs.map((l) => ` ${lineText(l)}`));
517
+ for (const k of ["writes", "reads", "stores"])
518
+ if (Array.isArray(c[k]))
519
+ out.push(` ${k}:`, ...c[k].flatMap(mapText));
520
+ for (const k of ["gives", "stream", "when", "who", "from", "to", "mode", "undo", "ref", "origin", "prefix", "lands", "ignore", "keep-raw"])
521
+ out.push(...kv(" ", k, c[k]));
522
+ if (isObj(c.terms) && Object.keys(c.terms).length) {
523
+ out.push(" terms:");
524
+ for (const [k, v] of Object.entries(c.terms))
525
+ out.push(` ${k}: ${String(v)}`);
526
+ }
527
+ if (c.absorb === false)
528
+ out.push(" absorb: no");
529
+ for (const [k, v] of Object.entries(c)) {
530
+ if (["contract", "kind", "label", "describe", "for", "needs", "writes", "reads", "stores", "gives", "terms", "stream", "when", "who", "from", "to", "mode", "undo", "ref", "origin", "prefix", "lands", "ignore", "keep-raw", "absorb"].includes(k))
531
+ continue;
532
+ if (k === "retry" && isObj(v))
533
+ out.push(` retry: ${v.times} every ${v.every}`);
534
+ else
535
+ out.push(...kv(" ", k, v));
536
+ }
537
+ return out;
538
+ }
539
+ function ruleText(r) {
540
+ return [`rule: ${r.rule}`, ...kv(" ", "for", r.for), ...(typeof r.when === "string" ? [` when: ${r.when}`] : []), ` then: ${r.then}`, ...kv(" ", "strength", r.strength), ...kv(" ", "because", r.because), ...kv(" ", "version", r.version)];
541
+ }
542
+ function instructionsText(b) {
543
+ const out = [`instructions: ${String(b.issuer)}`];
544
+ for (const i of b.instructions ?? []) {
545
+ out.push(` ${String(i.n)}: ${String(i.op)}${String(i.key)}${i.to ? ` -> ${String(i.to)}` : ""}`);
546
+ for (const m of i.map ?? [])
547
+ out.push(` ${q(String(m.from))} -> ${q(String(m.to))}`);
548
+ }
549
+ return out;
550
+ }
551
+ function recipeText(r) {
552
+ const m = r.migration;
553
+ const out = [`migration: ${m.from} -> ${m.to}`];
554
+ let facet;
555
+ for (const s of r.steps ?? []) {
556
+ if (s.facet !== facet) {
557
+ facet = String(s.facet);
558
+ out.push(` facet: ${facet}`);
559
+ }
560
+ for (const verb of ["rename", "precision", "retype"])
561
+ if (isObj(s[verb]))
562
+ out.push(` ${verb}: ${q(String(s[verb].from))} -> ${q(String(s[verb].to))}`);
563
+ if (isObj(s.merge))
564
+ out.push(` merge: ${list(s.merge.from)} -> ${q(String(s.merge.to))}`);
565
+ if (isObj(s.convert))
566
+ out.push(` convert: ${s.convert.from} -> ${s.convert.to}`, ...(s.convert.rounding && s.convert.rounding !== "nearest" ? [` rounding: ${s.convert.rounding}`] : []));
567
+ if (isObj(s.split))
568
+ out.push(` split: ${q(String(s.split.value))}`, ...(s.split.when ?? []).map((w) => ` when: ${w.if} -> ${q(String(w.to))}`), ...(s.split.else !== undefined ? [` else: ${q(String(s.split.else))}`] : []));
569
+ if (isObj(s.move))
570
+ out.push(` move: -> ${s.move.to}`);
571
+ if (typeof s.review === "string")
572
+ out.push(` review: ${s.review}`);
573
+ if (s.recompute === true)
574
+ out.push(" recompute");
575
+ if (isObj(s.fill))
576
+ out.push(` fill: ${q(String(s.fill.value))}`, ...(typeof s.fill.level === "string" ? [` for: ${s.fill.level}::`] : []), ...(typeof s.fill.when === "string" ? [` when: ${s.fill.when}`] : []));
577
+ }
578
+ return out;
579
+ }
580
+ const shapeText = (s) => [`shape: ${s.shape}`, " slots:", ...(s.slots ?? []).map((sl) => ` ${sl.required === true ? "!" : ""}${typeof sl.template === "string" ? JSON.stringify(sl.template) : sl.from} -> ${sl.to}`)];
581
+ function filterText(f) {
582
+ const out = [`filter: ${f.filter}`, ` facet: ${f.facet}`];
583
+ if (Array.isArray(f.buckets))
584
+ out.push(" buckets:", ...f.buckets.map((b) => { const bb = b; const name = /[\s:]/.test(String(bb.name)) ? JSON.stringify(bb.name) : String(bb.name); return isObj(bb.range) ? ` ${name}: ${bb.range.min}..${bb.range.max}` : ` ${name}: ${list(bb.values)}`; }));
585
+ out.push(...kv(" ", "dependent", f.dependent));
586
+ return out;
587
+ }
588
+ function viewText(v) {
589
+ const sel = (v.select ?? {});
590
+ const out = [`view: ${v.view}`, " select:", ...kv(" ", "level", sel.level), ...kv(" ", "wears", sel.wears), ...(typeof sel.when === "string" ? [` when: ${sel.when}`] : []), ...kv(" ", "list", sel.list)];
591
+ if (!Object.keys(sel).length)
592
+ out.pop();
593
+ out.push(...kv(" ", "partition", v.partition), ...(typeof v.of === "string" ? [` of: ${v.of}`] : []), ...kv(" ", "pin", v.pin), ...kv(" ", "hide", v.hide));
594
+ if (Array.isArray(v.order))
595
+ out.push(` order: ${v.order.map((o) => `${o.key} ${o.dir}`).join(", ")}`);
596
+ out.push(...kv(" ", "page", v.page), ...kv(" ", "shape", v.shape), ...kv(" ", "filters", v.filters), ...kv(" ", "index", v.index), ...kv(" ", "show", v.show), ...kv(" ", "fresh", v.fresh), ...kv(" ", "version", v.version));
597
+ return out;
598
+ }
599
+ const roleText = (r) => [`role: ${r.role}`, ` audience: ${r.audience}`, " may:", ...Object.entries(r.may ?? {}).map(([verb, t]) => ` ${verb}: ${t.join(", ")}`)];
600
+ function permissionsText(p) {
601
+ const out = [`permissions: ${p.permissions}`];
602
+ const menu = (p.menu ?? {});
603
+ if (Object.keys(menu).length)
604
+ out.push(" menu:", ...Object.entries(menu).map(([name, item]) => { const it = item; const verbs = Object.entries(it.verbs ?? {}).map(([v, t]) => `${v} ${t.join(" ")}`).join(", "); return ` ${name}: ${verbs}${it.on ? ` on: ${it.on}` : ""}`; }));
605
+ const presets = (p.presets ?? {});
606
+ if (Object.keys(presets).length)
607
+ out.push(" presets:", ...Object.entries(presets).map(([name, pr]) => ` ${name}: ${pr.items.join(", ")} audience: ${pr.audience}`));
608
+ const rel = (p.relations ?? {});
609
+ if (Object.keys(rel).length)
610
+ out.push(` relations: ${Object.entries(rel).map(([r, v]) => (v.only === true ? `${r} only` : r)).join(", ")}`);
611
+ return out;
612
+ }
613
+ /** The text form of a bundle: the package header, then every member, in the long form. */
614
+ export function printBundle(b) {
615
+ const out = [`package: ${b.package} /${b.version}`];
616
+ out.push(...kv(" ", "owner", b.owner), ...kv(" ", "label", b.label), ...kv(" ", "describe", b.describe), ` crubal: ${b.crubal ?? "2"}`, ...kv(" ", "licence", b.licence));
617
+ if (Array.isArray(b.extends))
618
+ out.push(` extends: ${b.extends.map((e) => `${e.package} /${e.version}`).join(", ")}`);
619
+ if (isObj(b.prefixes))
620
+ out.push(" prefixes:", ...Object.entries(b.prefixes).map(([k, v]) => ` ${k}: ${v}`));
621
+ if (isObj(b.units))
622
+ out.push(` units: ${b.units.package} /${b.units.version}`);
623
+ for (const k of ["confirm", "split", "recur"])
624
+ out.push(...kv(" ", k, b[k]));
625
+ if (isObj(b.terms))
626
+ out.push(" terms:", ...Object.entries(b.terms).map(([a, t]) => ` ${a}: ${t}`));
627
+ out.push("");
628
+ if (Array.isArray(b.trust))
629
+ out.push("trust:", ...b.trust.map((t) => ` ${t}`), "");
630
+ if (isObj(b["facet-trust"]))
631
+ out.push("facet-trust:", ...Object.entries(b["facet-trust"]).map(([k, v]) => ` ${k}: ${v.join(", ")}`), "");
632
+ const blocks = (key, fn) => { for (const x of b[key] ?? [])
633
+ out.push(...fn(x), ""); };
634
+ blocks("kinds", kindText);
635
+ blocks("facets", (f) => facetText(f, 0));
636
+ blocks("classes", classText);
637
+ blocks("rules", ruleText);
638
+ blocks("contracts", contractText);
639
+ blocks("recipes", recipeText);
640
+ blocks("instructions", instructionsText);
641
+ blocks("shapes", shapeText);
642
+ blocks("filters", filterText);
643
+ blocks("views", viewText);
644
+ blocks("roles", roleText);
645
+ if (isObj(b.permissions))
646
+ out.push(...permissionsText(b.permissions), "");
647
+ if (isObj(b.ownership))
648
+ out.push("ownership:", ...Object.entries(b.ownership).map(([k, v]) => ` ${k}: ${v.join(", ")}`), "");
649
+ if (isObj(b.emergency))
650
+ out.push("emergency:", ` approvers: ${b.emergency.approvers}`, ` longest: ${b.emergency.longest}`, "");
651
+ blocks("utilities", utilityText);
652
+ return `${out.join("\n").trimEnd()}\n`;
653
+ }
654
+ function utilityText(u) {
655
+ const out = [`utility: ${u.utility} /${u.version}`, ...kv(" ", "label", u.label), ...kv(" ", "describe", u.describe)];
656
+ if (Array.isArray(u.serves))
657
+ out.push(` serves: ${u.serves.map((s) => `${s.as}${Array.isArray(s.items) ? `(${s.items.join(", ")})` : ""}`).join(", ")}`);
658
+ if (Array.isArray(u.needs))
659
+ out.push(` needs: ${u.needs.map((n) => `${n.name}${n.version ? ` /${n.version}` : ""}`).join(", ")}`);
660
+ if (Array.isArray(u.setup))
661
+ out.push(` setup: ${u.setup.map((s) => (typeof s.connection === "string" ? `connection ${s.connection}` : `setting ${s.setting}`)).join(", ")}`);
662
+ return out;
663
+ }
664
+ /** Read a bundle: print it to text and read the text, so there is one parser. */
665
+ export function readBundle(b) {
666
+ const text = printBundle(b);
667
+ return { ...parsePackage(text), text };
668
+ }
669
+ /** Read a bundle's definitions into a dictionary that already holds what the package extends. */
670
+ export function readBundleInto(b, into = emptyDictionary()) {
671
+ return readDefinitions(printBundle(b), into);
672
+ }
673
+ /** An empty Parsed to read packages onto, in order. */
674
+ export function emptyParsed() {
675
+ const { perms } = readAccess("");
676
+ return { info: { name: "", version: "", owner: "", line: 0, crubal: 2, crubalLine: 0, extends: [], prefixes: new Map(), trust: [], dict: emptyDictionary(), contracts: [], header: {}, terms: {}, utilities: [] }, dict: emptyDictionary(), rules: [], contracts: [], views: { shapes: new Map(), filters: new Map(), views: new Map(), roles: new Map(), shown: new Map(), shownFor: new Map() }, perms, edges: [], recipes: [], instructions: new Map() };
677
+ }
678
+ /**
679
+ * Read a bundle onto what is already read (the packages it extends, or the installed packages before the
680
+ * owner's own definitions): the dictionary grows, and a member of the same name replaces the earlier one.
681
+ */
682
+ export function readBundleOnto(bundle, base) {
683
+ const text = printBundle(bundle);
684
+ readDefinitions(text, base.dict);
685
+ const [info] = readPackages(text);
686
+ if (info)
687
+ base.info = { ...info, dict: base.dict, terms: Object.keys(info.terms).length ? info.terms : base.info.terms, utilities: [...base.info.utilities.filter((h) => !info.utilities.some((n) => n.name === h.name)), ...info.utilities] };
688
+ const byName = (have, next) => [...have.filter((h) => !next.some((n) => n.name === h.name)), ...next];
689
+ base.rules = byName(base.rules, readRules(text));
690
+ base.contracts = byName(base.contracts, readContracts(text));
691
+ const views = readViews(text);
692
+ for (const k of ["shapes", "filters", "views", "roles", "shown", "shownFor"])
693
+ for (const [key, val] of views[k])
694
+ base.views[k].set(key, val);
695
+ const { perms, edges } = readAccess(text);
696
+ if (perms.name)
697
+ base.perms.name = perms.name;
698
+ // A menu item named by more than one package (platform's `people`, every package's `people: grant …`) is
699
+ // one item: a later package adds its verbs and targets to what stands, never replaces them. A preset of
700
+ // the same name is replaced whole: it is one declaration, not an accumulation.
701
+ for (const [k, v] of perms.menu) {
702
+ const have = base.perms.menu.get(k);
703
+ if (!have) {
704
+ base.perms.menu.set(k, v);
705
+ continue;
706
+ }
707
+ const verbs = { ...have.verbs };
708
+ for (const [verb, targets] of Object.entries(v.verbs))
709
+ verbs[verb] = [...new Set([...(verbs[verb] ?? []), ...targets])];
710
+ base.perms.menu.set(k, { ...have, ...v, verbs });
711
+ }
712
+ for (const [k, v] of perms.presets)
713
+ base.perms.presets.set(k, v);
714
+ for (const [k, v] of perms.relations)
715
+ base.perms.relations.set(k, v);
716
+ for (const [k, v] of perms.ownership)
717
+ base.perms.ownership.set(k, v);
718
+ if (perms.emergency)
719
+ base.perms.emergency = perms.emergency;
720
+ base.edges.push(...edges);
721
+ base.recipes.push(...readRecipes(text));
722
+ for (const [issuer, list] of readInstructionBlocks(text))
723
+ base.instructions.set(issuer, [...(base.instructions.get(issuer) ?? []), ...list]);
724
+ return base;
725
+ }
726
+ // ─── Hash, sign, verify ──────────────────────────────────────────────────────
727
+ export function bundleHash(b) {
728
+ const { hash: _h, sig: _s, ...rest } = b;
729
+ return sha256(serialize(rest));
730
+ }
731
+ /** Sign a bundle with the owner's Ed25519 private key (the JWK `d`, base64url); sets `hash` and `sig`. */
732
+ export function signBundle(b, privateD, publicX) {
733
+ const hash = bundleHash(b);
734
+ const key = createPrivateKey({ key: { kty: "OKP", crv: "Ed25519", x: publicX, d: privateD }, format: "jwk" });
735
+ const sig = edSign(null, Buffer.from(hash, "hex"), key).toString("base64url");
736
+ return { ...b, hash: `sha256:${hash}`, sig };
737
+ }
738
+ /** Check a bundle's hash and, with the owner's published key, its signature. */
739
+ export function verifyBundle(b, keys) {
740
+ const hash = bundleHash(b);
741
+ if (b.hash !== `sha256:${hash}`)
742
+ return { valid: false, reason: "hash" };
743
+ const owner = String(b.owner ?? "");
744
+ // The key in force when the version was published (11.7): a rotated key still verifies what it signed.
745
+ const k = keys ? keyFor(keys, owner, typeof b.version === "string" ? b.version : undefined) : undefined;
746
+ if (!k)
747
+ return keys ? { valid: false, reason: "no key" } : { valid: true };
748
+ if (k.alg !== "Ed25519" || typeof b.sig !== "string")
749
+ return { valid: false, reason: "signature" };
750
+ const key = createPublicKey({ key: { kty: "OKP", crv: "Ed25519", x: k.public }, format: "jwk" });
751
+ return edVerify(null, Buffer.from(hash, "hex"), key, Buffer.from(b.sig, "base64url")) ? { valid: true } : { valid: false, reason: "signature" };
752
+ }
753
+ //# sourceMappingURL=bundle.js.map