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
@@ -9,35 +9,27 @@ import { authResultToCanopyUser } from '../user.js';
9
9
  import { isAdmin } from '../authorization/index.js';
10
10
  import { clientOperatingStrategy, operatingStrategy } from '../operating-mode/index.js';
11
11
  import { getErrorMessage, redactCredentials, sanitizeErrorMessage } from '../utils/error.js';
12
- // canopyLogError, not console.error: http/handler.ts is shared code and not
13
- // guaranteed to stay out of the worker's runtime import closure (see
14
- // utils/logger.ts) — new log lines here should go through the indirection.
12
+ // canopyLogError, not console.error: this is shared code and not guaranteed to
13
+ // stay out of the worker's runtime import closure, so new log lines here go
14
+ // through the indirection (utils/logger.ts).
15
15
  import { canopyLogError } from '../utils/logger.js';
16
- /**
17
- * Build API context from options.
18
- */
19
16
  const buildContext = async (options) => {
20
17
  const services = options.services ?? (options.config ? await createCanopyServices(options.config) : undefined);
21
18
  if (!services) {
22
19
  throw new Error('CanopyCMS: config or services is required');
23
20
  }
24
21
  const operatingMode = services.config.mode;
25
- // Derive from the strategy (which resolves deploymentName via
26
- // resolveDeploymentName), not a hardcoded literal — a third, independent
27
- // default here that never accounted for deploymentName meant a
28
- // deployment-namespaced settings branch (e.g. canopycms-settings-acme)
29
- // never matched this check, so `getBranchContext(settingsBranch)` could
30
- // never auto-create it.
22
+ // Derive from the strategy, which resolves deploymentName; a literal here
23
+ // would not match a deployment-namespaced settings branch (say
24
+ // canopycms-settings-acme), so getBranchContext could never auto-create it.
31
25
  const settingsBranch = operatingStrategy(operatingMode).getSettingsBranchName(services.config);
32
26
  const getBranchContext = options.getBranchContext ??
33
27
  (async (branch, opts) => {
34
- // Try to load existing branch
35
28
  const existing = await loadBranchContext({
36
29
  branchName: branch,
37
30
  mode: operatingMode,
38
31
  });
39
32
  if (existing) {
40
- // Optionally load per-branch schema
41
33
  if (opts?.loadSchema) {
42
34
  const contentRootName = services.config.contentRoot || 'content';
43
35
  const cached = await services.branchSchemaCache.getSchema(existing.branchRoot, services.entrySchemaRegistry, contentRootName);
@@ -45,9 +37,8 @@ const buildContext = async (options) => {
45
37
  }
46
38
  return existing;
47
39
  }
48
- // In modes that support branching, auto-create system branches if they don't exist.
49
- // Read from services.config per-request (not a captured variable) so that
50
- // refreshActiveBranch() updates are reflected immediately.
40
+ // Read from services.config per-request, not a captured variable, so a
41
+ // refreshActiveBranch() update takes effect immediately.
51
42
  const baseBranch = services.config.defaultBaseBranch ?? 'main';
52
43
  const activeBranch = services.config.defaultActiveBranch ?? baseBranch;
53
44
  const shouldAutoCreate = clientOperatingStrategy(operatingMode).supportsBranching() &&
@@ -59,7 +50,6 @@ const buildContext = async (options) => {
59
50
  mode: operatingMode,
60
51
  createdBy: 'canopycms-system',
61
52
  });
62
- // Optionally load per-branch schema for auto-created branches
63
53
  if (opts?.loadSchema && context) {
64
54
  const contentRootName = services.config.contentRoot || 'content';
65
55
  const cached = await services.branchSchemaCache.getSchema(context.branchRoot, services.entrySchemaRegistry, contentRootName);
@@ -76,9 +66,6 @@ const buildContext = async (options) => {
76
66
  authPlugin: options.authPlugin,
77
67
  };
78
68
  };
79
- /**
80
- * Parse query parameters from URL.
81
- */
82
69
  const parseQueryParams = (url) => {
83
70
  try {
84
71
  const urlObj = new URL(url, 'http://localhost');
@@ -89,31 +76,9 @@ const parseQueryParams = (url) => {
89
76
  }
90
77
  };
91
78
  /**
92
- * Create a framework-agnostic Canopy request handler.
93
- *
94
- * This is the core handler that processes all Canopy API requests.
95
- * Framework adapters (Next.js, Express, Hono, etc.) should:
96
- * 1. Convert their framework's request to CanopyRequest
97
- * 2. Extract path segments from the URL
98
- * 3. Call this handler
99
- * 4. Convert the CanopyResponse to their framework's response
100
- *
101
- * @example
102
- * ```ts
103
- * // In a framework adapter
104
- * const coreHandler = createCanopyRequestHandler({
105
- * config: myConfig,
106
- * authPlugin: myAuthPlugin,
107
- * })
108
- *
109
- * // Framework-specific handler
110
- * async function handleRequest(frameworkReq) {
111
- * const canopyReq = convertToCanopyRequest(frameworkReq)
112
- * const segments = extractPathSegments(frameworkReq)
113
- * const response = await coreHandler(canopyReq, segments)
114
- * return convertToFrameworkResponse(response)
115
- * }
116
- * ```
79
+ * The core handler behind every Canopy API request. A framework adapter
80
+ * (Next.js, Express, Hono) converts its request to a CanopyRequest, extracts
81
+ * the path segments, calls this, and converts the CanopyResponse back.
117
82
  */
118
83
  export function createCanopyRequestHandler(options) {
119
84
  // Fail closed (SEC-C1): a dev/insecure auth plugin must never serve prod traffic.
@@ -121,10 +86,9 @@ export function createCanopyRequestHandler(options) {
121
86
  const mode = options.services?.config.mode ?? options.config?.mode;
122
87
  assertAuthPluginAllowedForMode(options.authPlugin, mode);
123
88
  const router = createCanopyRouter();
124
- // Build context once (memoized) and reuse across requests in the same warm
125
- // container/process. On rejection (e.g. transient cold-start / EFS not yet
126
- // mounted), the cache is cleared (API-H3) so the NEXT request retries
127
- // buildContext() instead of replaying the same rejection forever.
89
+ // Built once and reused across requests in the same warm container. On
90
+ // rejection (transient cold start, EFS not yet mounted) the cache is cleared
91
+ // (API-H3) so the NEXT request retries instead of replaying the rejection.
128
92
  let apiCtxPromise = null;
129
93
  const getContext = () => {
130
94
  if (!apiCtxPromise) {
@@ -135,37 +99,30 @@ export function createCanopyRequestHandler(options) {
135
99
  }
136
100
  return apiCtxPromise;
137
101
  };
138
- // Core request-handling logic, wrapped below by a top-level try/catch (API-C1).
139
- // getContext()/refreshActiveBranch()/authenticate()/match.handler() are NOT
140
- // individually try/catched here, and some handlers deliberately re-throw
141
- // unrecognized errors (e.g. api/content.ts, api/entries.ts) — without an outer
142
- // boundary, an unhandled throw would escape the framework adapter as a generic
143
- // 500 that breaks the uniform { ok, status, error } contract the editor depends on.
102
+ // Wrapped below by a top-level try/catch (API-C1). Nothing in here is
103
+ // individually try/catched, and some handlers deliberately re-throw
104
+ // unrecognized errors, so without that outer boundary an unhandled throw
105
+ // would escape the adapter as a generic 500 and break the uniform
106
+ // { ok, status, error } contract the editor depends on.
144
107
  const handleRequest = async (req, pathSegments) => {
145
- // Route matching (fast, do first before async work)
146
108
  const match = router.match(req.method, pathSegments);
147
109
  if (!match) {
148
110
  return jsonResponse({ ok: false, status: 404, error: 'Not found' }, 404);
149
111
  }
150
- // Get cached context
151
112
  const apiCtx = await getContext();
152
113
  // In dev mode, re-check if the developer switched git branches
153
114
  await apiCtx.services.refreshActiveBranch();
154
- // Authenticate and convert to CanopyUser
155
115
  const authResult = await options.authPlugin.authenticate(req);
156
- // API routes require authentication. Reject anonymous callers BEFORE any
157
- // workspace provisioning below, so they can neither trigger expensive git
158
- // operations nor read provisioning error details.
116
+ // API routes require authentication. Anonymous callers are rejected BEFORE
117
+ // any workspace provisioning below, so they can neither trigger expensive
118
+ // git operations nor read provisioning error details.
159
119
  if (!authResult.success || !authResult.user) {
160
120
  return jsonResponse({ ok: false, status: 401, error: authResult.error ?? 'Unauthorized' }, 401);
161
121
  }
162
- // Ensure the base/active branch workspace is provisioned on first
163
- // request. Internal groups no longer come from this branch (see
164
- // resolveCanopyUser below) — this call is purely so many endpoints that
165
- // assume the base/active workspace already exists (registry reads, etc.)
166
- // don't return confusing empty results on a cold start, so fail loudly
167
- // on a real provisioning error rather than let that surprise a later
168
- // handler.
122
+ // Provision the base/active branch workspace on first request, so the many
123
+ // endpoints that assume it exists (registry reads and the like) don't return
124
+ // confusing empty results on a cold start. A real provisioning error fails
125
+ // loudly here rather than surprising a later handler.
169
126
  const baseBranch = apiCtx.services.config.defaultBaseBranch ?? 'main';
170
127
  try {
171
128
  await apiCtx.getBranchContext(baseBranch);
@@ -173,16 +130,16 @@ export function createCanopyRequestHandler(options) {
173
130
  catch (err) {
174
131
  const message = getErrorMessage(err);
175
132
  if (err instanceof BranchMetadataCorruptError) {
176
- // Corrupt BASE branch metadata must not take down every endpoint —
177
- // the /admin recovery surface is how it gets fixed. Degrade instead:
178
- // keep routing (internal groups come from the settings workspace
179
- // below, independent of this branch's health).
133
+ // Corrupt BASE branch metadata must not take down every endpoint,
134
+ // since /admin is how it gets fixed. Keep routing instead: internal
135
+ // groups come from the settings workspace below, independent of this
136
+ // branch's health.
180
137
  console.error(`CanopyCMS: Base branch '${baseBranch}' has corrupt metadata; serving without base-branch provisioning until repaired: ${redactCredentials(message)}`);
181
138
  }
182
139
  else {
183
- // Full path detail to server logs; sanitized detail to the
184
- // (authenticated) client. Credentials (git errors can embed them) are
185
- // redacted even from server logs.
140
+ // Full path detail to server logs, sanitized detail to the
141
+ // (authenticated) client. Credentials, which git errors can embed, are
142
+ // redacted even from the server logs.
186
143
  console.error(`CanopyCMS: Failed to provision workspace for base branch '${baseBranch}': ${redactCredentials(message)}`);
187
144
  return jsonResponse({
188
145
  ok: false,
@@ -191,14 +148,13 @@ export function createCanopyRequestHandler(options) {
191
148
  }, 503);
192
149
  }
193
150
  }
194
- // Resolve the CanopyUser: internal groups are the single source of
195
- // truth for group-based privileges and MUST be loaded from the settings
196
- // workspace (see authorization/content.ts's createContentAccessChecker,
197
- // the pattern this follows) — never from a content branch clone, which
198
- // nothing in the product ever writes groups.json into. Fails loudly
199
- // (throws, mapped to a 503 below) rather than degrading to an empty
200
- // group list: "no groups" reads as "no privileges", so a silent
201
- // fallback would be a silent authorization change.
151
+ // Internal groups are the single source of truth for group-based privilege
152
+ // and MUST come from the settings workspace (the pattern in
153
+ // authorization/content.ts's createContentAccessChecker), never from a
154
+ // content branch clone, which nothing writes groups.json into. A failure
155
+ // throws, mapped to a 503 below, rather than degrading to an empty group
156
+ // list: "no groups" reads as "no privileges", so a silent fallback would be
157
+ // a silent authorization change.
202
158
  let user;
203
159
  try {
204
160
  user = await resolveCanopyUser(authResult, {
@@ -210,34 +166,32 @@ export function createCanopyRequestHandler(options) {
210
166
  catch (err) {
211
167
  const message = getErrorMessage(err);
212
168
  canopyLogError(`CanopyCMS: Failed to resolve internal groups from the settings workspace: ${redactCredentials(message)}`);
213
- // Same trade as the base-branch degradation above, for the same
214
- // reason: /admin is the recovery surface for exactly this failure
215
- // (a renamed settings branch trips assertSettingsWorkspaceIdentity
216
- // permanently until a human intervenes), so 503ing it too leaves an
217
- // operator with no in-product way to see why anything is down.
169
+ // Same trade as the base-branch degradation above: /admin is the recovery
170
+ // surface for exactly this failure (a renamed settings branch trips
171
+ // assertSettingsWorkspaceIdentity until a human intervenes), so 503ing it
172
+ // too would leave an operator no in-product way to see what is down.
218
173
  //
219
- // Safe because it can only REMOVE privilege, never grant it.
220
- // `authResultToCanopyUser` merges internal groups ADDITIVELY, and
221
- // path rules select on the user matching a target (never on the user
222
- // LACKING a group), so dropping them can flip allowed -> denied and
223
- // not the reverse. The one privilege that survives is bootstrap
224
- // admin, which comes from CANOPY_BOOTSTRAP_ADMIN_IDS in the
225
- // environment and does not touch the settings workspace at all.
174
+ // Safe because it can only REMOVE privilege, never grant it:
175
+ // `authResultToCanopyUser` merges internal groups ADDITIVELY, and path
176
+ // rules select on the user MATCHING a target, never on the user LACKING a
177
+ // group, so dropping them flips allowed -> denied and not the reverse. The
178
+ // one surviving privilege is bootstrap admin, which comes from
179
+ // CANOPY_BOOTSTRAP_ADMIN_IDS and never touches the settings workspace.
226
180
  //
227
- // A non-bootstrap admin still gets the 503, deliberately: the admin
228
- // guard would answer them 403, and "settings workspace unavailable"
229
- // is the more actionable of the two.
181
+ // A non-bootstrap admin still gets the 503, deliberately: the admin guard
182
+ // would answer them 403, and "settings workspace unavailable" is the more
183
+ // actionable of the two.
230
184
  const degradedUser = authResultToCanopyUser(authResult, apiCtx.services.bootstrapAdminIds);
231
185
  if (pathSegments[0] === 'admin' && isAdmin(degradedUser.groups)) {
232
186
  canopyLogError(`CanopyCMS: Serving ${req.method} /admin for a bootstrap admin with group-based privileges UNRESOLVED (settings workspace unavailable) so the recovery endpoints stay reachable.`);
233
187
  user = degradedUser;
234
188
  }
235
189
  else {
236
- // Name the settings branch: the failure this most often means is a
237
- // changed deploymentName pointing the deployment at a branch that
238
- // isn't the one its workspace was cloned for, and the branch name
239
- // is the part of that an operator can act on without CloudWatch
240
- // (paths are redacted out of client-facing messages).
190
+ // Name the settings branch: this usually means a changed deploymentName
191
+ // pointing the deployment at a branch other than the one its workspace
192
+ // was cloned for, and the branch name is the part of that an operator
193
+ // can act on without CloudWatch (paths are redacted from client-facing
194
+ // messages).
241
195
  const settingsBranch = operatingStrategy(apiCtx.services.config.mode).getSettingsBranchName(apiCtx.services.config);
242
196
  return jsonResponse({
243
197
  ok: false,
@@ -246,16 +200,13 @@ export function createCanopyRequestHandler(options) {
246
200
  }, 503);
247
201
  }
248
202
  }
249
- // API routes require authentication - reject anonymous users
250
203
  if (user.type === 'anonymous') {
251
204
  return jsonResponse({ ok: false, status: 401, error: authResult.error ?? 'Unauthorized' }, 401);
252
205
  }
253
- // Parse query params and merge with route params
254
206
  const queryParams = parseQueryParams(req.url);
255
207
  const mergedParams = { ...queryParams, ...match.params };
256
- // Parse body for non-GET requests. Multipart routes opt out (bodyFormat)
257
- // so their handler can read the (single-use) body stream itself via
258
- // req.formData() - calling req.json() first would consume it.
208
+ // Multipart routes opt out via bodyFormat so their handler can read the
209
+ // single-use body stream itself: calling req.json() first would consume it.
259
210
  let body;
260
211
  if (req.method !== 'GET' && match.bodyFormat !== 'multipart') {
261
212
  try {
@@ -265,17 +216,14 @@ export function createCanopyRequestHandler(options) {
265
216
  body = undefined;
266
217
  }
267
218
  }
268
- // Build API request
269
219
  const branch = mergedParams?.branch ??
270
220
  body?.branch;
271
221
  const apiReq = { user, body, branch, query: queryParams, rawRequest: req };
272
- // Validate params and body using the route's validation function (if available)
273
222
  if (match.validate) {
274
223
  const validationResult = match.validate({ params: mergedParams, body });
275
224
  if (!validationResult.ok) {
276
225
  return jsonResponse({ ok: false, status: 400, error: validationResult.error }, 400);
277
226
  }
278
- // Call handler with validated params/body based on what's defined
279
227
  const handlerArgs = [apiCtx, apiReq];
280
228
  if (validationResult.params !== undefined) {
281
229
  handlerArgs.push(validationResult.params);
@@ -284,16 +232,16 @@ export function createCanopyRequestHandler(options) {
284
232
  handlerArgs.push(validationResult.body);
285
233
  }
286
234
  const result = await match.handler(...handlerArgs);
287
- // Binary routes (e.g. asset serving) carry their own status/headers and
288
- // must reach the adapter untouched - wrapping them in jsonResponse would
289
- // JSON-serialize raw bytes and lose contentType/contentDisposition/etc.
235
+ // Binary routes carry their own status and headers and MUST reach the
236
+ // adapter untouched: jsonResponse would serialize raw bytes and drop
237
+ // contentType/contentDisposition.
290
238
  if (isCanopyBinaryResponse(result))
291
239
  return result;
292
240
  return jsonResponse(result, result.status);
293
241
  }
294
242
  else {
295
- // Should not happen - all routes should use defineEndpoint now
296
- // This is here for safety in case any route doesn't have validation
243
+ // Every route should use defineEndpoint; this is the safety net for one
244
+ // that carries no validate function.
297
245
  const result = await match.handler(apiCtx, apiReq, mergedParams);
298
246
  if (isCanopyBinaryResponse(result))
299
247
  return result;
@@ -312,9 +260,6 @@ export function createCanopyRequestHandler(options) {
312
260
  }
313
261
  };
314
262
  }
315
- /**
316
- * Create a handler with pre-built services from config.
317
- */
318
263
  export async function createCanopyRequestHandlerFromConfig(options) {
319
264
  return createCanopyRequestHandler({
320
265
  ...options,
@@ -1,19 +1,13 @@
1
1
  import type { ApiResponse } from '../api/types.js';
2
2
  import type { CanopyBinaryResponse } from './types.js';
3
3
  /**
4
- * Handler function signature for Canopy API routes.
5
- * Uses `any` to accommodate different handler signatures in the codebase.
6
- * Some handlers take (ctx, req, params), others take (ctx, params) directly.
7
- * Return type is widened to include `CanopyBinaryResponse` so routes that
8
- * stream bytes (e.g. asset serving) can be registered in the same route
9
- * table as ordinary JSON routes; the core handler discriminates on `kind`
10
- * before deciding whether to wrap the result in a JSON envelope.
4
+ * `any` because handler signatures differ: some take (ctx, req, params), others
5
+ * (ctx, params). The return type includes `CanopyBinaryResponse` so byte-
6
+ * streaming routes share this route table with JSON ones; http/handler.ts
7
+ * discriminates on `kind` before wrapping a result in a JSON envelope.
11
8
  */
12
9
  export type CanopyHandler = (...args: any[]) => Promise<ApiResponse<any> | CanopyBinaryResponse>;
13
- /**
14
- * Route definition for the Canopy API.
15
- * Maps HTTP method + path pattern to a handler.
16
- */
10
+ /** Route definition for the Canopy API. */
17
11
  export interface RouteDefinition {
18
12
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
19
13
  pattern: readonly string[];
@@ -30,43 +24,32 @@ export interface RouteDefinition {
30
24
  error: string;
31
25
  };
32
26
  /**
33
- * Opt out of the core handler's default eager `req.json()` body parsing
34
- * (see http/handler.ts). Set by routes that accept a non-JSON body (e.g.
35
- * multipart/form-data uploads) - the handler must read the body itself via
36
- * `req.formData()` instead, since a body stream can only be consumed once.
27
+ * Opts out of the core handler's eager `req.json()` (see http/handler.ts).
28
+ * Set by routes taking a non-JSON body: the body stream is single-use, so
29
+ * such a handler must read it itself via `req.formData()`.
37
30
  */
38
31
  bodyFormat?: 'multipart';
39
32
  }
40
- /**
41
- * Result of route matching.
42
- */
43
33
  export interface RouteMatch {
44
34
  handler: CanopyHandler;
45
35
  params: Record<string, string>;
46
36
  validate?: RouteDefinition['validate'];
47
37
  bodyFormat?: RouteDefinition['bodyFormat'];
48
38
  }
49
- /**
50
- * Core router - framework-agnostic route matching.
51
- */
39
+ /** Core router: framework-agnostic route matching. */
52
40
  export interface CanopyRouter {
53
- /** All registered routes */
54
41
  readonly routes: RouteDefinition[];
55
- /** Find a matching route for the given method and path segments */
56
42
  match(method: string, segments: string[]): RouteMatch | null;
57
43
  }
58
44
  /**
59
45
  * Find the most specific matching route for a method + path.
60
46
  *
61
- * Scans every route (not just the first structural match) so that route
62
- * *registration order* can never let a broad dynamic route (e.g. `:branch`)
63
- * shadow a narrower, differently-guarded static route (e.g. `assets`) that
64
- * happens to be registered later. Exported standalone (rather than inlined
65
- * into `createCanopyRouter`) so the precedence rule can be unit-tested
66
- * against synthetic route tables, independent of the real API surface.
47
+ * Scans EVERY route, not just the first structural match, so registration order
48
+ * can never let a broad dynamic route (`:branch`) shadow a narrower,
49
+ * differently-guarded static one (`assets`) registered later. Exported
50
+ * standalone so that precedence rule can be unit-tested against synthetic route
51
+ * tables, independent of the real API surface.
52
+ * @internal Exported for tests.
67
53
  */
68
54
  export declare function matchRoute(routes: readonly RouteDefinition[], method: string, segments: string[]): RouteMatch | null;
69
- /**
70
- * Create the standard Canopy router with all API routes.
71
- */
72
55
  export declare function createCanopyRouter(): CanopyRouter;
@@ -1,67 +1,19 @@
1
- import { BRANCH_ROUTES } from '../api/branch.js';
2
- import { WORKFLOW_ROUTES } from '../api/branch-status.js';
3
- import { COMMENT_ROUTES } from '../api/comments.js';
4
- import { CONTENT_ROUTES } from '../api/content.js';
5
- import { REFERENCE_OPTIONS_ROUTES } from '../api/reference-options.js';
6
- import { RESOLVE_REFERENCES_ROUTES } from '../api/resolve-references.js';
7
- import { ENTRY_ROUTES } from '../api/entries.js';
8
- import { ASSET_ROUTES, assetRawRoute } from '../api/assets.js';
9
- import { PERMISSION_ROUTES } from '../api/permissions.js';
10
- import { GROUP_ROUTES } from '../api/groups.js';
11
- import { USER_ROUTES } from '../api/user.js';
12
- import { SCHEMA_ROUTES } from '../api/schema.js';
13
- import { ADMIN_ROUTES } from '../api/admin.js';
1
+ import { buildCanopyRoutes } from '../api/routes.js';
14
2
  /**
15
- * Build the standard route definitions for the Canopy API.
16
- * Assembled from co-located route definitions in each API module.
3
+ * Match a route pattern against actual path segments: `:param` takes one
4
+ * segment, `...slug` takes the rest.
17
5
  *
18
- * This is a function (not a top-level constant) to ensure all route modules
19
- * have been fully initialized before we try to access their exports.
20
- * This prevents module initialization timing issues with ES modules.
21
- */
22
- function buildCanopyRoutes() {
23
- return [
24
- ...Object.values(BRANCH_ROUTES),
25
- ...Object.values(WORKFLOW_ROUTES),
26
- ...Object.values(COMMENT_ROUTES),
27
- ...Object.values(CONTENT_ROUTES),
28
- ...Object.values(REFERENCE_OPTIONS_ROUTES),
29
- ...Object.values(RESOLVE_REFERENCES_ROUTES),
30
- ...Object.values(ENTRY_ROUTES),
31
- ...Object.values(ASSET_ROUTES),
32
- assetRawRoute,
33
- ...Object.values(PERMISSION_ROUTES),
34
- ...Object.values(GROUP_ROUTES),
35
- ...Object.values(USER_ROUTES),
36
- ...Object.values(SCHEMA_ROUTES),
37
- ...Object.values(ADMIN_ROUTES),
38
- ].map((route) => ({
39
- method: route.method,
40
- pattern: route.pattern,
41
- handler: route.handler,
42
- // Include validation function if present (new-style routes from defineEndpoint)
43
- validate: 'validate' in route ? route.validate : undefined,
44
- bodyFormat: 'bodyFormat' in route ? route.bodyFormat : undefined,
45
- }));
46
- }
47
- /**
48
- * Match a route pattern against actual path segments.
49
- * Supports :param for single-segment params and ...slug for catch-all.
50
- *
51
- * Params are returned RAW (undecoded) here on purpose (C5): matching is
52
- * purely structural (segment counts, static-segment equality) and must never
53
- * throw. Decoding happens exactly once, uniformly for :param and catch-all
54
- * alike, in matchRoute() below - after the winning route has been picked, so
55
- * a malformed `%` escape is handled in a single place instead of aborting
56
- * mid-match (which previously let a URIError from a bad :param escape to
57
- * `decodeURIComponent` bubble all the way to http/handler.ts's top-level
58
- * catch and surface as a 500).
6
+ * Params come back RAW (undecoded) on purpose (C5): matching is purely
7
+ * structural and must NEVER throw. Decoding happens exactly once, uniformly for
8
+ * `:param` and catch-all alike, in matchRoute() below, after the winning route
9
+ * is picked — so a malformed `%` escape is handled in one place instead of
10
+ * aborting mid-match, where the URIError would reach http/handler.ts's
11
+ * top-level catch and surface as a 500 rather than a 400.
59
12
  */
60
13
  const matchPattern = (pattern, actual) => {
61
14
  const params = {};
62
15
  const actualCopy = [...actual];
63
16
  for (const part of pattern) {
64
- // Catch-all: consume remaining segments
65
17
  if (part.startsWith('...')) {
66
18
  const paramName = part.slice(3); // Remove '...' prefix
67
19
  params[paramName] = actualCopy.join('/');
@@ -72,24 +24,21 @@ const matchPattern = (pattern, actual) => {
72
24
  if (!next)
73
25
  return null;
74
26
  if (part.startsWith(':')) {
75
- // Dynamic segment - extract param (still raw; see decode note above)
27
+ // Still raw; see the decode note above.
76
28
  params[part.slice(1)] = next;
77
29
  }
78
30
  else if (part !== next) {
79
- // Static segment - must match exactly
80
31
  return null;
81
32
  }
82
33
  }
83
- // If there are leftover segments, no match
84
34
  if (actualCopy.length > 0)
85
35
  return null;
86
36
  return { params };
87
37
  };
88
38
  /**
89
- * Synthetic handler for a request whose matched route params contain a
90
- * malformed `%` escape sequence (C5) - e.g. a lone `%` or `%zz`. Reported as
91
- * a 400 (the request is malformed), never registered in the real route
92
- * table, and only ever reachable via matchRoute()'s decode step below.
39
+ * Synthetic handler for matched params carrying a malformed `%` escape (C5) —
40
+ * a lone `%`, `%zz`. The request is malformed, so it answers 400. Never
41
+ * registered in the route table; only matchRoute()'s decode step reaches it.
93
42
  */
94
43
  const malformedPathHandler = async () => ({
95
44
  ok: false,
@@ -97,18 +46,16 @@ const malformedPathHandler = async () => ({
97
46
  error: 'Malformed URL-encoded path segment',
98
47
  });
99
48
  /**
100
- * Specificity rank of a pattern segment. Higher wins.
101
- * A literal ("static") segment is more specific than a `:param`, which is
102
- * more specific than a `...catchall`.
49
+ * Specificity rank of a pattern segment, higher wins: a literal beats a
50
+ * `:param`, which beats a `...catchall`.
103
51
  */
104
52
  const STATIC_RANK = 2;
105
53
  const DYNAMIC_RANK = 1;
106
54
  const CATCHALL_RANK = 0;
107
55
  /**
108
- * Rank of the pattern segment governing position `index`.
109
- * A catch-all at or before `index` governs every position from there on
110
- * (in this codebase a catch-all is always the last pattern segment, so it
111
- * effectively "consumes" every subsequent position).
56
+ * Rank of the pattern segment governing position `index`. A catch-all at or
57
+ * before `index` governs every position from there on — it is always the last
58
+ * pattern segment here, so it consumes everything after itself.
112
59
  */
113
60
  const segmentRankAt = (pattern, index) => {
114
61
  for (let i = 0; i <= index && i < pattern.length; i++) {
@@ -120,13 +67,10 @@ const segmentRankAt = (pattern, index) => {
120
67
  return pattern[index].startsWith(':') ? DYNAMIC_RANK : STATIC_RANK;
121
68
  };
122
69
  /**
123
- * Compare two route patterns for specificity, position by position, for
124
- * routes that both matched the same actual segments.
125
- *
126
- * Returns a negative number if `a` is more specific than `b`, positive if
127
- * `b` is more specific than `a`, and 0 if they are tied (in which case the
128
- * caller should keep whichever route it already picked, so registration
129
- * order acts as the final, deterministic tiebreaker).
70
+ * Compare two patterns that both matched, position by position: negative when
71
+ * `a` is more specific, positive when `b` is, 0 when tied. On a tie the caller
72
+ * keeps the route it already picked, so registration order is the final,
73
+ * deterministic tiebreaker.
130
74
  */
131
75
  const compareSpecificity = (a, b) => {
132
76
  const maxLen = Math.max(a.length, b.length);
@@ -145,12 +89,12 @@ const compareSpecificity = (a, b) => {
145
89
  /**
146
90
  * Find the most specific matching route for a method + path.
147
91
  *
148
- * Scans every route (not just the first structural match) so that route
149
- * *registration order* can never let a broad dynamic route (e.g. `:branch`)
150
- * shadow a narrower, differently-guarded static route (e.g. `assets`) that
151
- * happens to be registered later. Exported standalone (rather than inlined
152
- * into `createCanopyRouter`) so the precedence rule can be unit-tested
153
- * against synthetic route tables, independent of the real API surface.
92
+ * Scans EVERY route, not just the first structural match, so registration order
93
+ * can never let a broad dynamic route (`:branch`) shadow a narrower,
94
+ * differently-guarded static one (`assets`) registered later. Exported
95
+ * standalone so that precedence rule can be unit-tested against synthetic route
96
+ * tables, independent of the real API surface.
97
+ * @internal Exported for tests.
154
98
  */
155
99
  export function matchRoute(routes, method, segments) {
156
100
  const upperMethod = method.toUpperCase();
@@ -161,24 +105,18 @@ export function matchRoute(routes, method, segments) {
161
105
  const match = matchPattern(route.pattern, segments);
162
106
  if (!match)
163
107
  continue;
164
- // Among all routes that match this request, pick the most specific one
165
- // (static segments beat :params, which beat ...catchalls).
166
108
  if (!best || compareSpecificity(route.pattern, best.route.pattern) < 0) {
167
109
  best = { route, params: match.params };
168
110
  }
169
111
  }
170
112
  if (!best)
171
113
  return null;
172
- // Decode every matched param exactly once, here - uniformly for :param and
173
- // catch-all values alike (C5). A malformed escape must produce a 400, not
174
- // an uncaught URIError that would otherwise reach http/handler.ts's
175
- // top-level catch and get reported as a 500. Downstream consumers
176
- // (Zod's logicalPathSchema/branchNameSchema etc. in each route's
177
- // `validate`, and any handler-local re-check such as entries.ts's
178
- // parseLogicalPath call) still run their own traversal validation against
179
- // this same final decoded value - decoding centrally here does not skip
180
- // that, it just removes the redundant second decode that used to run
181
- // alongside it in a few handlers.
114
+ // Decode every matched param exactly once, here, uniformly for :param and
115
+ // catch-all alike (C5). A malformed escape MUST produce a 400, not an
116
+ // uncaught URIError reaching http/handler.ts's top-level catch as a 500.
117
+ // Decoding centrally does not replace downstream validation: each route's
118
+ // `validate` (Zod's logicalPathSchema/branchNameSchema) and any handler-local
119
+ // re-check still run their own traversal checks against this decoded value.
182
120
  let decodedParams;
183
121
  try {
184
122
  decodedParams = Object.fromEntries(Object.entries(best.params).map(([key, value]) => [key, decodeURIComponent(value)]));
@@ -193,9 +131,6 @@ export function matchRoute(routes, method, segments) {
193
131
  bodyFormat: best.route.bodyFormat,
194
132
  };
195
133
  }
196
- /**
197
- * Create the standard Canopy router with all API routes.
198
- */
199
134
  export function createCanopyRouter() {
200
135
  const routes = buildCanopyRoutes();
201
136
  return {