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
@@ -28,106 +28,46 @@ import { canopyLogWarn } from './utils/logger.js';
28
28
  /** Logical path representing entries stored at the branch root (no parent collection). Rare in practice. */
29
29
  const EMPTY_LOGICAL_PATH = '';
30
30
  /**
31
- * Strips embedded IDs from each physical path segment to produce a logical path.
32
- * e.g. "content/posts.a1b2c3d4e5f6" → "content/posts"
33
- * This is a module-private helper for internal path conversion only.
31
+ * Strips the embedded ID from each physical segment to produce a logical path:
32
+ * "content/posts.a1b2c3d4e5f6" → "content/posts".
34
33
  */
35
34
  function toLogicalCollectionPath(physicalPath) {
36
35
  if (physicalPath === '.')
37
36
  return EMPTY_LOGICAL_PATH;
38
- // extractSlugFromFilename strips the ID (and extension) from each segment
39
37
  return physicalPath
40
38
  .split('/')
41
39
  .map((seg) => extractSlugFromFilename(seg))
42
40
  .join('/');
43
41
  }
44
42
  /**
45
- * ContentIdIndex manages the bidirectional mapping between content IDs and file paths.
43
+ * Bidirectional ContentId <-> path index over `{type}.{slug}.{12-char-id}.{ext}`
44
+ * files and `{slug}.{12-char-id}/` directories, scanned lazily on first access
45
+ * into per-process memory. Filenames on disk are the source of truth; the class
46
+ * is not thread-safe and takes no locks. Staleness is bounded as the module
47
+ * header describes, plus ContentStore's suspicious-lookup rebuild.
46
48
  *
47
- * IDs are embedded in filenames using the patterns:
48
- * - Entries: `{type}.{slug}.{12-char-id}.{ext}` (e.g., `post.dune.a1b2c3d4e5f6.json`)
49
- * - Collection directories: `{slug}.{12-char-id}/` (e.g., `posts.a1b2c3d4e5f6/`)
49
+ * A duplicate embedded ID always predates the scan: concurrent creates each
50
+ * generate a unique ID, and ContentStore.write() refuses to recreate an entry
51
+ * file whose ID the directory listing places at a different slug. A crash does
52
+ * produce one — renameEntry()'s `fs.link()` then `fs.unlink()` is not atomic.
50
53
  *
51
- * This class builds an in-memory index by scanning filenames recursively, providing O(1) lookups
52
- * in both directions (ID→path and path→ID).
54
+ * `buildFromFilenames()` quarantines such a collision rather than throwing,
55
+ * which would propagate out of every caller and brick read-by-id, reference
56
+ * resolution, listing and every write on the branch. The winner is the
57
+ * lexicographically-SMALLEST relativePath, compared as strings and never by
58
+ * visit order, so hosts scanning in different `readdir()` orders converge on
59
+ * the same one. Losers leave `idToLocation`/`pathToId`/`byCollection` entirely
60
+ * and are recorded in {@link getDuplicateIds} for branch-health.ts and the
61
+ * repair action in api/admin-branch-health.ts: degraded, not dead.
53
62
  *
54
- * The index is built lazily on first access to optimize Lambda cold starts.
55
- *
56
- * ## Multi-Process Consistency
57
- *
58
- * This class is NOT thread-safe. In multi-process environments (e.g., multiple Lambda
59
- * instances or server processes), each process maintains its own in-memory index.
60
- *
61
- * **Consistency guarantees:**
62
- * - Filenames on the filesystem are the source of truth
63
- * - Each process discovers the same filenames when building its index
64
- * - Write operations that change filenames are atomic (rename is atomic)
65
- * - Read operations always reflect current filesystem state after index rebuild
66
- *
67
- * **How staleness is bounded:**
68
- * - Same process: mutation sites invalidate registered ContentStores directly
69
- * (content-index-registry.ts); the next access rebuilds.
70
- * - Other processes: mutation sites bump an on-disk generation marker
71
- * (content-index-generation.ts) that ContentStore probes on access, so
72
- * stale indexes converge within the probe interval plus NFS caching delay.
73
- * - Residual windows self-heal via ContentStore's suspicious-lookup rebuild
74
- * (ID miss or index hit whose file is gone).
75
- *
76
- * **Race condition handling:**
77
- * - Multiple processes creating entries simultaneously: each generates a unique ID,
78
- * so this cannot itself manufacture a duplicate-ID pair.
79
- * - ContentStore.write() refuses to recreate an entry file whose ID the
80
- * directory listing places at a different slug (existence guard), so stale
81
- * indexes cannot manufacture duplicate-ID files either. Together these two
82
- * guarantees mean buildFromFilenames() can only ever encounter a duplicate
83
- * ID that ALREADY existed on disk before the scan started (see below).
84
- *
85
- * ## Duplicate-ID quarantine (not fail-fast)
86
- *
87
- * A duplicate embedded ID can still land on disk from an external crash --
88
- * e.g. ContentStore.renameEntry()'s `fs.link()` then `fs.unlink()` is not
89
- * atomic, and a crash between the two steps leaves two filenames sharing one
90
- * ID. Earlier versions of this class threw on the first such collision,
91
- * which propagated out of every caller and (since ContentStore's rebuild has
92
- * no catch around it) permanently bricked read-by-id, reference resolution,
93
- * collection listing, and every write on the branch -- one bad pair took the
94
- * whole branch down.
95
- *
96
- * `buildFromFilenames()` now quarantines a collision instead of throwing:
97
- * the two (or more) candidates sharing an ID are resolved to a single
98
- * deterministic winner -- the lexicographically-**smallest** relativePath,
99
- * chosen purely by comparing the candidate strings, never by which one the
100
- * scan happened to visit first. This is what makes the choice agree across
101
- * hosts: two Lambdas (or a Lambda and the EC2 worker) scanning the same
102
- * directory can observe entries in different `readdir()` orders, but the
103
- * MIN of a fixed set of strings does not depend on visitation order, so
104
- * every host converges on the same winner. The loser(s) are excluded from
105
- * `idToLocation`/`pathToId`/`byCollection` entirely (as if not indexed) and
106
- * recorded in {@link getDuplicateIds} for admin-facing health reporting
107
- * (branch-health.ts) and repair (api/admin-branch-health.ts). A branch with a
108
- * quarantined duplicate is degraded (that one ID pair is invisible to
109
- * ID-based lookups) but not dead.
110
- *
111
- * ### What quarantine does NOT promise
112
- *
113
- * Quarantining is an INDEX decision: this scan never touches the filesystem,
114
- * so nothing here moves or deletes the dropped file. It does not follow that
115
- * the dropped file is inert until an admin repairs it, and an earlier version
116
- * of this comment wrongly claimed exactly that ("left untouched on disk until
117
- * a human or the repair action moves them"). Slugs are resolved by directory
118
- * scan (`ContentStore.buildPaths()`), which knows nothing about this
119
- * quarantine, so the dropped file stays fully addressable by
120
- * collection+slug: a stale editor tab can still save, delete or rename it.
121
- *
122
- * The dangerous case was the save. `ContentStore.write()`'s index-repair step
123
- * reads "the index says this ID lives somewhere else" as "the slug changed"
124
- * and unlinks that other file -- which, with a duplicate, is a DIFFERENT
125
- * document (the kept one). It silently deleted the winner while reporting
126
- * success. `write()` now refuses such a save with `DuplicateContentIdError`
127
- * instead (see its "[F1] duplicate-ID guard"). `delete()`/`renameEntry()`
128
- * were never affected: they only ever touch the file the caller addressed,
129
- * and are deliberately still allowed, so removing or moving the dropped file
130
- * by hand remains possible without an admin.
63
+ * Quarantine is an INDEX decision only: this scan never touches the filesystem,
64
+ * and slugs resolve by directory scan (`ContentStore.buildPaths()`), which
65
+ * knows nothing about it, so the dropped file stays addressable by
66
+ * collection+slug. Hence `write()` refuses such a save with
67
+ * `DuplicateContentIdError` — its index-repair step reads "this ID lives
68
+ * elsewhere" as "the slug changed" and would unlink the kept file, a different
69
+ * document. `delete()`/`renameEntry()` stay allowed, each touching only the
70
+ * file the caller addressed, so the dropped file can be removed by hand.
131
71
  */
