@jini-ai/cms 0.3.11 → 0.4.0
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/CHANGELOG.md +59 -0
- package/README.md +47 -19
- package/dist/content-types/agent-tools.d.ts +3 -56
- package/dist/content-types/agent-tools.js +22 -6
- package/dist/content-types/cleanup.d.ts +18 -13
- package/dist/content-types/cleanup.js +16 -11
- package/dist/content-types/errors.d.ts +57 -24
- package/dist/content-types/errors.js +56 -39
- package/dist/content-types/field-defs.d.ts +8 -4
- package/dist/content-types/field-defs.js +19 -14
- package/dist/content-types/index-provisioning.d.ts +61 -47
- package/dist/content-types/index-provisioning.js +90 -66
- package/dist/content-types/index.d.ts +4 -3
- package/dist/content-types/lifecycle-dispatch.d.ts +11 -5
- package/dist/content-types/lifecycle-dispatch.js +5 -4
- package/dist/content-types/lifecycle.d.ts +23 -18
- package/dist/content-types/lifecycle.js +71 -64
- package/dist/content-types/repo.memory.d.ts +5 -4
- package/dist/content-types/repo.memory.js +3 -2
- package/dist/content-types/tool-registrations.d.ts +6 -8
- package/dist/content-types/tool-registrations.js +24 -28
- package/dist/content-types/types.d.ts +17 -13
- package/dist/content-types/types.js +11 -5
- package/dist/content-types/write-service.d.ts +20 -22
- package/dist/content-types/write-service.js +78 -75
- package/dist/core/authorization.d.ts +15 -0
- package/dist/core/authorization.js +2 -0
- package/dist/core/commands/change-set.d.ts +15 -7
- package/dist/core/commands/command.d.ts +45 -26
- package/dist/core/commands/command.js +73 -63
- package/dist/core/entity-liveness.d.ts +11 -7
- package/dist/core/entity-liveness.js +5 -4
- package/dist/core/index.d.ts +12 -5
- package/dist/core/index.js +10 -4
- package/dist/core/ports.d.ts +25 -24
- package/dist/core/ports.js +1 -0
- package/dist/core/tools/args.d.ts +49 -0
- package/dist/core/tools/args.js +46 -0
- package/dist/core/tools/index.d.ts +3 -0
- package/dist/core/tools/index.js +3 -0
- package/dist/entries/agent-tools.d.ts +1 -73
- package/dist/entries/agent-tools.js +19 -13
- package/dist/entries/errors.d.ts +28 -14
- package/dist/entries/errors.js +17 -11
- package/dist/entries/field-validation.d.ts +5 -3
- package/dist/entries/field-validation.js +7 -5
- package/dist/entries/index.d.ts +2 -2
- package/dist/entries/lifecycle-dispatch.d.ts +5 -2
- package/dist/entries/lifecycle-dispatch.js +2 -1
- package/dist/entries/list.d.ts +4 -2
- package/dist/entries/list.js +4 -3
- package/dist/entries/repo.memory.d.ts +12 -10
- package/dist/entries/repo.memory.js +11 -10
- package/dist/entries/tool-registrations.d.ts +6 -8
- package/dist/entries/tool-registrations.js +42 -41
- package/dist/entries/types.d.ts +0 -7
- package/dist/entries/write-service.d.ts +28 -29
- package/dist/entries/write-service.js +114 -105
- package/dist/http/settings/cms-adapter.d.ts +20 -0
- package/dist/http/settings/cms-adapter.js +124 -0
- package/dist/http/settings/contracts.d.ts +135 -0
- package/dist/http/settings/contracts.js +2 -0
- package/dist/http/settings/http.d.ts +10 -0
- package/dist/http/settings/http.js +211 -0
- package/dist/http/settings/index.d.ts +5 -0
- package/dist/http/settings/index.js +4 -0
- package/dist/http/settings/shared.d.ts +68 -0
- package/dist/http/settings/shared.js +54 -0
- package/dist/http/settings/stream.d.ts +24 -0
- package/dist/http/settings/stream.js +147 -0
- package/dist/media/agent-tools.d.ts +1 -66
- package/dist/media/agent-tools.js +5 -1
- package/dist/media/blob-gc-lock.d.ts +6 -2
- package/dist/media/blob-gc-lock.js +4 -2
- package/dist/media/blob-gc.d.ts +64 -51
- package/dist/media/blob-gc.js +62 -52
- package/dist/media/blob-key.d.ts +1 -1
- package/dist/media/content-type-sniffer.d.ts +3 -1
- package/dist/media/content-type-sniffer.js +1 -1
- package/dist/media/html-attributes.d.ts +9 -5
- package/dist/media/html-attributes.js +8 -6
- package/dist/media/image-transformer.js +1 -1
- package/dist/media/image-transformer.sharp.d.ts +6 -0
- package/dist/media/image-transformer.sharp.js +12 -6
- package/dist/media/import/fetch-image.d.ts +24 -0
- package/dist/media/import/fetch-image.js +137 -0
- package/dist/media/import/index.d.ts +3 -0
- package/dist/media/import/index.js +3 -0
- package/dist/media/import/ports.d.ts +38 -0
- package/dist/media/index.d.ts +7 -1
- package/dist/media/index.js +1 -0
- package/dist/media/media-service.d.ts +14 -10
- package/dist/media/media-service.js +57 -52
- package/dist/media/ports.d.ts +3 -2
- package/dist/media/rendition-service.d.ts +3 -3
- package/dist/media/rendition-service.js +38 -37
- package/dist/media/repo.memory.d.ts +18 -7
- package/dist/media/repo.memory.js +12 -6
- package/dist/media/tool-registrations.d.ts +16 -15
- package/dist/media/tool-registrations.js +84 -72
- package/dist/media/transform-lock.d.ts +22 -12
- package/dist/media/transform-lock.js +14 -13
- package/dist/media/transform-registry.d.ts +3 -3
- package/dist/media/transform-registry.js +19 -18
- package/dist/media/transform-types.d.ts +8 -3
- package/dist/media/transform-types.js +10 -5
- package/dist/media/types.d.ts +21 -4
- package/dist/media/types.js +15 -2
- package/dist/navigation/agent-tools.d.ts +1 -80
- package/dist/navigation/agent-tools.js +15 -3
- package/dist/navigation/contracts.d.ts +1 -1
- package/dist/navigation/index.d.ts +2 -2
- package/dist/navigation/menu-service.d.ts +57 -40
- package/dist/navigation/menu-service.js +80 -69
- package/dist/navigation/ports.d.ts +1 -1
- package/dist/navigation/read-model.d.ts +3 -1
- package/dist/navigation/read-model.js +2 -2
- package/dist/navigation/reconcile.d.ts +2 -2
- package/dist/navigation/reconcile.js +2 -1
- package/dist/navigation/repo.memory.d.ts +7 -3
- package/dist/navigation/repo.memory.js +4 -2
- package/dist/navigation/resolver.d.ts +4 -1
- package/dist/navigation/resolver.js +1 -1
- package/dist/navigation/tool-registrations.d.ts +6 -8
- package/dist/navigation/tool-registrations.js +44 -42
- package/dist/navigation/types.d.ts +1 -1
- package/dist/presentation/presentation.d.ts +11 -3
- package/dist/presentation/presentation.js +13 -6
- package/dist/presentation/repo.memory.d.ts +6 -2
- package/dist/presentation/repo.memory.js +3 -2
- package/dist/settings/agent-tools.d.ts +2 -75
- package/dist/settings/agent-tools.js +49 -45
- package/dist/settings/agent-value-write-tools.d.ts +22 -0
- package/dist/settings/agent-value-write-tools.js +154 -0
- package/dist/settings/agent-writable-preferences.js +8 -20
- package/dist/settings/agent-write-denylist.d.ts +19 -0
- package/dist/settings/agent-write-denylist.js +15 -0
- package/dist/settings/change-feed.d.ts +11 -2
- package/dist/settings/change-feed.js +4 -4
- package/dist/settings/definitions-dispatch.d.ts +8 -3
- package/dist/settings/definitions-dispatch.js +5 -5
- package/dist/settings/dictionaries/index.d.ts +5 -2
- package/dist/settings/dictionaries/index.js +2 -2
- package/dist/settings/dictionaries/settings-dialog.ar.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.bn.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.de.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.es.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.fa.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.fr.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.hi.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.hu.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.id.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.it.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ja.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ko.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.pl.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.pt-BR.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ru.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.th.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.tr.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.uk.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ur.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.zh-CN.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.zh-TW.js +0 -1
- package/dist/settings/ensure-definitions.d.ts +5 -5
- package/dist/settings/errors.d.ts +7 -1
- package/dist/settings/errors.js +7 -1
- package/dist/settings/index.d.ts +4 -1
- package/dist/settings/index.js +1 -0
- package/dist/settings/ports.d.ts +6 -5
- package/dist/settings/principal-lookup.d.ts +9 -0
- package/dist/settings/principal-lookup.js +2 -0
- package/dist/settings/purge-service.d.ts +5 -4
- package/dist/settings/purge-service.js +3 -2
- package/dist/settings/settings.d.ts +22 -7
- package/dist/settings/settings.js +47 -26
- package/dist/settings/tool-registrations.d.ts +25 -32
- package/dist/settings/tool-registrations.js +66 -56
- package/dist/settings/types.d.ts +1 -1
- package/dist/settings/ui-tab-definitions.d.ts +1 -1
- package/dist/settings/write-service.d.ts +32 -26
- package/dist/settings/write-service.js +26 -20
- package/dist/taxonomy/agent-tools.d.ts +1 -66
- package/dist/taxonomy/agent-tools.js +25 -15
- package/dist/taxonomy/content-lookup.js +1 -1
- package/dist/taxonomy/index.d.ts +2 -2
- package/dist/taxonomy/merge-term.d.ts +7 -4
- package/dist/taxonomy/merge-term.js +7 -5
- package/dist/taxonomy/repo.memory.d.ts +42 -19
- package/dist/taxonomy/repo.memory.js +14 -12
- package/dist/taxonomy/validation-chain.d.ts +39 -18
- package/dist/taxonomy/validation-chain.js +35 -25
- package/dist/taxonomy/write-service.d.ts +103 -44
- package/dist/taxonomy/write-service.js +221 -179
- package/dist/trash/adapters/record-store.d.ts +52 -0
- package/dist/trash/adapters/record-store.js +36 -0
- package/dist/trash/cursor.d.ts +20 -0
- package/dist/trash/cursor.js +36 -0
- package/dist/trash/follow-ups.d.ts +76 -0
- package/dist/trash/follow-ups.js +46 -0
- package/dist/trash/index.d.ts +8 -0
- package/dist/trash/index.js +8 -0
- package/dist/trash/ports.d.ts +308 -0
- package/dist/trash/ports.js +2 -0
- package/dist/trash/repo.memory.d.ts +50 -0
- package/dist/trash/repo.memory.js +114 -0
- package/dist/trash/sweeper.d.ts +77 -0
- package/dist/trash/sweeper.js +127 -0
- package/dist/trash/write-service.d.ts +100 -0
- package/dist/trash/write-service.js +242 -0
- package/dist/workspace/agent-tools.d.ts +1 -59
- package/dist/workspace/agent-tools.js +27 -12
- package/dist/workspace/create.d.ts +31 -6
- package/dist/workspace/create.js +18 -5
- package/dist/workspace/delete.d.ts +18 -13
- package/dist/workspace/delete.js +20 -12
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/repo.memory.d.ts +19 -4
- package/dist/workspace/repo.memory.js +26 -4
- package/dist/workspace/tool-registrations.d.ts +2 -2
- package/dist/workspace/tool-registrations.js +13 -13
- package/dist/workspace/update.d.ts +6 -5
- package/dist/workspace/update.js +9 -8
- package/package.json +44 -38
- package/dist/identity/admin-crud-service.d.ts +0 -280
- package/dist/identity/admin-crud-service.js +0 -468
- package/dist/identity/agent-tool-input.d.ts +0 -58
- package/dist/identity/agent-tool-input.js +0 -126
- package/dist/identity/agent-tools.d.ts +0 -101
- package/dist/identity/agent-tools.js +0 -290
- package/dist/identity/auth-service.d.ts +0 -118
- package/dist/identity/auth-service.js +0 -170
- package/dist/identity/authorize.d.ts +0 -90
- package/dist/identity/authorize.js +0 -94
- package/dist/identity/grant-service.d.ts +0 -198
- package/dist/identity/grant-service.js +0 -361
- package/dist/identity/hasher.d.ts +0 -39
- package/dist/identity/hasher.js +0 -39
- package/dist/identity/index.d.ts +0 -62
- package/dist/identity/index.js +0 -54
- package/dist/identity/password-policy.d.ts +0 -57
- package/dist/identity/password-policy.js +0 -66
- package/dist/identity/permission-migrations.d.ts +0 -86
- package/dist/identity/permission-migrations.js +0 -67
- package/dist/identity/permissions.d.ts +0 -35
- package/dist/identity/permissions.js +0 -603
- package/dist/identity/ports.d.ts +0 -201
- package/dist/identity/repo.memory.d.ts +0 -164
- package/dist/identity/repo.memory.js +0 -224
- package/dist/identity/seed.d.ts +0 -54
- package/dist/identity/seed.js +0 -429
- package/dist/identity/tool-registrations.d.ts +0 -50
- package/dist/identity/tool-registrations.js +0 -419
- package/dist/identity/types.d.ts +0 -185
- package/dist/identity/types.js +0 -64
- package/dist/identity/username.d.ts +0 -12
- package/dist/identity/username.js +0 -14
- /package/dist/{identity → media/import}/ports.js +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file Server-side allowlist for `MediaRecord.htmlAttributes` (2026-09-07) — the enforcement half
|
|
3
3
|
* of a validator that ALSO has a browser-side copy in the consuming product's admin
|
|
4
|
-
* (
|
|
4
|
+
* (its attribute parser and related rules).
|
|
5
5
|
*
|
|
6
6
|
* Deliberately duplicated, not shared via one imported module, for the same reason this codebase
|
|
7
7
|
* already hand-copies `DEFAULT_ALLOWED_MIME_TYPES` into that admin's `FILE_HANDLER_ALLOWED_MIME_TYPES`/
|
|
8
8
|
* `IMPORTABLE_CONTENT_TYPES`: `@jini-ai/cms/media`'s barrel (`index.ts`) re-exports files that
|
|
9
9
|
* import real Node built-ins (`node:crypto` in `media-service.ts`, `node:fs` in
|
|
10
10
|
* `blob-store.fs.ts`, native `sharp` bindings in `image-transformer.sharp.ts`) — importing this
|
|
11
|
-
* subpath from a browser-bundled
|
|
11
|
+
* subpath from a browser-bundled admin app risks pulling those into the admin
|
|
12
12
|
* SPA's bundle even if only this one pure function is actually used, and this admin's own copy
|
|
13
13
|
* already exists, tested, with no such risk. This file is the copy every Node-side consumer
|
|
14
|
-
* (`media-service.ts`'s own `updateMediaMetadata`, and
|
|
14
|
+
* (`media-service.ts`'s own `updateMediaMetadata`, and the host's render path, both pure
|
|
15
15
|
* backend code) can safely import from `@jini-ai/cms/media` instead of re-deriving the rules a
|
|
16
16
|
* third time.
|
|
17
17
|
*
|
|
@@ -45,7 +45,9 @@ export declare const MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES: readonly ["loading", "d
|
|
|
45
45
|
* @complexity O(n) in the name's length (one anchored regex test), then O(1) — one prefix check,
|
|
46
46
|
* one fixed-length array lookup.
|
|
47
47
|
*/
|
|
48
|
-
export declare function isAllowedMediaHtmlAttributeName(
|
|
48
|
+
export declare function isAllowedMediaHtmlAttributeName(requiredArgs: {
|
|
49
|
+
name: string;
|
|
50
|
+
}, optionalArgs?: Record<string, never>): boolean;
|
|
49
51
|
/** Why one parsed attribute token was rejected — see {@link parseMediaHtmlAttributes}'s own doc for
|
|
50
52
|
* why `event-handler`/`javascript-url` are checked, and reported, ahead of plain allowlist
|
|
51
53
|
* membership. */
|
|
@@ -76,7 +78,9 @@ export interface ParsedMediaHtmlAttributes {
|
|
|
76
78
|
* @complexity Time O(n) in `text`'s length (one regex pass over it), space O(k) for k parsed
|
|
77
79
|
* attributes.
|
|
78
80
|
*/
|
|
79
|
-
export declare function parseMediaHtmlAttributes(
|
|
81
|
+
export declare function parseMediaHtmlAttributes(requiredArgs: {
|
|
82
|
+
text: string;
|
|
83
|
+
}, optionalArgs?: Record<string, never>): ParsedMediaHtmlAttributes;
|
|
80
84
|
/** Formats a {@link MediaHtmlAttributeError} into a plain-English message naming the rejected
|
|
81
85
|
* attribute (owner requirement) — the server-side, non-localized counterpart to the admin's own
|
|
82
86
|
* `describeMediaHtmlAttributeError` (which additionally runs the message through `MEDIA_DICT` for
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file Server-side allowlist for `MediaRecord.htmlAttributes` (2026-09-07) — the enforcement half
|
|
3
3
|
* of a validator that ALSO has a browser-side copy in the consuming product's admin
|
|
4
|
-
* (
|
|
4
|
+
* (its attribute parser and related rules).
|
|
5
5
|
*
|
|
6
6
|
* Deliberately duplicated, not shared via one imported module, for the same reason this codebase
|
|
7
7
|
* already hand-copies `DEFAULT_ALLOWED_MIME_TYPES` into that admin's `FILE_HANDLER_ALLOWED_MIME_TYPES`/
|
|
8
8
|
* `IMPORTABLE_CONTENT_TYPES`: `@jini-ai/cms/media`'s barrel (`index.ts`) re-exports files that
|
|
9
9
|
* import real Node built-ins (`node:crypto` in `media-service.ts`, `node:fs` in
|
|
10
10
|
* `blob-store.fs.ts`, native `sharp` bindings in `image-transformer.sharp.ts`) — importing this
|
|
11
|
-
* subpath from a browser-bundled
|
|
11
|
+
* subpath from a browser-bundled admin app risks pulling those into the admin
|
|
12
12
|
* SPA's bundle even if only this one pure function is actually used, and this admin's own copy
|
|
13
13
|
* already exists, tested, with no such risk. This file is the copy every Node-side consumer
|
|
14
|
-
* (`media-service.ts`'s own `updateMediaMetadata`, and
|
|
14
|
+
* (`media-service.ts`'s own `updateMediaMetadata`, and the host's render path, both pure
|
|
15
15
|
* backend code) can safely import from `@jini-ai/cms/media` instead of re-deriving the rules a
|
|
16
16
|
* third time.
|
|
17
17
|
*
|
|
@@ -72,7 +72,8 @@ const MEDIA_HTML_ATTRIBUTE_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
|
72
72
|
* @complexity O(n) in the name's length (one anchored regex test), then O(1) — one prefix check,
|
|
73
73
|
* one fixed-length array lookup.
|
|
74
74
|
*/
|
|
75
|
-
export function isAllowedMediaHtmlAttributeName(
|
|
75
|
+
export function isAllowedMediaHtmlAttributeName(requiredArgs, optionalArgs = {}) {
|
|
76
|
+
const { name } = requiredArgs;
|
|
76
77
|
const lower = name.toLowerCase();
|
|
77
78
|
if (!MEDIA_HTML_ATTRIBUTE_NAME_PATTERN.test(lower))
|
|
78
79
|
return false;
|
|
@@ -98,7 +99,7 @@ function classifyMediaHtmlAttributeToken(name, value) {
|
|
|
98
99
|
return { reason: "event-handler", attribute: name };
|
|
99
100
|
if (value.trim().toLowerCase().startsWith("javascript:"))
|
|
100
101
|
return { reason: "javascript-url", attribute: name };
|
|
101
|
-
if (!isAllowedMediaHtmlAttributeName(name))
|
|
102
|
+
if (!isAllowedMediaHtmlAttributeName({ name: name }))
|
|
102
103
|
return { reason: "disallowed-name", attribute: name };
|
|
103
104
|
return null;
|
|
104
105
|
}
|
|
@@ -112,7 +113,8 @@ function classifyMediaHtmlAttributeToken(name, value) {
|
|
|
112
113
|
* @complexity Time O(n) in `text`'s length (one regex pass over it), space O(k) for k parsed
|
|
113
114
|
* attributes.
|
|
114
115
|
*/
|
|
115
|
-
export function parseMediaHtmlAttributes(
|
|
116
|
+
export function parseMediaHtmlAttributes(requiredArgs, optionalArgs = {}) {
|
|
117
|
+
const { text } = requiredArgs;
|
|
116
118
|
const trimmed = text.trim();
|
|
117
119
|
if (trimmed === "")
|
|
118
120
|
return { attributes: {}, error: null };
|
|
@@ -17,7 +17,7 @@ export class InMemoryImageTransformer {
|
|
|
17
17
|
const output = new Uint8Array(tagBytes.length + input.bytes.length);
|
|
18
18
|
output.set(tagBytes, 0);
|
|
19
19
|
output.set(input.bytes, tagBytes.length);
|
|
20
|
-
return { bytes: output, contentType: mimeForTransformFormat(input.params.format) };
|
|
20
|
+
return { bytes: output, contentType: mimeForTransformFormat({ format: input.params.format }) };
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
//# sourceMappingURL=image-transformer.js.map
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { ImageTransformerPort, TransformImageInput, TransformImageOutput } from "./image-transformer.js";
|
|
2
2
|
/** Thrown by {@link SharpImageTransformer.transform} when the `sharp` package cannot be loaded. */
|
|
3
3
|
export declare class ImageTransformUnavailableError extends Error {
|
|
4
|
+
constructor({ message }: {
|
|
5
|
+
message: string;
|
|
6
|
+
}, optionalArgs?: ErrorOptions);
|
|
4
7
|
}
|
|
5
8
|
/**
|
|
6
9
|
* Thrown by {@link SharpImageTransformer.transform} when `sharp` itself rejects the SOURCE bytes —
|
|
@@ -17,6 +20,9 @@ export declare class ImageTransformUnavailableError extends Error {
|
|
|
17
20
|
* states for the sibling `ImageTransformUnavailableError` case.
|
|
18
21
|
*/
|
|
19
22
|
export declare class ImageSourceCorruptError extends Error {
|
|
23
|
+
constructor({ message }: {
|
|
24
|
+
message: string;
|
|
25
|
+
}, optionalArgs?: ErrorOptions);
|
|
20
26
|
}
|
|
21
27
|
/**
|
|
22
28
|
* Real `ImageTransformerPort` adapter. Applies `params.width`/`height`
|
|
@@ -32,6 +32,9 @@ import { mimeForTransformFormat } from "./transform-types.js";
|
|
|
32
32
|
const require = createRequire(import.meta.url);
|
|
33
33
|
/** Thrown by {@link SharpImageTransformer.transform} when the `sharp` package cannot be loaded. */
|
|
34
34
|
export class ImageTransformUnavailableError extends Error {
|
|
35
|
+
constructor({ message }, optionalArgs = {}) {
|
|
36
|
+
super(message, optionalArgs);
|
|
37
|
+
}
|
|
35
38
|
}
|
|
36
39
|
/**
|
|
37
40
|
* Thrown by {@link SharpImageTransformer.transform} when `sharp` itself rejects the SOURCE bytes —
|
|
@@ -48,6 +51,9 @@ export class ImageTransformUnavailableError extends Error {
|
|
|
48
51
|
* states for the sibling `ImageTransformUnavailableError` case.
|
|
49
52
|
*/
|
|
50
53
|
export class ImageSourceCorruptError extends Error {
|
|
54
|
+
constructor({ message }, optionalArgs = {}) {
|
|
55
|
+
super(message, optionalArgs);
|
|
56
|
+
}
|
|
51
57
|
}
|
|
52
58
|
/**
|
|
53
59
|
* {@link TransformFormat} values that can actually carry multiple frames on output. GIF and WebP
|
|
@@ -70,10 +76,10 @@ function loadSharpFactory() {
|
|
|
70
76
|
return require("sharp");
|
|
71
77
|
}
|
|
72
78
|
catch (err) {
|
|
73
|
-
throw new ImageTransformUnavailableError("the 'sharp' npm package is not installed in this environment (run `npm install sharp`) — " +
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
throw new ImageTransformUnavailableError({ message: "the 'sharp' npm package is not installed in this environment (run `npm install sharp`) — " +
|
|
80
|
+
"SharpImageTransformer cannot perform a real pixel transform without it. This is a " +
|
|
81
|
+
"disclosed, optional-dependency blocker, not a silent stub: no bytes are faked or passed " +
|
|
82
|
+
"through unmodified." });
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
85
|
/**
|
|
@@ -149,12 +155,12 @@ export class SharpImageTransformer {
|
|
|
149
155
|
const outBuffer = await pipeline.toBuffer();
|
|
150
156
|
return {
|
|
151
157
|
bytes: new Uint8Array(outBuffer),
|
|
152
|
-
contentType: mimeForTransformFormat(input.params.format),
|
|
158
|
+
contentType: mimeForTransformFormat({ format: input.params.format }),
|
|
153
159
|
};
|
|
154
160
|
}
|
|
155
161
|
catch (err) {
|
|
156
162
|
const detail = err instanceof Error ? err.message : String(err);
|
|
157
|
-
throw new ImageSourceCorruptError(`the source image could not be decoded/re-encoded (target format '${input.params.format}'): ${detail}`);
|
|
163
|
+
throw new ImageSourceCorruptError({ message: `the source image could not be decoded/re-encoded (target format '${input.params.format}'): ${detail}` });
|
|
158
164
|
}
|
|
159
165
|
}
|
|
160
166
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Binary URL import; the outbound adapter owns per-hop SSRF controls, while this module enforces caller MIME/byte policy and preserves source provenance. */
|
|
2
|
+
import type { FetchImageRequired, FetchImageOptional, ValidateImageBytesRequired } from "./ports.js";
|
|
3
|
+
export declare class MediaImportValidationError extends Error {
|
|
4
|
+
constructor({ message }: {
|
|
5
|
+
message: string;
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
export interface FetchedImage {
|
|
9
|
+
readonly bytes: Uint8Array;
|
|
10
|
+
readonly contentType: string;
|
|
11
|
+
readonly url: URL;
|
|
12
|
+
}
|
|
13
|
+
export declare function parseImportUrl({ raw }: {
|
|
14
|
+
raw: string;
|
|
15
|
+
}): URL;
|
|
16
|
+
export declare function buildImportFilename({ url, contentType }: {
|
|
17
|
+
url: URL;
|
|
18
|
+
contentType: string;
|
|
19
|
+
}, { override }?: {
|
|
20
|
+
override?: string;
|
|
21
|
+
}): string;
|
|
22
|
+
export declare function validateImageBytes({ source, bytes, bytesTruncated, maxBytes, allowedContentTypes, sniffer }: ValidateImageBytesRequired): string;
|
|
23
|
+
export declare function fetchImage(required: FetchImageRequired, { timeoutMs }?: FetchImageOptional): Promise<FetchedImage>;
|
|
24
|
+
//# sourceMappingURL=fetch-image.d.ts.map
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Request/fetched-content refusal is distinct from an upload validator's already-in-hand byte checks.
|
|
2
|
+
export class MediaImportValidationError extends Error {
|
|
3
|
+
constructor({ message }) { super(message); }
|
|
4
|
+
}
|
|
5
|
+
// An extension derived from the sniffed type cannot contradict the payload's actual bytes.
|
|
6
|
+
const EXTENSION_BY_CONTENT_TYPE = {
|
|
7
|
+
"image/png": "png",
|
|
8
|
+
"image/jpeg": "jpg",
|
|
9
|
+
"image/gif": "gif",
|
|
10
|
+
"image/webp": "webp",
|
|
11
|
+
"image/avif": "avif",
|
|
12
|
+
"video/mp4": "mp4",
|
|
13
|
+
"video/webm": "webm",
|
|
14
|
+
};
|
|
15
|
+
// Early scheme/embedded-credential rejection gives callers actionable errors; it supplements,
|
|
16
|
+
// rather than replaces, the outbound guard's independent connect-time controls.
|
|
17
|
+
export function parseImportUrl({ raw }) {
|
|
18
|
+
let url;
|
|
19
|
+
try {
|
|
20
|
+
url = new URL(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new MediaImportValidationError({ message: `'url' must be an absolute URL including the scheme (for example 'https://example.com/image.png') — got '${raw}'.` });
|
|
24
|
+
}
|
|
25
|
+
if (url.protocol !== "https:") {
|
|
26
|
+
throw new MediaImportValidationError({ message: `'url' must use https — got '${url.protocol.replace(":", "")}'. Only https URLs can be imported.` });
|
|
27
|
+
}
|
|
28
|
+
if (url.username || url.password) {
|
|
29
|
+
throw new MediaImportValidationError({ message: "'url' must not embed credentials (user:password@host)." });
|
|
30
|
+
}
|
|
31
|
+
return url;
|
|
32
|
+
}
|
|
33
|
+
// Bound/sanitize path punctuation, controls, and traversal-like names. This labels an upload, not
|
|
34
|
+
// its filesystem location: content-addressed storage must not derive a path from this display name.
|
|
35
|
+
export function buildImportFilename({ url, contentType }, { override } = {}) {
|
|
36
|
+
const extension = EXTENSION_BY_CONTENT_TYPE[contentType] ?? "bin";
|
|
37
|
+
const rawName = override ?? decodeLastPathSegment(url);
|
|
38
|
+
const slug = rawName
|
|
39
|
+
.replace(/\.[a-zA-Z0-9]{1,5}$/, "")
|
|
40
|
+
.replace(/[^a-zA-Z0-9\s-]/g, " ")
|
|
41
|
+
.trim()
|
|
42
|
+
.replace(/\s+/g, "-")
|
|
43
|
+
.slice(0, 60)
|
|
44
|
+
.replace(/^-+|-+$/g, "");
|
|
45
|
+
return `${slug || "imported-image"}.${extension}`;
|
|
46
|
+
}
|
|
47
|
+
// A malformed escape should not fail an otherwise valid import; the name sanitizer handles it.
|
|
48
|
+
function decodeLastPathSegment(url) {
|
|
49
|
+
const segment = url.pathname.split("/").filter(Boolean).pop() ?? "";
|
|
50
|
+
try {
|
|
51
|
+
return decodeURIComponent(segment);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return segment;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Distinguish expired signed URLs (often 403/404) from a broken upstream host, so callers request a
|
|
58
|
+
// fresh link instead of repeating a doomed fetch. Keep recovery wording separate from network I/O.
|
|
59
|
+
function buildStatusError(url, status) {
|
|
60
|
+
const hint = status === 403 || status === 404
|
|
61
|
+
? " Signed or time-limited image URLs expire quickly — ask for a fresh URL rather than retrying this one."
|
|
62
|
+
: "";
|
|
63
|
+
return new MediaImportValidationError({ message: `fetching '${url.href}' returned HTTP ${status}, not an image.${hint}` });
|
|
64
|
+
}
|
|
65
|
+
// Check clipping BEFORE magic bytes: a truncated image can still sniff as PNG but is a corrupt file,
|
|
66
|
+
// not a smaller image. Never create a successful media row from a silently partial payload.
|
|
67
|
+
// The sniffer and allowlist decide type; remote headers/local filename extensions cannot authorize
|
|
68
|
+
// HTML, PDFs, or unsanitized SVG masquerading as images.
|
|
69
|
+
export function validateImageBytes({ source, bytes, bytesTruncated, maxBytes, allowedContentTypes, sniffer }) {
|
|
70
|
+
assertMaxBytes(maxBytes);
|
|
71
|
+
// Keep URL error labels for remote imports while letting local root-relative paths label refusals.
|
|
72
|
+
const isRemote = source instanceof URL;
|
|
73
|
+
const label = isRemote ? source.href : source;
|
|
74
|
+
if (bytesTruncated || bytes.byteLength > maxBytes) {
|
|
75
|
+
throw new MediaImportValidationError({ message: isRemote
|
|
76
|
+
? `the image at '${label}' exceeds the ${maxBytes}-byte import limit. Nothing was saved — a partially downloaded image would be a corrupt file, not a smaller one.`
|
|
77
|
+
: `file '${label}' exceeds the ${maxBytes}-byte import limit. Nothing was saved.` });
|
|
78
|
+
}
|
|
79
|
+
if (bytes.byteLength === 0) {
|
|
80
|
+
throw new MediaImportValidationError({ message: isRemote
|
|
81
|
+
? `'${label}' returned an empty response body — there is nothing to import.`
|
|
82
|
+
: `file '${label}' is empty — there is nothing to import.` });
|
|
83
|
+
}
|
|
84
|
+
const contentType = sniffer.sniff({ bytes });
|
|
85
|
+
if (!allowedContentTypes.has(contentType)) {
|
|
86
|
+
throw new MediaImportValidationError({ message: `'${label}' is not an importable file: its actual bytes are '${contentType}'. ` +
|
|
87
|
+
`Only ${[...allowedContentTypes].join(", ")} can be imported (${isRemote ? 'the served Content-Type header' : 'the file extension'} is deliberately ignored — the bytes decide).` });
|
|
88
|
+
}
|
|
89
|
+
return contentType;
|
|
90
|
+
}
|
|
91
|
+
export async function fetchImage(required, { timeoutMs = 20_000 } = {}) {
|
|
92
|
+
const url = parseImportUrl({ raw: required.url });
|
|
93
|
+
assertMaxBytes(required.maxBytes);
|
|
94
|
+
const response = await required.outboundGuard.send({
|
|
95
|
+
httpClient: required.httpClient,
|
|
96
|
+
request: {
|
|
97
|
+
method: "GET",
|
|
98
|
+
url: url.href,
|
|
99
|
+
headers: { Accept: [...required.allowedContentTypes].join(", ") },
|
|
100
|
+
timeoutMs,
|
|
101
|
+
maxResponseBytes: required.maxBytes,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
if (response.status < 200 || response.status >= 300) {
|
|
105
|
+
throw buildStatusError(url, response.status);
|
|
106
|
+
}
|
|
107
|
+
// Lossy UTF-8 decoding cannot reconstruct binary media. Fail loudly for clients missing raw bytes
|
|
108
|
+
// rather than store a mangled image and call it success; transport failures retain their own type.
|
|
109
|
+
const bytes = response.bodyBytes;
|
|
110
|
+
if (bytes === undefined) {
|
|
111
|
+
throw new MediaImportValidationError({ message: `the HTTP client returned no raw bytes for '${url.href}' — refusing to reconstruct image data from its lossy text decoding.` });
|
|
112
|
+
}
|
|
113
|
+
// A byte-specific verdict wins over truncation of the lossy text representation.
|
|
114
|
+
// The coarse flag ORs byte and text truncation: honoring it when a precise byte verdict exists
|
|
115
|
+
// once rejected complete images whose unused UTF-8 decode alone exceeded the cap. A legacy client
|
|
116
|
+
// with no precise verdict must still fail safe on the coarse flag.
|
|
117
|
+
const bytesTruncated = response.bodyBytesTruncated ?? response.bodyTruncated === true;
|
|
118
|
+
const contentType = validateImageBytes({ ...required, source: url, bytes, bytesTruncated });
|
|
119
|
+
return { bytes, contentType, url: resolveSourceUrl(url, response.finalUrl) };
|
|
120
|
+
}
|
|
121
|
+
// An older adapter may omit finalUrl; a malformed report can come from a hand-written port. Keep
|
|
122
|
+
// the requested URL as a less precise label instead of rejecting good bytes or blanking provenance.
|
|
123
|
+
function resolveSourceUrl(requested, reported) {
|
|
124
|
+
if (reported === undefined)
|
|
125
|
+
return requested;
|
|
126
|
+
try {
|
|
127
|
+
return new URL(reported);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return requested;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function assertMaxBytes(maxBytes) {
|
|
134
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1)
|
|
135
|
+
throw new MediaImportValidationError({ message: "maxBytes must be a positive safe integer" });
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=fetch-image.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Binary requests use the kernel HTTP seam, compatible with the guarded platform client. */
|
|
2
|
+
import type { HttpRequest, HttpResponse, HttpClientPort } from "@jini-ai/core/primitives";
|
|
3
|
+
/**
|
|
4
|
+
* The only outbound execution path. The adapter must enforce DNS/IP validation, peer pinning,
|
|
5
|
+
* redirect checks and byte/time ceilings for every hop. Bind a platform guarded client's send:
|
|
6
|
+
* `{ send: ({ request }) => guardedClient.send({ request }) }`.
|
|
7
|
+
* A URL preflight alone does not satisfy this port's contract.
|
|
8
|
+
*/
|
|
9
|
+
export interface OutboundGuardPort {
|
|
10
|
+
send(required: {
|
|
11
|
+
request: HttpRequest;
|
|
12
|
+
httpClient: HttpClientPort;
|
|
13
|
+
}): Promise<HttpResponse>;
|
|
14
|
+
}
|
|
15
|
+
export interface ContentSnifferPort {
|
|
16
|
+
sniff(required: {
|
|
17
|
+
bytes: Uint8Array;
|
|
18
|
+
}): string;
|
|
19
|
+
}
|
|
20
|
+
export interface ImageImportPolicy {
|
|
21
|
+
maxBytes: number;
|
|
22
|
+
allowedContentTypes: ReadonlySet<string>;
|
|
23
|
+
sniffer: ContentSnifferPort;
|
|
24
|
+
}
|
|
25
|
+
export interface ValidateImageBytesRequired extends ImageImportPolicy {
|
|
26
|
+
source: URL | string;
|
|
27
|
+
bytes: Uint8Array;
|
|
28
|
+
bytesTruncated: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface FetchImageRequired extends ImageImportPolicy {
|
|
31
|
+
url: string;
|
|
32
|
+
httpClient: HttpClientPort;
|
|
33
|
+
outboundGuard: OutboundGuardPort;
|
|
34
|
+
}
|
|
35
|
+
export interface FetchImageOptional {
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=ports.d.ts.map
|
package/dist/media/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export { InMemoryImageTransformer } from "./image-transformer.js";
|
|
|
32
32
|
export { SharpImageTransformer, ImageTransformUnavailableError, ImageSourceCorruptError } from "./image-transformer.sharp.js";
|
|
33
33
|
export { sniffContentType, type SniffedContentType } from "./content-type-sniffer.js";
|
|
34
34
|
/** The agent-tool catalog for this domain (see `agent-tools.ts` for what is deliberately omitted). */
|
|
35
|
-
export { mediaAgentToolCatalog,
|
|
35
|
+
export { mediaAgentToolCatalog, } from "./agent-tools.js";
|
|
36
36
|
/**
|
|
37
37
|
* The agent-tool wiring for this domain.
|
|
38
38
|
*
|
|
@@ -41,4 +41,10 @@ export { mediaAgentToolCatalog, type AgentToolDefinition as MediaAgentToolDefini
|
|
|
41
41
|
* the shape is the contract, so no host type needs to be named here.
|
|
42
42
|
*/
|
|
43
43
|
export { buildMediaRegistrations, mediaDerivedRisk, type MediaToolDeps, } from "./tool-registrations.js";
|
|
44
|
+
export type { UploadMediaRequired, UploadMediaOptional, ListMediaRequired, GetMediaByIdRequired, UpdateMediaMetadataRequired, TrashMediaRequired, PurgeMediaRequired, RollbackUploadedMediaRequired } from "./media-service.js";
|
|
45
|
+
export type { ResolveMediaRenditionRequired } from "./rendition-service.js";
|
|
46
|
+
export type { RegisterTransformRequired, GetLatestTransformDefinitionRequired, IsLatestTransformVersionRequired } from "./transform-registry.js";
|
|
47
|
+
export type { IsBlobUnreferencedRequired, TombstoneBlobRequired, RunBlobGcDeletePassRequired, RunBlobGcUnlinkPassRequired, RunBlobGcCycleRequired } from "./blob-gc.js";
|
|
48
|
+
export { rollbackUploadedMedia } from "./media-service.js";
|
|
49
|
+
export type { MediaRowCleanupDeps } from "./media-service.js";
|
|
44
50
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/media/index.js
CHANGED
|
@@ -29,4 +29,5 @@ export { mediaAgentToolCatalog, } from "./agent-tools.js";
|
|
|
29
29
|
* the shape is the contract, so no host type needs to be named here.
|
|
30
30
|
*/
|
|
31
31
|
export { buildMediaRegistrations, mediaDerivedRisk, } from "./tool-registrations.js";
|
|
32
|
+
export { rollbackUploadedMedia } from "./media-service.js";
|
|
32
33
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Clock, IdGenerator, UUID } from "@jini-ai/core/primitives";
|
|
2
2
|
import { MediaConflictError, MediaNotFoundError, MediaSourceImmutableError, MediaStillReferencedError, MediaValidationError, type MediaRecord, type MediaSource } from "./types.js";
|
|
3
3
|
import type { AssetBlobRepoPort, AssetRenditionRepoPort, BlobStorePort, MediaRepoPort } from "./ports.js";
|
|
4
4
|
export { MediaConflictError, MediaNotFoundError, MediaSourceImmutableError, MediaStillReferencedError, MediaValidationError, };
|
|
@@ -49,7 +49,9 @@ export declare function resolveWriteOnceSource(required: {
|
|
|
49
49
|
existing: MediaSource | undefined;
|
|
50
50
|
requestedSha256: string;
|
|
51
51
|
}, _optional?: Record<string, never>): MediaSource;
|
|
52
|
-
export declare function isValidMediaSlugFormat(
|
|
52
|
+
export declare function isValidMediaSlugFormat(requiredArgs: {
|
|
53
|
+
slug: string;
|
|
54
|
+
}, optionalArgs?: Record<string, never>): boolean;
|
|
53
55
|
export interface UploadMediaInput {
|
|
54
56
|
workspaceId: UUID;
|
|
55
57
|
bytes: Uint8Array;
|
|
@@ -62,8 +64,8 @@ export interface UploadMediaInput {
|
|
|
62
64
|
createdByPrincipal: string;
|
|
63
65
|
}
|
|
64
66
|
export interface UploadMediaDeps {
|
|
65
|
-
clock:
|
|
66
|
-
idGen:
|
|
67
|
+
clock: Clock;
|
|
68
|
+
idGen: IdGenerator;
|
|
67
69
|
mediaRepo: MediaRepoPort;
|
|
68
70
|
blobRepo: AssetBlobRepoPort;
|
|
69
71
|
renditionRepo: AssetRenditionRepoPort;
|
|
@@ -79,7 +81,7 @@ export interface UploadMediaOptional {
|
|
|
79
81
|
}
|
|
80
82
|
/**
|
|
81
83
|
* Validates, hashes, dedups by `(workspaceId, sha256)`, and writes a new media
|
|
82
|
-
* asset. Ordering
|
|
84
|
+
* asset. Ordering : bytes are written (or found already
|
|
83
85
|
* present via dedup) BEFORE the media row is saved — a media row never exists
|
|
84
86
|
* without corresponding bytes.
|
|
85
87
|
*
|
|
@@ -98,6 +100,7 @@ export interface UploadMediaOptional {
|
|
|
98
100
|
* overhead, not a scan) — plus {@link deriveUniqueMediaSlug}'s own O(n) in the number of prior
|
|
99
101
|
* same-base-slug collisions (see that function's doc).
|
|
100
102
|
* @overallScore 100
|
|
103
|
+
* See docs/decisions/DR-004-journaled-blob-gc.md.
|
|
101
104
|
*/
|
|
102
105
|
export declare function uploadMedia(required: UploadMediaRequired, optional?: UploadMediaOptional): Promise<{
|
|
103
106
|
media: MediaRecord;
|
|
@@ -167,11 +170,12 @@ export interface FindMediaByIdOrSlugRequired {
|
|
|
167
170
|
* falls through.
|
|
168
171
|
*
|
|
169
172
|
* Never throws (unlike {@link getMediaById}): every call site that needs this (an embed resolver, a
|
|
170
|
-
* public rendition route) already has its own
|
|
173
|
+
* public rendition route) already has its own -style non-throwing-miss contract, so this
|
|
171
174
|
* returns `null` on failure and lets the caller apply its own not-found handling rather than forcing
|
|
172
175
|
* one shape on every caller.
|
|
173
176
|
*
|
|
174
177
|
* @complexity O(1) — at most two indexed repo lookups, short-circuited on the first hit.
|
|
178
|
+
* See docs/decisions/DR-004-journaled-blob-gc.md.
|
|
175
179
|
*/
|
|
176
180
|
export declare function findMediaByIdOrSlug(required: FindMediaByIdOrSlugRequired, _optional?: Record<string, never>): Promise<MediaRecord | null>;
|
|
177
181
|
export interface UpdateMediaMetadataInput {
|
|
@@ -214,7 +218,7 @@ export interface UpdateMediaMetadataInput {
|
|
|
214
218
|
htmlAttributes?: string | null | undefined;
|
|
215
219
|
}
|
|
216
220
|
export interface UpdateMediaMetadataDeps {
|
|
217
|
-
clock:
|
|
221
|
+
clock: Clock;
|
|
218
222
|
mediaRepo: MediaRepoPort;
|
|
219
223
|
}
|
|
220
224
|
export interface UpdateMediaMetadataRequired {
|
|
@@ -225,7 +229,7 @@ export declare function updateMediaMetadata(required: UpdateMediaMetadataRequire
|
|
|
225
229
|
media: MediaRecord;
|
|
226
230
|
}>;
|
|
227
231
|
export interface TrashMediaDeps {
|
|
228
|
-
clock:
|
|
232
|
+
clock: Clock;
|
|
229
233
|
mediaRepo: MediaRepoPort;
|
|
230
234
|
}
|
|
231
235
|
export interface TrashMediaRequired {
|
|
@@ -257,7 +261,7 @@ export interface PurgeMediaDeps {
|
|
|
257
261
|
* that doesn't wire a clock into `PurgeMediaDeps` keeps compiling and
|
|
258
262
|
* behaving correctly unchanged.
|
|
259
263
|
*/
|
|
260
|
-
clock?:
|
|
264
|
+
clock?: Clock | undefined;
|
|
261
265
|
}
|
|
262
266
|
export interface PurgeMediaRequired {
|
|
263
267
|
deps: PurgeMediaDeps;
|
|
@@ -309,7 +313,7 @@ export interface MediaRowCleanupDeps {
|
|
|
309
313
|
blobRepo: AssetBlobRepoPort;
|
|
310
314
|
renditionRepo: AssetRenditionRepoPort;
|
|
311
315
|
/** Optional — see `PurgeMediaDeps.clock`'s identical doc. */
|
|
312
|
-
clock?:
|
|
316
|
+
clock?: Clock | undefined;
|
|
313
317
|
}
|
|
314
318
|
export interface RollbackUploadedMediaRequired {
|
|
315
319
|
deps: MediaRowCleanupDeps;
|