@mulmoclaude/core 4.1.1 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/assets/helps/collection-skills.md +17 -6
  2. package/assets/helps/error-recovery.md +53 -0
  3. package/dist/collection/core/calendarGrid.d.ts +2 -2
  4. package/dist/collection/core/serverTime.d.ts +93 -0
  5. package/dist/collection/index.cjs +9 -2
  6. package/dist/collection/index.d.ts +1 -0
  7. package/dist/collection/index.js +3 -3
  8. package/dist/collection/registry/server/index.cjs +2 -2
  9. package/dist/collection/registry/server/index.js +2 -2
  10. package/dist/collection/server/index.cjs +4 -3
  11. package/dist/collection/server/index.js +4 -4
  12. package/dist/collection/server/manageTool.d.ts +24 -0
  13. package/dist/collection-watchers/index.cjs +4 -4
  14. package/dist/collection-watchers/index.js +4 -4
  15. package/dist/{discovery-B9EESjHP.cjs → discovery-Dl9nRa-m.cjs} +38 -20
  16. package/dist/discovery-Dl9nRa-m.cjs.map +1 -0
  17. package/dist/{discovery-D88x2DmW.js → discovery-P64fgtuD.js} +38 -20
  18. package/dist/discovery-P64fgtuD.js.map +1 -0
  19. package/dist/feeds/index.cjs +2 -2
  20. package/dist/feeds/index.js +2 -2
  21. package/dist/feeds/server/index.cjs +4 -4
  22. package/dist/feeds/server/index.js +4 -4
  23. package/dist/google/index.cjs +2 -2
  24. package/dist/google/index.js +2 -2
  25. package/dist/{ingestTypes-B312BwXg.js → ingestTypes-CIy1lHjf.js} +2 -2
  26. package/dist/{ingestTypes-B312BwXg.js.map → ingestTypes-CIy1lHjf.js.map} +1 -1
  27. package/dist/{ingestTypes-B4XfrUoq.cjs → ingestTypes-CaX7_s-K.cjs} +2 -2
  28. package/dist/{ingestTypes-B4XfrUoq.cjs.map → ingestTypes-CaX7_s-K.cjs.map} +1 -1
  29. package/dist/{itemId-DfTm08jm.js → itemId-BY2YPBkT.js} +200 -6
  30. package/dist/itemId-BY2YPBkT.js.map +1 -0
  31. package/dist/{itemId-Dzrr3vs5.cjs → itemId-CEQdK01z.cjs} +241 -5
  32. package/dist/itemId-CEQdK01z.cjs.map +1 -0
  33. package/dist/{promptSafety-CJ-sC1pX.js → promptSafety-Bp780ezk.js} +2 -2
  34. package/dist/{promptSafety-CJ-sC1pX.js.map → promptSafety-Bp780ezk.js.map} +1 -1
  35. package/dist/{promptSafety-CuczAN9d.cjs → promptSafety-C0BWDsfT.cjs} +2 -2
  36. package/dist/{promptSafety-CuczAN9d.cjs.map → promptSafety-C0BWDsfT.cjs.map} +1 -1
  37. package/dist/{server-L12n8We3.js → server-BvXO49Vj.js} +87 -17
  38. package/dist/server-BvXO49Vj.js.map +1 -0
  39. package/dist/{server-meS6kjBN.cjs → server-CF1qllGV.cjs} +92 -16
  40. package/dist/server-CF1qllGV.cjs.map +1 -0
  41. package/package.json +1 -1
  42. package/dist/discovery-B9EESjHP.cjs.map +0 -1
  43. package/dist/discovery-D88x2DmW.js.map +0 -1
  44. package/dist/itemId-DfTm08jm.js.map +0 -1
  45. package/dist/itemId-Dzrr3vs5.cjs.map +0 -1
  46. package/dist/server-L12n8We3.js.map +0 -1
  47. package/dist/server-meS6kjBN.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { a as isErrorWithCode, l as isUnknownArray, r as hasNumberProp, s as isRecord } from "./dist-D8zokgGo.js";
