@jini-ai/cms 0.3.11 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +47 -19
  3. package/dist/content-types/agent-tools.d.ts +3 -56
  4. package/dist/content-types/agent-tools.js +22 -6
  5. package/dist/content-types/cleanup.d.ts +18 -13
  6. package/dist/content-types/cleanup.js +16 -11
  7. package/dist/content-types/errors.d.ts +57 -24
  8. package/dist/content-types/errors.js +56 -39
  9. package/dist/content-types/field-defs.d.ts +8 -4
  10. package/dist/content-types/field-defs.js +19 -14
  11. package/dist/content-types/index-provisioning.d.ts +61 -47
  12. package/dist/content-types/index-provisioning.js +90 -66
  13. package/dist/content-types/index.d.ts +4 -3
  14. package/dist/content-types/lifecycle-dispatch.d.ts +11 -5
  15. package/dist/content-types/lifecycle-dispatch.js +5 -4
  16. package/dist/content-types/lifecycle.d.ts +23 -18
  17. package/dist/content-types/lifecycle.js +71 -64
  18. package/dist/content-types/repo.memory.d.ts +5 -4
  19. package/dist/content-types/repo.memory.js +3 -2
  20. package/dist/content-types/tool-registrations.d.ts +6 -8
  21. package/dist/content-types/tool-registrations.js +24 -28
  22. package/dist/content-types/types.d.ts +17 -13
  23. package/dist/content-types/types.js +11 -5
  24. package/dist/content-types/write-service.d.ts +20 -22
  25. package/dist/content-types/write-service.js +78 -75
  26. package/dist/core/authorization.d.ts +15 -0
  27. package/dist/core/authorization.js +2 -0
  28. package/dist/core/commands/change-set.d.ts +15 -7
  29. package/dist/core/commands/command.d.ts +45 -26
  30. package/dist/core/commands/command.js +73 -63
  31. package/dist/core/entity-liveness.d.ts +11 -7
  32. package/dist/core/entity-liveness.js +5 -4
  33. package/dist/core/index.d.ts +12 -5
  34. package/dist/core/index.js +10 -4
  35. package/dist/core/ports.d.ts +25 -24
  36. package/dist/core/ports.js +1 -0
  37. package/dist/core/tools/args.d.ts +49 -0
  38. package/dist/core/tools/args.js +46 -0
  39. package/dist/core/tools/index.d.ts +3 -0
  40. package/dist/core/tools/index.js +3 -0
  41. package/dist/entries/agent-tools.d.ts +1 -73
  42. package/dist/entries/agent-tools.js +19 -13
  43. package/dist/entries/errors.d.ts +28 -14
  44. package/dist/entries/errors.js +17 -11
  45. package/dist/entries/field-validation.d.ts +5 -3
  46. package/dist/entries/field-validation.js +7 -5
  47. package/dist/entries/index.d.ts +2 -2
  48. package/dist/entries/lifecycle-dispatch.d.ts +5 -2
  49. package/dist/entries/lifecycle-dispatch.js +2 -1
  50. package/dist/entries/list.d.ts +4 -2
  51. package/dist/entries/list.js +4 -3
  52. package/dist/entries/repo.memory.d.ts +12 -10
  53. package/dist/entries/repo.memory.js +11 -10
  54. package/dist/entries/tool-registrations.d.ts +6 -8
  55. package/dist/entries/tool-registrations.js +42 -41
  56. package/dist/entries/types.d.ts +0 -7
  57. package/dist/entries/write-service.d.ts +28 -29
  58. package/dist/entries/write-service.js +114 -105
  59. package/dist/http/settings/cms-adapter.d.ts +20 -0
  60. package/dist/http/settings/cms-adapter.js +124 -0
  61. package/dist/http/settings/contracts.d.ts +135 -0
  62. package/dist/http/settings/contracts.js +2 -0
  63. package/dist/http/settings/http.d.ts +10 -0
  64. package/dist/http/settings/http.js +211 -0
  65. package/dist/http/settings/index.d.ts +5 -0
  66. package/dist/http/settings/index.js +4 -0
  67. package/dist/http/settings/shared.d.ts +68 -0
  68. package/dist/http/settings/shared.js +54 -0
  69. package/dist/http/settings/stream.d.ts +24 -0
  70. package/dist/http/settings/stream.js +147 -0
  71. package/dist/media/agent-tools.d.ts +1 -66
  72. package/dist/media/agent-tools.js +5 -1
  73. package/dist/media/blob-gc-lock.d.ts +6 -2
  74. package/dist/media/blob-gc-lock.js +4 -2
  75. package/dist/media/blob-gc.d.ts +64 -51
  76. package/dist/media/blob-gc.js +62 -52
  77. package/dist/media/blob-key.d.ts +1 -1
  78. package/dist/media/content-type-sniffer.d.ts +3 -1
  79. package/dist/media/content-type-sniffer.js +1 -1
  80. package/dist/media/html-attributes.d.ts +9 -5
  81. package/dist/media/html-attributes.js +8 -6
  82. package/dist/media/image-transformer.js +1 -1
  83. package/dist/media/image-transformer.sharp.d.ts +6 -0
  84. package/dist/media/image-transformer.sharp.js +12 -6
  85. package/dist/media/import/fetch-image.d.ts +24 -0
  86. package/dist/media/import/fetch-image.js +137 -0
  87. package/dist/media/import/index.d.ts +3 -0
  88. package/dist/media/import/index.js +3 -0
  89. package/dist/media/import/ports.d.ts +38 -0
  90. package/dist/media/index.d.ts +7 -1
  91. package/dist/media/index.js +1 -0
  92. package/dist/media/media-service.d.ts +14 -10
  93. package/dist/media/media-service.js +57 -52
  94. package/dist/media/ports.d.ts +3 -2
  95. package/dist/media/rendition-service.d.ts +3 -3
  96. package/dist/media/rendition-service.js +38 -37
  97. package/dist/media/repo.memory.d.ts +18 -7
  98. package/dist/media/repo.memory.js +12 -6
  99. package/dist/media/tool-registrations.d.ts +16 -15
  100. package/dist/media/tool-registrations.js +84 -72
  101. package/dist/media/transform-lock.d.ts +22 -12
  102. package/dist/media/transform-lock.js +14 -13
  103. package/dist/media/transform-registry.d.ts +3 -3
  104. package/dist/media/transform-registry.js +19 -18
  105. package/dist/media/transform-types.d.ts +8 -3
  106. package/dist/media/transform-types.js +10 -5
  107. package/dist/media/types.d.ts +21 -4
  108. package/dist/media/types.js +15 -2
  109. package/dist/navigation/agent-tools.d.ts +1 -80
  110. package/dist/navigation/agent-tools.js +15 -3
  111. package/dist/navigation/contracts.d.ts +1 -1
  112. package/dist/navigation/index.d.ts +2 -2
  113. package/dist/navigation/menu-service.d.ts +57 -40
  114. package/dist/navigation/menu-service.js +80 -69
  115. package/dist/navigation/ports.d.ts +1 -1
  116. package/dist/navigation/read-model.d.ts +3 -1
  117. package/dist/navigation/read-model.js +2 -2
  118. package/dist/navigation/reconcile.d.ts +2 -2
  119. package/dist/navigation/reconcile.js +2 -1
  120. package/dist/navigation/repo.memory.d.ts +7 -3
  121. package/dist/navigation/repo.memory.js +4 -2
  122. package/dist/navigation/resolver.d.ts +4 -1
  123. package/dist/navigation/resolver.js +1 -1
  124. package/dist/navigation/tool-registrations.d.ts +6 -8
  125. package/dist/navigation/tool-registrations.js +44 -42
  126. package/dist/navigation/types.d.ts +1 -1
  127. package/dist/presentation/presentation.d.ts +11 -3
  128. package/dist/presentation/presentation.js +13 -6
  129. package/dist/presentation/repo.memory.d.ts +6 -2
  130. package/dist/presentation/repo.memory.js +3 -2
  131. package/dist/settings/agent-tools.d.ts +2 -75
  132. package/dist/settings/agent-tools.js +49 -45
  133. package/dist/settings/agent-value-write-tools.d.ts +22 -0
  134. package/dist/settings/agent-value-write-tools.js +154 -0
  135. package/dist/settings/agent-writable-preferences.js +8 -20
  136. package/dist/settings/agent-write-denylist.d.ts +19 -0
  137. package/dist/settings/agent-write-denylist.js +15 -0
  138. package/dist/settings/change-feed.d.ts +11 -2
  139. package/dist/settings/change-feed.js +4 -4
  140. package/dist/settings/definitions-dispatch.d.ts +8 -3
  141. package/dist/settings/definitions-dispatch.js +5 -5
  142. package/dist/settings/dictionaries/index.d.ts +5 -2
  143. package/dist/settings/dictionaries/index.js +2 -2
  144. package/dist/settings/dictionaries/settings-dialog.ar.js +0 -1
  145. package/dist/settings/dictionaries/settings-dialog.bn.js +0 -1
  146. package/dist/settings/dictionaries/settings-dialog.de.js +0 -1
  147. package/dist/settings/dictionaries/settings-dialog.es.js +0 -1
  148. package/dist/settings/dictionaries/settings-dialog.fa.js +0 -1
  149. package/dist/settings/dictionaries/settings-dialog.fr.js +0 -1
  150. package/dist/settings/dictionaries/settings-dialog.hi.js +0 -1
  151. package/dist/settings/dictionaries/settings-dialog.hu.js +0 -1
  152. package/dist/settings/dictionaries/settings-dialog.id.js +0 -1
  153. package/dist/settings/dictionaries/settings-dialog.it.js +0 -1
  154. package/dist/settings/dictionaries/settings-dialog.ja.js +0 -1
  155. package/dist/settings/dictionaries/settings-dialog.ko.js +0 -1
  156. package/dist/settings/dictionaries/settings-dialog.pl.js +0 -1
  157. package/dist/settings/dictionaries/settings-dialog.pt-BR.js +0 -1
  158. package/dist/settings/dictionaries/settings-dialog.ru.js +0 -1
  159. package/dist/settings/dictionaries/settings-dialog.th.js +0 -1
  160. package/dist/settings/dictionaries/settings-dialog.tr.js +0 -1
  161. package/dist/settings/dictionaries/settings-dialog.uk.js +0 -1
  162. package/dist/settings/dictionaries/settings-dialog.ur.js +0 -1
  163. package/dist/settings/dictionaries/settings-dialog.zh-CN.js +0 -1
  164. package/dist/settings/dictionaries/settings-dialog.zh-TW.js +0 -1
  165. package/dist/settings/ensure-definitions.d.ts +5 -5
  166. package/dist/settings/errors.d.ts +7 -1
  167. package/dist/settings/errors.js +7 -1
  168. package/dist/settings/index.d.ts +4 -1
  169. package/dist/settings/index.js +1 -0
  170. package/dist/settings/ports.d.ts +6 -5
  171. package/dist/settings/principal-lookup.d.ts +9 -0
  172. package/dist/settings/principal-lookup.js +2 -0
  173. package/dist/settings/purge-service.d.ts +5 -4
  174. package/dist/settings/purge-service.js +3 -2
  175. package/dist/settings/settings.d.ts +22 -7
  176. package/dist/settings/settings.js +47 -26
  177. package/dist/settings/tool-registrations.d.ts +27 -32
  178. package/dist/settings/tool-registrations.js +66 -56
  179. package/dist/settings/types.d.ts +1 -1
  180. package/dist/settings/ui-tab-definitions.d.ts +1 -1
  181. package/dist/settings/write-service.d.ts +32 -26
  182. package/dist/settings/write-service.js +26 -20
  183. package/dist/taxonomy/agent-tools.d.ts +1 -66
  184. package/dist/taxonomy/agent-tools.js +25 -15
  185. package/dist/taxonomy/content-lookup.js +1 -1
  186. package/dist/taxonomy/index.d.ts +2 -2
  187. package/dist/taxonomy/merge-term.d.ts +7 -4
  188. package/dist/taxonomy/merge-term.js +7 -5
  189. package/dist/taxonomy/repo.memory.d.ts +42 -19
  190. package/dist/taxonomy/repo.memory.js +14 -12
  191. package/dist/taxonomy/validation-chain.d.ts +39 -18
  192. package/dist/taxonomy/validation-chain.js +35 -25
  193. package/dist/taxonomy/write-service.d.ts +103 -44
  194. package/dist/taxonomy/write-service.js +221 -179
  195. package/dist/trash/adapters/record-store.d.ts +52 -0
  196. package/dist/trash/adapters/record-store.js +36 -0
  197. package/dist/trash/cursor.d.ts +20 -0
  198. package/dist/trash/cursor.js +36 -0
  199. package/dist/trash/follow-ups.d.ts +76 -0
  200. package/dist/trash/follow-ups.js +46 -0
  201. package/dist/trash/index.d.ts +8 -0
  202. package/dist/trash/index.js +8 -0
  203. package/dist/trash/ports.d.ts +308 -0
  204. package/dist/trash/ports.js +2 -0
  205. package/dist/trash/repo.memory.d.ts +50 -0
  206. package/dist/trash/repo.memory.js +114 -0
  207. package/dist/trash/sweeper.d.ts +77 -0
  208. package/dist/trash/sweeper.js +127 -0
  209. package/dist/trash/write-service.d.ts +100 -0
  210. package/dist/trash/write-service.js +242 -0
  211. package/dist/workspace/agent-tools.d.ts +1 -59
  212. package/dist/workspace/agent-tools.js +27 -12
  213. package/dist/workspace/create.d.ts +31 -6
  214. package/dist/workspace/create.js +18 -5
  215. package/dist/workspace/delete.d.ts +18 -13
  216. package/dist/workspace/delete.js +20 -12
  217. package/dist/workspace/index.d.ts +1 -1
  218. package/dist/workspace/repo.memory.d.ts +19 -4
  219. package/dist/workspace/repo.memory.js +26 -4
  220. package/dist/workspace/tool-registrations.d.ts +2 -2
  221. package/dist/workspace/tool-registrations.js +13 -13
  222. package/dist/workspace/update.d.ts +6 -5
  223. package/dist/workspace/update.js +9 -8
  224. package/package.json +44 -38
  225. package/dist/core/tools/registration-kit.d.ts +0 -346
  226. package/dist/core/tools/registration-kit.js +0 -426
  227. package/dist/identity/admin-crud-service.d.ts +0 -280
  228. package/dist/identity/admin-crud-service.js +0 -468
  229. package/dist/identity/agent-tool-input.d.ts +0 -58
  230. package/dist/identity/agent-tool-input.js +0 -126
  231. package/dist/identity/agent-tools.d.ts +0 -101
  232. package/dist/identity/agent-tools.js +0 -290
  233. package/dist/identity/auth-service.d.ts +0 -118
  234. package/dist/identity/auth-service.js +0 -170
  235. package/dist/identity/authorize.d.ts +0 -90
  236. package/dist/identity/authorize.js +0 -94
  237. package/dist/identity/grant-service.d.ts +0 -198
  238. package/dist/identity/grant-service.js +0 -361
  239. package/dist/identity/hasher.d.ts +0 -39
  240. package/dist/identity/hasher.js +0 -39
  241. package/dist/identity/index.d.ts +0 -62
  242. package/dist/identity/index.js +0 -54
  243. package/dist/identity/password-policy.d.ts +0 -57
  244. package/dist/identity/password-policy.js +0 -66
  245. package/dist/identity/permission-migrations.d.ts +0 -86
  246. package/dist/identity/permission-migrations.js +0 -67
  247. package/dist/identity/permissions.d.ts +0 -35
  248. package/dist/identity/permissions.js +0 -603
  249. package/dist/identity/ports.d.ts +0 -201
  250. package/dist/identity/repo.memory.d.ts +0 -164
  251. package/dist/identity/repo.memory.js +0 -224
  252. package/dist/identity/seed.d.ts +0 -54
  253. package/dist/identity/seed.js +0 -429
  254. package/dist/identity/tool-registrations.d.ts +0 -50
  255. package/dist/identity/tool-registrations.js +0 -419
  256. package/dist/identity/types.d.ts +0 -185
  257. package/dist/identity/types.js +0 -64
  258. package/dist/identity/username.d.ts +0 -12
  259. package/dist/identity/username.js +0 -14
  260. package/dist/server/index.d.ts +0 -28
  261. package/dist/server/index.js +0 -28
  262. /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
