@orthacms/content-server 0.4.2 → 0.4.3

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 (135) hide show
  1. package/dist/index.d.ts +15 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +38 -1
  4. package/dist/lib/content.module.d.ts.map +1 -1
  5. package/dist/lib/content.module.js +23 -1
  6. package/dist/lib/entries/http/controllers/create-entry.controller.d.ts.map +1 -1
  7. package/dist/lib/entries/http/controllers/create-entry.controller.js +1 -1
  8. package/dist/lib/entries/http/controllers/update-entry.controller.d.ts.map +1 -1
  9. package/dist/lib/entries/http/controllers/update-entry.controller.js +1 -1
  10. package/dist/lib/entries/http/dto/extension-bag.validator.d.ts +14 -0
  11. package/dist/lib/entries/http/dto/extension-bag.validator.d.ts.map +1 -0
  12. package/dist/lib/entries/http/dto/extension-bag.validator.js +42 -0
  13. package/dist/lib/entries/http/dto/save-entry.dto.d.ts +16 -0
  14. package/dist/lib/entries/http/dto/save-entry.dto.d.ts.map +1 -1
  15. package/dist/lib/entries/http/dto/save-entry.dto.js +30 -0
  16. package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts +28 -2
  17. package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts.map +1 -1
  18. package/dist/lib/entries/infrastructure/persistence/entry-writer.service.js +112 -15
  19. package/dist/lib/entries/infrastructure/persistence/relation-link.service.d.ts +38 -5
  20. package/dist/lib/entries/infrastructure/persistence/relation-link.service.d.ts.map +1 -1
  21. package/dist/lib/entries/infrastructure/persistence/relation-link.service.js +38 -6
  22. package/dist/lib/entries/infrastructure/queries/entries.service.d.ts +3 -1
  23. package/dist/lib/entries/infrastructure/queries/entries.service.d.ts.map +1 -1
  24. package/dist/lib/entries/infrastructure/queries/entries.service.js +17 -5
  25. package/dist/lib/entries/infrastructure/queries/entry-match.query.d.ts +82 -0
  26. package/dist/lib/entries/infrastructure/queries/entry-match.query.d.ts.map +1 -0
  27. package/dist/lib/entries/infrastructure/queries/entry-match.query.js +152 -0
  28. package/dist/lib/extension/entry-access-source.d.ts +112 -0
  29. package/dist/lib/extension/entry-access-source.d.ts.map +1 -0
  30. package/dist/lib/extension/entry-filter-provider.d.ts +79 -0
  31. package/dist/lib/extension/entry-filter-provider.d.ts.map +1 -0
  32. package/dist/lib/extension/entry-filter-provider.js +149 -0
  33. package/dist/lib/extension/entry-write-extension.d.ts +185 -0
  34. package/dist/lib/extension/entry-write-extension.d.ts.map +1 -0
  35. package/dist/lib/extension/entry-write-extension.js +182 -0
  36. package/dist/lib/extension/entry-write-hook.d.ts +98 -0
  37. package/dist/lib/extension/entry-write-hook.d.ts.map +1 -0
  38. package/dist/lib/extension/read-scope.d.ts +107 -0
  39. package/dist/lib/extension/read-scope.d.ts.map +1 -0
  40. package/dist/lib/extension/read-scope.js +131 -0
  41. package/dist/lib/public-api/infrastructure/public-entries.query.d.ts +14 -1
  42. package/dist/lib/public-api/infrastructure/public-entries.query.d.ts.map +1 -1
  43. package/dist/lib/public-api/infrastructure/public-entries.query.js +20 -3
  44. package/dist/lib/public-api/infrastructure/public-expansion.query.d.ts +16 -4
  45. package/dist/lib/public-api/infrastructure/public-expansion.query.d.ts.map +1 -1
  46. package/dist/lib/public-api/infrastructure/public-expansion.query.js +29 -9
  47. package/dist/lib/revisions/application/use-cases/restore-revision.use-case.d.ts.map +1 -1
  48. package/dist/lib/revisions/application/use-cases/restore-revision.use-case.js +12 -1
  49. package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.d.ts +7 -1
  50. package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.d.ts.map +1 -1
  51. package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.js +9 -2
  52. package/dist/lib/revisions/types/revision-view.d.ts +11 -0
  53. package/dist/lib/revisions/types/revision-view.d.ts.map +1 -1
  54. package/dist/lib/utils/content-views-plugin.d.ts +28 -0
  55. package/dist/lib/utils/content-views-plugin.d.ts.map +1 -0
  56. package/dist/lib/utils/content-views-plugin.js +29 -0
  57. package/dist/lib/views/application/queries/saved-views.query.d.ts +16 -0
  58. package/dist/lib/views/application/queries/saved-views.query.d.ts.map +1 -0
  59. package/dist/lib/views/application/queries/saved-views.query.js +46 -0
  60. package/dist/lib/views/application/saved-view-access.service.d.ts +30 -0
  61. package/dist/lib/views/application/saved-view-access.service.d.ts.map +1 -0
  62. package/dist/lib/views/application/saved-view-access.service.js +55 -0
  63. package/dist/lib/views/application/use-cases/create-saved-view.use-case.d.ts +27 -0
  64. package/dist/lib/views/application/use-cases/create-saved-view.use-case.d.ts.map +1 -0
  65. package/dist/lib/views/application/use-cases/create-saved-view.use-case.js +57 -0
  66. package/dist/lib/views/application/use-cases/delete-saved-view.use-case.d.ts +15 -0
  67. package/dist/lib/views/application/use-cases/delete-saved-view.use-case.d.ts.map +1 -0
  68. package/dist/lib/views/application/use-cases/delete-saved-view.use-case.js +35 -0
  69. package/dist/lib/views/application/use-cases/set-default-view.use-case.d.ts +23 -0
  70. package/dist/lib/views/application/use-cases/set-default-view.use-case.d.ts.map +1 -0
  71. package/dist/lib/views/application/use-cases/set-default-view.use-case.js +51 -0
  72. package/dist/lib/views/application/use-cases/update-saved-view.use-case.d.ts +21 -0
  73. package/dist/lib/views/application/use-cases/update-saved-view.use-case.d.ts.map +1 -0
  74. package/dist/lib/views/application/use-cases/update-saved-view.use-case.js +61 -0
  75. package/dist/lib/views/application/view-scope.service.d.ts +25 -0
  76. package/dist/lib/views/application/view-scope.service.d.ts.map +1 -0
  77. package/dist/lib/views/application/view-scope.service.js +45 -0
  78. package/dist/lib/views/content-views.module.d.ts +22 -0
  79. package/dist/lib/views/content-views.module.d.ts.map +1 -0
  80. package/dist/lib/views/content-views.module.js +63 -0
  81. package/dist/lib/views/domain/errors/index.d.ts +6 -0
  82. package/dist/lib/views/domain/errors/index.d.ts.map +1 -0
  83. package/dist/lib/views/domain/errors/index.js +12 -0
  84. package/dist/lib/views/domain/errors/saved-view-forbidden.error.d.ts +10 -0
  85. package/dist/lib/views/domain/errors/saved-view-forbidden.error.d.ts.map +1 -0
  86. package/dist/lib/views/domain/errors/saved-view-forbidden.error.js +16 -0
  87. package/dist/lib/views/domain/errors/saved-view-limit-error.d.ts +9 -0
  88. package/dist/lib/views/domain/errors/saved-view-limit-error.d.ts.map +1 -0
  89. package/dist/lib/views/domain/errors/saved-view-limit-error.js +16 -0
  90. package/dist/lib/views/domain/errors/saved-view-name-taken.error.d.ts +5 -0
  91. package/dist/lib/views/domain/errors/saved-view-name-taken.error.d.ts.map +1 -0
  92. package/dist/lib/views/domain/errors/saved-view-name-taken.error.js +11 -0
  93. package/dist/lib/views/domain/errors/saved-view-not-found.error.d.ts +9 -0
  94. package/dist/lib/views/domain/errors/saved-view-not-found.error.d.ts.map +1 -0
  95. package/dist/lib/views/domain/errors/saved-view-not-found.error.js +15 -0
  96. package/dist/lib/views/domain/saved-view.d.ts +66 -0
  97. package/dist/lib/views/domain/saved-view.d.ts.map +1 -0
  98. package/dist/lib/views/domain/saved-view.js +18 -0
  99. package/dist/lib/views/domain/saved-view.repository.d.ts +77 -0
  100. package/dist/lib/views/domain/saved-view.repository.d.ts.map +1 -0
  101. package/dist/lib/views/domain/saved-view.repository.js +5 -0
  102. package/dist/lib/views/http/controllers/saved-views.controller.d.ts +45 -0
  103. package/dist/lib/views/http/controllers/saved-views.controller.d.ts.map +1 -0
  104. package/dist/lib/views/http/controllers/saved-views.controller.js +216 -0
  105. package/dist/lib/views/http/controllers/to-http-error.d.ts +10 -0
  106. package/dist/lib/views/http/controllers/to-http-error.d.ts.map +1 -0
  107. package/dist/lib/views/http/controllers/to-http-error.js +26 -0
  108. package/dist/lib/views/http/dto/save-view.dto.d.ts +54 -0
  109. package/dist/lib/views/http/dto/save-view.dto.d.ts.map +1 -0
  110. package/dist/lib/views/http/dto/save-view.dto.js +230 -0
  111. package/dist/lib/views/http/dto/view-payload.validator.d.ts +20 -0
  112. package/dist/lib/views/http/dto/view-payload.validator.d.ts.map +1 -0
  113. package/dist/lib/views/http/dto/view-payload.validator.js +52 -0
  114. package/dist/lib/views/infrastructure/persistence/drizzle-saved-view.repository.d.ts +17 -0
  115. package/dist/lib/views/infrastructure/persistence/drizzle-saved-view.repository.d.ts.map +1 -0
  116. package/dist/lib/views/infrastructure/persistence/drizzle-saved-view.repository.js +125 -0
  117. package/dist/lib/views/infrastructure/schema/external-refs.d.ts +68 -0
  118. package/dist/lib/views/infrastructure/schema/external-refs.d.ts.map +1 -0
  119. package/dist/lib/views/infrastructure/schema/external-refs.js +29 -0
  120. package/dist/lib/views/infrastructure/schema/index.d.ts +8 -0
  121. package/dist/lib/views/infrastructure/schema/index.d.ts.map +1 -0
  122. package/dist/lib/views/infrastructure/schema/index.js +13 -0
  123. package/dist/lib/views/infrastructure/schema/saved-views.d.ts +275 -0
  124. package/dist/lib/views/infrastructure/schema/saved-views.d.ts.map +1 -0
  125. package/dist/lib/views/infrastructure/schema/saved-views.js +87 -0
  126. package/dist/lib/views/views.constants.d.ts +26 -0
  127. package/dist/lib/views/views.constants.d.ts.map +1 -0
  128. package/dist/lib/views/views.constants.js +28 -0
  129. package/dist/lib/views/views.tokens.d.ts +17 -0
  130. package/dist/lib/views/views.tokens.d.ts.map +1 -0
  131. package/dist/lib/views/views.tokens.js +21 -0
  132. package/migrations/0000_saved_views.sql +29 -0
  133. package/migrations/meta/0000_snapshot.json +256 -0
  134. package/migrations/meta/_journal.json +13 -0
  135. package/package.json +13 -12
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ /**
3
+ * The **read scope port** — the seam a downstream plugin uses to narrow what
4
+ * the public content API returns, without this package knowing why.
5
+ *
6
+ * Content-server declares the port and consults it from `PublicEntriesQuery`
7
+ * and `PublicExpansionQuery`; an implementing plugin
8
+ * (`@orthacms/segments-server`, for reader entitlements) registers a scope
9
+ * through {@link contentReadScopeRegistrar}. Every fragment returned is AND-ed
10
+ * onto the visibility predicate the public read already states, so a scope can
11
+ * only ever *subtract* rows — there is no shape of return value that widens a
12
+ * read.
13
+ *
14
+ * ## Why a registry rather than a DI token
15
+ *
16
+ * The port takes any number of independent narrowings, and Nest has **no
17
+ * multi-provider**: two dynamic modules binding the same token do not merge,
18
+ * the second silently replaces the first. A second scoping plugin would
19
+ * therefore switch the first one off, with nothing to notice it — which for a
20
+ * visibility rule means content quietly becoming visible. So registration is a
21
+ * runtime `register(...)` call against a registry this module owns, the same
22
+ * shape and the same reason as `copilotToolsRegistrar`.
23
+ *
24
+ * ## Why not `CONTENT_ENTRY_EXTENSION`
25
+ *
26
+ * That port is documented as a **single binding**: one provider per app, and
27
+ * i18n already holds it. Its own note suggests binding a composite when a
28
+ * second extension appears, and for a write-pipeline extension that composes
29
+ * cleanly — but "narrow a read" and "extend a write" are different
30
+ * responsibilities with different failure modes. A composite would fuse i18n
31
+ * and entitlements into one provider, where a fault in either silently drops
32
+ * the other's clause.
33
+ *
34
+ * ## Two constraints on an implementation
35
+ *
36
+ * **Synchronous.** The predicate is assembled inside the query builder, and
37
+ * making that path async would ripple through every public read for the benefit
38
+ * of one provider. A scope that needs I/O — resolving who the caller is, say —
39
+ * must do it earlier in the request (a guard, an interceptor, middleware) and
40
+ * read the result from its own request-scoped state here.
41
+ *
42
+ * **Public reads only.** These fragments are not applied to the admin's own
43
+ * entries list. An admin caller is a member of the workspace looking at their
44
+ * own CMS, while a reader entitlement is about who may *consume* published
45
+ * content, and scoping the editor's list by it would hide from an author the
46
+ * very rows they are responsible for. The same split content grants already
47
+ * make.
48
+ */
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.ContentReadScopeRegistry = void 0;
51
+ exports.contentReadScopeRegistrar = contentReadScopeRegistrar;
52
+ const tslib_1 = require("tslib");
53
+ const common_1 = require("@nestjs/common");
54
+ /**
55
+ * Every registered narrowing, in registration order.
56
+ *
57
+ * Order does not affect the result — the fragments are AND-ed — but it is kept
58
+ * stable so a query log reads the same way twice.
59
+ */
60
+ let ContentReadScopeRegistry = class ContentReadScopeRegistry {
61
+ scopes = [];
62
+ /** Adds a scope. Registering the same instance twice is a no-op. */
63
+ register(scope) {
64
+ if (!this.scopes.includes(scope)) {
65
+ this.scopes.push(scope);
66
+ }
67
+ }
68
+ /** Every registered scope. Empty on an installation with none. */
69
+ all() {
70
+ return this.scopes;
71
+ }
72
+ /**
73
+ * The fragments for one read, ready to be spread into `and(...)`.
74
+ *
75
+ * Returns an empty array — not a fragment matching everything — when
76
+ * nothing is registered, which is the state every installation without a
77
+ * scoping plugin stays in.
78
+ */
79
+ fragments(context) {
80
+ if (!this.scopes.length) {
81
+ return [];
82
+ }
83
+ return this.scopes.map((scope) => scope.scope(context));
84
+ }
85
+ };
86
+ exports.ContentReadScopeRegistry = ContentReadScopeRegistry;
87
+ exports.ContentReadScopeRegistry = ContentReadScopeRegistry = tslib_1.__decorate([
88
+ (0, common_1.Injectable)()
89
+ ], ContentReadScopeRegistry);
90
+ /** Registers a plugin's read scopes with content at bootstrap. */
91
+ class ReadScopeBootstrapper {
92
+ label;
93
+ registry;
94
+ scopes;
95
+ logger = new common_1.Logger(ReadScopeBootstrapper.name);
96
+ constructor(label, registry, scopes) {
97
+ this.label = label;
98
+ this.registry = registry;
99
+ this.scopes = scopes;
100
+ }
101
+ onApplicationBootstrap() {
102
+ // A deployment that does not register `ContentPlugin` is not a real
103
+ // configuration, but a binding plugin should still boot rather than
104
+ // fail on an injection it cannot influence.
105
+ if (!this.registry) {
106
+ return;
107
+ }
108
+ for (const scope of this.scopes) {
109
+ this.registry.register(scope);
110
+ }
111
+ this.logger.log(`Registered the ${this.label} read scope with the content API.`);
112
+ }
113
+ }
114
+ /**
115
+ * Builds the DI provider that registers `scopes` with content's read-scope
116
+ * registry at bootstrap.
117
+ *
118
+ * A factory with an explicit `inject` list rather than a class with reflected
119
+ * parameters, for the reason `copilotToolsRegistrar` documents: an optional
120
+ * dependency typed as `Foo | null` emits `Object` for `design:paramtypes`, Nest
121
+ * injects `undefined` with no error, and the plugin ends up registering
122
+ * nothing. Here that failure would mean a visibility rule that silently stopped
123
+ * being applied.
124
+ */
125
+ function contentReadScopeRegistrar(label, ...scopes) {
126
+ return {
127
+ provide: `CONTENT_READ_SCOPE_REGISTRAR_${label.toUpperCase()}`,
128
+ useFactory: (registry, ...resolved) => new ReadScopeBootstrapper(label, registry, resolved),
129
+ inject: [{ token: ContentReadScopeRegistry, optional: true }, ...scopes]
130
+ };
131
+ }
@@ -1,5 +1,6 @@
1
1
  import { type Database } from '@orthacms/database';