2
2
  import { n as writeFileAtomic, t as canonicalRoot } from "./root-BMroU_mB.js";
3
- import { A as isSafeSlug, B as fieldText, D as SAFE_SLUG_PATTERN, E as SAFE_RECORD_ID_PATTERN, F as INGEST_KINDS, M as AGENT_INGEST_KIND, N as COMPUTED_TYPES, P as FEED_SCHEDULES, R as isReadOnlySchema, S as localCollectionKeyOf, T as sharedCollectionKey, V as fieldTextOrNull, k as isSafeRecordId, t as newItemId, x as isValidCollectionName, z as storageKindFor } from "./itemId-DfTm08jm.js";
3
+ import { B as COMPUTED_TYPES, G as isReadOnlySchema, H as INGEST_KINDS, I as isSafeRecordId, J as fieldTextOrNull, K as storageKindFor, L as isSafeSlug, M as sharedCollectionKey, N as SAFE_RECORD_ID_PATTERN, O as isValidCollectionName, P as SAFE_SLUG_PATTERN, V as FEED_SCHEDULES, _ as decodeRecordTimes, k as localCollectionKeyOf, q as fieldText, t as newItemId, y as encodeRecordTimes, z as AGENT_INGEST_KIND } from "./itemId-BY2YPBkT.js";
4
4
  import { n as projectRecordFields } from "./project-B0j_Vf_Q.js";
5
5
  import { n as backoffDelayMs, r as classifyListenerError } from "./listen-D5av7GzB.js";
6
6
  import { n as isSafeCustomViewI18nPath, r as isSafeCustomViewPath, t as isSafeActionTemplatePath } from "./templatePath-k_WNbL_Q.js";
@@ -8,6 +8,7 @@ import { readFileSync, realpathSync, watch } from "node:fs";
8
8
  import path from "node:path";
9
9
  import { createHash, randomBytes } from "node:crypto";
