canopycms 0.0.67-int.90 → 0.0.67

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 (539) hide show
  1. package/README.md +30 -3
  2. package/dist/ai/generate.js +1 -27
  3. package/dist/ai/handler.d.ts +0 -4
  4. package/dist/ai/handler.js +0 -11
  5. package/dist/ai/index.d.ts +1 -8
  6. package/dist/ai/index.js +1 -8
  7. package/dist/ai/json-to-markdown.js +15 -58
  8. package/dist/ai/resolve-branch.d.ts +2 -7
  9. package/dist/ai/resolve-branch.js +2 -8
  10. package/dist/ai/strip-mdx.d.ts +5 -13
  11. package/dist/ai/strip-mdx.js +5 -17
  12. package/dist/ai/to-plain-text.d.ts +10 -28
  13. package/dist/ai/to-plain-text.js +10 -28
  14. package/dist/ai/transform-components.d.ts +2 -4
  15. package/dist/ai/transform-components.js +8 -19
  16. package/dist/ai/types.d.ts +5 -10
  17. package/dist/api/admin-branch-health.d.ts +4 -12
  18. package/dist/api/admin-branch-health.js +26 -66
  19. package/dist/api/admin.d.ts +4 -12
  20. package/dist/api/admin.js +10 -26
  21. package/dist/api/assets.d.ts +0 -25
  22. package/dist/api/assets.js +50 -91
  23. package/dist/api/branch-merge.d.ts +1 -1
  24. package/dist/api/branch-merge.js +0 -2
  25. package/dist/api/branch-review.d.ts +0 -8
  26. package/dist/api/branch-review.js +0 -13
  27. package/dist/api/branch-status.d.ts +0 -3
  28. package/dist/api/branch-status.js +8 -33
  29. package/dist/api/branch-withdraw.d.ts +0 -1
  30. package/dist/api/branch-withdraw.js +6 -9
  31. package/dist/api/branch.d.ts +7 -12
  32. package/dist/api/branch.js +7 -44
  33. package/dist/api/client.d.ts +71 -224
  34. package/dist/api/client.js +82 -233
  35. package/dist/api/comments.d.ts +2 -4
  36. package/dist/api/comments.js +0 -17
  37. package/dist/api/content.d.ts +0 -6
  38. package/dist/api/content.js +66 -142
  39. package/dist/api/entries-constants.d.ts +5 -9
  40. package/dist/api/entries-constants.js +5 -9
  41. package/dist/api/entries.d.ts +8 -13
  42. package/dist/api/entries.js +20 -51
  43. package/dist/api/github-sync.d.ts +2 -4
  44. package/dist/api/github-sync.js +4 -8
  45. package/dist/api/groups.d.ts +3 -0
  46. package/dist/api/groups.js +8 -21
  47. package/dist/api/guards.d.ts +4 -16
  48. package/dist/api/guards.js +3 -22
  49. package/dist/api/index.d.ts +4 -4
  50. package/dist/api/permissions.d.ts +2 -0
  51. package/dist/api/permissions.js +18 -40
  52. package/dist/api/reference-options.d.ts +0 -1
  53. package/dist/api/reference-options.js +3 -11
  54. package/dist/api/request-body-hash.d.ts +12 -23
  55. package/dist/api/request-body-hash.js +12 -23
  56. package/dist/api/resolve-references.d.ts +0 -1
  57. package/dist/api/resolve-references.js +13 -28
  58. package/dist/api/route-builder.d.ts +3 -7
  59. package/dist/api/route-builder.js +1 -1
  60. package/dist/api/routes.d.ts +12 -0
  61. package/dist/api/routes.js +47 -0
  62. package/dist/api/schema.d.ts +23 -33
  63. package/dist/api/schema.js +20 -74
  64. package/dist/api/settings-helpers.js +4 -5
  65. package/dist/api/types.d.ts +0 -5
  66. package/dist/api/user.js +0 -5
  67. package/dist/api/validators.d.ts +14 -52
  68. package/dist/api/validators.js +14 -52
  69. package/dist/assets/asset-prefixes.d.ts +4 -8
  70. package/dist/assets/asset-prefixes.js +4 -8
  71. package/dist/assets/asset-src.d.ts +5 -22
  72. package/dist/assets/asset-src.js +5 -21
  73. package/dist/assets/factory.d.ts +1 -2
  74. package/dist/assets/factory.js +1 -2
  75. package/dist/assets/finalize.d.ts +3 -1
  76. package/dist/assets/finalize.js +3 -1
  77. package/dist/assets/index.d.ts +3 -3
  78. package/dist/assets/index.js +1 -1
  79. package/dist/assets/keys.d.ts +5 -3
  80. package/dist/assets/keys.js +6 -3
  81. package/dist/assets/pipeline.d.ts +3 -2
  82. package/dist/assets/pipeline.js +22 -56
  83. package/dist/assets/sharp-loader.d.ts +2 -10
  84. package/dist/assets/sharp-loader.js +2 -10
  85. package/dist/assets/store-local.d.ts +1 -2
  86. package/dist/assets/store-local.js +1 -2
  87. package/dist/assets/store-s3.d.ts +3 -4
  88. package/dist/assets/svg-sanitizer.d.ts +6 -14
  89. package/dist/assets/svg-sanitizer.js +6 -14
  90. package/dist/assets/transform-directives.d.ts +3 -4
  91. package/dist/assets/transform-directives.js +1 -1
  92. package/dist/assets/transform.d.ts +19 -30
  93. package/dist/assets/transform.js +18 -31
  94. package/dist/auth/cache.d.ts +2 -3
  95. package/dist/auth/cache.js +2 -3
  96. package/dist/auth/caching-auth-plugin.d.ts +11 -22
  97. package/dist/auth/caching-auth-plugin.js +12 -14
  98. package/dist/auth/context-helpers.d.ts +6 -20
  99. package/dist/auth/context-helpers.js +5 -17
  100. package/dist/auth/file-based-auth-cache.d.ts +10 -21
  101. package/dist/auth/file-based-auth-cache.js +23 -51
  102. package/dist/auth/plugin.d.ts +29 -61
  103. package/dist/auth/plugin.js +9 -13
  104. package/dist/auth/types.d.ts +11 -20
  105. package/dist/authorization/branch.d.ts +30 -49
  106. package/dist/authorization/branch.js +16 -42
  107. package/dist/authorization/content.d.ts +11 -40
  108. package/dist/authorization/content.js +13 -45
  109. package/dist/authorization/groups/index.d.ts +0 -3
  110. package/dist/authorization/groups/index.js +0 -3
  111. package/dist/authorization/groups/loader.d.ts +14 -28
  112. package/dist/authorization/groups/loader.js +14 -38
  113. package/dist/authorization/groups/schema.d.ts +0 -6
  114. package/dist/authorization/groups/schema.js +4 -7
  115. package/dist/authorization/helpers.d.ts +9 -28
  116. package/dist/authorization/helpers.js +9 -28
  117. package/dist/authorization/index.d.ts +3 -25
  118. package/dist/authorization/index.js +3 -35
  119. package/dist/authorization/path.d.ts +6 -13
  120. package/dist/authorization/path.js +8 -25
  121. package/dist/authorization/permissions/index.d.ts +0 -3
  122. package/dist/authorization/permissions/index.js +0 -3
  123. package/dist/authorization/permissions/loader.d.ts +14 -27
  124. package/dist/authorization/permissions/loader.js +14 -32
  125. package/dist/authorization/permissions/schema.d.ts +1 -6
  126. package/dist/authorization/permissions/schema.js +6 -13
  127. package/dist/authorization/protected-branch.d.ts +36 -65
  128. package/dist/authorization/protected-branch.js +29 -41
  129. package/dist/authorization/settings-file-store.d.ts +38 -81
  130. package/dist/authorization/settings-file-store.js +38 -79
  131. package/dist/authorization/types.d.ts +4 -26
  132. package/dist/authorization/types.js +0 -6
  133. package/dist/authorization/validation.d.ts +1 -23
  134. package/dist/authorization/validation.js +1 -24
  135. package/dist/branch-health.d.ts +39 -67
  136. package/dist/branch-health.js +27 -43
  137. package/dist/branch-metadata-file.d.ts +16 -32
  138. package/dist/branch-metadata-file.js +13 -26
  139. package/dist/branch-metadata.d.ts +49 -80
  140. package/dist/branch-metadata.js +57 -87
  141. package/dist/branch-registry.d.ts +26 -64
  142. package/dist/branch-registry.js +44 -93
  143. package/dist/branch-schema-cache.d.ts +34 -124
  144. package/dist/branch-schema-cache.js +49 -155
  145. package/dist/branch-workspace.d.ts +4 -4
  146. package/dist/branch-workspace.js +15 -19
  147. package/dist/build/generate-ai-content.d.ts +1 -0
  148. package/dist/build/generate-ai-content.js +12 -25
  149. package/dist/build-canopy.d.ts +17 -27
  150. package/dist/build-canopy.js +16 -23
  151. package/dist/build-mode.d.ts +25 -27
  152. package/dist/build-mode.js +25 -29
  153. package/dist/cli/cli.d.ts +15 -26
  154. package/dist/cli/cli.js +601 -978
  155. package/dist/cli/generate-ai-content.js +351 -628
  156. package/dist/cli/github-app-manifest.d.ts +84 -0
  157. package/dist/cli/github-app-manifest.js +161 -0
  158. package/dist/cli/init-github-app.d.ts +110 -444
  159. package/dist/cli/init-github-app.js +236 -773
  160. package/dist/cli/init.js +39 -80
  161. package/dist/cli/migrate.d.ts +13 -10
  162. package/dist/cli/migrate.js +22 -30
  163. package/dist/cli/project-detect.d.ts +17 -31
  164. package/dist/cli/project-detect.js +31 -42
  165. package/dist/cli/project-root.d.ts +4 -11
  166. package/dist/cli/project-root.js +4 -11
  167. package/dist/cli/prompt.d.ts +10 -0
  168. package/dist/cli/prompt.js +67 -0
  169. package/dist/cli/sync.d.ts +0 -8
  170. package/dist/cli/sync.js +8 -28
  171. package/dist/cli/template-files/middleware.ts.template +15 -29
  172. package/dist/cli/templates.d.ts +2 -12
  173. package/dist/cli/templates.js +4 -4
  174. package/dist/client.d.ts +1 -1
  175. package/dist/comment-store.d.ts +32 -53
  176. package/dist/comment-store.js +32 -52
  177. package/dist/config/flatten.d.ts +0 -3
  178. package/dist/config/flatten.js +2 -12
  179. package/dist/config/helpers.js +2 -2
  180. package/dist/config/index.d.ts +2 -2
  181. package/dist/config/index.js +1 -4
  182. package/dist/config/schemas/collection.d.ts +0 -8
  183. package/dist/config/schemas/collection.js +0 -10
  184. package/dist/config/schemas/config.d.ts +6 -78
  185. package/dist/config/schemas/config.js +38 -54
  186. package/dist/config/schemas/field.d.ts +2 -242
  187. package/dist/config/schemas/field.js +9 -20
  188. package/dist/config/schemas/media.js +1 -1
  189. package/dist/config/schemas/permissions.js +0 -2
  190. package/dist/config/schemas/url.d.ts +5 -6
  191. package/dist/config/schemas/url.js +5 -6
  192. package/dist/config/types.d.ts +45 -33
  193. package/dist/config/types.js +0 -1
  194. package/dist/config.d.ts +1 -8
  195. package/dist/config.js +1 -9
  196. package/dist/content-id-index.d.ts +76 -188
  197. package/dist/content-id-index.js +79 -230
  198. package/dist/content-index-generation.d.ts +27 -48
  199. package/dist/content-index-generation.js +35 -84
  200. package/dist/content-index-registry.d.ts +15 -24
  201. package/dist/content-index-registry.js +5 -9
  202. package/dist/content-listing.d.ts +64 -107
  203. package/dist/content-listing.js +68 -118
  204. package/dist/content-reader.d.ts +20 -29
  205. package/dist/content-reader.js +10 -18
  206. package/dist/content-store.d.ts +210 -336
  207. package/dist/content-store.js +413 -657
  208. package/dist/content-tree.d.ts +44 -86
  209. package/dist/content-tree.js +26 -54
  210. package/dist/context.d.ts +67 -94
  211. package/dist/context.js +53 -72
  212. package/dist/dev-content-watcher.d.ts +18 -18
  213. package/dist/dev-content-watcher.js +41 -44
  214. package/dist/editor/BranchManager.d.ts +3 -6
  215. package/dist/editor/BranchManager.js +24 -44
  216. package/dist/editor/CanopyEditor.js +5 -2
  217. package/dist/editor/CommentsPanel.js +0 -2
  218. package/dist/editor/Editor.js +19 -76
  219. package/dist/editor/EditorAuthGate.d.ts +27 -0
  220. package/dist/editor/EditorAuthGate.js +140 -0
  221. package/dist/editor/EntryNavigator.js +0 -20
  222. package/dist/editor/FormRenderer.d.ts +1 -2
  223. package/dist/editor/FormRenderer.js +8 -23
  224. package/dist/editor/admin/SystemHealthPanel.js +20 -37
  225. package/dist/editor/admin/useSystemHealth.d.ts +2 -3
  226. package/dist/editor/admin/useSystemHealth.js +10 -12
  227. package/dist/editor/client-reference-resolver.d.ts +2 -5
  228. package/dist/editor/client-reference-resolver.js +2 -19
  229. package/dist/editor/comments/BranchComments.js +0 -4
  230. package/dist/editor/comments/EntryComments.js +0 -4
  231. package/dist/editor/comments/FieldWrapper.js +0 -4
  232. package/dist/editor/comments/InlineCommentThread.js +0 -2
  233. package/dist/editor/comments/ThreadCarousel.d.ts +0 -58
  234. package/dist/editor/comments/ThreadCarousel.js +0 -12
  235. package/dist/editor/components/EditorFooter.d.ts +0 -8
  236. package/dist/editor/components/EditorFooter.js +0 -5
  237. package/dist/editor/components/EditorHeader.d.ts +0 -31
  238. package/dist/editor/components/EditorHeader.js +21 -71
  239. package/dist/editor/components/EditorSidebar.d.ts +0 -19
  240. package/dist/editor/components/EditorSidebar.js +0 -16
  241. package/dist/editor/components/EntryCreateModal.d.ts +4 -4
  242. package/dist/editor/components/EntryCreateModal.js +7 -22
  243. package/dist/editor/components/RenameEntryModal.js +0 -4
  244. package/dist/editor/components/UserBadge.js +0 -10
  245. package/dist/editor/components/index.js +0 -2
  246. package/dist/editor/context/ApiClientContext.d.ts +8 -7
  247. package/dist/editor/context/ApiClientContext.js +39 -11
  248. package/dist/editor/context/AssetContext.d.ts +7 -17
  249. package/dist/editor/context/AssetContext.js +7 -17
  250. package/dist/editor/context/EditorIdentityContext.d.ts +13 -0
  251. package/dist/editor/context/EditorIdentityContext.js +7 -0
  252. package/dist/editor/context/EditorStateContext.d.ts +10 -41
  253. package/dist/editor/context/EditorStateContext.js +7 -23
  254. package/dist/editor/context/SWRProvider.d.ts +3 -6
  255. package/dist/editor/context/index.d.ts +1 -20
  256. package/dist/editor/context/index.js +1 -20
  257. package/dist/editor/editor-utils.d.ts +8 -47
  258. package/dist/editor/editor-utils.js +19 -72
  259. package/dist/editor/fields/BlockField.d.ts +2 -2
  260. package/dist/editor/fields/BlockField.js +1 -2
  261. package/dist/editor/fields/CodeField.d.ts +0 -1
  262. package/dist/editor/fields/CodeField.js +0 -1
  263. package/dist/editor/fields/DateTimeField.d.ts +15 -22
  264. package/dist/editor/fields/DateTimeField.js +17 -26
  265. package/dist/editor/fields/ImageField.d.ts +2 -2
  266. package/dist/editor/fields/ImageField.js +8 -14
  267. package/dist/editor/fields/InlineGroupField.d.ts +0 -1
  268. package/dist/editor/fields/InlineGroupField.js +0 -1
  269. package/dist/editor/fields/MarkdownField.d.ts +0 -1
  270. package/dist/editor/fields/MarkdownField.js +6 -11
  271. package/dist/editor/fields/MdxImageDialog.d.ts +1 -1
  272. package/dist/editor/fields/MdxImageDialog.js +0 -1
  273. package/dist/editor/fields/NumberField.d.ts +9 -19
  274. package/dist/editor/fields/NumberField.js +11 -22
  275. package/dist/editor/fields/NumberListField.d.ts +7 -10
  276. package/dist/editor/fields/NumberListField.js +7 -10
  277. package/dist/editor/fields/ObjectField.d.ts +5 -8
  278. package/dist/editor/fields/ObjectField.js +0 -1
  279. package/dist/editor/fields/ReferenceField.d.ts +1 -1
  280. package/dist/editor/fields/ReferenceField.js +11 -16
  281. package/dist/editor/fields/SelectField.d.ts +2 -2
  282. package/dist/editor/fields/SelectField.js +0 -1
  283. package/dist/editor/fields/StringListField.d.ts +0 -4
  284. package/dist/editor/fields/StringListField.js +4 -9
  285. package/dist/editor/fields/TextField.d.ts +0 -1
  286. package/dist/editor/fields/TextField.js +0 -1
  287. package/dist/editor/fields/ToggleField.d.ts +0 -1
  288. package/dist/editor/fields/ToggleField.js +0 -1
  289. package/dist/editor/fields/entry-link/EntryLinkContext.js +2 -4
  290. package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +0 -3
  291. package/dist/editor/fields/entry-link/InsertEntryLink.js +0 -4
  292. package/dist/editor/group-manager/ExternalGroupsTab.d.ts +0 -3
  293. package/dist/editor/group-manager/ExternalGroupsTab.js +0 -1
  294. package/dist/editor/group-manager/GroupCard.d.ts +0 -3
  295. package/dist/editor/group-manager/GroupForm.d.ts +0 -3
  296. package/dist/editor/group-manager/InternalGroupsTab.d.ts +0 -3
  297. package/dist/editor/group-manager/InternalGroupsTab.js +0 -2
  298. package/dist/editor/group-manager/MemberList.d.ts +0 -3
  299. package/dist/editor/group-manager/hooks/useExternalGroupSearch.d.ts +0 -3
  300. package/dist/editor/group-manager/hooks/useExternalGroupSearch.js +0 -3
  301. package/dist/editor/group-manager/hooks/useGroupState.d.ts +0 -3
  302. package/dist/editor/group-manager/hooks/useGroupState.js +0 -5
  303. package/dist/editor/group-manager/hooks/useUserSearch.d.ts +0 -3
  304. package/dist/editor/group-manager/hooks/useUserSearch.js +0 -3
  305. package/dist/editor/group-manager/index.d.ts +0 -6
  306. package/dist/editor/group-manager/index.js +0 -10
  307. package/dist/editor/group-manager/types.d.ts +2 -5
  308. package/dist/editor/group-manager/types.js +0 -3
  309. package/dist/editor/hooks/index.d.ts +6 -11
  310. package/dist/editor/hooks/index.js +6 -12
  311. package/dist/editor/hooks/useBranchActions.d.ts +0 -14
  312. package/dist/editor/hooks/useBranchActions.js +0 -22
  313. package/dist/editor/hooks/useBranchManager.d.ts +2 -48
  314. package/dist/editor/hooks/useBranchManager.js +4 -64
  315. package/dist/editor/hooks/useBranchesData.d.ts +7 -6
  316. package/dist/editor/hooks/useBranchesData.js +7 -6
  317. package/dist/editor/hooks/useCommentSystem.d.ts +0 -49
  318. package/dist/editor/hooks/useCommentSystem.js +1 -36
  319. package/dist/editor/hooks/useDraftManager.d.ts +2 -35
  320. package/dist/editor/hooks/useDraftManager.js +23 -64
  321. package/dist/editor/hooks/useEditorLayout.d.ts +0 -20
  322. package/dist/editor/hooks/useEditorLayout.js +0 -23
  323. package/dist/editor/hooks/useEntriesData.d.ts +1 -0
  324. package/dist/editor/hooks/useEntriesData.js +2 -3
  325. package/dist/editor/hooks/useEntryLinkResolution.d.ts +2 -2
  326. package/dist/editor/hooks/useEntryLinkResolution.js +2 -2
  327. package/dist/editor/hooks/useEntryManager.d.ts +2 -27
  328. package/dist/editor/hooks/useEntryManager.js +29 -78
  329. package/dist/editor/hooks/useGroupManager.d.ts +0 -21
  330. package/dist/editor/hooks/useGroupManager.js +0 -21
  331. package/dist/editor/hooks/usePermissionManager.d.ts +0 -20
  332. package/dist/editor/hooks/usePermissionManager.js +0 -20
  333. package/dist/editor/hooks/useReferenceResolution.d.ts +2 -33
  334. package/dist/editor/hooks/useReferenceResolution.js +1 -48
  335. package/dist/editor/hooks/useSchemaManager.d.ts +8 -27
  336. package/dist/editor/hooks/useSchemaManager.js +0 -19
  337. package/dist/editor/hooks/useUserContext.d.ts +2 -9
  338. package/dist/editor/hooks/useUserContext.js +10 -10
  339. package/dist/editor/hooks/useUserMetadata.d.ts +0 -2
  340. package/dist/editor/hooks/useUserMetadata.js +0 -3
  341. package/dist/editor/media/AssetCard.d.ts +0 -1
  342. package/dist/editor/media/AssetCard.js +1 -2
  343. package/dist/editor/media/CropStep.d.ts +0 -1
  344. package/dist/editor/media/CropStep.js +0 -1
  345. package/dist/editor/media/MediaLibrary.d.ts +1 -2
  346. package/dist/editor/media/MediaLibrary.js +1 -2
  347. package/dist/editor/media/MediaLibraryBody.d.ts +0 -1
  348. package/dist/editor/media/MediaLibraryBody.js +0 -1
  349. package/dist/editor/media/crop-math.d.ts +1 -1
  350. package/dist/editor/media/crop-math.js +1 -1
  351. package/dist/editor/permission-manager/GroupSelector.d.ts +0 -3
  352. package/dist/editor/permission-manager/PermissionEditor.d.ts +0 -4
  353. package/dist/editor/permission-manager/PermissionEditor.js +0 -1
  354. package/dist/editor/permission-manager/PermissionLevelBadge.d.ts +0 -7
  355. package/dist/editor/permission-manager/PermissionLevelBadge.js +0 -4
  356. package/dist/editor/permission-manager/PermissionTree.d.ts +0 -4
  357. package/dist/editor/permission-manager/UserSelector.d.ts +0 -3
  358. package/dist/editor/permission-manager/constants.d.ts +0 -3
  359. package/dist/editor/permission-manager/hooks/useGroupsAndUsers.d.ts +0 -3
  360. package/dist/editor/permission-manager/hooks/useGroupsAndUsers.js +0 -10
  361. package/dist/editor/permission-manager/hooks/usePermissionTree.d.ts +0 -14
  362. package/dist/editor/permission-manager/hooks/usePermissionTree.js +0 -11
  363. package/dist/editor/permission-manager/index.d.ts +0 -7
  364. package/dist/editor/permission-manager/index.js +0 -16
  365. package/dist/editor/permission-manager/types.d.ts +2 -12
  366. package/dist/editor/permission-manager/types.js +0 -3
  367. package/dist/editor/permission-manager/utils.d.ts +0 -28
  368. package/dist/editor/permission-manager/utils.js +9 -23
  369. package/dist/editor/preview-bridge.d.ts +5 -1
  370. package/dist/editor/preview-bridge.js +4 -4
  371. package/dist/editor/schema-editor/CollectionEditor.d.ts +0 -5
  372. package/dist/editor/schema-editor/CollectionEditor.js +9 -31
  373. package/dist/editor/schema-editor/EntryTypeEditor.d.ts +0 -6
  374. package/dist/editor/schema-editor/EntryTypeEditor.js +3 -24
  375. package/dist/editor/schema-editor/index.d.ts +0 -7
  376. package/dist/editor/schema-editor/index.js +0 -7
  377. package/dist/editor/utils/env.d.ts +3 -18
  378. package/dist/editor/utils/env.js +6 -25
  379. package/dist/entry-link-resolver.d.ts +13 -35
  380. package/dist/entry-link-resolver.js +20 -53
  381. package/dist/entry-schema-registry.d.ts +20 -60
  382. package/dist/entry-schema-registry.js +20 -69
  383. package/dist/entry-schema.d.ts +118 -223
  384. package/dist/entry-schema.js +46 -91
  385. package/dist/git-manager.d.ts +101 -200
  386. package/dist/git-manager.js +211 -384
  387. package/dist/github-service.d.ts +34 -75
  388. package/dist/github-service.js +37 -91
  389. package/dist/http/handler.d.ts +6 -37
  390. package/dist/http/handler.js +65 -120
  391. package/dist/http/router.d.ts +15 -32
  392. package/dist/http/router.js +34 -99
  393. package/dist/http/types.d.ts +15 -40
  394. package/dist/http/types.js +3 -7
  395. package/dist/id.d.ts +4 -10
  396. package/dist/id.js +4 -10
  397. package/dist/index.js +6 -9
  398. package/dist/operating-mode/client-safe-strategy.d.ts +7 -14
  399. package/dist/operating-mode/client-safe-strategy.js +8 -28
  400. package/dist/operating-mode/client-unsafe-strategy.d.ts +6 -16
  401. package/dist/operating-mode/client-unsafe-strategy.js +9 -34
  402. package/dist/operating-mode/client.d.ts +3 -10
  403. package/dist/operating-mode/client.js +3 -8
  404. package/dist/operating-mode/deployment-name-fixtures.d.ts +14 -18
  405. package/dist/operating-mode/deployment-name-fixtures.js +14 -18
  406. package/dist/operating-mode/deployment-name.d.ts +19 -29
  407. package/dist/operating-mode/deployment-name.js +29 -47
  408. package/dist/operating-mode/index.d.ts +4 -25
  409. package/dist/operating-mode/index.js +9 -31
  410. package/dist/operating-mode/mode-env.d.ts +32 -54
  411. package/dist/operating-mode/mode-env.js +36 -58
  412. package/dist/operating-mode/types.d.ts +24 -72
  413. package/dist/operating-mode/types.js +1 -7
  414. package/dist/paths/branch-name.d.ts +30 -40
  415. package/dist/paths/branch-name.js +30 -40
  416. package/dist/paths/branch.d.ts +4 -18
  417. package/dist/paths/branch.js +6 -22
  418. package/dist/paths/index.d.ts +2 -13
  419. package/dist/paths/index.js +3 -23
  420. package/dist/paths/normalize-server.d.ts +3 -15
  421. package/dist/paths/normalize-server.js +3 -15
  422. package/dist/paths/normalize.d.ts +13 -33
  423. package/dist/paths/normalize.js +13 -33
  424. package/dist/paths/resolve.d.ts +4 -17
  425. package/dist/paths/resolve.js +4 -22
  426. package/dist/paths/types.d.ts +12 -34
  427. package/dist/paths/types.js +5 -7
  428. package/dist/paths/validation.d.ts +22 -119
  429. package/dist/paths/validation.js +30 -148
  430. package/dist/reference-resolver.d.ts +0 -13
  431. package/dist/reference-resolver.js +0 -18
  432. package/dist/resolve-canopy-user.d.ts +16 -18
  433. package/dist/resolve-canopy-user.js +17 -26
  434. package/dist/resource-generation.d.ts +16 -25
  435. package/dist/resource-generation.js +34 -97
  436. package/dist/schema/meta-loader.d.ts +2 -14
  437. package/dist/schema/meta-loader.js +15 -64
  438. package/dist/schema/resolver.d.ts +1 -4
  439. package/dist/schema/resolver.js +1 -6
  440. package/dist/schema/schema-store-types.d.ts +0 -12
  441. package/dist/schema/schema-store.d.ts +61 -138
  442. package/dist/schema/schema-store.js +85 -282
  443. package/dist/schema/types.d.ts +0 -4
  444. package/dist/server.d.ts +62 -93
  445. package/dist/server.js +63 -94
  446. package/dist/services.d.ts +23 -47
  447. package/dist/services.js +55 -98
  448. package/dist/settings-workspace.d.ts +17 -30
  449. package/dist/settings-workspace.js +51 -81
  450. package/dist/static/index.d.ts +26 -52
  451. package/dist/static/index.js +33 -67
  452. package/dist/{worker/task-queue.d.ts → task-queue/cms-task-queue.d.ts} +7 -9
  453. package/dist/task-queue/cms-task-queue.js +13 -0
  454. package/dist/task-queue/task-queue-config.d.ts +3 -0
  455. package/dist/{worker → task-queue}/task-queue-config.js +1 -6
  456. package/dist/task-queue/task-queue.d.ts +16 -22
  457. package/dist/task-queue/task-queue.js +20 -44
  458. package/dist/task-queue/types.d.ts +0 -1
  459. package/dist/task-queue/worker-status.d.ts +35 -0
  460. package/dist/task-queue/worker-status.js +40 -0
  461. package/dist/types.d.ts +40 -51
  462. package/dist/url-collision.d.ts +26 -40
  463. package/dist/url-collision.js +47 -63
  464. package/dist/url-exclusivity-fixtures.d.ts +15 -20
  465. package/dist/url-exclusivity-fixtures.js +14 -19
  466. package/dist/url-path-resolver.d.ts +8 -16
  467. package/dist/url-path-resolver.js +19 -29
  468. package/dist/user.d.ts +8 -21
  469. package/dist/user.js +10 -19
  470. package/dist/utils/async-mutex.js +4 -6
  471. package/dist/utils/atomic-write.d.ts +2 -11
  472. package/dist/utils/atomic-write.js +2 -11
  473. package/dist/utils/body-field.d.ts +8 -15
  474. package/dist/utils/body-field.js +8 -15
  475. package/dist/utils/content-serialize.d.ts +18 -24
  476. package/dist/utils/content-serialize.js +109 -160
  477. package/dist/utils/content-write-lock.d.ts +51 -106
  478. package/dist/utils/content-write-lock.js +66 -111
  479. package/dist/utils/debug.d.ts +7 -23
  480. package/dist/utils/debug.js +7 -23
  481. package/dist/utils/entry-url.d.ts +11 -23
  482. package/dist/utils/entry-url.js +11 -26
  483. package/dist/utils/error.d.ts +25 -90
  484. package/dist/utils/error.js +60 -147
  485. package/dist/utils/flatten-group-fields.d.ts +5 -7
  486. package/dist/utils/flatten-group-fields.js +5 -7
  487. package/dist/utils/format.d.ts +1 -5
  488. package/dist/utils/format.js +1 -5
  489. package/dist/utils/fs.d.ts +3 -4
  490. package/dist/utils/fs.js +3 -4
  491. package/dist/utils/git.d.ts +52 -83
  492. package/dist/utils/git.js +79 -125
  493. package/dist/utils/logger.d.ts +35 -50
  494. package/dist/utils/logger.js +35 -52
  495. package/dist/utils/provisioning-lock.d.ts +23 -35
  496. package/dist/utils/provisioning-lock.js +49 -69
  497. package/dist/utils/sanitize-href.d.ts +5 -10
  498. package/dist/utils/sanitize-href.js +8 -18
  499. package/dist/utils/title-field.d.ts +17 -20
  500. package/dist/utils/title-field.js +18 -27
  501. package/dist/utils/typed-filename.d.ts +20 -48
  502. package/dist/utils/typed-filename.js +25 -61
  503. package/dist/utils/url-prefix.d.ts +3 -3
  504. package/dist/utils/url-prefix.js +3 -3
  505. package/dist/validation/block-structural-keys.d.ts +16 -31
  506. package/dist/validation/block-structural-keys.js +19 -29
  507. package/dist/validation/deletion-checker.d.ts +0 -34
  508. package/dist/validation/deletion-checker.js +1 -43
  509. package/dist/validation/entry-link-validator.d.ts +6 -13
  510. package/dist/validation/entry-link-validator.js +4 -13
  511. package/dist/validation/entry-type-reference-validator.d.ts +13 -18
  512. package/dist/validation/entry-type-reference-validator.js +13 -18
  513. package/dist/validation/entry-validator.d.ts +13 -25
  514. package/dist/validation/entry-validator.js +15 -32
  515. package/dist/validation/field-traversal.d.ts +3 -30
  516. package/dist/validation/field-traversal.js +1 -32
  517. package/dist/validation/reference-validator.d.ts +5 -24
  518. package/dist/validation/reference-validator.js +5 -30
  519. package/dist/worker/cms-worker.d.ts +172 -232
  520. package/dist/worker/cms-worker.js +241 -333
  521. package/dist/worker/git-sync.d.ts +32 -133
  522. package/dist/worker/git-sync.js +129 -191
  523. package/dist/worker/github-auth.d.ts +13 -19
  524. package/dist/worker/github-auth.js +13 -19
  525. package/dist/worker/history-rewrite.d.ts +53 -66
  526. package/dist/worker/history-rewrite.js +54 -60
  527. package/dist/worker/log.d.ts +24 -41
  528. package/dist/worker/log.js +25 -42
  529. package/dist/worker/rebase.d.ts +20 -110
  530. package/dist/worker/rebase.js +279 -354
  531. package/dist/worker/task-runner.d.ts +32 -66
  532. package/dist/worker/task-runner.js +126 -162
  533. package/dist/worker/worker-context.d.ts +41 -81
  534. package/package.json +4 -4
  535. package/dist/cli/template-files/middleware-clerk.ts.template +0 -37
  536. package/dist/worker/task-queue-config.d.ts +0 -8
  537. package/dist/worker/task-queue.js +0 -19
  538. package/dist/worker/worker-status.d.ts +0 -47
  539. package/dist/worker/worker-status.js +0 -52
