@mulmoclaude/core 0.14.0 → 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.
- package/assets/helps/collection-skills.md +27 -5
- package/dist/{calendarGrid-kO6rGfm0.js → calendarGrid-CaR-q36Y.js} +65 -2
- package/dist/calendarGrid-CaR-q36Y.js.map +1 -0
- package/dist/{calendarGrid-C1rfCXJ3.cjs → calendarGrid-gN4sei7s.cjs} +100 -1
- package/dist/calendarGrid-gN4sei7s.cjs.map +1 -0
- package/dist/collection/core/backlinks.d.ts +20 -0
- package/dist/collection/core/recordZ.d.ts +2 -4
- package/dist/collection/core/schema.d.ts +7 -0
- package/dist/collection/core/schemaZ.d.ts +32 -0
- package/dist/collection/core/uiTypes.d.ts +22 -0
- package/dist/collection/index.cjs +17 -36
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.d.ts +1 -0
- package/dist/collection/index.js +11 -33
- package/dist/collection/index.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/index.cjs +6 -2
- package/dist/collection/server/index.d.ts +1 -0
- package/dist/collection/server/index.js +3 -2
- package/dist/collection/server/io.d.ts +9 -0
- package/dist/collection/server/ontology.d.ts +35 -0
- package/dist/collection-watchers/index.cjs +5 -4
- package/dist/collection-watchers/index.cjs.map +1 -1
- package/dist/collection-watchers/index.js +3 -2
- package/dist/collection-watchers/index.js.map +1 -1
- package/dist/feeds/index.cjs +2 -2
- package/dist/feeds/index.js +2 -2
- package/dist/feeds/server/index.cjs +3 -3
- package/dist/feeds/server/index.js +3 -3
- package/dist/{ingestTypes-BIFXlw7M.cjs → ingestTypes-DG8RExWt.cjs} +2 -2
- package/dist/{ingestTypes-BIFXlw7M.cjs.map → ingestTypes-DG8RExWt.cjs.map} +1 -1
- package/dist/{ingestTypes-KuYPX9Ea.js → ingestTypes-V4upKeBx.js} +2 -2
- package/dist/{ingestTypes-KuYPX9Ea.js.map → ingestTypes-V4upKeBx.js.map} +1 -1
- package/dist/{server-Cc6XcyOA.js → server-BNTR7ySK.js} +121 -16
- package/dist/server-BNTR7ySK.js.map +1 -0
- package/dist/{server-Cd5WdM8a.cjs → server-bsNrf2yW.cjs} +139 -22
- package/dist/server-bsNrf2yW.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/calendarGrid-C1rfCXJ3.cjs.map +0 -1
- package/dist/calendarGrid-kO6rGfm0.js.map +0 -1
- package/dist/server-Cc6XcyOA.js.map +0 -1
- package/dist/server-Cd5WdM8a.cjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-D6vf50IK.cjs");
|
|
2
|
-
const require_calendarGrid = require("./calendarGrid-
|
|
2
|
+
const require_calendarGrid = require("./calendarGrid-gN4sei7s.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);
|
|
@@ -239,7 +239,9 @@ async function writeFileAtomic(filePath, content) {
|
|
|
239
239
|
* inside an otherwise-contained data dir — without this, a record
|
|
240
240
|
* file could symlink to /etc/passwd and the detail endpoint would
|
|
241
241
|
* happily serve it. Returns false on ENOENT and on any other lstat
|
|
242
|
-
* failure so the caller's "missing" branch covers those cases too.
|
|
242
|
+
* failure so the caller's "missing" branch covers those cases too.
|
|
243
|
+
* Exported so `ontology.ts`'s record COUNT classifies entries with the
|
|
244
|
+
* SAME lstat logic — the two must agree on what a record file is. */
|
|
243
245
|
async function isRegularFile(filePath) {
|
|
244
246
|
try {
|
|
245
247
|
return (await (0, node_fs_promises.lstat)(filePath)).isFile();
|
|
@@ -635,13 +637,6 @@ ${templateText}`;
|
|
|
635
637
|
}
|
|
636
638
|
//#endregion
|
|
637
639
|
//#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. */
|
|
640
|
-
var COMPUTED_TYPES = /* @__PURE__ */ new Set([
|
|
641
|
-
"derived",
|
|
642
|
-
"embed",
|
|
643
|
-
"toggle"
|
|
644
|
-
]);
|
|
645
640
|
/** The emptiness rule shared by `required` and the "only check present
|
|
646
641
|
* values" gate. NOT a truthiness check — `0` and `false` are filled. */
|
|
647
642
|
var isEmptyValue = (value) => value === void 0 || value === null || value === "";
|
|
@@ -720,7 +715,7 @@ var compiled = /* @__PURE__ */ new WeakMap();
|
|
|
720
715
|
function compileRecordZ(schema, tier) {
|
|
721
716
|
const cached = compiled.get(schema)?.[tier];
|
|
722
717
|
if (cached) return cached;
|
|
723
|
-
const stored = Object.entries(schema.fields).filter(([, spec]) => !COMPUTED_TYPES.has(spec.type));
|
|
718
|
+
const stored = Object.entries(schema.fields).filter(([, spec]) => !require_calendarGrid.COMPUTED_TYPES.has(spec.type));
|
|
724
719
|
const validator = zod.z.looseObject({}).superRefine((record, ctx) => {
|
|
725
720
|
for (const [key, spec] of stored) {
|
|
726
721
|
const problem = recordFieldProblem(key, spec, record[key], tier);
|
|
@@ -998,6 +993,24 @@ var EmbedFieldZ = zod.z.object({
|
|
|
998
993
|
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
994
|
path: ["id"]
|
|
1000
995
|
});
|
|
996
|
+
/** Display-only REVERSE refs (plan step ② of plans/collection-ontology.md):
|
|
997
|
+
* a read-only sub-table of the records in collection `from` whose `via`
|
|
998
|
+
* ref field stores THIS record's primary key. Stores nothing (joins
|
|
999
|
+
* `COMPUTED_TYPES`); resolution is shared server/client via
|
|
1000
|
+
* `core/backlinks.ts`. `display` names the `from` columns to show;
|
|
1001
|
+
* `filter` (the standard `when` shape, matched against each SOURCE
|
|
1002
|
+
* record) narrows the rows. Validation is shape-only, like `embed`:
|
|
1003
|
+
* `from` must be a safe slug, but whether it exists — and whether `via` /
|
|
1004
|
+
* `display` name real fields there — resolves fail-soft at render
|
|
1005
|
+
* (empty sub-table). Do NOT add cross-schema existence checks here. */
|
|
1006
|
+
var BacklinksFieldZ = zod.z.object({
|
|
1007
|
+
type: zod.z.literal("backlinks"),
|
|
1008
|
+
...fieldBase,
|
|
1009
|
+
from: zod.z.string().min(1),
|
|
1010
|
+
via: zod.z.string().trim().min(1),
|
|
1011
|
+
display: zod.z.array(zod.z.string().trim().min(1)).min(1),
|
|
1012
|
+
filter: WhenZ.optional()
|
|
1013
|
+
}).refine((spec) => require_calendarGrid.isSafeSlug(spec.from), slugMessage("from"));
|
|
1001
1014
|
/** A checkbox that is a pure PROJECTION of an `enum` field — it stores
|
|
1002
1015
|
* nothing of its own. Checked when the enum named by `field` equals
|
|
1003
1016
|
* `onValue`; toggling writes `onValue` / `offValue` back to that enum
|
|
@@ -1020,6 +1033,7 @@ var FieldSpecZ = zod.z.discriminatedUnion("type", [
|
|
|
1020
1033
|
TableFieldZ,
|
|
1021
1034
|
DerivedFieldZ,
|
|
1022
1035
|
EmbedFieldZ,
|
|
1036
|
+
BacklinksFieldZ,
|
|
1023
1037
|
ToggleFieldZ
|
|
1024
1038
|
]);
|
|
1025
1039
|
/** A schema-declared record action, rendered as a button in the read-only
|
|
@@ -1550,6 +1564,72 @@ function toDetail(collection) {
|
|
|
1550
1564
|
};
|
|
1551
1565
|
}
|
|
1552
1566
|
//#endregion
|
|
1567
|
+
//#region src/collection/server/ontology.ts
|
|
1568
|
+
/** Extract the relations a schema declares, in field declaration order:
|
|
1569
|
+
* top-level `ref` / `embed` / `backlinks` fields plus `ref` sub-fields
|
|
1570
|
+
* inside `table` columns. Pure — exported so the phase-2 graph panel
|
|
1571
|
+
* can reuse it on already-loaded schemas. */
|
|
1572
|
+
function schemaRelations(schema) {
|
|
1573
|
+
const relations = [];
|
|
1574
|
+
for (const [key, spec] of Object.entries(schema.fields)) {
|
|
1575
|
+
if (spec.type === "ref" || spec.type === "embed") relations.push({
|
|
1576
|
+
field: key,
|
|
1577
|
+
kind: spec.type,
|
|
1578
|
+
to: spec.to
|
|
1579
|
+
});
|
|
1580
|
+
if (spec.type === "backlinks") relations.push({
|
|
1581
|
+
field: key,
|
|
1582
|
+
kind: "backlinks",
|
|
1583
|
+
to: spec.from
|
|
1584
|
+
});
|
|
1585
|
+
if (spec.type !== "table") continue;
|
|
1586
|
+
for (const [subKey, subSpec] of Object.entries(spec.of)) if (subSpec.type === "ref") relations.push({
|
|
1587
|
+
field: `${key}.${subKey}`,
|
|
1588
|
+
kind: "ref",
|
|
1589
|
+
to: subSpec.to
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
return relations;
|
|
1593
|
+
}
|
|
1594
|
+
/** Count the record files in a collection's data dir — the same
|
|
1595
|
+
* `<id>.json` entries `listItems` considers, WITHOUT parsing them (the
|
|
1596
|
+
* ontology is a summary; a malformed record is still a record). Each
|
|
1597
|
+
* candidate is classified by the SHARED `isRegularFile` lstat helper —
|
|
1598
|
+
* the exact file-disclosure defense `listItems` applies — so a
|
|
1599
|
+
* symlinked record never counts and the count can't diverge from the
|
|
1600
|
+
* readable set on any filesystem (Codex review on PR #2099, twice).
|
|
1601
|
+
* Fail-soft: a missing dir or a dataDir escaping the workspace via
|
|
1602
|
+
* symlink counts 0. */
|
|
1603
|
+
async function countRecordFiles(dataDir, workspaceRoot) {
|
|
1604
|
+
if (!isContainedInRoot(dataDir, workspaceRoot)) return 0;
|
|
1605
|
+
try {
|
|
1606
|
+
const names = (await (0, node_fs_promises.readdir)(dataDir)).filter((name) => name.endsWith(".json") && !name.startsWith("."));
|
|
1607
|
+
return (await Promise.all(names.map((name) => isRegularFile(node_path.default.join(dataDir, name))))).filter(Boolean).length;
|
|
1608
|
+
} catch {
|
|
1609
|
+
return 0;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
async function toOntologyEntry(collection, workspaceRoot) {
|
|
1613
|
+
const { schema } = collection;
|
|
1614
|
+
return {
|
|
1615
|
+
slug: collection.slug,
|
|
1616
|
+
title: schema.title,
|
|
1617
|
+
icon: schema.icon,
|
|
1618
|
+
primaryKey: schema.primaryKey,
|
|
1619
|
+
displayField: schema.displayField ?? schema.primaryKey,
|
|
1620
|
+
recordCount: await countRecordFiles(collection.dataDir, workspaceRoot),
|
|
1621
|
+
relations: schemaRelations(schema)
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
/** Build the workspace ontology: every discovered collection (slug-sorted,
|
|
1625
|
+
* discovery's order), each with its outbound relations and a
|
|
1626
|
+
* readdir-cheap record count — fine to call on demand. */
|
|
1627
|
+
async function buildWorkspaceOntology(opts = {}) {
|
|
1628
|
+
const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();
|
|
1629
|
+
const collections = await discoverCollections(opts);
|
|
1630
|
+
return Promise.all(collections.map((collection) => toOntologyEntry(collection, workspaceRoot)));
|
|
1631
|
+
}
|
|
1632
|
+
//#endregion
|
|
1553
1633
|
//#region src/collection/server/derive.ts
|
|
1554
1634
|
/** Slugs of every collection referenced by a `ref` field — top-level
|
|
1555
1635
|
* and one level into `table` sub-fields (nested tables are
|
|
@@ -1572,6 +1652,13 @@ function uniqueEmbedTargets(schema) {
|
|
|
1572
1652
|
for (const field of Object.values(schema.fields)) if (field.type === "embed" && typeof field.to === "string" && field.to.length > 0) targets.add(field.to);
|
|
1573
1653
|
return [...targets];
|
|
1574
1654
|
}
|
|
1655
|
+
/** Slugs of every SOURCE collection a `backlinks` field reverses over —
|
|
1656
|
+
* loaded exactly like ref/embed targets (whole collection, once). */
|
|
1657
|
+
function uniqueBacklinkSources(schema) {
|
|
1658
|
+
const sources = /* @__PURE__ */ new Set();
|
|
1659
|
+
for (const field of Object.values(schema.fields)) if (field.type === "backlinks" && field.from.length > 0) sources.add(field.from);
|
|
1660
|
+
return [...sources];
|
|
1661
|
+
}
|
|
1575
1662
|
async function loadTarget(slug, opts) {
|
|
1576
1663
|
const target = await loadCollection(slug, opts);
|
|
1577
1664
|
if (!target) return null;
|
|
@@ -1586,11 +1673,16 @@ async function loadTarget(slug, opts) {
|
|
|
1586
1673
|
byId
|
|
1587
1674
|
};
|
|
1588
1675
|
}
|
|
1589
|
-
/** Load every ref/embed target collection once.
|
|
1590
|
-
* targets are simply absent — downstream derefs
|
|
1591
|
-
*
|
|
1676
|
+
/** Load every ref/embed target and backlink source collection once.
|
|
1677
|
+
* Unknown / unloadable targets are simply absent — downstream derefs
|
|
1678
|
+
* resolve to null (em-dash) and backlinks to an empty row set, the
|
|
1679
|
+
* same fail-soft the UI renders. */
|
|
1592
1680
|
async function loadLinkedTargets(schema, opts) {
|
|
1593
|
-
const slugs = [.../* @__PURE__ */ new Set([
|
|
1681
|
+
const slugs = [.../* @__PURE__ */ new Set([
|
|
1682
|
+
...uniqueRefTargets(schema),
|
|
1683
|
+
...uniqueEmbedTargets(schema),
|
|
1684
|
+
...uniqueBacklinkSources(schema)
|
|
1685
|
+
])];
|
|
1594
1686
|
const loaded = {};
|
|
1595
1687
|
for (const slug of slugs) {
|
|
1596
1688
|
const target = await loadTarget(slug, opts);
|
|
@@ -1601,9 +1693,21 @@ async function loadLinkedTargets(schema, opts) {
|
|
|
1601
1693
|
function toRefRecords(linked) {
|
|
1602
1694
|
return Object.fromEntries(Object.entries(linked).map(([slug, target]) => [slug, target.byId]));
|
|
1603
1695
|
}
|
|
1696
|
+
/** The matching source rows for one `backlinks` field, projected to the
|
|
1697
|
+
* source primaryKey + `display` columns — so getItems on a
|
|
1698
|
+
* heavily-referenced record stays a summary, not a dump of the source
|
|
1699
|
+
* collection. Missing source ⇒ [] (fail-soft). The rows come from the
|
|
1700
|
+
* DERIVED source records (`byId`), so `display`/`filter` on a derived
|
|
1701
|
+
* source column (an invoice `total`) works. */
|
|
1702
|
+
function projectBacklinks(field, schema, enriched, linked) {
|
|
1703
|
+
const source = linked[field.from];
|
|
1704
|
+
if (!source) return [];
|
|
1705
|
+
return require_calendarGrid.backlinkRows(field, String(enriched[schema.primaryKey] ?? ""), Object.values(source.byId)).map((row) => require_calendarGrid.projectBacklinkRow(row, field.display, source.schema.primaryKey));
|
|
1706
|
+
}
|
|
1604
1707
|
/** Project the computed (never-stored) field kinds onto one derived
|
|
1605
1708
|
* record: `toggle` → boolean off its enum, `embed` → the target record
|
|
1606
|
-
* (fixed `id` or per-record `idField`), or null when missing
|
|
1709
|
+
* (fixed `id` or per-record `idField`), or null when missing,
|
|
1710
|
+
* `backlinks` → the matching source rows (see `projectBacklinks`). */
|
|
1607
1711
|
function projectComputed(schema, enriched, linked) {
|
|
1608
1712
|
for (const [key, field] of Object.entries(schema.fields)) {
|
|
1609
1713
|
if (field.type === "toggle" && field.field) enriched[key] = String(enriched[field.field] ?? "") === field.onValue;
|
|
@@ -1611,6 +1715,7 @@ function projectComputed(schema, enriched, linked) {
|
|
|
1611
1715
|
const targetId = require_calendarGrid.embedTargetId(field, enriched);
|
|
1612
1716
|
enriched[key] = targetId && linked[field.to]?.byId[targetId] || null;
|
|
1613
1717
|
}
|
|
1718
|
+
if (field.type === "backlinks") enriched[key] = projectBacklinks(field, schema, enriched, linked);
|
|
1614
1719
|
}
|
|
1615
1720
|
return enriched;
|
|
1616
1721
|
}
|
|
@@ -2147,12 +2252,6 @@ async function deleteCustomView(collection, viewId, opts = {}) {
|
|
|
2147
2252
|
};
|
|
2148
2253
|
}
|
|
2149
2254
|
//#endregion
|
|
2150
|
-
Object.defineProperty(exports, "COMPUTED_TYPES", {
|
|
2151
|
-
enumerable: true,
|
|
2152
|
-
get: function() {
|
|
2153
|
-
return COMPUTED_TYPES;
|
|
2154
|
-
}
|
|
2155
|
-
});
|
|
2156
2255
|
Object.defineProperty(exports, "CollectionSchemaZ", {
|
|
2157
2256
|
enumerable: true,
|
|
2158
2257
|
get: function() {
|
|
@@ -2195,6 +2294,12 @@ Object.defineProperty(exports, "buildCollectionActionSeedPrompt", {
|
|
|
2195
2294
|
return buildCollectionActionSeedPrompt;
|
|
2196
2295
|
}
|
|
2197
2296
|
});
|
|
2297
|
+
Object.defineProperty(exports, "buildWorkspaceOntology", {
|
|
2298
|
+
enumerable: true,
|
|
2299
|
+
get: function() {
|
|
2300
|
+
return buildWorkspaceOntology;
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2198
2303
|
Object.defineProperty(exports, "collectionsRegistriesConfigPath", {
|
|
2199
2304
|
enumerable: true,
|
|
2200
2305
|
get: function() {
|
|
@@ -2303,6 +2408,12 @@ Object.defineProperty(exports, "isContainedInWorkspace", {
|
|
|
2303
2408
|
return isContainedInWorkspace;
|
|
2304
2409
|
}
|
|
2305
2410
|
});
|
|
2411
|
+
Object.defineProperty(exports, "isRegularFile", {
|
|
2412
|
+
enumerable: true,
|
|
2413
|
+
get: function() {
|
|
2414
|
+
return isRegularFile;
|
|
2415
|
+
}
|
|
2416
|
+
});
|
|
2306
2417
|
Object.defineProperty(exports, "isTriggerDue", {
|
|
2307
2418
|
enumerable: true,
|
|
2308
2419
|
get: function() {
|
|
@@ -2423,6 +2534,12 @@ Object.defineProperty(exports, "safeSlugName", {
|
|
|
2423
2534
|
return safeSlugName;
|
|
2424
2535
|
}
|
|
2425
2536
|
});
|
|
2537
|
+
Object.defineProperty(exports, "schemaRelations", {
|
|
2538
|
+
enumerable: true,
|
|
2539
|
+
get: function() {
|
|
2540
|
+
return schemaRelations;
|
|
2541
|
+
}
|
|
2542
|
+
});
|
|
2426
2543
|
Object.defineProperty(exports, "setCollectionChangePublisher", {
|
|
2427
2544
|
enumerable: true,
|
|
2428
2545
|
get: function() {
|
|
@@ -2472,4 +2589,4 @@ Object.defineProperty(exports, "writeItem", {
|
|
|
2472
2589
|
}
|
|
2473
2590
|
});
|
|
2474
2591
|
|
|
2475
|
-
//# sourceMappingURL=server-
|
|
2592
|
+
//# sourceMappingURL=server-bsNrf2yW.cjs.map
|