canopycms 0.0.56 → 0.0.58

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 (272) hide show
  1. package/dist/ai/json-to-markdown.js +74 -3
  2. package/dist/ai/json-to-markdown.js.map +1 -1
  3. package/dist/api/__test__/mock-client.d.ts +9 -5
  4. package/dist/api/__test__/mock-client.d.ts.map +1 -1
  5. package/dist/api/__test__/mock-client.js +15 -7
  6. package/dist/api/__test__/mock-client.js.map +1 -1
  7. package/dist/api/assets.d.ts +100 -36
  8. package/dist/api/assets.d.ts.map +1 -1
  9. package/dist/api/assets.js +338 -51
  10. package/dist/api/assets.js.map +1 -1
  11. package/dist/api/branch.d.ts +6 -0
  12. package/dist/api/branch.d.ts.map +1 -1
  13. package/dist/api/branch.js +63 -19
  14. package/dist/api/branch.js.map +1 -1
  15. package/dist/api/client.d.ts +16 -5
  16. package/dist/api/client.d.ts.map +1 -1
  17. package/dist/api/client.js +24 -6
  18. package/dist/api/client.js.map +1 -1
  19. package/dist/api/content.d.ts +2 -2
  20. package/dist/api/entries.d.ts +4 -4
  21. package/dist/api/entries.d.ts.map +1 -1
  22. package/dist/api/entries.js +10 -2
  23. package/dist/api/entries.js.map +1 -1
  24. package/dist/api/index.d.ts +1 -1
  25. package/dist/api/index.d.ts.map +1 -1
  26. package/dist/api/index.js.map +1 -1
  27. package/dist/api/route-builder.d.ts +9 -0
  28. package/dist/api/route-builder.d.ts.map +1 -1
  29. package/dist/api/route-builder.js +1 -0
  30. package/dist/api/route-builder.js.map +1 -1
  31. package/dist/api/schema.d.ts +4 -4
  32. package/dist/api/types.d.ts +11 -1
  33. package/dist/api/types.d.ts.map +1 -1
  34. package/dist/assets/asset-prefixes.d.ts +31 -0
  35. package/dist/assets/asset-prefixes.d.ts.map +1 -0
  36. package/dist/assets/asset-prefixes.js +20 -0
  37. package/dist/assets/asset-prefixes.js.map +1 -0
  38. package/dist/assets/asset-src.d.ts +23 -0
  39. package/dist/assets/asset-src.d.ts.map +1 -0
  40. package/dist/assets/asset-src.js +28 -0
  41. package/dist/assets/asset-src.js.map +1 -0
  42. package/dist/assets/asset-url.d.ts +35 -0
  43. package/dist/assets/asset-url.d.ts.map +1 -0
  44. package/dist/assets/asset-url.js +77 -0
  45. package/dist/assets/asset-url.js.map +1 -0
  46. package/dist/assets/factory.d.ts +18 -0
  47. package/dist/assets/factory.d.ts.map +1 -0
  48. package/dist/assets/factory.js +42 -0
  49. package/dist/assets/factory.js.map +1 -0
  50. package/dist/assets/finalize.d.ts +36 -0
  51. package/dist/assets/finalize.d.ts.map +1 -0
  52. package/dist/assets/finalize.js +72 -0
  53. package/dist/assets/finalize.js.map +1 -0
  54. package/dist/assets/index.d.ts +20 -0
  55. package/dist/assets/index.d.ts.map +1 -0
  56. package/dist/assets/index.js +19 -0
  57. package/dist/assets/index.js.map +1 -0
  58. package/dist/assets/keys.d.ts +56 -0
  59. package/dist/assets/keys.d.ts.map +1 -0
  60. package/dist/assets/keys.js +88 -0
  61. package/dist/assets/keys.js.map +1 -0
  62. package/dist/assets/pipeline.d.ts +50 -0
  63. package/dist/assets/pipeline.d.ts.map +1 -0
  64. package/dist/assets/pipeline.js +202 -0
  65. package/dist/assets/pipeline.js.map +1 -0
  66. package/dist/assets/store-local.d.ts +84 -0
  67. package/dist/assets/store-local.d.ts.map +1 -0
  68. package/dist/assets/store-local.js +254 -0
  69. package/dist/assets/store-local.js.map +1 -0
  70. package/dist/assets/store-s3.d.ts +71 -0
  71. package/dist/assets/store-s3.d.ts.map +1 -0
  72. package/dist/assets/store-s3.js +183 -0
  73. package/dist/assets/store-s3.js.map +1 -0
  74. package/dist/assets/svg-sanitizer.d.ts +37 -0
  75. package/dist/assets/svg-sanitizer.d.ts.map +1 -0
  76. package/dist/assets/svg-sanitizer.js +222 -0
  77. package/dist/assets/svg-sanitizer.js.map +1 -0
  78. package/dist/assets/transform-directives.d.ts +97 -0
  79. package/dist/assets/transform-directives.d.ts.map +1 -0
  80. package/dist/assets/transform-directives.js +267 -0
  81. package/dist/assets/transform-directives.js.map +1 -0
  82. package/dist/assets/transform.d.ts +51 -0
  83. package/dist/assets/transform.d.ts.map +1 -0
  84. package/dist/assets/transform.js +190 -0
  85. package/dist/assets/transform.js.map +1 -0
  86. package/dist/assets/types.d.ts +82 -0
  87. package/dist/assets/types.d.ts.map +1 -0
  88. package/dist/assets/types.js +10 -0
  89. package/dist/assets/types.js.map +1 -0
  90. package/dist/authorization/groups/schema.d.ts +2 -2
  91. package/dist/authorization/permissions/schema.d.ts +2 -2
  92. package/dist/branch-metadata.d.ts +71 -6
  93. package/dist/branch-metadata.d.ts.map +1 -1
  94. package/dist/branch-metadata.js +132 -116
  95. package/dist/branch-metadata.js.map +1 -1
  96. package/dist/branch-registry.d.ts +78 -9
  97. package/dist/branch-registry.d.ts.map +1 -1
  98. package/dist/branch-registry.js +155 -29
  99. package/dist/branch-registry.js.map +1 -1
  100. package/dist/branch-schema-cache.d.ts +126 -11
  101. package/dist/branch-schema-cache.d.ts.map +1 -1
  102. package/dist/branch-schema-cache.js +208 -58
  103. package/dist/branch-schema-cache.js.map +1 -1
  104. package/dist/cli/generate-ai-content.js +1993 -1506
  105. package/dist/cli/init.js +16 -4
  106. package/dist/cli/migrate.d.ts.map +1 -1
  107. package/dist/cli/migrate.js +25 -0
  108. package/dist/cli/migrate.js.map +1 -1
  109. package/dist/cli/sync.js +5 -5
  110. package/dist/cli/sync.js.map +1 -1
  111. package/dist/comment-store.d.ts +45 -15
  112. package/dist/comment-store.d.ts.map +1 -1
  113. package/dist/comment-store.js +80 -101
  114. package/dist/comment-store.js.map +1 -1
  115. package/dist/config/helpers.d.ts.map +1 -1
  116. package/dist/config/helpers.js +2 -1
  117. package/dist/config/helpers.js.map +1 -1
  118. package/dist/config/index.d.ts +2 -2
  119. package/dist/config/index.d.ts.map +1 -1
  120. package/dist/config/index.js +1 -1
  121. package/dist/config/index.js.map +1 -1
  122. package/dist/config/schemas/config.d.ts +10 -0
  123. package/dist/config/schemas/config.d.ts.map +1 -1
  124. package/dist/config/schemas/field.d.ts +38 -3
  125. package/dist/config/schemas/field.d.ts.map +1 -1
  126. package/dist/config/schemas/field.js +18 -0
  127. package/dist/config/schemas/field.js.map +1 -1
  128. package/dist/config/schemas/media.d.ts +8 -0
  129. package/dist/config/schemas/media.d.ts.map +1 -1
  130. package/dist/config/schemas/media.js +9 -0
  131. package/dist/config/schemas/media.js.map +1 -1
  132. package/dist/config/types.d.ts +43 -3
  133. package/dist/config/types.d.ts.map +1 -1
  134. package/dist/config/types.js +4 -1
  135. package/dist/config/types.js.map +1 -1
  136. package/dist/content-index-generation.d.ts +40 -4
  137. package/dist/content-index-generation.d.ts.map +1 -1
  138. package/dist/content-index-generation.js +70 -73
  139. package/dist/content-index-generation.js.map +1 -1
  140. package/dist/content-store.d.ts +63 -0
  141. package/dist/content-store.d.ts.map +1 -1
  142. package/dist/content-store.js +421 -194
  143. package/dist/content-store.js.map +1 -1
  144. package/dist/editor/CanopyEditor.d.ts.map +1 -1
  145. package/dist/editor/CanopyEditor.js +1 -1
  146. package/dist/editor/CanopyEditor.js.map +1 -1
  147. package/dist/editor/Editor.d.ts +2 -0
  148. package/dist/editor/Editor.d.ts.map +1 -1
  149. package/dist/editor/Editor.js +82 -80
  150. package/dist/editor/Editor.js.map +1 -1
  151. package/dist/editor/FormRenderer.d.ts.map +1 -1
  152. package/dist/editor/FormRenderer.js +9 -0
  153. package/dist/editor/FormRenderer.js.map +1 -1
  154. package/dist/editor/components/EditorSidebar.d.ts +6 -1
  155. package/dist/editor/components/EditorSidebar.d.ts.map +1 -1
  156. package/dist/editor/components/EditorSidebar.js +4 -3
  157. package/dist/editor/components/EditorSidebar.js.map +1 -1
  158. package/dist/editor/context/AssetContext.d.ts +25 -0
  159. package/dist/editor/context/AssetContext.d.ts.map +1 -0
  160. package/dist/editor/context/AssetContext.js +26 -0
  161. package/dist/editor/context/AssetContext.js.map +1 -0
  162. package/dist/editor/context/index.d.ts +1 -0
  163. package/dist/editor/context/index.d.ts.map +1 -1
  164. package/dist/editor/context/index.js +1 -0
  165. package/dist/editor/context/index.js.map +1 -1
  166. package/dist/editor/fields/ImageField.d.ts +22 -0
  167. package/dist/editor/fields/ImageField.d.ts.map +1 -0
  168. package/dist/editor/fields/ImageField.js +100 -0
  169. package/dist/editor/fields/ImageField.js.map +1 -0
  170. package/dist/editor/fields/MarkdownField.d.ts.map +1 -1
  171. package/dist/editor/fields/MarkdownField.js +30 -4
  172. package/dist/editor/fields/MarkdownField.js.map +1 -1
  173. package/dist/editor/fields/MdxImageDialog.d.ts +16 -0
  174. package/dist/editor/fields/MdxImageDialog.d.ts.map +1 -0
  175. package/dist/editor/fields/MdxImageDialog.js +73 -0
  176. package/dist/editor/fields/MdxImageDialog.js.map +1 -0
  177. package/dist/editor/media/AssetCard.d.ts +21 -0
  178. package/dist/editor/media/AssetCard.d.ts.map +1 -0
  179. package/dist/editor/media/AssetCard.js +41 -0
  180. package/dist/editor/media/AssetCard.js.map +1 -0
  181. package/dist/editor/media/CropStep.d.ts +26 -0
  182. package/dist/editor/media/CropStep.d.ts.map +1 -0
  183. package/dist/editor/media/CropStep.js +38 -0
  184. package/dist/editor/media/CropStep.js.map +1 -0
  185. package/dist/editor/media/MediaLibrary.d.ts +22 -0
  186. package/dist/editor/media/MediaLibrary.d.ts.map +1 -0
  187. package/dist/editor/media/MediaLibrary.js +17 -0
  188. package/dist/editor/media/MediaLibrary.js.map +1 -0
  189. package/dist/editor/media/MediaLibraryBody.d.ts +17 -0
  190. package/dist/editor/media/MediaLibraryBody.d.ts.map +1 -0
  191. package/dist/editor/media/MediaLibraryBody.js +133 -0
  192. package/dist/editor/media/MediaLibraryBody.js.map +1 -0
  193. package/dist/editor/media/crop-math.d.ts +37 -0
  194. package/dist/editor/media/crop-math.d.ts.map +1 -0
  195. package/dist/editor/media/crop-math.js +62 -0
  196. package/dist/editor/media/crop-math.js.map +1 -0
  197. package/dist/editor/media/upload-asset.d.ts +31 -0
  198. package/dist/editor/media/upload-asset.d.ts.map +1 -0
  199. package/dist/editor/media/upload-asset.js +56 -0
  200. package/dist/editor/media/upload-asset.js.map +1 -0
  201. package/dist/editor/media/upload-constants.d.ts +27 -0
  202. package/dist/editor/media/upload-constants.d.ts.map +1 -0
  203. package/dist/editor/media/upload-constants.js +40 -0
  204. package/dist/editor/media/upload-constants.js.map +1 -0
  205. package/dist/editor/media/useAssetUpload.d.ts +19 -0
  206. package/dist/editor/media/useAssetUpload.d.ts.map +1 -0
  207. package/dist/editor/media/useAssetUpload.js +53 -0
  208. package/dist/editor/media/useAssetUpload.js.map +1 -0
  209. package/dist/editor/media/xhr-upload.d.ts +20 -0
  210. package/dist/editor/media/xhr-upload.d.ts.map +1 -0
  211. package/dist/editor/media/xhr-upload.js +42 -0
  212. package/dist/editor/media/xhr-upload.js.map +1 -0
  213. package/dist/editor/theme.d.ts +2 -1
  214. package/dist/editor/theme.d.ts.map +1 -1
  215. package/dist/editor/theme.js +1 -0
  216. package/dist/editor/theme.js.map +1 -1
  217. package/dist/entry-schema.d.ts +19 -0
  218. package/dist/entry-schema.d.ts.map +1 -1
  219. package/dist/entry-schema.js.map +1 -1
  220. package/dist/git-manager.d.ts +15 -10
  221. package/dist/git-manager.d.ts.map +1 -1
  222. package/dist/git-manager.js +16 -11
  223. package/dist/git-manager.js.map +1 -1
  224. package/dist/http/handler.d.ts +5 -2
  225. package/dist/http/handler.d.ts.map +1 -1
  226. package/dist/http/handler.js +13 -4
  227. package/dist/http/handler.js.map +1 -1
  228. package/dist/http/index.d.ts +2 -2
  229. package/dist/http/index.d.ts.map +1 -1
  230. package/dist/http/index.js +1 -1
  231. package/dist/http/index.js.map +1 -1
  232. package/dist/http/router.d.ts +14 -1
  233. package/dist/http/router.d.ts.map +1 -1
  234. package/dist/http/router.js +4 -1
  235. package/dist/http/router.js.map +1 -1
  236. package/dist/http/types.d.ts +40 -0
  237. package/dist/http/types.d.ts.map +1 -1
  238. package/dist/http/types.js +9 -0
  239. package/dist/http/types.js.map +1 -1
  240. package/dist/index.d.ts +3 -0
  241. package/dist/index.d.ts.map +1 -1
  242. package/dist/index.js +4 -0
  243. package/dist/index.js.map +1 -1
  244. package/dist/resource-generation.d.ts +54 -0
  245. package/dist/resource-generation.d.ts.map +1 -0
  246. package/dist/resource-generation.js +156 -0
  247. package/dist/resource-generation.js.map +1 -0
  248. package/dist/schema/schema-store.d.ts +31 -4
  249. package/dist/schema/schema-store.d.ts.map +1 -1
  250. package/dist/schema/schema-store.js +47 -8
  251. package/dist/schema/schema-store.js.map +1 -1
  252. package/dist/server.d.ts +45 -0
  253. package/dist/server.d.ts.map +1 -1
  254. package/dist/server.js +41 -0
  255. package/dist/server.js.map +1 -1
  256. package/dist/sync-core.js +2 -2
  257. package/dist/sync-core.js.map +1 -1
  258. package/dist/utils/occ-json-write.d.ts +100 -0
  259. package/dist/utils/occ-json-write.d.ts.map +1 -0
  260. package/dist/utils/occ-json-write.js +234 -0
  261. package/dist/utils/occ-json-write.js.map +1 -0
  262. package/dist/validation/entry-validator.d.ts.map +1 -1
  263. package/dist/validation/entry-validator.js +56 -1
  264. package/dist/validation/entry-validator.js.map +1 -1
  265. package/dist/worker/cms-worker.d.ts.map +1 -1
  266. package/dist/worker/cms-worker.js +21 -3
  267. package/dist/worker/cms-worker.js.map +1 -1
  268. package/package.json +13 -1
  269. package/dist/asset-store.d.ts +0 -32
  270. package/dist/asset-store.d.ts.map +0 -1
  271. package/dist/asset-store.js +0 -85
  272. package/dist/asset-store.js.map +0 -1
