@mulmoclaude/core 0.13.1 → 0.14.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/assets/helps/billing-clients-worklog.md +15 -12
- package/assets/helps/collection-skills.md +42 -19
- package/assets/helps/todo-collection.md +13 -7
- package/dist/{deriveAll-CMFXDQ_G.js → calendarGrid-C1rfCXJ3.cjs} +468 -3
- package/dist/calendarGrid-C1rfCXJ3.cjs.map +1 -0
- package/dist/{deriveAll-BQ_p5HSB.cjs → calendarGrid-kO6rGfm0.js} +289 -74
- package/dist/calendarGrid-kO6rGfm0.js.map +1 -0
- package/dist/collection/core/ids.d.ts +12 -0
- package/dist/collection/core/recordZ.d.ts +25 -0
- package/dist/collection/core/schema.d.ts +65 -418
- package/dist/collection/core/schemaZ.d.ts +846 -0
- package/dist/collection/core/templatePath.d.ts +34 -0
- package/dist/collection/core/where.d.ts +9 -15
- package/dist/collection/index.cjs +34 -295
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.d.ts +1 -0
- package/dist/collection/index.js +5 -270
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/paths.cjs +1 -1
- package/dist/collection/paths.cjs.map +1 -1
- package/dist/collection/paths.js +1 -1
- package/dist/collection/paths.js.map +1 -1
- package/dist/collection/registry/server/index.cjs +1 -1
- package/dist/collection/registry/server/index.js +1 -1
- package/dist/collection/server/discovery.d.ts +2 -244
- package/dist/collection/server/index.cjs +3 -1
- package/dist/collection/server/index.js +2 -2
- package/dist/collection/server/templatePath.d.ts +1 -34
- package/dist/collection/server/validate.d.ts +11 -7
- package/dist/collection-watchers/index.cjs +1 -1
- package/dist/collection-watchers/index.js +1 -1
- package/dist/feeds/index.cjs +4 -4
- package/dist/feeds/index.js +2 -2
- package/dist/feeds/ingestTypes.d.ts +15 -40
- package/dist/feeds/server/index.cjs +5 -5
- package/dist/feeds/server/index.js +3 -3
- package/dist/{ingestTypes-C7EheYZX.cjs → ingestTypes-BIFXlw7M.cjs} +3 -3
- package/dist/ingestTypes-BIFXlw7M.cjs.map +1 -0
- package/dist/{ingestTypes-CmJeOUJc.js → ingestTypes-KuYPX9Ea.js} +2 -2
- package/dist/ingestTypes-KuYPX9Ea.js.map +1 -0
- package/dist/{server-DKXXFTbw.js → server-Cc6XcyOA.js} +417 -187
- package/dist/server-Cc6XcyOA.js.map +1 -0
- package/dist/{server-G6GtOdaW.cjs → server-Cd5WdM8a.cjs} +440 -198
- package/dist/server-Cd5WdM8a.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/deriveAll-BQ_p5HSB.cjs.map +0 -1
- package/dist/deriveAll-CMFXDQ_G.js.map +0 -1
- package/dist/ingestTypes-C7EheYZX.cjs.map +0 -1
- package/dist/ingestTypes-CmJeOUJc.js.map +0 -1
- package/dist/server-DKXXFTbw.js.map +0 -1
- package/dist/server-G6GtOdaW.cjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-D6vf50IK.cjs");
|
|
2
|
-
const
|
|
2
|
+
const require_calendarGrid = require("./calendarGrid-C1rfCXJ3.cjs");
|
|
3
3
|
const require_collection_paths = require("./collection/paths.cjs");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
@@ -80,7 +80,6 @@ var log = {
|
|
|
80
80
|
//#endregion
|
|
81
81
|
//#region src/collection/server/paths.ts
|
|
82
82
|
var SCHEMA_FILE = "schema.json";
|
|
83
|
-
var SAFE_SLUG_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
|
|
84
83
|
/** Sanitise a user-supplied slug into a safe directory-name leaf.
|
|
85
84
|
* Returns null for anything that fails the slug whitelist OR isn't a
|
|
86
85
|
* basename (i.e. survives `path.basename` round-trip unchanged).
|
|
@@ -88,12 +87,11 @@ var SAFE_SLUG_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
|
|
|
88
87
|
* `js/path-injection` sanitiser. */
|
|
89
88
|
function safeSlugName(slug) {
|
|
90
89
|
if (typeof slug !== "string") return null;
|
|
91
|
-
if (!SAFE_SLUG_PATTERN.test(slug)) return null;
|
|
90
|
+
if (!require_calendarGrid.SAFE_SLUG_PATTERN.test(slug)) return null;
|
|
92
91
|
const basename = node_path.default.basename(slug);
|
|
93
92
|
if (basename !== slug) return null;
|
|
94
93
|
return basename;
|
|
95
94
|
}
|
|
96
|
-
var SAFE_RECORD_ID_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/;
|
|
97
95
|
/** Sanitise a user-supplied record id into a safe filename stem. Like
|
|
98
96
|
* `safeSlugName` but tolerates interior dots (so natural keys work),
|
|
99
97
|
* while still rejecting any `..` substring, path separators, and
|
|
@@ -101,7 +99,7 @@ var SAFE_RECORD_ID_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/;
|
|
|
101
99
|
* `js/path-injection` sanitiser CodeQL recognises on `safeSlugName`. */
|
|
102
100
|
function safeRecordId(recordId) {
|
|
103
101
|
if (typeof recordId !== "string") return null;
|
|
104
|
-
if (!SAFE_RECORD_ID_PATTERN.test(recordId)) return null;
|
|
102
|
+
if (!require_calendarGrid.SAFE_RECORD_ID_PATTERN.test(recordId)) return null;
|
|
105
103
|
if (recordId.includes("..")) return null;
|
|
106
104
|
const basename = node_path.default.basename(recordId);
|
|
107
105
|
if (basename !== recordId) return null;
|
|
@@ -636,13 +634,119 @@ ${dataJson}
|
|
|
636
634
|
${templateText}`;
|
|
637
635
|
}
|
|
638
636
|
//#endregion
|
|
639
|
-
//#region src/collection/
|
|
640
|
-
|
|
637
|
+
//#region src/collection/core/recordZ.ts
|
|
638
|
+
/** derived/embed/toggle are host-computed or projected — never written to
|
|
639
|
+
* the record JSON, so required / value checks must not apply to them. */
|
|
641
640
|
var COMPUTED_TYPES = /* @__PURE__ */ new Set([
|
|
642
641
|
"derived",
|
|
643
642
|
"embed",
|
|
644
643
|
"toggle"
|
|
645
644
|
]);
|
|
645
|
+
/** The emptiness rule shared by `required` and the "only check present
|
|
646
|
+
* values" gate. NOT a truthiness check — `0` and `false` are filled. */
|
|
647
|
+
var isEmptyValue = (value) => value === void 0 || value === null || value === "";
|
|
648
|
+
/** The historical write-gate checks, verbatim: required non-empty, enum
|
|
649
|
+
* membership (compared as strings, so a numeric `5` satisfies `"5"`). */
|
|
650
|
+
function enforcedProblem(key, spec, value) {
|
|
651
|
+
const empty = isEmptyValue(value);
|
|
652
|
+
if (spec.required && empty) return `missing required field '${key}'`;
|
|
653
|
+
if (!empty && spec.type === "enum" && !spec.values.includes(String(value))) return `'${key}' = '${String(value)}' is not one of [${spec.values.join(", ")}]`;
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
/** Numeric coercion for the strict `number`/`money` check: a plain number,
|
|
657
|
+
* or a non-blank numeric string (renderers coerce those via `Number(...)`,
|
|
658
|
+
* so they display fine). Anything else — arrays (`[]` stringifies to `""`
|
|
659
|
+
* = 0, `[42]` to `"42"`), booleans, objects — is NaN and gets flagged. */
|
|
660
|
+
function coerceNumeric(value) {
|
|
661
|
+
if (typeof value === "number") return value;
|
|
662
|
+
if (typeof value === "string" && value.trim() !== "") return Number(value);
|
|
663
|
+
return NaN;
|
|
664
|
+
}
|
|
665
|
+
/** Report-only per-type checks on a PRESENT value. Date / datetime reuse the
|
|
666
|
+
* calendar's STRICT civil parsers (`parseIsoDate` / `parseIsoDateTime`), so
|
|
667
|
+
* the lint flags exactly the values the calendar / trigger / spawn code
|
|
668
|
+
* would silently drop — impossible days like `2026-02-30`, and datetimes
|
|
669
|
+
* outside the canonical `YYYY-MM-DDTHH:MM[:SS]` shape (e.g. a `Z` suffix,
|
|
670
|
+
* which the day view can't place). `string`-backed types accept anything
|
|
671
|
+
* stringifiable; `ref` existence is out of scope. */
|
|
672
|
+
function strictTypeProblem(key, spec, value) {
|
|
673
|
+
switch (spec.type) {
|
|
674
|
+
case "number":
|
|
675
|
+
case "money": return Number.isFinite(coerceNumeric(value)) ? null : `'${key}' = '${String(value)}' is not numeric (a '${spec.type}' field stores a plain number)`;
|
|
676
|
+
case "boolean": return value === true || value === false ? null : `'${key}' = '${String(value)}' is not a boolean (store true or false, unquoted)`;
|
|
677
|
+
case "date": return require_calendarGrid.parseIsoDate(value) !== null ? null : `'${key}' = '${String(value)}' is not a real YYYY-MM-DD date`;
|
|
678
|
+
case "datetime": return require_calendarGrid.parseIsoDateTime(value) !== null ? null : `'${key}' = '${String(value)}' is not a YYYY-MM-DDTHH:MM datetime (seconds optional, no timezone suffix — the shape the calendar parses)`;
|
|
679
|
+
default: return null;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/** Strict check for a PRESENT `table` value: an array of row objects, each
|
|
683
|
+
* row conforming to the sub-schema (required / enum / typed sub-values).
|
|
684
|
+
* First row problem wins, prefixed with the row number so the fix is
|
|
685
|
+
* locatable. */
|
|
686
|
+
function strictTableProblem(key, spec, value) {
|
|
687
|
+
if (!Array.isArray(value)) return `'${key}' = '${String(value)}' is not an array of rows (a 'table' field stores an array of row objects)`;
|
|
688
|
+
for (let index = 0; index < value.length; index++) {
|
|
689
|
+
const row = value[index];
|
|
690
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return `'${key}' row ${index + 1} is not an object`;
|
|
691
|
+
for (const [subKey, subSpec] of Object.entries(spec.of)) {
|
|
692
|
+
const subValue = row[subKey];
|
|
693
|
+
const problem = enforcedProblem(subKey, subSpec, subValue) ?? (isEmptyValue(subValue) ? null : strictTypeProblem(subKey, subSpec, subValue));
|
|
694
|
+
if (problem) return `'${key}' row ${index + 1}: ${problem}`;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
/** First problem for one field's stored value under `tier`, or null.
|
|
700
|
+
* Enforced checks always run (and their messages never vary by tier — the
|
|
701
|
+
* scan and the write gate must agree on them); strict adds the per-type
|
|
702
|
+
* layer on present values only. */
|
|
703
|
+
function recordFieldProblem(key, spec, value, tier) {
|
|
704
|
+
const enforced = enforcedProblem(key, spec, value);
|
|
705
|
+
if (enforced || tier === "enforced") return enforced;
|
|
706
|
+
if (isEmptyValue(value)) return null;
|
|
707
|
+
if (spec.type === "table") return strictTableProblem(key, spec, value);
|
|
708
|
+
return strictTypeProblem(key, spec, value);
|
|
709
|
+
}
|
|
710
|
+
var compiled = /* @__PURE__ */ new WeakMap();
|
|
711
|
+
/** Compile `schema.fields` into a zod validator for a stored record.
|
|
712
|
+
* Loose object: unknown keys are allowed and any declared key may be
|
|
713
|
+
* absent (records are user files, not parse-and-rewrite targets —
|
|
714
|
+
* callers validate, they never persist the parse output). The checks run
|
|
715
|
+
* as ONE object-level refine iterating fields in declaration order —
|
|
716
|
+
* per-key shape schemas can't express "key may be absent BUT its absence
|
|
717
|
+
* must still reach the required check", and the single loop keeps the
|
|
718
|
+
* first reported issue identical to the historical first-problem-wins
|
|
719
|
+
* contract. */
|
|
720
|
+
function compileRecordZ(schema, tier) {
|
|
721
|
+
const cached = compiled.get(schema)?.[tier];
|
|
722
|
+
if (cached) return cached;
|
|
723
|
+
const stored = Object.entries(schema.fields).filter(([, spec]) => !COMPUTED_TYPES.has(spec.type));
|
|
724
|
+
const validator = zod.z.looseObject({}).superRefine((record, ctx) => {
|
|
725
|
+
for (const [key, spec] of stored) {
|
|
726
|
+
const problem = recordFieldProblem(key, spec, record[key], tier);
|
|
727
|
+
if (problem) ctx.addIssue({
|
|
728
|
+
code: "custom",
|
|
729
|
+
message: problem,
|
|
730
|
+
path: [key]
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
const entry = compiled.get(schema) ?? {};
|
|
735
|
+
entry[tier] = validator;
|
|
736
|
+
compiled.set(schema, entry);
|
|
737
|
+
return validator;
|
|
738
|
+
}
|
|
739
|
+
/** First schema problem on an in-memory record under `tier`, or null. One
|
|
740
|
+
* issue per record keeps the report short and the fix obvious (the
|
|
741
|
+
* historical contract of `validateRecordObject`). */
|
|
742
|
+
function firstRecordProblem(record, schema, tier) {
|
|
743
|
+
const result = compileRecordZ(schema, tier).safeParse(record);
|
|
744
|
+
if (result.success) return null;
|
|
745
|
+
return result.error.issues[0]?.message ?? "record failed schema validation";
|
|
746
|
+
}
|
|
747
|
+
//#endregion
|
|
748
|
+
//#region src/collection/server/validate.ts
|
|
749
|
+
var MAX_ISSUES = 25;
|
|
646
750
|
/** Read every `<id>.json` under the collection's dataDir and report the
|
|
647
751
|
* ones that won't load or violate the schema. An empty list means every
|
|
648
752
|
* record is fine. */
|
|
@@ -705,72 +809,70 @@ async function inspectRecord(fullPath, name, schema) {
|
|
|
705
809
|
file: name,
|
|
706
810
|
problem: "not a JSON object — skipped, won't appear"
|
|
707
811
|
};
|
|
708
|
-
const problem = validateRecordObject(parsed, name.replace(/\.json$/, ""), schema);
|
|
812
|
+
const problem = validateRecordObject(parsed, name.replace(/\.json$/, ""), schema, "strict");
|
|
709
813
|
return problem ? {
|
|
710
814
|
file: name,
|
|
711
815
|
problem
|
|
712
816
|
} : null;
|
|
713
817
|
}
|
|
714
818
|
/** First schema problem on an in-memory record (primaryKey↔id mismatch,
|
|
715
|
-
*
|
|
716
|
-
*
|
|
717
|
-
*
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
|
|
819
|
+
* then the compiled per-field checks — see `../core/recordZ` for the two
|
|
820
|
+
* tiers), or null when it's fine. One issue per record keeps the report
|
|
821
|
+
* short and the fix obvious. Pure + exported so write paths
|
|
822
|
+
* (manageCollection putItems) can gate on the SAME enforced rules the
|
|
823
|
+
* post-hoc file scan reports — `itemId` is the id the record is (or
|
|
824
|
+
* would be) stored under. The default `"enforced"` tier keeps every
|
|
825
|
+
* write gate on the historical three checks; only pass `"strict"` from
|
|
826
|
+
* report-only surfaces. */
|
|
827
|
+
function validateRecordObject(record, itemId, schema, tier = "enforced") {
|
|
721
828
|
const idValue = record[schema.primaryKey];
|
|
722
829
|
if (typeof idValue !== "string" || idValue !== itemId) return `'${schema.primaryKey}' is '${String(idValue ?? "")}' but must equal the filename ('${itemId}'), or the record can't be opened`;
|
|
723
|
-
|
|
724
|
-
if (COMPUTED_TYPES.has(spec.type)) continue;
|
|
725
|
-
const value = record[field];
|
|
726
|
-
const empty = value === void 0 || value === null || value === "";
|
|
727
|
-
if (spec.required && empty) return `missing required field '${field}'`;
|
|
728
|
-
if (!empty && spec.type === "enum" && spec.values && !spec.values.includes(String(value))) return `'${field}' = '${String(value)}' is not one of [${spec.values.join(", ")}]`;
|
|
729
|
-
}
|
|
730
|
-
return null;
|
|
830
|
+
return firstRecordProblem(record, schema, tier);
|
|
731
831
|
}
|
|
732
832
|
//#endregion
|
|
733
|
-
//#region src/collection/
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
var
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
path: ["id"]
|
|
753
|
-
};
|
|
754
|
-
var enumRefine = (spec) => spec.type !== "enum" || Array.isArray(spec.values) && spec.values.length > 0 && spec.values.every((value) => typeof value === "string" && value.length > 0);
|
|
755
|
-
var enumMessage = {
|
|
756
|
-
message: "fields with type 'enum' must declare a non-empty `values` array of non-empty strings",
|
|
757
|
-
path: ["values"]
|
|
833
|
+
//#region src/collection/core/schemaZ.ts
|
|
834
|
+
/** Optional visibility predicate shared by actions and fields: the target
|
|
835
|
+
* shows only when the open record's `field` (stringified) is one of `in`.
|
|
836
|
+
* Domain-free — `field` is any non-empty key, `in` a non-empty array of
|
|
837
|
+
* non-empty values; the host never interprets the meaning.
|
|
838
|
+
*
|
|
839
|
+
* `trim().min(1)` rather than bare `min(1)` so a whitespace-only string
|
|
840
|
+
* (" ") fails validation — otherwise the cell formatter / dropdown would
|
|
841
|
+
* render visual blanks that look like missing data. Applied consistently to
|
|
842
|
+
* every "non-empty string" slot in this file (CodeRabbit PR #1497). */
|
|
843
|
+
var WhenZ = zod.z.object({
|
|
844
|
+
field: zod.z.string().trim().min(1),
|
|
845
|
+
in: zod.z.array(zod.z.string().trim().min(1)).min(1)
|
|
846
|
+
});
|
|
847
|
+
var fieldBase = {
|
|
848
|
+
label: zod.z.string().min(1),
|
|
849
|
+
primary: zod.z.boolean().optional(),
|
|
850
|
+
required: zod.z.boolean().optional(),
|
|
851
|
+
when: WhenZ.optional()
|
|
758
852
|
};
|
|
759
|
-
var
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
const hasPointer = typeof spec.currencyField === "string" && spec.currencyField.trim().length > 0;
|
|
763
|
-
return hasLiteral || hasPointer;
|
|
853
|
+
var currencyKeys = {
|
|
854
|
+
currency: zod.z.string().trim().min(1).optional(),
|
|
855
|
+
currencyField: zod.z.string().trim().min(1).optional()
|
|
764
856
|
};
|
|
857
|
+
var hasCurrencySource = (spec) => spec.currency !== void 0 || spec.currencyField !== void 0;
|
|
765
858
|
var currencyMessage = {
|
|
766
859
|
message: "fields that render as money (type 'money', or 'derived' with display 'money') must declare either a literal `currency` (ISO 4217 code, e.g. 'USD', 'JPY') or a `currencyField` naming the record field that holds the code",
|
|
767
860
|
path: ["currency"]
|
|
768
861
|
};
|
|
769
|
-
var
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
});
|
|
773
|
-
|
|
862
|
+
var slugMessage = (key) => ({
|
|
863
|
+
message: `\`${key}\` must be a valid collection slug (alphanumeric / hyphen / underscore, no path separators)`,
|
|
864
|
+
path: [key]
|
|
865
|
+
});
|
|
866
|
+
/** The plain scalar field types. Stored and edited as primitive values; no
|
|
867
|
+
* variant-specific keys.
|
|
868
|
+
* - `image`: a workspace-relative image path (e.g. a `data/attachments/...`
|
|
869
|
+
* upload); rendered as an <img> in the detail view (not the list table —
|
|
870
|
+
* a per-row fetch is too expensive at scale). Stored as a plain string.
|
|
871
|
+
* - `file`: a workspace-relative file path as a plain string (e.g. an
|
|
872
|
+
* `artifacts/html/<name>.html` app). Rendered as a clickable link in both
|
|
873
|
+
* the list table and the detail view: HTML / SVG artifacts open their
|
|
874
|
+
* rendered form in a new tab; any other path opens in the File Explorer. */
|
|
875
|
+
var ScalarFieldZ = zod.z.object({
|
|
774
876
|
type: zod.z.enum([
|
|
775
877
|
"string",
|
|
776
878
|
"text",
|
|
@@ -780,18 +882,39 @@ var SubFieldSpecSchema = zod.z.object({
|
|
|
780
882
|
"datetime",
|
|
781
883
|
"boolean",
|
|
782
884
|
"markdown",
|
|
783
|
-
"
|
|
784
|
-
"
|
|
785
|
-
"enum"
|
|
885
|
+
"image",
|
|
886
|
+
"file"
|
|
786
887
|
]),
|
|
888
|
+
...fieldBase
|
|
889
|
+
});
|
|
890
|
+
/** A link to another collection: the record stores the target item's
|
|
891
|
+
* primary-key slug and the host renders a clickable link + dropdown picker.
|
|
892
|
+
* `to` must be a real slug (not `../foo`, not `mc-clients/extra` — see
|
|
893
|
+
* Codex P2 on PR #1495); whether the target collection exists resolves
|
|
894
|
+
* fail-soft at render time, never here. */
|
|
895
|
+
var RefFieldZ = zod.z.object({
|
|
896
|
+
type: zod.z.literal("ref"),
|
|
897
|
+
...fieldBase,
|
|
898
|
+
to: zod.z.string().min(1)
|
|
899
|
+
}).refine((spec) => require_calendarGrid.isSafeSlug(spec.to), slugMessage("to"));
|
|
900
|
+
/** A money amount. See `currencyKeys` for the currency-source contract. */
|
|
901
|
+
var MoneyFieldZ = zod.z.object({
|
|
902
|
+
type: zod.z.literal("money"),
|
|
903
|
+
...fieldBase,
|
|
904
|
+
...currencyKeys
|
|
905
|
+
}).refine(hasCurrencySource, currencyMessage);
|
|
906
|
+
/** A closed set of allowed string values. The form renders a `<select>`
|
|
907
|
+
* populated from `values`; storage is a plain string. */
|
|
908
|
+
var EnumFieldZ = zod.z.object({
|
|
909
|
+
type: zod.z.literal("enum"),
|
|
910
|
+
...fieldBase,
|
|
911
|
+
values: zod.z.array(zod.z.string().trim().min(1)).min(1)
|
|
912
|
+
});
|
|
913
|
+
var subFieldBase = {
|
|
787
914
|
label: zod.z.string().min(1),
|
|
788
|
-
required: zod.z.boolean().optional()
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
currencyField: zod.z.string().trim().min(1).optional(),
|
|
792
|
-
values: zod.z.array(zod.z.string().trim().min(1)).min(1).optional()
|
|
793
|
-
}).refine(refRefine, refMessage).refine(enumRefine, enumMessage).refine(currencyRefine, currencyMessage);
|
|
794
|
-
var FieldSpecSchema = zod.z.object({
|
|
915
|
+
required: zod.z.boolean().optional()
|
|
916
|
+
};
|
|
917
|
+
var SubScalarFieldZ = zod.z.object({
|
|
795
918
|
type: zod.z.enum([
|
|
796
919
|
"string",
|
|
797
920
|
"text",
|
|
@@ -800,62 +923,126 @@ var FieldSpecSchema = zod.z.object({
|
|
|
800
923
|
"date",
|
|
801
924
|
"datetime",
|
|
802
925
|
"boolean",
|
|
803
|
-
"markdown"
|
|
804
|
-
"ref",
|
|
805
|
-
"money",
|
|
806
|
-
"enum",
|
|
807
|
-
"table",
|
|
808
|
-
"derived",
|
|
809
|
-
"embed",
|
|
810
|
-
"image",
|
|
811
|
-
"file",
|
|
812
|
-
"toggle"
|
|
926
|
+
"markdown"
|
|
813
927
|
]),
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
928
|
+
...subFieldBase
|
|
929
|
+
});
|
|
930
|
+
var SubRefFieldZ = zod.z.object({
|
|
931
|
+
type: zod.z.literal("ref"),
|
|
932
|
+
...subFieldBase,
|
|
933
|
+
to: zod.z.string().min(1)
|
|
934
|
+
}).refine((spec) => require_calendarGrid.isSafeSlug(spec.to), slugMessage("to"));
|
|
935
|
+
var SubMoneyFieldZ = zod.z.object({
|
|
936
|
+
type: zod.z.literal("money"),
|
|
937
|
+
...subFieldBase,
|
|
938
|
+
...currencyKeys
|
|
939
|
+
}).refine(hasCurrencySource, currencyMessage);
|
|
940
|
+
var SubEnumFieldZ = zod.z.object({
|
|
941
|
+
type: zod.z.literal("enum"),
|
|
942
|
+
...subFieldBase,
|
|
943
|
+
values: zod.z.array(zod.z.string().trim().min(1)).min(1)
|
|
944
|
+
});
|
|
945
|
+
var SubFieldSpecZ = zod.z.discriminatedUnion("type", [
|
|
946
|
+
SubScalarFieldZ,
|
|
947
|
+
SubRefFieldZ,
|
|
948
|
+
SubMoneyFieldZ,
|
|
949
|
+
SubEnumFieldZ
|
|
950
|
+
]);
|
|
951
|
+
/** A flat sub-table: each row is a record of `of`'s sub-schema (insertion
|
|
952
|
+
* order = column order). v0 disallows nested tables and derived columns to
|
|
953
|
+
* keep the editor + evaluator simple. */
|
|
954
|
+
var TableFieldZ = zod.z.object({
|
|
955
|
+
type: zod.z.literal("table"),
|
|
956
|
+
...fieldBase,
|
|
957
|
+
of: zod.z.record(zod.z.string(), SubFieldSpecZ)
|
|
958
|
+
}).refine((spec) => Object.keys(spec.of).length > 0, {
|
|
959
|
+
message: "fields with type 'table' must declare a non-empty `of` (sub-schema for each row)",
|
|
960
|
+
path: ["of"]
|
|
961
|
+
});
|
|
962
|
+
/** A computed scalar: `formula` is a tiny expression evaluated against the
|
|
963
|
+
* record — `+ - * /`, parens, identifier refs to top-level fields,
|
|
964
|
+
* `sum(tableField[].col)`, and `sum(tableField[].col * tableField[].col)`
|
|
965
|
+
* (see `./derivedFormula`). `display` picks the inner type the value renders
|
|
966
|
+
* as (default `"number"`) — restricted to the non-composite display targets,
|
|
967
|
+
* since a derived value is a scalar. Never stored; computed by `deriveAll`
|
|
968
|
+
* on both server and client. */
|
|
969
|
+
var DerivedFieldZ = zod.z.object({
|
|
970
|
+
type: zod.z.literal("derived"),
|
|
971
|
+
...fieldBase,
|
|
972
|
+
formula: zod.z.string().trim().min(1),
|
|
832
973
|
display: zod.z.enum([
|
|
833
974
|
"string",
|
|
834
975
|
"number",
|
|
835
976
|
"money",
|
|
836
977
|
"date"
|
|
837
978
|
]).optional(),
|
|
838
|
-
|
|
839
|
-
}).refine(
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
979
|
+
...currencyKeys
|
|
980
|
+
}).refine((spec) => spec.display !== "money" || hasCurrencySource(spec), currencyMessage);
|
|
981
|
+
/** Pulls a record from another collection into the read-only detail view.
|
|
982
|
+
* Display-only — nothing is stored on this record, so it never appears in
|
|
983
|
+
* the list table or the edit form. Must declare a valid `to` slug (same
|
|
984
|
+
* path-traversal guard as `ref`) AND exactly one of `id` (a fixed target
|
|
985
|
+
* record, e.g. `me` for the singleton profile — same for every record) or
|
|
986
|
+
* `idField` (a sibling top-level field naming the per-record target, e.g.
|
|
987
|
+
* an invoice's `issuerId` selecting which profile to embed as the bill-from
|
|
988
|
+
* block; an absent/empty value resolves fail-soft to "no record"). The
|
|
989
|
+
* `idField` target is validated to be a real `ref`/`string` field by a
|
|
990
|
+
* schema-level refine below. */
|
|
991
|
+
var EmbedFieldZ = zod.z.object({
|
|
992
|
+
type: zod.z.literal("embed"),
|
|
993
|
+
...fieldBase,
|
|
994
|
+
to: zod.z.string().min(1),
|
|
995
|
+
id: zod.z.string().trim().min(1).optional(),
|
|
996
|
+
idField: zod.z.string().trim().min(1).optional()
|
|
997
|
+
}).refine((spec) => require_calendarGrid.isSafeSlug(spec.to) && spec.id !== void 0 !== (spec.idField !== void 0), {
|
|
998
|
+
message: "fields with type 'embed' must declare a `to` (valid collection slug) and exactly one of `id` (a fixed record's primary key) or `idField` (a sibling field naming the per-record target)",
|
|
999
|
+
path: ["id"]
|
|
1000
|
+
});
|
|
1001
|
+
/** A checkbox that is a pure PROJECTION of an `enum` field — it stores
|
|
1002
|
+
* nothing of its own. Checked when the enum named by `field` equals
|
|
1003
|
+
* `onValue`; toggling writes `onValue` / `offValue` back to that enum
|
|
1004
|
+
* field. Lets a "done" checkbox front a kanban `status` field with the enum
|
|
1005
|
+
* as the single source of truth (no separate stored boolean to keep in
|
|
1006
|
+
* sync). `field` / `onValue` / `offValue` are validated against the target
|
|
1007
|
+
* enum's `values` by a schema-level refine below. */
|
|
1008
|
+
var ToggleFieldZ = zod.z.object({
|
|
1009
|
+
type: zod.z.literal("toggle"),
|
|
1010
|
+
...fieldBase,
|
|
1011
|
+
field: zod.z.string().trim().min(1),
|
|
1012
|
+
onValue: zod.z.string().trim().min(1),
|
|
1013
|
+
offValue: zod.z.string().trim().min(1)
|
|
1014
|
+
});
|
|
1015
|
+
var FieldSpecZ = zod.z.discriminatedUnion("type", [
|
|
1016
|
+
ScalarFieldZ,
|
|
1017
|
+
RefFieldZ,
|
|
1018
|
+
MoneyFieldZ,
|
|
1019
|
+
EnumFieldZ,
|
|
1020
|
+
TableFieldZ,
|
|
1021
|
+
DerivedFieldZ,
|
|
1022
|
+
EmbedFieldZ,
|
|
1023
|
+
ToggleFieldZ
|
|
1024
|
+
]);
|
|
1025
|
+
/** A schema-declared record action, rendered as a button in the read-only
|
|
1026
|
+
* detail view. Domain-free: the host validates the shape; the meaning
|
|
1027
|
+
* (which role, which template) is data. v1 ships only `kind: "chat"` —
|
|
1028
|
+
* start a new chat in `role` with a templated seed prompt; the enum
|
|
1029
|
+
* reserves room for a future `"mutate"` (status transitions) without
|
|
1030
|
+
* another schema-shape change. */
|
|
1031
|
+
var ActionSpecZ = zod.z.object({
|
|
850
1032
|
id: zod.z.string().trim().min(1),
|
|
851
1033
|
label: zod.z.string().trim().min(1),
|
|
852
1034
|
icon: zod.z.string().trim().min(1).optional(),
|
|
853
1035
|
kind: zod.z.enum(["chat"]),
|
|
854
1036
|
role: zod.z.string().trim().min(1),
|
|
855
1037
|
template: zod.z.string().trim().min(1).refine(require_collection_paths.isSafeActionTemplatePath, "must be a safe path under `templates/` (e.g. `templates/invoice.md`; no `..`, no leading `/`, no backslash)"),
|
|
856
|
-
when:
|
|
857
|
-
});
|
|
858
|
-
|
|
1038
|
+
when: WhenZ.optional()
|
|
1039
|
+
});
|
|
1040
|
+
/** A custom (LLM-authored) HTML view registration. Domain-free: the host
|
|
1041
|
+
* validates the shape; the view's behaviour lives in the HTML file. `file`
|
|
1042
|
+
* is constrained to `views/*.html` (path-safe) so the view-file reader can
|
|
1043
|
+
* never reach the data folder or the schema/template files. `id` is
|
|
1044
|
+
* validated to be a real slug + unique by schema-level refines below. */
|
|
1045
|
+
var CustomViewZ = zod.z.object({
|
|
859
1046
|
id: zod.z.string().trim().min(1),
|
|
860
1047
|
label: zod.z.string().trim().min(1),
|
|
861
1048
|
icon: zod.z.string().trim().min(1).optional(),
|
|
@@ -868,7 +1055,14 @@ var CustomViewSchema = zod.z.object({
|
|
|
868
1055
|
imageFields: zod.z.array(zod.z.string().trim().min(1)).optional(),
|
|
869
1056
|
imageMaxEdge: zod.z.number().int().min(1).optional()
|
|
870
1057
|
});
|
|
871
|
-
|
|
1058
|
+
/** Recurrence advance for `spawn.every`. `interval` is a positive integer
|
|
1059
|
+
* count of `unit`s (`interval: 3` + `unit: "month"` = quarterly);
|
|
1060
|
+
* `dayOfMonth` (month/year only) is the CANONICAL day-of-month anchor
|
|
1061
|
+
* (1-31, read from the rule and clamped per-month at compute time so "31st
|
|
1062
|
+
* of every month" never drifts) or the `"last"` sentinel for end-of-month.
|
|
1063
|
+
* `.strict()` so the union below cleanly rejects an object carrying BOTH
|
|
1064
|
+
* `unit` and `fromField` (it fails this arm on the unknown `fromField`). */
|
|
1065
|
+
var EveryLiteralZ = zod.z.object({
|
|
872
1066
|
unit: zod.z.enum([
|
|
873
1067
|
"day",
|
|
874
1068
|
"week",
|
|
@@ -878,17 +1072,114 @@ var EveryLiteralSchema = zod.z.object({
|
|
|
878
1072
|
interval: zod.z.number().int().min(1),
|
|
879
1073
|
dayOfMonth: zod.z.union([zod.z.number().int().min(1).max(31), zod.z.literal("last")]).optional()
|
|
880
1074
|
}).strict();
|
|
881
|
-
|
|
1075
|
+
/** Field-driven recurrence: pick the interval per-record by an `enum`
|
|
1076
|
+
* field's value — one collection can mix daily / weekly / monthly
|
|
1077
|
+
* obligations in a single list. `map` keys are validated to exactly cover
|
|
1078
|
+
* that field's `values` by a `CollectionSchemaZ` refine (which can see the
|
|
1079
|
+
* sibling `fields`); here each map value just has to be a well-formed
|
|
1080
|
+
* literal `every`. `.strict()` mirrors the literal arm so a both-keys
|
|
1081
|
+
* object fails this arm too. */
|
|
1082
|
+
var EveryFieldDrivenZ = zod.z.object({
|
|
882
1083
|
fromField: zod.z.string().trim().min(1),
|
|
883
|
-
map: zod.z.record(zod.z.string(),
|
|
1084
|
+
map: zod.z.record(zod.z.string(), EveryLiteralZ)
|
|
884
1085
|
}).strict();
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1086
|
+
/** Either a single literal interval (applied to every record) or the
|
|
1087
|
+
* field-driven map. Two `.strict()` arms mean "both keys" and "neither
|
|
1088
|
+
* key" both fail validation, with no extra refine. */
|
|
1089
|
+
var EveryZ = zod.z.union([EveryLiteralZ, EveryFieldDrivenZ]);
|
|
1090
|
+
/** Host-driven recurrence: when a record satisfies `when` (default:
|
|
1091
|
+
* "`completionField` value ∈ `completionDoneValues`"), the host creates the
|
|
1092
|
+
* next record with a forward-advanced `triggerField` date. `carry` copies
|
|
1093
|
+
* record fields verbatim onto the successor; `set` forces fixed values
|
|
1094
|
+
* (typically resetting the status field to its pending value). The
|
|
1095
|
+
* successor's id and contents are a pure function of (source record, this
|
|
1096
|
+
* rule); creation is create-if-absent, so the mechanism stays convergent. */
|
|
1097
|
+
var SpawnZ = zod.z.object({
|
|
1098
|
+
when: WhenZ.optional(),
|
|
1099
|
+
every: EveryZ,
|
|
889
1100
|
carry: zod.z.array(zod.z.string().trim().min(1)).optional(),
|
|
890
1101
|
set: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
891
1102
|
});
|
|
1103
|
+
/** Declarative retrieval config for a Feed (a collection that refills itself
|
|
1104
|
+
* from the internet). `http-json` needs `itemsAt` (a path to the items
|
|
1105
|
+
* array) only when the response body isn't itself the array; rss/atom yield
|
|
1106
|
+
* items natively and ignore it — so no kind-specific requirement here. */
|
|
1107
|
+
var DeclarativeIngestZ = zod.z.object({
|
|
1108
|
+
kind: zod.z.enum(require_calendarGrid.INGEST_KINDS),
|
|
1109
|
+
url: zod.z.string().url(),
|
|
1110
|
+
schedule: zod.z.enum(require_calendarGrid.FEED_SCHEDULES),
|
|
1111
|
+
atHour: zod.z.number().int().min(0).max(23).optional(),
|
|
1112
|
+
itemsAt: zod.z.string().trim().min(1).optional(),
|
|
1113
|
+
map: zod.z.record(zod.z.string().trim().min(1), zod.z.string().trim().min(1)),
|
|
1114
|
+
idFrom: zod.z.string().trim().min(1).optional(),
|
|
1115
|
+
maxItems: zod.z.number().int().min(0).optional()
|
|
1116
|
+
});
|
|
1117
|
+
/** Agent-performed retrieval. Valid on any collection (the primary consumer
|
|
1118
|
+
* is skill-backed collections — feeds keep their declarative kinds). No
|
|
1119
|
+
* `url`/`map`: the worker owns retrieval and record shape, seeded by
|
|
1120
|
+
* `template` + a summary of every record, run in `role`. `template` is
|
|
1121
|
+
* validated the SAME way an action's template is (safe path under
|
|
1122
|
+
* `templates/`), so the skill-bridge mirrors it identically. */
|
|
1123
|
+
var AgentIngestZ = zod.z.object({
|
|
1124
|
+
kind: zod.z.literal(require_calendarGrid.AGENT_INGEST_KIND),
|
|
1125
|
+
schedule: zod.z.enum(require_calendarGrid.FEED_SCHEDULES),
|
|
1126
|
+
atHour: zod.z.number().int().min(0).max(23).optional(),
|
|
1127
|
+
role: zod.z.string().trim().min(1),
|
|
1128
|
+
template: zod.z.string().trim().min(1).refine(require_collection_paths.isSafeActionTemplatePath, "must be a safe path under `templates/` (e.g. `templates/refresh.md`; no `..`, no leading `/`, no backslash)")
|
|
1129
|
+
});
|
|
1130
|
+
/** `ingest` is a discriminated union on `kind`: the three declarative
|
|
1131
|
+
* retrievers fetch-and-map; `agent` dispatches a hidden worker. Optional on
|
|
1132
|
+
* every schema — skill-backed collections usually omit it; only feeds
|
|
1133
|
+
* discovered from `<workspace>/feeds/` are REQUIRED to carry it (gated by
|
|
1134
|
+
* `acceptParsedSchema`). */
|
|
1135
|
+
var IngestZ = zod.z.discriminatedUnion("kind", [DeclarativeIngestZ, AgentIngestZ]);
|
|
1136
|
+
var ValueRefZ = zod.z.object({
|
|
1137
|
+
record: zod.z.string().trim().min(1).optional(),
|
|
1138
|
+
field: zod.z.string().trim().min(1)
|
|
1139
|
+
});
|
|
1140
|
+
var WhereCondZ = zod.z.object({
|
|
1141
|
+
field: zod.z.string().trim().min(1),
|
|
1142
|
+
op: zod.z.enum([
|
|
1143
|
+
"eq",
|
|
1144
|
+
"ne",
|
|
1145
|
+
"in",
|
|
1146
|
+
"gt",
|
|
1147
|
+
"gte",
|
|
1148
|
+
"lt",
|
|
1149
|
+
"lte",
|
|
1150
|
+
"contains"
|
|
1151
|
+
]),
|
|
1152
|
+
value: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]).optional(),
|
|
1153
|
+
valueFrom: ValueRefZ.optional()
|
|
1154
|
+
}).refine((cond) => cond.value !== void 0 !== (cond.valueFrom !== void 0), {
|
|
1155
|
+
message: "a where condition must declare exactly one of `value` (a literal) or `valueFrom` (a reference to another record's field), never both or neither",
|
|
1156
|
+
path: ["value"]
|
|
1157
|
+
}).refine((cond) => cond.value === void 0 || cond.op === "in" === Array.isArray(cond.value), {
|
|
1158
|
+
message: "`in` requires an array `value` (the allowed set); every other op requires a single string `value`",
|
|
1159
|
+
path: ["value"]
|
|
1160
|
+
});
|
|
1161
|
+
var WhereZ = zod.z.array(WhereCondZ);
|
|
1162
|
+
var DynamicIconSourceZ = zod.z.object({
|
|
1163
|
+
collection: zod.z.string().trim().min(1),
|
|
1164
|
+
from: zod.z.enum([
|
|
1165
|
+
"latest",
|
|
1166
|
+
"first",
|
|
1167
|
+
"when"
|
|
1168
|
+
]).optional(),
|
|
1169
|
+
orderBy: zod.z.string().trim().min(1).optional(),
|
|
1170
|
+
where: WhereZ.optional()
|
|
1171
|
+
});
|
|
1172
|
+
var DynamicIconRuleZ = zod.z.object({
|
|
1173
|
+
where: WhereZ,
|
|
1174
|
+
icon: zod.z.string().trim().min(1)
|
|
1175
|
+
});
|
|
1176
|
+
var DynamicIconSpecZ = zod.z.object({
|
|
1177
|
+
source: DynamicIconSourceZ,
|
|
1178
|
+
rules: zod.z.array(DynamicIconRuleZ),
|
|
1179
|
+
fallback: zod.z.string().trim().min(1).optional()
|
|
1180
|
+
});
|
|
1181
|
+
var isDateLike = (type) => type === "date" || type === "datetime";
|
|
1182
|
+
var isTimeStringField = (type) => type === "string" || type === "text";
|
|
892
1183
|
var CODE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
893
1184
|
"string",
|
|
894
1185
|
"text",
|
|
@@ -926,7 +1217,7 @@ function spawnSuccessorStartsInert(schema) {
|
|
|
926
1217
|
}
|
|
927
1218
|
function fieldDrivenSpawnEvery(schema) {
|
|
928
1219
|
const every = schema.spawn?.every;
|
|
929
|
-
if (!every || !
|
|
1220
|
+
if (!every || !("fromField" in every)) return null;
|
|
930
1221
|
return every;
|
|
931
1222
|
}
|
|
932
1223
|
function fieldDrivenFromFieldIsEnum(schema) {
|
|
@@ -954,93 +1245,30 @@ function fieldDrivenFromFieldCarried(schema) {
|
|
|
954
1245
|
}
|
|
955
1246
|
return (carry ?? []).includes(driven.fromField);
|
|
956
1247
|
}
|
|
957
|
-
var DeclarativeIngestZ = zod.z.object({
|
|
958
|
-
kind: zod.z.enum(require_deriveAll.INGEST_KINDS),
|
|
959
|
-
url: zod.z.string().url(),
|
|
960
|
-
schedule: zod.z.enum(require_deriveAll.FEED_SCHEDULES),
|
|
961
|
-
atHour: zod.z.number().int().min(0).max(23).optional(),
|
|
962
|
-
itemsAt: zod.z.string().trim().min(1).optional(),
|
|
963
|
-
map: zod.z.record(zod.z.string().trim().min(1), zod.z.string().trim().min(1)),
|
|
964
|
-
idFrom: zod.z.string().trim().min(1).optional(),
|
|
965
|
-
maxItems: zod.z.number().int().min(0).optional()
|
|
966
|
-
});
|
|
967
|
-
var AgentIngestZ = zod.z.object({
|
|
968
|
-
kind: zod.z.literal(require_deriveAll.AGENT_INGEST_KIND),
|
|
969
|
-
schedule: zod.z.enum(require_deriveAll.FEED_SCHEDULES),
|
|
970
|
-
atHour: zod.z.number().int().min(0).max(23).optional(),
|
|
971
|
-
role: zod.z.string().trim().min(1),
|
|
972
|
-
template: zod.z.string().trim().min(1).refine(require_collection_paths.isSafeActionTemplatePath, "must be a safe path under `templates/` (e.g. `templates/refresh.md`; no `..`, no leading `/`, no backslash)")
|
|
973
|
-
});
|
|
974
|
-
var IngestSchemaZ = zod.z.discriminatedUnion("kind", [DeclarativeIngestZ, AgentIngestZ]);
|
|
975
|
-
var ValueRefZ = zod.z.object({
|
|
976
|
-
record: zod.z.string().trim().min(1).optional(),
|
|
977
|
-
field: zod.z.string().trim().min(1)
|
|
978
|
-
});
|
|
979
|
-
var WhereCondZ = zod.z.object({
|
|
980
|
-
field: zod.z.string().trim().min(1),
|
|
981
|
-
op: zod.z.enum([
|
|
982
|
-
"eq",
|
|
983
|
-
"ne",
|
|
984
|
-
"in",
|
|
985
|
-
"gt",
|
|
986
|
-
"gte",
|
|
987
|
-
"lt",
|
|
988
|
-
"lte",
|
|
989
|
-
"contains"
|
|
990
|
-
]),
|
|
991
|
-
value: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]).optional(),
|
|
992
|
-
valueFrom: ValueRefZ.optional()
|
|
993
|
-
}).refine((cond) => cond.value !== void 0 !== (cond.valueFrom !== void 0), {
|
|
994
|
-
message: "a where condition must declare exactly one of `value` (a literal) or `valueFrom` (a reference to another record's field), never both or neither",
|
|
995
|
-
path: ["value"]
|
|
996
|
-
}).refine((cond) => cond.value === void 0 || cond.op === "in" === Array.isArray(cond.value), {
|
|
997
|
-
message: "`in` requires an array `value` (the allowed set); every other op requires a single string `value`",
|
|
998
|
-
path: ["value"]
|
|
999
|
-
});
|
|
1000
|
-
var WhereZ = zod.z.array(WhereCondZ);
|
|
1001
|
-
var DynamicIconSourceZ = zod.z.object({
|
|
1002
|
-
collection: zod.z.string().trim().min(1),
|
|
1003
|
-
from: zod.z.enum([
|
|
1004
|
-
"latest",
|
|
1005
|
-
"first",
|
|
1006
|
-
"when"
|
|
1007
|
-
]).optional(),
|
|
1008
|
-
orderBy: zod.z.string().trim().min(1).optional(),
|
|
1009
|
-
where: WhereZ.optional()
|
|
1010
|
-
});
|
|
1011
|
-
var DynamicIconRuleZ = zod.z.object({
|
|
1012
|
-
where: WhereZ,
|
|
1013
|
-
icon: zod.z.string().trim().min(1)
|
|
1014
|
-
});
|
|
1015
|
-
var DynamicIconSpecZ = zod.z.object({
|
|
1016
|
-
source: DynamicIconSourceZ,
|
|
1017
|
-
rules: zod.z.array(DynamicIconRuleZ),
|
|
1018
|
-
fallback: zod.z.string().trim().min(1).optional()
|
|
1019
|
-
});
|
|
1020
1248
|
var CollectionSchemaZ = zod.z.object({
|
|
1021
1249
|
title: zod.z.string().min(1),
|
|
1022
1250
|
icon: zod.z.string().min(1),
|
|
1023
1251
|
dataPath: zod.z.string().min(1),
|
|
1024
1252
|
primaryKey: zod.z.string().min(1),
|
|
1025
1253
|
singleton: zod.z.string().trim().min(1).optional(),
|
|
1026
|
-
fields: zod.z.record(zod.z.string(),
|
|
1027
|
-
actions: zod.z.array(
|
|
1028
|
-
collectionActions: zod.z.array(
|
|
1254
|
+
fields: zod.z.record(zod.z.string(), FieldSpecZ),
|
|
1255
|
+
actions: zod.z.array(ActionSpecZ).optional(),
|
|
1256
|
+
collectionActions: zod.z.array(ActionSpecZ).optional(),
|
|
1029
1257
|
completionField: zod.z.string().trim().min(1).optional(),
|
|
1030
1258
|
completionDoneValues: zod.z.array(zod.z.string().trim().min(1)).min(1).optional(),
|
|
1031
1259
|
displayField: zod.z.string().trim().min(1).optional(),
|
|
1032
1260
|
triggerField: zod.z.string().trim().min(1).optional(),
|
|
1033
1261
|
triggerLeadDays: zod.z.number().int().min(0).optional(),
|
|
1034
|
-
spawn:
|
|
1262
|
+
spawn: SpawnZ.optional(),
|
|
1035
1263
|
calendarField: zod.z.string().trim().min(1).optional(),
|
|
1036
1264
|
calendarEndField: zod.z.string().trim().min(1).optional(),
|
|
1037
1265
|
calendarTimeField: zod.z.string().trim().min(1).optional(),
|
|
1038
1266
|
kanbanField: zod.z.string().trim().min(1).optional(),
|
|
1039
|
-
views: zod.z.array(
|
|
1040
|
-
notifyWhen:
|
|
1041
|
-
ingest:
|
|
1267
|
+
views: zod.z.array(CustomViewZ).optional(),
|
|
1268
|
+
notifyWhen: WhenZ.optional(),
|
|
1269
|
+
ingest: IngestZ.optional(),
|
|
1042
1270
|
dynamicIcon: DynamicIconSpecZ.optional()
|
|
1043
|
-
}).refine((schema) => schema.singleton === void 0 ||
|
|
1271
|
+
}).refine((schema) => schema.singleton === void 0 || require_calendarGrid.isSafeRecordId(schema.singleton), {
|
|
1044
1272
|
message: "schema `singleton` must be a valid item id (alphanumeric / hyphen / underscore / interior dot, no `..` or path separators)",
|
|
1045
1273
|
path: ["singleton"]
|
|
1046
1274
|
}).refine((schema) => schema.actions === void 0 || new Set(schema.actions.map((action) => action.id)).size === schema.actions.length, {
|
|
@@ -1131,13 +1359,15 @@ var CollectionSchemaZ = zod.z.object({
|
|
|
1131
1359
|
}).refine((schema) => schema.notifyWhen === void 0 || schema.fields[schema.notifyWhen.field] !== void 0, {
|
|
1132
1360
|
message: "schema `notifyWhen.field` must name a top-level field declared in `fields`",
|
|
1133
1361
|
path: ["notifyWhen"]
|
|
1134
|
-
}).refine((schema) => schema.views === void 0 || schema.views.every((view) =>
|
|
1362
|
+
}).refine((schema) => schema.views === void 0 || schema.views.every((view) => require_calendarGrid.isSafeSlug(view.id)), {
|
|
1135
1363
|
message: "every `views[].id` must be a valid slug (alphanumeric / hyphen / underscore, no path separators)",
|
|
1136
1364
|
path: ["views"]
|
|
1137
1365
|
}).refine((schema) => schema.views === void 0 || new Set(schema.views.map((view) => view.id)).size === schema.views.length, {
|
|
1138
1366
|
message: "schema `views` must have unique `id`s",
|
|
1139
1367
|
path: ["views"]
|
|
1140
1368
|
});
|
|
1369
|
+
//#endregion
|
|
1370
|
+
//#region src/collection/server/discovery.ts
|
|
1141
1371
|
function applyFeedSchemaDefaults(parsed, slug) {
|
|
1142
1372
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
|
|
1143
1373
|
const obj = parsed;
|
|
@@ -1349,7 +1579,7 @@ async function loadTarget(slug, opts) {
|
|
|
1349
1579
|
const byId = {};
|
|
1350
1580
|
for (const item of items) {
|
|
1351
1581
|
const itemId = item[target.schema.primaryKey];
|
|
1352
|
-
if (typeof itemId === "string" && itemId.length > 0) byId[itemId] =
|
|
1582
|
+
if (typeof itemId === "string" && itemId.length > 0) byId[itemId] = require_calendarGrid.deriveAll(target.schema, item, {});
|
|
1353
1583
|
}
|
|
1354
1584
|
return {
|
|
1355
1585
|
schema: target.schema,
|
|
@@ -1378,7 +1608,7 @@ function projectComputed(schema, enriched, linked) {
|
|
|
1378
1608
|
for (const [key, field] of Object.entries(schema.fields)) {
|
|
1379
1609
|
if (field.type === "toggle" && field.field) enriched[key] = String(enriched[field.field] ?? "") === field.onValue;
|
|
1380
1610
|
if (field.type === "embed" && field.to) {
|
|
1381
|
-
const targetId =
|
|
1611
|
+
const targetId = require_calendarGrid.embedTargetId(field, enriched);
|
|
1382
1612
|
enriched[key] = targetId && linked[field.to]?.byId[targetId] || null;
|
|
1383
1613
|
}
|
|
1384
1614
|
}
|
|
@@ -1392,7 +1622,7 @@ async function enrichItems(collection, items, opts = {}) {
|
|
|
1392
1622
|
const { schema } = collection;
|
|
1393
1623
|
const linked = await loadLinkedTargets(schema, opts);
|
|
1394
1624
|
const refRecords = toRefRecords(linked);
|
|
1395
|
-
return items.map((item) => projectComputed(schema,
|
|
1625
|
+
return items.map((item) => projectComputed(schema, require_calendarGrid.deriveAll(schema, item, refRecords), linked));
|
|
1396
1626
|
}
|
|
1397
1627
|
//#endregion
|
|
1398
1628
|
//#region src/collection/server/dynamicIcon.ts
|
|
@@ -1428,9 +1658,9 @@ async function computeCollectionIcon(collection, opts = {}) {
|
|
|
1428
1658
|
const source = await loadCollection(spec.source.collection, opts);
|
|
1429
1659
|
if (!source) return spec.fallback ?? schema.icon;
|
|
1430
1660
|
const ordered = sortByPrimaryKey(await listItems(source.dataDir, { workspaceRoot: opts.workspaceRoot }), source.schema.primaryKey);
|
|
1431
|
-
const orderBy = spec.source.orderBy ??
|
|
1661
|
+
const orderBy = spec.source.orderBy ?? require_calendarGrid.firstDateField(source.schema);
|
|
1432
1662
|
const recordsById = buildRecordsById(ordered, source.schema.primaryKey);
|
|
1433
|
-
return
|
|
1663
|
+
return require_calendarGrid.resolveIcon(require_calendarGrid.selectDynamicRecord(ordered, spec.source, orderBy, recordsById), spec, schema.icon, recordsById);
|
|
1434
1664
|
} catch (err) {
|
|
1435
1665
|
log.warn("collections", "dynamic icon compute failed, falling back", {
|
|
1436
1666
|
slug: collection.slug,
|
|
@@ -1564,7 +1794,7 @@ function matchesWhen(when, schema, item) {
|
|
|
1564
1794
|
* Discovery rejects a map that doesn't cover the enum's values, so null
|
|
1565
1795
|
* here means a record that predates a map/enum edit. */
|
|
1566
1796
|
function resolveEvery(every, sourceItem) {
|
|
1567
|
-
if (!
|
|
1797
|
+
if (!require_calendarGrid.isFieldDrivenEvery(every)) return every;
|
|
1568
1798
|
const raw = sourceItem[every.fromField];
|
|
1569
1799
|
if (raw === void 0 || raw === null || raw === "") return null;
|
|
1570
1800
|
return every.map[String(raw)] ?? null;
|
|
@@ -1604,7 +1834,7 @@ function logSpawnSkip(slug, triggerField, every, sourceItem, sourceId) {
|
|
|
1604
1834
|
});
|
|
1605
1835
|
return;
|
|
1606
1836
|
}
|
|
1607
|
-
const fromField =
|
|
1837
|
+
const fromField = require_calendarGrid.isFieldDrivenEvery(every) ? every.fromField : void 0;
|
|
1608
1838
|
log.warn("collections", "spawn skipped: no `every` mapping for frequency value", {
|
|
1609
1839
|
slug,
|
|
1610
1840
|
sourceId,
|
|
@@ -1971,6 +2201,12 @@ Object.defineProperty(exports, "collectionsRegistriesConfigPath", {
|
|
|
1971
2201
|
return collectionsRegistriesConfigPath;
|
|
1972
2202
|
}
|
|
1973
2203
|
});
|
|
2204
|
+
Object.defineProperty(exports, "compileRecordZ", {
|
|
2205
|
+
enumerable: true,
|
|
2206
|
+
get: function() {
|
|
2207
|
+
return compileRecordZ;
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
1974
2210
|
Object.defineProperty(exports, "computeCollectionIcon", {
|
|
1975
2211
|
enumerable: true,
|
|
1976
2212
|
get: function() {
|
|
@@ -2145,6 +2381,12 @@ Object.defineProperty(exports, "readSkillTemplate", {
|
|
|
2145
2381
|
return readSkillTemplate;
|
|
2146
2382
|
}
|
|
2147
2383
|
});
|
|
2384
|
+
Object.defineProperty(exports, "recordFieldProblem", {
|
|
2385
|
+
enumerable: true,
|
|
2386
|
+
get: function() {
|
|
2387
|
+
return recordFieldProblem;
|
|
2388
|
+
}
|
|
2389
|
+
});
|
|
2148
2390
|
Object.defineProperty(exports, "resolveCreateItemId", {
|
|
2149
2391
|
enumerable: true,
|
|
2150
2392
|
get: function() {
|
|
@@ -2230,4 +2472,4 @@ Object.defineProperty(exports, "writeItem", {
|
|
|
2230
2472
|
}
|
|
2231
2473
|
});
|
|
2232
2474
|
|
|
2233
|
-
//# sourceMappingURL=server-
|
|
2475
|
+
//# sourceMappingURL=server-Cd5WdM8a.cjs.map
|