@@ -52,38 +52,29 @@ export declare class ContentConflictError extends Error {
52
52
  }
53
53
  /**
54
54
  * [SYNC-C1] Thrown when a mutation could not take the branch's cross-host
55
- * content-write lock within its bounded wait -- in practice, the worker is
56
- * mid-rebase on this branch's working tree (utils/content-write-lock.ts).
55
+ * content-write lock within its bounded wait (utils/content-write-lock.ts) --
56
+ * usually the worker mid-rebase on this branch's working tree, though
57
+ * writer-vs-writer contention raises it too (see ContentWriteLockBusyError).
57
58
  *
58
- * A `ContentConflictError` subclass so every existing 409 mapping keeps
59
- * working unchanged; the distinct type exists so the API can surface THIS
60
- * message ("the branch is busy, retry") instead of the generic "modified by
61
- * another editor", which would be actively misleading. The default wording
62
- * covers writer-vs-writer contention too, which this lock also produces --
63
- * see ContentWriteLockBusyError.
59
+ * A `ContentConflictError` subclass so every 409 mapping keeps working; the
60
+ * distinct type lets the API surface THIS message ("the branch is busy, retry")
61
+ * instead of the misleading generic "modified by another editor".
64
62
  */
65
63
  export declare class BranchSyncingError extends ContentConflictError {
66
64
  constructor(message: string);
67
65
  }
