@jini-ai/cms 0.3.11 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +47 -19
- package/dist/content-types/agent-tools.d.ts +3 -56
- package/dist/content-types/agent-tools.js +22 -6
- package/dist/content-types/cleanup.d.ts +18 -13
- package/dist/content-types/cleanup.js +16 -11
- package/dist/content-types/errors.d.ts +57 -24
- package/dist/content-types/errors.js +56 -39
- package/dist/content-types/field-defs.d.ts +8 -4
- package/dist/content-types/field-defs.js +19 -14
- package/dist/content-types/index-provisioning.d.ts +61 -47
- package/dist/content-types/index-provisioning.js +90 -66
- package/dist/content-types/index.d.ts +4 -3
- package/dist/content-types/lifecycle-dispatch.d.ts +11 -5
- package/dist/content-types/lifecycle-dispatch.js +5 -4
- package/dist/content-types/lifecycle.d.ts +23 -18
- package/dist/content-types/lifecycle.js +71 -64
- package/dist/content-types/repo.memory.d.ts +5 -4
- package/dist/content-types/repo.memory.js +3 -2
- package/dist/content-types/tool-registrations.d.ts +6 -8
- package/dist/content-types/tool-registrations.js +24 -28
- package/dist/content-types/types.d.ts +17 -13
- package/dist/content-types/types.js +11 -5
- package/dist/content-types/write-service.d.ts +20 -22
- package/dist/content-types/write-service.js +78 -75
- package/dist/core/authorization.d.ts +15 -0
- package/dist/core/authorization.js +2 -0
- package/dist/core/commands/change-set.d.ts +15 -7
- package/dist/core/commands/command.d.ts +45 -26
- package/dist/core/commands/command.js +73 -63
- package/dist/core/entity-liveness.d.ts +11 -7
- package/dist/core/entity-liveness.js +5 -4
- package/dist/core/index.d.ts +12 -5
- package/dist/core/index.js +10 -4
- package/dist/core/ports.d.ts +25 -24
- package/dist/core/ports.js +1 -0
- package/dist/core/tools/args.d.ts +49 -0
- package/dist/core/tools/args.js +46 -0
- package/dist/core/tools/index.d.ts +3 -0
- package/dist/core/tools/index.js +3 -0
- package/dist/entries/agent-tools.d.ts +1 -73
- package/dist/entries/agent-tools.js +19 -13
- package/dist/entries/errors.d.ts +28 -14
- package/dist/entries/errors.js +17 -11
- package/dist/entries/field-validation.d.ts +5 -3
- package/dist/entries/field-validation.js +7 -5
- package/dist/entries/index.d.ts +2 -2
- package/dist/entries/lifecycle-dispatch.d.ts +5 -2
- package/dist/entries/lifecycle-dispatch.js +2 -1
- package/dist/entries/list.d.ts +4 -2
- package/dist/entries/list.js +4 -3
- package/dist/entries/repo.memory.d.ts +12 -10
- package/dist/entries/repo.memory.js +11 -10
- package/dist/entries/tool-registrations.d.ts +6 -8
- package/dist/entries/tool-registrations.js +42 -41
- package/dist/entries/types.d.ts +0 -7
- package/dist/entries/write-service.d.ts +28 -29
- package/dist/entries/write-service.js +114 -105
- package/dist/http/settings/cms-adapter.d.ts +20 -0
- package/dist/http/settings/cms-adapter.js +124 -0
- package/dist/http/settings/contracts.d.ts +135 -0
- package/dist/http/settings/contracts.js +2 -0
- package/dist/http/settings/http.d.ts +10 -0
- package/dist/http/settings/http.js +211 -0
- package/dist/http/settings/index.d.ts +5 -0
- package/dist/http/settings/index.js +4 -0
- package/dist/http/settings/shared.d.ts +68 -0
- package/dist/http/settings/shared.js +54 -0
- package/dist/http/settings/stream.d.ts +24 -0
- package/dist/http/settings/stream.js +147 -0
- package/dist/media/agent-tools.d.ts +1 -66
- package/dist/media/agent-tools.js +5 -1
- package/dist/media/blob-gc-lock.d.ts +6 -2
- package/dist/media/blob-gc-lock.js +4 -2
- package/dist/media/blob-gc.d.ts +64 -51
- package/dist/media/blob-gc.js +62 -52
- package/dist/media/blob-key.d.ts +1 -1
- package/dist/media/content-type-sniffer.d.ts +3 -1
- package/dist/media/content-type-sniffer.js +1 -1
- package/dist/media/html-attributes.d.ts +9 -5
- package/dist/media/html-attributes.js +8 -6
- package/dist/media/image-transformer.js +1 -1
- package/dist/media/image-transformer.sharp.d.ts +6 -0
- package/dist/media/image-transformer.sharp.js +12 -6
- package/dist/media/import/fetch-image.d.ts +24 -0
- package/dist/media/import/fetch-image.js +137 -0
- package/dist/media/import/index.d.ts +3 -0
- package/dist/media/import/index.js +3 -0
- package/dist/media/import/ports.d.ts +38 -0
- package/dist/media/index.d.ts +7 -1
- package/dist/media/index.js +1 -0
- package/dist/media/media-service.d.ts +14 -10
- package/dist/media/media-service.js +57 -52
- package/dist/media/ports.d.ts +3 -2
- package/dist/media/rendition-service.d.ts +3 -3
- package/dist/media/rendition-service.js +38 -37
- package/dist/media/repo.memory.d.ts +18 -7
- package/dist/media/repo.memory.js +12 -6
- package/dist/media/tool-registrations.d.ts +16 -15
- package/dist/media/tool-registrations.js +84 -72
- package/dist/media/transform-lock.d.ts +22 -12
- package/dist/media/transform-lock.js +14 -13
- package/dist/media/transform-registry.d.ts +3 -3
- package/dist/media/transform-registry.js +19 -18
- package/dist/media/transform-types.d.ts +8 -3
- package/dist/media/transform-types.js +10 -5
- package/dist/media/types.d.ts +21 -4
- package/dist/media/types.js +15 -2
- package/dist/navigation/agent-tools.d.ts +1 -80
- package/dist/navigation/agent-tools.js +15 -3
- package/dist/navigation/contracts.d.ts +1 -1
- package/dist/navigation/index.d.ts +2 -2
- package/dist/navigation/menu-service.d.ts +57 -40
- package/dist/navigation/menu-service.js +80 -69
- package/dist/navigation/ports.d.ts +1 -1
- package/dist/navigation/read-model.d.ts +3 -1
- package/dist/navigation/read-model.js +2 -2
- package/dist/navigation/reconcile.d.ts +2 -2
- package/dist/navigation/reconcile.js +2 -1
- package/dist/navigation/repo.memory.d.ts +7 -3
- package/dist/navigation/repo.memory.js +4 -2
- package/dist/navigation/resolver.d.ts +4 -1
- package/dist/navigation/resolver.js +1 -1
- package/dist/navigation/tool-registrations.d.ts +6 -8
- package/dist/navigation/tool-registrations.js +44 -42
- package/dist/navigation/types.d.ts +1 -1
- package/dist/presentation/presentation.d.ts +11 -3
- package/dist/presentation/presentation.js +13 -6
- package/dist/presentation/repo.memory.d.ts +6 -2
- package/dist/presentation/repo.memory.js +3 -2
- package/dist/settings/agent-tools.d.ts +2 -75
- package/dist/settings/agent-tools.js +49 -45
- package/dist/settings/agent-value-write-tools.d.ts +22 -0
- package/dist/settings/agent-value-write-tools.js +154 -0
- package/dist/settings/agent-writable-preferences.js +8 -20
- package/dist/settings/agent-write-denylist.d.ts +19 -0
- package/dist/settings/agent-write-denylist.js +15 -0
- package/dist/settings/change-feed.d.ts +11 -2
- package/dist/settings/change-feed.js +4 -4
- package/dist/settings/definitions-dispatch.d.ts +8 -3
- package/dist/settings/definitions-dispatch.js +5 -5
- package/dist/settings/dictionaries/index.d.ts +5 -2
- package/dist/settings/dictionaries/index.js +2 -2
- package/dist/settings/dictionaries/settings-dialog.ar.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.bn.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.de.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.es.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.fa.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.fr.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.hi.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.hu.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.id.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.it.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ja.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ko.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.pl.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.pt-BR.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ru.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.th.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.tr.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.uk.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ur.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.zh-CN.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.zh-TW.js +0 -1
- package/dist/settings/ensure-definitions.d.ts +5 -5
- package/dist/settings/errors.d.ts +7 -1
- package/dist/settings/errors.js +7 -1
- package/dist/settings/index.d.ts +4 -1
- package/dist/settings/index.js +1 -0
- package/dist/settings/ports.d.ts +6 -5
- package/dist/settings/principal-lookup.d.ts +9 -0
- package/dist/settings/principal-lookup.js +2 -0
- package/dist/settings/purge-service.d.ts +5 -4
- package/dist/settings/purge-service.js +3 -2
- package/dist/settings/settings.d.ts +22 -7
- package/dist/settings/settings.js +47 -26
- package/dist/settings/tool-registrations.d.ts +25 -32
- package/dist/settings/tool-registrations.js +66 -56
- package/dist/settings/types.d.ts +1 -1
- package/dist/settings/ui-tab-definitions.d.ts +1 -1
- package/dist/settings/write-service.d.ts +32 -26
- package/dist/settings/write-service.js +26 -20
- package/dist/taxonomy/agent-tools.d.ts +1 -66
- package/dist/taxonomy/agent-tools.js +25 -15
- package/dist/taxonomy/content-lookup.js +1 -1
- package/dist/taxonomy/index.d.ts +2 -2
- package/dist/taxonomy/merge-term.d.ts +7 -4
- package/dist/taxonomy/merge-term.js +7 -5
- package/dist/taxonomy/repo.memory.d.ts +42 -19
- package/dist/taxonomy/repo.memory.js +14 -12
- package/dist/taxonomy/validation-chain.d.ts +39 -18
- package/dist/taxonomy/validation-chain.js +35 -25
- package/dist/taxonomy/write-service.d.ts +103 -44
- package/dist/taxonomy/write-service.js +221 -179
- package/dist/trash/adapters/record-store.d.ts +52 -0
- package/dist/trash/adapters/record-store.js +36 -0
- package/dist/trash/cursor.d.ts +20 -0
- package/dist/trash/cursor.js +36 -0
- package/dist/trash/follow-ups.d.ts +76 -0
- package/dist/trash/follow-ups.js +46 -0
- package/dist/trash/index.d.ts +8 -0
- package/dist/trash/index.js +8 -0
- package/dist/trash/ports.d.ts +308 -0
- package/dist/trash/ports.js +2 -0
- package/dist/trash/repo.memory.d.ts +50 -0
- package/dist/trash/repo.memory.js +114 -0
- package/dist/trash/sweeper.d.ts +77 -0
- package/dist/trash/sweeper.js +127 -0
- package/dist/trash/write-service.d.ts +100 -0
- package/dist/trash/write-service.js +242 -0
- package/dist/workspace/agent-tools.d.ts +1 -59
- package/dist/workspace/agent-tools.js +27 -12
- package/dist/workspace/create.d.ts +31 -6
- package/dist/workspace/create.js +18 -5
- package/dist/workspace/delete.d.ts +18 -13
- package/dist/workspace/delete.js +20 -12
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/repo.memory.d.ts +19 -4
- package/dist/workspace/repo.memory.js +26 -4
- package/dist/workspace/tool-registrations.d.ts +2 -2
- package/dist/workspace/tool-registrations.js +13 -13
- package/dist/workspace/update.d.ts +6 -5
- package/dist/workspace/update.js +9 -8
- package/package.json +44 -38
- package/dist/identity/admin-crud-service.d.ts +0 -280
- package/dist/identity/admin-crud-service.js +0 -468
- package/dist/identity/agent-tool-input.d.ts +0 -58
- package/dist/identity/agent-tool-input.js +0 -126
- package/dist/identity/agent-tools.d.ts +0 -101
- package/dist/identity/agent-tools.js +0 -290
- package/dist/identity/auth-service.d.ts +0 -118
- package/dist/identity/auth-service.js +0 -170
- package/dist/identity/authorize.d.ts +0 -90
- package/dist/identity/authorize.js +0 -94
- package/dist/identity/grant-service.d.ts +0 -198
- package/dist/identity/grant-service.js +0 -361
- package/dist/identity/hasher.d.ts +0 -39
- package/dist/identity/hasher.js +0 -39
- package/dist/identity/index.d.ts +0 -62
- package/dist/identity/index.js +0 -54
- package/dist/identity/password-policy.d.ts +0 -57
- package/dist/identity/password-policy.js +0 -66
- package/dist/identity/permission-migrations.d.ts +0 -86
- package/dist/identity/permission-migrations.js +0 -67
- package/dist/identity/permissions.d.ts +0 -35
- package/dist/identity/permissions.js +0 -603
- package/dist/identity/ports.d.ts +0 -201
- package/dist/identity/repo.memory.d.ts +0 -164
- package/dist/identity/repo.memory.js +0 -224
- package/dist/identity/seed.d.ts +0 -54
- package/dist/identity/seed.js +0 -429
- package/dist/identity/tool-registrations.d.ts +0 -50
- package/dist/identity/tool-registrations.js +0 -419
- package/dist/identity/types.d.ts +0 -185
- package/dist/identity/types.js +0 -64
- package/dist/identity/username.d.ts +0 -12
- package/dist/identity/username.js +0 -14
- /package/dist/{identity → media/import}/ports.js +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { Clock } from "@jini-ai/core/primitives";
|
|
2
|
+
import type { Result } from "@jini-ai/core/primitives";
|
|
3
|
+
import type { ActorIdentityInput, EntryRecord, EntryStatus, OwningContentType } from "./types.js";
|
|
3
4
|
/**
|
|
4
5
|
* @file The `entries` write chokepoint: `createEntry`,
|
|
5
6
|
* `updateEntry`, `publishEntry`, `unpublishEntry`.
|
|
@@ -38,16 +39,8 @@ import type { ActorIdentityInput, EntryRecord, EntryStatus, OwningContentType, R
|
|
|
38
39
|
* tools call these functions directly, never through an HTTP route), so it must be at least as
|
|
39
40
|
* expressive as the route, not merely consistent with it.
|
|
40
41
|
*/
|
|
41
|
-
export type AuthorizeFn
|
|
42
|
-
|
|
43
|
-
permission: string;
|
|
44
|
-
workspaceId: string;
|
|
45
|
-
entityType?: string | undefined;
|
|
46
|
-
entityId?: string | undefined;
|
|
47
|
-
}) => Promise<{
|
|
48
|
-
allowed: boolean;
|
|
49
|
-
reason: string;
|
|
50
|
-
}>;
|
|
42
|
+
export type { AuthorizationPort as AuthorizeFn } from '../core/authorization.js';
|
|
43
|
+
import type { AuthorizationPort as AuthorizeFn } from '../core/authorization.js';
|
|
51
44
|
export interface EntryRevisionInput {
|
|
52
45
|
entryId: string;
|
|
53
46
|
workspaceId: string;
|
|
@@ -70,7 +63,9 @@ export interface EntryRepoPort {
|
|
|
70
63
|
}): Promise<EntryRecord | null>;
|
|
71
64
|
save(row: EntryRecord): Promise<void>;
|
|
72
65
|
appendRevision(revision: EntryRevisionInput): Promise<void>;
|
|
73
|
-
transaction<T>(
|
|
66
|
+
transaction<T>(required: {
|
|
67
|
+
fn: () => Promise<T>;
|
|
68
|
+
}): Promise<T>;
|
|
74
69
|
}
|
|
75
70
|
export interface ContentTypeLookupPort {
|
|
76
71
|
findByKey(params: {
|
|
@@ -94,7 +89,7 @@ export interface CreateEntryRequired {
|
|
|
94
89
|
deps: {
|
|
95
90
|
entryRepo: EntryRepoPort;
|
|
96
91
|
contentTypeRepo: ContentTypeLookupPort;
|
|
97
|
-
clock:
|
|
92
|
+
clock: Clock;
|
|
98
93
|
ids: {
|
|
99
94
|
newId: () => string;
|
|
100
95
|
};
|
|
@@ -121,15 +116,16 @@ export interface CreateEntryRequired {
|
|
|
121
116
|
};
|
|
122
117
|
}
|
|
123
118
|
/**
|
|
124
|
-
*
|
|
125
|
-
* workspace
|
|
126
|
-
* type's current schema -> `(workspaceId, type, slug)` uniqueness
|
|
119
|
+
* — creates a new entry. Order: authorize -> owning-type exists AND is owned by this
|
|
120
|
+
* workspace -> owning-type is `active` -> `fieldsJson` validates against the
|
|
121
|
+
* type's current schema -> `(workspaceId, type, slug)` uniqueness -> same-tx write +
|
|
127
122
|
* revision + watermark + optional `deps.onWritten` side effect -> `entry.created` outbox event
|
|
128
|
-
*
|
|
123
|
+
*
|
|
129
124
|
*
|
|
130
125
|
* @complexity O(1) plus one content-type read, one field-validation pass, one slug lookup, and one
|
|
131
126
|
* same-tx write pair.
|
|
132
127
|
* @overallScore 100
|
|
128
|
+
* See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
|
|
133
129
|
*/
|
|
134
130
|
export declare function createEntry(required: CreateEntryRequired): Promise<Result<{
|
|
135
131
|
entry: EntryRecord;
|
|
@@ -137,7 +133,7 @@ export declare function createEntry(required: CreateEntryRequired): Promise<Resu
|
|
|
137
133
|
interface ExistingEntryTransitionDeps {
|
|
138
134
|
entryRepo: EntryRepoPort;
|
|
139
135
|
contentTypeRepo: ContentTypeLookupPort;
|
|
140
|
-
clock:
|
|
136
|
+
clock: Clock;
|
|
141
137
|
authorize: AuthorizeFn;
|
|
142
138
|
outbox: OutboxPort;
|
|
143
139
|
watermark?: WatermarkPort;
|
|
@@ -173,13 +169,14 @@ export interface UpdateEntryRequired {
|
|
|
173
169
|
};
|
|
174
170
|
}
|
|
175
171
|
/**
|
|
176
|
-
*
|
|
172
|
+
* — updates an existing entry's `title`/`fieldsJson`/`bodyJson` (only the fields supplied
|
|
177
173
|
* are changed). Rejected `ContentTypeNotActiveError` only if the owning type is `tombstone`
|
|
178
|
-
*
|
|
174
|
+
* a `deprecated` owning type is fine.
|
|
179
175
|
*
|
|
180
176
|
* @complexity O(1) plus the shared resolve step and, when `fieldsJson` is supplied, one
|
|
181
177
|
* field-validation pass.
|
|
182
178
|
* @overallScore 100
|
|
179
|
+
* See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
|
|
183
180
|
*/
|
|
184
181
|
export declare function updateEntry(required: UpdateEntryRequired): Promise<Result<{
|
|
185
182
|
entry: EntryRecord;
|
|
@@ -188,7 +185,7 @@ export interface ImportEntryRequired {
|
|
|
188
185
|
deps: {
|
|
189
186
|
entryRepo: EntryRepoPort;
|
|
190
187
|
contentTypeRepo: ContentTypeLookupPort;
|
|
191
|
-
clock:
|
|
188
|
+
clock: Clock;
|
|
192
189
|
authorize: AuthorizeFn;
|
|
193
190
|
outbox: OutboxPort;
|
|
194
191
|
watermark?: WatermarkPort;
|
|
@@ -229,14 +226,15 @@ export interface ImportEntryRequired {
|
|
|
229
226
|
* cannot produce, and the reason this is a new function rather than a wider `createEntry`.
|
|
230
227
|
*
|
|
231
228
|
* A tombstoned owning type refuses the import (mirrors `resolveExistingEntryForTransition`'s
|
|
232
|
-
*
|
|
233
|
-
* import —
|
|
229
|
+
* rule); unlike `createEntry`'s, a `deprecated` owning type does NOT block an
|
|
230
|
+
* import — exists to stop new manual authoring against a type an operator is winding down,
|
|
234
231
|
* not to stop a publish run from keeping an already-existing entry's already-existing type in
|
|
235
232
|
* sync. Slug uniqueness is deliberately not checked here: the publish factory's own `address`
|
|
236
233
|
* precheck (config.address, F1) owns that refusal before `importEntry` is ever called.
|
|
237
234
|
*
|
|
238
235
|
* @complexity O(1) plus one content-type read, one field-validation pass, one id lookup, and one
|
|
239
236
|
* same-tx write pair.
|
|
237
|
+
* See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
|
|
240
238
|
*/
|
|
241
239
|
export declare function importEntry(required: ImportEntryRequired): Promise<Result<{
|
|
242
240
|
entry: EntryRecord;
|
|
@@ -250,24 +248,25 @@ export interface PublishUnpublishEntryRequired {
|
|
|
250
248
|
};
|
|
251
249
|
}
|
|
252
250
|
/**
|
|
253
|
-
*
|
|
254
|
-
* type is `tombstone
|
|
251
|
+
* — flips an entry to `published`. Rejected `ContentTypeNotActiveError` only if the owning
|
|
252
|
+
* type is `tombstone`, with NO outbox event enqueued on rejection.
|
|
255
253
|
*
|
|
256
254
|
* @complexity O(1) plus the shared resolve step and one same-tx write pair.
|
|
257
255
|
* @overallScore 100
|
|
256
|
+
* See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
|
|
258
257
|
*/
|
|
259
258
|
export declare function publishEntry(required: PublishUnpublishEntryRequired): Promise<Result<{
|
|
260
259
|
entry: EntryRecord;
|
|
261
260
|
}, Error>>;
|
|
262
261
|
/**
|
|
263
|
-
*
|
|
264
|
-
* owning type is `tombstone
|
|
262
|
+
* — flips an entry to `unpublished`. Rejected `ContentTypeNotActiveError` only if the
|
|
263
|
+
* owning type is `tombstone`, with NO outbox event enqueued on rejection.
|
|
265
264
|
*
|
|
266
265
|
* @complexity O(1) plus the shared resolve step and one same-tx write pair.
|
|
267
266
|
* @overallScore 100
|
|
267
|
+
* See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
|
|
268
268
|
*/
|
|
269
269
|
export declare function unpublishEntry(required: PublishUnpublishEntryRequired): Promise<Result<{
|
|
270
270
|
entry: EntryRecord;
|
|
271
271
|
}, Error>>;
|
|
272
|
-
export {};
|
|
273
272
|
//# sourceMappingURL=write-service.d.ts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nowIso as kernelNowIso } from "@jini-ai/core/primitives";
|
|
1
2
|
import { ContentTypeNotActiveError, ContentTypeNotFoundError, EntryFieldValidationError, EntryNotFoundError, EntrySlugConflictError, ForbiddenError, VersionConflictError, } from "./errors.js";
|
|
2
3
|
import { validateFieldsAgainstSchema } from "./field-validation.js";
|
|
3
4
|
function delegationFields(input) {
|
|
@@ -7,38 +8,40 @@ function delegationFields(input) {
|
|
|
7
8
|
};
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
-
* workspace
|
|
12
|
-
* type's current schema -> `(workspaceId, type, slug)` uniqueness
|
|
11
|
+
* — creates a new entry. Order: authorize -> owning-type exists AND is owned by this
|
|
12
|
+
* workspace -> owning-type is `active` -> `fieldsJson` validates against the
|
|
13
|
+
* type's current schema -> `(workspaceId, type, slug)` uniqueness -> same-tx write +
|
|
13
14
|
* revision + watermark + optional `deps.onWritten` side effect -> `entry.created` outbox event
|
|
14
|
-
*
|
|
15
|
+
*
|
|
15
16
|
*
|
|
16
17
|
* @complexity O(1) plus one content-type read, one field-validation pass, one slug lookup, and one
|
|
17
18
|
* same-tx write pair.
|
|
18
19
|
* @overallScore 100
|
|
20
|
+
* See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
|
|
19
21
|
*/
|
|
20
22
|
export async function createEntry(required) {
|
|
21
23
|
const { deps, input } = required;
|
|
22
|
-
const authResult = await deps.authorize({ principalId: input.actorId, permission: "admin.collections.manage", workspaceId: input.workspaceId, entityType: "entry" });
|
|
24
|
+
const authResult = await deps.authorize({ principalId: input.actorId, permission: "admin.collections.manage", workspaceId: input.workspaceId }, { entityType: "entry" });
|
|
23
25
|
if (!authResult.allowed) {
|
|
24
|
-
return { ok: false, error: new ForbiddenError(`principal '${input.actorId}' cannot create an entry (${authResult.reason})`) };
|
|
26
|
+
return { ok: false, error: new ForbiddenError({ message: `principal '${input.actorId}' cannot create an entry (${authResult.reason})` }) };
|
|
25
27
|
}
|
|
26
28
|
const contentType = await deps.contentTypeRepo.findByKey({ workspaceId: input.workspaceId, key: input.type });
|
|
27
29
|
if (!contentType || contentType.workspaceId !== input.workspaceId) {
|
|
28
|
-
return { ok: false, error: new ContentTypeNotFoundError(`content type '${input.type}' was not found in workspace '${input.workspaceId}'`) };
|
|
30
|
+
return { ok: false, error: new ContentTypeNotFoundError({ message: `content type '${input.type}' was not found in workspace '${input.workspaceId}'` }) };
|
|
29
31
|
}
|
|
30
32
|
if (contentType.status !== "active") {
|
|
31
|
-
|
|
33
|
+
// See docs/decisions/DR-002-content-lifecycle-and-cleanup.md for the invariant behind this refusal; keep external identifiers out of caller-facing messages.
|
|
34
|
+
return { ok: false, error: new ContentTypeNotActiveError({ message: `content type '${input.type}' is not active; new entries cannot be created` }) };
|
|
32
35
|
}
|
|
33
|
-
const validation = validateFieldsAgainstSchema({ schema: contentType.fields, fieldsJson: input.fieldsJson, owner: input.owner });
|
|
36
|
+
const validation = validateFieldsAgainstSchema({ schema: contentType.fields, fieldsJson: input.fieldsJson }, { owner: input.owner });
|
|
34
37
|
if (!validation.valid) {
|
|
35
|
-
return { ok: false, error: new EntryFieldValidationError(validation.fieldErrors) };
|
|
38
|
+
return { ok: false, error: new EntryFieldValidationError({ fieldErrors: validation.fieldErrors }) };
|
|
36
39
|
}
|
|
37
40
|
const existing = await deps.entryRepo.findBySlug({ workspaceId: input.workspaceId, type: input.type, slug: input.slug });
|
|
38
41
|
if (existing) {
|
|
39
|
-
return { ok: false, error: new EntrySlugConflictError(`an entry with slug '${input.slug}' already exists for type '${input.type}' in workspace '${input.workspaceId}'`) };
|
|
42
|
+
return { ok: false, error: new EntrySlugConflictError({ message: `an entry with slug '${input.slug}' already exists for type '${input.type}' in workspace '${input.workspaceId}'` }) };
|
|
40
43
|
}
|
|
41
|
-
const now = deps.clock
|
|
44
|
+
const now = kernelNowIso({ clock: deps.clock });
|
|
42
45
|
const entry = {
|
|
43
46
|
id: deps.ids.newId(),
|
|
44
47
|
workspaceId: input.workspaceId,
|
|
@@ -53,60 +56,63 @@ export async function createEntry(required) {
|
|
|
53
56
|
updatedAt: now,
|
|
54
57
|
version: 1,
|
|
55
58
|
};
|
|
56
|
-
await deps.entryRepo.transaction(async () => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
await deps.entryRepo.transaction({ fn: async () => {
|
|
60
|
+
await deps.entryRepo.save(entry);
|
|
61
|
+
await deps.entryRepo.appendRevision({
|
|
62
|
+
entryId: entry.id,
|
|
63
|
+
workspaceId: input.workspaceId,
|
|
64
|
+
op: "create",
|
|
65
|
+
stateJson: entry,
|
|
66
|
+
actorId: input.actorId,
|
|
67
|
+
...delegationFields(input),
|
|
68
|
+
recordedAt: now,
|
|
69
|
+
});
|
|
70
|
+
if (deps.watermark)
|
|
71
|
+
await deps.watermark.stampWatermark({ workspaceId: input.workspaceId });
|
|
72
|
+
if (deps.onWritten)
|
|
73
|
+
await deps.onWritten(entry);
|
|
74
|
+
} });
|
|
72
75
|
await deps.outbox.enqueue({ name: "entry.created", payload: { workspaceId: input.workspaceId, entryId: entry.id, type: input.type, slug: input.slug } });
|
|
73
76
|
return { ok: true, value: { entry } };
|
|
74
77
|
}
|
|
75
78
|
/**
|
|
76
|
-
*
|
|
79
|
+
* shared resolve step for `updateEntry`/`publishEntry`/`unpublishEntry`: authorize ->
|
|
77
80
|
* find the entry -> find its owning type -> reject ONLY if that type is `tombstone`
|
|
78
|
-
* (`deprecated` blocks nothing here, unlike `createEntry`'s
|
|
81
|
+
* (`deprecated` blocks nothing here, unlike `createEntry`'s rule) -> `expectedVersion`
|
|
79
82
|
* check.
|
|
80
83
|
*
|
|
81
84
|
* @complexity O(1) plus one entry read and one content-type read.
|
|
82
85
|
* @overallScore 100
|
|
86
|
+
* See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
|
|
83
87
|
*/
|
|
84
88
|
async function resolveExistingEntryForTransition(deps, input) {
|
|
85
|
-
const authResult = await deps.authorize({ principalId: input.actorId, permission: "admin.collections.manage", workspaceId: input.workspaceId, entityType: "entry" });
|
|
89
|
+
const authResult = await deps.authorize({ principalId: input.actorId, permission: "admin.collections.manage", workspaceId: input.workspaceId }, { entityType: "entry" });
|
|
86
90
|
if (!authResult.allowed) {
|
|
87
|
-
return { ok: false, error: new ForbiddenError(`principal '${input.actorId}' cannot modify entry '${input.id}' (${authResult.reason})`) };
|
|
91
|
+
return { ok: false, error: new ForbiddenError({ message: `principal '${input.actorId}' cannot modify entry '${input.id}' (${authResult.reason})` }) };
|
|
88
92
|
}
|
|
89
93
|
const entry = await deps.entryRepo.findById({ workspaceId: input.workspaceId, id: input.id });
|
|
90
94
|
if (!entry) {
|
|
91
|
-
return { ok: false, error: new EntryNotFoundError(`entry '${input.id}' was not found in workspace '${input.workspaceId}'`) };
|
|
95
|
+
return { ok: false, error: new EntryNotFoundError({ message: `entry '${input.id}' was not found in workspace '${input.workspaceId}'` }) };
|
|
92
96
|
}
|
|
93
97
|
const contentType = await deps.contentTypeRepo.findByKey({ workspaceId: input.workspaceId, key: entry.type });
|
|
94
98
|
if (contentType && contentType.status === "tombstone") {
|
|
95
|
-
|
|
99
|
+
// See docs/decisions/DR-002-content-lifecycle-and-cleanup.md for the invariant behind this refusal; keep external identifiers out of caller-facing messages.
|
|
100
|
+
return { ok: false, error: new ContentTypeNotActiveError({ message: `content type '${entry.type}' is tombstoned; existing entries cannot be updated/published/unpublished` }) };
|
|
96
101
|
}
|
|
97
102
|
if (input.expectedVersion !== entry.version) {
|
|
98
|
-
return { ok: false, error: new VersionConflictError(`expected version ${input.expectedVersion} for entry '${input.id}', found ${entry.version}`) };
|
|
103
|
+
return { ok: false, error: new VersionConflictError({ message: `expected version ${input.expectedVersion} for entry '${input.id}', found ${entry.version}` }) };
|
|
99
104
|
}
|
|
100
105
|
return { ok: true, value: { entry, contentType } };
|
|
101
106
|
}
|
|
102
107
|
/**
|
|
103
|
-
*
|
|
108
|
+
* — updates an existing entry's `title`/`fieldsJson`/`bodyJson` (only the fields supplied
|
|
104
109
|
* are changed). Rejected `ContentTypeNotActiveError` only if the owning type is `tombstone`
|
|
105
|
-
*
|
|
110
|
+
* a `deprecated` owning type is fine.
|
|
106
111
|
*
|
|
107
112
|
* @complexity O(1) plus the shared resolve step and, when `fieldsJson` is supplied, one
|
|
108
113
|
* field-validation pass.
|
|
109
114
|
* @overallScore 100
|
|
115
|
+
* See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
|
|
110
116
|
*/
|
|
111
117
|
export async function updateEntry(required) {
|
|
112
118
|
const { deps, input } = required;
|
|
@@ -116,13 +122,13 @@ export async function updateEntry(required) {
|
|
|
116
122
|
const { entry: current, contentType } = resolved.value;
|
|
117
123
|
let fieldsJson = current.fieldsJson;
|
|
118
124
|
if (input.fieldsJson !== undefined) {
|
|
119
|
-
const validation = validateFieldsAgainstSchema({ schema: contentType?.fields ?? [], fieldsJson: input.fieldsJson, owner: input.owner });
|
|
125
|
+
const validation = validateFieldsAgainstSchema({ schema: contentType?.fields ?? [], fieldsJson: input.fieldsJson }, { owner: input.owner });
|
|
120
126
|
if (!validation.valid) {
|
|
121
|
-
return { ok: false, error: new EntryFieldValidationError(validation.fieldErrors) };
|
|
127
|
+
return { ok: false, error: new EntryFieldValidationError({ fieldErrors: validation.fieldErrors }) };
|
|
122
128
|
}
|
|
123
129
|
fieldsJson = input.fieldsJson;
|
|
124
130
|
}
|
|
125
|
-
const now = deps.clock
|
|
131
|
+
const now = kernelNowIso({ clock: deps.clock });
|
|
126
132
|
const updated = {
|
|
127
133
|
...current,
|
|
128
134
|
title: input.title ?? current.title,
|
|
@@ -131,22 +137,22 @@ export async function updateEntry(required) {
|
|
|
131
137
|
updatedAt: now,
|
|
132
138
|
version: current.version + 1,
|
|
133
139
|
};
|
|
134
|
-
await deps.entryRepo.transaction(async () => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
140
|
+
await deps.entryRepo.transaction({ fn: async () => {
|
|
141
|
+
await deps.entryRepo.save(updated);
|
|
142
|
+
await deps.entryRepo.appendRevision({
|
|
143
|
+
entryId: current.id,
|
|
144
|
+
workspaceId: input.workspaceId,
|
|
145
|
+
op: "update",
|
|
146
|
+
stateJson: updated,
|
|
147
|
+
actorId: input.actorId,
|
|
148
|
+
...delegationFields(input),
|
|
149
|
+
recordedAt: now,
|
|
150
|
+
});
|
|
151
|
+
if (deps.watermark)
|
|
152
|
+
await deps.watermark.stampWatermark({ workspaceId: input.workspaceId });
|
|
153
|
+
if (deps.onWritten)
|
|
154
|
+
await deps.onWritten(updated);
|
|
155
|
+
} });
|
|
150
156
|
await deps.outbox.enqueue({ name: "entry.updated", payload: { workspaceId: input.workspaceId, entryId: current.id } });
|
|
151
157
|
return { ok: true, value: { entry: updated } };
|
|
152
158
|
}
|
|
@@ -158,52 +164,53 @@ export async function updateEntry(required) {
|
|
|
158
164
|
* cannot produce, and the reason this is a new function rather than a wider `createEntry`.
|
|
159
165
|
*
|
|
160
166
|
* A tombstoned owning type refuses the import (mirrors `resolveExistingEntryForTransition`'s
|
|
161
|
-
*
|
|
162
|
-
* import —
|
|
167
|
+
* rule); unlike `createEntry`'s, a `deprecated` owning type does NOT block an
|
|
168
|
+
* import — exists to stop new manual authoring against a type an operator is winding down,
|
|
163
169
|
* not to stop a publish run from keeping an already-existing entry's already-existing type in
|
|
164
170
|
* sync. Slug uniqueness is deliberately not checked here: the publish factory's own `address`
|
|
165
171
|
* precheck (config.address, F1) owns that refusal before `importEntry` is ever called.
|
|
166
172
|
*
|
|
167
173
|
* @complexity O(1) plus one content-type read, one field-validation pass, one id lookup, and one
|
|
168
174
|
* same-tx write pair.
|
|
175
|
+
* See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
|
|
169
176
|
*/
|
|
170
177
|
export async function importEntry(required) {
|
|
171
178
|
const { deps, input } = required;
|
|
172
|
-
const authResult = await deps.authorize({ principalId: input.actorId, permission: "admin.collections.manage", workspaceId: input.workspaceId, entityType: "entry" });
|
|
179
|
+
const authResult = await deps.authorize({ principalId: input.actorId, permission: "admin.collections.manage", workspaceId: input.workspaceId }, { entityType: "entry" });
|
|
173
180
|
if (!authResult.allowed) {
|
|
174
|
-
return { ok: false, error: new ForbiddenError(`principal '${input.actorId}' cannot import entry '${input.id}' (${authResult.reason})`) };
|
|
181
|
+
return { ok: false, error: new ForbiddenError({ message: `principal '${input.actorId}' cannot import entry '${input.id}' (${authResult.reason})` }) };
|
|
175
182
|
}
|
|
176
183
|
const contentType = await deps.contentTypeRepo.findByKey({ workspaceId: input.workspaceId, key: input.type });
|
|
177
184
|
if (!contentType || contentType.workspaceId !== input.workspaceId) {
|
|
178
|
-
return { ok: false, error: new ContentTypeNotFoundError(`content type '${input.type}' was not found in workspace '${input.workspaceId}'`) };
|
|
185
|
+
return { ok: false, error: new ContentTypeNotFoundError({ message: `content type '${input.type}' was not found in workspace '${input.workspaceId}'` }) };
|
|
179
186
|
}
|
|
180
187
|
if (contentType.status === "tombstone") {
|
|
181
|
-
return { ok: false, error: new ContentTypeNotActiveError(`content type '${input.type}' is tombstoned; entries cannot be imported into it`) };
|
|
188
|
+
return { ok: false, error: new ContentTypeNotActiveError({ message: `content type '${input.type}' is tombstoned; entries cannot be imported into it` }) };
|
|
182
189
|
}
|
|
183
|
-
const validation = validateFieldsAgainstSchema({ schema: contentType.fields, fieldsJson: input.fieldsJson, owner: input.owner });
|
|
190
|
+
const validation = validateFieldsAgainstSchema({ schema: contentType.fields, fieldsJson: input.fieldsJson }, { owner: input.owner });
|
|
184
191
|
if (!validation.valid) {
|
|
185
|
-
return { ok: false, error: new EntryFieldValidationError(validation.fieldErrors) };
|
|
192
|
+
return { ok: false, error: new EntryFieldValidationError({ fieldErrors: validation.fieldErrors }) };
|
|
186
193
|
}
|
|
187
194
|
const existing = await deps.entryRepo.findById({ workspaceId: input.workspaceId, id: input.id });
|
|
188
195
|
if (input.expectedVersion === undefined) {
|
|
189
196
|
if (existing) {
|
|
190
|
-
return { ok: false, error: new VersionConflictError(`entry '${input.id}' already exists in workspace '${input.workspaceId}', but no expectedVersion was supplied for import`) };
|
|
197
|
+
return { ok: false, error: new VersionConflictError({ message: `entry '${input.id}' already exists in workspace '${input.workspaceId}', but no expectedVersion was supplied for import` }) };
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
200
|
else {
|
|
194
201
|
if (!existing) {
|
|
195
|
-
return { ok: false, error: new VersionConflictError(`expected version ${input.expectedVersion} for entry '${input.id}', but no such entry exists`) };
|
|
202
|
+
return { ok: false, error: new VersionConflictError({ message: `expected version ${input.expectedVersion} for entry '${input.id}', but no such entry exists` }) };
|
|
196
203
|
}
|
|
197
204
|
if (existing.version !== input.expectedVersion) {
|
|
198
|
-
return { ok: false, error: new VersionConflictError(`expected version ${input.expectedVersion} for entry '${input.id}', found ${existing.version}`) };
|
|
205
|
+
return { ok: false, error: new VersionConflictError({ message: `expected version ${input.expectedVersion} for entry '${input.id}', found ${existing.version}` }) };
|
|
199
206
|
}
|
|
200
207
|
// Same id, different type means this is not the row the caller's plan was made against; a
|
|
201
|
-
// silent type move would also dodge the
|
|
208
|
+
// silent type move would also dodge the tombstone check on the entry's REAL type. See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
|
|
202
209
|
if (existing.type !== input.type) {
|
|
203
|
-
return { ok: false, error: new VersionConflictError(`entry '${input.id}' is of type '${existing.type}', not '${input.type}'; an import cannot change an entry's type`) };
|
|
210
|
+
return { ok: false, error: new VersionConflictError({ message: `entry '${input.id}' is of type '${existing.type}', not '${input.type}'; an import cannot change an entry's type` }) };
|
|
204
211
|
}
|
|
205
212
|
}
|
|
206
|
-
const now = deps.clock
|
|
213
|
+
const now = kernelNowIso({ clock: deps.clock });
|
|
207
214
|
const entry = {
|
|
208
215
|
id: input.id,
|
|
209
216
|
workspaceId: input.workspaceId,
|
|
@@ -218,22 +225,22 @@ export async function importEntry(required) {
|
|
|
218
225
|
updatedAt: now,
|
|
219
226
|
version: existing ? existing.version + 1 : 1,
|
|
220
227
|
};
|
|
221
|
-
await deps.entryRepo.transaction(async () => {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
228
|
+
await deps.entryRepo.transaction({ fn: async () => {
|
|
229
|
+
await deps.entryRepo.save(entry);
|
|
230
|
+
await deps.entryRepo.appendRevision({
|
|
231
|
+
entryId: entry.id,
|
|
232
|
+
workspaceId: input.workspaceId,
|
|
233
|
+
op: existing ? "update" : "create",
|
|
234
|
+
stateJson: entry,
|
|
235
|
+
actorId: input.actorId,
|
|
236
|
+
...delegationFields(input),
|
|
237
|
+
recordedAt: now,
|
|
238
|
+
});
|
|
239
|
+
if (deps.watermark)
|
|
240
|
+
await deps.watermark.stampWatermark({ workspaceId: input.workspaceId });
|
|
241
|
+
if (deps.onWritten)
|
|
242
|
+
await deps.onWritten(entry);
|
|
243
|
+
} });
|
|
237
244
|
await deps.outbox.enqueue({ name: "entry.imported", payload: { workspaceId: input.workspaceId, entryId: entry.id, type: input.type, slug: input.slug } });
|
|
238
245
|
return { ok: true, value: { entry } };
|
|
239
246
|
}
|
|
@@ -243,7 +250,7 @@ async function transitionEntryStatus(required, target) {
|
|
|
243
250
|
if (!resolved.ok)
|
|
244
251
|
return resolved;
|
|
245
252
|
const { entry: current } = resolved.value;
|
|
246
|
-
const now = deps.clock
|
|
253
|
+
const now = kernelNowIso({ clock: deps.clock });
|
|
247
254
|
const updated = {
|
|
248
255
|
...current,
|
|
249
256
|
status: target.status,
|
|
@@ -251,39 +258,41 @@ async function transitionEntryStatus(required, target) {
|
|
|
251
258
|
updatedAt: now,
|
|
252
259
|
version: current.version + 1,
|
|
253
260
|
};
|
|
254
|
-
await deps.entryRepo.transaction(async () => {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
261
|
+
await deps.entryRepo.transaction({ fn: async () => {
|
|
262
|
+
await deps.entryRepo.save(updated);
|
|
263
|
+
await deps.entryRepo.appendRevision({
|
|
264
|
+
entryId: current.id,
|
|
265
|
+
workspaceId: input.workspaceId,
|
|
266
|
+
op: target.op,
|
|
267
|
+
stateJson: updated,
|
|
268
|
+
actorId: input.actorId,
|
|
269
|
+
...delegationFields(input),
|
|
270
|
+
recordedAt: now,
|
|
271
|
+
});
|
|
272
|
+
if (deps.watermark)
|
|
273
|
+
await deps.watermark.stampWatermark({ workspaceId: input.workspaceId });
|
|
274
|
+
} });
|
|
268
275
|
await deps.outbox.enqueue({ name: target.eventName, payload: { workspaceId: input.workspaceId, entryId: current.id } });
|
|
269
276
|
return { ok: true, value: { entry: updated } };
|
|
270
277
|
}
|
|
271
278
|
/**
|
|
272
|
-
*
|
|
273
|
-
* type is `tombstone
|
|
279
|
+
* — flips an entry to `published`. Rejected `ContentTypeNotActiveError` only if the owning
|
|
280
|
+
* type is `tombstone`, with NO outbox event enqueued on rejection.
|
|
274
281
|
*
|
|
275
282
|
* @complexity O(1) plus the shared resolve step and one same-tx write pair.
|
|
276
283
|
* @overallScore 100
|
|
284
|
+
* See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
|
|
277
285
|
*/
|
|
278
286
|
export async function publishEntry(required) {
|
|
279
287
|
return transitionEntryStatus(required, { status: "published", op: "publish", eventName: "entry.published" });
|
|
280
288
|
}
|
|
281
289
|
/**
|
|
282
|
-
*
|
|
283
|
-
* owning type is `tombstone
|
|
290
|
+
* — flips an entry to `unpublished`. Rejected `ContentTypeNotActiveError` only if the
|
|
291
|
+
* owning type is `tombstone`, with NO outbox event enqueued on rejection.
|
|
284
292
|
*
|
|
285
293
|
* @complexity O(1) plus the shared resolve step and one same-tx write pair.
|
|
286
294
|
* @overallScore 100
|
|
295
|
+
* See docs/decisions/DR-002-content-lifecycle-and-cleanup.md.
|
|
287
296
|
*/
|
|
288
297
|
export async function unpublishEntry(required) {
|
|
289
298
|
return transitionEntryStatus(required, { status: "unpublished", op: "unpublish", eventName: "entry.unpublished" });
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type SettingsWriteServiceDeps, type SettingsRepoPort } from "../../settings/index.js";
|
|
2
|
+
import type { SettingsService, SettingsPermissions, SettingsChangeFeed } from "./contracts.js";
|
|
3
|
+
/** Keep the CMS write chokepoint while translating its permission vocabulary to host policy. */
|
|
4
|
+
/** Bind the CMS ledger and write chokepoint without copying their domain logic.
|
|
5
|
+
* Effective reads scan definitions and resolve each namespace key; mutations inherit CMS transactions/errors.
|
|
6
|
+
* @complexity O(1) setup. Per operation: O(definitions + keys) reads, or O(batch size) writes.
|
|
7
|
+
* @example createCmsSettingsService({ deps: cmsPorts, permissions });
|
|
8
|
+
*/
|
|
9
|
+
export declare function createCmsSettingsService(required: {
|
|
10
|
+
deps: SettingsWriteServiceDeps;
|
|
11
|
+
permissions: SettingsPermissions;
|
|
12
|
+
}): SettingsService;
|
|
13
|
+
/** Adapt the ledger through CMS tenant/principal disclosure rules, returning names and cursors only.
|
|
14
|
+
* @complexity O(1) setup; collect is O(page size), with namespace lookup per visible revision.
|
|
15
|
+
* @example createCmsSettingsChangeFeed({ repo });
|
|
16
|
+
*/
|
|
17
|
+
export declare function createCmsSettingsChangeFeed(required: {
|
|
18
|
+
repo: SettingsRepoPort;
|
|
19
|
+
}): SettingsChangeFeed;
|
|
20
|
+
//# sourceMappingURL=cms-adapter.d.ts.map
|