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
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Finalize pipeline: turns raw uploaded bytes into a validated AssetMeta
3
+ * (+ optional public object for svg/pdf), or a typed rejection. Pure
4
+ * transform - no store I/O here (see finalize.ts for the store orchestration
5
+ * that dedups, writes, and calls this).
6
+ *
7
+ * Server-only - never import this module from client/editor code. It pulls
8
+ * in `file-type`, `image-size`, and the SVG sanitizer, none of which belong
9
+ * in a browser bundle.
10
+ */
11
+ import type { AssetMeta } from './types';
12
+ /** Client-declared content types accepted at presign time (UX only - never trusted at finalize). */
13
+ export declare const ALLOWED_UPLOAD_CONTENT_TYPES: Set<string>;
14
+ export interface FinalizeInput {
15
+ data: Uint8Array;
16
+ filename: string;
17
+ uploadedBy?: string;
18
+ }
19
+ export interface FinalizePublicObject {
20
+ key: string;
21
+ data: Uint8Array;
22
+ contentType: string;
23
+ contentDisposition: string;
24
+ cacheControl: string;
25
+ }
26
+ export interface FinalizeSuccess {
27
+ ok: true;
28
+ meta: AssetMeta;
29
+ /** Canonical bytes to persist as the original - sanitized bytes for svg, untouched otherwise. */
30
+ data: Uint8Array;
31
+ publicObject?: FinalizePublicObject;
32
+ }
33
+ export interface FinalizeRejection {
34
+ ok: false;
35
+ status: 413 | 415;
36
+ error: string;
37
+ }
38
+ export type FinalizeResult = FinalizeSuccess | FinalizeRejection;
39
+ /**
40
+ * Run the finalize pipeline over freshly-uploaded bytes: sniff the real file
41
+ * type (never trust the client-declared contentType beyond presign-time UX),
42
+ * sanitize if svg, hash, extract dimensions, and build the AssetMeta + any
43
+ * public object (svg/pdf only) the caller should persist.
44
+ *
45
+ * Order matters: for SVG, sanitization happens BEFORE hashing - the sanitized
46
+ * bytes are what get hashed and stored, so a later re-sanitization (e.g. a
47
+ * stricter sanitizer version) cannot desync a stored hash from its content.
48
+ */
49
+ export declare function runFinalizePipeline(input: FinalizeInput): Promise<FinalizeResult>;
50
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/assets/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAMxC,oGAAoG;AACpG,eAAO,MAAM,4BAA4B,aAIvC,CAAA;AAkBF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,UAAU,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,IAAI,CAAA;IACR,IAAI,EAAE,SAAS,CAAA;IACf,iGAAiG;IACjG,IAAI,EAAE,UAAU,CAAA;IAChB,YAAY,CAAC,EAAE,oBAAoB,CAAA;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,KAAK,CAAA;IACT,MAAM,EAAE,GAAG,GAAG,GAAG,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG,iBAAiB,CAAA;AAoEhE;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CA8FvF"}
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Finalize pipeline: turns raw uploaded bytes into a validated AssetMeta
3
+ * (+ optional public object for svg/pdf), or a typed rejection. Pure
4
+ * transform - no store I/O here (see finalize.ts for the store orchestration
5
+ * that dedups, writes, and calls this).
6
+ *
7
+ * Server-only - never import this module from client/editor code. It pulls
8
+ * in `file-type`, `image-size`, and the SVG sanitizer, none of which belong
9
+ * in a browser bundle.
10
+ */
11
+ import { fileTypeFromBuffer } from 'file-type';
12
+ import { imageSize } from 'image-size';
13
+ import { create as createContentDisposition } from 'content-disposition';
14
+ import { hashBytes, publicKey, slugifyFilename } from './keys.js';
15
+ import { sanitizeSvg } from './svg-sanitizer.js';
16
+ import { MAX_INPUT_PIXELS } from './transform-directives.js';
17
+ const RASTER_MIME_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp', 'image/gif']);
18
+ const PDF_MIME_TYPE = 'application/pdf';
19
+ const SVG_MIME_TYPE = 'image/svg+xml';
20
+ /** Client-declared content types accepted at presign time (UX only - never trusted at finalize). */
21
+ export const ALLOWED_UPLOAD_CONTENT_TYPES = new Set([
22
+ ...RASTER_MIME_TYPES,
23
+ PDF_MIME_TYPE,
24
+ SVG_MIME_TYPE,
25
+ ]);
26
+ const PDF_MAX_BYTES = 25 * 1024 * 1024;
27
+ /**
28
+ * Defensive raster byte cap, independent of whatever the configured
29
+ * AssetStore's own upload-size limit happens to be (that limit lives at the
30
+ * store/API boundary, not here) - mirrors PDF_MAX_BYTES's role as a
31
+ * pipeline-level backstop rather than the sole enforcement point.
32
+ */
33
+ const RASTER_MAX_BYTES = 50 * 1024 * 1024;
34
+ /** EXIF orientation values 5-8 are 90-degree rotations, which swap width/height. */
35
+ const isRotated90 = (orientation) => orientation !== undefined && orientation >= 5 && orientation <= 8;
36
+ const PUBLIC_CACHE_CONTROL = 'public, max-age=31536000, immutable';
37
+ /** U+FEFF (byte-order mark). Built from a char code, not a literal, so this file's source stays pure ASCII. */
38
+ const BOM = String.fromCharCode(0xfeff);
39
+ /**
40
+ * Strip anything that may legally precede the root `<svg>` element: a BOM,
41
+ * whitespace, XML comments, the `<?xml ... ?>` prolog, and a `<!DOCTYPE ...>`
42
+ * declaration - in any order, any number of times (some tools emit more than
43
+ * one comment). Loops until a pass makes no further change.
44
+ */
45
+ function stripSvgPreamble(text) {
46
+ let stripped = text.startsWith(BOM) ? text.slice(BOM.length) : text;
47
+ let previous;
48
+ do {
49
+ previous = stripped;
50
+ stripped = stripped.replace(/^\s+/, '');
51
+ stripped = stripped.replace(/^<!--[\s\S]*?-->/, '');
52
+ stripped = stripped.replace(/^<\?xml[\s\S]*?\?>/i, '');
53
+ stripped = stripped.replace(/^<!DOCTYPE[\s\S]*?>/i, '');
54
+ } while (stripped !== previous);
55
+ return stripped;
56
+ }
57
+ /**
58
+ * Detect whether `data` is an SVG document. `file-type` cannot do this (SVG is
59
+ * a text format, not magic-byte-detectable) - and, discovered empirically,
60
+ * `file-type` does NOT simply return `undefined` for all SVGs either: an SVG
61
+ * with an `<?xml ?>` prolog gets positively sniffed as generic `application/xml`.
62
+ * So the pipeline's real branch condition is "not a recognized raster/pdf mime"
63
+ * (which covers both file-type returning undefined AND returning some other
64
+ * non-allowlisted guess like application/xml), and within that branch this
65
+ * function does the actual SVG determination: decode as UTF-8 (reject on
66
+ * failure), strip anything that may precede the root element, and require the
67
+ * first element to be `<svg` (case-insensitive).
68
+ *
69
+ * Returns the decoded text on success (so the caller doesn't have to decode
70
+ * twice), or `null` if this is not a decodable SVG document.
71
+ */
72
+ function sniffSvg(data) {
73
+ let text;
74
+ try {
75
+ text = new TextDecoder('utf-8', { fatal: true }).decode(data);
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ const stripped = stripSvgPreamble(text);
81
+ return /^<svg[\s>/]/i.test(stripped) ? text : null;
82
+ }
83
+ /**
84
+ * Extract width/height (rasters and svg only - PDFs have no pixel dims).
85
+ * Non-fatal on failure: a corrupt-but-magic-byte-valid file still finalizes,
86
+ * just without dimensions (AssetMeta.width/height are optional).
87
+ */
88
+ function computeDimensions(data) {
89
+ try {
90
+ const result = imageSize(data);
91
+ let { width, height } = result;
92
+ if (isRotated90(result.orientation)) {
93
+ ;
94
+ [width, height] = [height, width];
95
+ }
96
+ return { width, height };
97
+ }
98
+ catch {
99
+ return {};
100
+ }
101
+ }
102
+ /**
103
+ * Run the finalize pipeline over freshly-uploaded bytes: sniff the real file
104
+ * type (never trust the client-declared contentType beyond presign-time UX),
105
+ * sanitize if svg, hash, extract dimensions, and build the AssetMeta + any
106
+ * public object (svg/pdf only) the caller should persist.
107
+ *
108
+ * Order matters: for SVG, sanitization happens BEFORE hashing - the sanitized
109
+ * bytes are what get hashed and stored, so a later re-sanitization (e.g. a
110
+ * stricter sanitizer version) cannot desync a stored hash from its content.
111
+ */
112
+ export async function runFinalizePipeline(input) {
113
+ const sniffed = await fileTypeFromBuffer(input.data);
114
+ let kind;
115
+ let mime;
116
+ let ext;
117
+ let canonicalData = input.data;
118
+ if (sniffed && RASTER_MIME_TYPES.has(sniffed.mime)) {
119
+ if (input.data.byteLength > RASTER_MAX_BYTES) {
120
+ return {
121
+ ok: false,
122
+ status: 413,
123
+ error: `Raster image exceeds the ${RASTER_MAX_BYTES}-byte limit`,
124
+ };
125
+ }
126
+ kind = 'raster';
127
+ mime = sniffed.mime;
128
+ ext = sniffed.ext;
129
+ }
130
+ else if (sniffed && sniffed.mime === PDF_MIME_TYPE) {
131
+ if (input.data.byteLength > PDF_MAX_BYTES) {
132
+ return { ok: false, status: 413, error: `PDF exceeds the ${PDF_MAX_BYTES}-byte limit` };
133
+ }
134
+ kind = 'pdf';
135
+ mime = PDF_MIME_TYPE;
136
+ ext = sniffed.ext;
137
+ }
138
+ else {
139
+ // Not a recognized raster/pdf - could be a bare/prolog'd SVG, or genuine junk.
140
+ const svgText = sniffSvg(input.data);
141
+ if (!svgText) {
142
+ return { ok: false, status: 415, error: 'Unsupported or unrecognized file type' };
143
+ }
144
+ kind = 'svg';
145
+ mime = SVG_MIME_TYPE;
146
+ ext = 'svg';
147
+ canonicalData = new TextEncoder().encode(sanitizeSvg(svgText));
148
+ }
149
+ const hash32 = hashBytes(canonicalData);
150
+ const { slug } = slugifyFilename(input.filename);
151
+ const dims = kind === 'pdf' ? {} : computeDimensions(canonicalData);
152
+ // Decompression-bomb defense: a raster's on-disk size says nothing about
153
+ // its decoded pixel count (a solid-color 30000x30000 PNG compresses to a
154
+ // few KB) - reject on width*height before this asset is ever finalized, so
155
+ // the transform engine (transform.ts's own `limitInputPixels`) is never the
156
+ // only thing standing between an attacker and a huge decode. SVG is exempt:
157
+ // `computeDimensions` reads its declared viewBox/width/height attributes,
158
+ // not a decoded raster, so there is no analogous decode-time memory cost.
159
+ if (kind === 'raster' && dims.width !== undefined && dims.height !== undefined) {
160
+ const pixelCount = dims.width * dims.height;
161
+ if (pixelCount > MAX_INPUT_PIXELS) {
162
+ return {
163
+ ok: false,
164
+ status: 413,
165
+ error: `Image dimensions ${dims.width}x${dims.height} (${pixelCount} pixels) exceed the ${MAX_INPUT_PIXELS}-pixel limit`,
166
+ };
167
+ }
168
+ }
169
+ const meta = {
170
+ hash32,
171
+ filename: input.filename,
172
+ slug,
173
+ ext,
174
+ mime,
175
+ size: canonicalData.byteLength,
176
+ ...(dims.width !== undefined && { width: dims.width }),
177
+ ...(dims.height !== undefined && { height: dims.height }),
178
+ kind,
179
+ ...(input.uploadedBy && { uploadedBy: input.uploadedBy }),
180
+ uploadedAt: new Date().toISOString(),
181
+ };
182
+ let publicObject;
183
+ if (kind === 'svg' || kind === 'pdf') {
184
+ // svg: rendered inline by the browser. pdf: downloaded under its real
185
+ // original filename (never the slug) via Content-Disposition, built with
186
+ // the content-disposition package (RFC 5987) rather than string
187
+ // interpolation, so header-injection characters in the filename can never
188
+ // reach a raw HTTP header value.
189
+ const contentDisposition = createContentDisposition(input.filename, {
190
+ type: kind === 'svg' ? 'inline' : 'attachment',
191
+ });
192
+ publicObject = {
193
+ key: publicKey(hash32, slug, ext),
194
+ data: canonicalData,
195
+ contentType: mime,
196
+ contentDisposition,
197
+ cacheControl: PUBLIC_CACHE_CONTROL,
198
+ };
199
+ }
200
+ return { ok: true, meta, data: canonicalData, publicObject };
201
+ }
202
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/assets/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Filesystem-backed AssetStore for dev and tests. Mirrors the same
3
+ * bucket-prefix layout as S3AssetStore (see keys.ts) so content behaves
4
+ * identically across adapters.
5
+ */
6
+ import type { AssetMeta, AssetStore, BeginUploadInput, PublicObject, StagedUploadTarget } from './types';
7
+ export interface LocalAssetStoreOptions {
8
+ /** Root directory. The five bucket prefixes are created as subdirectories under it. */
9
+ root: string;
10
+ }
11
+ export declare class LocalAssetStore implements AssetStore {
12
+ readonly capabilities: {
13
+ directUpload: boolean;
14
+ };
15
+ private readonly root;
16
+ constructor(options: LocalAssetStoreOptions);
17
+ /**
18
+ * Resolve a store key to an absolute filesystem path, guarding against
19
+ * escape from the root. Keys are internally generated (never taken
20
+ * verbatim from user input), but this guard is kept as defense-in-depth
21
+ * (ported from the v1 LocalAssetStore).
22
+ *
23
+ * A naive `resolved.startsWith(root)` check would let a sibling directory
24
+ * that merely shares the root's name as a prefix (e.g. root `.../assets`,
25
+ * sibling `.../assets-evil`) pass. Comparing against `root + path.sep`
26
+ * closes that gap.
27
+ */
28
+ private resolveKey;
29
+ private headersSidecarPath;
30
+ private writeHeadersSidecar;
31
+ private readHeadersSidecar;
32
+ private readFileOrNull;
33
+ private writeFile;
34
+ private deleteFileIfPresent;
35
+ beginUpload(_input: BeginUploadInput): Promise<StagedUploadTarget>;
36
+ /**
37
+ * Staging methods accept caller-influenced keys (finalize receives the key
38
+ * from the client), so they must never operate outside the staging prefix —
39
+ * otherwise a crafted key turns the staging cleanup into an arbitrary-object
40
+ * delete. The API layer validates too; this is defense-in-depth.
41
+ */
42
+ private assertStagingKey;
43
+ writeStaging(key: string, data: Uint8Array, _contentType?: string): Promise<void>;
44
+ readStaging(key: string): Promise<Uint8Array | null>;
45
+ deleteStaging(key: string): Promise<void>;
46
+ putOriginal(input: {
47
+ hash32: string;
48
+ ext: string;
49
+ data: Uint8Array;
50
+ contentType: string;
51
+ }): Promise<void>;
52
+ readOriginal(hash32: string): Promise<{
53
+ data: Uint8Array;
54
+ ext: string;
55
+ contentType?: string;
56
+ } | null>;
57
+ putPublicObject(input: {
58
+ key: string;
59
+ data: Uint8Array;
60
+ contentType: string;
61
+ contentDisposition?: string;
62
+ cacheControl?: string;
63
+ }): Promise<void>;
64
+ readPublicObject(key: string): Promise<PublicObject | null>;
65
+ /**
66
+ * Atomic-exclusive create: opens with the `wx` flag so two concurrent
67
+ * writers racing on the same hash32 have exactly one winner ('created')
68
+ * and exactly one loser ('already-exists'). The rename-based
69
+ * `atomicWriteFile` helper is deliberately NOT used here — a rename
70
+ * always clobbers the destination, which would let the second writer
71
+ * silently overwrite the first instead of losing cleanly.
72
+ */
73
+ putMetaIfAbsent(hash32: string, meta: AssetMeta): Promise<'created' | 'already-exists'>;
74
+ getMeta(hash32: string): Promise<AssetMeta | null>;
75
+ listMeta(input?: {
76
+ cursor?: string;
77
+ limit?: number;
78
+ }): Promise<{
79
+ items: AssetMeta[];
80
+ nextCursor?: string;
81
+ }>;
82
+ deleteMeta(hash32: string): Promise<void>;
83
+ }
84
+ //# sourceMappingURL=store-local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-local.d.ts","sourceRoot":"","sources":["../../src/assets/store-local.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EACV,SAAS,EACT,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EACnB,MAAM,SAAS,CAAA;AAEhB,MAAM,WAAW,sBAAsB;IACrC,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAA;CACb;AAcD,qBAAa,eAAgB,YAAW,UAAU;IAChD,QAAQ,CAAC,YAAY;;MAA0B;IAC/C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAQ;gBAEjB,OAAO,EAAE,sBAAsB;IAI3C;;;;;;;;;;OAUG;IACH,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,kBAAkB;YAIZ,mBAAmB;YAInB,kBAAkB;YAUlB,cAAc;YAUd,SAAS;YAIT,mBAAmB;IAU3B,WAAW,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAQxE;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAMlB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjF,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAKpD,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzC,WAAW,CAAC,KAAK,EAAE;QACvB,MAAM,EAAE,MAAM,CAAA;QACd,GAAG,EAAE,MAAM,CAAA;QACX,IAAI,EAAE,UAAU,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;KACpB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMX,YAAY,CAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAsBpE,eAAe,CAAC,KAAK,EAAE;QAC3B,GAAG,EAAE,MAAM,CAAA;QACX,IAAI,EAAE,UAAU,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;QACnB,kBAAkB,CAAC,EAAE,MAAM,CAAA;QAC3B,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC;IAUX,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAajE;;;;;;;OAOG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,GAAG,gBAAgB,CAAC;IAYvF,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAWlD,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QACnE,KAAK,EAAE,SAAS,EAAE,CAAA;QAClB,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAC;IAkDI,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAShD"}
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Filesystem-backed AssetStore for dev and tests. Mirrors the same
3
+ * bucket-prefix layout as S3AssetStore (see keys.ts) so content behaves
4
+ * identically across adapters.
5
+ */
6
+ import fs from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ import { randomUUID } from 'node:crypto';
9
+ import { getErrorMessage, isFileExistsError, isNotFoundError } from '../utils/error.js';
10
+ import { atomicWriteFile } from '../utils/atomic-write.js';
11
+ import { ASSET_PREFIXES, metaKey, originalKey, stagingKey } from './keys.js';
12
+ /** Matches S3AssetStore's default; local has no separate configuration knob for it. */
13
+ const DEFAULT_MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
14
+ const DEFAULT_LIST_LIMIT = 24;
15
+ const HEADERS_SIDECAR_SUFFIX = '.headers.json';
16
+ export class LocalAssetStore {
17
+ constructor(options) {
18
+ this.capabilities = { directUpload: false };
19
+ this.root = path.resolve(options.root);
20
+ }
21
+ /**
22
+ * Resolve a store key to an absolute filesystem path, guarding against
23
+ * escape from the root. Keys are internally generated (never taken
24
+ * verbatim from user input), but this guard is kept as defense-in-depth
25
+ * (ported from the v1 LocalAssetStore).
26
+ *
27
+ * A naive `resolved.startsWith(root)` check would let a sibling directory
28
+ * that merely shares the root's name as a prefix (e.g. root `.../assets`,
29
+ * sibling `.../assets-evil`) pass. Comparing against `root + path.sep`
30
+ * closes that gap.
31
+ */
32
+ resolveKey(key) {
33
+ const resolved = path.resolve(this.root, key);
34
+ const rootWithSep = this.root.endsWith(path.sep) ? this.root : this.root + path.sep;
35
+ if (resolved !== this.root && !resolved.startsWith(rootWithSep)) {
36
+ throw new Error(`Path traversal detected for key: ${key}`);
37
+ }
38
+ return resolved;
39
+ }
40
+ headersSidecarPath(filePath) {
41
+ return `${filePath}${HEADERS_SIDECAR_SUFFIX}`;
42
+ }
43
+ async writeHeadersSidecar(filePath, headers) {
44
+ await atomicWriteFile(this.headersSidecarPath(filePath), JSON.stringify(headers));
45
+ }
46
+ async readHeadersSidecar(filePath) {
47
+ try {
48
+ const raw = await fs.readFile(this.headersSidecarPath(filePath), 'utf-8');
49
+ return JSON.parse(raw);
50
+ }
51
+ catch (err) {
52
+ if (isNotFoundError(err))
53
+ return {};
54
+ throw err;
55
+ }
56
+ }
57
+ async readFileOrNull(filePath) {
58
+ try {
59
+ const buf = await fs.readFile(filePath);
60
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
61
+ }
62
+ catch (err) {
63
+ if (isNotFoundError(err))
64
+ return null;
65
+ throw err;
66
+ }
67
+ }
68
+ async writeFile(filePath, data) {
69
+ await atomicWriteFile(filePath, Buffer.from(data));
70
+ }
71
+ async deleteFileIfPresent(filePath) {
72
+ try {
73
+ await fs.unlink(filePath);
74
+ }
75
+ catch (err) {
76
+ if (!isNotFoundError(err))
77
+ throw err;
78
+ }
79
+ // Best-effort sidecar cleanup; a missing sidecar is not an error.
80
+ await fs.unlink(this.headersSidecarPath(filePath)).catch(() => { });
81
+ }
82
+ async beginUpload(_input) {
83
+ return {
84
+ mode: 'proxied',
85
+ stagingKey: stagingKey(randomUUID()),
86
+ maxBytes: DEFAULT_MAX_UPLOAD_BYTES,
87
+ };
88
+ }
89
+ /**
90
+ * Staging methods accept caller-influenced keys (finalize receives the key
91
+ * from the client), so they must never operate outside the staging prefix —
92
+ * otherwise a crafted key turns the staging cleanup into an arbitrary-object
93
+ * delete. The API layer validates too; this is defense-in-depth.
94
+ */
95
+ assertStagingKey(key) {
96
+ if (!key.startsWith(`${ASSET_PREFIXES.staging}/`)) {
97
+ throw new Error(`Not a staging key: ${key}`);
98
+ }
99
+ }
100
+ async writeStaging(key, data, _contentType) {
101
+ this.assertStagingKey(key);
102
+ await this.writeFile(this.resolveKey(key), data);
103
+ }
104
+ async readStaging(key) {
105
+ this.assertStagingKey(key);
106
+ return this.readFileOrNull(this.resolveKey(key));
107
+ }
108
+ async deleteStaging(key) {
109
+ this.assertStagingKey(key);
110
+ await this.deleteFileIfPresent(this.resolveKey(key));
111
+ }
112
+ async putOriginal(input) {
113
+ const filePath = this.resolveKey(originalKey(input.hash32, input.ext));
114
+ await this.writeFile(filePath, input.data);
115
+ await this.writeHeadersSidecar(filePath, { contentType: input.contentType });
116
+ }
117
+ async readOriginal(hash32) {
118
+ const dir = this.resolveKey(ASSET_PREFIXES.originals);
119
+ let entries;
120
+ try {
121
+ entries = await fs.readdir(dir);
122
+ }
123
+ catch (err) {
124
+ if (isNotFoundError(err))
125
+ return null;
126
+ throw err;
127
+ }
128
+ const prefix = `${hash32}.`;
129
+ const match = entries.find((name) => name.startsWith(prefix) && !name.endsWith(HEADERS_SIDECAR_SUFFIX));
130
+ if (!match)
131
+ return null;
132
+ const filePath = path.join(dir, match);
133
+ const data = await this.readFileOrNull(filePath);
134
+ if (!data)
135
+ return null;
136
+ const headers = await this.readHeadersSidecar(filePath);
137
+ return { data, ext: match.slice(prefix.length), contentType: headers.contentType };
138
+ }
139
+ async putPublicObject(input) {
140
+ const filePath = this.resolveKey(input.key);
141
+ await this.writeFile(filePath, input.data);
142
+ await this.writeHeadersSidecar(filePath, {
143
+ contentType: input.contentType,
144
+ contentDisposition: input.contentDisposition,
145
+ cacheControl: input.cacheControl,
146
+ });
147
+ }
148
+ async readPublicObject(key) {
149
+ const filePath = this.resolveKey(key);
150
+ const data = await this.readFileOrNull(filePath);
151
+ if (!data)
152
+ return null;
153
+ const headers = await this.readHeadersSidecar(filePath);
154
+ return {
155
+ data,
156
+ contentType: headers.contentType,
157
+ contentDisposition: headers.contentDisposition,
158
+ cacheControl: headers.cacheControl,
159
+ };
160
+ }
161
+ /**
162
+ * Atomic-exclusive create: opens with the `wx` flag so two concurrent
163
+ * writers racing on the same hash32 have exactly one winner ('created')
164
+ * and exactly one loser ('already-exists'). The rename-based
165
+ * `atomicWriteFile` helper is deliberately NOT used here — a rename
166
+ * always clobbers the destination, which would let the second writer
167
+ * silently overwrite the first instead of losing cleanly.
168
+ */
169
+ async putMetaIfAbsent(hash32, meta) {
170
+ const filePath = this.resolveKey(metaKey(hash32));
171
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
172
+ try {
173
+ await fs.writeFile(filePath, JSON.stringify(meta), { flag: 'wx' });
174
+ return 'created';
175
+ }
176
+ catch (err) {
177
+ if (isFileExistsError(err))
178
+ return 'already-exists';
179
+ throw err;
180
+ }
181
+ }
182
+ async getMeta(hash32) {
183
+ const filePath = this.resolveKey(metaKey(hash32));
184
+ try {
185
+ const raw = await fs.readFile(filePath, 'utf-8');
186
+ return JSON.parse(raw);
187
+ }
188
+ catch (err) {
189
+ if (isNotFoundError(err))
190
+ return null;
191
+ throw err;
192
+ }
193
+ }
194
+ async listMeta(input) {
195
+ const limit = input?.limit ?? DEFAULT_LIST_LIMIT;
196
+ const dir = this.resolveKey(ASSET_PREFIXES.meta);
197
+ let filenames;
198
+ try {
199
+ filenames = (await fs.readdir(dir)).filter((name) => name.endsWith('.json'));
200
+ }
201
+ catch (err) {
202
+ if (isNotFoundError(err))
203
+ filenames = [];
204
+ else
205
+ throw err;
206
+ }
207
+ const all = [];
208
+ for (const name of filenames) {
209
+ try {
210
+ const raw = await fs.readFile(path.join(dir, name), 'utf-8');
211
+ all.push(JSON.parse(raw));
212
+ }
213
+ catch (err) {
214
+ // A meta file racing with deleteMeta is expected under concurrency;
215
+ // anything else is a real problem worth surfacing via getErrorMessage.
216
+ if (!isNotFoundError(err)) {
217
+ throw new Error(`Failed to read asset meta file '${name}': ${getErrorMessage(err)}`);
218
+ }
219
+ }
220
+ }
221
+ // Newest first; hash32 is the deterministic tiebreak for equal timestamps
222
+ // (common in fast test loops using Date.now()), which keeps pagination
223
+ // stable and duplicate/gap-free across calls.
224
+ all.sort((a, b) => {
225
+ if (a.uploadedAt !== b.uploadedAt)
226
+ return a.uploadedAt < b.uploadedAt ? 1 : -1;
227
+ return a.hash32 < b.hash32 ? 1 : -1;
228
+ });
229
+ let startIndex = 0;
230
+ if (input?.cursor) {
231
+ const afterHash32 = Buffer.from(input.cursor, 'base64').toString('utf-8');
232
+ const idx = all.findIndex((m) => m.hash32 === afterHash32);
233
+ startIndex = idx === -1 ? 0 : idx + 1;
234
+ }
235
+ const items = all.slice(startIndex, startIndex + limit);
236
+ const hasMore = startIndex + limit < all.length;
237
+ const nextCursor = hasMore
238
+ ? Buffer.from(items[items.length - 1].hash32, 'utf-8').toString('base64')
239
+ : undefined;
240
+ return { items, nextCursor };
241
+ }
242
+ async deleteMeta(hash32) {
243
+ const filePath = this.resolveKey(metaKey(hash32));
244
+ try {
245
+ await fs.unlink(filePath);
246
+ }
247
+ catch (err) {
248
+ if (isNotFoundError(err))
249
+ return;
250
+ throw err;
251
+ }
252
+ }
253
+ }
254
+ //# sourceMappingURL=store-local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-local.js","sourceRoot":"","sources":["../../src/assets/store-local.ts"],"names":[],"mappings":"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"}