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.
- package/dist/ai/json-to-markdown.js +74 -3
- package/dist/ai/json-to-markdown.js.map +1 -1
- package/dist/api/__test__/mock-client.d.ts +9 -5
- package/dist/api/__test__/mock-client.d.ts.map +1 -1
- package/dist/api/__test__/mock-client.js +15 -7
- package/dist/api/__test__/mock-client.js.map +1 -1
- package/dist/api/assets.d.ts +100 -36
- package/dist/api/assets.d.ts.map +1 -1
- package/dist/api/assets.js +338 -51
- package/dist/api/assets.js.map +1 -1
- package/dist/api/branch.d.ts +6 -0
- package/dist/api/branch.d.ts.map +1 -1
- package/dist/api/branch.js +63 -19
- package/dist/api/branch.js.map +1 -1
- package/dist/api/client.d.ts +16 -5
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +24 -6
- package/dist/api/client.js.map +1 -1
- package/dist/api/content.d.ts +2 -2
- package/dist/api/entries.d.ts +4 -4
- package/dist/api/entries.d.ts.map +1 -1
- package/dist/api/entries.js +10 -2
- package/dist/api/entries.js.map +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/route-builder.d.ts +9 -0
- package/dist/api/route-builder.d.ts.map +1 -1
- package/dist/api/route-builder.js +1 -0
- package/dist/api/route-builder.js.map +1 -1
- package/dist/api/schema.d.ts +4 -4
- package/dist/api/types.d.ts +11 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/assets/asset-prefixes.d.ts +31 -0
- package/dist/assets/asset-prefixes.d.ts.map +1 -0
- package/dist/assets/asset-prefixes.js +20 -0
- package/dist/assets/asset-prefixes.js.map +1 -0
- package/dist/assets/asset-src.d.ts +23 -0
- package/dist/assets/asset-src.d.ts.map +1 -0
- package/dist/assets/asset-src.js +28 -0
- package/dist/assets/asset-src.js.map +1 -0
- package/dist/assets/asset-url.d.ts +35 -0
- package/dist/assets/asset-url.d.ts.map +1 -0
- package/dist/assets/asset-url.js +77 -0
- package/dist/assets/asset-url.js.map +1 -0
- package/dist/assets/factory.d.ts +18 -0
- package/dist/assets/factory.d.ts.map +1 -0
- package/dist/assets/factory.js +42 -0
- package/dist/assets/factory.js.map +1 -0
- package/dist/assets/finalize.d.ts +36 -0
- package/dist/assets/finalize.d.ts.map +1 -0
- package/dist/assets/finalize.js +72 -0
- package/dist/assets/finalize.js.map +1 -0
- package/dist/assets/index.d.ts +20 -0
- package/dist/assets/index.d.ts.map +1 -0
- package/dist/assets/index.js +19 -0
- package/dist/assets/index.js.map +1 -0
- package/dist/assets/keys.d.ts +56 -0
- package/dist/assets/keys.d.ts.map +1 -0
- package/dist/assets/keys.js +88 -0
- package/dist/assets/keys.js.map +1 -0
- package/dist/assets/pipeline.d.ts +50 -0
- package/dist/assets/pipeline.d.ts.map +1 -0
- package/dist/assets/pipeline.js +202 -0
- package/dist/assets/pipeline.js.map +1 -0
- package/dist/assets/store-local.d.ts +84 -0
- package/dist/assets/store-local.d.ts.map +1 -0
- package/dist/assets/store-local.js +254 -0
- package/dist/assets/store-local.js.map +1 -0
- package/dist/assets/store-s3.d.ts +71 -0
- package/dist/assets/store-s3.d.ts.map +1 -0
- package/dist/assets/store-s3.js +183 -0
- package/dist/assets/store-s3.js.map +1 -0
- package/dist/assets/svg-sanitizer.d.ts +37 -0
- package/dist/assets/svg-sanitizer.d.ts.map +1 -0
- package/dist/assets/svg-sanitizer.js +222 -0
- package/dist/assets/svg-sanitizer.js.map +1 -0
- package/dist/assets/transform-directives.d.ts +97 -0
- package/dist/assets/transform-directives.d.ts.map +1 -0
- package/dist/assets/transform-directives.js +267 -0
- package/dist/assets/transform-directives.js.map +1 -0
- package/dist/assets/transform.d.ts +51 -0
- package/dist/assets/transform.d.ts.map +1 -0
- package/dist/assets/transform.js +190 -0
- package/dist/assets/transform.js.map +1 -0
- package/dist/assets/types.d.ts +82 -0
- package/dist/assets/types.d.ts.map +1 -0
- package/dist/assets/types.js +10 -0
- package/dist/assets/types.js.map +1 -0
- package/dist/authorization/groups/schema.d.ts +2 -2
- package/dist/authorization/permissions/schema.d.ts +2 -2
- package/dist/branch-metadata.d.ts +71 -6
- package/dist/branch-metadata.d.ts.map +1 -1
- package/dist/branch-metadata.js +132 -116
- package/dist/branch-metadata.js.map +1 -1
- package/dist/branch-registry.d.ts +78 -9
- package/dist/branch-registry.d.ts.map +1 -1
- package/dist/branch-registry.js +155 -29
- package/dist/branch-registry.js.map +1 -1
- package/dist/branch-schema-cache.d.ts +126 -11
- package/dist/branch-schema-cache.d.ts.map +1 -1
- package/dist/branch-schema-cache.js +208 -58
- package/dist/branch-schema-cache.js.map +1 -1
- package/dist/cli/generate-ai-content.js +1993 -1506
- package/dist/cli/init.js +16 -4
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/migrate.js +25 -0
- package/dist/cli/migrate.js.map +1 -1
- package/dist/cli/sync.js +5 -5
- package/dist/cli/sync.js.map +1 -1
- package/dist/comment-store.d.ts +45 -15
- package/dist/comment-store.d.ts.map +1 -1
- package/dist/comment-store.js +80 -101
- package/dist/comment-store.js.map +1 -1
- package/dist/config/helpers.d.ts.map +1 -1
- package/dist/config/helpers.js +2 -1
- package/dist/config/helpers.js.map +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schemas/config.d.ts +10 -0
- package/dist/config/schemas/config.d.ts.map +1 -1
- package/dist/config/schemas/field.d.ts +38 -3
- package/dist/config/schemas/field.d.ts.map +1 -1
- package/dist/config/schemas/field.js +18 -0
- package/dist/config/schemas/field.js.map +1 -1
- package/dist/config/schemas/media.d.ts +8 -0
- package/dist/config/schemas/media.d.ts.map +1 -1
- package/dist/config/schemas/media.js +9 -0
- package/dist/config/schemas/media.js.map +1 -1
- package/dist/config/types.d.ts +43 -3
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +4 -1
- package/dist/config/types.js.map +1 -1
- package/dist/content-index-generation.d.ts +40 -4
- package/dist/content-index-generation.d.ts.map +1 -1
- package/dist/content-index-generation.js +70 -73
- package/dist/content-index-generation.js.map +1 -1
- package/dist/content-store.d.ts +63 -0
- package/dist/content-store.d.ts.map +1 -1
- package/dist/content-store.js +421 -194
- package/dist/content-store.js.map +1 -1
- package/dist/editor/CanopyEditor.d.ts.map +1 -1
- package/dist/editor/CanopyEditor.js +1 -1
- package/dist/editor/CanopyEditor.js.map +1 -1
- package/dist/editor/Editor.d.ts +2 -0
- package/dist/editor/Editor.d.ts.map +1 -1
- package/dist/editor/Editor.js +82 -80
- package/dist/editor/Editor.js.map +1 -1
- package/dist/editor/FormRenderer.d.ts.map +1 -1
- package/dist/editor/FormRenderer.js +9 -0
- package/dist/editor/FormRenderer.js.map +1 -1
- package/dist/editor/components/EditorSidebar.d.ts +6 -1
- package/dist/editor/components/EditorSidebar.d.ts.map +1 -1
- package/dist/editor/components/EditorSidebar.js +4 -3
- package/dist/editor/components/EditorSidebar.js.map +1 -1
- package/dist/editor/context/AssetContext.d.ts +25 -0
- package/dist/editor/context/AssetContext.d.ts.map +1 -0
- package/dist/editor/context/AssetContext.js +26 -0
- package/dist/editor/context/AssetContext.js.map +1 -0
- package/dist/editor/context/index.d.ts +1 -0
- package/dist/editor/context/index.d.ts.map +1 -1
- package/dist/editor/context/index.js +1 -0
- package/dist/editor/context/index.js.map +1 -1
- package/dist/editor/fields/ImageField.d.ts +22 -0
- package/dist/editor/fields/ImageField.d.ts.map +1 -0
- package/dist/editor/fields/ImageField.js +100 -0
- package/dist/editor/fields/ImageField.js.map +1 -0
- package/dist/editor/fields/MarkdownField.d.ts.map +1 -1
- package/dist/editor/fields/MarkdownField.js +30 -4
- package/dist/editor/fields/MarkdownField.js.map +1 -1
- package/dist/editor/fields/MdxImageDialog.d.ts +16 -0
- package/dist/editor/fields/MdxImageDialog.d.ts.map +1 -0
- package/dist/editor/fields/MdxImageDialog.js +73 -0
- package/dist/editor/fields/MdxImageDialog.js.map +1 -0
- package/dist/editor/media/AssetCard.d.ts +21 -0
- package/dist/editor/media/AssetCard.d.ts.map +1 -0
- package/dist/editor/media/AssetCard.js +41 -0
- package/dist/editor/media/AssetCard.js.map +1 -0
- package/dist/editor/media/CropStep.d.ts +26 -0
- package/dist/editor/media/CropStep.d.ts.map +1 -0
- package/dist/editor/media/CropStep.js +38 -0
- package/dist/editor/media/CropStep.js.map +1 -0
- package/dist/editor/media/MediaLibrary.d.ts +22 -0
- package/dist/editor/media/MediaLibrary.d.ts.map +1 -0
- package/dist/editor/media/MediaLibrary.js +17 -0
- package/dist/editor/media/MediaLibrary.js.map +1 -0
- package/dist/editor/media/MediaLibraryBody.d.ts +17 -0
- package/dist/editor/media/MediaLibraryBody.d.ts.map +1 -0
- package/dist/editor/media/MediaLibraryBody.js +133 -0
- package/dist/editor/media/MediaLibraryBody.js.map +1 -0
- package/dist/editor/media/crop-math.d.ts +37 -0
- package/dist/editor/media/crop-math.d.ts.map +1 -0
- package/dist/editor/media/crop-math.js +62 -0
- package/dist/editor/media/crop-math.js.map +1 -0
- package/dist/editor/media/upload-asset.d.ts +31 -0
- package/dist/editor/media/upload-asset.d.ts.map +1 -0
- package/dist/editor/media/upload-asset.js +56 -0
- package/dist/editor/media/upload-asset.js.map +1 -0
- package/dist/editor/media/upload-constants.d.ts +27 -0
- package/dist/editor/media/upload-constants.d.ts.map +1 -0
- package/dist/editor/media/upload-constants.js +40 -0
- package/dist/editor/media/upload-constants.js.map +1 -0
- package/dist/editor/media/useAssetUpload.d.ts +19 -0
- package/dist/editor/media/useAssetUpload.d.ts.map +1 -0
- package/dist/editor/media/useAssetUpload.js +53 -0
- package/dist/editor/media/useAssetUpload.js.map +1 -0
- package/dist/editor/media/xhr-upload.d.ts +20 -0
- package/dist/editor/media/xhr-upload.d.ts.map +1 -0
- package/dist/editor/media/xhr-upload.js +42 -0
- package/dist/editor/media/xhr-upload.js.map +1 -0
- package/dist/editor/theme.d.ts +2 -1
- package/dist/editor/theme.d.ts.map +1 -1
- package/dist/editor/theme.js +1 -0
- package/dist/editor/theme.js.map +1 -1
- package/dist/entry-schema.d.ts +19 -0
- package/dist/entry-schema.d.ts.map +1 -1
- package/dist/entry-schema.js.map +1 -1
- package/dist/git-manager.d.ts +15 -10
- package/dist/git-manager.d.ts.map +1 -1
- package/dist/git-manager.js +16 -11
- package/dist/git-manager.js.map +1 -1
- package/dist/http/handler.d.ts +5 -2
- package/dist/http/handler.d.ts.map +1 -1
- package/dist/http/handler.js +13 -4
- package/dist/http/handler.js.map +1 -1
- package/dist/http/index.d.ts +2 -2
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +1 -1
- package/dist/http/index.js.map +1 -1
- package/dist/http/router.d.ts +14 -1
- package/dist/http/router.d.ts.map +1 -1
- package/dist/http/router.js +4 -1
- package/dist/http/router.js.map +1 -1
- package/dist/http/types.d.ts +40 -0
- package/dist/http/types.d.ts.map +1 -1
- package/dist/http/types.js +9 -0
- package/dist/http/types.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/resource-generation.d.ts +54 -0
- package/dist/resource-generation.d.ts.map +1 -0
- package/dist/resource-generation.js +156 -0
- package/dist/resource-generation.js.map +1 -0
- package/dist/schema/schema-store.d.ts +31 -4
- package/dist/schema/schema-store.d.ts.map +1 -1
- package/dist/schema/schema-store.js +47 -8
- package/dist/schema/schema-store.js.map +1 -1
- package/dist/server.d.ts +45 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +41 -0
- package/dist/server.js.map +1 -1
- package/dist/sync-core.js +2 -2
- package/dist/sync-core.js.map +1 -1
- package/dist/utils/occ-json-write.d.ts +100 -0
- package/dist/utils/occ-json-write.d.ts.map +1 -0
- package/dist/utils/occ-json-write.js +234 -0
- package/dist/utils/occ-json-write.js.map +1 -0
- package/dist/validation/entry-validator.d.ts.map +1 -1
- package/dist/validation/entry-validator.js +56 -1
- package/dist/validation/entry-validator.js.map +1 -1
- package/dist/worker/cms-worker.d.ts.map +1 -1
- package/dist/worker/cms-worker.js +21 -3
- package/dist/worker/cms-worker.js.map +1 -1
- package/package.json +13 -1
- package/dist/asset-store.d.ts +0 -32
- package/dist/asset-store.d.ts.map +0 -1
- package/dist/asset-store.js +0 -85
- 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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"}
|