@orthacms/content-server 0.4.2 → 0.5.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 (246) hide show
  1. package/dist/index.d.ts +16 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +41 -1
  4. package/dist/lib/content.module.d.ts.map +1 -1
  5. package/dist/lib/content.module.js +31 -1
  6. package/dist/lib/copilot/entry-proposal.applier.d.ts.map +1 -1
  7. package/dist/lib/copilot/entry-proposal.applier.js +5 -4
  8. package/dist/lib/docs/describe-content-api.d.ts +7 -0
  9. package/dist/lib/docs/describe-content-api.d.ts.map +1 -1
  10. package/dist/lib/docs/describe-content-api.js +177 -53
  11. package/dist/lib/docs/describe-views-api.d.ts +19 -0
  12. package/dist/lib/docs/describe-views-api.d.ts.map +1 -0
  13. package/dist/lib/docs/describe-views-api.js +76 -0
  14. package/dist/lib/docs/field-schema.d.ts.map +1 -1
  15. package/dist/lib/docs/field-schema.js +36 -2
  16. package/dist/lib/docs/openapi-writer.d.ts +49 -0
  17. package/dist/lib/docs/openapi-writer.d.ts.map +1 -0
  18. package/dist/lib/docs/openapi-writer.js +76 -0
  19. package/dist/lib/docs/public-api-schemas.d.ts +45 -0
  20. package/dist/lib/docs/public-api-schemas.d.ts.map +1 -0
  21. package/dist/lib/docs/public-api-schemas.js +377 -0
  22. package/dist/lib/docs/views-schemas.d.ts +22 -0
  23. package/dist/lib/docs/views-schemas.d.ts.map +1 -0
  24. package/dist/lib/docs/views-schemas.js +156 -0
  25. package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.d.ts +8 -2
  26. package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.d.ts.map +1 -1
  27. package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.js +19 -4
  28. package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.d.ts +2 -2
  29. package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.d.ts.map +1 -1
  30. package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.js +14 -4
  31. package/dist/lib/entries/application/use-cases/publish-entry.use-case.d.ts +6 -8
  32. package/dist/lib/entries/application/use-cases/publish-entry.use-case.d.ts.map +1 -1
  33. package/dist/lib/entries/application/use-cases/publish-entry.use-case.js +10 -4
  34. package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.d.ts +2 -5
  35. package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.d.ts.map +1 -1
  36. package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.js +6 -1
  37. package/dist/lib/entries/domain/entry.d.ts +8 -1
  38. package/dist/lib/entries/domain/entry.d.ts.map +1 -1
  39. package/dist/lib/entries/domain/entry.js +12 -12
  40. package/dist/lib/entries/domain/events/entry-events.d.ts +47 -5
  41. package/dist/lib/entries/domain/events/entry-events.d.ts.map +1 -1
  42. package/dist/lib/entries/domain/events/entry-events.js +26 -10
  43. package/dist/lib/entries/entries.constants.d.ts +10 -2
  44. package/dist/lib/entries/entries.constants.d.ts.map +1 -1
  45. package/dist/lib/entries/entries.constants.js +11 -2
  46. package/dist/lib/entries/http/controllers/bulk-entries.controller.d.ts +2 -2
  47. package/dist/lib/entries/http/controllers/bulk-entries.controller.d.ts.map +1 -1
  48. package/dist/lib/entries/http/controllers/bulk-entries.controller.js +8 -6
  49. package/dist/lib/entries/http/controllers/create-entry.controller.d.ts.map +1 -1
  50. package/dist/lib/entries/http/controllers/create-entry.controller.js +1 -1
  51. package/dist/lib/entries/http/controllers/update-entry.controller.d.ts.map +1 -1
  52. package/dist/lib/entries/http/controllers/update-entry.controller.js +1 -1
  53. package/dist/lib/entries/http/dto/extension-bag.validator.d.ts +14 -0
  54. package/dist/lib/entries/http/dto/extension-bag.validator.d.ts.map +1 -0
  55. package/dist/lib/entries/http/dto/extension-bag.validator.js +42 -0
  56. package/dist/lib/entries/http/dto/save-entry.dto.d.ts +16 -0
  57. package/dist/lib/entries/http/dto/save-entry.dto.d.ts.map +1 -1
  58. package/dist/lib/entries/http/dto/save-entry.dto.js +30 -0
  59. package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts +50 -8
  60. package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts.map +1 -1
  61. package/dist/lib/entries/infrastructure/persistence/entry-writer.service.js +183 -32
  62. package/dist/lib/entries/infrastructure/persistence/relation-link.service.d.ts +38 -5
  63. package/dist/lib/entries/infrastructure/persistence/relation-link.service.d.ts.map +1 -1
  64. package/dist/lib/entries/infrastructure/persistence/relation-link.service.js +38 -6
  65. package/dist/lib/entries/infrastructure/queries/entries.service.d.ts +3 -1
  66. package/dist/lib/entries/infrastructure/queries/entries.service.d.ts.map +1 -1
  67. package/dist/lib/entries/infrastructure/queries/entries.service.js +17 -5
  68. package/dist/lib/entries/infrastructure/queries/entry-match.query.d.ts +82 -0
  69. package/dist/lib/entries/infrastructure/queries/entry-match.query.d.ts.map +1 -0
  70. package/dist/lib/entries/infrastructure/queries/entry-match.query.js +152 -0
  71. package/dist/lib/extension/entry-filter-provider.d.ts +79 -0
  72. package/dist/lib/extension/entry-filter-provider.d.ts.map +1 -0
  73. package/dist/lib/extension/entry-filter-provider.js +149 -0
  74. package/dist/lib/extension/entry-write-extension.d.ts +185 -0
  75. package/dist/lib/extension/entry-write-extension.d.ts.map +1 -0
  76. package/dist/lib/extension/entry-write-extension.js +182 -0
  77. package/dist/lib/extension/read-scope.d.ts +107 -0
  78. package/dist/lib/extension/read-scope.d.ts.map +1 -0
  79. package/dist/lib/extension/read-scope.js +131 -0
  80. package/dist/lib/insights/docs/describe-content-insights-api.d.ts +23 -0
  81. package/dist/lib/insights/docs/describe-content-insights-api.d.ts.map +1 -0
  82. package/dist/lib/insights/docs/describe-content-insights-api.js +329 -0
  83. package/dist/lib/mcp/content-tools.provider.d.ts.map +1 -1
  84. package/dist/lib/mcp/content-tools.provider.js +10 -9
  85. package/dist/lib/public-api/http/controllers/public-entry-writes.controller.d.ts +14 -13
  86. package/dist/lib/public-api/http/controllers/public-entry-writes.controller.d.ts.map +1 -1
  87. package/dist/lib/public-api/http/controllers/public-entry-writes.controller.js +62 -46
  88. package/dist/lib/public-api/http/guards/api-token-workspace.guard.d.ts.map +1 -1
  89. package/dist/lib/public-api/http/guards/api-token-workspace.guard.js +8 -0
  90. package/dist/lib/public-api/infrastructure/public-entries.query.d.ts +14 -1
  91. package/dist/lib/public-api/infrastructure/public-entries.query.d.ts.map +1 -1
  92. package/dist/lib/public-api/infrastructure/public-entries.query.js +20 -3
  93. package/dist/lib/public-api/infrastructure/public-entry-writes.service.d.ts +10 -9
  94. package/dist/lib/public-api/infrastructure/public-entry-writes.service.d.ts.map +1 -1
  95. package/dist/lib/public-api/infrastructure/public-entry-writes.service.js +23 -24
  96. package/dist/lib/public-api/infrastructure/public-expansion.query.d.ts +16 -4
  97. package/dist/lib/public-api/infrastructure/public-expansion.query.d.ts.map +1 -1
  98. package/dist/lib/public-api/infrastructure/public-expansion.query.js +29 -9
  99. package/dist/lib/public-api/infrastructure/token-actor.d.ts +37 -0
  100. package/dist/lib/public-api/infrastructure/token-actor.d.ts.map +1 -0
  101. package/dist/lib/public-api/infrastructure/token-actor.js +58 -0
  102. package/dist/lib/revisions/application/use-cases/restore-revision.use-case.d.ts.map +1 -1
  103. package/dist/lib/revisions/application/use-cases/restore-revision.use-case.js +23 -1
  104. package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.d.ts +7 -1
  105. package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.d.ts.map +1 -1
  106. package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.js +9 -2
  107. package/dist/lib/revisions/infrastructure/purge/revisions-workspace.purger.d.ts +28 -0
  108. package/dist/lib/revisions/infrastructure/purge/revisions-workspace.purger.d.ts.map +1 -0
  109. package/dist/lib/revisions/infrastructure/purge/revisions-workspace.purger.js +52 -0
  110. package/dist/lib/revisions/types/revision-view.d.ts +11 -0
  111. package/dist/lib/revisions/types/revision-view.d.ts.map +1 -1
  112. package/dist/lib/utils/content-plugin.d.ts.map +1 -1
  113. package/dist/lib/utils/content-plugin.js +10 -1
  114. package/dist/lib/utils/content-views-plugin.d.ts +28 -0
  115. package/dist/lib/utils/content-views-plugin.d.ts.map +1 -0
  116. package/dist/lib/utils/content-views-plugin.js +37 -0
  117. package/dist/lib/views/application/queries/saved-views.query.d.ts +16 -0
  118. package/dist/lib/views/application/queries/saved-views.query.d.ts.map +1 -0
  119. package/dist/lib/views/application/queries/saved-views.query.js +46 -0
  120. package/dist/lib/views/application/saved-view-access.service.d.ts +30 -0
  121. package/dist/lib/views/application/saved-view-access.service.d.ts.map +1 -0
  122. package/dist/lib/views/application/saved-view-access.service.js +55 -0
  123. package/dist/lib/views/application/use-cases/create-saved-view.use-case.d.ts +30 -0
  124. package/dist/lib/views/application/use-cases/create-saved-view.use-case.d.ts.map +1 -0
  125. package/dist/lib/views/application/use-cases/create-saved-view.use-case.js +81 -0
  126. package/dist/lib/views/application/use-cases/delete-saved-view.use-case.d.ts +18 -0
  127. package/dist/lib/views/application/use-cases/delete-saved-view.use-case.d.ts.map +1 -0
  128. package/dist/lib/views/application/use-cases/delete-saved-view.use-case.js +56 -0
  129. package/dist/lib/views/application/use-cases/set-default-view.use-case.d.ts +23 -0
  130. package/dist/lib/views/application/use-cases/set-default-view.use-case.d.ts.map +1 -0
  131. package/dist/lib/views/application/use-cases/set-default-view.use-case.js +51 -0
  132. package/dist/lib/views/application/use-cases/update-saved-view.use-case.d.ts +24 -0
  133. package/dist/lib/views/application/use-cases/update-saved-view.use-case.d.ts.map +1 -0
  134. package/dist/lib/views/application/use-cases/update-saved-view.use-case.js +90 -0
  135. package/dist/lib/views/application/view-scope.service.d.ts +25 -0
  136. package/dist/lib/views/application/view-scope.service.d.ts.map +1 -0
  137. package/dist/lib/views/application/view-scope.service.js +45 -0
  138. package/dist/lib/views/content-views.module.d.ts +22 -0
  139. package/dist/lib/views/content-views.module.d.ts.map +1 -0
  140. package/dist/lib/views/content-views.module.js +63 -0
  141. package/dist/lib/views/domain/errors/index.d.ts +6 -0
  142. package/dist/lib/views/domain/errors/index.d.ts.map +1 -0
  143. package/dist/lib/views/domain/errors/index.js +12 -0
  144. package/dist/lib/views/domain/errors/saved-view-forbidden.error.d.ts +10 -0
  145. package/dist/lib/views/domain/errors/saved-view-forbidden.error.d.ts.map +1 -0
  146. package/dist/lib/views/domain/errors/saved-view-forbidden.error.js +16 -0
  147. package/dist/lib/views/domain/errors/saved-view-limit-error.d.ts +9 -0
  148. package/dist/lib/views/domain/errors/saved-view-limit-error.d.ts.map +1 -0
  149. package/dist/lib/views/domain/errors/saved-view-limit-error.js +16 -0
  150. package/dist/lib/views/domain/errors/saved-view-name-taken.error.d.ts +5 -0
  151. package/dist/lib/views/domain/errors/saved-view-name-taken.error.d.ts.map +1 -0
  152. package/dist/lib/views/domain/errors/saved-view-name-taken.error.js +11 -0
  153. package/dist/lib/views/domain/errors/saved-view-not-found.error.d.ts +9 -0
  154. package/dist/lib/views/domain/errors/saved-view-not-found.error.d.ts.map +1 -0
  155. package/dist/lib/views/domain/errors/saved-view-not-found.error.js +15 -0
  156. package/dist/lib/views/domain/events/saved-view-events.d.ts +28 -0
  157. package/dist/lib/views/domain/events/saved-view-events.d.ts.map +1 -0
  158. package/dist/lib/views/domain/events/saved-view-events.js +38 -0
  159. package/dist/lib/views/domain/saved-view.d.ts +66 -0
  160. package/dist/lib/views/domain/saved-view.d.ts.map +1 -0
  161. package/dist/lib/views/domain/saved-view.js +18 -0
  162. package/dist/lib/views/domain/saved-view.repository.d.ts +77 -0
  163. package/dist/lib/views/domain/saved-view.repository.d.ts.map +1 -0
  164. package/dist/lib/views/domain/saved-view.repository.js +5 -0
  165. package/dist/lib/views/http/controllers/saved-views.controller.d.ts +45 -0
  166. package/dist/lib/views/http/controllers/saved-views.controller.d.ts.map +1 -0
  167. package/dist/lib/views/http/controllers/saved-views.controller.js +216 -0
  168. package/dist/lib/views/http/controllers/to-http-error.d.ts +10 -0
  169. package/dist/lib/views/http/controllers/to-http-error.d.ts.map +1 -0
  170. package/dist/lib/views/http/controllers/to-http-error.js +26 -0
  171. package/dist/lib/views/http/dto/save-view.dto.d.ts +54 -0
  172. package/dist/lib/views/http/dto/save-view.dto.d.ts.map +1 -0
  173. package/dist/lib/views/http/dto/save-view.dto.js +230 -0
  174. package/dist/lib/views/http/dto/view-payload.validator.d.ts +20 -0
  175. package/dist/lib/views/http/dto/view-payload.validator.d.ts.map +1 -0
  176. package/dist/lib/views/http/dto/view-payload.validator.js +52 -0
  177. package/dist/lib/views/infrastructure/persistence/drizzle-saved-view.repository.d.ts +26 -0
  178. package/dist/lib/views/infrastructure/persistence/drizzle-saved-view.repository.d.ts.map +1 -0
  179. package/dist/lib/views/infrastructure/persistence/drizzle-saved-view.repository.js +135 -0
  180. package/dist/lib/views/infrastructure/schema/external-refs.d.ts +68 -0
  181. package/dist/lib/views/infrastructure/schema/external-refs.d.ts.map +1 -0
  182. package/dist/lib/views/infrastructure/schema/external-refs.js +29 -0
  183. package/dist/lib/views/infrastructure/schema/index.d.ts +8 -0
  184. package/dist/lib/views/infrastructure/schema/index.d.ts.map +1 -0
  185. package/dist/lib/views/infrastructure/schema/index.js +13 -0
  186. package/dist/lib/views/infrastructure/schema/saved-views.d.ts +275 -0
  187. package/dist/lib/views/infrastructure/schema/saved-views.d.ts.map +1 -0
  188. package/dist/lib/views/infrastructure/schema/saved-views.js +87 -0
  189. package/dist/lib/views/views.constants.d.ts +26 -0
  190. package/dist/lib/views/views.constants.d.ts.map +1 -0
  191. package/dist/lib/views/views.constants.js +28 -0
  192. package/dist/lib/views/views.tokens.d.ts +17 -0
  193. package/dist/lib/views/views.tokens.d.ts.map +1 -0
  194. package/dist/lib/views/views.tokens.js +21 -0
  195. package/migrations/0000_saved_views.sql +29 -0
  196. package/migrations/meta/0000_snapshot.json +256 -0
  197. package/migrations/meta/_journal.json +13 -0
  198. package/package.json +13 -12
  199. package/dist/lib/entries/controllers/bulk-entries.controller.d.ts +0 -26
  200. package/dist/lib/entries/controllers/bulk-entries.controller.d.ts.map +0 -1
  201. package/dist/lib/entries/controllers/create-entry.controller.d.ts +0 -20
  202. package/dist/lib/entries/controllers/create-entry.controller.d.ts.map +0 -1
  203. package/dist/lib/entries/controllers/delete-entry.controller.d.ts +0 -24
  204. package/dist/lib/entries/controllers/delete-entry.controller.d.ts.map +0 -1
  205. package/dist/lib/entries/controllers/get-entry.controller.d.ts +0 -34
  206. package/dist/lib/entries/controllers/get-entry.controller.d.ts.map +0 -1
  207. package/dist/lib/entries/controllers/list-entries.controller.d.ts +0 -20
  208. package/dist/lib/entries/controllers/list-entries.controller.d.ts.map +0 -1
  209. package/dist/lib/entries/controllers/publish-entry.controller.d.ts +0 -19
  210. package/dist/lib/entries/controllers/publish-entry.controller.d.ts.map +0 -1
  211. package/dist/lib/entries/controllers/resolve-type.d.ts +0 -9
  212. package/dist/lib/entries/controllers/resolve-type.d.ts.map +0 -1
  213. package/dist/lib/entries/controllers/update-entry.controller.d.ts +0 -18
  214. package/dist/lib/entries/controllers/update-entry.controller.d.ts.map +0 -1
  215. package/dist/lib/entries/dto/bulk-ids.dto.d.ts +0 -11
  216. package/dist/lib/entries/dto/bulk-ids.dto.d.ts.map +0 -1
  217. package/dist/lib/entries/dto/list-entries-query.dto.d.ts +0 -46
  218. package/dist/lib/entries/dto/list-entries-query.dto.d.ts.map +0 -1
  219. package/dist/lib/entries/dto/relation-delta-map.validator.d.ts +0 -19
  220. package/dist/lib/entries/dto/relation-delta-map.validator.d.ts.map +0 -1
  221. package/dist/lib/entries/dto/save-entry.dto.d.ts +0 -46
  222. package/dist/lib/entries/dto/save-entry.dto.d.ts.map +0 -1
  223. package/dist/lib/entries/infrastructure/persistence/field-selection.d.ts +0 -24
  224. package/dist/lib/entries/infrastructure/persistence/field-selection.d.ts.map +0 -1
  225. package/dist/lib/entries/infrastructure/queries/entry-filter-schema.d.ts +0 -40
  226. package/dist/lib/entries/infrastructure/queries/entry-filter-schema.d.ts.map +0 -1
  227. package/dist/lib/entries/services/entries.service.d.ts +0 -59
  228. package/dist/lib/entries/services/entries.service.d.ts.map +0 -1
  229. package/dist/lib/entries/services/entry-counter.service.d.ts +0 -31
  230. package/dist/lib/entries/services/entry-counter.service.d.ts.map +0 -1
  231. package/dist/lib/entries/services/entry-filter-schema.d.ts +0 -32
  232. package/dist/lib/entries/services/entry-filter-schema.d.ts.map +0 -1
  233. package/dist/lib/entries/services/entry-row.d.ts +0 -56
  234. package/dist/lib/entries/services/entry-row.d.ts.map +0 -1
  235. package/dist/lib/entries/services/entry-writer.service.d.ts +0 -193
  236. package/dist/lib/entries/services/entry-writer.service.d.ts.map +0 -1
  237. package/dist/lib/entries/services/relation-link.service.d.ts +0 -115
  238. package/dist/lib/entries/services/relation-link.service.d.ts.map +0 -1
  239. package/dist/lib/extension/per-locale-relation.d.ts +0 -18
  240. package/dist/lib/extension/per-locale-relation.d.ts.map +0 -1
  241. package/dist/lib/public/controllers/get-public-entry.controller.d.ts +0 -26
  242. package/dist/lib/public/controllers/get-public-entry.controller.d.ts.map +0 -1
  243. package/dist/lib/public/controllers/list-public-entries.controller.d.ts +0 -24
  244. package/dist/lib/public/controllers/list-public-entries.controller.d.ts.map +0 -1
  245. package/dist/lib/public/controllers/public-schema.controller.d.ts +0 -18
  246. package/dist/lib/public/controllers/public-schema.controller.d.ts.map +0 -1
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_EXTENSION_KEYS = void 0;
4
+ exports.MaxExtensionKeys = MaxExtensionKeys;
5
+ const class_validator_1 = require("class-validator");
6
+ /**
7
+ * Most extension keys one save may carry.
8
+ *
9
+ * A key names an installed plugin, so the real ceiling is single digits; this is
10
+ * a guard against a client posting an object with thousands of keys, each of
11
+ * which the registry would otherwise walk on every write. Deliberately not a
12
+ * bound on the *values* — those are the extensions' own contracts, and
13
+ * content-server declares the bag opaque.
14
+ */
15
+ exports.MAX_EXTENSION_KEYS = 16;
16
+ /** Rejects an extension bag with more keys than {@link MAX_EXTENSION_KEYS}. */
17
+ function MaxExtensionKeys(options) {
18
+ return function (target, propertyName) {
19
+ (0, class_validator_1.registerDecorator)({
20
+ name: 'maxExtensionKeys',
21
+ target: target.constructor,
22
+ propertyName,
23
+ options,
24
+ validator: {
25
+ validate(value) {
26
+ if (value === undefined || value === null)
27
+ return true;
28
+ if (typeof value !== 'object' || Array.isArray(value)) {
29
+ // `@IsObject()` owns that message; saying it twice
30
+ // would put two sentences about one field in the 400.
31
+ return true;
32
+ }
33
+ return (Object.keys(value).length <=
34
+ exports.MAX_EXTENSION_KEYS);
35
+ },
36
+ defaultMessage() {
37
+ return `extensions may name at most ${exports.MAX_EXTENSION_KEYS} plugins`;
38
+ }
39
+ }
40
+ });
41
+ };
42
+ }
@@ -44,5 +44,21 @@ export declare class SaveEntryDto {
44
44
  * the workspace. Read on create only.
45
45
  */
46
46
  localeGroupId?: string;
47
+ /**
48
+ * State a **different plugin** owns about this entry, keyed by that plugin's
49
+ * extension key — opaque here, exactly like {@link locale}: content-server
50
+ * forwards the bag to the bound entry-write extensions and never looks
51
+ * inside it. `@orthacms/segments-server` reads `access` from it.
52
+ *
53
+ * It rides the save body rather than a second request so the entry, its
54
+ * links, the plugin's state and the version recording all of them commit in
55
+ * one transaction — and so the version records what the save applied instead
56
+ * of what it replaced.
57
+ *
58
+ * A key **omitted** is left alone; a key present is written. An **unknown**
59
+ * key is ignored rather than refused, because the same client may be talking
60
+ * to a deployment without that plugin installed.
61
+ */
62
+ extensions?: Record<string, unknown>;
47
63
  }