@@ -1,65 +1,359 @@
1
1
  import { z } from 'zod';
2
2
  import { defineEndpoint } from './route-builder.js';
3
+ import { ASSET_PREFIXES } from '../assets/keys.js';
4
+ import { ALLOWED_UPLOAD_CONTENT_TYPES } from '../assets/pipeline.js';
5
+ import { finalizeStagedUpload } from '../assets/finalize.js';
6
+ import { assetSrc } from '../assets/asset-src.js';
7
+ import { formatDirectives, parseTransformPath } from '../assets/transform-directives.js';
8
+ import { applyTransform } from '../assets/transform.js';
9
+ function toAssetRecord(meta) {
10
+ return { ...meta, src: assetSrc(meta) };
11
+ }
12
+ const MOCK_ASSET_RECORD = {
13
+ hash32: 'a'.repeat(32),
14
+ filename: 'sample.png',
15
+ slug: 'sample',
16
+ ext: 'png',
17
+ mime: 'image/png',
18
+ size: 1024,
19
+ width: 100,
20
+ height: 100,
21
+ kind: 'raster',
22
+ uploadedAt: '2024-01-01T00:00:00.000Z',
23
+ src: '/assets/t/orig/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/sample.png',
24
+ };
3
25
  // ============================================================================
4
26
  // Zod Schemas for Validation