10
10
  import { lstat, mkdir, open, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
11
+ import { Timestamp } from "firebase/firestore";
11
12
  import { tmpdir } from "node:os";
12
13
  import iconv from "iconv-lite";
13
14
  import { z } from "zod";
@@ -473,10 +474,10 @@ async function guarded(key, email, run) {
473
474
  * It also removes the reason a submit path would have had to accept the
474
475
  * primary key as a `createField` at all — a submission that cannot name its
475
476
  * own id is exactly right when the id is the thing being assigned. */
476
- function toItem(data, docId, primaryKey) {
477
+ function toItem(data, docId, schema) {
477
478
  return isRecord(data) ? {
478
- ...data,
479
- [primaryKey]: docId
479
+ ...decodeRecordTimes(data, schema),
480
+ [schema.primaryKey]: docId
480
481
  } : null;
481
482
  }
482
483
  /** Record ids are validated with the SAME helper every other backend uses.
@@ -488,26 +489,26 @@ function withSafeId(itemId, onInvalid, run) {
488
489
  if (safeId === null) return onInvalid();
489
490
  return run(safeId, requireHandle());
490
491
  }
491
- async function firestoreList(key, primaryKey) {
492
+ async function firestoreList(key, schema) {
492
493
  const { docs, email } = requireHandle();
493
- return (await guarded(key, email, () => docs.list(sharedItemsPath(key)))).map((entry) => toItem(entry.data, entry.id, primaryKey)).filter((item) => item !== null);
494
+ return (await guarded(key, email, () => docs.list(sharedItemsPath(key)))).map((entry) => toItem(entry.data, entry.id, schema)).filter((item) => item !== null);
494
495
  }
495
496
  /** Paging is emulated over a full ordered read rather than pushed into
496
497
  * Firestore: `offset` has no server-side form there (the cursor API needs the
497
498
  * preceding document, which a stateless offset/limit call doesn't have), and
498
499
  * `total` needs the full count anyway. Hence `nativePaging: false` — the
499
500
  * capability is honest about the cost. */
500
- async function firestorePage(key, primaryKey, opts) {
501
- const items = await firestoreList(key, primaryKey);
501
+ async function firestorePage(key, schema, opts) {
502
+ const items = await firestoreList(key, schema);
502
503
  const offset = Math.max(0, opts.offset ?? 0);
503
504
  return {
504
- items: projectItemFields(opts.limit === void 0 ? items.slice(offset) : items.slice(offset, offset + Math.max(0, opts.limit)), opts.fields, primaryKey),
505
+ items: projectItemFields(opts.limit === void 0 ? items.slice(offset) : items.slice(offset, offset + Math.max(0, opts.limit)), opts.fields, schema.primaryKey),
505
506
  total: items.length,
506
507
  truncated: false
507
508
  };
508
509
  }
509
- async function firestoreRead(key, itemId, primaryKey) {
510
- return withSafeId(itemId, () => Promise.resolve(null), async (safeId, { docs, email }) => toItem(await guarded(key, email, () => docs.get(sharedItemsPath(key), safeId)), safeId, primaryKey));
510
+ async function firestoreRead(key, itemId, schema) {
511
+ return withSafeId(itemId, () => Promise.resolve(null), async (safeId, { docs, email }) => toItem(await guarded(key, email, () => docs.get(sharedItemsPath(key), safeId)), safeId, schema));
511
512
  }
512
513
  /** Publish the "records changed" ping for a shared collection.
513
514
  *
@@ -522,18 +523,35 @@ function publishShared(key, ids, operation) {
522
523
  op: operation
523
524
  }, key.aid));
524
525
  }
525
- async function firestoreWrite(key, itemId, item, opts) {
526
+ /** The write half of the decode above, and it is not optional.
527
+ *
528
+ * A record is read, edited and written back WHOLE — `putItems`' upsert
529
+ * replaces the document — so a decoded stamp would return to Firestore as a
530
+ * string. The rules freeze that field by comparing `diff().affectedKeys()`, so
531
+ * that write is refused and so is every later one: the record becomes
532
+ * permanently unupdatable. Dropping the key instead does not help, because a
533
+ * removed key is an affected key too.
534
+ *
535
+ * Only a `datetime` field carrying the exact canonical form is converted, and
536
+ * that form is only ever produced by the decode — so the pair is closed over
537
+ * its own output and cannot re-type a value somebody else wrote. */
538
+ async function firestoreWrite(key, itemId, item, schema, opts) {
526
539
  return withSafeId(itemId, () => Promise.resolve({
527
540
  kind: "invalid-id",
528
541
  itemId
529
542
  }), async (safeId, { docs, email }) => {
530
543
  const collectionPath = sharedItemsPath(key);
544
+ const asTimestamp = (parts) => new Timestamp(parts.seconds, parts.nanoseconds);
531
545
  if (opts.refuseOverwrite) {
532
- if (!await guarded(key, email, () => docs.create(collectionPath, safeId, item))) return {
546
+ if (!await guarded(key, email, () => docs.create(collectionPath, safeId, encodeRecordTimes(item, null, asTimestamp)))) return {
533
547
  kind: "conflict",
534
548
  itemId: safeId
535
549
  };
536
- } else await guarded(key, email, () => docs.set(collectionPath, safeId, item));
550
+ } else {
551
+ const previous = await guarded(key, email, () => docs.get(collectionPath, safeId));
552
+ const stored = encodeRecordTimes(item, isRecord(previous) ? previous : null, asTimestamp);
553
+ await guarded(key, email, () => docs.set(collectionPath, safeId, stored));
554
+ }
537
555
  if (opts.slug) publishShared(key, [safeId], "upsert");
538
556
  return {
539
557
  kind: "ok",
@@ -666,7 +684,7 @@ function armSharedWatch(key, onChange) {
666
684
  /** The store factory registered for `storage.type === "firestore"`.
667
685
  * Synchronous and connection-agnostic by contract — see the header. */
668
686
  function firestoreStoreFor(collection, opts) {
669
- const { primaryKey } = collection.schema;
687
+ const { schema } = collection;
670
688
  const ioOpts = {
671
689
  ...opts,
672
690
  slug: opts.slug ?? collection.slug
@@ -677,10 +695,10 @@ function firestoreStoreFor(collection, opts) {
677
695
  nativeQuery: false,
678
696
  nativePaging: false
679
697
  },
680
- list: async () => firestoreList(keyOf(collection), primaryKey),
681
- page: async (pageOpts = {}) => firestorePage(keyOf(collection), primaryKey, pageOpts),
682
- read: async (itemId) => firestoreRead(keyOf(collection), itemId, primaryKey),
683
- write: async (itemId, item, writeOpts = {}) => firestoreWrite(keyOf(collection), itemId, item, {
698
+ list: async () => firestoreList(keyOf(collection), schema),
699
+ page: async (pageOpts = {}) => firestorePage(keyOf(collection), schema, pageOpts),
700
+ read: async (itemId) => firestoreRead(keyOf(collection), itemId, schema),
701
+ write: async (itemId, item, writeOpts = {}) => firestoreWrite(keyOf(collection), itemId, item, schema, {
684
702
  ...ioOpts,
685
703
  refuseOverwrite: writeOpts.refuseOverwrite
686
704
  }),
@@ -3315,4 +3333,4 @@ function toDetail(collection) {
3315
3333
  //#endregion
3316
3334
  export { collectionsRegistriesConfigPath as $, readItem as A, SCHEMA_FILE as B, CollectionQueryZ as C, generateItemId as D, deleteItem as E, loadAppManifest as F, safeRecordId as G, itemFilePath as H, parseAppManifest as I, isBackendUnavailable as J, safeSlugName as K, sharedItemsPath as L, writeItem as M, APP_MANIFEST_FILE as N, isRegularFile as O, appManifestReason as P, collectionChangePayload as Q, pageFromFullRead as R, compileJsonlQuery as S, MAX_QUERY_ROWS as T, resolveDataDir as U, isContainedInRoot as V, resolveTemplatePath as W, archiveDir as X, COLLECTION_ROOT_REQUIRED as Y, collectionChangeKey as Z, dedupeByRecordId as _, createHostSlot as _t, toDetail as a, localCollectionKey as at, queryCsv as b, resolveMutateSet as c, projectSkillsDir as ct, storeFor as d, setFirestoreAccessor as dt, configureCollectionHost as et, checkpointSqliteDatabase as f, setSharedCollectionsSupport as ft, decodeCsvRecordId as g, createForwardingLogger as gt, csvRowToItem as h, stagingSkillDir as ht, resolvePrimaryField as i, isPresetSlug as it, resolveCreateItemId as j, listItems as k, collectionWritable as l, publishCollectionChange as lt, cacheDir as m, skillsStagingDir as mt, discoverCollections as n, getWorkspaceRoot as nt, toSummary as o, log as ot, MAX_CSV_ROWS as p, sharedCollectionChangePayload as pt, BackendUnavailableError as q, loadCollection as r, hostSupportsSharedCollections as rt, CollectionSchemaZ as s, peekWorkspaceRoot as st, acceptParsedSchema as t, firestoreHandle as tt, readOnlyRefusal as u, setCollectionChangePublisher as ut, encodeCsvRecordId as v, DEFAULT_QUERY_ROWS as w, compileCsvQuery as x, normalizeCsvValue as y, projectItemFields as z };
3317
3335
 
3318
- //# sourceMappingURL=discovery-D88x2DmW.js.map
3336
+ //# sourceMappingURL=discovery-P64fgtuD.js.map