48
64
  //# sourceMappingURL=save-entry.dto.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"save-entry.dto.d.ts","sourceRoot":"","sources":["../../../../../src/lib/entries/http/dto/save-entry.dto.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAiBjE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,YAAY;IACrB,wCAAwC;IASxC,MAAM,EAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEjC;;;OAGG;IAwBH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAE1C;;;;;OAKG;IAWH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;OAKG;IASH,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B"}
1
+ {"version":3,"file":"save-entry.dto.d.ts","sourceRoot":"","sources":["../../../../../src/lib/entries/http/dto/save-entry.dto.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAqBjE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,YAAY;IACrB,wCAAwC;IASxC,MAAM,EAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEjC;;;OAGG;IAwBH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAE1C;;;;;OAKG;IAWH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;OAKG;IASH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;;;;;;;;OAcG;IAYH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC"}
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
5
5
  const swagger_1 = require("@nestjs/swagger");
6
6
  const class_validator_1 = require("class-validator");
7
7
  const relation_delta_map_validator_1 = require("./relation-delta-map.validator");
8
+ const extension_bag_validator_1 = require("./extension-bag.validator");
8
9
  /** Longest accepted locale slug (BCP-47's practical bound). */
9
10
  const LOCALE_MAX_LENGTH = 35;
10
11
  /** OpenAPI schema of one uuid-array leg of a relation delta. */
@@ -58,6 +59,22 @@ class SaveEntryDto {
58
59
  * the workspace. Read on create only.
59
60
  */
60
61
  localeGroupId;
62
+ /**
63
+ * State a **different plugin** owns about this entry, keyed by that plugin's
64
+ * extension key — opaque here, exactly like {@link locale}: content-server
65
+ * forwards the bag to the bound entry-write extensions and never looks
66
+ * inside it. `@orthacms/segments-server` reads `access` from it.
67
+ *
68
+ * It rides the save body rather than a second request so the entry, its
69
+ * links, the plugin's state and the version recording all of them commit in
70
+ * one transaction — and so the version records what the save applied instead
71
+ * of what it replaced.
72
+ *
73
+ * A key **omitted** is left alone; a key present is written. An **unknown**
74
+ * key is ignored rather than refused, because the same client may be talking
75
+ * to a deployment without that plugin installed.
76
+ */
77
+ extensions;
61
78
  }
62
79
  exports.SaveEntryDto = SaveEntryDto;
63
80
  tslib_1.__decorate([
@@ -118,3 +135,16 @@ tslib_1.__decorate([
118
135
  (0, class_validator_1.IsUUID)(),
119
136
  tslib_1.__metadata("design:type", String)
120
137
  ], SaveEntryDto.prototype, "localeGroupId", void 0);
138
+ tslib_1.__decorate([
139
+ (0, swagger_1.ApiPropertyOptional)({
140
+ type: 'object',
141
+ maxProperties: extension_bag_validator_1.MAX_EXTENSION_KEYS,
142
+ additionalProperties: true,
143
+ example: { access: { allow: [], deny: [] } },
144
+ description: 'Per-plugin state stored alongside the entry, keyed by extension (e.g. `access`). Opaque to content-server: forwarded to the bound entry-write extensions, written inside the save’s transaction, and captured by the revision. An omitted key is left unchanged; an unknown key is ignored.'
145
+ }),
146
+ (0, class_validator_1.IsOptional)(),
147
+ (0, class_validator_1.IsObject)(),
148
+ (0, extension_bag_validator_1.MaxExtensionKeys)(),
149
+ tslib_1.__metadata("design:type", Object)
150
+ ], SaveEntryDto.prototype, "extensions", void 0);
@@ -1,6 +1,7 @@
1
1
  import { OutboxWriter, UnitOfWork, type Database, type EventActor } from '@orthacms/database';
2
2
  import { type ContentEntryExtension } from '../../../extension/entry-extension';
3
3
  import { type MediaAssetResolver } from '../../../extension/media-asset-resolver';
4
+ import { EntryWriteExtensionRegistry } from '../../../extension/entry-write-extension';
4
5
  import type { AnyContentType } from '../../../types/content-type';
5
6
  import { EntryValidationService, type ValidationIssue } from '../../../validation/services/entry-validation.service';
6
7
  import type { EntryRecord, RelationDelta, RelationFieldView } from '../../types/entry-list-view';
@@ -35,9 +36,10 @@ export declare class EntryWriterService {
35
36
  private readonly validation;
36
37
  private readonly relations;
37
38
  private readonly revisionStore;
39
+ private readonly writeExtensions;
38
40
  private readonly extension?;
39
41
  private readonly mediaResolver?;
40
- constructor(db: Database, uow: UnitOfWork, outbox: OutboxWriter, validation: EntryValidationService, relations: RelationLinkService, revisionStore: RevisionStore, extension?: ContentEntryExtension | undefined, mediaResolver?: MediaAssetResolver | undefined);
42
+ constructor(db: Database, uow: UnitOfWork, outbox: OutboxWriter, validation: EntryValidationService, relations: RelationLinkService, revisionStore: RevisionStore, writeExtensions: EntryWriteExtensionRegistry, extension?: ContentEntryExtension | undefined, mediaResolver?: MediaAssetResolver | undefined);
41
43
  /**
42
44
  * The active unit-of-work transaction, typed as the handle drizzle hands a
43
45
  * `db.transaction` callback.
@@ -49,16 +51,32 @@ export declare class EntryWriterService {
49
51
  * `uow.run(...)`, where the two are the same object.
50
52
  */
51
53
  private tx;
52
- /** One event per affected row, built from each row's own id. */
54
+ /**
55
+ * One event per affected row, built from each row's own id **and the row
56
+ * itself** — the latter because an entry event carries a frozen `title`
57
+ * snapshot, and a title is per row rather than per batch.
58
+ */
53
59
  private eventsFor;
60
+ /**
61
+ * What an entry event says about its subject: the type, the workspace, and
62
+ * a **frozen** human label read off the row being written.
63
+ *
64
+ * Frozen rather than looked up, for the same reason `actor_email` is: the
65
+ * audit row has to outlive its subject. `entry.purged` destroys the only
66
+ * other copy of the name, so a row that stored just the uuid could never
67
+ * answer "what did they delete" — not even in principle.
68
+ */
69
+ private subjectOf;
54
70
  /**
55
71
  * Append `events` to the transactional outbox, stamped with the acting
56
- * user, from inside the active unit of work.
72
+ * principal, from inside the active unit of work.
57
73
  *
58
- * `actor` is `null` for a write made with an API token: revisions record a
59
- * *user* id and a token is not one, and an audit row naming a person who
60
- * did not do it is worse than one that says "System". Attributing a token's
61
- * writes is a real gap that wants its own column, not a misused one.
74
+ * A **token** may be the actor here, and that is the whole difference from
75
+ * {@link revisionActorId} below: the audit row now carries an `actor_type`,
76
+ * so naming a credential is a fact rather than a misused user column. A
77
+ * write made with a bearer token used to reach the log as "System", which
78
+ * left every write over the public REST API, GraphQL and MCP unattributable
79
+ * to the credential that made it.
62
80
  */
63
81
  private emit;
64
82
  /**
@@ -119,6 +137,14 @@ export declare class EntryWriterService {
119
137
  * two of them from deadlocking on the pair.
120
138
  */
121
139
  private appendRevisionsFor;
140
+ /**
141
+ * The rows behind a set of ids, on this transaction.
142
+ *
143
+ * Soft-deleted rows are **included**: a locale in the trash is still a row
144
+ * whose state an extension just changed, and it comes back on restore
145
+ * expecting a timeline that says so.
146
+ */
147
+ private rowsByIds;
122
148
  /**
123
149
  * Create an entry **owned by `workspaceId`** (stamped onto the row so the
124
150
  * reader's workspace filter and every later scoped write see it). A
@@ -136,8 +162,13 @@ export declare class EntryWriterService {
136
162
  * extension validates them and stamps the envelope columns (a
137
163
  * `localeGroupId` makes the row a **sibling** in that translation group). A
138
164
  * duplicate `(group, locale)` surfaces as a **409** via {@link uniqueGuarded}.
165
+ *
166
+ * `extensions` is the same kind of thing one plugin further out: an opaque
167
+ * bag forwarded to the bound {@link EntryWriteExtensionRegistry}, so a plugin
168
+ * that owns state *about* an entry (segments' audiences) writes it inside
169
+ * this transaction and has it captured by the same version.
139
170
  */
140
- create(type: AnyContentType, values: Record<string, unknown>, workspaceId: string, relations?: Record<string, RelationDelta>, locale?: string, localeGroupId?: string, actor?: EventActor | null): Promise<EntryRecord>;
171
+ create(type: AnyContentType, values: Record<string, unknown>, workspaceId: string, relations?: Record<string, RelationDelta>, locale?: string, localeGroupId?: string, actor?: EventActor | null, extensions?: Record<string, unknown>): Promise<EntryRecord>;
141
172
  /**
142
173
  * First page (+ total) of every relation field's links for one live entry,
143
174
  * keyed by field name — what the editor loads on open. Each field is
@@ -180,6 +211,17 @@ export declare class EntryWriterService {
180
211
  * really did change.
181
212
  */
182
213
  appendRevision?: boolean;
214
+ /**
215
+ * Opaque per-plugin state to store alongside the entry, forwarded to
216
+ * the bound {@link EntryWriteExtensionRegistry} inside this write's
217
+ * transaction. A key the caller omits is left alone — a save that
218
+ * said nothing about audiences must not clear them.
219
+ *
220
+ * A **restore** passes the restored version's `snapshot.extra` here,
221
+ * which is what makes going back to Tuesday put Tuesday's audiences
222
+ * back too rather than leaving today's readers on Tuesday's words.
223
+ */
224
+ extensions?: Record<string, unknown>;
183
225
  }): Promise<EntryRecord>;
184
226
  /**
185
227
  * Stamp `status='published'` + `published_at`/`updated_at` on one live row,
@@ -1 +1 @@
1
- {"version":3,"file":"entry-writer.service.d.ts","sourceRoot":"","sources":["../../../../../src/lib/entries/infrastructure/persistence/entry-writer.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"entry-writer.service.d.ts","sourceRoot":"","sources":["../../../../../src/lib/entries/infrastructure/persistence/entry-writer.service.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,7 @@ const utils_server_1 = require("@orthacms/utils-server");
10
10
  const content_domain_1 = require("@orthacms/content-domain");
11
11
  const entry_extension_1 = require("../../../extension/entry-extension");
12
12
  const media_asset_resolver_1 = require("../../../extension/media-asset-resolver");
13
+ const entry_write_extension_1 = require("../../../extension/entry-write-extension");
13
14
  const media_accept_1 = require("./media-accept");
14
15
  const content_type_1 = require("../../../types/content-type");
15
16
  const fields_1 = require("../../../types/fields");
@@ -58,19 +59,28 @@ let EntryWriterService = class EntryWriterService {
58
59
  validation;
59
60
  relations;
60
61
  revisionStore;
62
+ writeExtensions;
61
63
  extension;
62
64
  mediaResolver;
63
65
  constructor(db,
64
66
  // The transaction boundary every write now runs inside, and the outbox
65
67
  // that joins it. Together they are what makes an entry's domain event
66
68
  // commit with — and never without — the row it describes.
67
- uow, outbox, validation, relations, revisionStore, extension, mediaResolver) {
69
+ uow, outbox, validation, relations, revisionStore,
70
+ // Every bound entry-write extension (segments' reader entitlements
71
+ // today) — state another plugin owns *about* the entry, written on this
72
+ // save's transaction and captured by its revision. A registry rather
73
+ // than a token because Nest cannot merge two bindings of one token, and
74
+ // an extension that silently stopped being called would silently stop
75
+ // restricting content.
76
+ writeExtensions, extension, mediaResolver) {
68
77
  this.db = db;
69
78
  this.uow = uow;
70
79
  this.outbox = outbox;
71
80
  this.validation = validation;
72
81
  this.relations = relations;
73
82
  this.revisionStore = revisionStore;
83
+ this.writeExtensions = writeExtensions;
74
84
  this.extension = extension;
75
85
  this.mediaResolver = mediaResolver;
76
86
  }
@@ -87,18 +97,40 @@ let EntryWriterService = class EntryWriterService {
87
97
  tx() {
88
98
  return this.uow.current();
89
99
  }
90
- /** One event per affected row, built from each row's own id. */
100
+ /**
101
+ * One event per affected row, built from each row's own id **and the row
102
+ * itself** — the latter because an entry event carries a frozen `title`
103
+ * snapshot, and a title is per row rather than per batch.
104
+ */
91
105
  eventsFor(rows, build) {
92
- return rows.map((row) => build(row['id']));
106
+ return rows.map((row) => build(row['id'], row));
107
+ }
108
+ /**
109
+ * What an entry event says about its subject: the type, the workspace, and
110
+ * a **frozen** human label read off the row being written.
111
+ *
112
+ * Frozen rather than looked up, for the same reason `actor_email` is: the
113
+ * audit row has to outlive its subject. `entry.purged` destroys the only
114
+ * other copy of the name, so a row that stored just the uuid could never
115
+ * answer "what did they delete" — not even in principle.
116
+ */
117
+ subjectOf(type, workspaceId, row) {
118
+ return {
119
+ contentType: type.name,
120
+ workspaceId,
121
+ title: row ? (0, entry_row_1.entryTitle)(type, row) : null
122
+ };
93
123
  }
94
124
  /**
95
125
  * Append `events` to the transactional outbox, stamped with the acting
96
- * user, from inside the active unit of work.
126
+ * principal, from inside the active unit of work.
97
127
  *
98
- * `actor` is `null` for a write made with an API token: revisions record a
99
- * *user* id and a token is not one, and an audit row naming a person who
100
- * did not do it is worse than one that says "System". Attributing a token's
101
- * writes is a real gap that wants its own column, not a misused one.
128
+ * A **token** may be the actor here, and that is the whole difference from
129
+ * {@link revisionActorId} below: the audit row now carries an `actor_type`,
130
+ * so naming a credential is a fact rather than a misused user column. A
131
+ * write made with a bearer token used to reach the log as "System", which
132
+ * left every write over the public REST API, GraphQL and MCP unattributable
133
+ * to the credential that made it.
102
134
  */
103
135
  async emit(events, actor) {
104
136
  if (events.length === 0)
@@ -216,6 +248,17 @@ let EntryWriterService = class EntryWriterService {
216
248
  async appendRevision(tx, type, row, workspaceId, actorId) {
217
249
  const id = row['id'];
218
250
  const links = await this.relations.snapshotLinks(tx, type, row);
251
+ // Whatever the bound extensions hold for this row *now* — read on the
252
+ // save's own transaction, after any `apply` has run, so the version
253
+ // records what committed. Captured unconditionally, including for a
254
+ // sibling row this save only touched indirectly: a version that omitted
255
+ // the state it did not change would restore as a version that had none.
256
+ const extra = await this.writeExtensions.captureAll({
257
+ executor: tx,
258
+ type,
259
+ entryId: id,
260
+ workspaceId
261
+ });
219
262
  await this.revisionStore.lockEntry(tx, id);
220
263
  const number = await this.revisionStore.nextNumber(tx, id);
221
264
  const revision = revision_1.Revision.createDraft({
@@ -225,7 +268,7 @@ let EntryWriterService = class EntryWriterService {
225
268
  localeGroupId: row['localeGroupId'] ?? null,
226
269
  locale: row['locale'] ?? null,
227
270
  number,
228
- snapshot: (0, revision_snapshot_1.buildSnapshot)(type, row, links),
271
+ snapshot: (0, revision_snapshot_1.buildSnapshot)(type, row, links, extra),
229
272
  createdBy: actorId
230
273
  });
231
274
  await this.revisionStore.append(tx, revision);
@@ -241,14 +284,50 @@ let EntryWriterService = class EntryWriterService {
241
284
  * advisory lock, and a stable order across concurrent savers is what keeps
242
285
  * two of them from deadlocking on the pair.
243
286
  */
244
- async appendRevisionsFor(tx, type, rows, workspaceId, actorId) {
245
- if (!rows?.length)
287
+ async appendRevisionsFor(tx, type, rows, workspaceId, actorId,
288
+ /**
289
+ * Ids of further rows an **entry-write extension** changed, which it
290
+ * reports rather than returns whole — it wrote a table of its own, not
291
+ * these ones, so it has no row to hand back. Merged with `rows` and
292
+ * deduplicated by id: one save can reach a sibling through both paths
293
+ * (a shared field *and* an audience), and appending two revisions for it
294
+ * would read as two edits.
295
+ */
296
+ alsoChangedIds = []) {
297
+ const byId = new Map();
298
+ for (const row of rows ?? [])
299
+ byId.set(String(row['id']), row);
300
+ const missing = alsoChangedIds.filter((id) => !byId.has(id));
301
+ for (const row of await this.rowsByIds(tx, type, missing, workspaceId)) {
302
+ byId.set(String(row['id']), row);
303
+ }
304
+ if (!byId.size)
246
305
  return;
247
- const ordered = [...rows].sort((a, b) => String(a['id']).localeCompare(String(b['id'])));
306
+ const ordered = [...byId.values()].sort((a, b) => String(a['id']).localeCompare(String(b['id'])));
248
307
  for (const row of ordered) {
249
308
  await this.appendRevision(tx, type, row, workspaceId, actorId);
250
309
  }
251
310
  }
311
+ /**
312
+ * The rows behind a set of ids, on this transaction.
313
+ *
314
+ * Soft-deleted rows are **included**: a locale in the trash is still a row
315
+ * whose state an extension just changed, and it comes back on restore
316
+ * expecting a timeline that says so.
317
+ */
318
+ async rowsByIds(tx, type, ids, workspaceId) {
319
+ if (!ids.length)
320
+ return [];
321
+ const columns = type.table;
322
+ const id = columns['id'];
323
+ const workspace = columns['workspaceId'];
324
+ if (!id || !workspace)
325
+ return [];
326
+ return (await tx
327
+ .select()
328
+ .from(type.table)
329
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.inArray)(id, [...ids]), (0, drizzle_orm_1.eq)(workspace, workspaceId))));
330
+ }
252
331
  /**
253
332
  * Create an entry **owned by `workspaceId`** (stamped onto the row so the
254
333
  * reader's workspace filter and every later scoped write see it). A
@@ -266,9 +345,14 @@ let EntryWriterService = class EntryWriterService {
266
345
  * extension validates them and stamps the envelope columns (a
267
346
  * `localeGroupId` makes the row a **sibling** in that translation group). A
268
347
  * duplicate `(group, locale)` surfaces as a **409** via {@link uniqueGuarded}.
348
+ *
349
+ * `extensions` is the same kind of thing one plugin further out: an opaque
350
+ * bag forwarded to the bound {@link EntryWriteExtensionRegistry}, so a plugin
351
+ * that owns state *about* an entry (segments' audiences) writes it inside
352
+ * this transaction and has it captured by the same version.
269
353
  */
270
- async create(type, values, workspaceId, relations, locale, localeGroupId, actor) {
271
- const actorId = actor?.id ?? null;
354
+ async create(type, values, workspaceId, relations, locale, localeGroupId, actor, extensions) {
355
+ const actorId = revisionActorId(actor);
272
356
  // `coerceValues` stamps every declared field onto the bag, so the
273
357
  // "sent in both places" check below has to read the RAW body — after
274
358
  // coercion every field looks present.
@@ -342,16 +426,42 @@ let EntryWriterService = class EntryWriterService {
342
426
  // group must land consistent with the group's shared values.
343
427
  // A no-op for a fresh, sibling-less group.
344
428
  const touched = await this.extension?.afterUpdate(tx, type, inserted, coerced, workspaceId, { created: true });
429
+ // Anything a bound plugin was asked to store about this
430
+ // entry — the row exists now, so it finally has something to
431
+ // hang off, and it lands before the snapshot below reads it
432
+ // back. A create is the case that forced this into the write
433
+ // at all: until the insert there is no id to write against.
434
+ const alsoChanged = await this.writeExtensions.applyAll({
435
+ executor: tx,
436
+ type,
437
+ entryId: id,
438
+ workspaceId
439
+ }, extensions);
440
+ // …and, for a row joining an existing locale group, whatever
441
+ // that group already holds. `applyAll` runs only for keys
442
+ // the caller sent, and "create a translation" sends none —
443
+ // so without this a translation of a restricted article was
444
+ // a public copy of it. After `applyAll`, so an extension the
445
+ // caller just told what to store is not overwritten.
446
+ await this.writeExtensions.inheritAll({
447
+ executor: tx,
448
+ type,
449
+ entryId: id,
450
+ workspaceId
451
+ });
345
452
  // Snapshot the just-created document as its first revision,
346
453
  // inside this same transaction.
347
454
  await this.appendRevision(tx, type, inserted, workspaceId, actorId ?? null);
348
- // …and one for every sibling the extension rewrote, so a
349
- // shared value landing on them is in their history too.
350
- await this.appendRevisionsFor(tx, type, touched, workspaceId, actorId ?? null);
455
+ // …and one for every sibling either extension path rewrote,
456
+ // so a shared value — or an audience — landing on them is in
457
+ // their history too.
458
+ await this.appendRevisionsFor(tx, type, touched, workspaceId, actorId ?? null, alsoChanged);
351
459
  // Inside the transaction, so the fact and the row it
352
460
  // describes commit together or not at all — the whole point
353
461
  // of the outbox.
354
- await this.emit([(0, entry_events_1.entryCreated)(id, type.name)], actor);
462
+ await this.emit([
463
+ (0, entry_events_1.entryCreated)(id, this.subjectOf(type, workspaceId, inserted))
464
+ ], actor);
355
465
  return inserted;
356
466
  }), type);
357
467
  return (0, entry_row_1.toRecord)(type, row);
@@ -431,7 +541,7 @@ let EntryWriterService = class EntryWriterService {
431
541
  * type validates now.
432
542
  */
433
543
  async update(type, id, values, workspaceId, relations, actor, options) {
434
- const actorId = actor?.id ?? null;
544
+ const actorId = revisionActorId(actor);
435
545
  let coerced = (0, entry_row_1.coerceValues)(type, values);
436
546
  // The row's own locale is what a relation link must match. Read up front
437
547
  // (one indexed lookup, and only on localized types) so the pre-transaction
@@ -504,19 +614,32 @@ let EntryWriterService = class EntryWriterService {
504
614
  // locale siblings) run inside the same transaction — a failure
505
615
  // rolls the whole save back.
506
616
  const touched = await this.extension?.afterUpdate(tx, type, updated, coerced, workspaceId, { created: false });
617
+ // Whatever a bound plugin was asked to store about this entry, on
618
+ // this transaction and before the snapshot reads it back — so the
619
+ // version records the audiences the save actually applied rather
620
+ // than the ones it replaced. This runs even when no version is
621
+ // appended (publish-a-version): the state is the entry's, not the
622
+ // revision's.
623
+ const alsoChanged = await this.writeExtensions.applyAll({ executor: tx, type, entryId: id, workspaceId }, options?.extensions);
507
624
  // Snapshot the updated document as a new draft revision, inside this
508
625
  // same transaction — unless the caller is re-applying a version that
509
626
  // already exists in the timeline (publish-a-version).
510
627
  if (options?.appendRevision ?? true) {
511
628
  await this.appendRevision(tx, type, updated, workspaceId, actorId ?? null);
512
629
  }
513
- // …and one for every sibling the extension rewrote.
514
- await this.appendRevisionsFor(tx, type, touched, workspaceId, actorId ?? null);
630
+ // …and one for every sibling either extension path rewrote — the
631
+ // rows i18n synced a shared field to, and the rows a write extension
632
+ // reports it also changed (segments writes an entry's audiences to
633
+ // its whole locale group). Both are the same defect if skipped: a
634
+ // row whose state moved while its timeline did not.
635
+ await this.appendRevisionsFor(tx, type, touched, workspaceId, actorId ?? null, alsoChanged);
515
636
  // A save that changed no field value raises nothing: it is a round
516
637
  // trip, not an editorial change, and the log is read by people.
517
638
  const fields = this.changedFields(type, before, updated);
518
639
  if (fields.length > 0) {
519
- await this.emit([(0, entry_events_1.entryUpdated)(id, type.name, fields)], actor);
640
+ await this.emit([
641
+ (0, entry_events_1.entryUpdated)(id, this.subjectOf(type, workspaceId, updated), fields)
642
+ ], actor);
520
643
  }
521
644
  return updated;
522
645
  });
@@ -685,7 +808,9 @@ let EntryWriterService = class EntryWriterService {
685
808
  .returning(), type);
686
809
  if (!row)
687
810
  throw this.notFound(type, id);
688
- await this.emit([(0, entry_events_1.entryDeleted)(id, type.name, Boolean(type.paranoid))], actor);
811
+ await this.emit([
812
+ (0, entry_events_1.entryDeleted)(id, this.subjectOf(type, workspaceId, row), Boolean(type.paranoid))
813
+ ], actor);
689
814
  });
690
815
  }
691
816
  /**
@@ -709,7 +834,9 @@ let EntryWriterService = class EntryWriterService {
709
834
  .returning(), type);
710
835
  if (!row)
711
836
  throw this.notFound(type, id);
712
- await this.emit([(0, entry_events_1.entryRestored)(id, type.name)], actor);
837
+ await this.emit([
838
+ (0, entry_events_1.entryRestored)(id, this.subjectOf(type, workspaceId, row))
839
+ ], actor);
713
840
  return (0, entry_row_1.toRecord)(type, row);
714
841
  });
715
842
  }
@@ -727,7 +854,9 @@ let EntryWriterService = class EntryWriterService {
727
854
  throw this.notFound(type, id);
728
855
  // The one content action that leaves nothing behind to inspect, so
729
856
  // the event is the only remaining record that it happened.
730
- await this.emit([(0, entry_events_1.entryPurged)(id, type.name)], actor);
857
+ await this.emit([
858
+ (0, entry_events_1.entryPurged)(id, this.subjectOf(type, workspaceId, row))
859
+ ], actor);
731
860
  });
732
861
  }
733
862
  /** Delete a set of entries: soft for paranoid types, hard otherwise. */
@@ -753,7 +882,7 @@ let EntryWriterService = class EntryWriterService {
753
882
  .returning(), type);
754
883
  // One event per row actually removed, not one per id asked for:
755
884
  // batching is a way of asking, and the log records what happened.
756
- await this.emit(this.eventsFor(rows, (id) => (0, entry_events_1.entryDeleted)(id, type.name, Boolean(type.paranoid))), actor);
885
+ await this.emit(this.eventsFor(rows, (id, row) => (0, entry_events_1.entryDeleted)(id, this.subjectOf(type, workspaceId, row), Boolean(type.paranoid))), actor);
757
886
  return { count: rows.length };
758
887
  });
759
888
  }
