@mulmoclaude/core 0.13.1 → 0.15.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 (59) hide show
  1. package/assets/helps/billing-clients-worklog.md +15 -12
  2. package/assets/helps/collection-skills.md +67 -22
  3. package/assets/helps/todo-collection.md +13 -7
  4. package/dist/{deriveAll-BQ_p5HSB.cjs → calendarGrid-CaR-q36Y.js} +352 -74
  5. package/dist/calendarGrid-CaR-q36Y.js.map +1 -0
  6. package/dist/calendarGrid-gN4sei7s.cjs +1070 -0
  7. package/dist/calendarGrid-gN4sei7s.cjs.map +1 -0
  8. package/dist/collection/core/backlinks.d.ts +20 -0
  9. package/dist/collection/core/ids.d.ts +12 -0
  10. package/dist/collection/core/recordZ.d.ts +23 -0
  11. package/dist/collection/core/schema.d.ts +72 -418
  12. package/dist/collection/core/schemaZ.d.ts +878 -0
  13. package/dist/collection/core/templatePath.d.ts +34 -0
  14. package/dist/collection/core/uiTypes.d.ts +22 -0
  15. package/dist/collection/core/where.d.ts +9 -15
  16. package/dist/collection/index.cjs +50 -330
  17. package/dist/collection/index.cjs.map +1 -1
  18. package/dist/collection/index.d.ts +2 -0
  19. package/dist/collection/index.js +14 -301
  20. package/dist/collection/index.js.map +1 -1
  21. package/dist/collection/paths.cjs +1 -1
  22. package/dist/collection/paths.cjs.map +1 -1
  23. package/dist/collection/paths.js +1 -1
  24. package/dist/collection/paths.js.map +1 -1
  25. package/dist/collection/registry/server/index.cjs +1 -1
  26. package/dist/collection/registry/server/index.js +1 -1
  27. package/dist/collection/server/discovery.d.ts +2 -244
  28. package/dist/collection/server/index.cjs +8 -2
  29. package/dist/collection/server/index.d.ts +1 -0
  30. package/dist/collection/server/index.js +3 -2
  31. package/dist/collection/server/io.d.ts +9 -0
  32. package/dist/collection/server/ontology.d.ts +35 -0
  33. package/dist/collection/server/templatePath.d.ts +1 -34
  34. package/dist/collection/server/validate.d.ts +11 -7
  35. package/dist/collection-watchers/index.cjs +5 -4
  36. package/dist/collection-watchers/index.cjs.map +1 -1
  37. package/dist/collection-watchers/index.js +3 -2
  38. package/dist/collection-watchers/index.js.map +1 -1
  39. package/dist/feeds/index.cjs +4 -4
  40. package/dist/feeds/index.js +2 -2
  41. package/dist/feeds/ingestTypes.d.ts +15 -40
  42. package/dist/feeds/server/index.cjs +5 -5
  43. package/dist/feeds/server/index.js +3 -3
  44. package/dist/{ingestTypes-C7EheYZX.cjs → ingestTypes-DG8RExWt.cjs} +3 -3
  45. package/dist/ingestTypes-DG8RExWt.cjs.map +1 -0
  46. package/dist/{ingestTypes-CmJeOUJc.js → ingestTypes-V4upKeBx.js} +2 -2
  47. package/dist/ingestTypes-V4upKeBx.js.map +1 -0
  48. package/dist/{server-DKXXFTbw.js → server-BNTR7ySK.js} +531 -196
  49. package/dist/server-BNTR7ySK.js.map +1 -0
  50. package/dist/{server-G6GtOdaW.cjs → server-bsNrf2yW.cjs} +572 -213
  51. package/dist/server-bsNrf2yW.cjs.map +1 -0
  52. package/package.json +1 -1
  53. package/dist/deriveAll-BQ_p5HSB.cjs.map +0 -1
  54. package/dist/deriveAll-CMFXDQ_G.js +0 -506
  55. package/dist/deriveAll-CMFXDQ_G.js.map +0 -1
  56. package/dist/ingestTypes-C7EheYZX.cjs.map +0 -1
  57. package/dist/ingestTypes-CmJeOUJc.js.map +0 -1
  58. package/dist/server-DKXXFTbw.js.map +0 -1
  59. package/dist/server-G6GtOdaW.cjs.map +0 -1