68
66
  /**
69
- * [F1] Thrown when a save's content ID is carried by MORE THAN ONE file in
70
- * the branch's content tree -- the duplicate-ID state `ContentIdIndex`
71
- * quarantines (see its "Duplicate-ID quarantine" section).
67
+ * [F1] Thrown when a save's content ID is carried by MORE THAN ONE file in the branch's
68
+ * content tree -- the duplicate-ID state `ContentIdIndex` quarantines.
72
69
  *
73
- * Why refuse rather than write: with two files sharing one ID, "this entry"
74
- * is ambiguous, and every way of proceeding is worse than stopping.
75
- * Following the index would mutate (and, via the slug-change cleanup, DELETE)
76
- * a file the caller never addressed -- the data-loss bug this class exists to
77
- * prevent. Writing only the addressed file would succeed silently into a file
78
- * that is invisible to every ID-based lookup (reads-by-id, references,
79
- * listings all resolve to the OTHER copy) and that the repair action later
80
- * archives away, so the editor's work would appear to evaporate with no error
81
- * anywhere. Refusing mutates nothing under any interleaving, and says what is
82
- * wrong and who can fix it.
70
+ * Refuse rather than write: with two files sharing one ID "this entry" is ambiguous and every
71
+ * way of proceeding is worse. Following the index would DELETE (via the slug-change cleanup) a
72
+ * file the caller never addressed; writing only the addressed file would succeed into a file
73
+ * invisible to every ID-based lookup and later archived by the repair action, so the editor's
74
+ * work evaporates with no error anywhere. Refusing mutates nothing under any interleaving.
83
75
  *
84
- * A `ContentConflictError` subclass so every existing 409 mapping keeps
85
- * working; the distinct type exists so the API can surface THIS message
86
- * rather than the generic "modified by another editor", which would send the
76
+ * A `ContentConflictError` subclass so every 409 mapping keeps working; the distinct type lets
77
+ * the API surface THIS message rather than "modified by another editor", which would send the
87
78
  * editor into a reload-and-retry loop that cannot succeed.
88
79
  */