5
27
  // ============================================================================
6
- const uploadAssetBodySchema = z.object({
7
- key: z.string().min(1),
8
- contentType: z.string().optional(),
9
- data: z.instanceof(Buffer).or(z.instanceof(Uint8Array)),
28
+ const filenameSchema = z.string().min(1).max(255);
29
+ const presignAssetBodySchema = z.object({
30
+ filename: filenameSchema,
31
+ contentType: z.string().min(1),
32
+ size: z.number().int().positive().optional(),
33
+ });
34
+ const finalizeAssetBodySchema = z.object({
35
+ stagingKey: z.string().min(1),
36
+ filename: filenameSchema,
10
37
  });
11
38
  /**
12
- * List assets - any authenticated user can list assets.
13
- *
14
39
  * Declared as `params` (not just parsed ad hoc from req.query) so the client
15
40
  * generator (scripts/generate-client.ts) sees a paramsSchema and emits a
16
- * method that accepts and forwards `prefix` (API-H4) instead of a no-arg
17
- * `assets.list()` that can never pass it.
41
+ * method that accepts and forwards `cursor`/`limit` (API-H4) instead of a
42
+ * no-arg `assets.list()` that can never pass them.
18
43
  */
19
44
  const listAssetsParamsSchema = z.object({
20
- prefix: z.string().optional(),
45
+ cursor: z.string().optional(),
46
+ limit: z.coerce.number().int().positive().max(100).optional(),
21
47
  });
22
- const listAssetsHandler = async (ctx, req) => {
48
+ /** hash32 is a sha-256 truncated to 32 hex chars (keys.ts's `hashBytes`) - never any other shape. */
49
+ const hash32Schema = z
50
+ .string()
51
+ .regex(/^[a-f0-9]{32}$/, 'key must be a 32-character lowercase hex string');
52
+ const deleteAssetParamsSchema = z.object({ key: hash32Schema });
53
+ // ============================================================================
54
+ // Handlers
55
+ // ============================================================================
56
+ /**
57
+ * Presign a direct (or proxied) upload target. Any authenticated user - there
58
+ * is no finer "editor" role, and upload needs to work for every non-admin
59
+ * user too (see guard semantics in .claude/future-tasks/assets-media-system.md).
60
+ */
61
+ const presignAssetHandler = async (ctx, _req, body) => {
23
62
  if (!ctx.assetStore)
24
63
  return { ok: false, status: 501, error: 'Asset store not configured' };
25
- const query = listAssetsParamsSchema.safeParse(req.query ?? {});
26
- if (!query.success) {
27
- return { ok: false, status: 400, error: query.error.message };
64
+ if (!ALLOWED_UPLOAD_CONTENT_TYPES.has(body.contentType)) {
65
+ return { ok: false, status: 415, error: `Unsupported content type: ${body.contentType}` };
66
+ }
67
+ const target = await ctx.assetStore.beginUpload({
68
+ filename: body.filename,
69
+ contentType: body.contentType,
70
+ size: body.size,
71
+ });
72
+ // The client-declared size is only a fast, up-front UX check - the store's
73
+ // own presigned target (S3's content-length-range condition, or the local
74
+ // store's own cap) is what actually enforces the limit at upload time.
75
+ if (body.size !== undefined && body.size > target.maxBytes) {
76
+ return { ok: false, status: 413, error: `File exceeds the ${target.maxBytes}-byte limit` };
28
77
  }
29
- const assets = await ctx.assetStore.list(query.data.prefix ?? '');
30
- return { ok: true, status: 200, data: { assets } };
78
+ return { ok: true, status: 200, data: { upload: target } };
31
79
  };
32
80
  /**
33
- * Upload asset - requires privileged access (Admin or Reviewer).
81
+ * Finalize a staged upload: read the staged bytes, run the finalize pipeline
82
+ * (sniff/hash/dims/sanitize), write originals + meta, and return the result.
83
+ * Any authenticated user (same rationale as presign).
34
84
  */
35
- const uploadAssetHandler = async (_gc, ctx, req, body) => {
85
+ const finalizeAssetHandler = async (ctx, req, body) => {
36
86
  if (!ctx.assetStore)
37
87
  return { ok: false, status: 501, error: 'Asset store not configured' };
38
- const asset = await ctx.assetStore.upload(body.key, body.data, body.contentType);
39
- return { ok: true, status: 200, data: { asset } };
88
+ const result = await finalizeStagedUpload(ctx.assetStore, body.stagingKey, body.filename, req.user.userId);
89
+ if (!result.ok) {
90
+ return { ok: false, status: result.status, error: result.error };
91
+ }
92
+ return { ok: true, status: 200, data: { asset: toAssetRecord(result.meta) } };
40
93
  };
41
94
  /**
42
- * Delete asset - requires Admin access.
43
- *
44
- * Declared as `params` (see listAssetsParamsSchema above) so the generated
45
- * client's `assets.delete()` method accepts and forwards `key` (API-H4).
95
+ * Proxied upload for stores that don't support direct-to-storage presigning
96
+ * (LocalAssetStore in dev). Reads a multipart/form-data body (a `file` part,
97
+ * plus an optional `filename` field overriding `file.name`) rather than JSON -
98
+ * this route sets `bodyFormat: 'multipart'` below so the core handler skips
99
+ * its default `req.json()` parsing, since the body stream can only be read
100
+ * once (see http/handler.ts). Any authenticated user (same rationale as
101
+ * presign/finalize).
46
102
  */
47
- const deleteAssetParamsSchema = z.object({ key: z.string().min(1) });
48
- const deleteAssetHandler = async (_gc, ctx, req) => {
103
+ const uploadProxiedHandler = async (ctx, req) => {
49
104
  if (!ctx.assetStore)
50
105
  return { ok: false, status: 501, error: 'Asset store not configured' };
51
- const deleteQuery = deleteAssetParamsSchema.safeParse(req.query ?? {});
52
- if (!deleteQuery.success) {
53
- return { ok: false, status: 400, error: 'key query parameter required' };
106
+ if (ctx.assetStore.capabilities.directUpload) {
107
+ return {
108
+ ok: false,
109
+ status: 400,
110
+ error: 'This asset store supports direct upload - use POST /assets/presign instead',
111
+ };
54
112
  }
55
- await ctx.assetStore.delete(deleteQuery.data.key);
113
+ if (!req.rawRequest?.formData) {
114
+ return {
115
+ ok: false,
116
+ status: 400,
117
+ error: 'This server adapter does not support multipart form-data uploads',
118
+ };
119
+ }
120
+ // Early size guard from the Content-Length header, BEFORE the multipart
121
+ // body is ever read - `formData()`/`filePart.arrayBuffer()` below fully
122
+ // buffer the upload into memory, so without this an over-cap request still
123
+ // pays the full read cost before the (correct, but too-late) post-read
124
+ // check further down rejects it. `beginUpload()` is the store-agnostic way
125
+ // to learn "the store's max" (mirrors presignAssetHandler's own use of it)
126
+ // - filename/contentType are placeholders here since only `.maxBytes` is
127
+ // read; the real `beginUpload()` call below (with the actual filename)
128
+ // still runs after the body is read to build the real staging target.
129
+ const contentLengthHeader = req.rawRequest.header('content-length');
130
+ if (contentLengthHeader !== null) {
131
+ const contentLength = Number(contentLengthHeader);
132
+ if (Number.isFinite(contentLength)) {
133
+ const { maxBytes } = await ctx.assetStore.beginUpload({
134
+ filename: 'upload',
135
+ contentType: 'application/octet-stream',
136
+ });
137
+ if (contentLength > maxBytes) {
138
+ return { ok: false, status: 413, error: `File exceeds the ${maxBytes}-byte limit` };
139
+ }
140
+ }
141
+ }
142
+ let formData;
143
+ try {
144
+ formData = await req.rawRequest.formData();
145
+ }
146
+ catch {
147
+ return { ok: false, status: 400, error: 'Could not parse multipart form-data body' };
148
+ }
149
+ const filePart = formData.get('file');
150
+ if (!(filePart instanceof Blob)) {
151
+ return { ok: false, status: 400, error: 'A "file" part is required' };
152
+ }
153
+ const filenameOverride = formData.get('filename');
154
+ const filename = typeof filenameOverride === 'string' && filenameOverride.length > 0
155
+ ? filenameOverride
156
+ : filePart instanceof File
157
+ ? filePart.name
158
+ : 'upload';
159
+ const data = new Uint8Array(await filePart.arrayBuffer());
160
+ const target = await ctx.assetStore.beginUpload({
161
+ filename,
162
+ contentType: filePart.type || 'application/octet-stream',
163
+ size: data.byteLength,
164
+ });
165
+ // Defense-in-depth: the Content-Length guard above is the primary check
166
+ // (it runs before the body is buffered at all) but a missing or lying
167
+ // Content-Length header would skip it entirely, so the actual byte count
168
+ // is still re-checked here against the same store-provided bound.
169
+ if (data.byteLength > target.maxBytes) {
170
+ return { ok: false, status: 413, error: `File exceeds the ${target.maxBytes}-byte limit` };
171
+ }
172
+ await ctx.assetStore.writeStaging(target.stagingKey, data, filePart.type || undefined);
173
+ const result = await finalizeStagedUpload(ctx.assetStore, target.stagingKey, filename, req.user.userId);
174
+ if (!result.ok) {
175
+ return { ok: false, status: result.status, error: result.error };
176
+ }
177
+ return { ok: true, status: 200, data: { asset: toAssetRecord(result.meta) } };
178
+ };
179
+ /**
180
+ * List assets - any authenticated user can list assets (key enumeration is
181
+ * accepted: unlisted != private, see assets-media-system.md).
182
+ */
183
+ const listAssetsHandler = async (ctx, _req, params) => {
184
+ if (!ctx.assetStore)
185
+ return { ok: false, status: 501, error: 'Asset store not configured' };
186
+ const { items, nextCursor } = await ctx.assetStore.listMeta({
187
+ cursor: params.cursor,
188
+ limit: params.limit,
189
+ });
190
+ return { ok: true, status: 200, data: { assets: items.map(toAssetRecord), nextCursor } };
191
+ };
192
+ /**
193
+ * Delete asset - requires Admin access. `key` is the asset's hash32 and is
194
+ * validated (32 lowercase hex chars) by `deleteAssetParamsSchema` before this
195
+ * handler ever runs. Deletes the meta sidecar only - blobs are immortal until
196
+ * a future GC worker task (see assets-media-system.md).
197
+ */
198
+ const deleteAssetHandler = async (_gc, ctx, _req, params) => {
199
+ if (!ctx.assetStore)
200
+ return { ok: false, status: 501, error: 'Asset store not configured' };
201
+ await ctx.assetStore.deleteMeta(params.key);
56
202
  return { ok: true, status: 200, data: { deleted: true } };
57
203
  };
204
+ /** Cache-Control applied to every transform output this route writes/serves - matches finalize.ts's PUBLIC_CACHE_CONTROL for static public objects. */
205
+ const TRANSFORM_CACHE_CONTROL = 'public, max-age=31536000, immutable';
206
+ /**
207
+ * Lazy dev-mode emulation of the prod transform Lambda (PR 7 reuses
208
+ * `parseTransformPath`/`formatDirectives`/`applyTransform` unchanged): parse
209
+ * the request, load the original, transform it, write the result back under
210
+ * its CANONICAL key (so a non-canonically-ordered directive string still
211
+ * dedupes with any equivalent request), then serve the bytes just computed
212
+ * (no re-read from the store).
213
+ *
214
+ * `key` here is the full store key already confirmed to start with the
215
+ * `assets/t/` prefix and to have missed the cache-hit `readPublicObject`
216
+ * check in `rawAssetHandler`.
217
+ */
218
+ async function serveLazyTransform(assetStore, key) {
219
+ const rest = key.slice(ASSET_PREFIXES.transform.length + 1);
220
+ const parsed = parseTransformPath(rest.split('/'));
221
+ if (!parsed.ok) {
222
+ return { ok: false, status: 400, error: parsed.error };
223
+ }
224
+ const meta = await assetStore.getMeta(parsed.hash32);
225
+ if (!meta) {
226
+ return { ok: false, status: 404, error: 'Not found' };
227
+ }
228
+ if (meta.kind !== 'raster') {
229
+ return { ok: false, status: 400, error: 'Not a raster asset - svg/pdf are served statically' };
230
+ }
231
+ // When the URL omits an explicit `f=` format, the transform preserves the
232
+ // source format, so the URL's `{ext}` must match the source's real ext
233
+ // exactly - the parser alone can't check this (it doesn't know the source
234
+ // format until this meta lookup).
235
+ const requestedFormat = parsed.directives.identity ? undefined : parsed.directives.format;
236
+ if (requestedFormat === undefined && parsed.ext !== meta.ext) {
237
+ return { ok: false, status: 400, error: 'Extension does not match the source format' };
238
+ }
239
+ const original = await assetStore.readOriginal(parsed.hash32);
240
+ if (!original) {
241
+ return { ok: false, status: 404, error: 'Not found' };
242
+ }
243
+ const transformed = await applyTransform({ data: original.data, ext: original.ext }, parsed.directives);
244
+ if (!transformed.ok) {
245
+ return { ok: false, status: 502, error: transformed.error };
246
+ }
247
+ const canonicalKey = `${ASSET_PREFIXES.transform}/${formatDirectives(parsed.directives)}/${parsed.hash32}/${parsed.slug}.${parsed.ext}`;
248
+ await assetStore.putPublicObject({
249
+ key: canonicalKey,
250
+ data: transformed.data,
251
+ contentType: transformed.contentType,
252
+ cacheControl: TRANSFORM_CACHE_CONTROL,
253
+ });
254
+ return {
255
+ kind: 'binary',
256
+ status: 200,
257
+ body: transformed.data,
258
+ headers: { contentType: transformed.contentType, cacheControl: TRANSFORM_CACHE_CONTROL },
259
+ };
260
+ }
261
+ /**
262
+ * Serve a public asset object (sanitized svg/pdf finalize wrote, or a
263
+ * previously-computed transform output) for dev-mode `/assets/*` rewrites.
264
+ * Hand-built (not `defineEndpoint`) rather than registered in
265
+ * `ASSET_ROUTES`/the client generator: this route returns raw bytes
266
+ * (`CanopyBinaryResponse`), not a JSON envelope, so a generated client
267
+ * method that calls `response.json()` would be actively wrong. Consumers hit
268
+ * this route directly (an `<img>`/`<a>` src, or a framework rewrite), never
269
+ * through `client.ts`.
270
+ *
271
+ * Transform outputs (`assets/t/...`) are cache-checked exactly like any
272
+ * other public object first - only a MISS under the `assets/t/` prefix falls
273
+ * through to `serveLazyTransform`, which computes and caches the bytes. This
274
+ * mirrors the prod design (CloudFront origin-group -> S3 -> Lambda on miss).
275
+ */
276
+ const rawAssetHandler = async (ctx, _req, params) => {
277
+ if (!ctx.assetStore)
278
+ return { ok: false, status: 501, error: 'Asset store not configured' };
279
+ const key = params.key ?? '';
280
+ const publicPrefix = `${ASSET_PREFIXES.public}/`;
281
+ const transformPrefix = `${ASSET_PREFIXES.transform}/`;
282
+ // Defense-in-depth: the local store re-guards path traversal on its own key
283
+ // resolution, but reject obviously-wrong keys before ever touching the
284
+ // store, and never distinguish "malformed key" from "not found" in the
285
+ // response (no oracle for probing).
286
+ if (!key.startsWith(publicPrefix) || key.includes('..')) {
287
+ return { ok: false, status: 404, error: 'Not found' };
288
+ }
289
+ const object = await ctx.assetStore.readPublicObject(key);
290
+ if (object) {
291
+ return {
292
+ kind: 'binary',
293
+ status: 200,
294
+ body: object.data,
295
+ headers: {
296
+ contentType: object.contentType,
297
+ contentDisposition: object.contentDisposition,
298
+ cacheControl: object.cacheControl,
299
+ },
300
+ };
301
+ }
302
+ if (!key.startsWith(transformPrefix)) {
303
+ return { ok: false, status: 404, error: 'Not found' };
304
+ }
305
+ return serveLazyTransform(ctx.assetStore, key);
306
+ };
58
307
  // ============================================================================
59
308
  // Route Definitions with defineEndpoint
60
309
  // ============================================================================
61
310
  /**
62
- * List all assets
311
+ * POST /assets/presign
312
+ */
313
+ const presignAsset = defineEndpoint({
314
+ namespace: 'assets',
315
+ name: 'presign',
316
+ method: 'POST',
317
+ path: '/assets/presign',
318
+ body: presignAssetBodySchema,
319
+ bodyType: 'PresignAssetBody',
320
+ responseType: 'PresignAssetResponse',
321
+ response: {},
322
+ defaultMockData: {
323
+ upload: { mode: 'proxied', stagingKey: 'asset-staging/mock', maxBytes: 52428800 },
324
+ },
325
+ handler: presignAssetHandler,
326
+ });
327
+ /**
328
+ * POST /assets/finalize
329
+ */
330
+ const finalizeAsset = defineEndpoint({
331
+ namespace: 'assets',
332
+ name: 'finalize',
333
+ method: 'POST',
334
+ path: '/assets/finalize',
335
+ body: finalizeAssetBodySchema,
336
+ bodyType: 'FinalizeAssetBody',
337
+ responseType: 'FinalizeAssetResponse',
338
+ response: {},
339
+ defaultMockData: { asset: MOCK_ASSET_RECORD },
340
+ handler: finalizeAssetHandler,
341
+ });
342
+ /**
343
+ * POST /assets/upload (proxied, multipart/form-data - dev/local-store only)
344
+ */
345
+ const uploadProxied = defineEndpoint({
346
+ namespace: 'assets',
347
+ name: 'uploadProxied',
348
+ method: 'POST',
349
+ path: '/assets/upload',
350
+ bodyFormat: 'multipart',
351
+ responseType: 'FinalizeAssetResponse',
352
+ response: {},
353
+ defaultMockData: { asset: MOCK_ASSET_RECORD },
354
+ handler: uploadProxiedHandler,
355
+ });
356
+ /**
63
357
  * GET /assets
64
358
  */
65
359
  const listAssets = defineEndpoint({
@@ -74,25 +368,7 @@ const listAssets = defineEndpoint({
74
368
  handler: listAssetsHandler,
75
369
  });
76
370
  /**
77
- * Upload an asset
78
- * POST /assets
79
- */
80
- const uploadAsset = defineEndpoint({
81
- namespace: 'assets',
82
- name: 'upload',
83
- method: 'POST',
84
- path: '/assets',
85
- body: uploadAssetBodySchema,
86
- bodyType: 'UploadAssetBody',
87
- responseType: 'AssetUploadResponse',
88
- response: {},
89
- defaultMockData: { asset: { key: '', url: '' } },
90
- guards: ['privileged'],
91
- handler: uploadAssetHandler,
92
- });
93
- /**
94
- * Delete an asset
95
- * DELETE /assets?key=...
371
+ * DELETE /assets?key={hash32}
96
372
  */
97
373
  const deleteAsset = defineEndpoint({
98
374
  namespace: 'assets',
@@ -107,11 +383,22 @@ const deleteAsset = defineEndpoint({
107
383
  handler: deleteAssetHandler,
108
384
  });
109
385
  /**
110
- * Exported routes for router registration
386
+ * Exported routes for router registration and client codegen.
111
387
  */
112
388
  export const ASSET_ROUTES = {
389
+ presign: presignAsset,
390
+ finalize: finalizeAsset,
391
+ uploadProxied,
113
392
  list: listAssets,
114
- upload: uploadAsset,
115
393
  delete: deleteAsset,
116
394
  };
395
+ /**
396
+ * GET /assets/raw/{key...} - see `rawAssetHandler` above for why this is
397
+ * registered separately from `ASSET_ROUTES` instead of through defineEndpoint.
398
+ */
399
+ export const assetRawRoute = {
400
+ method: 'GET',
401
+ pattern: ['assets', 'raw', '...key'],
402
+ handler: rawAssetHandler,
403
+ };
117
404
  //# sourceMappingURL=assets.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../src/api/assets.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../src/api/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIhD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAA;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACrF,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAKpD,SAAS,aAAa,CAAC,IAAe;IACpC,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAA;AACzC,CAAC;AAED,MAAM,iBAAiB,GAAgB;IACrC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;IACtB,QAAQ,EAAE,YAAY;IACtB,IAAI,EAAE,QAAQ;IACd,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;IACX,IAAI,EAAE,QAAQ;IACd,UAAU,EAAE,0BAA0B;IACtC,GAAG,EAAE,4DAA4D;CAClE,CAAA;AAcD,+EAA+E;AAC/E,6BAA6B;AAC7B,+EAA+E;AAE/E,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;AAEjD,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,QAAQ,EAAE,cAAc;IACxB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CAC7C,CAAC,CAAA;AAGF,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7B,QAAQ,EAAE,cAAc;CACzB,CAAC,CAAA;AAGF;;;;;GAKG;AACH,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;CAC9D,CAAC,CAAA;AAGF,qGAAqG;AACrG,MAAM,YAAY,GAAG,CAAC;KACnB,MAAM,EAAE;KACR,KAAK,CAAC,gBAAgB,EAAE,iDAAiD,CAAC,CAAA;AAE7E,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAA;AAG/D,+EAA+E;AAC/E,WAAW;AACX,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,KAAK,EAC/B,GAAe,EACf,IAAgB,EAChB,IAAsB,EACS,EAAE;IACjC,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAA;IAE3F,IAAI,CAAC,4BAA4B,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACxD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,6BAA6B,IAAI,CAAC,WAAW,EAAE,EAAE,CAAA;IAC3F,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC;QAC9C,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,IAAI,EAAE,IAAI,CAAC,IAAI;KAChB,CAAC,CAAA;IAEF,2EAA2E;IAC3E,0EAA0E;IAC1E,uEAAuE;IACvE,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC3D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,oBAAoB,MAAM,CAAC,QAAQ,aAAa,EAAE,CAAA;IAC5F,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAA;AAC5D,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,oBAAoB,GAAG,KAAK,EAChC,GAAe,EACf,GAAe,EACf,IAAuB,EACS,EAAE;IAClC,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAA;IAE3F,MAAM,MAAM,GAAG,MAAM,oBAAoB,CACvC,GAAG,CAAC,UAAU,EACd,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,QAAQ,EACb,GAAG,CAAC,IAAI,CAAC,MAAM,CAChB,CAAA;IAED,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAA;IAClE,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,CAAA;AAC/E,CAAC,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,oBAAoB,GAAG,KAAK,EAChC,GAAe,EACf,GAAe,EACiB,EAAE;IAClC,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAA;IAE3F,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,YAAY,EAAE,CAAC;QAC7C,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,KAAK,EAAE,4EAA4E;SACpF,CAAA;IACH,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,EAAE,CAAC;QAC9B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,KAAK,EAAE,kEAAkE;SAC1E,CAAA;IACH,CAAC;IAED,wEAAwE;IACxE,wEAAwE;IACxE,2EAA2E;IAC3E,uEAAuE;IACvE,2EAA2E;IAC3E,2EAA2E;IAC3E,yEAAyE;IACzE,uEAAuE;IACvE,sEAAsE;IACtE,MAAM,mBAAmB,GAAG,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAA;IACnE,IAAI,mBAAmB,KAAK,IAAI,EAAE,CAAC;QACjC,MAAM,aAAa,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAA;QACjD,IAAI,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACnC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC;gBACpD,QAAQ,EAAE,QAAQ;gBAClB,WAAW,EAAE,0BAA0B;aACxC,CAAC,CAAA;YACF,IAAI,aAAa,GAAG,QAAQ,EAAE,CAAC;gBAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,oBAAoB,QAAQ,aAAa,EAAE,CAAA;YACrF,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,QAAkB,CAAA;IACtB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAA;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,0CAA0C,EAAE,CAAA;IACtF,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACrC,IAAI,CAAC,CAAC,QAAQ,YAAY,IAAI,CAAC,EAAE,CAAC;QAChC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,2BAA2B,EAAE,CAAA;IACvE,CAAC;IAED,MAAM,gBAAgB,GAAG,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IACjD,MAAM,QAAQ,GACZ,OAAO,gBAAgB,KAAK,QAAQ,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC;QACjE,CAAC,CAAC,gBAAgB;QAClB,CAAC,CAAC,QAAQ,YAAY,IAAI;YACxB,CAAC,CAAC,QAAQ,CAAC,IAAI;YACf,CAAC,CAAC,QAAQ,CAAA;IAEhB,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;IAEzD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC;QAC9C,QAAQ;QACR,WAAW,EAAE,QAAQ,CAAC,IAAI,IAAI,0BAA0B;QACxD,IAAI,EAAE,IAAI,CAAC,UAAU;KACtB,CAAC,CAAA;IACF,wEAAwE;IACxE,sEAAsE;IACtE,yEAAyE;IACzE,kEAAkE;IAClE,IAAI,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,oBAAoB,MAAM,CAAC,QAAQ,aAAa,EAAE,CAAA;IAC5F,CAAC;IAED,MAAM,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,SAAS,CAAC,CAAA;IAEtF,MAAM,MAAM,GAAG,MAAM,oBAAoB,CACvC,GAAG,CAAC,UAAU,EACd,MAAM,CAAC,UAAU,EACjB,QAAQ,EACR,GAAG,CAAC,IAAI,CAAC,MAAM,CAChB,CAAA;IACD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAA;IAClE,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,CAAA;AAC/E,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,iBAAiB,GAAG,KAAK,EAC7B,GAAe,EACf,IAAgB,EAChB,MAAwB,EACK,EAAE;IAC/B,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAA;IAE3F,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC;QAC1D,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC,CAAA;IACF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,UAAU,EAAE,EAAE,CAAA;AAC1F,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,kBAAkB,GAAG,KAAK,EAC9B,GAA0B,EAC1B,GAAe,EACf,IAAgB,EAChB,MAAyB,EACK,EAAE;IAChC,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAA;IAE3F,MAAM,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC3C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAA;AAC3D,CAAC,CAAA;AAED,uJAAuJ;AACvJ,MAAM,uBAAuB,GAAG,qCAAqC,CAAA;AAErE;;;;;;;;;;;GAWG;AACH,KAAK,UAAU,kBAAkB,CAC/B,UAAsB,EACtB,GAAW;IAEX,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC3D,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IAClD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAA;IACxD,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACpD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,CAAA;IACvD,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC3B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,oDAAoD,EAAE,CAAA;IAChG,CAAC;IAED,0EAA0E;IAC1E,uEAAuE;IACvE,0EAA0E;IAC1E,kCAAkC;IAClC,MAAM,eAAe,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAA;IACzF,IAAI,eAAe,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,4CAA4C,EAAE,CAAA;IACxF,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAC7D,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,CAAA;IACvD,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,cAAc,CACtC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,EAC1C,MAAM,CAAC,UAAU,CAClB,CAAA;IACD,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC;QACpB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE,CAAA;IAC7D,CAAC;IAED,MAAM,YAAY,GAAG,GAAG,cAAc,CAAC,SAAS,IAAI,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,GAAG,EAAE,CAAA;IACvI,MAAM,UAAU,CAAC,eAAe,CAAC;QAC/B,GAAG,EAAE,YAAY;QACjB,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,WAAW,EAAE,WAAW,CAAC,WAAW;QACpC,YAAY,EAAE,uBAAuB;KACtC,CAAC,CAAA;IAEF,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,GAAG;QACX,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,OAAO,EAAE,EAAE,WAAW,EAAE,WAAW,CAAC,WAAW,EAAE,YAAY,EAAE,uBAAuB,EAAE;KACzF,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,eAAe,GAAG,KAAK,EAC3B,GAAe,EACf,IAAgB,EAChB,MAA8B,EACsB,EAAE;IACtD,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAA;IAE3F,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAA;IAC5B,MAAM,YAAY,GAAG,GAAG,cAAc,CAAC,MAAM,GAAG,CAAA;IAChD,MAAM,eAAe,GAAG,GAAG,cAAc,CAAC,SAAS,GAAG,CAAA;IACtD,4EAA4E;IAC5E,uEAAuE;IACvE,uEAAuE;IACvE,oCAAoC;IACpC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,CAAA;IACvD,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAA;IACzD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,OAAO,EAAE;gBACP,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;gBAC7C,YAAY,EAAE,MAAM,CAAC,YAAY;aAClC;SACF,CAAA;IACH,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACrC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,CAAA;IACvD,CAAC;IAED,OAAO,kBAAkB,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAA;AAChD,CAAC,CAAA;AAED,+EAA+E;AAC/E,wCAAwC;AACxC,+EAA+E;AAE/E;;GAEG;AACH,MAAM,YAAY,GAAG,cAAc,CAAC;IAClC,SAAS,EAAE,QAAQ;IACnB,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,iBAAiB;IACvB,IAAI,EAAE,sBAAsB;IAC5B,QAAQ,EAAE,kBAAkB;IAC5B,YAAY,EAAE,sBAAsB;IACpC,QAAQ,EAAE,EAA0B;IACpC,eAAe,EAAE;QACf,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,oBAAoB,EAAE,QAAQ,EAAE,QAAQ,EAAE;KAClF;IACD,OAAO,EAAE,mBAAmB;CAC7B,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,aAAa,GAAG,cAAc,CAAC;IACnC,SAAS,EAAE,QAAQ;IACnB,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,kBAAkB;IACxB,IAAI,EAAE,uBAAuB;IAC7B,QAAQ,EAAE,mBAAmB;IAC7B,YAAY,EAAE,uBAAuB;IACrC,QAAQ,EAAE,EAA2B;IACrC,eAAe,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE;IAC7C,OAAO,EAAE,oBAAoB;CAC9B,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,aAAa,GAAG,cAAc,CAAC;IACnC,SAAS,EAAE,QAAQ;IACnB,IAAI,EAAE,eAAe;IACrB,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,gBAAgB;IACtB,UAAU,EAAE,WAAW;IACvB,YAAY,EAAE,uBAAuB;IACrC,QAAQ,EAAE,EAA2B;IACrC,eAAe,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE;IAC7C,OAAO,EAAE,oBAAoB;CAC9B,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,UAAU,GAAG,cAAc,CAAC;IAChC,SAAS,EAAE,QAAQ;IACnB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,KAAK;IACb,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,sBAAsB;IAC9B,YAAY,EAAE,oBAAoB;IAClC,QAAQ,EAAE,EAAwB;IAClC,eAAe,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;IAC/B,OAAO,EAAE,iBAAiB;CAC3B,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,WAAW,GAAG,cAAc,CAAC;IACjC,SAAS,EAAE,QAAQ;IACnB,IAAI,EAAE,QAAQ;IACd,MAAM,EAAE,QAAQ;IAChB,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,uBAAuB;IAC/B,YAAY,EAAE,qBAAqB;IACnC,QAAQ,EAAE,EAAyB;IACnC,eAAe,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;IAClC,MAAM,EAAE,CAAC,OAAO,CAAU;IAC1B,OAAO,EAAE,kBAAkB;CAC5B,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,OAAO,EAAE,YAAY;IACrB,QAAQ,EAAE,aAAa;IACvB,aAAa;IACb,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,WAAW;CACX,CAAA;AAEV;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAoB;IAC5C,MAAM,EAAE,KAAK;IACb,OAAO,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC;IACpC,OAAO,EAAE,eAAe;CACzB,CAAA"}
@@ -19,6 +19,12 @@ export type BranchListResponse = ApiResponse<{
19
19
  /** Response type for branch deletion */
20
20
  export type BranchDeleteResponse = ApiResponse<{
21
21
  deleted: boolean;
22
+ /**
23
+ * Set when branch.json was removed (so the branch is logically gone from
24
+ * the registry) but the full directory removal failed -- an orphan clone
25
+ * persists on disk, invisible to the API, until manually cleaned up.
26
+ */
27
+ cleanupWarning?: string;
22
28
  }>;
23
29
  declare const createBranchBodySchema: z.ZodObject<{
24
30
  branch: z.ZodType<import("../paths").BranchName, z.ZodTypeDef, import("../paths").BranchName>;
@@ -1 +1 @@
1
- {"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/api/branch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/api/branch.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { z } from 'zod';
4
4
  import { BranchWorkspaceManager } from '../branch-workspace.js';
5
5
  import { getBranchMetadataFileManager } from '../branch-metadata.js';
6
+ import { withOccFileLock } from '../utils/occ-json-write.js';
6
7
  import { defineEndpoint } from './route-builder.js';
7
8
  import { createDebugLogger } from '../utils/debug.js';
8
9
  import { clientOperatingStrategy } from '../operating-mode/index.js';
@@ -210,24 +211,56 @@ export const deleteBranchHandler = async (ctx, req, params) => {
210
211
  error: 'Cannot delete branch with open pull request',
211
212
  };
212
213
  }
213
- // Delete branch metadata file so it disappears from registry scans
214
+ // Delete branch metadata file so it disappears from registry scans.
215
+ // Hold the same server-enforced lockfile branch-metadata saves hold
216
+ // (see utils/occ-json-write.ts): an unguarded unlink racing a concurrent
217
+ // save() would let the save's create path resurrect a phantom branch.json
218
+ // inside a deleted branch, which the registry's next scan would list as a
219
+ // live branch with no clone. The directory removal happens inside the
220
+ // same hold so a racing save cannot slip between unlink and rm either.
214
221
  const metadataFile = path.join(branchContext.branchRoot, '.canopy-meta', 'branch.json');
222
+ let cleanupWarning;
215
223
  try {
216
- await fs.unlink(metadataFile);
224
+ await withOccFileLock(metadataFile, async () => {
225
+ try {
226
+ await fs.unlink(metadataFile);
227
+ }
228
+ catch (err) {
229
+ if (!isNotFoundError(err)) {
230
+ console.error(`CanopyCMS: Failed to delete branch metadata for ${branchName}:`, getErrorMessage(err));
231
+ }
232
+ }
233
+ // In multi-branch modes, also delete the entire branch directory.
234
+ // Retry transient EFS/NFS errors (ENOTEMPTY from a concurrent writer,
235
+ // EBUSY) a few times before giving up -- rm's failure must never be
236
+ // swallowed: metadata is gone either way (the branch is logically
237
+ // deleted and will no longer appear in listings), but silently
238
+ // succeeding here would leave a full orphan clone on disk with
239
+ // nothing in the API surfacing its existence.
240
+ if (branchContext.branchRoot !== branchContext.baseRoot) {
241
+ try {
242
+ await fs.rm(branchContext.branchRoot, {
243
+ recursive: true,
244
+ force: true,
245
+ maxRetries: 3,
246
+ retryDelay: 100,
247
+ });
248
+ }
249
+ catch (err) {
250
+ cleanupWarning = `Failed to fully remove branch directory: ${getErrorMessage(err)}`;
251
+ console.error(`CanopyCMS: Failed to delete branch directory for ${branchName}:`, getErrorMessage(err));
252
+ }
253
+ }
254
+ });
217
255
  }
218
256
  catch (err) {
219
- if (!isNotFoundError(err)) {
220
- console.error(`CanopyCMS: Failed to delete branch metadata for ${branchName}:`, getErrorMessage(err));
221
- }
222
- }
223
- // In multi-branch modes, also delete the entire branch directory
224
- if (branchContext.branchRoot !== branchContext.baseRoot) {
225
- try {
226
- await fs.rm(branchContext.branchRoot, { recursive: true, force: true });
227
- }
228
- catch (err) {
229
- console.error(`CanopyCMS: Failed to delete branch directory for ${branchName}:`, getErrorMessage(err));
230
- }
257
+ // Lock acquisition failed (e.g. contention past the retry budget) —
258
+ // surface as an error rather than silently skipping the delete.
259
+ return {
260
+ ok: false,
261
+ status: 409,
262
+ error: `Branch is busy, try again: ${getErrorMessage(err)}`,
263
+ };
231
264
  }
232
265
  // Invalidate registry cache so next list() will regenerate without this branch
233
266
  if (!ctx.services.registry) {
@@ -238,7 +271,11 @@ export const deleteBranchHandler = async (ctx, req, params) => {
238
271
  };
239
272
  }
240
273
  await ctx.services.registry.invalidate();
241
- return { ok: true, status: 200, data: { deleted: true } };
274
+ return {
275
+ ok: true,
276
+ status: 200,
277
+ data: { deleted: true, ...(cleanupWarning && { cleanupWarning }) },
278
+ };
242
279
  };
243
280
  /**
244
281
  * Check if a user can modify branch access.
@@ -271,10 +308,17 @@ export const updateBranchAccessHandler = async (ctx, req, params, body) => {
271
308
  error: 'You do not have permission to modify this branch',
272
309
  };
273
310
  }
274
- // Build new access control
275
- const newAccess = {
276
- ...branchContext.branch.access,
277
- };
311
+ // Build the access DELTA from only the keys the caller actually supplied —
312
+ // never spread branchContext.branch.access (a snapshot resolved before
313
+ // this handler acquired anything) wholesale. save()'s field-level merge
314
+ // (branch-metadata.ts) takes the incoming access object's keys over the
315
+ // freshly-reloaded on-disk ones, so a full stale spread here would
316
+ // silently revert any key a concurrent request changed via the OTHER key
317
+ // in the gap between this handler's getBranchContext() and its save()
318
+ // call. An omitted key must be ABSENT from this object (not merely
319
+ // undefined-valued) so save()'s spread-merge leaves the on-disk value
320
+ // untouched; a supplied `[]` still comes through and clears the field.
321
+ const newAccess = {};
278
322
  if (body.allowedUsers !== undefined) {
279
323
  newAccess.allowedUsers = body.allowedUsers;
280
324
  }