2
2
  import { type ContentEntryExtension } from '../../extension/entry-extension';
3
+ import { ContentReadScopeRegistry } from '../../extension/read-scope';
3
4
  import { type AnyContentType } from '../../types/content-type';
4
5
  import { RelationLinkService } from '../../entries/infrastructure/persistence/relation-link.service';
5
6
  import type { PublicMediaFieldView, PublicRelationFieldView } from '../types/public-expansion';
@@ -49,7 +50,8 @@ export declare class PublicEntriesQuery {
49
50
  private readonly expansion;
50
51
  private readonly relationLinks;
51
52
  private readonly extension?;
52
- constructor(db: Database, expansion: PublicExpansionQuery, relationLinks: RelationLinkService, extension?: ContentEntryExtension | undefined);
53
+ private readonly readScopes?;
54
+ constructor(db: Database, expansion: PublicExpansionQuery, relationLinks: RelationLinkService, extension?: ContentEntryExtension | undefined, readScopes?: ContentReadScopeRegistry | undefined);
53
55
  /**
54
56
  * One page of a type's publicly readable entries. Count and page share the
55
57
  * WHERE and run concurrently, so the request pays the slower of the two.
@@ -225,6 +227,17 @@ export declare class PublicEntriesQuery {
225
227
  * visibility rule in exactly one respect and inherits the rest verbatim.
226
228
  */
227
229
  private liveWhere;
230
+ /**
231
+ * The fragments contributed by the bound `CONTENT_READ_SCOPE` providers.
232
+ *
233
+ * Applied here, in {@link liveWhere}, rather than in `readableWhere`: this
234
+ * is the clause every public read shares — the list, the single-entry read
235
+ * and the translation lookup that deliberately steps around the locale
236
+ * scope all pass through it — so a scope cannot be missed by a read that
237
+ * chose the narrower helper. AND-ed like everything else around them, which
238
+ * is what makes a scope structurally incapable of widening a read.
239
+ */
240
+ private readScopeWhere;
228
241
  /**
229
242
  * The publish-state clause. `published` — the default and everything a
230
243
  * read-only token can ask for — is the API's headline rule; the other two
@@ -1 +1 @@
1
- {"version":3,"file":"public-entries.query.d.ts","sourceRoot":"","sources":["../../../../src/lib/public-api/infrastructure/public-entries.query.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"public-entries.query.d.ts","sourceRoot":"","sources":["../../../../src/lib/public-api/infrastructure/public-entries.query.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EAEH,KAAK,qBAAqB,EAC7B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,gEAAgE,CAAC;AACrG,OAAO,KAAK,EACR,oBAAoB,EACpB,uBAAuB,EAC1B,MAAM,2BAA2B,CAAC;AAKnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEhE,OAAO,EAGH,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EACjC,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAc9E;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAClB;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACvB;IAAE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBACa,kBAAkB;IAEL,OAAO,CAAC,QAAQ,CAAC,EAAE;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAK9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAK3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;gBAZO,EAAE,EAAE,QAAQ,EAC9B,SAAS,EAAE,oBAAoB,EAC/B,aAAa,EAAE,mBAAmB,EAKlC,SAAS,CAAC,EAAE,qBAAqB,YAAA,EAKjC,UAAU,CAAC,EAAE,wBAAwB,YAAA;IAG1D;;;OAGG;IACG,IAAI,CACN,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,yBAAyB,EAChC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAClC,OAAO,CAAC,mBAAmB,CAAC;IAiE/B;;;;;OAKG;YACW,gBAAgB;IAqE9B;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;IAWzB,0EAA0E;IAC1E,OAAO,CAAC,eAAe;IAQvB;;;;;;;;;;;;;;OAcG;YACW,sBAAsB;IAqDpC;;;;;OAKG;IACG,cAAc,CAChB,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,mBAAmB,GAC3B,OAAO,CAAC,WAAW,EAAE,CAAC;IAoBzB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,UAAU;IAyClB;;;;;;;;;;;;;;;;OAgBG;IACG,MAAM,CACR,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,mBAAmB,EAC1B,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAClC,OAAO,CAAC,WAAW,CAAC;IA0DvB;;;;;;;;;;;;;;OAcG;IACG,kBAAkB,CACpB,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAQnC;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,OAAO,CAAC,UAAU;IA6BlB,mEAAmE;IACnE,OAAO,CAAC,WAAW;IAMnB;;;;OAIG;IACG,aAAa,CACf,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,EACjC,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,eAAe,GAC7B,OAAO,CAAC,uBAAuB,CAAC;IAyCnC,yEAAyE;IACnE,OAAO,CACT,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE,YAAY,EACrB,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,SAA0B,EAC/B,UAAU,CAAC,EAAE,eAAe,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAyBhD;;;;;OAKG;YACW,WAAW;IAoBzB;;;;;;;;;;;;;;;;;;;;OAoBG;YACW,eAAe;IAoB7B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAcrB;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAcjB;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;IAOtB;;;;;;;;OAQG;IACH,OAAO,CAAC,WAAW;IAcnB;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO;CAmBlB"}
@@ -7,6 +7,7 @@ const drizzle_orm_1 = require("drizzle-orm");
7
7
  const database_1 = require("@orthacms/database");
8
8
  const utils_server_1 = require("@orthacms/utils-server");
9
9
  const entry_extension_1 = require("../../extension/entry-extension");
10
+ const read_scope_1 = require("../../extension/read-scope");
10
11
  const content_type_1 = require("../../types/content-type");
11
12
  const fields_1 = require("../../types/fields");
12
13
  const relation_link_service_1 = require("../../entries/infrastructure/persistence/relation-link.service");
@@ -48,11 +49,13 @@ let PublicEntriesQuery = class PublicEntriesQuery {
48
49
  expansion;
49
50
  relationLinks;
50
51
  extension;
51
- constructor(db, expansion, relationLinks, extension) {
52
+ readScopes;
53
+ constructor(db, expansion, relationLinks, extension, readScopes) {
52
54
  this.db = db;
53
55
  this.expansion = expansion;
54
56
  this.relationLinks = relationLinks;
55
57
  this.extension = extension;
58
+ this.readScopes = readScopes;
56
59
  }
57
60
  /**
58
61
  * One page of a type's publicly readable entries. Count and page share the
@@ -500,7 +503,20 @@ let PublicEntriesQuery = class PublicEntriesQuery {
500
503
  */
501
504
  liveWhere(type, workspaceId, visibility = 'published') {
502
505
  const table = type.table;
503
- return (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(table['workspaceId'], workspaceId), this.statusWhere(type, visibility), type.paranoid ? (0, drizzle_orm_1.isNull)(table['deletedAt']) : undefined);
506
+ return (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(table['workspaceId'], workspaceId), this.statusWhere(type, visibility), type.paranoid ? (0, drizzle_orm_1.isNull)(table['deletedAt']) : undefined, ...this.readScopeWhere(type, workspaceId));
507
+ }
508
+ /**
509
+ * The fragments contributed by the bound `CONTENT_READ_SCOPE` providers.
510
+ *
511
+ * Applied here, in {@link liveWhere}, rather than in `readableWhere`: this
512
+ * is the clause every public read shares — the list, the single-entry read
513
+ * and the translation lookup that deliberately steps around the locale
514
+ * scope all pass through it — so a scope cannot be missed by a read that
515
+ * chose the narrower helper. AND-ed like everything else around them, which
516
+ * is what makes a scope structurally incapable of widening a read.
517
+ */
518
+ readScopeWhere(type, workspaceId) {
519
+ return this.readScopes?.fragments({ type, workspaceId }) ?? [];
504
520
  }
505
521
  /**
506
522
  * The publish-state clause. `published` — the default and everything a
@@ -552,6 +568,7 @@ exports.PublicEntriesQuery = PublicEntriesQuery = tslib_1.__decorate([
552
568
  tslib_1.__param(0, (0, database_1.InjectDatabase)()),
553
569
  tslib_1.__param(3, (0, common_1.Optional)()),
554
570
  tslib_1.__param(3, (0, common_1.Inject)(entry_extension_1.CONTENT_ENTRY_EXTENSION)),
571
+ tslib_1.__param(4, (0, common_1.Optional)()),
555
572
  tslib_1.__metadata("design:paramtypes", [Object, public_expansion_query_1.PublicExpansionQuery,
556
- relation_link_service_1.RelationLinkService, Object])
573
+ relation_link_service_1.RelationLinkService, Object, read_scope_1.ContentReadScopeRegistry])
557
574
  ], PublicEntriesQuery);
@@ -1,6 +1,7 @@
1
1
  import { type Database } from '@orthacms/database';
2
2
  import { type AnyContentType } from '../../types/content-type';
3
3
  import { type MediaAssetResolver } from '../../extension/media-asset-resolver';
4
+ import { ContentReadScopeRegistry } from '../../extension/read-scope';
4
5
  import { RelationLinkService } from '../../entries/infrastructure/persistence/relation-link.service';
5
6
  import type { PublicMediaFieldView, PublicRelationFieldView } from '../types/public-expansion';
6
7
  /** A generated content row seen as a bag of values by property name. */
@@ -31,7 +32,16 @@ export declare class PublicExpansionQuery {
31
32
  private readonly db;
32
33
  private readonly relationLinks;
33
34
  private readonly media?;
34
- constructor(db: Database, relationLinks: RelationLinkService, media?: MediaAssetResolver | undefined);
35
+ private readonly readScopes?;
36
+ constructor(db: Database, relationLinks: RelationLinkService, media?: MediaAssetResolver | undefined, readScopes?: ContentReadScopeRegistry | undefined);
37
+ /**
38
+ * The read-scope fragments for one hop's target type.
39
+ *
40
+ * The target is a different content type from the one the request named, so
41
+ * the scope is asked about **it** — a scope keyed to the requested type
42
+ * would leave every relation target unguarded.
43
+ */
44
+ private readScopeWhere;
35
45
  /**
36
46
  * Validate a `?relationFields=` list: every name must be a relation field
37
47
  * on the type, and its target must be a content type the workspace was
@@ -47,9 +57,11 @@ export declare class PublicExpansionQuery {
47
57
  * One capped page of links per requested relation field, for every row on
48
58
  * the page, keyed by entry id.
49
59
  *
50
- * `publishedOnly` is the public difference: a draft target is neither shown
51
- * nor counted, so `total` is the number of links a caller can actually
52
- * reach.
60
+ * `publishedOnly` is the public difference: a draft target or one a bound
61
+ * read scope hides from this reader is neither shown **nor counted**, so
62
+ * `total` is the number of links this caller can actually reach. Counting
63
+ * them would leak the cardinality of what is hidden: "5 links, 2 visible"
64
+ * says three restricted records exist here.
53
65
  */
54
66
  relationsForRows(type: AnyContentType, fields: string[], rows: Row[], workspaceId: string, limit?: number): Promise<Map<string, Record<string, PublicRelationFieldView>>>;
55
67
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"public-expansion.query.d.ts","sourceRoot":"","sources":["../../../../src/lib/public-api/infrastructure/public-expansion.query.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE7E,OAAO,EAEH,KAAK,kBAAkB,EAC1B,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gEAAgE,CAAC;AAErG,OAAO,KAAK,EACR,oBAAoB,EAEpB,uBAAuB,EAC1B,MAAM,2BAA2B,CAAC;AAInC,wEAAwE;AACxE,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAKnC;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAwCtC;;;;;;;;;;;;GAYG;AACH,qBACa,oBAAoB;IAEP,OAAO,CAAC,QAAQ,CAAC,EAAE;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAG9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAJY,EAAE,EAAE,QAAQ,EAC9B,aAAa,EAAE,mBAAmB,EAGlC,KAAK,CAAC,EAAE,kBAAkB,YAAA;IAG/C;;;;;;;OAOG;IACH,mBAAmB,CACf,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC7B,MAAM,EAAE;IA8BX,yEAAyE;IACzE,gBAAgB,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE;IAiBzE;;;;;;;OAOG;IACG,gBAAgB,CAClB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,GAAG,EAAE,EACX,WAAW,EAAE,MAAM,EACnB,KAAK,SAA0B,GAChC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC;IAyChE;;;;OAIG;IACG,oBAAoB,CACtB,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,EACjE,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IAqBnD;;;;;;;;OAQG;YACW,iBAAiB;IAsD/B;;;;;;OAMG;IACG,YAAY,CACd,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,GAAG,EAAE,EACX,WAAW,EAAE,MAAM,EACnB,KAAK,SAA0B,GAChC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;IA+F7D;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IAgBvB,4EAA4E;IAC5E,OAAO,CAAC,WAAW;CA+BtB"}
1
+ {"version":3,"file":"public-expansion.query.d.ts","sourceRoot":"","sources":["../../../../src/lib/public-api/infrastructure/public-expansion.query.ts"],"names":[],"mappings":"AASA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE7E,OAAO,EAEH,KAAK,kBAAkB,EAC1B,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gEAAgE,CAAC;AAErG,OAAO,KAAK,EACR,oBAAoB,EAEpB,uBAAuB,EAC1B,MAAM,2BAA2B,CAAC;AAInC,wEAAwE;AACxE,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAKnC;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAwCtC;;;;;;;;;;;;GAYG;AACH,qBACa,oBAAoB;IAEP,OAAO,CAAC,QAAQ,CAAC,EAAE;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAG9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IAKvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;gBATO,EAAE,EAAE,QAAQ,EAC9B,aAAa,EAAE,mBAAmB,EAGlC,KAAK,CAAC,EAAE,kBAAkB,YAAA,EAK1B,UAAU,CAAC,EAAE,wBAAwB,YAAA;IAG1D;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAOtB;;;;;;;OAOG;IACH,mBAAmB,CACf,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC7B,MAAM,EAAE;IA8BX,yEAAyE;IACzE,gBAAgB,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE;IAiBzE;;;;;;;;;OASG;IACG,gBAAgB,CAClB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,GAAG,EAAE,EACX,WAAW,EAAE,MAAM,EACnB,KAAK,SAA0B,GAChC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC;IA6ChE;;;;OAIG;IACG,oBAAoB,CACtB,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,EACjE,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IAqBnD;;;;;;;;OAQG;YACW,iBAAiB;IAuD/B;;;;;;OAMG;IACG,YAAY,CACd,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,GAAG,EAAE,EACX,WAAW,EAAE,MAAM,EACnB,KAAK,SAA0B,GAChC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;IA+F7D;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IAgBvB,4EAA4E;IAC5E,OAAO,CAAC,WAAW;CA+BtB"}
@@ -9,6 +9,7 @@ const content_domain_1 = require("@orthacms/content-domain");
9
9
  const content_type_1 = require("../../types/content-type");
10
10
  const fields_1 = require("../../types/fields");
11
11
  const media_asset_resolver_1 = require("../../extension/media-asset-resolver");
12
+ const read_scope_1 = require("../../extension/read-scope");
12
13
  const relation_link_service_1 = require("../../entries/infrastructure/persistence/relation-link.service");
13
14
  const public_list_entries_query_dto_1 = require("../http/dto/public-list-entries-query.dto");
14
15
  const public_entry_row_1 = require("./public-entry-row");
@@ -73,10 +74,22 @@ let PublicExpansionQuery = class PublicExpansionQuery {
73
74
  db;
74
75
  relationLinks;
75
76
  media;
76
- constructor(db, relationLinks, media) {
77
+ readScopes;
78
+ constructor(db, relationLinks, media, readScopes) {
77
79
  this.db = db;
78
80
  this.relationLinks = relationLinks;
79
81
  this.media = media;
82
+ this.readScopes = readScopes;
83
+ }
84
+ /**
85
+ * The read-scope fragments for one hop's target type.
86
+ *
87
+ * The target is a different content type from the one the request named, so
88
+ * the scope is asked about **it** — a scope keyed to the requested type
89
+ * would leave every relation target unguarded.
90
+ */
91
+ readScopeWhere(type, workspaceId) {
92
+ return this.readScopes?.fragments({ type, workspaceId }) ?? [];
80
93
  }
81
94
  /**
82
95
  * Validate a `?relationFields=` list: every name must be a relation field
@@ -121,9 +134,11 @@ let PublicExpansionQuery = class PublicExpansionQuery {
121
134
  * One capped page of links per requested relation field, for every row on
122
135
  * the page, keyed by entry id.
123
136
  *
124
- * `publishedOnly` is the public difference: a draft target is neither shown
125
- * nor counted, so `total` is the number of links a caller can actually
126
- * reach.
137
+ * `publishedOnly` is the public difference: a draft target or one a bound
138
+ * read scope hides from this reader is neither shown **nor counted**, so
139
+ * `total` is the number of links this caller can actually reach. Counting
140
+ * them would leak the cardinality of what is hidden: "5 links, 2 visible"
141
+ * says three restricted records exist here.
127
142
  */
128
143
  async relationsForRows(type, fields, rows, workspaceId, limit = public_list_entries_query_dto_1.DEFAULT_EXPANSION_LIMIT) {
129
144
  const out = new Map();
@@ -140,9 +155,13 @@ let PublicExpansionQuery = class PublicExpansionQuery {
140
155
  for (const [field, value] of Object.entries(byField)) {
141
156
  const linked = entriesById.get(field);
142
157
  view[field] = {
143
- // Preserve the preview's link order, and drop any id the
144
- // hydration didn't return (it named a target the caller
145
- // can't see already excluded from `total` upstream).
158
+ // Preserve the preview's link order. Nothing should drop
159
+ // here any more `previewForEntries` applies the same
160
+ // visibility (published-only **and** the bound read scopes)
161
+ // inside its window, so an unreachable target is already
162
+ // out of both `items` and `total`. The filter stays as a
163
+ // belt: a hydration miss must never leave a hole in the
164
+ // array, and it is cheap to keep honest.
146
165
  items: value.items
147
166
  .map((ref) => linked?.get(ref.id))
148
167
  .filter((entry) => !!entry),
@@ -209,7 +228,7 @@ let PublicExpansionQuery = class PublicExpansionQuery {
209
228
  ? (0, drizzle_orm_1.eq)(cols['status'], content_type_1.ENTRY_STATUS.Published)
210
229
  : undefined, target.paranoid
211
230
  ? (0, drizzle_orm_1.isNull)(cols['deletedAt'])
212
- : undefined)));
231
+ : undefined, ...this.readScopeWhere(target, workspaceId))));
213
232
  out.set(field, new Map(rows.map((row) => [
214
233
  row['id'],
215
234
  (0, public_entry_row_1.toPublicEntry)(target, row)
@@ -358,7 +377,8 @@ exports.PublicExpansionQuery = PublicExpansionQuery = tslib_1.__decorate([
358
377
  tslib_1.__param(0, (0, database_1.InjectDatabase)()),
359
378
  tslib_1.__param(2, (0, common_1.Optional)()),
360
379
  tslib_1.__param(2, (0, media_asset_resolver_1.InjectMediaAssetResolver)()),
361
- tslib_1.__metadata("design:paramtypes", [Object, relation_link_service_1.RelationLinkService, Object])
380
+ tslib_1.__param(3, (0, common_1.Optional)()),
381
+ tslib_1.__metadata("design:paramtypes", [Object, relation_link_service_1.RelationLinkService, Object, read_scope_1.ContentReadScopeRegistry])
362
382
  ], PublicExpansionQuery);
363
383
  /** The asset ids stored in a media field — one id, or an ordered list. */
364
384
  function mediaRefsOf(value) {
@@ -1 +1 @@
1
- {"version":3,"file":"restore-revision.use-case.d.ts","sourceRoot":"","sources":["../../../../../src/lib/revisions/application/use-cases/restore-revision.use-case.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kEAAkE,CAAC;AACtG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAEH,KAAK,aAAa,EACrB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;GASG;AACH,qBACa,sBAAsB;IAE3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACA,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAD5B,MAAM,EAAE,kBAAkB,EACH,KAAK,EAAE,aAAa;IAG1D,OAAO,CACT,IAAI,EAAE,cAAc,EACpB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM;IACnB;;;;OAIG;IACH,KAAK,EAAE,UAAU,GAAG,IAAI,EACxB,OAAO,CAAC,EAAE;QACN;;;;;WAKG;QACH,cAAc,CAAC,EAAE,OAAO,CAAC;KAC5B,GACF,OAAO,CAAC,WAAW,CAAC;CA+B1B"}
1
+ {"version":3,"file":"restore-revision.use-case.d.ts","sourceRoot":"","sources":["../../../../../src/lib/revisions/application/use-cases/restore-revision.use-case.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kEAAkE,CAAC;AACtG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAEH,KAAK,aAAa,EACrB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;GASG;AACH,qBACa,sBAAsB;IAE3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACA,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAD5B,MAAM,EAAE,kBAAkB,EACH,KAAK,EAAE,aAAa;IAG1D,OAAO,CACT,IAAI,EAAE,cAAc,EACpB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM;IACnB;;;;OAIG;IACH,KAAK,EAAE,UAAU,GAAG,IAAI,EACxB,OAAO,CAAC,EAAE;QACN;;;;;WAKG;QACH,cAAc,CAAC,EAAE,OAAO,CAAC;KAC5B,GACF,OAAO,CAAC,WAAW,CAAC;CA0C1B"}
@@ -46,7 +46,18 @@ let RestoreRevisionUseCase = class RestoreRevisionUseCase {
46
46
  values[field] = ids;
47
47
  }
48
48
  }
49
- return this.writer.update(type, id, values, workspaceId, undefined, actor, options);
49
+ return this.writer.update(type, id, values, workspaceId, undefined, actor, {
50
+ ...options,
51
+ // Put back whatever the bound extensions held when this version was
52
+ // captured — segments' audiences, today. Without it "go back to
53
+ // Tuesday" would restore Tuesday's words in front of today's
54
+ // readers, which is the half of a restore nobody would think to
55
+ // check. A version captured before any extension existed carries no
56
+ // bag, and an absent bag changes nothing (an omitted key is left
57
+ // alone) — which is the right reading: that version knows nothing
58
+ // about audiences, it does not assert the entry had none.
59
+ extensions: detail.snapshot.extra
60
+ });
50
61
  }
51
62
  };
52
63
  exports.RestoreRevisionUseCase = RestoreRevisionUseCase;
@@ -11,7 +11,13 @@ type Row = Record<string, unknown>;
11
11
  * `relations` is read separately from the join tables — those links never travel
12
12
  * in the `values` bag — inside the save transaction, so the snapshot reflects
13
13
  * exactly what committed.
14
+ *
15
+ * `extra` is the same idea one plugin further out: state a **bound entry-write
16
+ * extension** owns in a table this package knows nothing about (segments'
17
+ * audiences), read back on the same transaction. It is omitted when there is
18
+ * none, so a snapshot on an installation with no extensions is byte-for-byte
19
+ * what it was before the port existed.
14
20
  */
15
- export declare function buildSnapshot(type: AnyContentType, row: Row, relations: Record<string, string[]>): RevisionSnapshot;
21
+ export declare function buildSnapshot(type: AnyContentType, row: Row, relations: Record<string, string[]>, extra?: Record<string, unknown>): RevisionSnapshot;
16
22
  export {};
17
23
  //# sourceMappingURL=revision-snapshot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"revision-snapshot.d.ts","sourceRoot":"","sources":["../../../../../src/lib/revisions/infrastructure/persistence/revision-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,wEAAwE;AACxE,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnC;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CACzB,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GACpC,gBAAgB,CAKlB"}
1
+ {"version":3,"file":"revision-snapshot.d.ts","sourceRoot":"","sources":["../../../../../src/lib/revisions/infrastructure/persistence/revision-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,wEAAwE;AACxE,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CACzB,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EACnC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,gBAAgB,CAMlB"}
@@ -11,10 +11,17 @@ const entry_row_1 = require("../../../entries/infrastructure/persistence/entry-r
11
11
  * `relations` is read separately from the join tables — those links never travel
12
12
  * in the `values` bag — inside the save transaction, so the snapshot reflects
13
13
  * exactly what committed.
14
+ *
15
+ * `extra` is the same idea one plugin further out: state a **bound entry-write
16
+ * extension** owns in a table this package knows nothing about (segments'
17
+ * audiences), read back on the same transaction. It is omitted when there is
18
+ * none, so a snapshot on an installation with no extensions is byte-for-byte
19
+ * what it was before the port existed.
14
20
  */
15
- function buildSnapshot(type, row, relations) {
21
+ function buildSnapshot(type, row, relations, extra) {
16
22
  return {
17
23
  values: (0, entry_row_1.toRecord)(type, row).values,
18
- relations
24
+ relations,
25
+ ...(extra ? { extra } : {})
19
26
  };
20
27
  }
@@ -17,6 +17,17 @@ export interface RevisionSnapshot {
17
17
  * never travel in {@link values}.
18
18
  */
19
19
  relations: Record<string, string[]>;
20
+ /**
21
+ * What each bound **entry-write extension** held for the entry at capture
22
+ * time, keyed by its `key` — state a different plugin owns in a table this
23
+ * package knows nothing about (`segments` records who could read the entry).
24
+ *
25
+ * Opaque here: content-server stores it, hands it back on a restore, and
26
+ * never looks inside. Absent on an installation with no extensions, and on
27
+ * every version captured before one was installed — so a reader must treat
28
+ * a missing bag as "nothing is known", never as "the entry had none".
29
+ */
30
+ extra?: Record<string, unknown>;
20
31
  }
21
32
  /**
22
33
  * One revision as served to the admin timeline — the metadata, without the
@@ -1 +1 @@
1
- {"version":3,"file":"revision-view.d.ts","sourceRoot":"","sources":["../../../../src/lib/revisions/types/revision-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EACR,QAAQ,EACR,WAAW,EACd,MAAM,qCAAqC,CAAC;AAE7C;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACvC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC5B,mBAAmB;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,wDAAwD;IACxD,WAAW,EAAE,OAAO,CAAC;IACrB,sEAAsE;IACtE,QAAQ,EAAE,OAAO,CAAC;IAClB,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wEAAwE;AACxE,MAAM,WAAW,cAAe,SAAQ,eAAe;IACnD,6BAA6B;IAC7B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IAC7C,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;CAC1C;AAED,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"revision-view.d.ts","sourceRoot":"","sources":["../../../../src/lib/revisions/types/revision-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EACR,QAAQ,EACR,WAAW,EACd,MAAM,qCAAqC,CAAC;AAE7C;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACpC;;;;;;;;;OASG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC5B,mBAAmB;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,wDAAwD;IACxD,WAAW,EAAE,OAAO,CAAC;IACrB,sEAAsE;IACtE,QAAQ,EAAE,OAAO,CAAC;IAClB,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wEAAwE;AACxE,MAAM,WAAW,cAAe,SAAQ,eAAe;IACnD,6BAA6B;IAC7B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IAC7C,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;CAC1C;AAED,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1,28 @@
1
+ import type { ServerPlugin } from '@orthacms/bootstrap-server';
2
+ import type { ContentServerPlugin } from './content-plugin';
3
+ /** Options for {@link ContentViewsPlugin}. */
4
+ export interface ContentViewsPluginOptions {
5
+ /**
6
+ * The content plugin this rides on — its registry is what turns a view's
7
+ * `content:<typeName>` scope back into a known content type. Taking the
8
+ * whole plugin (rather than a bare registry) makes the dependency visible
9
+ * at the composition root: the entry has to be listed *after* the content
10
+ * one, which is also the migration order its foreign keys need.
11
+ */
12
+ content: ContentServerPlugin;
13
+ }
14
+ /**
15
+ * Saved list views — the named filter/sort/column slices an editor returns to.
16
+ *
17
+ * A **separate `ServerPlugin` entry from the same package** as `ContentPlugin`,
18
+ * and deliberately so. `ServerPlugin.migrations` is one descriptor, and
19
+ * content's is already carrying the HOST's generated collection tables; a
20
+ * second entry is how this package ships its own fixed tables without touching
21
+ * that contract. The host applies both under their own tracking tables.
22
+ *
23
+ * Order matters at the composition root: `saved_views` has foreign keys into
24
+ * identity's `users` and workspaces' `workspaces`, and nothing declares that
25
+ * dependency — list this after both, and after `ContentPlugin` itself.
26
+ */
27
+ export declare function ContentViewsPlugin(options: ContentViewsPluginOptions): ServerPlugin;
28
+ //# sourceMappingURL=content-views-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-views-plugin.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/content-views-plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,8CAA8C;AAC9C,MAAM,WAAW,yBAAyB;IACtC;;;;;;OAMG;IACH,OAAO,EAAE,mBAAmB,CAAC;CAChC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAC9B,OAAO,EAAE,yBAAyB,GACnC,YAAY,CAUd"}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContentViewsPlugin = ContentViewsPlugin;
4
+ const node_path_1 = require("node:path");
5
+ const content_views_module_1 = require("../views/content-views.module");
6
+ /**
7
+ * Saved list views — the named filter/sort/column slices an editor returns to.
8
+ *
9
+ * A **separate `ServerPlugin` entry from the same package** as `ContentPlugin`,
10
+ * and deliberately so. `ServerPlugin.migrations` is one descriptor, and
11
+ * content's is already carrying the HOST's generated collection tables; a
12
+ * second entry is how this package ships its own fixed tables without touching
13
+ * that contract. The host applies both under their own tracking tables.
14
+ *
15
+ * Order matters at the composition root: `saved_views` has foreign keys into
16
+ * identity's `users` and workspaces' `workspaces`, and nothing declares that
17
+ * dependency — list this after both, and after `ContentPlugin` itself.
18
+ */
19
+ function ContentViewsPlugin(options) {
20
+ return {
21
+ name: 'content-views',
22
+ module: content_views_module_1.ContentViewsModule.forRoot(options.content.registry),
23
+ // src/lib/utils → ../../../migrations = <pkg>/migrations.
24
+ migrations: {
25
+ dir: () => (0, node_path_1.join)(__dirname, '../../../migrations'),
26
+ table: '__drizzle_migrations_content_views'
27
+ }
28
+ };
29
+ }
@@ -0,0 +1,16 @@
1
+ import type { SavedView } from '../../domain/saved-view';
2
+ import { type SavedViewRecord, type SavedViewRepository } from '../../domain/saved-view.repository';
3
+ /**
4
+ * The read side: every view a caller may see for one list, with the two
5
+ * per-caller flags the switcher renders from (`isOwn` gates Save and Delete,
6
+ * `isDefault` marks the row and drives the landing view).
7
+ */
8
+ export declare class SavedViewsQuery {
9
+ private readonly repository;
10
+ constructor(repository: SavedViewRepository);
11
+ /** Views visible to `readerId` in this workspace + scope. */
12
+ list(workspaceId: string, scope: string, readerId: string): Promise<SavedView[]>;
13
+ }
14
+ /** Projects a stored row into the API shape for one reader. */
15
+ export declare function toSavedView(record: SavedViewRecord, readerId: string, defaultId: string | null): SavedView;
16
+ //# sourceMappingURL=saved-views.query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"saved-views.query.d.ts","sourceRoot":"","sources":["../../../../../src/lib/views/application/queries/saved-views.query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAEH,KAAK,eAAe,EACpB,KAAK,mBAAmB,EAC3B,MAAM,oCAAoC,CAAC;AAE5C;;;;GAIG;AACH,qBACa,eAAe;IAGpB,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,mBAAmB;IAGpD,6DAA6D;IACvD,IAAI,CACN,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACjB,OAAO,CAAC,SAAS,EAAE,CAAC;CAS1B;AAED,+DAA+D;AAC/D,wBAAgB,WAAW,CACvB,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,GACzB,SAAS,CAYX"}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SavedViewsQuery = void 0;
4
+ exports.toSavedView = toSavedView;
5
+ const tslib_1 = require("tslib");
6
+ const common_1 = require("@nestjs/common");
7
+ const saved_view_repository_1 = require("../../domain/saved-view.repository");
8
+ /**
9
+ * The read side: every view a caller may see for one list, with the two
10
+ * per-caller flags the switcher renders from (`isOwn` gates Save and Delete,
11
+ * `isDefault` marks the row and drives the landing view).
12
+ */
13
+ let SavedViewsQuery = class SavedViewsQuery {
14
+ repository;
15
+ constructor(repository) {
16
+ this.repository = repository;
17
+ }
18
+ /** Views visible to `readerId` in this workspace + scope. */
19
+ async list(workspaceId, scope, readerId) {
20
+ const [records, defaultId] = await Promise.all([
21
+ this.repository.listVisible(workspaceId, scope, readerId),
22
+ this.repository.findDefaultId(readerId, scope)
23
+ ]);
24
+ return records.map((record) => toSavedView(record, readerId, defaultId));
25
+ }
26
+ };
27
+ exports.SavedViewsQuery = SavedViewsQuery;
28
+ exports.SavedViewsQuery = SavedViewsQuery = tslib_1.__decorate([
29
+ (0, common_1.Injectable)(),
30
+ tslib_1.__param(0, (0, common_1.Inject)(saved_view_repository_1.SAVED_VIEW_REPOSITORY)),
31
+ tslib_1.__metadata("design:paramtypes", [Object])
32
+ ], SavedViewsQuery);
33
+ /** Projects a stored row into the API shape for one reader. */
34
+ function toSavedView(record, readerId, defaultId) {
35
+ return {
36
+ id: record.id,
37
+ scope: record.scope,
38
+ name: record.name,
39
+ visibility: record.visibility,
40
+ ownerId: record.ownerId,
41
+ isOwn: record.ownerId === readerId,
42
+ isDefault: record.id === defaultId,
43
+ payload: record.payload,
44
+ updatedAt: record.updatedAt.toISOString()
45
+ };
46
+ }
@@ -0,0 +1,30 @@
1
+ import { AccessPolicy, PermissionsService, type PublicUser } from '@orthacms/identity-server';
2
+ import { type ViewVisibility } from '../domain/saved-view';
3
+ import type { SavedViewRecord } from '../domain/saved-view.repository';
4
+ /**
5
+ * The two authorization rules saved views add on top of the route guards.
6
+ *
7
+ * The guards already establish that the caller is authenticated, a member of
8
+ * the open workspace, and may read content. What is left is per-row and
9
+ * per-visibility, so it cannot live in a decorator:
10
+ *
11
+ * - **Only the owner writes.** Editing or deleting someone else's view is a
12
+ * 403 even for an administrator — a shared view is its author's, and the
13
+ * remedy for disagreeing with one is "Save as new", not a silent rewrite.
14
+ * - **Sharing is a permission.** Making a view workspace-visible needs
15
+ * `views:share`; a member without it still saves as many private views as
16
+ * they like.
17
+ */
18
+ export declare class SavedViewAccessService {
19
+ private readonly permissions;
20
+ private readonly accessPolicy;
21
+ constructor(permissions: PermissionsService, accessPolicy: AccessPolicy);
22
+ /** Throws unless `user` owns `view`. */
23
+ assertOwned(view: SavedViewRecord, user: PublicUser): void;
24
+ /**
25
+ * Throws unless `user` may store a view at `visibility`. Private views need
26
+ * nothing beyond membership; workspace-visible ones need `views:share`.
27
+ */
28
+ assertCanUseVisibility(visibility: ViewVisibility, user: PublicUser): Promise<void>;
29
+ }
30
+ //# sourceMappingURL=saved-view-access.service.d.ts.map