@@ -1,506 +0,0 @@
1
- //#region src/collection/core/schema.ts
2
- /** Declarative retriever kinds a Feed's `ingest.kind` may declare. The host's
3
- * feeds engine dispatches on these; they live here (with the schema contract)
4
- * so the schema validator can enforce them. The host re-exports these from
5
- * `server/workspace/feeds/ingestTypes.ts`. */
6
- var INGEST_KINDS = [
7
- "rss",
8
- "atom",
9
- "http-json"
10
- ];
11
- /** The agent-performed ingest kind. Instead of a declarative fetch, the host
12
- * dispatches a hidden background chat (origin `system`) in `ingest.role`,
13
- * seeded with `ingest.template` + a summary of every record, on the
14
- * `ingest.schedule` cadence; the worker edits records via the collections io
15
- * layer. Kept separate from {@link INGEST_KINDS} (which the declarative
16
- * retriever registry keys on) so the schema validator can model `ingest` as a
17
- * discriminated union without the feeds engine gaining an "agent" retriever. */
18
- var AGENT_INGEST_KIND = "agent";
19
- /** Refresh cadences a Feed's `ingest.schedule` may declare. */
20
- var FEED_SCHEDULES = [
21
- "hourly",
22
- "daily",
23
- "weekly",
24
- "on-demand"
25
- ];
26
- /** Narrowing guard: true when `every` is the field-driven arm. */
27
- function isFieldDrivenEvery(every) {
28
- return "fromField" in every;
29
- }
30
- /** Resolve an `embed` field's target record id: the fixed `id`, or the value
31
- * of the sibling `idField` on this record (empty string when neither applies
32
- * — the caller renders that as "no record"). Pure + isomorphic so the server
33
- * projection (`derive.ts`) and the client preview (`useCollectionRendering`)
34
- * resolve embeds identically. */
35
- function embedTargetId(field, record) {
36
- if (field.id) return field.id;
37
- if (field.idField && record) return String(record[field.idField] ?? "");
38
- return "";
39
- }
40
- //#endregion
41
- //#region src/collection/core/where.ts
42
- /** True when `record[field]` is absent (`undefined`/`null`) — the only case
43
- * where `ne` and every other op disagree on the result. */
44
- function isMissing(raw) {
45
- return raw === void 0 || raw === null;
46
- }
47
- /** The effective comparison value for `cond`: its literal `value`, or — for
48
- * a `valueFrom` reference — the target field read out of `recordsById`.
49
- * `undefined` means UNRESOLVED (no such record, or the field on it is
50
- * missing); the caller must treat that as "never matches", not as a
51
- * literal `undefined` value to compare against. */
52
- function resolveValue(cond, record, recordsById) {
53
- if (!cond.valueFrom) return cond.value;
54
- const { record: refRecord, field } = cond.valueFrom;
55
- const raw = (refRecord === void 0 ? record : recordsById[refRecord])?.[field];
56
- return isMissing(raw) ? void 0 : String(raw);
57
- }
58
- function matchesNumericOp(operator, left, right) {
59
- if (operator === "gt") return left > right;
60
- if (operator === "gte") return left >= right;
61
- if (operator === "lt") return left < right;
62
- return left <= right;
63
- }
64
- /** `Number("")` / `Number(" ")` are `0`, not `NaN`, so treat a blank string
65
- * as non-numeric explicitly — an empty field must fail a numeric compare,
66
- * not read as zero. */
67
- function toNumber(raw) {
68
- return raw.trim() === "" ? NaN : Number(raw);
69
- }
70
- function matchesNumeric(operator, raw, value) {
71
- if (Array.isArray(value)) return false;
72
- const left = toNumber(raw);
73
- const right = toNumber(value);
74
- if (Number.isNaN(left) || Number.isNaN(right)) return false;
75
- return matchesNumericOp(operator, left, right);
76
- }
77
- /** True when the present string `raw` satisfies `operator` against the
78
- * resolved `value` (field known to exist — MISSING is handled by the
79
- * caller before this runs, and an UNRESOLVED `valueFrom` never reaches
80
- * here either). */
81
- function matchesPresent(operator, raw, value) {
82
- switch (operator) {
83
- case "eq": return raw === String(value);
84
- case "ne": return raw !== String(value);
85
- case "in": return Array.isArray(value) && value.includes(raw);
86
- case "contains": return raw.includes(String(value));
87
- case "gt":
88
- case "gte":
89
- case "lt":
90
- case "lte": return matchesNumeric(operator, raw, value);
91
- default: return false;
92
- }
93
- }
94
- /** True when `record` satisfies one condition, given `recordsById` to
95
- * resolve a `valueFrom` reference. Two independent MISSING cases, checked
96
- * in order:
97
- * - `record[cond.field]` absent (`undefined`/`null`) → matches only `ne`
98
- * (vacuously true — "not equal to X" holds when there's no value at
99
- * all); every other op is false. Unchanged from the literal-`value`
100
- * behaviour, regardless of whether `valueFrom` would also resolve.
101
- * - the resolved comparison value is UNRESOLVED (a `valueFrom` whose
102
- * target record/field doesn't exist) → false for EVERY op, including
103
- * `ne` — a broken reference must never spuriously match. */
104
- function matchesCond(cond, record, recordsById) {
105
- const raw = record[cond.field];
106
- if (isMissing(raw)) return cond.op === "ne";
107
- const value = resolveValue(cond, record, recordsById);
108
- if (value === void 0) return false;
109
- return matchesPresent(cond.op, String(raw), value);
110
- }
111
- /** True when `record` satisfies every condition in `where` (AND). An empty
112
- * `where` matches everything. `recordsById` — the source collection's
113
- * records keyed by primaryKey — resolves any `valueFrom` reference;
114
- * omitted (default `{}`) for callers with no cross-record lookups, in
115
- * which case every `valueFrom` condition is UNRESOLVED and so never
116
- * matches. */
117
- function matchesWhere(where, record, recordsById = {}) {
118
- return where.every((cond) => matchesCond(cond, record, recordsById));
119
- }
120
- //#endregion
121
- //#region src/collection/core/dynamicIcon.ts
122
- /** The record with the greatest `String(record[field])` (localeCompare) —
123
- * ties keep the first-seen record (stable left-to-right `reduce`). */
124
- function latestByField(pool, field) {
125
- return pool.reduce((latest, candidate) => String(candidate[field] ?? "").localeCompare(String(latest[field] ?? "")) > 0 ? candidate : latest);
126
- }
127
- /** Reduce `records` to the one record that decides the effective icon, per
128
- * `source`'s `where` filter + `from` strategy:
129
- * - pool = `source.where`-filtered records, or every record when unset;
130
- * - an empty pool resolves to `null` (no source record → fallback);
131
- * - `from: "first"` / `"when"` → the first pool record (storage order);
132
- * - `from: "latest"` (default), with `orderBy` given → the pool record
133
- * whose `String(record[orderBy])` sorts highest;
134
- * - `from: "latest"`, with no `orderBy` → the last pool record.
135
- * `recordsById` (the source collection's records keyed by primaryKey)
136
- * resolves any `valueFrom` reference inside `source.where`; omitted for
137
- * callers with no cross-record lookups. */
138
- function selectDynamicRecord(records, source, orderBy, recordsById = {}) {
139
- const { where } = source;
140
- const pool = where ? records.filter((record) => matchesWhere(where, record, recordsById)) : records;
141
- if (pool.length === 0) return null;
142
- if (source.from === "first" || source.from === "when") return pool[0];
143
- return orderBy ? latestByField(pool, orderBy) : pool[pool.length - 1];
144
- }
145
- /** Map a resolved source record to the effective icon: `spec.fallback`
146
- * (or the collection's own static `icon`) when there's no record or no
147
- * rule matches; otherwise the `icon` of the first rule whose `where`
148
- * matches the record. `recordsById` resolves any `valueFrom` reference
149
- * inside a rule's `where`, same as `selectDynamicRecord`. */
150
- function resolveIcon(record, spec, staticIcon, recordsById = {}) {
151
- const fallback = spec.fallback ?? staticIcon;
152
- if (!record) return fallback;
153
- const matched = spec.rules.find((rule) => matchesWhere(rule.where, record, recordsById));
154
- return matched ? matched.icon : fallback;
155
- }
156
- var isDateLikeField = (field) => field.type === "date" || field.type === "datetime";
157
- /** The first field key (declaration order) whose type is `date` or
158
- * `datetime` — the default `orderBy` for `from: "latest"` when a
159
- * `DynamicIconSource` doesn't name one. `undefined` when the schema has
160
- * no date-like field. */
161
- function firstDateField(schema) {
162
- return Object.entries(schema.fields).find(([, field]) => isDateLikeField(field))?.[0];
163
- }
164
- //#endregion
165
- //#region src/collection/core/derivedFormula.ts
166
- function evaluateDerived(formula, ctx) {
167
- let tokens;
168
- try {
169
- tokens = tokenize(formula);
170
- } catch {
171
- return null;
172
- }
173
- const parser = new Parser(tokens);
174
- let ast;
175
- try {
176
- ast = parser.parseExpr();
177
- if (!parser.atEnd()) return null;
178
- } catch {
179
- return null;
180
- }
181
- const value = evaluate(ast, ctx);
182
- return Number.isFinite(value) ? value : null;
183
- }
184
- var SINGLE_CHAR_PUNCT = /* @__PURE__ */ new Set([
185
- "(",
186
- ")",
187
- "+",
188
- "-",
189
- "*",
190
- "/",
191
- "."
192
- ]);
193
- function consumeWhitespace(cur) {
194
- const char = cur.input[cur.index];
195
- if (char === " " || char === " " || char === "\n") {
196
- cur.index++;
197
- return true;
198
- }
199
- return false;
200
- }
201
- function consumeNumber(cur) {
202
- const char = cur.input[cur.index] ?? "";
203
- const next = cur.input[cur.index + 1] ?? "";
204
- if (!isDigit(char) && !(char === "." && isDigit(next))) return null;
205
- let raw = "";
206
- while (cur.index < cur.input.length) {
207
- const here = cur.input[cur.index] ?? "";
208
- if (!isDigit(here) && here !== ".") break;
209
- raw += here;
210
- cur.index++;
211
- }
212
- const num = Number(raw);
213
- if (!Number.isFinite(num)) throw new Error("bad number");
214
- return {
215
- kind: "number",
216
- value: num
217
- };
218
- }
219
- function consumeIdent(cur) {
220
- if (!isIdentStart(cur.input[cur.index] ?? "")) return null;
221
- let raw = "";
222
- while (cur.index < cur.input.length && isIdentChar(cur.input[cur.index] ?? "")) {
223
- raw += cur.input[cur.index];
224
- cur.index++;
225
- }
226
- return {
227
- kind: "ident",
228
- value: raw
229
- };
230
- }
231
- function consumePunct(cur) {
232
- const char = cur.input[cur.index] ?? "";
233
- if (char === "[" && cur.input[cur.index + 1] === "]") {
234
- cur.index += 2;
235
- return { kind: "[]" };
236
- }
237
- if (SINGLE_CHAR_PUNCT.has(char)) {
238
- cur.index++;
239
- return { kind: char };
240
- }
241
- return null;
242
- }
243
- function tokenize(input) {
244
- const tokens = [];
245
- const cur = {
246
- input,
247
- index: 0
248
- };
249
- while (cur.index < input.length) {
250
- if (consumeWhitespace(cur)) continue;
251
- const numTok = consumeNumber(cur);
252
- if (numTok) {
253
- tokens.push(numTok);
254
- continue;
255
- }
256
- const punctTok = consumePunct(cur);
257
- if (punctTok) {
258
- tokens.push(punctTok);
259
- continue;
260
- }
261
- const identTok = consumeIdent(cur);
262
- if (identTok) {
263
- tokens.push(identTok);
264
- continue;
265
- }
266
- throw new Error(`unexpected char ${input[cur.index]}`);
267
- }
268
- return tokens;
269
- }
270
- function isDigit(char) {
271
- return char >= "0" && char <= "9";
272
- }
273
- function isIdentStart(char) {
274
- return char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char === "_";
275
- }
276
- function isIdentChar(char) {
277
- return isIdentStart(char) || isDigit(char);
278
- }
279
- var Parser = class {
280
- tokens;
281
- cursor = 0;
282
- constructor(tokens) {
283
- this.tokens = tokens;
284
- }
285
- atEnd() {
286
- return this.cursor >= this.tokens.length;
287
- }
288
- peek() {
289
- return this.tokens[this.cursor];
290
- }
291
- consume() {
292
- const tok = this.tokens[this.cursor++];
293
- if (!tok) throw new Error("unexpected end of input");
294
- return tok;
295
- }
296
- expect(kind) {
297
- const tok = this.consume();
298
- if (tok.kind !== kind) throw new Error(`expected ${kind}, got ${tok.kind}`);
299
- return tok;
300
- }
301
- parseExpr() {
302
- let left = this.parseTerm();
303
- while (this.peek()?.kind === "+" || this.peek()?.kind === "-") {
304
- const operator = this.consume().kind;
305
- const right = this.parseTerm();
306
- left = {
307
- kind: "binop",
308
- operator,
309
- left,
310
- right
311
- };
312
- }
313
- return left;
314
- }
315
- parseTerm() {
316
- let left = this.parseFactor();
317
- while (this.peek()?.kind === "*" || this.peek()?.kind === "/") {
318
- const operator = this.consume().kind;
319
- const right = this.parseFactor();
320
- left = {
321
- kind: "binop",
322
- operator,
323
- left,
324
- right
325
- };
326
- }
327
- return left;
328
- }
329
- parseFactor() {
330
- const tok = this.peek();
331
- if (!tok) throw new Error("unexpected end in factor");
332
- if (tok.kind === "number") {
333
- this.consume();
334
- return {
335
- kind: "num",
336
- value: tok.value
337
- };
338
- }
339
- if (tok.kind === "(") {
340
- this.consume();
341
- const inner = this.parseExpr();
342
- this.expect(")");
343
- return inner;
344
- }
345
- if (tok.kind === "ident") {
346
- const name = tok.value ?? "";
347
- if (name === "sum" && this.tokens[this.cursor + 1]?.kind === "(") {
348
- this.consume();
349
- this.expect("(");
350
- const arg = this.parseSumArg();
351
- this.expect(")");
352
- return {
353
- kind: "sum",
354
- arg
355
- };
356
- }
357
- this.consume();
358
- if (this.peek()?.kind === ".") {
359
- this.consume();
360
- return {
361
- kind: "ref",
362
- field: name,
363
- col: this.expect("ident").value
364
- };
365
- }
366
- return {
367
- kind: "ident",
368
- name
369
- };
370
- }
371
- throw new Error(`unexpected token ${tok.kind} in factor`);
372
- }
373
- parseSumArg() {
374
- const factors = [];
375
- const operators = [];
376
- factors.push(this.parseTableCol());
377
- while (this.peek()?.kind === "*" || this.peek()?.kind === "/") {
378
- const operator = this.consume().kind;
379
- operators.push(operator);
380
- factors.push(this.parseTableCol());
381
- }
382
- return {
383
- factors,
384
- operators
385
- };
386
- }
387
- parseTableCol() {
388
- const tableTok = this.expect("ident");
389
- this.expect("[]");
390
- this.expect(".");
391
- const colTok = this.expect("ident");
392
- return {
393
- table: tableTok.value,
394
- col: colTok.value
395
- };
396
- }
397
- };
398
- function evaluate(node, ctx) {
399
- if (node.kind === "num") return node.value;
400
- if (node.kind === "ident") {
401
- const raw = ctx.record[node.name];
402
- return toFiniteNumber(raw);
403
- }
404
- if (node.kind === "ref") {
405
- const target = ctx.refs?.[node.field] ?? null;
406
- if (!target) return NaN;
407
- return toFiniteNumber(target[node.col]);
408
- }
409
- if (node.kind === "binop") {
410
- const left = evaluate(node.left, ctx);
411
- const right = evaluate(node.right, ctx);
412
- return applyBinop(node.operator, left, right);
413
- }
414
- if (node.kind === "sum") return evaluateSum(node.arg, ctx);
415
- throw new Error(`unknown node`);
416
- }
417
- function applyBinop(operator, left, right) {
418
- if (!Number.isFinite(left) || !Number.isFinite(right)) return NaN;
419
- if (operator === "+") return left + right;
420
- if (operator === "-") return left - right;
421
- if (operator === "*") return left * right;
422
- if (right === 0) return NaN;
423
- return left / right;
424
- }
425
- function evaluateSum(arg, ctx) {
426
- if (arg.factors.length === 0) return 0;
427
- const tableName = arg.factors[0].table;
428
- for (const factor of arg.factors) if (factor.table !== tableName) return NaN;
429
- const rows = ctx.record[tableName];
430
- if (!Array.isArray(rows)) return 0;
431
- let total = 0;
432
- for (const row of rows) {
433
- if (!row || typeof row !== "object") continue;
434
- let product = toFiniteNumber(row[arg.factors[0].col]);
435
- if (!Number.isFinite(product)) return NaN;
436
- for (let i = 1; i < arg.factors.length; i++) {
437
- const value = toFiniteNumber(row[arg.factors[i].col]);
438
- if (!Number.isFinite(value)) return NaN;
439
- product = applyBinop(arg.operators[i - 1], product, value);
440
- }
441
- total += product;
442
- }
443
- return total;
444
- }
445
- function toFiniteNumber(value) {
446
- if (typeof value === "number") return Number.isFinite(value) ? value : NaN;
447
- if (typeof value === "string" && value.length > 0) {
448
- const num = Number(value);
449
- return Number.isFinite(num) ? num : NaN;
450
- }
451
- return NaN;
452
- }
453
- //#endregion
454
- //#region src/collection/core/deriveAll.ts
455
- /** Map each `ref` field's stored slug to its loaded target record (or
456
- * null when dangling / not loaded), keyed by the LOCAL field name —
457
- * the shape `evaluateDerived` reads for `<field>.<col>` derefs. */
458
- function resolveRowRefs(schema, record, refRecords) {
459
- const refs = {};
460
- for (const [key, field] of Object.entries(schema.fields)) {
461
- if (field.type !== "ref" || !field.to) continue;
462
- const slug = record[key];
463
- refs[key] = typeof slug === "string" ? refRecords[field.to]?.[slug] ?? null : null;
464
- }
465
- return refs;
466
- }
467
- /** Evaluate every `derived` field against `base`, saturating so a
468
- * derived field can read another derived field computed in an earlier
469
- * pass (`subtotal → tax → total` converges in ≤ field-count passes).
470
- * Cycles can't loop forever — passes are bounded by the number of
471
- * derived fields and the loop breaks as soon as a pass changes
472
- * nothing. Failed formulas stay ABSENT (the UI renders them as
473
- * em-dash). Returns a copy; `base` is never mutated.
474
- *
475
- * Derived keys already present in `base` are stripped before
476
- * evaluation: computed output is host-truth, never persisted-input
477
- * fallback. A record JSON can carry a stale (or forged) derived value
478
- * — raw Write/Edit, legacy data — and without the strip, a failing
479
- * formula would silently surface that value as if the host computed
480
- * it. */
481
- function deriveAll(schema, base, refRecords) {
482
- const derivedKeys = new Set(Object.keys(schema.fields).filter((key) => schema.fields[key]?.type === "derived"));
483
- const enriched = Object.fromEntries(Object.entries(base).filter(([key]) => !derivedKeys.has(key)));
484
- const refs = resolveRowRefs(schema, base, refRecords);
485
- const maxPasses = Object.values(schema.fields).filter((field) => field.type === "derived").length;
486
- for (let pass = 0; pass < maxPasses; pass++) {
487
- let mutated = false;
488
- for (const [key, field] of Object.entries(schema.fields)) {
489
- if (field.type !== "derived" || !field.formula) continue;
490
- const next = evaluateDerived(field.formula, {
491
- record: enriched,
492
- refs
493
- });
494
- if (next !== null && enriched[key] !== next) {
495
- enriched[key] = next;
496
- mutated = true;
497
- }
498
- }
499
- if (!mutated) break;
500
- }
501
- return enriched;
502
- }
503
- //#endregion
504
- export { resolveIcon as a, AGENT_INGEST_KIND as c, embedTargetId as d, isFieldDrivenEvery as f, firstDateField as i, FEED_SCHEDULES as l, resolveRowRefs as n, selectDynamicRecord as o, evaluateDerived as r, matchesWhere as s, deriveAll as t, INGEST_KINDS as u };
505
-
506
- //# sourceMappingURL=deriveAll-CMFXDQ_G.js.map