@@ -769,7 +898,7 @@ let EntryWriterService = class EntryWriterService {
769
898
  .delete(type.table)
770
899
  .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.inArray)(t['id'], ids), this.scope(type, workspaceId), (0, drizzle_orm_1.isNotNull)(t['deletedAt'])))
771
900
  .returning(), type);
772
- await this.emit(this.eventsFor(rows, (id) => (0, entry_events_1.entryPurged)(id, type.name)), actor);
901
+ await this.emit(this.eventsFor(rows, (id, row) => (0, entry_events_1.entryPurged)(id, this.subjectOf(type, workspaceId, row))), actor);
773
902
  return { count: rows.length };
774
903
  });
775
904
  }
@@ -794,7 +923,7 @@ let EntryWriterService = class EntryWriterService {
794
923
  })
795
924
  .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.inArray)(t['id'], ids), this.scope(type, workspaceId), (0, drizzle_orm_1.isNotNull)(t['deletedAt'])))
796
925
  .returning(), type);
797
- await this.emit(this.eventsFor(rows, (id) => (0, entry_events_1.entryRestored)(id, type.name)), actor);
926
+ await this.emit(this.eventsFor(rows, (id, row) => (0, entry_events_1.entryRestored)(id, this.subjectOf(type, workspaceId, row))), actor);
798
927
  return { count: rows.length };