89
80
  export declare class DuplicateContentIdError extends ContentConflictError {
@@ -94,27 +85,22 @@ export declare class DuplicateContentIdError extends ContentConflictError {
94
85
  }
95
86
  /**
96
87
  * Thrown when a create or rename would give a SECOND entry a `urlPath` another entry already
97
- * holds -- the write-boundary half of the invariant `assertNoDuplicateUrlPaths` enforces at build
98
- * time (see url-collision.ts for which shapes count and, just as importantly, which do not).
88
+ * holds -- the write-boundary half of the invariant `assertNoDuplicateUrlPaths` enforces at
89
+ * build time (url-collision.ts says which shapes count and which deliberately do not).
99
90
  *
100
- * Why refuse rather than write: only one of the two entries can be served at that URL, so the
101
- * other silently has no route anywhere. Allowing the write trades a clear error now for a page
102
- * that quietly does not exist later -- and, because the loser is picked by resolver precedence
103
- * rather than by the author, not necessarily the page they were editing.
91
+ * Refuse rather than write: only one of the two can be served at that URL, so the other
92
+ * silently has no route anywhere -- and the loser is picked by resolver precedence rather than
93
+ * by the author, so not necessarily the page they were editing.
104
94
  *
105
- * A `ContentConflictError` subclass so every existing 409 mapping keeps working; the distinct
106
- * type exists so the API can surface THIS message rather than the generic "modified by another
107
- * editor", which would send the editor into a reload-and-retry loop that cannot succeed.
95
+ * A `ContentConflictError` subclass so every 409 mapping keeps working; the distinct type lets
96
+ * the API surface THIS message rather than the generic "modified by another editor".
108
97
  */
109
98
  export declare class UrlPathConflictError extends ContentConflictError {
110
99
  /** Absolute path of the entry already holding the contested URL. */
111
100
  readonly conflictingPath: string;
112
101
  constructor(message: string, conflictingPath: string);
113
102
  }
114
- /**
115
- * Get the default entry type from a collection's entries array.
116
- * Returns the entry marked as default, or the first one, or undefined if no entries.
117
- */
103
+ /** The entry type marked `default`, else the first, else undefined. */
118
104
  export declare function getDefaultEntryType(entries: readonly EntryTypeConfig[] | undefined): EntryTypeConfig | undefined;
119
105
  export interface ContentStoreOptions {
120
106
  /**
@@ -126,21 +112,17 @@ export interface ContentStoreOptions {
126
112
  indexFreshnessIntervalMs?: number;
127
113
  /**
128
114
  * Directory name (relative to `root`) holding the content tree — i.e.
129
- * `config.contentRoot`. The ID index scans from here, so an adopter with a
130
- * non-default content root would otherwise get an index built from a
131
- * directory that does not exist: empty, so every ID-based lookup (reference
132
- * resolution, entry links, order cleanup, rename) silently misses while
133
- * path-based reads keep working.
134
- *
115
+ * `config.contentRoot`. The ID index scans from here, so a non-default content
116
+ * root would otherwise build the index from a directory that does not exist:
117
+ * empty, so every ID-based lookup (reference resolution, entry links, order
118
+ * cleanup, rename) silently misses while path-based reads keep working.
135
119
  * Defaults to 'content'.
136
120
  *
137
- * ENFORCED BY LINT, not by the type. Omitting it is silent and data-shaped
138
- * when wrong -- nothing throws, nothing logs, and only ID-addressed lookups
139
- * miss -- so every production construction must pass it. That is checked by
140
- * the `no-restricted-syntax` rule on `new ContentStore(...)` in
141
- * eslint.config.mjs, which is scoped to non-test sources: making the field
142
- * required in the TYPE would have forced the argument on 61 test call sites
143
- * that legitimately want the default, for no gain in the 11 production ones.
121
+ * ENFORCED BY LINT, not by the type -- the `no-restricted-syntax` rule on
122
+ * `new ContentStore(...)` in eslint.config.mjs, scoped to non-test sources.
123
+ * Omitting it is silent and data-shaped when wrong, so every production
124
+ * construction must pass it; making the field required in the TYPE would force
125
+ * the argument on dozens of test call sites that legitimately want the default.
144
126
  */
145
127
  contentRootName?: string;
146
128
  /**
@@ -152,41 +134,25 @@ export interface ContentStoreOptions {
152
134
  contentWriteLockWaitMs?: number;
153
135
  }
154
136
  /**
155
- * Per-batch memo for reference resolution, keyed by content ID.
156
- *
157
- * Exists because a batch surface resolves the SAME reference over and over: a shared
158
- * block ("call to action", "promo card") referenced by 40 pages costs 40 separate
159
- * `read()`s of one small file in a single `listEntries()` pass, and a search-index build
160
- * over thousands of entries multiplies that. Pass one cache through a whole batch and
161
- * each distinct target is read once.
137
+ * Per-batch memo for reference resolution, keyed by content ID (plus `includeBody`).
162
138
  *
163
- * Values are the in-flight **promise**, not the settled value, so concurrent lookups
164
- * inside a `Promise.all` collapse onto one read rather than each starting their own —
165
- * the same in-flight dedup `ContentStore.indexBuild` uses for index rebuilds.
139
+ * A batch surface resolves the SAME reference over and over: a shared block referenced by 40
140
+ * pages costs 40 separate `read()`s of one small file in a single `listEntries()` pass. One
141
+ * cache per batch and each distinct target is read once.
166
142
  *
167
- * What is cached is the READ, not the object handed out: every occurrence receives its own
168
- * deep copy, so a caller mutating one resolved reference cannot rewrite it for the other 39
169
- * entries pointing at the same target. See `resolveSingleReference` for why that matters and
170
- * why it does not undo the saving.
143
+ * Values are the in-flight **promise**, so concurrent lookups inside a `Promise.all` collapse
144
+ * onto one read. What is cached is the READ, not the object handed out: every occurrence gets
145
+ * its own deep copy (see `resolveSingleReference`).
171
146
  *
172
- * ## Lifetime and invalidation
147
+ * Lifetime is one `listEntries()` / `buildContentTree()` call, with no invalidation -- strictly
148
+ * shorter than the `ContentStore` whose memoized `idIndex()` it sits on, so it adds no
149
+ * staleness window and is out of scope for the generation-marker protocol in
150
+ * `docs/concurrency.md`. Never make one module-global, persist one, or reuse one across
151
+ * requests.
173
152
  *
174
- * There is no invalidation, and that is the design: a cache lives inside a single
175
- * `listEntries()` / `buildContentTree()` call and is dropped when it returns. That is
176
- * strictly shorter than the lifetime of the `ContentStore` whose memoized `idIndex()` it
177
- * sits on top of, so it introduces no staleness window that store did not already have,
178
- * and it is out of scope for the generation-marker protocol in
179
- * `docs/concurrency.md` (which governs caches rebuilt by scanning that OUTLIVE the
180
- * mutations they can miss). Never make one module-global, never persist one, and never
181
- * reuse one across requests.
182
- *
183
- * Misses are memoized alongside hits, deliberately: one batch should be internally
184
- * coherent, and a shared block resolving to data on page 1 and to `null` on page 40 of
185
- * the same sitemap is worse than either consistent answer. The accepted cost is that a
186
- * later occurrence in the same batch can no longer get incidentally lucky after a
187
- * sibling lookup wins the stale-index refresh throttle. Each DISTINCT id still gets its
188
- * full self-healing retry, because that retry happens inside the memoized promise (see
189
- * `resolveSingleReference`).
153
+ * Misses are memoized alongside hits so one batch stays internally coherent: a shared block
154
+ * resolving to data on page 1 and `null` on page 40 of one sitemap is worse than either
155
+ * consistent answer. Each DISTINCT id still gets its full self-healing retry, inside the memo.
190
156
  */
191
157
  export type ReferenceResolveCache = Map<string, Promise<Record<string, unknown> | null>>;
192
158
  /** Create a cache for one batch of reference resolution. See {@link ReferenceResolveCache}. */
@@ -218,20 +184,16 @@ export declare class ContentStore {
218
184
  constructor(root: string, flatSchema: FlatSchemaItem[], options?: ContentStoreOptions);
219
185
  /**
220
186
  * [SYNC-C1] Run a working-tree mutation under the branch's cross-host
221
- * content-write lock (utils/content-write-lock.ts), on top of the
222
- * in-process locks the callee takes for itself.
223
- *
224
- * The in-process mutex serializes writers inside ONE process; it says
225
- * nothing about the EC2 worker rebasing this same tree on shared EFS, which
226
- * destroys an in-flight save (`checkout --theirs` overwrites it and the
227
- * rebase then reports success; `rebase --abort` hard-resets it). This is the
228
- * layer that actually excludes the two.
187
+ * content-write lock (utils/content-write-lock.ts), on top of the in-process
188
+ * locks the callee takes for itself. The in-process mutex serializes writers
189
+ * inside ONE process and says nothing about the EC2 worker rebasing this same
190
+ * tree on shared EFS, which destroys an in-flight save.
229
191
  *
230
- * Reads deliberately do NOT take it -- an extra EFS round-trip per read is
231
- * not an acceptable cost, and reads cannot be destroyed by a rebase.
192
+ * Reads deliberately do NOT take it: an extra EFS round-trip per read is not an
193
+ * acceptable cost, and a rebase cannot destroy a read.
232
194
  *
233
- * Acquisition order is always content lock -> `withLock`, never the reverse,
234
- * so the two cannot deadlock.
195
+ * Acquisition order is always content lock -> `withLock`, never the reverse, so
196
+ * the two cannot deadlock.
235
197
  */
236
198
  private withContentWriteExclusion;
237
199
  /**
@@ -239,18 +201,16 @@ export declare class ContentStore {
239
201
  *
240
202
  * Called (via content-index-registry) after in-process operations that change the
241
203
  * working tree underneath this store — git checkout/merge/rebase, the worker's
242
- * rebase loop, and sync-core's content replacement. Without this, ID→path lookups
243
- * keep resolving to pre-mutation paths and saves can target moved/deleted files.
244
- *
245
- * Cheap: only bumps a generation counter; the rebuild happens lazily.
204
+ * rebase loop, sync-core's content replacement. Without it, ID→path lookups keep
205
+ * resolving to pre-mutation paths and saves can target moved/deleted files.
206
+ * Cheap: bumps a generation counter, the rebuild is lazy.
246
207
  */
247
208
  invalidateIndex(): void;
248
209
  /**
249
- * Get the ID index, ensuring it's loaded and current first.
250
- * Loads lazily on first access and rebuilds after invalidateIndex(); repeated
251
- * accesses with no intervening invalidation reuse the already-built index,
252
- * except for a throttled probe of the on-disk generation marker that detects
253
- * mutations made by OTHER processes sharing this root (e.g. on EFS).
210
+ * The ID index, rebuilt first if stale. Loads lazily, rebuilds after
211
+ * invalidateIndex(), and otherwise reuses the built index apart from a throttled
212
+ * probe of the on-disk generation marker, which detects mutations by OTHER
213
+ * processes sharing this root (e.g. on EFS).
254
214
  */
255
215
  idIndex(): Promise<ContentIdIndex>;
256
216
  /**
@@ -260,157 +220,126 @@ export declare class ContentStore {
260
220
  */
261
221
  private shouldProbeDiskGeneration;
262
222
  /**
263
- * After one of this store's own successful mutations: publish the change to
264
- * other processes (bump the on-disk marker) and adopt the written token,
265
- * since the in-memory index was already updated incrementally — avoiding a
266
- * pointless self-rescan on the next probe. Adoption is skipped unless the
267
- * index is quiescent and `updatedIndex` is still the live instance: if a
268
- * rebuild raced with the mutation, its scan may predate our file change, so
269
- * we leave the recorded token older and let the next probe observe our bump
270
- * and trigger the healing rebuild.
223
+ * After one of this store's own successful mutations: publish it to other
224
+ * processes (bump the on-disk marker) and adopt the written token, since the
225
+ * in-memory index was already updated incrementally. Adoption is skipped unless
226
+ * the index is quiescent and `updatedIndex` is still the live instance: a rebuild
227
+ * that raced the mutation may have scanned before our file change, so leaving the
228
+ * recorded token older lets the next probe observe our bump and heal.
271
229
  */
272
230
  private recordOwnMutation;
273
231
  /**
274
- * Backstop for the residual staleness windows of the cross-process marker
275
- * (NFS attribute caching, probe throttle, self-adoption — see
276
- * content-index-generation.ts): force one rebuild in response to a
277
- * suspicious lookup (an ID miss, or an index hit whose file is gone), but
278
- * throttle how often a caller can force one. Time-boxed so genuinely
279
- * dangling IDs cost at most one rescan per window. Returns true if a
280
- * refresh was performed by THIS call; callers that lose the throttle race
281
- * still benefit — idIndex() dedupes concurrent builds, so a caller that won
282
- * the race rebuilds the index for everyone, and callers should still retry
283
- * their lookup against the live index regardless of this return value.
232
+ * Backstop for the residual staleness windows of the cross-process marker (NFS
233
+ * attribute caching, probe throttle, self-adoption — see
234
+ * content-index-generation.ts): force one rebuild in response to a suspicious
235
+ * lookup (an ID miss, or an index hit whose file is gone), throttled so a
236
+ * genuinely dangling ID costs at most one rescan per window. Returns true only if
237
+ * THIS call refreshed; a caller that loses the throttle race must still retry its
238
+ * lookup, since idIndex() dedupes concurrent builds and the winner rebuilds for
239
+ * everyone.
284
240
  */
285
241
  private refreshIndexForSuspiciousLookup;
286
242
  /**
287
- * Every file in `dir` whose filename embeds `id`, as root-relative paths
288
- * (empty when there is no such file, or no such dir).
243
+ * Every file in `dir` whose filename embeds `id`, as root-relative paths (empty
244
+ * when there is no such file, or no such dir).
289
245
  *
290
- * [F1] Returns ALL matches, not the first: more than one match is a
291
- * duplicate-ID pair, and callers must be able to tell that apart from a
292
- * clean single hit rather than silently picking whichever one `readdir()`
293
- * happened to yield first.
246
+ * [F1] Returns ALL matches, not the first: more than one match is a duplicate-ID
247
+ * pair, and callers must be able to tell that apart from a clean single hit rather
248
+ * than silently picking whichever one `readdir()` happened to yield first.
294
249
  */
295
250
  private findEntryPathsById;
296
- /**
297
- * Get all schema items for iteration.
298
- * Used internally by ReferenceResolver for path matching.
299
- */
300
251
  getSchemaItems(): IterableIterator<FlatSchemaItem>;
301
252
  private assertSchemaItem;
302
253
  /**
303
254
  * Is this path a COLLECTION schema item?
304
255
  *
305
256
  * The non-throwing form of `assertCollection`, reading the same `schemaIndex` -- which is the
306
- * point. A caller that gates on this cannot disagree with what `buildPaths` will then do: the
307
- * Map is last-wins, so where a subcollection's path collides with a parent's entry-type name
308
- * both this and `buildPaths` see the collection. A `find` over the flat schema LIST is
309
- * first-wins and would not.
257
+ * point: a caller that gates on this cannot disagree with what `buildPaths` will then do,
258
+ * because the Map is last-wins where a subcollection's path collides with a parent's
259
+ * entry-type name. A `find` over the flat schema LIST is first-wins and would not.
310
260
  *
311
- * Type-only, deliberately -- `resolvePath` additionally requires `entries`, but a collection
312
- * with subcollections and no entries of its own is legal, and mirroring that stricter test here
313
- * would reject something `buildPaths` accepts.
261
+ * Type-only, deliberately: `resolvePath` additionally requires `entries`, but a collection
262
+ * with subcollections and no entries of its own is legal, and testing for that here would
263
+ * reject something `buildPaths` accepts.
314
264
  *
315
265
  * Exists for `readByUrlPath`'s URL-addressability gate; see `ReadContentInput`'s
316
- * `urlAddressableOnly` and the note on `buildPaths`' entry-type branch below.
266
+ * `urlAddressableOnly`.
317
267
  */
318
268
  isCollectionPath(collectionPath: LogicalPath): boolean;
319
269
  /**
320
270
  * Does this collection declare `entryTypeName` in its `entries` config?
321
271
  *
322
- * Mirrors `parseTypedFilename(filename, collection.entries)`, which is how `listEntries` decides
323
- * whether a file on disk is one of the collection's entries at all. `buildPaths`' own directory
324
- * scan deliberately does NOT check this -- it matches on slug alone, so that an entry whose type
325
- * was renamed out of the schema stays findable and therefore still editable, renameable and
326
- * deletable. Only URL resolution consults this, so what enumeration hides is not served.
272
+ * Mirrors `parseTypedFilename(filename, collection.entries)`, which is how `listEntries`
273
+ * decides whether a file on disk is one of the collection's entries at all. `buildPaths`'
274
+ * own directory scan deliberately does NOT check this -- it matches on slug alone, so an
275
+ * entry whose type was renamed out of the schema stays findable and therefore editable,
276
+ * renameable and deletable. Only URL resolution consults this, so what enumeration hides is
277
+ * not served.
327
278
  *
328
- * Returns FALSE when the collection declares no `entries` at all, which is stricter than
329
- * `parseTypedFilename`'s own `if (entryTypes && ...)` guard and deliberately so: the enumerating
330
- * surface is not `parseTypedFilename`, it is `listCollectionEntries`, and that returns `[]`
331
- * outright for a collection with no `entries`. A collections-only container therefore publishes
332
- * nothing, so a URL read that resolved a file sitting in one would be answering where nothing is
333
- * advertised -- the exact disagreement this predicate exists to close. Such a file cannot have
334
- * been created by the CMS (there is no entry type to create it as); it arrived by hand, by merge
335
- * or by retrofit, and it is invisible to the sitemap and to static params either way.
279
+ * Returns FALSE when the collection declares no `entries` at all -- stricter than
280
+ * `parseTypedFilename`'s own guard, and deliberately so: the enumerating surface is
281
+ * `listCollectionEntries`, which returns `[]` outright for such a collection, so a URL read
282
+ * resolving a file sitting in one would answer where nothing is advertised.
336
283
  *
337
- * Returns true for a path that is not a collection at all, because that is rule 1's question,
338
- * not this one's -- and under `urlAddressableOnly` rule 1 has already rejected it.
284
+ * Returns true for a path that is not a collection at all, because that is rule 1's
285
+ * question, and under `urlAddressableOnly` rule 1 has already rejected it.
339
286
  */
340
287
  declaresEntryType(collectionPath: LogicalPath, entryTypeName: string): boolean;
341
288
  private assertCollection;
342
289
  /**
343
- * Lock key for an existing entry, addressed by its permanent content ID
344
- * (stable across renames -- the whole point of this locking scheme; see
345
- * .claude/future-tasks/resolved/content-store-lock-key.md). Namespaced by store
346
- * root: the same content ID exists in every clone of a branch, and one
347
- * process can hold ContentStore instances on several clones (dev
348
- * workspace roots, prod branch clones) at once, so the root prefix keeps
349
- * the keyspace scoped per-store. It also keeps this ID keyspace disjoint
350
- * from other modules' raw-path-keyed locks sharing the same withLock()
351
- * map (comment-store, branch-metadata, etc).
290
+ * Lock key for an existing entry, addressed by its permanent content ID --
291
+ * stable across renames, which is the whole point of this locking scheme.
292
+ * Namespaced by store root: the same content ID exists in every clone of a
293
+ * branch and one process can hold stores on several clones at once, so the
294
+ * prefix scopes the keyspace per store and keeps it disjoint from other
295
+ * modules' raw-path-keyed locks sharing the same withLock() map.
352
296
  */
353
297
  private idLockKey;
354
298
  /**
355
- * Stable collection+slug identifier for lock-key namespacing. Mirrors the
356
- * schemaItem resolution buildPaths() performs for entry-type delegation:
357
- * an entry-type item delegates to its parent collection with the slug
358
- * defaulting to the entry type's own name (see buildPaths()'s
359
- * `schemaItem.type === 'entry-type'` branch).
299
+ * Stable collection+slug identifier for lock-key namespacing. Mirrors
300
+ * buildPaths()' entry-type delegation: an entry-type item delegates to its
301
+ * parent collection with the slug defaulting to the entry type's own name.
360
302
  */
361
303
  private collectionSlugKey;
362
304
  /**
363
305
  * Lock key for a not-yet-existing entry (a create). Serializes concurrent
364
- * same-slug creates WITHIN this process: the second call's in-lock
365
- * buildPaths() re-resolution will find the first call's just-written file
366
- * and fold in as an edit (see write()). A concurrent create racing from a
367
- * DIFFERENT process is NOT covered by this in-process mutex -- accepted
368
- * per the epic's design review: each writer mints its own fresh ID and
369
- * writes its own distinct filename, so both writes succeed and the result
370
- * is two same-slug files with different IDs (a slug-uniqueness violation
371
- * surfaced on the next listing/lookup), never a duplicate-ID collision
372
- * that would poison index rebuilds.
306
+ * same-slug creates WITHIN this process: the second call's in-lock buildPaths()
307
+ * re-resolution finds the first call's just-written file and folds in as an edit.
308
+ *
309
+ * A create racing from a DIFFERENT process is NOT covered by this in-process
310
+ * mutex, and that is accepted: each writer mints its own fresh ID and filename, so
311
+ * both writes succeed and the result is two same-slug files with different IDs (a
312
+ * slug-uniqueness violation surfaced on the next listing/lookup), never a
313
+ * duplicate-ID collision that would poison index rebuilds.
373
314
  */
374
315
  private createLockKey;
375
316
  /**
376
- * Lock key for a write()/delete() pre-pass classification. Existing
377
- * entries lock on their content ID (idLockKey()). Legacy entries with no
378
- * embedded ID (pre-ID-era filenames -- renameEntry() already refuses to
379
- * touch these, via its four-part filename check, so there is no rename
380
- * race to guard against for them) fall back to the physical path, matching
381
- * this store's original locking behavior for that narrow case. Not-yet-
382
- * existing entries lock on a per-slug create-key (createLockKey()).
317
+ * Lock key for a write()/delete() pre-pass classification: an existing entry
318
+ * locks on its content ID (idLockKey()), a not-yet-existing one on a per-slug
319
+ * create-key (createLockKey()). An entry with no embedded ID falls back to its
320
+ * physical path -- renameEntry() refuses to touch those (its four-part filename
321
+ * check), so they have no rename race to guard against.
383
322
  */
384
323
  private entryLockKey;
385
324
  /**
386
325
  * Refuse a create/rename that would give a second entry a `urlPath` another entry already
387
326
  * holds. See url-collision.ts for the two shapes that count, and the legitimate
388
327
  * landing-page-beside-a-collection shape that deliberately does not.
389
- *
390
- * @param collectionDir Absolute physical directory the entry will live in.
391
- * @param slug The entry's slug, as it will be written.
392
328
  */
393
329
  private assertUrlPathAvailable;
394
330
  /**
395
- * Build absolute and relative paths with security validation.
396
- * All entries use the unified filename pattern: {type}.{slug}.{id}.{ext}
397
- *
398
- * SECURITY BOUNDARY: This method prevents path traversal attacks by:
399
- * 1. Validating that resolved paths stay within the content root
400
- * 2. Checking slugs for malicious patterns (via validateSlug)
401
- * 3. Using path.resolve to normalize paths before validation
331
+ * Build absolute and relative paths for an entry, filename pattern
332
+ * `{type}.{slug}.{id}.{ext}`. `options.existingId` addresses a known entry; without it a
333
+ * new ID is generated. `options.entryTypeName` picks among a collection's entry types,
334
+ * defaulting to its default one.
402
335
  *
403
- * This validation is performed BEFORE file I/O in resolveDocumentPath(),
404
- * ensuring permission checks happen before any file system access.
405
- *
406
- * @param options.existingId - Optional ID to use (for edits). If not provided, generates new ID.
407
- * @param options.entryTypeName - For collections with multiple entry types, specify which one to use. Defaults to the default entry type.
336
+ * SECURITY BOUNDARY against path traversal: resolved paths must stay inside the content
337
+ * root, slugs go through `validateSlug`, and everything is `path.resolve`d before those
338
+ * checks. All of it runs BEFORE any file I/O in resolveDocumentPath(), so permission checks
339
+ * happen before filesystem access.
408
340
  */
409
341
  private buildPaths;
410
- /**
411
- * Path resolution: resolves a URL path to a schema item
412
- * - Try as collection + slug (last segment = slug)
413
- */
342
+ /** Resolve path segments to a collection schema item plus the trailing slug. */
414
343
  resolvePath(pathSegments: string[]): {
415
344
  schemaItem: FlatSchemaItem;
416
345
  slug: Slug;
@@ -420,21 +349,17 @@ export declare class ContentStore {
420
349
  relativePath: PhysicalPath;
421
350
  id?: string;
422
351
  /**
423
- * Always populated for a valid schema item: the collection branch below
424
- * always resolves a name (falling back to `'entry'` when the collection
425
- * has no matching/default entry type config), and the entry-type branch
426
- * delegates to it with its own name set explicitly. Only non-collection,
427
- * non-entry-type schema items (impossible via the public API, which only
428
- * ever resolves to one of those two) skip both branches, hitting the
429
- * throw below instead of returning at all.
352
+ * Always populated for a valid schema item: the collection branch below resolves a name
353
+ * (falling back to `'entry'` when the collection has no matching/default entry type
354
+ * config) and the entry-type branch delegates to it with its own name. Anything else
355
+ * throws below rather than returning.
430
356
  */
431
357
  entryTypeName: string;
432
358
  /**
433
- * True if this slug already had a file on disk (a directory-scan finding,
434
- * or `options.existingId` asserting one) -- i.e. this resolution is an
435
- * edit, not a create. Used by write()/delete()/renameEntry() to pick a
436
- * stable lock key (see entryLockKey()); NOT the same as `id` being set,
437
- * since a brand-new entry also gets an `id` (freshly generated below).
359
+ * True if this slug already had a file on disk (the directory scan found one, or
360
+ * `options.existingId` asserts one) -- i.e. this resolution is an edit, not a create.
361
+ * write()/delete()/renameEntry() use it to pick a stable lock key (see entryLockKey()).
362
+ * NOT the same as `id` being set: a brand-new entry also gets an `id`, generated below.
438
363
  */
439
364
  existed: boolean;
440
365
  }>;
@@ -453,7 +378,6 @@ export declare class ContentStore {
453
378
  readById(id: ContentId): Promise<ContentDocument | null>;
454
379
  private readByIdOnce;
455
380
  /**
456
- * Get the ID for an entry given its collection and slug.
457
381
  * Returns null if no ID exists yet.
458
382
  */
459
383
  getIdForEntry(collectionPath: LogicalPath, slug: Slug): Promise<ContentId | null>;
@@ -464,80 +388,60 @@ export declare class ContentStore {
464
388
  */
465
389
  documentExists(collectionPath: LogicalPath, slug?: Slug | ''): Promise<boolean>;
466
390
  /**
467
- * Resolve the actual on-disk entry type of an existing collection entry, by
468
- * inspecting its filename directly. Entry filenames embed their type
469
- * (`{type}.{slug}.{id}.{ext}`) and it is immutable after creation — see the
470
- * existing-file lookup in buildPaths(), which this mirrors. Returns
471
- * undefined if no entry exists yet at this slug.
391
+ * The on-disk entry type of an existing collection entry, read from its filename
392
+ * (`{type}.{slug}.{id}.{ext}`), which is immutable after creation -- mirroring the
393
+ * existing-file lookup in buildPaths(). Undefined if no entry exists yet at this slug.
472
394
  *
473
- * Used at the API write boundary (api/content.ts) to validate an existing
474
- * entry's payload against ITS real schema rather than a caller-supplied (or
475
- * omitted/spoofed) `entryType` param — ContentStore.write() preserves the
476
- * on-disk type regardless of what's requested, so validation must agree
477
- * with what will actually be written (post-review M2).
395
+ * The API write boundary (api/content.ts) validates an existing entry's payload against ITS
396
+ * real schema rather than a caller-supplied (or omitted, or spoofed) `entryType` param:
397
+ * ContentStore.write() preserves the on-disk type regardless of what is requested, so
398
+ * validation must agree with what will actually be written.
478
399
  *
479
- * Cheap: one buildPaths() resolution plus a stat, no file content is read —
480
- * same cost class as documentExists().
400
+ * Cheap: one buildPaths() resolution plus a stat, no file content is read.
481
401
  */
482
402
  getExistingEntryType(collectionPath: LogicalPath, slug?: Slug | ''): Promise<string | undefined>;
483
403
  /**
484
- * Count existing entries of a given entry type in a collection, by filename
485
- * (entry filenames embed their type: `{type}.{slug}.{id}.{ext}`). Used to
486
- * enforce `EntryTypeConfig.maxItems` server-side at the create boundary.
404
+ * Count existing entries of a given entry type in a collection, by filename (entry
405
+ * filenames embed their type: `{type}.{slug}.{id}.{ext}`). Enforces
406
+ * `EntryTypeConfig.maxItems` server-side at the create boundary.
487
407
  */
488
408
  countEntriesOfType(collectionPath: LogicalPath, entryTypeName: string): Promise<number>;
489
409
  /**
490
- * Test-only seam: awaited right after the pre-pass buildPaths() resolves
491
- * in delete() and renameEntry(), before the lock key is used to acquire
492
- * anything. No-op in production. A test subclass can override this to
493
- * inject a controlled pause in that exact window, letting a test
494
- * deterministically run OTHER mutations (which don't hold this call's
495
- * not-yet-acquired lock) to completion before this call proceeds to
496
- * acquire its (possibly now-stale) lock key -- see the "Deterministic
497
- * interleavings" testing pattern in docs/concurrency.md and
498
- * branch-registry.test.ts's `BlockingRegistry` for the same idiom.
410
+ * Test-only seam: awaited right after the pre-pass buildPaths() resolves in delete() and
411
+ * renameEntry(), while neither has yet acquired a lock. No-op in production. A test subclass
412
+ * overrides it to pause in that exact window and run OTHER mutations to completion, making
413
+ * the stale-lock-key path deterministic -- see the "Deterministic interleavings" testing
414
+ * pattern in docs/concurrency.md and branch-registry.test.ts's `BlockingRegistry`.
499
415
  */
500
416
  protected afterPrePassForTesting(): Promise<void>;
501
- /**
502
- * Delete an entry and remove it from the index.
503
- */
417
+ /** Delete an entry and remove it from the index. */
504
418
  delete(collectionPath: LogicalPath, slug: Slug): Promise<void>;
505
419
  /**
506
420
  * Rename an entry by changing its slug (middle segment of filename).
507
421
  * Entry filename pattern: {entryTypeName}.{slug}.{id}.{ext}
508
- *
509
- * @param collectionPath - Logical path to the collection
510
- * @param currentSlug - Current slug of the entry
511
- * @param newSlug - New slug (must be unique within collection)
512
- * @returns Object with new logical path
513
- * @throws ContentStoreError if entry doesn't exist, new slug conflicts, or validation fails
514
422
  */
515
423
  renameEntry(collectionPath: LogicalPath, currentSlug: Slug, newSlug: Slug): Promise<{
516
424
  newPath: LogicalPath;
517
425
  }>;
518
- /**
519
- * List all entries in a collection tree (including subcollections).
520
- * For example, passing 'content/data-catalog' returns entries from
521
- * 'content/data-catalog', 'content/data-catalog/partner-a', etc.
522
- * Returns array of entry metadata (relativePath, collection, slug).
523
- * Returns empty array if the collection doesn't exist.
524
- */
525
426
  /**
526
427
  * Resolve a schema collection referenced by name or logical path.
527
428
  *
528
- * Accepts either a full logical path ("content/authors") or a bare
529
- * collection name ("authors") — the contract reference fields use for
530
- * `collections: [...]` (see README). This is the single normalization
531
- * point shared by reference-option loading and reference validation so
532
- * the dropdown and the write boundary can never disagree.
429
+ * Accepts a full logical path ("content/authors") or a bare collection name ("authors") --
430
+ * the contract reference fields use for `collections: [...]` (see README). The single
431
+ * normalization point shared by reference-option loading and reference validation, so the
432
+ * dropdown and the write boundary can never disagree.
533
433
  *
534
- * CAVEAT: the bare-name fallback matches on the LAST path segment and
535
- * returns the first hit in schema order — with two collections sharing a
536
- * leaf name (e.g. content/blog/posts and content/news/posts), a bare
537
- * 'posts' is ambiguous. Use the full logical path in schemas that nest
538
- * same-named collections.
434
+ * CAVEAT: the bare-name fallback matches on the LAST path segment and returns the first hit
435
+ * in schema order, so with two collections sharing a leaf name (content/blog/posts,
436
+ * content/news/posts) a bare 'posts' is ambiguous. Use the full logical path in schemas that
437
+ * nest same-named collections.
539
438
  */
540
439
  resolveCollectionItem(collectionPath: string): FlatSchemaItem | undefined;
440
+ /**
441
+ * Every entry in a collection tree, including subcollections: 'content/data-catalog'
442
+ * returns entries from it and from 'content/data-catalog/partner-a' and so on. Empty when
443
+ * the collection does not exist.
444
+ */
541
445
  getCollectionEntryPaths(collectionPath: LogicalPath): Promise<Array<{
542
446
  relativePath: PhysicalPath;
543
447
  collection: LogicalPath;
@@ -546,87 +450,57 @@ export declare class ContentStore {
546
450
  /**
547
451
  * Resolve every `reference` field in `data` against `fields`, returning a copy.
548
452
  *
549
- * This is what `read()` applies automatically (unless `resolveReferences: false`), exposed
550
- * so BATCH surfaces can opt into the same resolution without duplicating the walk: the
551
- * listing primitives in content-listing.ts and content-tree.ts read entry files straight
552
- * off disk and never touch this store, so before this existed a `reference` field reached
553
- * `listEntries()`/`buildContentTree()` callers as a bare id string or `null`.
554
- *
555
- * Pass one {@link ReferenceResolveCache} across a whole batch so a target referenced by
556
- * many entries is read once rather than once per referencing entry. Omit `cache` and the
557
- * behavior is exactly what `read()` has always done, unmemoized.
453
+ * What `read()` applies automatically (unless `resolveReferences: false`), exposed so BATCH
454
+ * surfaces can opt into the same resolution without duplicating the walk: the listing
455
+ * primitives in content-listing.ts and content-tree.ts read entry files straight off disk
456
+ * and never touch this store. Pass one {@link ReferenceResolveCache} across a whole batch so
457
+ * a target referenced by many entries is read once; omit `cache` for exactly `read()`'s
458
+ * unmemoized behavior.
558
459
  *
559
- * **Path ACLs are not consulted for the targets.** Resolution goes through this store's
560
- * own `read()`, below the per-entry permission check in content-reader.ts — so a resolved
561
- * target may be an entry the caller could not `read()` directly. That is pre-existing
562
- * `read()` behavior, matched here on purpose so one rule covers both; see the
563
- * `resolveReferences` option in content-listing.ts for the note adopters see.
460
+ * **Path ACLs are not consulted for the targets.** Resolution goes through this store's own
461
+ * `read()`, below the per-entry permission check in content-reader.ts, so a resolved target
462
+ * may be an entry the caller could not `read()` directly -- pre-existing `read()` behavior,
463
+ * matched here on purpose so one rule covers both.
564
464
  */
565
465
  resolveReferences(data: Record<string, unknown>, fields: EntrySchema, cache?: ReferenceResolveCache): Promise<Record<string, unknown>>;
566
- /**
567
- * Recursively resolve reference fields in data.
568
- * This traverses objects, arrays, and blocks to find and resolve all reference fields.
569
- */
570
466
  private resolveReferencesInData;
571
467
  /**
572
- * Resolve a single reference ID to full entry data, memoized when a batch cache is
573
- * supplied. Without a cache this is a straight pass-through to the uncached path, so
574
- * `read()` behaves exactly as it always has.
575
- *
576
- * Every occurrence gets its OWN deep copy, even on a cache hit. Without that, the memo
577
- * would hand one shared object to all 40 entries referencing the same block — a mutation
578
- * in one caller's `extract` (truncating a body for a search index, deleting a field) would
579
- * silently rewrite it for every sibling, and both `list: true` elements of `[id, id]` would
580
- * be the same instance. The copy is what keeps the cache a pure performance optimization
581
- * instead of a semantic change.
582
- *
583
- * Note the uncached path is NOT the clean baseline it looks like. For a json/yaml target it
584
- * genuinely reparses fresh per occurrence, but for md/mdx gray-matter serves `data` from a
585
- * process-global content-keyed cache, so `resolveSingleReferenceOnce`'s top-level spread
586
- * severs exactly one level and NESTED frontmatter objects alias across occurrences, calls and
587
- * requests. That is pre-existing `read()` behavior and out of scope here — deliberately, since
588
- * touching it would change `read()` — but it means the cached path is the safer of the two,
589
- * not a relaxation of a guarantee the uncached one provides. See
590
- * `.claude/future-tasks/graymatter-cache-shared-frontmatter.md`.
591
- *
592
- * What the copy costs, measured rather than assumed (2000 occurrences of one target, local
593
- * disk, so every read hits the page cache — the friendliest possible case for NOT caching):
594
- * a snippet-sized target is ~63x cheaper to clone than to re-read-and-parse, while a 265KB
595
- * JSON target is ~0.8x, i.e. cloning is marginally SLOWER than reparsing it. So the win is
596
- * large in the case this exists for and roughly a wash at the pathological end, never a
597
- * blow-up. Two things keep the bad end narrow: an md/mdx target resolves to its FRONTMATTER
598
- * only *unless the field sets `includeBody`* (`read()` puts the body on `doc.body`, which
599
- * `resolveSingleReferenceOnce` spreads in only for an embedding field), so by default body
600
- * size is irrelevant no matter how long the document and only a genuinely huge JSON/YAML
601
- * target reaches the wash. **`includeBody: true` is the case that CAN reach it on markdown**:
602
- * the body then sits inside the memoized object and is cloned once per referencing entry, so
603
- * a long document embedded by many pages pays that repeatedly — the reason `includeBody`
604
- * exists as an opt-in per field rather than as resolution's default. And in the deployment this
605
- * targets, content lives on EFS/NFS where the syscall the memo removes dominates parse and
606
- * clone alike, which the local-disk numbers above understate badly. Correctness is the
607
- * reason for the copy regardless; the numbers are here so nobody has to re-derive them
608
- * before touching this.
468
+ * Resolve a single reference ID to full entry data, memoized when a batch cache is supplied.
469
+ * Without a cache this is a straight pass-through, so `read()` behaves as it always has.
470
+ *
471
+ * Every occurrence gets its OWN deep copy, even on a cache hit. Without that, the memo would
472
+ * hand one shared object to all 40 entries referencing the same block -- a mutation in one
473
+ * caller's `extract` would silently rewrite it for every sibling, and both `list: true`
474
+ * elements of `[id, id]` would be the same instance. The copy is what keeps the cache a pure
475
+ * performance optimization instead of a semantic change.
476
+ *
477
+ * The uncached path is NOT the clean baseline it looks like: for md/mdx, gray-matter serves
478
+ * `data` from a process-global content-keyed cache, so `resolveSingleReferenceOnce`'s
479
+ * top-level spread severs exactly one level and NESTED frontmatter objects alias across
480
+ * occurrences, calls and requests. Pre-existing `read()` behavior, deliberately out of scope
481
+ * here; see `.claude/future-tasks/graymatter-cache-shared-frontmatter.md`.
482
+ *
483
+ * Measured (2000 occurrences of one target, local disk, the friendliest case for NOT
484
+ * caching): cloning a snippet-sized target is ~63x cheaper than re-reading and parsing it, a
485
+ * 265KB JSON target ~0.8x -- a wash at the pathological end, never a blow-up, and on the
486
+ * EFS/NFS this targets the syscall the memo removes dominates both. An md/mdx target
487
+ * resolves to its FRONTMATTER unless the field sets `includeBody`, which is why
488
+ * `includeBody` is opt-in per field: an embedded body sits inside the memoized object and is
489
+ * cloned once per referencing entry.
609
490
  */
610
491
  private resolveSingleReference;
611
492
  /**
612
- * Returns null if the reference is invalid or missing.
613
- * Includes id, slug, and collection fields for debugging.
493
+ * Returns null if the reference is invalid or missing. Includes id, slug and collection.
614
494
  *
615
- * Suspicious results (ID missing from the index, or an index hit whose file
616
- * is gone) trigger one forced index refresh and a retry — self-healing for
617
- * mutations by other processes inside the marker's residual windows. The
618
- * forced refresh itself is throttled (see refreshIndexForSuspiciousLookup),
619
- * but the retry against the live index always runs regardless of whether
620
- * this call won the throttle race: when a `list: true` reference array is
621
- * resolved via Promise.all, every miss shares the same stale snapshot, and
622
- * idIndex() dedupes concurrent builds — so a sibling call that wins the
623
- * throttle and rebuilds heals every other miss in the same batch, not just
624
- * the first.
495
+ * A suspicious result (ID missing from the index, or an index hit whose file is gone)
496
+ * triggers one forced index refresh and a retry — self-healing for mutations by other
497
+ * processes inside the marker's residual windows. The forced refresh is itself throttled
498
+ * (see refreshIndexForSuspiciousLookup) but the retry against the live index always runs:
499
+ * every miss in a `list: true` Promise.all shares one stale snapshot, and idIndex() dedupes
500
+ * concurrent builds, so a sibling that wins the throttle heals the whole batch.
625
501
  *
626
- * A {@link ReferenceResolveCache} sits ABOVE this, never inside it, so every
627
- * distinct id still runs the full refresh-and-retry above. What a cache changes
628
- * is only that repeats of the SAME id in one batch share that one attempt's
629
- * outcome instead of each getting an independent throw of the dice.
502
+ * A {@link ReferenceResolveCache} sits ABOVE this, never inside it, so every distinct id
503
+ * still runs the full refresh-and-retry; a cache only makes repeats of the SAME id share it.
630
504
  */
631
505
  private resolveSingleReferenceUncached;
632
506
  private resolveSingleReferenceOnce;