- * (`apps/admin/src/features/media/rules.ts`, `parseMediaHtmlAttributes` and friends).
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 Vite app (that product's `apps/admin`) risks pulling those into the admin
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 that product's `apps/website` render path, both pure
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(name: string): boolean;
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(text: string): ParsedMediaHtmlAttributes;
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
- * (`apps/admin/src/features/media/rules.ts`, `parseMediaHtmlAttributes` and friends).
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 Vite app (that product's `apps/admin`) risks pulling those into the admin
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 that product's `apps/website` render path, both pure
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(name) {
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(text) {
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
- "SharpImageTransformer cannot perform a real pixel transform without it. This is a " +
75
- "disclosed, optional-dependency blocker, not a silent stub: no bytes are faked or passed " +
76
- "through unmodified.");
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,3 @@
1
+ export * from "./ports.js";
2
+ export * from "./fetch-image.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export * from "./ports.js";
2
+ export * from "./fetch-image.js";
3
+ //# sourceMappingURL=index.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
@@ -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, type AgentToolDefinition as MediaAgentToolDefinition, type AgentToolSideEffect as MediaAgentToolSideEffect, type AgentToolActorClassRule as MediaAgentToolActorClassRule, } from "./agent-tools.js";
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
@@ -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 { ClockPort, IdGeneratorPort, UUID } from "../core/ports.js";
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(slug: string): boolean;
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: ClockPort;
66
- idGen: IdGeneratorPort;
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 (INV-1a): bytes are written (or found already
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 REQ-27-style non-throwing-miss contract, so this
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: ClockPort;
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: ClockPort;
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?: ClockPort | undefined;
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?: ClockPort | undefined;
316
+ clock?: Clock | undefined;
313
317
  }
314
318
  export interface RollbackUploadedMediaRequired {
315
319
  deps: MediaRowCleanupDeps;