@jini-ai/cms 0.3.10 → 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.
Files changed (258) 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 +25 -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 +106 -45
  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/identity/admin-crud-service.d.ts +0 -280
  226. package/dist/identity/admin-crud-service.js +0 -468
  227. package/dist/identity/agent-tool-input.d.ts +0 -58
  228. package/dist/identity/agent-tool-input.js +0 -126
  229. package/dist/identity/agent-tools.d.ts +0 -101
  230. package/dist/identity/agent-tools.js +0 -290
  231. package/dist/identity/auth-service.d.ts +0 -118
  232. package/dist/identity/auth-service.js +0 -170
  233. package/dist/identity/authorize.d.ts +0 -90
  234. package/dist/identity/authorize.js +0 -94
  235. package/dist/identity/grant-service.d.ts +0 -198
  236. package/dist/identity/grant-service.js +0 -361
  237. package/dist/identity/hasher.d.ts +0 -39
  238. package/dist/identity/hasher.js +0 -39
  239. package/dist/identity/index.d.ts +0 -62
  240. package/dist/identity/index.js +0 -54
  241. package/dist/identity/password-policy.d.ts +0 -57
  242. package/dist/identity/password-policy.js +0 -66
  243. package/dist/identity/permission-migrations.d.ts +0 -86
  244. package/dist/identity/permission-migrations.js +0 -67
  245. package/dist/identity/permissions.d.ts +0 -35
  246. package/dist/identity/permissions.js +0 -603
  247. package/dist/identity/ports.d.ts +0 -201
  248. package/dist/identity/repo.memory.d.ts +0 -164
  249. package/dist/identity/repo.memory.js +0 -224
  250. package/dist/identity/seed.d.ts +0 -54
  251. package/dist/identity/seed.js +0 -429
  252. package/dist/identity/tool-registrations.d.ts +0 -50
  253. package/dist/identity/tool-registrations.js +0 -419
  254. package/dist/identity/types.d.ts +0 -185
  255. package/dist/identity/types.js +0 -64
  256. package/dist/identity/username.d.ts +0 -12
  257. package/dist/identity/username.js +0 -14
  258. /package/dist/{identity → media/import}/ports.js +0 -0
@@ -1,7 +1,8 @@
1
- import { buildDomainRegistrations, indexCatalogById, requireInputRecord, requireString, requireToolPermission, withSchemaOnRejection, } from "../core/tools/registration-kit.js";
1
+ import { buildDomainRegistrations, indexCatalogById, requireInputRecord, requireString, withSchemaOnRejection } from "@jini-ai/core";
2
+ import { adaptLegacyAuthorize, requireToolPermission } from "../core/tools/index.js";
2
3
  import { mediaAgentToolCatalog } from "./agent-tools.js";
3
4
  import { listMedia, MediaValidationError, rollbackUploadedMedia, trashMedia, updateMediaMetadata, uploadMedia } from "./media-service.js";
4
- const CATALOG_BY_ID = indexCatalogById(mediaAgentToolCatalog);
5
+ const CATALOG_BY_ID = indexCatalogById({ catalog: mediaAgentToolCatalog });
5
6
  /**
6
7
  * This wiring layer's OWN risk classification, authored from what each handler below actually
7
8
  * calls. See `DerivedRiskByToolId` in the kit for why it is independent of the catalog's own
@@ -19,7 +20,7 @@ export const mediaDerivedRisk = new Map([
19
20
  ["media_trash_asset", "mutates-durable-state"],
20
21
  ]);
21
22
  /** The only Media rejection worth decorating with the published schema — a shape problem a different input would fix. */
