@mulmoclaude/collection-plugin 0.5.2 → 0.5.3

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/dist/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as INGEST_KINDS, t as FEED_SCHEDULES } from "./schema-DyfSfVzh.js";
1
+ import { n as INGEST_KINDS, r as isFieldDrivenEvery, t as FEED_SCHEDULES } from "./schema-BcnitlL8.js";
2
2
  import { t as deriveAll } from "./deriveAll-BHcs1erT.js";
3
3
  import path from "node:path";
4
4
  import { promises, realpathSync } from "node:fs";
@@ -777,7 +777,7 @@ var CustomViewSchema = z.object({
777
777
  file: z.string().trim().min(1).refine(isSafeCustomViewPath, "must be a safe path under `views/` ending in `.html` (e.g. `views/year.html`; no `..`, no leading `/`, no backslash)"),
778
778
  capabilities: z.array(z.enum(["read", "write"])).optional()
779
779
  });
780
- var EverySchema = z.object({
780
+ var EveryLiteralSchema = z.object({
781
781
  unit: z.enum([
782
782
  "day",
783
783
  "week",
@@ -786,7 +786,12 @@ var EverySchema = z.object({
786
786
  ]),
787
787
  interval: z.number().int().min(1),
788
788
  dayOfMonth: z.union([z.number().int().min(1).max(31), z.literal("last")]).optional()
789
- });
789
+ }).strict();
790
+ var EveryFieldDrivenSchema = z.object({
791
+ fromField: z.string().trim().min(1),
792
+ map: z.record(z.string(), EveryLiteralSchema)
793
+ }).strict();
794
+ var EverySchema = z.union([EveryLiteralSchema, EveryFieldDrivenSchema]);
790
795
  var SpawnSchema = z.object({
791
796
  when: WhenSchema.optional(),
792
797
  every: EverySchema,
@@ -828,6 +833,36 @@ function spawnSuccessorStartsInert(schema) {
828
833
  if (spawn.set && Object.prototype.hasOwnProperty.call(spawn.set, field)) return !values.includes(String(spawn.set[field]));
829
834
  return !(spawn.carry ?? []).includes(field);
830
835
  }
836
+ function fieldDrivenSpawnEvery(schema) {
837
+ const every = schema.spawn?.every;
838
+ if (!every || !isFieldDrivenEvery(every)) return null;
839
+ return every;
840
+ }
841
+ function fieldDrivenFromFieldIsEnum(schema) {
842
+ const driven = fieldDrivenSpawnEvery(schema);
843
+ if (!driven) return true;
844
+ return schema.fields[driven.fromField]?.type === "enum";
845
+ }
846
+ function fieldDrivenMapCoversValues(schema) {
847
+ const driven = fieldDrivenSpawnEvery(schema);
848
+ if (!driven) return true;
849
+ const target = schema.fields[driven.fromField];
850
+ if (!target || target.type !== "enum" || target.values === void 0) return true;
851
+ const values = new Set(target.values);
852
+ const keys = Object.keys(driven.map);
853
+ return keys.length === values.size && keys.every((key) => values.has(key));
854
+ }
855
+ function fieldDrivenFromFieldCarried(schema) {
856
+ const driven = fieldDrivenSpawnEvery(schema);
857
+ if (!driven) return true;
858
+ const { carry, set } = schema.spawn ?? {};
859
+ if (set && Object.prototype.hasOwnProperty.call(set, driven.fromField)) {
860
+ const raw = set[driven.fromField];
861
+ if (raw === void 0 || raw === null || raw === "") return false;
862
+ return Object.prototype.hasOwnProperty.call(driven.map, String(raw));
863
+ }
864
+ return (carry ?? []).includes(driven.fromField);
865
+ }
831
866
  var IngestSchemaZ = z.object({
832
867
  kind: z.enum(INGEST_KINDS),
833
868
  url: z.string().url(),
@@ -904,6 +939,15 @@ var CollectionSchemaZ = z.object({
904
939
  }).refine((schema) => spawnSuccessorStartsInert(schema), {
905
940
  message: "`spawn` must leave the successor in a non-matching state (e.g. `set` the status to a pending value); seeding the predicate field to a matching value via `set`/`carry` would respawn forever",
906
941
  path: ["spawn"]
942
+ }).refine((schema) => fieldDrivenFromFieldIsEnum(schema), {
943
+ message: "`spawn.every.fromField` must name a top-level `enum` field declared in `fields`",
944
+ path: ["spawn"]
945
+ }).refine((schema) => fieldDrivenMapCoversValues(schema), {
946
+ message: "`spawn.every.map` keys must exactly cover the `values` of the `enum` named by `fromField` (no missing or extra keys)",
947
+ path: ["spawn"]
948
+ }).refine((schema) => fieldDrivenFromFieldCarried(schema), {
949
+ message: "`spawn.every.fromField` must appear in `spawn.carry`, or be written by `spawn.set` to a value present in `spawn.every.map`, so the successor keeps a resolvable recurrence interval",
950
+ path: ["spawn"]
907
951
  }).refine((schema) => schema.calendarField === void 0 || isDateLike(schema.fields[schema.calendarField]?.type), {
908
952
  message: "schema `calendarField` must name a top-level `date` or `datetime` field declared in `fields`",
909
953
  path: ["calendarField"]
@@ -1310,6 +1354,18 @@ function matchesWhen(when, schema, item) {
1310
1354
  const raw = item[completionField];
1311
1355
  return raw !== void 0 && raw !== null && completionDoneValues.includes(String(raw));
1312
1356
  }
1357
+ /** Resolve the literal `every` that applies to `sourceItem`. Literal-arm
1358
+ * `spawn.every` passes through unchanged. Field-driven `spawn.every` reads
1359
+ * `sourceItem[fromField]` and looks it up in `map`; an empty field or a
1360
+ * value with no map entry yields null (caller skips + logs — see plan §5).
1361
+ * Discovery rejects a map that doesn't cover the enum's values, so null
1362
+ * here means a record that predates a map/enum edit. */
1363
+ function resolveEvery(every, sourceItem) {
1364
+ if (!isFieldDrivenEvery(every)) return every;
1365
+ const raw = sourceItem[every.fromField];
1366
+ if (raw === void 0 || raw === null || raw === "") return null;
1367
+ return every.map[String(raw)] ?? null;
1368
+ }
1313
1369
  /** Build the successor record purely from (schema, source record, source
1314
1370
  * id). Returns null when the schema has no spawn/triggerField or the
1315
1371
  * source's trigger date is unparseable. */
@@ -1318,7 +1374,9 @@ function computeSuccessor(schema, sourceItem, sourceId) {
1318
1374
  if (!spawn || !triggerField) return null;
1319
1375
  const srcDate = parseCivil(sourceItem[triggerField]);
1320
1376
  if (srcDate === null) return null;
1321
- const next = advanceTriggerDate(srcDate, spawn.every);
1377
+ const every = resolveEvery(spawn.every, sourceItem);
1378
+ if (every === null) return null;
1379
+ const next = advanceTriggerDate(srcDate, every);
1322
1380
  const nextId = successorId(sourceId, next);
1323
1381
  const record = {};
1324
1382
  for (const field of spawn.carry ?? []) if (Object.prototype.hasOwnProperty.call(sourceItem, field)) record[field] = sourceItem[field];
@@ -1330,6 +1388,27 @@ function computeSuccessor(schema, sourceItem, sourceId) {
1330
1388
  record
1331
1389
  };
1332
1390
  }
1391
+ /** Warn precisely about which of `computeSuccessor`'s two null causes fired
1392
+ * (plan §5): an unparseable source trigger date (the original cause), or a
1393
+ * field-driven `every` whose record value has no `map` entry (a record that
1394
+ * predates a map/enum edit — discovery rejects this statically otherwise). */
1395
+ function logSpawnSkip(slug, triggerField, every, sourceItem, sourceId) {
1396
+ if (parseCivil(sourceItem[triggerField]) === null) {
1397
+ log.warn("collections", "spawn skipped: source trigger date unparseable", {
1398
+ slug,
1399
+ sourceId,
1400
+ triggerField
1401
+ });
1402
+ return;
1403
+ }
1404
+ const fromField = isFieldDrivenEvery(every) ? every.fromField : void 0;
1405
+ log.warn("collections", "spawn skipped: no `every` mapping for frequency value", {
1406
+ slug,
1407
+ sourceId,
1408
+ fromField,
1409
+ value: fromField === void 0 ? void 0 : sourceItem[fromField]
1410
+ });
1411
+ }
1333
1412
  /** Idempotently create the successor for `sourceItem` when it matches the
1334
1413
  * spawn predicate. No-op when the schema declares no spawn, the
1335
1414
  * predicate doesn't match, the trigger date is unparseable, or the
@@ -1341,11 +1420,7 @@ async function maybeSpawnSuccessor(slug, schema, dataDir, sourceItem, sourceId,
1341
1420
  if (!matchesWhen(spawn.when, schema, sourceItem)) return;
1342
1421
  const computed = computeSuccessor(schema, sourceItem, sourceId);
1343
1422
  if (computed === null) {
1344
- log.warn("collections", "spawn skipped: source trigger date unparseable", {
1345
- slug,
1346
- sourceId,
1347
- triggerField: schema.triggerField
1348
- });
1423
+ logSpawnSkip(slug, schema.triggerField, spawn.every, sourceItem, sourceId);
1349
1424
  return;
1350
1425
  }
1351
1426
  if (matchesWhen(spawn.when, schema, computed.record)) {
@@ -1607,6 +1682,6 @@ async function deleteCustomView(collection, viewId, opts = {}) {
1607
1682
  };
1608
1683
  }
1609
1684
  //#endregion
1610
- export { COMPUTED_TYPES, CollectionSchemaZ, SCHEMA_FILE, acceptParsedSchema, advanceTriggerDate, buildActionSeedPrompt, buildCollectionActionSeedPrompt, computeSuccessor, configureCollectionHost, daysInMonth, deleteCollection, deleteCollectionRefusalMessage, deleteCustomView, deleteItem, discoverCollections, enrichItems, formatCivil, generateItemId, getWorkspaceRoot, isContainedInRoot, isContainedInWorkspace, isSafeActionTemplatePath, isSafeCustomViewPath, isSafeTemplatePath, isTriggerDue, itemFilePath, listItems, loadCollection, log, maybeSpawnSuccessor, parseCivil, readCustomViewHtml, readItem, readSkillTemplate, resolveCreateItemId, resolveDataDir, resolveTemplatePath, safeRecordId, safeSlugName, successorId, toDetail, toSummary, validateCollectionRecords, validateRecordObject, writeItem };
1685
+ export { COMPUTED_TYPES, CollectionSchemaZ, SCHEMA_FILE, acceptParsedSchema, advanceTriggerDate, buildActionSeedPrompt, buildCollectionActionSeedPrompt, computeSuccessor, configureCollectionHost, daysInMonth, deleteCollection, deleteCollectionRefusalMessage, deleteCustomView, deleteItem, discoverCollections, enrichItems, formatCivil, generateItemId, getWorkspaceRoot, isContainedInRoot, isContainedInWorkspace, isSafeActionTemplatePath, isSafeCustomViewPath, isSafeTemplatePath, isTriggerDue, itemFilePath, listItems, loadCollection, log, maybeSpawnSuccessor, parseCivil, readCustomViewHtml, readItem, readSkillTemplate, resolveCreateItemId, resolveDataDir, resolveEvery, resolveTemplatePath, safeRecordId, safeSlugName, successorId, toDetail, toSummary, validateCollectionRecords, validateRecordObject, writeItem };
1611
1686
 
1612
1687
  //# sourceMappingURL=server.js.map