799
928
  });
800
929
  }
@@ -1221,12 +1350,34 @@ exports.EntryWriterService = EntryWriterService = tslib_1.__decorate([
1221
1350
  (0, common_1.Injectable)(),
1222
1351
  tslib_1.__param(0, (0, database_1.InjectDatabase)()),
1223
1352
  tslib_1.__param(5, (0, revision_store_1.InjectRevisionStore)()),
1224
- tslib_1.__param(6, (0, common_1.Optional)()),
1225
- tslib_1.__param(6, (0, common_1.Inject)(entry_extension_1.CONTENT_ENTRY_EXTENSION)),
1226
1353
  tslib_1.__param(7, (0, common_1.Optional)()),
1227
- tslib_1.__param(7, (0, media_asset_resolver_1.InjectMediaAssetResolver)()),
1354
+ tslib_1.__param(7, (0, common_1.Inject)(entry_extension_1.CONTENT_ENTRY_EXTENSION)),
1355
+ tslib_1.__param(8, (0, common_1.Optional)()),
1356
+ tslib_1.__param(8, (0, media_asset_resolver_1.InjectMediaAssetResolver)()),
1228
1357
  tslib_1.__metadata("design:paramtypes", [Object, database_1.UnitOfWork,
1229
1358
  database_1.OutboxWriter,
1230
1359
  entry_validation_service_1.EntryValidationService,
1231
- relation_link_service_1.RelationLinkService, Object, Object, Object])
1360
+ relation_link_service_1.RelationLinkService, Object, entry_write_extension_1.EntryWriteExtensionRegistry, Object, Object])
1232
1361
  ], EntryWriterService);
1362
+ /**
1363
+ * The **user** id to stamp on a revision, or `null` when the writer is not a
1364
+ * person.
1365
+ *
1366
+ * `content_entry_revisions.created_by` records a `users` row. A token is not
1367
+ * one, so writing its id there would make the history resolve every version to
1368
+ * a nonexistent person — which is why the public API used to pass no actor at
1369
+ * all rather than pass the token. The audit path has since gained an
1370
+ * `actor_type` and can name a credential honestly, so the two answers had to be
1371
+ * separated: {@link EntryWriterService.emit} gets the real principal, and this
1372
+ * gets a user id or nothing.
1373
+ *
1374
+ * An actor with no `type` is a person — every actor predates the field except
1375
+ * the token ones that introduced it.
1376
+ */
1377
+ function revisionActorId(actor) {
1378
+ if (!actor)
1379
+ return null;
1380
+ return (actor.type ?? database_1.EVENT_ACTOR_TYPE.User) === database_1.EVENT_ACTOR_TYPE.User
1381
+ ? actor.id
1382
+ : null;
1383
+ }