22
- function isMediaShapeRejection(error) {
23
+ function isMediaShapeRejection({ error }) {
23
24
  return error instanceof MediaValidationError;
24
25
  }
25
26
  /**
@@ -53,10 +54,21 @@ function toMediaToolView(record) {
53
54
  * responsibility — see `MediaToolDeps.resolvePublicUrls`'s own doc).
54
55
  */
55
56
  async function toMediaToolViewsWithPublicUrls(routeDeps, records) {
56
- const urls = routeDeps.resolvePublicUrls ? await routeDeps.resolvePublicUrls(records) : new Map();
57
+ const urls = routeDeps.resolvePublicUrls ? await routeDeps.resolvePublicUrls({ assets: records }) : new Map();
57
58
  return records.map((record) => ({ ...toMediaToolView(record), publicUrl: urls.get(record.id) ?? null }));
58
59
  }
59
- export function buildMediaRegistrations(routeDeps) {
60
+ export function buildMediaRegistrations(required, optional = {}) {
61
+ const routeDeps = {
62
+ authorize: required.authorize,
63
+ workspaceId: required.workspaceId,
64
+ clock: required.clock,
65
+ idGen: required.idGen,
66
+ mediaRepo: required.mediaRepo,
67
+ assetBlobRepo: required.assetBlobRepo,
68
+ assetRenditionRepo: required.assetRenditionRepo,
69
+ blobStore: required.blobStore,
70
+ ...optional,
71
+ };
60
72
  const mediaWriteDeps = () => ({
61
73
  clock: routeDeps.clock,
62
74
  idGen: routeDeps.idGen,
@@ -67,84 +79,84 @@ export function buildMediaRegistrations(routeDeps) {
67
79
  });
68
80
  const handlers = {
69
81
  media_list_assets: async (ctx) => {
70
- await requireToolPermission(routeDeps, { principalId: ctx.principal.id, permission: "media.read", entityType: "media" });
82
+ await requireToolPermission({ authorize: adaptLegacyAuthorize({ authorize: routeDeps.authorize }), workspaceId: routeDeps.workspaceId, principalId: ctx.principal.id, permission: "media.read" }, { entityType: "media" });
71
83
  const { media } = await listMedia({ deps: { mediaRepo: routeDeps.mediaRepo }, input: { workspaceId: routeDeps.workspaceId } });
72
84
  return { media: await toMediaToolViewsWithPublicUrls(routeDeps, media) };
73
85
  },
74
86
  media_upload_asset: async (ctx) => {
75
- const input = requireInputRecord(ctx.input);
76
- await requireToolPermission(routeDeps, { principalId: ctx.principal.id, permission: "media.upload", entityType: "media" });
77
- return withSchemaOnRejection({ toolId: "media_upload_asset", catalog: CATALOG_BY_ID, isShapeRejection: isMediaShapeRejection }, async () => {
78
- const dataBase64 = requireString(input, "dataBase64");
79
- let bytes;
80
- try {
81
- bytes = new Uint8Array(Buffer.from(dataBase64, "base64"));
82
- }
83
- catch {
84
- throw new Error("'dataBase64' is not valid base64");
85
- }
86
- const { media } = await uploadMedia({
87
- deps: mediaWriteDeps(),
88
- input: {
89
- workspaceId: routeDeps.workspaceId,
90
- bytes,
91
- filename: requireString(input, "filename"),
92
- contentType: requireString(input, "contentType"),
93
- alt: typeof input.alt === "string" ? input.alt : undefined,
94
- caption: typeof input.caption === "string" ? input.caption : undefined,
95
- credit: typeof input.credit === "string" ? input.credit : undefined,
96
- createdByPrincipal: ctx.principal.id,
97
- },
98
- }, { maxUploadBytes: routeDeps.maxUploadBytes });
99
- if (routeDeps.recordUploadContentType) {
87
+ const input = requireInputRecord({ input: ctx.input });
88
+ await requireToolPermission({ authorize: adaptLegacyAuthorize({ authorize: routeDeps.authorize }), workspaceId: routeDeps.workspaceId, principalId: ctx.principal.id, permission: "media.upload" }, { entityType: "media" });
89
+ return withSchemaOnRejection({ toolId: "media_upload_asset", catalog: CATALOG_BY_ID, isShapeRejection: isMediaShapeRejection, fn: async () => {
90
+ const dataBase64 = requireString({ input, key: "dataBase64" });
91
+ let bytes;
100
92
  try {
101
- await routeDeps.recordUploadContentType({ media, bytes });
93
+ bytes = new Uint8Array(Buffer.from(dataBase64, "base64"));
102
94
  }
103
- catch (err) {
104
- // The blob/media/rendition rows uploadMedia() just wrote must not survive as orphans
105
- // when the caller is about to see this whole upload as failed (the fix for that gap —
106
- // see rollbackUploadedMedia's own doc). The original hook error is rethrown unchanged.
107
- await rollbackUploadedMedia({
108
- deps: {
109
- mediaRepo: routeDeps.mediaRepo,
110
- blobRepo: routeDeps.assetBlobRepo,
111
- renditionRepo: routeDeps.assetRenditionRepo,
112
- clock: routeDeps.clock,
113
- },
114
- input: { workspaceId: routeDeps.workspaceId, media },
115
- });
116
- throw err;
95
+ catch {
96
+ throw new Error("'dataBase64' is not valid base64");
117
97
  }
118
- }
119
- const [view] = await toMediaToolViewsWithPublicUrls(routeDeps, [media]);
120
- return { media: view };
121
- });
98
+ const { media } = await uploadMedia({
99
+ deps: mediaWriteDeps(),
100
+ input: {
101
+ workspaceId: routeDeps.workspaceId,
102
+ bytes,
103
+ filename: requireString({ input, key: "filename" }),
104
+ contentType: requireString({ input, key: "contentType" }),
105
+ alt: typeof input.alt === "string" ? input.alt : undefined,
106
+ caption: typeof input.caption === "string" ? input.caption : undefined,
107
+ credit: typeof input.credit === "string" ? input.credit : undefined,
108
+ createdByPrincipal: ctx.principal.id,
109
+ },
110
+ }, { maxUploadBytes: routeDeps.maxUploadBytes });
111
+ if (routeDeps.recordUploadContentType) {
112
+ try {
113
+ await routeDeps.recordUploadContentType({ media, bytes });
114
+ }
115
+ catch (err) {
116
+ // The blob/media/rendition rows uploadMedia() just wrote must not survive as orphans
117
+ // when the caller is about to see this whole upload as failed (the fix for that gap —
118
+ // see rollbackUploadedMedia's own doc). The original hook error is rethrown unchanged.
119
+ await rollbackUploadedMedia({
120
+ deps: {
121
+ mediaRepo: routeDeps.mediaRepo,
122
+ blobRepo: routeDeps.assetBlobRepo,
123
+ renditionRepo: routeDeps.assetRenditionRepo,
124
+ clock: routeDeps.clock,
125
+ },
126
+ input: { workspaceId: routeDeps.workspaceId, media },
127
+ });
128
+ throw err;
129
+ }
130
+ }
131
+ const [view] = await toMediaToolViewsWithPublicUrls(routeDeps, [media]);
132
+ return { media: view };
133
+ } });
122
134
  },
123
135
  media_update_metadata: async (ctx) => {
124
- const input = requireInputRecord(ctx.input);
125
- const mediaId = requireString(input, "mediaId");
126
- await requireToolPermission(routeDeps, { principalId: ctx.principal.id, permission: "media.update", entityType: "media", entityId: mediaId });
127
- return withSchemaOnRejection({ toolId: "media_update_metadata", catalog: CATALOG_BY_ID, isShapeRejection: isMediaShapeRejection }, async () => {
128
- const { media } = await updateMediaMetadata({
129
- deps: { clock: routeDeps.clock, mediaRepo: routeDeps.mediaRepo },
130
- input: {
131
- workspaceId: routeDeps.workspaceId,
132
- id: mediaId,
133
- title: typeof input.title === "string" ? input.title : undefined,
134
- alt: typeof input.alt === "string" ? input.alt : undefined,
135
- caption: typeof input.caption === "string" ? input.caption : undefined,
136
- credit: typeof input.credit === "string" ? input.credit : undefined,
137
- slug: typeof input.slug === "string" ? input.slug : undefined,
138
- cssClass: typeof input.cssClass === "string" ? input.cssClass : undefined,
139
- htmlAttributes: typeof input.htmlAttributes === "string" ? input.htmlAttributes : undefined,
140
- },
141
- });
142
- return { media: toMediaToolView(media) };
143
- });
136
+ const input = requireInputRecord({ input: ctx.input });
137
+ const mediaId = requireString({ input, key: "mediaId" });
138
+ await requireToolPermission({ authorize: adaptLegacyAuthorize({ authorize: routeDeps.authorize }), workspaceId: routeDeps.workspaceId, principalId: ctx.principal.id, permission: "media.update" }, { entityType: "media", entityId: mediaId });
139
+ return withSchemaOnRejection({ toolId: "media_update_metadata", catalog: CATALOG_BY_ID, isShapeRejection: isMediaShapeRejection, fn: async () => {
140
+ const { media } = await updateMediaMetadata({
141
+ deps: { clock: routeDeps.clock, mediaRepo: routeDeps.mediaRepo },
142
+ input: {
143
+ workspaceId: routeDeps.workspaceId,
144
+ id: mediaId,
145
+ title: typeof input.title === "string" ? input.title : undefined,
146
+ alt: typeof input.alt === "string" ? input.alt : undefined,
147
+ caption: typeof input.caption === "string" ? input.caption : undefined,
148
+ credit: typeof input.credit === "string" ? input.credit : undefined,
149
+ slug: typeof input.slug === "string" ? input.slug : undefined,
150
+ cssClass: typeof input.cssClass === "string" ? input.cssClass : undefined,
151
+ htmlAttributes: typeof input.htmlAttributes === "string" ? input.htmlAttributes : undefined,
152
+ },
153
+ });
154
+ return { media: toMediaToolView(media) };
155
+ } });
144
156
  },
145
157
  media_trash_asset: async (ctx) => {
146
- const mediaId = requireString(requireInputRecord(ctx.input), "mediaId");
147
- await requireToolPermission(routeDeps, { principalId: ctx.principal.id, permission: "media.delete", entityType: "media", entityId: mediaId });
158
+ const mediaId = requireString({ input: requireInputRecord({ input: ctx.input }), key: "mediaId" });
159
+ await requireToolPermission({ authorize: adaptLegacyAuthorize({ authorize: routeDeps.authorize }), workspaceId: routeDeps.workspaceId, principalId: ctx.principal.id, permission: "media.delete" }, { entityType: "media", entityId: mediaId });
148
160
  const { media } = await trashMedia({
149
161
  deps: { clock: routeDeps.clock, mediaRepo: routeDeps.mediaRepo },
150
162
  input: { workspaceId: routeDeps.workspaceId, id: mediaId },
@@ -1,21 +1,22 @@
1
1
  /**
2
2
  * @file Generic in-process keyed mutex factory, extracted from
3
- * `blob-gc-lock.ts`'s `withSha256Lock` shape (INV-1's
3
+ * `blob-gc-lock.ts`'s `withSha256Lock` shape (
4
4
  * "Serialization" clause) so the same FIFO-per-key critical-section pattern
5
5
  * can back two independent lock namespaces this domain needs:
6
6
  *
7
- * - `withTransformRegistryLock` — serializes `registerTransform`'s
8
- * read-max-version-then-insert-next-version sequence per `(workspaceId,
9
- * name)`, so two concurrent registrations of the same name can't both
10
- * read the same "current max" and mint a colliding version.
11
- * - `withRenditionLock` — serializes lazy rendition generation per
12
- * `(sha256, transformName, version)` ("single-flight"), so two
13
- * concurrent requests for the same not-yet-generated rendition never run
14
- * the image transformer twice.
7
+ * - `withTransformRegistryLock` — serializes `registerTransform`'s
8
+ * read-max-version-then-insert-next-version sequence per `(workspaceId,
9
+ * name)`, so two concurrent registrations of the same name can't both
10
+ * read the same "current max" and mint a colliding version.
11
+ * - `withRenditionLock` — serializes lazy rendition generation per
12
+ * `(sha256, transformName, version)` ("single-flight"), so two
13
+ * concurrent requests for the same not-yet-generated rendition never run
14
+ * the image transformer twice.
15
15
  *
16
16
  * `blob-gc-lock.ts` itself is untouched — this is a separate module, not a
17
17
  * modification of that file's exports (its shape is deliberately mirrored
18
18
  * here, not imported, so `blob-gc-lock.ts`'s own signature stays untouched).
19
+ * See docs/decisions/DR-004-journaled-blob-gc.md.
19
20
  */
20
21
  /**
21
22
  * Creates one independent keyed-lock namespace: calls sharing the same `key`
@@ -28,9 +29,18 @@
28
29
  * by the number of other calls currently queued for the same key.
29
30
  * @overallScore 100
30
31
  */
31
- export declare function createKeyedLock(): <T>(key: string, criticalSection: () => Promise<T>) => Promise<T>;
32
+ export declare function createKeyedLock(_required: Record<string, never>, _optional?: Record<string, never>): <T>(required: {
33
+ key: string;
34
+ criticalSection: () => Promise<T>;
35
+ }, optional?: Record<string, never>) => Promise<T>;
32
36
  /** Keyed on `${workspaceId}:${name}` — see file header. */
33
- export declare const withTransformRegistryLock: <T>(key: string, criticalSection: () => Promise<T>) => Promise<T>;
37
+ export declare const withTransformRegistryLock: <T>(required: {
38
+ key: string;
39
+ criticalSection: () => Promise<T>;
40
+ }, optional?: Record<string, never>) => Promise<T>;
34
41
  /** Keyed on `${sha256}:${transformName}:${version}` — see file header. */
35
- export declare const withRenditionLock: <T>(key: string, criticalSection: () => Promise<T>) => Promise<T>;
42
+ export declare const withRenditionLock: <T>(required: {
43
+ key: string;
44
+ criticalSection: () => Promise<T>;
45
+ }, optional?: Record<string, never>) => Promise<T>;
36
46
  //# sourceMappingURL=transform-lock.d.ts.map
@@ -1,21 +1,22 @@
1
1
  /**
2
2
  * @file Generic in-process keyed mutex factory, extracted from
3
- * `blob-gc-lock.ts`'s `withSha256Lock` shape (INV-1's
3
+ * `blob-gc-lock.ts`'s `withSha256Lock` shape (
4
4
  * "Serialization" clause) so the same FIFO-per-key critical-section pattern
5
5
  * can back two independent lock namespaces this domain needs:
6
6
  *
7
- * - `withTransformRegistryLock` — serializes `registerTransform`'s
8
- * read-max-version-then-insert-next-version sequence per `(workspaceId,
9
- * name)`, so two concurrent registrations of the same name can't both
10
- * read the same "current max" and mint a colliding version.
11
- * - `withRenditionLock` — serializes lazy rendition generation per
12
- * `(sha256, transformName, version)` ("single-flight"), so two
13
- * concurrent requests for the same not-yet-generated rendition never run
14
- * the image transformer twice.
7
+ * - `withTransformRegistryLock` — serializes `registerTransform`'s
8
+ * read-max-version-then-insert-next-version sequence per `(workspaceId,
9
+ * name)`, so two concurrent registrations of the same name can't both
10
+ * read the same "current max" and mint a colliding version.
11
+ * - `withRenditionLock` — serializes lazy rendition generation per
12
+ * `(sha256, transformName, version)` ("single-flight"), so two
13
+ * concurrent requests for the same not-yet-generated rendition never run
14
+ * the image transformer twice.
15
15
  *
16
16
  * `blob-gc-lock.ts` itself is untouched — this is a separate module, not a
17
17
  * modification of that file's exports (its shape is deliberately mirrored
18
18
  * here, not imported, so `blob-gc-lock.ts`'s own signature stays untouched).
19
+ * See docs/decisions/DR-004-journaled-blob-gc.md.
19
20
  */
20
21
  /**
21
22
  * Creates one independent keyed-lock namespace: calls sharing the same `key`
@@ -28,9 +29,9 @@
28
29
  * by the number of other calls currently queued for the same key.
29
30
  * @overallScore 100
30
31
  */
31
- export function createKeyedLock() {
32
+ export function createKeyedLock(_required, _optional = {}) {
32
33
  const lockTails = new Map();
33
- return function withLock(key, criticalSection) {
34
+ return function withLock({ key, criticalSection }, _optional = {}) {
34
35
  const priorTail = lockTails.get(key) ?? Promise.resolve();
35
36
  const run = priorTail.then(criticalSection, criticalSection);
36
37
  const settledTail = run.then(() => undefined, () => undefined);
@@ -44,7 +45,7 @@ export function createKeyedLock() {
44
45
  };
45
46
  }
46
47
  /** Keyed on `${workspaceId}:${name}` — see file header. */
47
- export const withTransformRegistryLock = createKeyedLock();
48
+ export const withTransformRegistryLock = createKeyedLock({});
48
49
  /** Keyed on `${sha256}:${transformName}:${version}` — see file header. */
49
- export const withRenditionLock = createKeyedLock();
50
+ export const withRenditionLock = createKeyedLock({});
50
51
  //# sourceMappingURL=transform-lock.js.map
@@ -6,7 +6,7 @@
6
6
  * Mirrors `media-service.ts`'s shape: plain async functions taking
7
7
  * `{ deps, input }` (+ optional `options`), typed domain errors.
8
8
  */
9
- import type { ClockPort, IdGeneratorPort, UUID } from "../core/ports.js";
9
+ import type { Clock, IdGenerator, UUID } from "@jini-ai/core/primitives";
10
10
  import type { TransformDefinitionRepoPort } from "./ports.js";
11
11
  import { TransformValidationError, type TransformDefinitionRecord, type TransformParams } from "./transform-types.js";
12
12
  export { TransformValidationError };
@@ -18,8 +18,8 @@ export interface RegisterTransformInput {
18
18
  owner: string;
19
19
  }
20
20
  export interface RegisterTransformDeps {
21
- clock: ClockPort;
22
- idGen: IdGeneratorPort;
21
+ clock: Clock;
22
+ idGen: IdGenerator;
23
23
  transformRepo: TransformDefinitionRepoPort;
24
24
  }
25
25
  export interface RegisterTransformRequired {
@@ -1,3 +1,4 @@
1
+ import { nowIso as kernelNowIso } from "@jini-ai/core/primitives";
1
2
  import { assertValidTransformParams, TransformValidationError } from "./transform-types.js";
2
3
  import { withTransformRegistryLock } from "./transform-lock.js";
3
4
  export { TransformValidationError };
@@ -21,26 +22,26 @@ export async function registerTransform(required, _optional = {}) {
21
22
  const { deps, input } = required;
22
23
  const name = input.name.trim();
23
24
  if (!name)
24
- throw new TransformValidationError("transform name is required");
25
+ throw new TransformValidationError({ message: "transform name is required" });
25
26
  const owner = input.owner.trim();
26
27
  if (!owner)
27
- throw new TransformValidationError("owner is required");
28
- assertValidTransformParams(input.params);
29
- return withTransformRegistryLock(`${input.workspaceId}:${name}`, async () => {
30
- const existingVersions = await deps.transformRepo.listByName({ workspaceId: input.workspaceId, name });
31
- const currentMaxVersion = existingVersions.reduce((max, row) => Math.max(max, row.version), 0);
32
- const definition = {
33
- id: deps.idGen.newId(),
34
- workspaceId: input.workspaceId,
35
- name,
36
- version: currentMaxVersion + 1,
37
- params: input.params,
38
- owner,
39
- createdAt: deps.clock.nowIso(),
40
- };
41
- await deps.transformRepo.insert(definition);
42
- return { definition };
43
- });
28
+ throw new TransformValidationError({ message: "owner is required" });
29
+ assertValidTransformParams({ format: input.params.format }, { width: input.params.width, height: input.params.height, fit: input.params.fit });
30
+ return withTransformRegistryLock({ key: `${input.workspaceId}:${name}`, criticalSection: async () => {
31
+ const existingVersions = await deps.transformRepo.listByName({ workspaceId: input.workspaceId, name });
32
+ const currentMaxVersion = existingVersions.reduce((max, row) => Math.max(max, row.version), 0);
33
+ const definition = {
34
+ id: deps.idGen.newId(),
35
+ workspaceId: input.workspaceId,
36
+ name,
37
+ version: currentMaxVersion + 1,
38
+ params: input.params,
39
+ owner,
40
+ createdAt: kernelNowIso({ clock: deps.clock }),
41
+ };
42
+ await deps.transformRepo.insert(definition);
43
+ return { definition };
44
+ } });
44
45
  }
45
46
  /**
46
47
  * Returns the highest-`version` row registered for `name`, or `null` if
@@ -21,7 +21,7 @@
21
21
  * solves the identical "who registered this, extensibly" problem for the
22
22
  * permission catalog.
23
23
  */
24
- import type { ISODateTime, UUID } from "../core/ports.js";
24
+ import type { ISODateTime, UUID } from "@jini-ai/core/primitives";
25
25
  /** Resize behavior, mirrors `sharp`'s `fit` vocabulary (a deliberately small subset). */
26
26
  export type TransformFit = "cover" | "contain" | "fill" | "inside" | "outside";
27
27
  /**
@@ -60,12 +60,17 @@ export interface TransformDefinitionRecord {
60
60
  createdAt: ISODateTime;
61
61
  }
62
62
  export declare class TransformValidationError extends Error {
63
+ constructor({ message }: {
64
+ message: string;
65
+ }, optionalArgs?: ErrorOptions);
63
66
  }
64
67
  /**
65
68
  * @complexity O(1).
66
69
  * @overallScore 100
67
70
  */
68
- export declare function mimeForTransformFormat(format: TransformFormat): string;
71
+ export declare function mimeForTransformFormat(requiredArgs: {
72
+ format: TransformFormat;
73
+ }, optionalArgs?: Record<string, never>): string;
69
74
  /** Upper bound on a requested resize dimension — an output-side resource-use guard (not the
70
75
  * full pixel-bomb ingest cap, which polices upload input; this polices the transform
71
76
  * definition itself so a registered definition can't ask the image transformer to allocate an
@@ -78,5 +83,5 @@ export declare const MAX_TRANSFORM_DIMENSION_PX = 8000;
78
83
  * @complexity O(1).
79
84
  * @overallScore 100
80
85
  */
81
- export declare function assertValidTransformParams(params: TransformParams): void;
86
+ export declare function assertValidTransformParams(required: Pick<TransformParams, "format">, optional?: Omit<TransformParams, "format">): void;
82
87
  //# sourceMappingURL=transform-types.d.ts.map
@@ -1,4 +1,7 @@
1
1
  export class TransformValidationError extends Error {
2
+ constructor({ message }, optionalArgs = {}) {
3
+ super(message, optionalArgs);
4
+ }
2
5
  }
3
6
  /** Maps a {@link TransformFormat} to the MIME type served on the rendition response. */
4
7
  const MIME_BY_TRANSFORM_FORMAT = {
@@ -11,7 +14,8 @@ const MIME_BY_TRANSFORM_FORMAT = {
11
14
  * @complexity O(1).
12
15
  * @overallScore 100
13
16
  */
14
- export function mimeForTransformFormat(format) {
17
+ export function mimeForTransformFormat(requiredArgs, optionalArgs = {}) {
18
+ const { format } = requiredArgs;
15
19
  return MIME_BY_TRANSFORM_FORMAT[format];
16
20
  }
17
21
  /** Upper bound on a requested resize dimension — an output-side resource-use guard (not the
@@ -26,9 +30,10 @@ export const MAX_TRANSFORM_DIMENSION_PX = 8000;
26
30
  * @complexity O(1).
27
31
  * @overallScore 100
28
32
  */
29
- export function assertValidTransformParams(params) {
33
+ export function assertValidTransformParams(required, optional = {}) {
34
+ const params = { format: required.format, ...optional };
30
35
  if (!params.format || !(params.format in MIME_BY_TRANSFORM_FORMAT)) {
31
- throw new TransformValidationError(`transform params.format must be one of ${Object.keys(MIME_BY_TRANSFORM_FORMAT).join(", ")}`);
36
+ throw new TransformValidationError({ message: `transform params.format must be one of ${Object.keys(MIME_BY_TRANSFORM_FORMAT).join(", ")}` });
32
37
  }
33
38
  for (const [key, value] of [
34
39
  ["width", params.width],
@@ -37,11 +42,11 @@ export function assertValidTransformParams(params) {
37
42
  if (value === undefined)
38
43
  continue;
39
44
  if (!Number.isInteger(value) || value < 1 || value > MAX_TRANSFORM_DIMENSION_PX) {
40
- throw new TransformValidationError(`transform params.${key} must be an integer between 1 and ${MAX_TRANSFORM_DIMENSION_PX}`);
45
+ throw new TransformValidationError({ message: `transform params.${key} must be an integer between 1 and ${MAX_TRANSFORM_DIMENSION_PX}` });
41
46
  }
42
47
  }
43
48
  if (params.fit !== undefined && !(params.width && params.height)) {
44
- throw new TransformValidationError("transform params.fit requires both width and height");
49
+ throw new TransformValidationError({ message: "transform params.fit requires both width and height" });
45
50
  }
46
51
  }
47
52
  //# sourceMappingURL=transform-types.js.map
@@ -22,7 +22,7 @@
22
22
  * the original design describes — they were always meant to be core-owned tables
23
23
  * independent of the entries model, so no scope adjustment was needed there.
24
24
  */
25
- import type { ISODateTime, UUID } from "../core/ports.js";
25
+ import type { ISODateTime, UUID } from "@jini-ai/core/primitives";
26
26
  /** Deletion ladder status (mirrored from `navigation`'s menu ladder). */
27
27
  export type MediaStatus = "active" | "trashed";
28
28
  /**
@@ -91,13 +91,14 @@ export interface MediaRecord {
91
91
  htmlAttributes: string | null;
92
92
  }
93
93
  /**
94
- * Blob-GC lifecycle status (INV-1's two-phase journaled protocol,
94
+ * Blob-GC lifecycle status ( two-phase journaled protocol,
95
95
  * implemented in `blob-gc.ts`). `active` — has at least one live reference as
96
96
  * of the last check (or has never been checked). `tombstoned` — the
97
97
  * tombstone-pass found it unreferenced and is waiting out `gc_grace` before
98
98
  * the delete-pass may remove the row; a dedup upload observing `tombstoned`
99
99
  * resurrects it back to `active` in the same locked section instead of
100
100
  * writing a duplicate blob.
101
+ * See docs/decisions/DR-004-journaled-blob-gc.md.
101
102
  */
102
103
  export type AssetBlobStatus = "active" | "tombstoned";
103
104
  /**
@@ -119,7 +120,7 @@ export interface AssetBlobRecord {
119
120
  tombstonedAt?: ISODateTime | undefined;
120
121
  }
121
122
  /**
122
- * `blob_gc_journal` sidecar (INV-1's two-phase protocol,
123
+ * `blob_gc_journal` sidecar ( two-phase protocol,
123
124
  * `blob-gc.ts`): written by the delete-pass in the same locked step as the
124
125
  * `asset_blobs` row deletion, drained by the unlink-pass. This is what makes
125
126
  * the unlink crash-safe/retriable instead of "delete row, hope the unlink
@@ -128,6 +129,7 @@ export interface AssetBlobRecord {
128
129
  * in-memory table like every other `media` repo in this pass — it does not
129
130
  * survive a process restart; see `repo.memory.ts` file header for the
130
131
  * standing disclosed precedent.)
132
+ * See docs/decisions/DR-004-journaled-blob-gc.md.
131
133
  */
132
134
  export interface BlobGcJournalEntry {
133
135
  id: UUID;
@@ -152,16 +154,28 @@ export interface AssetRenditionRecord {
152
154
  createdAt: ISODateTime;
153
155
  }
154
156
  export declare class MediaNotFoundError extends Error {
157
+ constructor({ message }: {
158
+ message: string;
159
+ }, optionalArgs?: ErrorOptions);
155
160
  }
156
161
  export declare class MediaValidationError extends Error {
162
+ constructor({ message }: {
163
+ message: string;
164
+ }, optionalArgs?: ErrorOptions);
157
165
  }
158
166
  export declare class MediaConflictError extends Error {
167
+ constructor({ message }: {
168
+ message: string;
169
+ }, optionalArgs?: ErrorOptions);
159
170
  }
160
171
  /**
161
172
  * The write-once source guard rejected an attempt to change an
162
173
  * already-set `source.sha256` ("settable-once-from-absent").
163
174
  */
164
175
  export declare class MediaSourceImmutableError extends MediaValidationError {
176
+ constructor({ message }: {
177
+ message: string;
178
+ }, optionalArgs?: ErrorOptions);
165
179
  }
166
180
  /**
167
181
  * The 409-style purge rejection (deletion ladder, mirrors
@@ -171,6 +185,9 @@ export declare class MediaSourceImmutableError extends MediaValidationError {
171
185
  */
172
186
  export declare class MediaStillReferencedError extends MediaConflictError {
173
187
  readonly referencing: readonly string[];
174
- constructor(message: string, referencing: readonly string[]);
188
+ constructor(requiredArgs: {
189
+ message: string;
190
+ referencing: readonly string[];
191
+ }, optionalArgs?: Record<string, never>);
175
192
  }
176
193
  //# sourceMappingURL=types.d.ts.map
@@ -1,14 +1,26 @@
1
1
  export class MediaNotFoundError extends Error {
2
+ constructor({ message }, optionalArgs = {}) {
3
+ super(message, optionalArgs);
4
+ }
2
5
  }
3
6
  export class MediaValidationError extends Error {
7
+ constructor({ message }, optionalArgs = {}) {
8
+ super(message, optionalArgs);
9
+ }
4
10
  }
5
11
  export class MediaConflictError extends Error {
12
+ constructor({ message }, optionalArgs = {}) {
13
+ super(message, optionalArgs);
14
+ }
6
15
  }
7
16
  /**
8
17
  * The write-once source guard rejected an attempt to change an
9
18
  * already-set `source.sha256` ("settable-once-from-absent").
10
19
  */
11
20
  export class MediaSourceImmutableError extends MediaValidationError {
21
+ constructor({ message }, optionalArgs = {}) {
22
+ super({ message }, optionalArgs);
23
+ }
12
24
  }
13
25
  /**
14
26
  * The 409-style purge rejection (deletion ladder, mirrors
@@ -18,8 +30,9 @@ export class MediaSourceImmutableError extends MediaValidationError {
18
30
  */
19
31
  export class MediaStillReferencedError extends MediaConflictError {
20
32
  referencing;
21
- constructor(message, referencing) {
22
- super(message);
33
+ constructor(requiredArgs, optionalArgs = {}) {
34
+ const { message, referencing } = requiredArgs;
35
+ super({ message: message });
23
36
  this.referencing = referencing;
24
37
  }
25
38
  }