@jini-ai/cms 0.3.4 → 0.3.6
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/README.md +22 -17
- package/dist/content-types/agent-tools.d.ts.map +1 -1
- package/dist/content-types/agent-tools.js +1 -1
- package/dist/content-types/agent-tools.js.map +1 -1
- package/dist/content-types/errors.d.ts +19 -1
- package/dist/content-types/errors.d.ts.map +1 -1
- package/dist/content-types/errors.js +22 -1
- package/dist/content-types/errors.js.map +1 -1
- package/dist/content-types/index-provisioning.d.ts +56 -10
- package/dist/content-types/index-provisioning.d.ts.map +1 -1
- package/dist/content-types/index-provisioning.js +86 -13
- package/dist/content-types/index-provisioning.js.map +1 -1
- package/dist/content-types/index.d.ts +15 -3
- package/dist/content-types/index.d.ts.map +1 -1
- package/dist/content-types/index.js +14 -2
- package/dist/content-types/index.js.map +1 -1
- package/dist/content-types/lifecycle.js +3 -3
- package/dist/content-types/lifecycle.js.map +1 -1
- package/dist/content-types/types.d.ts +64 -3
- package/dist/content-types/types.d.ts.map +1 -1
- package/dist/content-types/types.js +67 -3
- package/dist/content-types/types.js.map +1 -1
- package/dist/content-types/write-service.d.ts +18 -3
- package/dist/content-types/write-service.d.ts.map +1 -1
- package/dist/content-types/write-service.js +28 -2
- package/dist/content-types/write-service.js.map +1 -1
- package/dist/core/ports.d.ts +12 -2
- package/dist/core/ports.d.ts.map +1 -1
- package/dist/entries/field-validation.d.ts.map +1 -1
- package/dist/entries/field-validation.js +96 -1
- package/dist/entries/field-validation.js.map +1 -1
- package/dist/entries/write-service.d.ts +14 -0
- package/dist/entries/write-service.d.ts.map +1 -1
- package/dist/entries/write-service.js +2 -2
- package/dist/entries/write-service.js.map +1 -1
- package/dist/identity/admin-crud-service.d.ts +28 -0
- package/dist/identity/admin-crud-service.d.ts.map +1 -1
- package/dist/identity/admin-crud-service.js +30 -0
- package/dist/identity/admin-crud-service.js.map +1 -1
- package/dist/identity/auth-service.d.ts +32 -1
- package/dist/identity/auth-service.d.ts.map +1 -1
- package/dist/identity/auth-service.js +44 -15
- package/dist/identity/auth-service.js.map +1 -1
- package/dist/identity/index.d.ts +4 -1
- package/dist/identity/index.d.ts.map +1 -1
- package/dist/identity/index.js +4 -1
- package/dist/identity/index.js.map +1 -1
- package/dist/identity/seed.d.ts +14 -2
- package/dist/identity/seed.d.ts.map +1 -1
- package/dist/identity/seed.js +197 -45
- package/dist/identity/seed.js.map +1 -1
- package/dist/media/blob-store.fs.d.ts +7 -0
- package/dist/media/blob-store.fs.d.ts.map +1 -1
- package/dist/media/blob-store.fs.js +12 -2
- package/dist/media/blob-store.fs.js.map +1 -1
- package/dist/media/content-type-sniffer.d.ts +1 -1
- package/dist/media/content-type-sniffer.d.ts.map +1 -1
- package/dist/media/content-type-sniffer.js +52 -4
- package/dist/media/content-type-sniffer.js.map +1 -1
- package/dist/media/html-attributes.d.ts +90 -0
- package/dist/media/html-attributes.d.ts.map +1 -0
- package/dist/media/html-attributes.js +161 -0
- package/dist/media/html-attributes.js.map +1 -0
- package/dist/media/image-transformer.sharp.d.ts +35 -1
- package/dist/media/image-transformer.sharp.d.ts.map +1 -1
- package/dist/media/image-transformer.sharp.js +44 -2
- package/dist/media/image-transformer.sharp.js.map +1 -1
- package/dist/media/index.d.ts +2 -1
- package/dist/media/index.d.ts.map +1 -1
- package/dist/media/index.js +2 -1
- package/dist/media/index.js.map +1 -1
- package/dist/media/media-service.d.ts +93 -1
- package/dist/media/media-service.d.ts.map +1 -1
- package/dist/media/media-service.js +254 -7
- package/dist/media/media-service.js.map +1 -1
- package/dist/media/ports.d.ts +8 -1
- package/dist/media/ports.d.ts.map +1 -1
- package/dist/media/rendition-service.d.ts +7 -1
- package/dist/media/rendition-service.d.ts.map +1 -1
- package/dist/media/rendition-service.js +10 -4
- package/dist/media/rendition-service.js.map +1 -1
- package/dist/media/repo.memory.d.ts +4 -0
- package/dist/media/repo.memory.d.ts.map +1 -1
- package/dist/media/repo.memory.js +3 -0
- package/dist/media/repo.memory.js.map +1 -1
- package/dist/media/tool-registrations.d.ts +6 -2
- package/dist/media/tool-registrations.d.ts.map +1 -1
- package/dist/media/tool-registrations.js +19 -2
- package/dist/media/tool-registrations.js.map +1 -1
- package/dist/media/types.d.ts +26 -0
- package/dist/media/types.d.ts.map +1 -1
- package/dist/media/types.js.map +1 -1
- package/dist/navigation/agent-tools.d.ts.map +1 -1
- package/dist/navigation/agent-tools.js +8 -1
- package/dist/navigation/agent-tools.js.map +1 -1
- package/dist/navigation/index.d.ts +10 -1
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +10 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/menu-service.d.ts +60 -4
- package/dist/navigation/menu-service.d.ts.map +1 -1
- package/dist/navigation/menu-service.js +106 -8
- package/dist/navigation/menu-service.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Server-side allowlist for `MediaRecord.htmlAttributes` (2026-09-07) — the enforcement half
|
|
3
|
+
* of a validator that ALSO has a browser-side copy in Tovu's admin
|
|
4
|
+
* (`apps/admin/src/features/media/rules.ts`, `parseMediaHtmlAttributes` and friends).
|
|
5
|
+
*
|
|
6
|
+
* Deliberately duplicated, not shared via one imported module, for the same reason this codebase
|
|
7
|
+
* already hand-copies `DEFAULT_ALLOWED_MIME_TYPES` into Tovu's `FILE_HANDLER_ALLOWED_MIME_TYPES`/
|
|
8
|
+
* `IMPORTABLE_CONTENT_TYPES`: `@jini-ai/cms/media`'s barrel (`index.ts`) re-exports files that
|
|
9
|
+
* import real Node built-ins (`node:crypto` in `media-service.ts`, `node:fs` in
|
|
10
|
+
* `blob-store.fs.ts`, native `sharp` bindings in `image-transformer.sharp.ts`) — importing this
|
|
11
|
+
* subpath from a browser-bundled Vite app (Tovu's `apps/admin`) risks pulling those into the admin
|
|
12
|
+
* SPA's bundle even if only this one pure function is actually used, and this admin's own copy
|
|
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 Tovu's `apps/website` render path, both pure
|
|
15
|
+
* backend code) can safely import from `@jini-ai/cms/media` instead of re-deriving the rules a
|
|
16
|
+
* third time.
|
|
17
|
+
*
|
|
18
|
+
* THE RULES THEMSELVES ARE NOT REDESIGNED HERE — this is a verbatim port of the admin copy's logic
|
|
19
|
+
* (allowlist membership, `on*`/`javascript:` precedence, the tokenizer, the four rejection reasons).
|
|
20
|
+
* Extend `MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES` in BOTH copies together if a new attribute is ever
|
|
21
|
+
* needed — that is the one piece of drift risk this duplication accepts, the same risk every other
|
|
22
|
+
* hand-copied list in this codebase already accepts.
|
|
23
|
+
*
|
|
24
|
+
* This is a SECURITY boundary, not a syntax convenience: media metadata is authored in Tovu's admin
|
|
25
|
+
* but rendered on the public site, so a free-text HTML-attribute passthrough is a stored-XSS vector
|
|
26
|
+
* (`onerror`, `onclick`, `style`, `href="javascript:"`, any `on*` handler) the moment it reaches a
|
|
27
|
+
* public page. A client-side-only check is not a control, since the API accepts whatever a caller
|
|
28
|
+
* sends — this file is what makes the write path (`updateMediaMetadata`) and the render path (a
|
|
29
|
+
* host's own tag-building code) actually enforce it, not just the admin form.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Exact-match attribute names the allowlist accepts beyond the open-ended `data-`/`aria-` prefix
|
|
33
|
+
* families (checked separately in {@link isAllowedMediaHtmlAttributeName}). Picked for the owner's
|
|
34
|
+
* stated near-term uses (animations, custom WebMCP hooks) plus the standard `<img>`/`<video>`
|
|
35
|
+
* attributes those uses actually need; extend this list, not the parser, when a new one is needed —
|
|
36
|
+
* and keep it in sync with the identical constant in Tovu's admin copy (see this file's header).
|
|
37
|
+
*/
|
|
38
|
+
export const MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES = [
|
|
39
|
+
"loading",
|
|
40
|
+
"decoding",
|
|
41
|
+
"playsinline",
|
|
42
|
+
"muted",
|
|
43
|
+
"loop",
|
|
44
|
+
"autoplay",
|
|
45
|
+
"poster",
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* The shape EVERY accepted attribute name must have, checked before allowlist membership
|
|
49
|
+
* (2026-09-07 stored-XSS fix; keep identical in the admin copy).
|
|
50
|
+
*
|
|
51
|
+
* The `data-`/`aria-` families below are open-ended by design — no fixed suffix list — so the
|
|
52
|
+
* prefix check alone accepted whatever characters {@link HTML_ATTRIBUTE_TOKEN}'s name class
|
|
53
|
+
* (`[^\s="']+`, which excludes only whitespace, `=` and quotes) let through. `<`, `>` and `/` are
|
|
54
|
+
* all legal in that class, and a renderer templating a name into ` name="value"` escapes only the
|
|
55
|
+
* VALUE — so a stored `data-x><svg/onload=alert(1)` closed the `<img>` and opened a live
|
|
56
|
+
* `<svg onload>` on the public page. Constraining the NAME to the characters a real HTML attribute
|
|
57
|
+
* name can contain is what makes "the value is escaped" sufficient: no accepted name can carry a
|
|
58
|
+
* character that terminates a tag or starts an attribute.
|
|
59
|
+
*
|
|
60
|
+
* Deliberately narrower than the HTML spec's own name production (which permits far more): this
|
|
61
|
+
* field exists for `data-*`/`aria-*` hooks and a short list of standard `<img>`/`<video>`
|
|
62
|
+
* attributes, every one of which fits `[a-z][a-z0-9-]*`. Widening this is a deliberate decision,
|
|
63
|
+
* not something a new allowlist entry should need.
|
|
64
|
+
*/
|
|
65
|
+
const MEDIA_HTML_ATTRIBUTE_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
66
|
+
/** Whether `name` is on the allowlist — a well-formed attribute name
|
|
67
|
+
* ({@link MEDIA_HTML_ATTRIBUTE_NAME_PATTERN}) that is either an exact match against
|
|
68
|
+
* {@link MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES} or carries a `data-`/`aria-` prefix (both open-ended
|
|
69
|
+
* families with no fixed suffix list). Case-insensitive: HTML attribute names are themselves
|
|
70
|
+
* case-insensitive, and an operator typing `DATA-FOO` should not slip past a lowercase-only check.
|
|
71
|
+
*
|
|
72
|
+
* @complexity O(n) in the name's length (one anchored regex test), then O(1) — one prefix check,
|
|
73
|
+
* one fixed-length array lookup.
|
|
74
|
+
*/
|
|
75
|
+
export function isAllowedMediaHtmlAttributeName(name) {
|
|
76
|
+
const lower = name.toLowerCase();
|
|
77
|
+
if (!MEDIA_HTML_ATTRIBUTE_NAME_PATTERN.test(lower))
|
|
78
|
+
return false;
|
|
79
|
+
if (lower.startsWith("data-") || lower.startsWith("aria-"))
|
|
80
|
+
return true;
|
|
81
|
+
return MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES.includes(lower);
|
|
82
|
+
}
|
|
83
|
+
/** Matches one `name`, or one `name="value"`/`name='value'`/`name=value` pair — the same loose
|
|
84
|
+
* shape real HTML attribute syntax allows, since that is the syntax an operator typing this field
|
|
85
|
+
* would naturally reach for. */
|
|
86
|
+
const HTML_ATTRIBUTE_TOKEN = /([^\s="']+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s"']+)))?/g;
|
|
87
|
+
/** Classifies one already-tokenized `name`/`value` pair. Split out of {@link parseMediaHtmlAttributes}
|
|
88
|
+
* so each rejection reason is its own directly testable branch.
|
|
89
|
+
*
|
|
90
|
+
* Order matters: `on*`/`javascript:` are checked BEFORE allowlist membership, so a rejected
|
|
91
|
+
* `onerror="..."` always reports as `event-handler` (the more specific, more actionable reason)
|
|
92
|
+
* rather than the generic `disallowed-name`.
|
|
93
|
+
*
|
|
94
|
+
* @complexity O(1) — three fixed checks against one already-extracted token.
|
|
95
|
+
*/
|
|
96
|
+
function classifyMediaHtmlAttributeToken(name, value) {
|
|
97
|
+
if (name.startsWith("on"))
|
|
98
|
+
return { reason: "event-handler", attribute: name };
|
|
99
|
+
if (value.trim().toLowerCase().startsWith("javascript:"))
|
|
100
|
+
return { reason: "javascript-url", attribute: name };
|
|
101
|
+
if (!isAllowedMediaHtmlAttributeName(name))
|
|
102
|
+
return { reason: "disallowed-name", attribute: name };
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Parses the `htmlAttributes` field's free text (`name="value" name2="value2"`, or a bare boolean
|
|
107
|
+
* `name`) into a validated attribute map, rejecting anything not on the allowlist. Nothing here
|
|
108
|
+
* tries to sanitize or escape an otherwise-disallowed name into something safe — it is rejected
|
|
109
|
+
* outright, and the caller must show the reason (not silently drop it) so an operator can tell a
|
|
110
|
+
* typo from a hard "no".
|
|
111
|
+
*
|
|
112
|
+
* @complexity Time O(n) in `text`'s length (one regex pass over it), space O(k) for k parsed
|
|
113
|
+
* attributes.
|
|
114
|
+
*/
|
|
115
|
+
export function parseMediaHtmlAttributes(text) {
|
|
116
|
+
const trimmed = text.trim();
|
|
117
|
+
if (trimmed === "")
|
|
118
|
+
return { attributes: {}, error: null };
|
|
119
|
+
const attributes = {};
|
|
120
|
+
HTML_ATTRIBUTE_TOKEN.lastIndex = 0;
|
|
121
|
+
let consumed = 0;
|
|
122
|
+
let match;
|
|
123
|
+
while ((match = HTML_ATTRIBUTE_TOKEN.exec(trimmed)) !== null) {
|
|
124
|
+
// Non-whitespace text between the previous match and this one is a fragment the token pattern
|
|
125
|
+
// could not parse as a name (e.g. a stray quote) — reported once, at the first gap, rather
|
|
126
|
+
// than silently skipped.
|
|
127
|
+
const skipped = trimmed.slice(consumed, match.index);
|
|
128
|
+
if (skipped.trim() !== "")
|
|
129
|
+
return { attributes: {}, error: { reason: "malformed", attribute: skipped.trim() } };
|
|
130
|
+
consumed = match.index + match[0].length;
|
|
131
|
+
const name = match[1].toLowerCase();
|
|
132
|
+
const value = match[2] ?? match[3] ?? match[4] ?? "";
|
|
133
|
+
const rejection = classifyMediaHtmlAttributeToken(name, value);
|
|
134
|
+
if (rejection)
|
|
135
|
+
return { attributes: {}, error: rejection };
|
|
136
|
+
attributes[name] = value;
|
|
137
|
+
}
|
|
138
|
+
const trailing = trimmed.slice(consumed);
|
|
139
|
+
if (trailing.trim() !== "")
|
|
140
|
+
return { attributes: {}, error: { reason: "malformed", attribute: trailing.trim() } };
|
|
141
|
+
return { attributes, error: null };
|
|
142
|
+
}
|
|
143
|
+
/** Formats a {@link MediaHtmlAttributeError} into a plain-English message naming the rejected
|
|
144
|
+
* attribute (owner requirement) — the server-side, non-localized counterpart to the admin's own
|
|
145
|
+
* `describeMediaHtmlAttributeError` (which additionally runs the message through `MEDIA_DICT` for
|
|
146
|
+
* the operator's locale). Domain errors elsewhere in this file (`MediaValidationError`'s other
|
|
147
|
+
* messages) are likewise always plain English, so this matches that existing convention rather
|
|
148
|
+
* than inventing a localized-error contract this service has never had.
|
|
149
|
+
*
|
|
150
|
+
* @complexity O(1).
|
|
151
|
+
*/
|
|
152
|
+
export function describeMediaHtmlAttributeError(error) {
|
|
153
|
+
if (error.reason === "event-handler")
|
|
154
|
+
return `event handler attributes like '${error.attribute}' are not allowed`;
|
|
155
|
+
if (error.reason === "javascript-url")
|
|
156
|
+
return `'${error.attribute}' cannot use a javascript: value`;
|
|
157
|
+
if (error.reason === "malformed")
|
|
158
|
+
return `could not parse HTML attributes near '${error.attribute}'`;
|
|
159
|
+
return `'${error.attribute}' is not an allowed HTML attribute`;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=html-attributes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-attributes.js","sourceRoot":"","sources":["../../src/media/html-attributes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAG;IAChD,SAAS;IACT,UAAU;IACV,aAAa;IACb,OAAO;IACP,MAAM;IACN,UAAU;IACV,QAAQ;CACA,CAAC;AAEX;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,iCAAiC,GAAG,mBAAmB,CAAC;AAE9D;;;;;;;;GAQG;AACH,MAAM,UAAU,+BAA+B,CAAC,IAAY;IAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,IAAI,CAAC,iCAAiC,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACjE,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACxE,OAAQ,kCAAwD,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACnF,CAAC;AAyBD;;iCAEiC;AACjC,MAAM,oBAAoB,GAAG,sDAAsD,CAAC;AAEpF;;;;;;;;GAQG;AACH,SAAS,+BAA+B,CAAC,IAAY,EAAE,KAAa;IAClE,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC/E,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAC/G,IAAI,CAAC,+BAA+B,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAClG,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAE3D,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,oBAAoB,CAAC,SAAS,GAAG,CAAC,CAAC;IACnC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7D,8FAA8F;QAC9F,2FAA2F;QAC3F,yBAAyB;QACzB,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACrD,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC;QAChH,QAAQ,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QAEzC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,MAAM,SAAS,GAAG,+BAA+B,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC/D,IAAI,SAAS;YAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAC3D,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IAC3B,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC;IAClH,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,+BAA+B,CAAC,KAA8B;IAC5E,IAAI,KAAK,CAAC,MAAM,KAAK,eAAe;QAAE,OAAO,kCAAkC,KAAK,CAAC,SAAS,mBAAmB,CAAC;IAClH,IAAI,KAAK,CAAC,MAAM,KAAK,gBAAgB;QAAE,OAAO,IAAI,KAAK,CAAC,SAAS,kCAAkC,CAAC;IACpG,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW;QAAE,OAAO,yCAAyC,KAAK,CAAC,SAAS,GAAG,CAAC;IACrG,OAAO,IAAI,KAAK,CAAC,SAAS,oCAAoC,CAAC;AACjE,CAAC"}
|
|
@@ -24,8 +24,42 @@ export declare class ImageSourceCorruptError extends Error {
|
|
|
24
24
|
* `params.format` (via `sharp().toFormat(...)`, always — re-encode is
|
|
25
25
|
* unconditional).
|
|
26
26
|
*
|
|
27
|
+
* Animated sources (multi-frame GIF/WebP/APNG): loaded with `{ animated: true }` whenever the
|
|
28
|
+
* target format can carry animation (`ANIMATION_CAPABLE_FORMATS`), so `.resize()` and `.toFormat()`
|
|
29
|
+
* operate on every frame instead of silently decoding only frame 0. This is safe to request
|
|
30
|
+
* unconditionally for a genuinely single-frame source too — verified empirically against this
|
|
31
|
+
* package's pinned `sharp@0.35.3`: a static image loaded with `{ animated: true }` produces
|
|
32
|
+
* byte-identical dimensions/output to loading it without the option (sharp resolves `nPages` to 1
|
|
33
|
+
* from the format's own page count, there is no multi-page metadata to read all of).
|
|
34
|
+
*
|
|
35
|
+
* When the target format CANNOT carry animation (jpeg/png), `{ animated: true }` is deliberately
|
|
36
|
+
* NOT passed — verified empirically that doing so anyway is worse than a plain flatten: sharp
|
|
37
|
+
* writes the whole multi-frame "toilet roll" (all frames stacked vertically) out as one tall,
|
|
38
|
+
* visibly-corrupted static image rather than either a clean single frame or a real animated
|
|
39
|
+
* output. Loading without the option keeps sharp's default single-page decode, which already
|
|
40
|
+
* yields a normal frame-0 still — a deliberate, now-documented flatten instead of an accident.
|
|
41
|
+
*
|
|
42
|
+
* Multi-frame resize correctness: `sharp`'s native pipeline is already page-height-aware — the
|
|
43
|
+
* `width`/`height` passed to `.resize()` are resolved against the PER-FRAME `pageHeight`, not the
|
|
44
|
+
* full multi-frame canvas, and the crop/scale math is applied identically per frame (verified with
|
|
45
|
+
* a 4-frame fixture and an aggressive mismatched-aspect crop — each frame's content stayed inside
|
|
46
|
+
* its own frame boundary). No special-cased width-only or manual `pageHeight` recomputation is
|
|
47
|
+
* needed here; adding one would be redundant with (and could conflict with) sharp's own handling.
|
|
48
|
+
*
|
|
49
|
+
* Resource bounds: sharp's default `limitInputPixels` (~268M px, unconditional, not raised by
|
|
50
|
+
* `{ animated: true }`) is checked against the FULL decoded canvas — for a multi-page load that
|
|
51
|
+
* means `width * (pageHeight * pages)`, i.e. total pixels across all frames combined, not per
|
|
52
|
+
* frame. Turning on `{ animated: true }` therefore does not remove or widen that ceiling. It does
|
|
53
|
+
* mean legitimate large animated files now do proportionally more decode/re-encode work than the
|
|
54
|
+
* previous (buggy) frame-0-only path — expected, since fully processing an animated file is the
|
|
55
|
+
* point of this fix — bounded on the input side by `DEFAULT_MAX_UPLOAD_BYTES` (10 MiB compressed)
|
|
56
|
+
* before any of these bytes ever reach this transformer.
|
|
57
|
+
*
|
|
27
58
|
* @complexity O(pixels) — dominated by `sharp`'s native resize/encode work,
|
|
28
|
-
* outside this function's control.
|
|
59
|
+
* outside this function's control. For an animated source this now scales with total pixels across
|
|
60
|
+
* ALL frames combined (previously just frame 0) — see "Resource bounds" above for why this widened
|
|
61
|
+
* cost is bounded by the same pre-existing `limitInputPixels`/upload-size caps rather than being an
|
|
62
|
+
* unbounded new surface. Runs IN-PROCESS in this build (the
|
|
29
63
|
* out-of-process worker the original design calls for to protect the host from
|
|
30
64
|
* `sharp` OOMing is explicitly out of scope for this task — see
|
|
31
65
|
* `rendition-service.ts`'s file header).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-transformer.sharp.d.ts","sourceRoot":"","sources":["../../src/media/image-transformer.sharp.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"image-transformer.sharp.d.ts","sourceRoot":"","sources":["../../src/media/image-transformer.sharp.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAM9G,mGAAmG;AACnG,qBAAa,8BAA+B,SAAQ,KAAK;CAAG;AAE5D;;;;;;;;;;;;;GAaG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;CAAG;AA4CrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,qBAAa,qBAAsB,YAAW,oBAAoB;IAC1D,SAAS,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAoC3E"}
|
|
@@ -49,6 +49,13 @@ export class ImageTransformUnavailableError extends Error {
|
|
|
49
49
|
*/
|
|
50
50
|
export class ImageSourceCorruptError extends Error {
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* {@link TransformFormat} values that can actually carry multiple frames on output. GIF and WebP
|
|
54
|
+
* both support animated re-encoding; JPEG and PNG do not (this build's plain libvips PNG writer
|
|
55
|
+
* does not emit APNG). Gates the `{ animated: true }` load option below — see that call site's
|
|
56
|
+
* comment for why loading animated but encoding to a non-animated format must NOT happen.
|
|
57
|
+
*/
|
|
58
|
+
const ANIMATION_CAPABLE_FORMATS = new Set(["webp", "gif"]);
|
|
52
59
|
/**
|
|
53
60
|
* Lazily resolves the `sharp` module. See file header for why this is a
|
|
54
61
|
* deferred, untyped `require()` (via `createRequire`) rather than a
|
|
@@ -75,8 +82,42 @@ function loadSharpFactory() {
|
|
|
75
82
|
* `params.format` (via `sharp().toFormat(...)`, always — re-encode is
|
|
76
83
|
* unconditional).
|
|
77
84
|
*
|
|
85
|
+
* Animated sources (multi-frame GIF/WebP/APNG): loaded with `{ animated: true }` whenever the
|
|
86
|
+
* target format can carry animation (`ANIMATION_CAPABLE_FORMATS`), so `.resize()` and `.toFormat()`
|
|
87
|
+
* operate on every frame instead of silently decoding only frame 0. This is safe to request
|
|
88
|
+
* unconditionally for a genuinely single-frame source too — verified empirically against this
|
|
89
|
+
* package's pinned `sharp@0.35.3`: a static image loaded with `{ animated: true }` produces
|
|
90
|
+
* byte-identical dimensions/output to loading it without the option (sharp resolves `nPages` to 1
|
|
91
|
+
* from the format's own page count, there is no multi-page metadata to read all of).
|
|
92
|
+
*
|
|
93
|
+
* When the target format CANNOT carry animation (jpeg/png), `{ animated: true }` is deliberately
|
|
94
|
+
* NOT passed — verified empirically that doing so anyway is worse than a plain flatten: sharp
|
|
95
|
+
* writes the whole multi-frame "toilet roll" (all frames stacked vertically) out as one tall,
|
|
96
|
+
* visibly-corrupted static image rather than either a clean single frame or a real animated
|
|
97
|
+
* output. Loading without the option keeps sharp's default single-page decode, which already
|
|
98
|
+
* yields a normal frame-0 still — a deliberate, now-documented flatten instead of an accident.
|
|
99
|
+
*
|
|
100
|
+
* Multi-frame resize correctness: `sharp`'s native pipeline is already page-height-aware — the
|
|
101
|
+
* `width`/`height` passed to `.resize()` are resolved against the PER-FRAME `pageHeight`, not the
|
|
102
|
+
* full multi-frame canvas, and the crop/scale math is applied identically per frame (verified with
|
|
103
|
+
* a 4-frame fixture and an aggressive mismatched-aspect crop — each frame's content stayed inside
|
|
104
|
+
* its own frame boundary). No special-cased width-only or manual `pageHeight` recomputation is
|
|
105
|
+
* needed here; adding one would be redundant with (and could conflict with) sharp's own handling.
|
|
106
|
+
*
|
|
107
|
+
* Resource bounds: sharp's default `limitInputPixels` (~268M px, unconditional, not raised by
|
|
108
|
+
* `{ animated: true }`) is checked against the FULL decoded canvas — for a multi-page load that
|
|
109
|
+
* means `width * (pageHeight * pages)`, i.e. total pixels across all frames combined, not per
|
|
110
|
+
* frame. Turning on `{ animated: true }` therefore does not remove or widen that ceiling. It does
|
|
111
|
+
* mean legitimate large animated files now do proportionally more decode/re-encode work than the
|
|
112
|
+
* previous (buggy) frame-0-only path — expected, since fully processing an animated file is the
|
|
113
|
+
* point of this fix — bounded on the input side by `DEFAULT_MAX_UPLOAD_BYTES` (10 MiB compressed)
|
|
114
|
+
* before any of these bytes ever reach this transformer.
|
|
115
|
+
*
|
|
78
116
|
* @complexity O(pixels) — dominated by `sharp`'s native resize/encode work,
|
|
79
|
-
* outside this function's control.
|
|
117
|
+
* outside this function's control. For an animated source this now scales with total pixels across
|
|
118
|
+
* ALL frames combined (previously just frame 0) — see "Resource bounds" above for why this widened
|
|
119
|
+
* cost is bounded by the same pre-existing `limitInputPixels`/upload-size caps rather than being an
|
|
120
|
+
* unbounded new surface. Runs IN-PROCESS in this build (the
|
|
80
121
|
* out-of-process worker the original design calls for to protect the host from
|
|
81
122
|
* `sharp` OOMing is explicitly out of scope for this task — see
|
|
82
123
|
* `rendition-service.ts`'s file header).
|
|
@@ -90,6 +131,7 @@ function loadSharpFactory() {
|
|
|
90
131
|
export class SharpImageTransformer {
|
|
91
132
|
async transform(input) {
|
|
92
133
|
const sharpFactory = loadSharpFactory();
|
|
134
|
+
const preserveAnimation = ANIMATION_CAPABLE_FORMATS.has(input.params.format);
|
|
93
135
|
// Pipeline construction through `toBuffer()` all live in one try/catch: any of these steps can
|
|
94
136
|
// reject on bad SOURCE bytes (`sharp`'s decode is lazy — a malformed file often only surfaces
|
|
95
137
|
// once a downstream operation like `toBuffer()` actually reads it), and every such rejection
|
|
@@ -97,7 +139,7 @@ export class SharpImageTransformer {
|
|
|
97
139
|
// call in the chain actually threw. See {@link ImageSourceCorruptError}'s own doc for why this is
|
|
98
140
|
// named/rethrown rather than left as an undifferentiated `sharp` error.
|
|
99
141
|
try {
|
|
100
|
-
let pipeline = sharpFactory(Buffer.from(input.bytes));
|
|
142
|
+
let pipeline = sharpFactory(Buffer.from(input.bytes), preserveAnimation ? { animated: true } : undefined);
|
|
101
143
|
if (input.params.width !== undefined || input.params.height !== undefined) {
|
|
102
144
|
pipeline = pipeline.resize(input.params.width, input.params.height, {
|
|
103
145
|
fit: input.params.fit ?? "cover",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-transformer.sharp.js","sourceRoot":"","sources":["../../src/media/image-transformer.sharp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"image-transformer.sharp.js","sourceRoot":"","sources":["../../src/media/image-transformer.sharp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,mGAAmG;AACnG,MAAM,OAAO,8BAA+B,SAAQ,KAAK;CAAG;AAE5D;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;CAAG;AAcrD;;;;;GAKG;AACH,MAAM,yBAAyB,GAAiC,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AAEzF;;;;;;;;GAQG;AACH,SAAS,gBAAgB;IACvB,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,OAAO,CAAiB,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,8BAA8B,CACtC,2FAA2F;YACzF,oFAAoF;YACpF,0FAA0F;YAC1F,qBAAqB,CACxB,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,MAAM,OAAO,qBAAqB;IAChC,KAAK,CAAC,SAAS,CAAC,KAA0B;QACxC,MAAM,YAAY,GAAG,gBAAgB,EAAE,CAAC;QACxC,MAAM,iBAAiB,GAAG,yBAAyB,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAE7E,+FAA+F;QAC/F,8FAA8F;QAC9F,6FAA6F;QAC7F,gGAAgG;QAChG,kGAAkG;QAClG,wEAAwE;QACxE,IAAI,CAAC;YACH,IAAI,QAAQ,GAAG,YAAY,CACzB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EACxB,iBAAiB,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CACnD,CAAC;YAEF,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC1E,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE;oBAClE,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,OAAO;iBACjC,CAAC,CAAC;YACL,CAAC;YAED,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAClD,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAE5C,OAAO;gBACL,KAAK,EAAE,IAAI,UAAU,CAAC,SAAS,CAAC;gBAChC,WAAW,EAAE,sBAAsB,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;aACzD,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,MAAM,IAAI,uBAAuB,CAC/B,oEAAoE,KAAK,CAAC,MAAM,CAAC,MAAM,OAAO,MAAM,EAAE,CACvG,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
|
package/dist/media/index.d.ts
CHANGED
|
@@ -15,12 +15,13 @@ export type { MediaStatus, MediaSource, MediaRecord, AssetBlobStatus, AssetBlobR
|
|
|
15
15
|
export { MediaNotFoundError, MediaValidationError, MediaConflictError, MediaSourceImmutableError, MediaStillReferencedError, } from "./types.js";
|
|
16
16
|
export type { MediaRepoPort, AssetBlobRepoPort, AssetRenditionRepoPort, BlobStorePort, BlobGcJournalRepoPort, PutBlobInput, TransformDefinitionRepoPort, } from "./ports.js";
|
|
17
17
|
export { computeBlobStorageKey } from "./blob-key.js";
|
|
18
|
+
export { MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES, isAllowedMediaHtmlAttributeName, parseMediaHtmlAttributes, describeMediaHtmlAttributeError, type MediaHtmlAttributeRejectionReason, type MediaHtmlAttributeError, type ParsedMediaHtmlAttributes, } from "./html-attributes.js";
|
|
18
19
|
export { InMemoryMediaRepo, InMemoryAssetBlobRepo, InMemoryAssetRenditionRepo, InMemoryBlobGcJournalRepo, InMemoryTransformDefinitionRepo, } from "./repo.memory.js";
|
|
19
20
|
export { withSha256Lock } from "./blob-gc-lock.js";
|
|
20
21
|
export { DEFAULT_GC_GRACE_MS, resolveGcGraceMs, isBlobUnreferenced, tombstoneBlobIfUnreferenced, runBlobGcDeletePass, runBlobGcUnlinkPass, runBlobGcCycle, runMonthlyOrphanSweepStub, } from "./blob-gc.js";
|
|
21
22
|
export { InMemoryBlobStore } from "./blob-store.memory.js";
|
|
22
23
|
export { LocalFsBlobStore, type LocalFsBlobStoreDeps } from "./blob-store.fs.js";
|
|
23
|
-
export { DEFAULT_MAX_UPLOAD_BYTES, DEFAULT_ALLOWED_MIME_TYPES, resolveWriteOnceSource, uploadMedia, listMedia, getMediaById, updateMediaMetadata, trashMedia, purgeMedia, type UploadMediaInput, type UploadMediaDeps, type UpdateMediaMetadataInput, } from "./media-service.js";
|
|
24
|
+
export { DEFAULT_MAX_UPLOAD_BYTES, DEFAULT_ALLOWED_MIME_TYPES, resolveWriteOnceSource, uploadMedia, listMedia, getMediaById, findMediaByIdOrSlug, updateMediaMetadata, trashMedia, purgeMedia, type UploadMediaInput, type UploadMediaDeps, type UpdateMediaMetadataInput, type FindMediaByIdOrSlugRequired, } from "./media-service.js";
|
|
24
25
|
export type { TransformFit, TransformFormat, TransformParams, TransformDefinitionRecord, } from "./transform-types.js";
|
|
25
26
|
export { TransformValidationError, mimeForTransformFormat, MAX_TRANSFORM_DIMENSION_PX } from "./transform-types.js";
|
|
26
27
|
export { withRenditionLock, withTransformRegistryLock } from "./transform-lock.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,YAAY,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACX,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,qBAAqB,EACrB,YAAY,EACZ,2BAA2B,GAC5B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,0BAA0B,EAC1B,yBAAyB,EACzB,+BAA+B,GAChC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,2BAA2B,EAC3B,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,yBAAyB,GAC1B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAEjF,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,WAAW,EACX,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,UAAU,EACV,UAAU,EACV,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,wBAAwB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"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"}
|
package/dist/media/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export { MediaNotFoundError, MediaValidationError, MediaConflictError, MediaSourceImmutableError, MediaStillReferencedError, } from "./types.js";
|
|
2
2
|
export { computeBlobStorageKey } from "./blob-key.js";
|
|
3
|
+
export { MEDIA_HTML_ATTRIBUTE_ALLOWED_NAMES, isAllowedMediaHtmlAttributeName, parseMediaHtmlAttributes, describeMediaHtmlAttributeError, } from "./html-attributes.js";
|
|
3
4
|
export { InMemoryMediaRepo, InMemoryAssetBlobRepo, InMemoryAssetRenditionRepo, InMemoryBlobGcJournalRepo, InMemoryTransformDefinitionRepo, } from "./repo.memory.js";
|
|
4
5
|
export { withSha256Lock } from "./blob-gc-lock.js";
|
|
5
6
|
export { DEFAULT_GC_GRACE_MS, resolveGcGraceMs, isBlobUnreferenced, tombstoneBlobIfUnreferenced, runBlobGcDeletePass, runBlobGcUnlinkPass, runBlobGcCycle, runMonthlyOrphanSweepStub, } from "./blob-gc.js";
|
|
6
7
|
export { InMemoryBlobStore } from "./blob-store.memory.js";
|
|
7
8
|
export { LocalFsBlobStore } from "./blob-store.fs.js";
|
|
8
|
-
export { DEFAULT_MAX_UPLOAD_BYTES, DEFAULT_ALLOWED_MIME_TYPES, resolveWriteOnceSource, uploadMedia, listMedia, getMediaById, updateMediaMetadata, trashMedia, purgeMedia, } from "./media-service.js";
|
|
9
|
+
export { DEFAULT_MAX_UPLOAD_BYTES, DEFAULT_ALLOWED_MIME_TYPES, resolveWriteOnceSource, uploadMedia, listMedia, getMediaById, findMediaByIdOrSlug, updateMediaMetadata, trashMedia, purgeMedia, } from "./media-service.js";
|
|
9
10
|
export { TransformValidationError, mimeForTransformFormat, MAX_TRANSFORM_DIMENSION_PX } from "./transform-types.js";
|
|
10
11
|
export { withRenditionLock, withTransformRegistryLock } from "./transform-lock.js";
|
|
11
12
|
export { registerTransform, getLatestTransformDefinition, isLatestTransformVersion, isReferencedByPublishedContent, } from "./transform-registry.js";
|
package/dist/media/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAuBA,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,YAAY,CAAC;AAYpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,0BAA0B,EAC1B,yBAAyB,EACzB,+BAA+B,GAChC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,2BAA2B,EAC3B,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,yBAAyB,GAC1B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAA6B,MAAM,oBAAoB,CAAC;AAEjF,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,WAAW,EACX,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,UAAU,EACV,UAAU,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAuBA,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,YAAY,CAAC;AAYpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,OAAO,EACL,kCAAkC,EAClC,+BAA+B,EAC/B,wBAAwB,EACxB,+BAA+B,GAIhC,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,0BAA0B,EAC1B,yBAAyB,EACzB,+BAA+B,GAChC,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,2BAA2B,EAC3B,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,yBAAyB,GAC1B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAA6B,MAAM,oBAAoB,CAAC;AAEjF,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,WAAW,EACX,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,EACV,UAAU,GAKX,MAAM,oBAAoB,CAAC;AAe5B,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEpH,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAEnF,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,wBAAwB,EACxB,8BAA8B,GAG/B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,qBAAqB,GAItB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAElE,OAAO,EAAE,qBAAqB,EAAE,8BAA8B,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE9H,gFAAgF;AAChF,8EAA8E;AAC9E,8EAA8E;AAC9E,uBAAuB;AACvB,gFAAgF;AAChF,OAAO,EAAE,gBAAgB,EAA2B,MAAM,2BAA2B,CAAC;AAEtF,sGAAsG;AACtG,OAAO,EACL,qBAAqB,GAItB,MAAM,kBAAkB,CAAC;AAE1B;;;;;;GAMG;AACH,OAAO,EACL,uBAAuB,EACvB,gBAAgB,GAEjB,MAAM,yBAAyB,CAAC"}
|
|
@@ -10,6 +10,14 @@ export declare const DEFAULT_MAX_UPLOAD_BYTES: number;
|
|
|
10
10
|
* that sanitizer is not built in this pass, so SVG upload is rejected rather
|
|
11
11
|
* than accepted unsanitized.
|
|
12
12
|
*
|
|
13
|
+
* `image/avif` (owner-directed, 2026-09-06): a fifth still-image type, on the
|
|
14
|
+
* same footing as the four above — `content-type-sniffer.ts` identifies it by
|
|
15
|
+
* ISO-BMFF brand and the installed `sharp`/libvips build decodes it, so it
|
|
16
|
+
* flows through the ordinary transform/rendition pipeline and is re-encoded
|
|
17
|
+
* like any other image. Adding it required a matching sniffer fix, not just
|
|
18
|
+
* this line: AVIF shares MP4's `ftyp` container tag, so before that fix an
|
|
19
|
+
* AVIF was sniffed as `video/mp4` and served as an unplayable video.
|
|
20
|
+
*
|
|
13
21
|
* `video/mp4`/`video/webm` (owner-directed, 2026-08-24): the two formats
|
|
14
22
|
* `content-type-sniffer.ts` already recognizes by magic bytes. Unlike the four
|
|
15
23
|
* image types above, an accepted video is never re-encoded — there is no
|
|
@@ -86,7 +94,8 @@ export interface UploadMediaOptional {
|
|
|
86
94
|
*
|
|
87
95
|
* @complexity O(1) — one hash, one blob lookup, at most one blob write, one
|
|
88
96
|
* media write, one rendition write (the lock itself adds O(1) scheduling
|
|
89
|
-
* overhead, not a scan)
|
|
97
|
+
* overhead, not a scan) — plus {@link deriveUniqueMediaSlug}'s own O(n) in the number of prior
|
|
98
|
+
* same-base-slug collisions (see that function's doc).
|
|
90
99
|
* @overallScore 100
|
|
91
100
|
*/
|
|
92
101
|
export declare function uploadMedia(required: UploadMediaRequired, optional?: UploadMediaOptional): Promise<{
|
|
@@ -131,6 +140,39 @@ export interface GetMediaByIdRequired {
|
|
|
131
140
|
export declare function getMediaById(required: GetMediaByIdRequired, _optional?: Record<string, never>): Promise<{
|
|
132
141
|
media: MediaRecord;
|
|
133
142
|
}>;
|
|
143
|
+
export interface FindMediaByIdOrSlugRequired {
|
|
144
|
+
deps: {
|
|
145
|
+
mediaRepo: MediaRepoPort;
|
|
146
|
+
};
|
|
147
|
+
input: {
|
|
148
|
+
workspaceId: UUID;
|
|
149
|
+
idOrSlug: string;
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Resolves a media asset by either its id or its slug — ID FIRST, slug second. The slug is the
|
|
154
|
+
* handle a human typed into an embed marker or a hand-authored `<img>`/`<video>` tag; the id is the
|
|
155
|
+
* opaque UUID every existing reference already uses, and every URL a host's renderer emits.
|
|
156
|
+
*
|
|
157
|
+
* THE ORDER IS A SECURITY PROPERTY, not a preference (2026-09-07). This resolved slug-first, on the
|
|
158
|
+
* documented assumption that "an id never collides with a slug in practice since slugs pass through
|
|
159
|
+
* {@link isValidMediaSlugFormat}". That assumption was false: a lowercase UUID matched the old slug
|
|
160
|
+
* pattern `[a-z0-9-]+` exactly, so setting asset B's slug to asset A's id made every `/m/{A.id}/…`
|
|
161
|
+
* URL — the ones a renderer emitted for A, already live in published pages — resolve to B instead.
|
|
162
|
+
* An id is minted by the system and is never a value a human chooses, so it must always win; the
|
|
163
|
+
* write path additionally refuses UUID-shaped slugs now (see
|
|
164
|
+
* {@link MEDIA_SLUG_UUID_SHAPE_PATTERN}), but ordering is what protects rows written before that
|
|
165
|
+
* rule existed. Id-first costs nothing on the slug path: a real slug simply misses the id lookup and
|
|
166
|
+
* falls through.
|
|
167
|
+
*
|
|
168
|
+
* Never throws (unlike {@link getMediaById}): every call site that needs this (an embed resolver, a
|
|
169
|
+
* public rendition route) already has its own REQ-27-style non-throwing-miss contract, so this
|
|
170
|
+
* returns `null` on failure and lets the caller apply its own not-found handling rather than forcing
|
|
171
|
+
* one shape on every caller.
|
|
172
|
+
*
|
|
173
|
+
* @complexity O(1) — at most two indexed repo lookups, short-circuited on the first hit.
|
|
174
|
+
*/
|
|
175
|
+
export declare function findMediaByIdOrSlug(required: FindMediaByIdOrSlugRequired, _optional?: Record<string, never>): Promise<MediaRecord | null>;
|
|
134
176
|
export interface UpdateMediaMetadataInput {
|
|
135
177
|
workspaceId: UUID;
|
|
136
178
|
id: UUID;
|
|
@@ -152,6 +194,23 @@ export interface UpdateMediaMetadataInput {
|
|
|
152
194
|
* a string that trims to empty is stored as `null` (equivalent to clearing it), matching the
|
|
153
195
|
* "empty means unset" convention `title`/`alt`/etc. already follow via `.trim()`. */
|
|
154
196
|
cssClass?: string | null | undefined;
|
|
197
|
+
/**
|
|
198
|
+
* Explicit slug edit (2026-09-07). `undefined` (key omitted) leaves the stored slug unchanged —
|
|
199
|
+
* in particular, changing `title` on the SAME call never touches `slug`; they are independent
|
|
200
|
+
* fields by design (see `MediaRecord.slug`'s doc). Unlike `title`/`cssClass`, `slug` has no
|
|
201
|
+
* "empty means unset" fallback: a media asset's slug is never null once assigned, so a caller
|
|
202
|
+
* cannot clear it back to absent, only replace it with a different valid slug.
|
|
203
|
+
*/
|
|
204
|
+
slug?: string | undefined;
|
|
205
|
+
/**
|
|
206
|
+
* Same undefined/null/value contract as `cssClass` above (2026-09-07). A provided non-empty string
|
|
207
|
+
* is validated against `html-attributes.ts`'s allowlist BEFORE it is trimmed and stored — an `on*`
|
|
208
|
+
* handler, a `javascript:` value, or any disallowed name throws `MediaValidationError` naming the
|
|
209
|
+
* exact rejected attribute, and NOTHING is written (this field included) when that happens. A
|
|
210
|
+
* string that validates but trims to empty is stored as `null`, matching `cssClass`'s identical
|
|
211
|
+
* "empty means unset" convention.
|
|
212
|
+
*/
|
|
213
|
+
htmlAttributes?: string | null | undefined;
|
|
155
214
|
}
|
|
156
215
|
export interface UpdateMediaMetadataDeps {
|
|
157
216
|
clock: ClockPort;
|
|
@@ -241,4 +300,37 @@ export interface PurgeMediaRequired {
|
|
|
241
300
|
export declare function purgeMedia(required: PurgeMediaRequired, _optional?: Record<string, never>): Promise<{
|
|
242
301
|
purged: true;
|
|
243
302
|
}>;
|
|
303
|
+
/** Deps shared by both callers of {@link removeMediaRowsAndTombstoneBlob} — the subset of
|
|
304
|
+
* `PurgeMediaDeps` that cleanup actually needs (no `blobStore`: neither caller deletes bytes
|
|
305
|
+
* directly, only tombstones the row via `tombstoneBlobIfUnreferenced`). */
|
|
306
|
+
export interface MediaRowCleanupDeps {
|
|
307
|
+
mediaRepo: MediaRepoPort;
|
|
308
|
+
blobRepo: AssetBlobRepoPort;
|
|
309
|
+
renditionRepo: AssetRenditionRepoPort;
|
|
310
|
+
/** Optional — see `PurgeMediaDeps.clock`'s identical doc. */
|
|
311
|
+
clock?: ClockPort | undefined;
|
|
312
|
+
}
|
|
313
|
+
export interface RollbackUploadedMediaRequired {
|
|
314
|
+
deps: MediaRowCleanupDeps;
|
|
315
|
+
input: {
|
|
316
|
+
workspaceId: UUID;
|
|
317
|
+
media: MediaRecord;
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Compensating rollback for {@link uploadMedia}: removes the rendition + media rows it just wrote
|
|
322
|
+
* and tombstones the blob if no other active media row in the workspace still references its
|
|
323
|
+
* sha256 (delegates to {@link removeMediaRowsAndTombstoneBlob}, `purgeMedia`'s own cleanup step).
|
|
324
|
+
*
|
|
325
|
+
* For a caller-side step that runs AFTER `uploadMedia()` has already committed and then fails
|
|
326
|
+
* (e.g. `tool-registrations.ts`'s optional `recordUploadContentType` hook) — without this, the
|
|
327
|
+
* rows `uploadMedia()` wrote survive as orphans (bytes and/or rows nothing points at) even though
|
|
328
|
+
* the caller sees the whole upload as failed. Unlike `purgeMedia`, this performs no trashed-status
|
|
329
|
+
* guard: it is an internal compensating action for a partially-failed operation, not a user-facing
|
|
330
|
+
* delete.
|
|
331
|
+
*
|
|
332
|
+
* @complexity O(n) in the workspace's media row count, inherited from
|
|
333
|
+
* {@link removeMediaRowsAndTombstoneBlob}.
|
|
334
|
+
*/
|
|
335
|
+
export declare function rollbackUploadedMedia(required: RollbackUploadedMediaRequired, _optional?: Record<string, never>): Promise<void>;
|
|
244
336
|
//# sourceMappingURL=media-service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-service.d.ts","sourceRoot":"","sources":["../../src/media/media-service.ts"],"names":[],"mappings":"AAsDA,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,yBAAyB,EACzB,yBAAyB,EACzB,oBAAoB,EACpB,KAAK,WAAW,EAChB,KAAK,WAAW,EAEjB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"media-service.d.ts","sourceRoot":"","sources":["../../src/media/media-service.ts"],"names":[],"mappings":"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"}
|