132
72
  export class ContentIdIndex {
133
73
  constructor(root) {
@@ -135,18 +75,16 @@ export class ContentIdIndex {
135
75
  this.pathToId = new Map();
136
76
  this.byCollection = new Map();
137
77
  /**
138
- * IDs discovered on more than one file during buildFromFilenames, keyed by
139
- * id, mapping to every quarantined (losing) path seen for that id. Never
140
- * populated by add()/remove()/updatePath(), which still throw on collision
141
- * -- see those methods' doc comments.
78
+ * Quarantined (losing) paths per duplicated id, populated by
79
+ * buildFromFilenames only: add()/remove()/updatePath() throw on collision.
142
80
  */
143
81
  this.duplicateIds = new Map();
144
82
  this.root = path.resolve(root);
145
83
  }
146
84
  /**
147
- * Build index by scanning filenames recursively. Duplicate embedded IDs
148
- * are quarantined, not thrown on -- see the class doc comment's
149
- * "Duplicate-ID quarantine" section and {@link getDuplicateIds}.
85
+ * Build the index by scanning filenames recursively. Duplicate embedded IDs
86
+ * are quarantined, not thrown on — see the class doc and
87
+ * {@link getDuplicateIds}.
150
88
  */
151
89
  async buildFromFilenames(startPath = '') {
152
90
  await this.scanDirectory(startPath);
@@ -162,7 +100,7 @@ export class ContentIdIndex {
162
100
  this.byCollection.get(location.collection).add(id);
163
101
  }
164
102
  }
165
- /** Remove a location from all three indexes -- used when a lexicographically-earlier duplicate displaces it. */
103
+ /** Remove a location from all three indexes, when an earlier duplicate displaces it. */
166
104
  evictLocation(location) {
167
105
  this.idToLocation.delete(location.id);
168
106
  this.pathToId.delete(location.relativePath);
@@ -175,7 +113,7 @@ export class ContentIdIndex {
175
113
  }
176
114
  }
177
115
  }
178
- /** Record a quarantined duplicate path for admin-facing health reporting (see getDuplicateIds). */
116
+ /** Record a quarantined duplicate path for health reporting (see getDuplicateIds). */
179
117
  recordDuplicate(id, droppedPath) {
180
118
  let dropped = this.duplicateIds.get(id);
181
119
  if (!dropped) {
@@ -189,7 +127,6 @@ export class ContentIdIndex {
189
127
  try {
190
128
  const entries = await fs.readdir(absoluteDir, { withFileTypes: true });
191
129
  for (const entry of entries) {
192
- // Skip hidden files and directories (including _ids_)
193
130
  if (entry.name.startsWith('.') || entry.name === '_ids_') {
194
131
  continue;
195
132
  }
@@ -201,11 +138,8 @@ export class ContentIdIndex {
201
138
  type: entry.isDirectory() ? 'collection' : 'entry',
202
139
  relativePath: fullRelativePath, // filesystem path with embedded IDs
203
140
  };
204
- // Extract slug and collection for entries
205
141
  if (!entry.isDirectory()) {
206
142
  const slug = extractSlugFromFilename(entry.name);
207
- // Convert physical collection path to logical by stripping embedded IDs from each segment
208
- // e.g., "content/posts.a1b2c3d4e5f6" → "content/posts"
209
143
  const physicalCollection = path.dirname(fullRelativePath);
210
144
  const collectionPath = toLogicalCollectionPath(physicalCollection);
211
145
  location.slug = slug;
@@ -213,11 +147,9 @@ export class ContentIdIndex {
213
147
  }
214
148
  const existing = this.idToLocation.get(id);
215
149
  if (existing) {
216
- // Duplicate embedded ID -- quarantine instead of throwing (see
217
- // class doc comment). Deterministic winner: the
218
- // lexicographically-smaller relativePath, decided purely by
219
- // comparing the two strings so every host scanning the same
220
- // directory picks the same winner regardless of readdir order.
150
+ // Quarantine, don't throw (class doc). The winner is the
151
+ // lexicographically-smaller relativePath, so every host picks the
152
+ // same one whatever order readdir handed them to it in.
221
153
  const newWins = fullRelativePath < existing.relativePath;
222
154
  const kept = newWins ? fullRelativePath : existing.relativePath;
223
155
  const dropped = newWins ? existing.relativePath : fullRelativePath;
@@ -229,10 +161,6 @@ export class ContentIdIndex {
229
161
  else {
230
162
  this.recordDuplicate(id, fullRelativePath);
231
163
  }
232
- // Written for an operator who has never seen this code: name
233
- // both files, say explicitly that the dropped one still exists
234
- // (quarantine, not deletion), and point at the concrete recovery
235
- // action rather than an internal method name.
236
164
  canopyLogWarn(`[ContentIdIndex] Duplicate content ID ${id}: "${kept}" and "${dropped}" both ` +
237
165
  `embed this ID. Keeping "${kept}" for ID-based lookups (reads, references, ` +
238
166
  `listings); "${dropped}" is excluded from those lookups for now but has NOT ` +
@@ -246,7 +174,6 @@ export class ContentIdIndex {
246
174
  this.insertLocation(id, location);
247
175
  }
248
176
  }
249
- // Recurse into directories
250
177
  if (entry.isDirectory()) {
251
178
  await this.scanDirectory(fullRelativePath);
252
179
  }
@@ -260,11 +187,9 @@ export class ContentIdIndex {
260
187
  }
261
188
  }
262
189
  /**
263
- * IDs discovered on more than one file during the last buildFromFilenames
264
- * scan. Empty unless the content tree currently has a duplicate-ID pair
265
- * (see the class doc comment). Consumed by branch-health.ts's admin scan
266
- * and by the repair-content-duplicates admin action
267
- * (api/admin-branch-health.ts).
190
+ * IDs the last buildFromFilenames scan found on more than one file; empty
191
+ * otherwise. Consumed by branch-health.ts's admin scan and by the
192
+ * repair-content-duplicates action (api/admin-branch-health.ts).
268
193
  */
269
194
  getDuplicateIds() {
270
195
  const result = [];
@@ -276,22 +201,18 @@ export class ContentIdIndex {
276
201
  return result;
277
202
  }
278
203
  /**
279
- * The quarantine record for ONE id, or null when that id is not
280
- * duplicated. The O(1) counterpart to {@link getDuplicateIds} for the
281
- * write path, which has to ask this question on every save whose resolved
282
- * path disagrees with the index (ContentStore.write()'s duplicate-ID
283
- * guard) and must not pay for materializing the whole list.
204
+ * The quarantine record for ONE id, or null when that id is not duplicated.
205
+ * The O(1) counterpart to {@link getDuplicateIds}, for ContentStore.write()'s
206
+ * duplicate-ID guard: it asks on every save whose resolved path disagrees
207
+ * with the index, so it must not pay to materialize the whole list.
284
208
  */
285
209
  getDuplicateFor(id) {
286
210
  const dropped = this.duplicateIds.get(id);
287
211
  if (!dropped)
288
212
  return null;
289
213
  const kept = this.idToLocation.get(id);
290
- // Defensive: unreachable in practice -- recordDuplicate() only ever
291
- // runs alongside an insertLocation() for the same id (either just
292
- // before, on the winner-displaced branch, or already present, on the
293
- // loser branch), so a winner always exists once a duplicate is
294
- // recorded.
214
+ // Defensive, unreachable: recordDuplicate() always runs alongside an
215
+ // insertLocation() for the same id, so a winner exists once one is recorded.
295
216
  if (!kept)
296
217
  return null;
297
218
  return {
@@ -300,33 +221,19 @@ export class ContentIdIndex {
300
221
  droppedPaths: Array.from(dropped).sort(),
301
222
  };
302
223
  }
303
- /**
304
- * Forward lookup: ID → location (O(1))
305
- */
224
+ /** Forward lookup: ID → location (O(1)). */
306
225
  findById(id) {
307
226
  return this.idToLocation.get(id) || null;
308
227
  }
309
- /**
310
- * Reverse lookup: path → ID (O(1))
311
- */
228
+ /** Reverse lookup: path → ID (O(1)). */
312
229
  findByPath(relativePath) {
313
230
  return this.pathToId.get(relativePath) || null;
314
231
  }
315
- /**
316
- * Get all ID locations in the index.
317
- * Useful for validation and checking references.
318
- */
232
+ /** Every ID location in the index, for validation and reference checking. */
319
233
  getAllLocations() {
320
234
  return Array.from(this.idToLocation.values());
321
235
  }
322
- /**
323
- * Get all entries in a collection by collection path.
324
- *
325
- * Performance: O(1) + O(m) where m is the number of entries in the collection.
326
- *
327
- * @param collectionPath - The collection path (e.g., "content/posts")
328
- * @returns Array of IdLocation objects for entries in the collection
329
- */
236
+ /** Entries in one collection: O(1) + O(entries in it). */
330
237
  getEntriesInCollection(collectionPath) {
331
238
  const idSet = this.byCollection.get(collectionPath);
332
239
  if (!idSet) {
@@ -342,14 +249,9 @@ export class ContentIdIndex {
342
249
  return locations;
343
250
  }
344
251
  /**
345
- * Get all entries in a collection and its subcollections (tree traversal).
346
- * For example, getEntriesInCollectionTree("content/data-catalog") returns entries
347
- * in "content/data-catalog", "content/data-catalog/openstax", etc.
348
- *
349
- * Performance: O(k * m) where k is number of matching collections and m is avg entries per collection.
350
- *
351
- * @param collectionPath - The root collection path to search
352
- * @returns Array of IdLocation objects for entries in the collection tree
252
+ * Entries in a collection and every subcollection: "content/docs" returns
253
+ * "content/docs", "content/docs/api", and so on. O(matching collections ×
254
+ * entries each).
353
255
  */
354
256
  getEntriesInCollectionTree(collectionPath) {
355
257
  const locations = [];
@@ -372,7 +274,6 @@ export class ContentIdIndex {
372
274
  *
373
275
  * Performance: O(n) where n is total number of entries.
374
276
  *
375
- * @returns Array of all IdLocation objects that are entries
376
277
  */
377
278
  getAllEntryLocations() {
378
279
  const locations = [];
@@ -387,16 +288,14 @@ export class ContentIdIndex {
387
288
  return locations;
388
289
  }
389
290
  /**
390
- * Add a new entry or collection to the index.
391
- * Note: This only updates the in-memory index. The file with embedded ID
392
- * must already exist on disk (created by ContentStore).
291
+ * Add an entry or collection. In-memory only: the file with the embedded ID
292
+ * must already exist on disk, created by ContentStore. Throws on collision.
393
293
  */
394
294
  add(location) {
395
295
  const id = extractIdFromFilename(path.basename(location.relativePath));
396
296
  if (!id) {
397
297
  throw new Error(`Cannot add location without ID in filename: ${location.relativePath}`);
398
298
  }
399
- // Collision detection
400
299
  if (this.idToLocation.has(id)) {
401
300
  const existing = this.idToLocation.get(id);
402
301
  throw new Error(`ID collision detected: ${id}\n` +
@@ -409,7 +308,6 @@ export class ContentIdIndex {
409
308
  };
410
309
  this.idToLocation.set(id, fullLocation);
411
310
  this.pathToId.set(location.relativePath, id);
412
- // Add to collection index if it's an entry
413
311
  if (fullLocation.type === 'entry' && fullLocation.collection) {
414
312
  if (!this.byCollection.has(fullLocation.collection)) {
415
313
  this.byCollection.set(fullLocation.collection, new Set());
@@ -418,19 +316,18 @@ export class ContentIdIndex {
418
316
  }
419
317
  }
420
318
  /**
421
- * Remove an entry or collection from the index by ID.
422
- * Note: This only updates the in-memory index. The file must be deleted separately.
319
+ * Remove an entry or collection by ID. In-memory only: the caller deletes the
320
+ * file separately.
423
321
  */
424
322
  remove(id) {
425
323
  const location = this.idToLocation.get(id);
426
324
  if (!location)
427
325
  return;
428
- // Remove from collection index if it's an entry
429
326
  if (location.type === 'entry' && location.collection) {
430
327
  const idSet = this.byCollection.get(location.collection);
431
328
  if (idSet) {
432
329
  idSet.delete(id);
433
- // Clean up empty Sets to prevent memory leaks
330
+ // Drop empty Sets so the collection map cannot grow without bound.
434
331
  if (idSet.size === 0) {
435
332
  this.byCollection.delete(location.collection);
436
333
  }
@@ -440,27 +337,22 @@ export class ContentIdIndex {
440
337
  this.pathToId.delete(location.relativePath);
441
338
  }
442
339
  /**
443
- * Update the path for an existing ID (e.g., after file rename/move).
444
- * This is used to keep the index in sync when files are renamed.
340
+ * Repoint an existing ID at a new path; keeps the index in step with a
341
+ * rename or move. Throws when the ID is unknown.
445
342
  */
446
343
  updatePath(id, newRelativePath) {
447
344
  const location = this.idToLocation.get(id);
448
345
  if (!location) {
449
346
  throw new Error(`Cannot update path for unknown ID: ${id}`);
450
347
  }
451
- // Remove old path mapping
452
348
  this.pathToId.delete(location.relativePath);
453
- // Update location
454
349
  location.relativePath = newRelativePath;
455
- // Update slug and collection for entries
456
350
  if (location.type === 'entry') {
457
351
  const oldCollection = location.collection;
458
352
  location.slug = extractSlugFromFilename(path.basename(newRelativePath));
459
353
  const physicalCollection = path.dirname(newRelativePath);
460
354
  location.collection = toLogicalCollectionPath(physicalCollection);
461
- // Update collection index if collection changed
462
355
  if (oldCollection !== location.collection) {
463
- // Remove from old collection
464
356
  if (oldCollection) {
465
357
  const oldSet = this.byCollection.get(oldCollection);
466
358
  if (oldSet) {
@@ -470,7 +362,6 @@ export class ContentIdIndex {
470
362
  }
471
363
  }
472
364
  }
473
- // Add to new collection
474
365
  if (location.collection) {
475
366
  if (!this.byCollection.has(location.collection)) {
476
367
  this.byCollection.set(location.collection, new Set());
@@ -479,39 +370,28 @@ export class ContentIdIndex {
479
370
  }
480
371
  }
481
372
  }
482
- // Add new path mapping
483
373
  this.pathToId.set(newRelativePath, id);
484
374
  }
485
375
  }
486
376
  /**
487
- * Extract ID from filename.
488
- * Returns null if filename doesn't contain an ID or is a metadata file.
489
- *
490
- * Pattern:
491
- * - Collection entry files: type.slug.id.ext → ID is parts[parts.length - 2] (e.g., "post.dune.a1b2c3d4e5f6.json")
492
- * - Collection directories: slug.id → ID is parts[1] (e.g., "posts.a1b2c3d4e5f6")
493
- * - Metadata files: .collection.json, .gitignore, etc. → null
377
+ * The ID embedded in a filename, or null when there is none.
494
378
  *
495
- * Edge cases:
496
- * - Slugs with dots: "post.my.page.a1b2c3d4e5f6.json" → extracts "a1b2c3d4e5f6"
497
- * - Hidden files with IDs: ".hidden.a1b2c3d4e5f6.json" → returns null (metadata)
498
- * - No ID present: "file.json" → returns null
379
+ * - entry file `type.slug.id.ext` → the second-to-last part; a dotted slug
380
+ * ("post.my.page.a1b2c3d4e5f6.json") still resolves, since only position
381
+ * from the end matters
382
+ * - collection directory `slug.id` → the last part
383
+ * - anything dot-prefixed → null, metadata, even `.hidden.a1b2c3d4e5f6.json`
499
384
  */
500
385
  export function extractIdFromFilename(filename) {
501
- // Skip metadata files (no IDs) - anything starting with dot is metadata
502
- // This includes .collection.json, .gitignore, and even .hidden.id.json
503
386
  if (filename.startsWith('.')) {
504
387
  return null;
505
388
  }
506
389
  const parts = filename.split('.');
507
- // Files: type.slug.id.ext → need at least 3 parts
508
- // The ID is always the second-to-last part before the extension
509
390
  if (parts.length >= 3) {
510
391
  const candidate = parts[parts.length - 2];
511
392
  if (isValidId(candidate))
512
393
  return candidate;
513
394
  }
514
- // Directories: slug.id → exactly 2 parts (slug and ID, no extension)
515
395
  if (parts.length === 2) {
516
396
  const candidate = parts[parts.length - 1];
517
397
  if (isValidId(candidate))
@@ -520,16 +400,9 @@ export function extractIdFromFilename(filename) {
520
400
  return null;
521
401
  }
522
402
  /**
523
- * Resolve a logical collection path to its actual filesystem path with embedded IDs.
524
- * Recursively resolves each path segment to handle nested collections.
525
- *
526
- * Example:
527
- * Input: resolveCollectionPath(root, "content/docs/api")
528
- * Output: "/abs/path/to/content/docs.bChqT78gcaLd/api.meiuwxTSo7UN"
529
- *
530
- * @param root - Absolute path to the workspace root
531
- * @param logicalPath - Logical path from schema (e.g., "content/docs/api")
532
- * @returns Absolute filesystem path with embedded IDs, or null if path doesn't exist
403
+ * Resolve a logical collection path to the filesystem path with embedded IDs,
404
+ * one segment at a time so nested collections work: "content/docs/api" →
405
+ * "<root>/content/docs.bChqT78gcaLd/api.meiuwxTSo7UN". Null if it is not there.
533
406
  */
534
407
  export async function resolveCollectionPath(root, logicalPath) {
535
408
  // Dynamic import: keep Node built-ins out of browser/edge bundles
@@ -543,7 +416,6 @@ export async function resolveCollectionPath(root, logicalPath) {
543
416
  const matchingDir = entries.find((entry) => {
544
417
  if (!entry.isDirectory())
545
418
  return false;
546
- // Extract logical name from directory (strips embedded ID)
547
419
  const logicalName = extractSlugFromFilename(entry.name);
548
420
  return logicalName === segment.toLowerCase();
549
421
  });
@@ -551,7 +423,6 @@ export async function resolveCollectionPath(root, logicalPath) {
551
423
  currentPath = path.join(currentPath, matchingDir.name);
552
424
  }
553
425
  else {
554
- // Directory not found - might not exist yet
555
426
  return null;
556
427
  }
557
428
  }
@@ -564,22 +435,14 @@ export async function resolveCollectionPath(root, logicalPath) {
564
435
  return currentPath;
565
436
  }
566
437
  /**
567
- * Extract entry type name from filename.
568
- * For collection entry files with pattern type.slug.id.ext, returns the type (first part).
569
- *
570
- * Examples:
571
- * - "post.my-slug.a1b2c3d4e5f6.json" → "post"
572
- * - "article.test.a1b2c3d4e5f6.md" → "article"
573
- * - "posts.a1b2c3d4e5f6" → null (directory, not an entry file)
574
- *
575
- * @param filename - The filename to parse
576
- * @returns Entry type name or null if not a valid entry file
438
+ * The entry type of an entry file: the first part of `type.slug.id.ext`, so
439
+ * "post.my-slug.a1b2c3d4e5f6.json" → "post". Null for anything else, including
440
+ * a collection directory ("posts.a1b2c3d4e5f6").
577
441
  */
578
442
  export function extractEntryTypeFromFilename(filename) {
579
443
  if (filename.startsWith('.'))
580
444
  return null;
581
445
  const parts = filename.split('.');
582
- // Need at least 4 parts for type.slug.id.ext
583
446
  if (parts.length >= 4) {
584
447
  const possibleId = parts[parts.length - 2];
585
448
  if (isValidId(possibleId)) {
@@ -589,50 +452,36 @@ export function extractEntryTypeFromFilename(filename) {
589
452
  return null;
590
453
  }
591
454
  /**
592
- * Extract slug from filename.
593
- *
594
- * Collection entries: type.slug.id.ext → slug is parts[1...-2] (between type and ID)
595
- * Directories: slug.id → slug is parts[0] (before ID)
596
- *
597
- * For collection entry files (4+ parts), automatically strips the first part (type) to extract just the slug.
598
- *
599
- * Examples:
600
- * - "post.my-slug.a1b2c3d4e5f6.json" → "my-slug" (4 parts)
601
- * - "post.my.page.a1b2c3d4e5f6.json" → "my.page" (dotted slug, 5 parts)
602
- * - "posts.a1b2c3d4e5f6" → "posts" (directory, 2 parts)
455
+ * The slug in a filename, lowercased: everything between the type and the ID in
456
+ * `type.slug.id.ext`, or the part before the ID in a `slug.id` directory. A
457
+ * dotted slug survives ("post.my.page.a1b2c3d4e5f6.json" → "my.page"). Falls
458
+ * back to the extensionless filename when there is no ID.
603
459
  *
604
- * @param filename - The filename to parse
605
- * @param entryTypeName - Optional entry type name for explicit type matching (e.g., "post")
606
- * If provided and matches first part, strips it from slug
460
+ * `entryTypeName`, when it matches the first part, strips that part explicitly
461
+ * instead of relying on the 4-part auto-detection.
607
462
  */
608
463
  export function extractSlugFromFilename(filename, entryTypeName) {
609
464
  const parts = filename.split('.');
610
- // Files: type.slug.id.ext (at least 3 parts)
611
465
  if (parts.length >= 3) {
612
466
  const possibleId = parts[parts.length - 2];
613
467
  if (isValidId(possibleId)) {
614
- // Get all parts before ID (excluding extension)
615
468
  let slugParts = parts.slice(0, parts.length - 2);
616
- // If entryTypeName is provided and matches the first part, strip it
617
469
  if (entryTypeName && slugParts.length > 1 && slugParts[0] === entryTypeName) {
618
470
  slugParts = slugParts.slice(1);
619
471
  }
620
- // Auto-detect: 4+ parts means type.slug.id.ext format
472
+ // Auto-detected: 4+ parts is type.slug.id.ext, so the first part is the type.
621
473
  else if (parts.length >= 4 && slugParts.length > 1) {
622
- // Strip first part (the type) to get just the slug
623
474
  slugParts = slugParts.slice(1);
624
475
  }
625
476
  return slugParts.join('.').toLowerCase();
626
477
  }
627
478
  }
628
- // Directories: slug.id (exactly 2 parts)
629
479
  if (parts.length === 2) {
630
480
  const possibleId = parts[parts.length - 1];
631
481
  if (isValidId(possibleId)) {
632
482
  return parts[0].toLowerCase();
633
483
  }
634
484
  }
635
- // No ID found, remove extension and return filename without extension
636
485
  if (parts.length > 1) {
637
486
  return parts.slice(0, -1).join('.').toLowerCase();
638
487
  }