@jini-ai/cms 0.5.2 → 0.5.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.
- package/CHANGELOG.md +11 -0
- package/README.md +14 -8
- package/dist/comments/errors.d.ts +23 -0
- package/dist/comments/errors.js +25 -0
- package/dist/comments/express/index.d.ts +2 -0
- package/dist/comments/express/index.js +2 -0
- package/dist/comments/express/submit-handler.d.ts +30 -0
- package/dist/comments/express/submit-handler.js +85 -0
- package/dist/comments/hooks.d.ts +39 -0
- package/dist/comments/hooks.js +41 -0
- package/dist/comments/index.d.ts +11 -0
- package/dist/comments/index.js +11 -0
- package/dist/comments/ingress.d.ts +57 -0
- package/dist/comments/ingress.js +173 -0
- package/dist/comments/module.d.ts +94 -0
- package/dist/comments/module.js +73 -0
- package/dist/comments/ports.d.ts +184 -0
- package/dist/comments/ports.js +2 -0
- package/dist/comments/repo.memory.d.ts +70 -0
- package/dist/comments/repo.memory.js +120 -0
- package/dist/comments/sanitize.d.ts +15 -0
- package/dist/comments/sanitize.js +31 -0
- package/dist/comments/spam.heuristic.d.ts +8 -0
- package/dist/comments/spam.heuristic.js +54 -0
- package/dist/comments/sql/index.d.ts +3 -0
- package/dist/comments/sql/index.js +3 -0
- package/dist/comments/sql/repo.d.ts +77 -0
- package/dist/comments/sql/repo.js +190 -0
- package/dist/comments/sql/repo.rows.d.ts +49 -0
- package/dist/comments/sql/repo.rows.js +71 -0
- package/dist/comments/types.d.ts +187 -0
- package/dist/comments/types.js +39 -0
- package/dist/comments/write-service.d.ts +121 -0
- package/dist/comments/write-service.js +144 -0
- package/dist/core/commands/command.d.ts +2 -9
- package/dist/core/commands/command.js +2 -9
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +1 -0
- package/dist/core/keyset-cursor.d.ts +25 -0
- package/dist/core/keyset-cursor.js +20 -0
- package/dist/core/ports.d.ts +30 -0
- package/dist/core/ports.js +0 -1
- package/dist/core/tools/args.d.ts +1 -46
- package/dist/core/tools/args.js +1 -44
- package/dist/media/index.d.ts +0 -3
- package/dist/media/index.js +1 -4
- package/dist/media/node.d.ts +5 -0
- package/dist/media/node.js +6 -0
- package/dist/redirects/capture.d.ts +80 -0
- package/dist/redirects/capture.js +66 -0
- package/dist/redirects/express/index.d.ts +27 -0
- package/dist/redirects/express/index.js +40 -0
- package/dist/redirects/hit-sink.d.ts +56 -0
- package/dist/redirects/hit-sink.js +44 -0
- package/dist/redirects/host-config/index.d.ts +40 -0
- package/dist/redirects/host-config/index.js +145 -0
- package/dist/redirects/index.d.ts +14 -0
- package/dist/redirects/index.js +14 -0
- package/dist/redirects/matcher.d.ts +58 -0
- package/dist/redirects/matcher.js +144 -0
- package/dist/redirects/order.d.ts +30 -0
- package/dist/redirects/order.js +26 -0
- package/dist/redirects/ports.d.ts +242 -0
- package/dist/redirects/ports.internal.d.ts +57 -0
- package/dist/redirects/ports.internal.js +14 -0
- package/dist/redirects/ports.js +2 -0
- package/dist/redirects/redirects.d.ts +226 -0
- package/dist/redirects/redirects.js +590 -0
- package/dist/redirects/referrer-alias.d.ts +31 -0
- package/dist/redirects/referrer-alias.js +33 -0
- package/dist/redirects/repo.memory.d.ts +51 -0
- package/dist/redirects/repo.memory.js +98 -0
- package/dist/redirects/reserved-destination.d.ts +58 -0
- package/dist/redirects/reserved-destination.js +59 -0
- package/dist/redirects/resolver.d.ts +100 -0
- package/dist/redirects/resolver.js +87 -0
- package/dist/redirects/spa/index.d.ts +30 -0
- package/dist/redirects/spa/index.js +27 -0
- package/dist/redirects/sql/index.d.ts +4 -0
- package/dist/redirects/sql/index.js +4 -0
- package/dist/redirects/sql/repo.d.ts +82 -0
- package/dist/redirects/sql/repo.js +143 -0
- package/dist/redirects/sql/repo.rows.d.ts +49 -0
- package/dist/redirects/sql/repo.rows.js +79 -0
- package/dist/redirects/sql/row-types.d.ts +34 -0
- package/dist/redirects/sql/row-types.js +2 -0
- package/dist/redirects/target-gate.d.ts +30 -0
- package/dist/redirects/target-gate.js +85 -0
- package/dist/redirects/types.d.ts +213 -0
- package/dist/redirects/types.js +42 -0
- package/dist/seo/absolute-url.d.ts +58 -0
- package/dist/seo/absolute-url.js +117 -0
- package/dist/seo/dom/index.d.ts +14 -0
- package/dist/seo/dom/index.js +52 -0
- package/dist/seo/errors.d.ts +35 -0
- package/dist/seo/errors.js +35 -0
- package/dist/seo/feed.d.ts +49 -0
- package/dist/seo/feed.js +61 -0
- package/dist/seo/index.d.ts +13 -0
- package/dist/seo/index.js +13 -0
- package/dist/seo/media.d.ts +57 -0
- package/dist/seo/media.js +134 -0
- package/dist/seo/page-head.d.ts +18 -0
- package/dist/seo/page-head.js +114 -0
- package/dist/seo/ports.d.ts +221 -0
- package/dist/seo/ports.js +27 -0
- package/dist/seo/seo.d.ts +73 -0
- package/dist/seo/seo.js +230 -0
- package/dist/seo/sitemap.d.ts +139 -0
- package/dist/seo/sitemap.js +217 -0
- package/dist/seo/types.d.ts +278 -0
- package/dist/seo/types.js +2 -0
- package/dist/seo/write-service.d.ts +79 -0
- package/dist/seo/write-service.js +273 -0
- package/dist/seo/xml-escape.d.ts +16 -0
- package/dist/seo/xml-escape.js +19 -0
- package/dist/settings/express/cms-adapter.d.ts +20 -0
- package/dist/settings/express/cms-adapter.js +124 -0
- package/dist/settings/express/contracts.d.ts +135 -0
- package/dist/settings/express/contracts.js +2 -0
- package/dist/settings/express/http.d.ts +10 -0
- package/dist/settings/express/http.js +211 -0
- package/dist/settings/express/index.d.ts +5 -0
- package/dist/settings/express/index.js +5 -0
- package/dist/settings/express/shared.d.ts +68 -0
- package/dist/settings/express/shared.js +54 -0
- package/dist/settings/express/stream.d.ts +24 -0
- package/dist/settings/express/stream.js +147 -0
- package/dist/settings/index.d.ts +3 -38
- package/dist/settings/index.js +3 -35
- package/dist/taxonomy/write-service.d.ts +2 -30
- package/dist/widgets/authorize-helper.d.ts +42 -0
- package/dist/widgets/authorize-helper.js +16 -0
- package/dist/widgets/concurrency.d.ts +12 -0
- package/dist/widgets/concurrency.js +44 -0
- package/dist/widgets/config-validation.d.ts +34 -0
- package/dist/widgets/config-validation.js +125 -0
- package/dist/widgets/embed-service.d.ts +133 -0
- package/dist/widgets/embed-service.js +460 -0
- package/dist/widgets/embed-validation.d.ts +23 -0
- package/dist/widgets/embed-validation.js +38 -0
- package/dist/widgets/entry-payload.d.ts +103 -0
- package/dist/widgets/entry-payload.js +160 -0
- package/dist/widgets/errors.d.ts +136 -0
- package/dist/widgets/errors.js +136 -0
- package/dist/widgets/host-ports.d.ts +149 -0
- package/dist/widgets/host-ports.js +2 -0
- package/dist/widgets/html/html-embeds.d.ts +247 -0
- package/dist/widgets/html/html-embeds.js +408 -0
- package/dist/widgets/html/index.d.ts +5 -0
- package/dist/widgets/html/index.js +4 -0
- package/dist/widgets/html/legacy-widget-markers.d.ts +8 -0
- package/dist/widgets/html/legacy-widget-markers.js +45 -0
- package/dist/widgets/html/resolve-html-page-embeds.d.ts +42 -0
- package/dist/widgets/html/resolve-html-page-embeds.js +57 -0
- package/dist/widgets/index.d.ts +20 -0
- package/dist/widgets/index.js +19 -0
- package/dist/widgets/markers/html-attributes.d.ts +98 -0
- package/dist/widgets/markers/html-attributes.js +216 -0
- package/dist/widgets/markers/index.d.ts +3 -0
- package/dist/widgets/markers/index.js +3 -0
- package/dist/widgets/markers/marker.d.ts +496 -0
- package/dist/widgets/markers/marker.js +780 -0
- package/dist/widgets/page-embed-types.d.ts +42 -0
- package/dist/widgets/page-embed-types.js +42 -0
- package/dist/widgets/ports.d.ts +142 -0
- package/dist/widgets/ports.js +2 -0
- package/dist/widgets/read-service.d.ts +81 -0
- package/dist/widgets/read-service.js +79 -0
- package/dist/widgets/region-area-service.d.ts +110 -0
- package/dist/widgets/region-area-service.js +180 -0
- package/dist/widgets/registry.d.ts +217 -0
- package/dist/widgets/registry.js +172 -0
- package/dist/widgets/repo.memory.d.ts +41 -0
- package/dist/widgets/repo.memory.js +48 -0
- package/dist/widgets/resolver-service.d.ts +209 -0
- package/dist/widgets/resolver-service.js +909 -0
- package/dist/widgets/resolvers/contact-form.d.ts +26 -0
- package/dist/widgets/resolvers/contact-form.js +54 -0
- package/dist/widgets/resolvers/create-core-resolvers.d.ts +34 -0
- package/dist/widgets/resolvers/create-core-resolvers.js +11 -0
- package/dist/widgets/resolvers/index.d.ts +65 -0
- package/dist/widgets/resolvers/index.js +155 -0
- package/dist/widgets/resolvers/menu.d.ts +42 -0
- package/dist/widgets/resolvers/menu.js +43 -0
- package/dist/widgets/resolvers/ports.d.ts +82 -0
- package/dist/widgets/resolvers/ports.js +2 -0
- package/dist/widgets/resolvers/recent-entries.d.ts +71 -0
- package/dist/widgets/resolvers/recent-entries.js +165 -0
- package/dist/widgets/resolvers/taxonomy.d.ts +12 -0
- package/dist/widgets/resolvers/taxonomy.js +34 -0
- package/dist/widgets/sql/index.d.ts +3 -0
- package/dist/widgets/sql/index.js +3 -0
- package/dist/widgets/sql/repo.d.ts +41 -0
- package/dist/widgets/sql/repo.js +82 -0
- package/dist/widgets/sql/tables.d.ts +13 -0
- package/dist/widgets/sql/tables.js +2 -0
- package/dist/widgets/types.d.ts +213 -0
- package/dist/widgets/types.js +17 -0
- package/dist/widgets/where-used.d.ts +41 -0
- package/dist/widgets/where-used.js +11 -0
- package/dist/widgets/write-service.d.ts +204 -0
- package/dist/widgets/write-service.js +332 -0
- package/dist/workspace/delete.d.ts +1 -1
- package/package.json +220 -11
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Widget-instance CRUD through the existing entries chokepoint (SPEC-043 REQ-01..06, REQ-42/43).
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* Same chokepoint discipline as `posts`/`menus`/`forms` — no parallel mutation path. A widget
|
|
6
|
+
* instance is a `type='widget'` entries row (ADR-022 §1); every write here composes
|
|
7
|
+
* `features/entries/write-service.ts`'s real `createEntry`/`updateEntry` chokepoint (never
|
|
8
|
+
* reimplemented) so revisions and slug-uniqueness come for free.
|
|
9
|
+
*
|
|
10
|
+
* Deleting (2026-09-21, generic Trash): `trashWidgetInstance` moves a widget to the Trash through
|
|
11
|
+
* the injected `remove` and is UNCONDITIONAL (EC-07 — a trashed target degrades to the REQ-28
|
|
12
|
+
* placeholder). The old second rung, `purgeWidgetInstance` (a `purged` payload status that never
|
|
13
|
+
* removed the row, REQ-42/43), is retired: "delete permanently" is now the Trash's purge, and
|
|
14
|
+
* `adoptLegacyTrashedWidgets` moves the widgets it left behind into the Trash, and
|
|
15
|
+
* `restoreWidgetPriorStatus` makes one of them active again when it is restored.
|
|
16
|
+
*
|
|
17
|
+
* `entry_refs` extraction (INV-06) runs inside the same DB transaction as the triggering
|
|
18
|
+
* `createEntry`/`updateEntry` call, via that chokepoint's optional `deps.onWritten` hook (added
|
|
19
|
+
* 2026-07-21 — a narrow, additive extension to `features/entries/write-service.ts`, invoked after
|
|
20
|
+
* `save`/`appendRevision` but before commit, so a failed extraction rolls back the whole write; see
|
|
21
|
+
* the implementation report for why this was previously sequenced after instead).
|
|
22
|
+
*
|
|
23
|
+
* Architectural role:
|
|
24
|
+
* `widgets` domain logic (implementation outline C-005).
|
|
25
|
+
*/
|
|
26
|
+
import type { Clock as ClockPort, UUID } from "@jini-ai/core/primitives";
|
|
27
|
+
import type { OutboxPort } from "../core/index.js";
|
|
28
|
+
import type { EntryRefsRepoPort } from "./ports.js";
|
|
29
|
+
import type { ContentTypeRepoPort } from "../content-types/index.js";
|
|
30
|
+
import type { EntryListPort, EntryRepoPort } from "../entries/index.js";
|
|
31
|
+
import { type WidgetsAuthorizeFn } from "./authorize-helper.js";
|
|
32
|
+
import type { RemoveWidgetFn } from "./ports.js";
|
|
33
|
+
import type { WidgetInstanceEntry, WidgetTypeKey } from "./types.js";
|
|
34
|
+
import type { WidgetHostPorts } from "./host-ports.js";
|
|
35
|
+
export interface WidgetWriteServiceDeps {
|
|
36
|
+
host: Pick<WidgetHostPorts, "entries" | "contentTypes" | "entryRefs" | "slugify">;
|
|
37
|
+
entryRepo: EntryRepoPort & EntryListPort;
|
|
38
|
+
contentTypeRepo: ContentTypeRepoPort;
|
|
39
|
+
entryRefsRepo: EntryRefsRepoPort;
|
|
40
|
+
clock: ClockPort;
|
|
41
|
+
ids: {
|
|
42
|
+
newId: () => string;
|
|
43
|
+
};
|
|
44
|
+
authorize: WidgetsAuthorizeFn;
|
|
45
|
+
outbox: OutboxPort;
|
|
46
|
+
}
|
|
47
|
+
export interface CreateWidgetInstanceInput {
|
|
48
|
+
readonly workspaceId: UUID;
|
|
49
|
+
readonly actor: {
|
|
50
|
+
readonly principalId: UUID;
|
|
51
|
+
};
|
|
52
|
+
readonly widgetType: WidgetTypeKey;
|
|
53
|
+
readonly title: string;
|
|
54
|
+
readonly config: Record<string, unknown>;
|
|
55
|
+
readonly slug?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface CreateWidgetInstanceRequired {
|
|
58
|
+
deps: WidgetWriteServiceDeps;
|
|
59
|
+
input: CreateWidgetInstanceInput;
|
|
60
|
+
}
|
|
61
|
+
/** REQ-01/02/03: authorize → validate config against the type's registered schema → create via the chokepoint. */
|
|
62
|
+
export declare function createWidgetInstance(required: CreateWidgetInstanceRequired, _optional?: Record<string, never>): Promise<{
|
|
63
|
+
instance: WidgetInstanceEntry;
|
|
64
|
+
}>;
|
|
65
|
+
export interface UpdateWidgetInstanceInput {
|
|
66
|
+
readonly workspaceId: UUID;
|
|
67
|
+
readonly actor: {
|
|
68
|
+
readonly principalId: UUID;
|
|
69
|
+
};
|
|
70
|
+
readonly widgetInstanceId: UUID;
|
|
71
|
+
readonly baseVersion: number;
|
|
72
|
+
readonly config: Record<string, unknown>;
|
|
73
|
+
/** Optional (SPEC-043 ui.spec §4.3): omitted keeps the current title, forwarded to
|
|
74
|
+
* `updateEntry`'s own `title` field (`input.title ?? current.title`) the same way create's
|
|
75
|
+
* `title` reaches the entry row — never folded into `fieldsJson`, which only carries
|
|
76
|
+
* widget-domain payload (`widgetType`/`config`/`status`). */
|
|
77
|
+
readonly title?: string;
|
|
78
|
+
}
|
|
79
|
+
export interface UpdateWidgetInstanceRequired {
|
|
80
|
+
deps: WidgetWriteServiceDeps;
|
|
81
|
+
input: UpdateWidgetInstanceInput;
|
|
82
|
+
}
|
|
83
|
+
/** REQ-05/06: update via the chokepoint, rejecting a stale `baseVersion` with a typed conflict. */
|
|
84
|
+
export declare function updateWidgetInstance(required: UpdateWidgetInstanceRequired, _optional?: Record<string, never>): Promise<{
|
|
85
|
+
instance: WidgetInstanceEntry;
|
|
86
|
+
}>;
|
|
87
|
+
export interface ImportWidgetInstanceInput {
|
|
88
|
+
readonly workspaceId: UUID;
|
|
89
|
+
readonly actor: {
|
|
90
|
+
readonly principalId: UUID;
|
|
91
|
+
};
|
|
92
|
+
/** The source's own id, kept so a publish round trip lands on the same row (and placements that
|
|
93
|
+
* name it still resolve). */
|
|
94
|
+
readonly id: UUID;
|
|
95
|
+
readonly slug: string;
|
|
96
|
+
readonly title: string;
|
|
97
|
+
readonly widgetType: WidgetTypeKey;
|
|
98
|
+
readonly config: Record<string, unknown>;
|
|
99
|
+
/** `undefined` = the row must not exist yet; a number = it must exist at exactly this version. */
|
|
100
|
+
readonly expectedVersion: number | undefined;
|
|
101
|
+
}
|
|
102
|
+
export interface ImportWidgetInstanceRequired {
|
|
103
|
+
deps: WidgetWriteServiceDeps;
|
|
104
|
+
input: ImportWidgetInstanceInput;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Publish-content's widget write: create-or-update at a caller-given id, with the same permission,
|
|
108
|
+
* type-registration and config checks as {@link createWidgetInstance}/{@link updateWidgetInstance},
|
|
109
|
+
* through Jini's `importEntry` (the id-keeping chokepoint). Always writes an `active` instance.
|
|
110
|
+
*/
|
|
111
|
+
export declare function importWidgetInstance(required: ImportWidgetInstanceRequired, _optional?: Record<string, never>): Promise<{
|
|
112
|
+
instance: WidgetInstanceEntry;
|
|
113
|
+
}>;
|
|
114
|
+
export interface TrashWidgetInstanceInput {
|
|
115
|
+
readonly workspaceId: UUID;
|
|
116
|
+
readonly actor: {
|
|
117
|
+
readonly principalId: UUID;
|
|
118
|
+
/** The assistant's AI marker on the Trash row's `actor.pluginId` (2026-09-21, trash T4c) —
|
|
119
|
+
* unset by every non-assistant caller, so a human's own trash from the admin screen stays
|
|
120
|
+
* human-only. See {@link RemoveWidgetFn}'s `actor.pluginId`. */
|
|
121
|
+
readonly pluginId?: UUID | null;
|
|
122
|
+
};
|
|
123
|
+
readonly widgetInstanceId: UUID;
|
|
124
|
+
}
|
|
125
|
+
/** {@link WidgetWriteServiceDeps} plus the Trash, injected: `remove` (bound to `"widget"` at the
|
|
126
|
+
* composition root). */
|
|
127
|
+
export interface WidgetTrashDeps extends WidgetWriteServiceDeps {
|
|
128
|
+
remove: RemoveWidgetFn;
|
|
129
|
+
}
|
|
130
|
+
export interface TrashWidgetInstanceRequired {
|
|
131
|
+
deps: Pick<WidgetTrashDeps, "entryRepo" | "clock" | "authorize" | "remove">;
|
|
132
|
+
input: TrashWidgetInstanceInput;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Deleting a widget = moving it to the Trash (the injected `remove`). UNCONDITIONAL, per ADR-047 §7
|
|
136
|
+
* and feature.spec.md EC-07: a widget still placed somewhere can be trashed, and every placement
|
|
137
|
+
* then renders the REQ-28 placeholder until it is restored. Only a purge from the Trash removes it
|
|
138
|
+
* for good.
|
|
139
|
+
*
|
|
140
|
+
* Never parses the payload, so a widget whose `fields_json` is corrupt can still be deleted, and
|
|
141
|
+
* never touches its outgoing `entry_refs` (a restore needs them).
|
|
142
|
+
*
|
|
143
|
+
* @throws WidgetInstanceNotFoundError when the widget is missing, already trashed, or not a widget.
|
|
144
|
+
* @throws WidgetVersionConflictError when the row changed between the read and the move.
|
|
145
|
+
* @complexity O(1): one read, one `remove` call (and one more read only on a version conflict).
|
|
146
|
+
*/
|
|
147
|
+
export declare function trashWidgetInstance(required: TrashWidgetInstanceRequired, _optional?: Record<string, never>): Promise<{
|
|
148
|
+
widgetInstanceId: UUID;
|
|
149
|
+
version: number | null;
|
|
150
|
+
}>;
|
|
151
|
+
export interface AdoptLegacyTrashedWidgetsRequired {
|
|
152
|
+
deps: Pick<WidgetTrashDeps, "entryRepo" | "clock" | "remove">;
|
|
153
|
+
input: {
|
|
154
|
+
readonly workspaceId: UUID;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* One-time, idempotent boot step (owner decision 6): every widget the old delete ladder left with
|
|
159
|
+
* payload status `trash` or `purged` goes into the Trash, so it gets the same 60 days as anything
|
|
160
|
+
* else deleted today.
|
|
161
|
+
*
|
|
162
|
+
* The payload is NOT touched: while the widget is in the Trash it keeps saying `trash`/`purged`,
|
|
163
|
+
* because an older site build that knows nothing about `entries.deleted_at` may still read it and
|
|
164
|
+
* would otherwise show the widget as live. The Trash row records `"active"` as its prior marker;
|
|
165
|
+
* restoring it runs {@link restoreWidgetPriorStatus}, which applies that status to the payload.
|
|
166
|
+
*
|
|
167
|
+
* A second run finds nothing: an adopted widget is in the Trash, so the entries reads no longer
|
|
168
|
+
* return it. A widget whose payload does not parse is skipped; nothing about it says it was deleted.
|
|
169
|
+
*
|
|
170
|
+
* @returns the id and title of each widget it moved to the Trash (the boot step logs them).
|
|
171
|
+
* @throws when the Trash refuses a widget (it changed under the adoption) — boot logs it and the
|
|
172
|
+
* next boot retries the rest.
|
|
173
|
+
* @complexity O(w) over the workspace's live widgets: one list, then one `remove` per adopted widget.
|
|
174
|
+
*/
|
|
175
|
+
export declare function adoptLegacyTrashedWidgets(required: AdoptLegacyTrashedWidgetsRequired, _optional?: Record<string, never>): Promise<{
|
|
176
|
+
adopted: Array<{
|
|
177
|
+
id: UUID;
|
|
178
|
+
title: string;
|
|
179
|
+
}>;
|
|
180
|
+
}>;
|
|
181
|
+
export interface RestoreWidgetPriorStatusRequired {
|
|
182
|
+
deps: WidgetWriteServiceDeps;
|
|
183
|
+
input: {
|
|
184
|
+
readonly workspaceId: UUID;
|
|
185
|
+
readonly widgetInstanceId: UUID;
|
|
186
|
+
readonly priorStatus: string;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Runs as a widget comes back from the Trash with a recorded prior status — only an adopted legacy
|
|
191
|
+
* widget has one (see {@link adoptLegacyTrashedWidgets}). Sets the payload's `trash`/`purged` status
|
|
192
|
+
* back to `active` and re-derives the widget's outgoing refs (the old purge had retracted them), in
|
|
193
|
+
* the restore's own transaction.
|
|
194
|
+
*
|
|
195
|
+
* Leaves the widget alone when there is nothing to fix: a prior status other than `active`, a
|
|
196
|
+
* payload that does not parse (a restore must still bring a corrupt widget back byte-identical), or
|
|
197
|
+
* a payload already live.
|
|
198
|
+
*
|
|
199
|
+
* @throws WidgetInstanceNotFoundError when the widget does not read back (the restore just cleared
|
|
200
|
+
* its marker, so this means the row is not a widget) — the restore rolls back with it.
|
|
201
|
+
* @complexity O(1): one read and at most one `updateEntry`.
|
|
202
|
+
*/
|
|
203
|
+
export declare function restoreWidgetPriorStatus(required: RestoreWidgetPriorStatusRequired, _optional?: Record<string, never>): Promise<void>;
|
|
204
|
+
//# sourceMappingURL=write-service.d.ts.map
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { nowIso as clockNowIso } from "@jini-ai/core/primitives";
|
|
2
|
+
import { PRE_AUTHORIZED, requireWidgetPermission, } from "./authorize-helper.js";
|
|
3
|
+
import { withEntryLock } from "./concurrency.js";
|
|
4
|
+
import { validateWidgetConfig } from "./config-validation.js";
|
|
5
|
+
import { buildWidgetInstanceFieldsJson, ensureWidgetContentTypesRegistered, parseWidgetInstancePayload, toWidgetInstanceEntry, } from "./entry-payload.js";
|
|
6
|
+
import { WidgetConfigValidationError, WidgetInstanceNotFoundError, WidgetTypeUnregisteredError, WidgetVersionConflictError, } from "./errors.js";
|
|
7
|
+
import { findWidgetTypeRegistration } from "./registry.js";
|
|
8
|
+
import { WIDGET_CONTENT_TYPE, WIDGET_FIELD_NAMESPACE } from "./types.js";
|
|
9
|
+
function slugify(title, host) {
|
|
10
|
+
const base = host.slugify(title);
|
|
11
|
+
return base.length > 0 ? base : "widget";
|
|
12
|
+
}
|
|
13
|
+
async function extractAndStoreInstanceRefs(deps, workspaceId, entry) {
|
|
14
|
+
const payload = parseWidgetInstancePayload({ fieldsJson: entry.fieldsJson });
|
|
15
|
+
const refs = deps.host.entryRefs.extract({
|
|
16
|
+
workspaceId,
|
|
17
|
+
sourceEntryId: entry.id,
|
|
18
|
+
sourceEntryType: WIDGET_CONTENT_TYPE,
|
|
19
|
+
bodyJson: entry.bodyJson,
|
|
20
|
+
fieldsExt: { [WIDGET_FIELD_NAMESPACE]: { widgetType: payload.widgetType, config: payload.config } },
|
|
21
|
+
});
|
|
22
|
+
await deps.entryRefsRepo.replaceForSource({ workspaceId, sourceEntryId: entry.id, refs });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The one shared shape every `createEntry`/`updateEntry` call in this file needs — mirrors
|
|
26
|
+
* `region-area-service.ts`'s identical `entriesWriteDeps` helper. Bridges `deps.outbox` (the raw,
|
|
27
|
+
* full-`DomainEvent` infra port — see `WidgetWriteServiceDeps.outbox`) through `toEntryOutbox` into
|
|
28
|
+
* the narrower `{enqueue({name,payload})}` shape `createEntry`/`updateEntry` declare locally.
|
|
29
|
+
* Previously each of the four call sites below passed `deps.outbox` straight through unwrapped —
|
|
30
|
+
* compiled fine (`features/entries/write-service.ts`'s own narrow `OutboxPort` accepted it via a
|
|
31
|
+
* structural/bivariance loophole), but threw `NOT NULL constraint failed: outbox_events.id` against
|
|
32
|
+
* the real SQLite outbox in production on every widget create/update/trash/purge, invisible against
|
|
33
|
+
* the in-memory test double (which accepts any shape). One composer means the bridge can't be
|
|
34
|
+
* missed at a fifth call site later — see `ADS-memory/.local-artifacts/agent-reports/
|
|
35
|
+
* 20260803-widget-delete-outbox-bug.md` and `20260803-jini-outbox-contract.md` for the full
|
|
36
|
+
* investigation. `onWritten` is deliberately NOT included — it differs per call site (or is absent
|
|
37
|
+
* entirely, as in `trashWidgetInstance`), so each caller still supplies its own.
|
|
38
|
+
*/
|
|
39
|
+
function entriesWriteDeps(deps, workspaceId) {
|
|
40
|
+
return {
|
|
41
|
+
entryRepo: deps.entryRepo,
|
|
42
|
+
contentTypeRepo: deps.contentTypeRepo,
|
|
43
|
+
clock: deps.clock,
|
|
44
|
+
ids: deps.ids,
|
|
45
|
+
authorize: PRE_AUTHORIZED,
|
|
46
|
+
outbox: deps.host.entries.toEntryOutbox({ outbox: deps.outbox, clock: deps.clock, idGen: deps.ids, workspaceId }),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** REQ-01/02/03: authorize → validate config against the type's registered schema → create via the chokepoint. */
|
|
50
|
+
export async function createWidgetInstance(required, _optional = {}) {
|
|
51
|
+
const { deps, input } = required;
|
|
52
|
+
await requireWidgetPermission({
|
|
53
|
+
authorize: deps.authorize,
|
|
54
|
+
actor: input.actor,
|
|
55
|
+
workspaceId: input.workspaceId,
|
|
56
|
+
permission: "widgets.create",
|
|
57
|
+
});
|
|
58
|
+
// `input.widgetType` is typed `WidgetTypeKey` but only ASSERTED to be one — HTTP routes
|
|
59
|
+
// (`server/routes/admin/widgets/create.ts`/`agent-tools.ts`) cast an unvalidated request-body
|
|
60
|
+
// string to it. `findWidgetTypeRegistration` is the correct accessor here: `undefined` is
|
|
61
|
+
// genuinely reachable, and this check is what catches an unregistered type at this boundary.
|
|
62
|
+
const registration = findWidgetTypeRegistration({ raw: input.widgetType });
|
|
63
|
+
if (!registration) {
|
|
64
|
+
throw new WidgetTypeUnregisteredError({ message: `widget type '${input.widgetType}' is not registered (REQ-03)`, widgetType: input.widgetType });
|
|
65
|
+
}
|
|
66
|
+
const validation = validateWidgetConfig({ schema: registration.configSchema, config: input.config });
|
|
67
|
+
if (!validation.valid) {
|
|
68
|
+
throw new WidgetConfigValidationError({ message: `config for widget type '${input.widgetType}' failed schema validation (REQ-02)`, fieldErrors: validation.fieldErrors });
|
|
69
|
+
}
|
|
70
|
+
await ensureWidgetContentTypesRegistered({ deps, workspaceId: input.workspaceId });
|
|
71
|
+
const slug = input.slug ?? `${slugify(input.title, deps.host)}-${deps.ids.newId().slice(0, 8)}`;
|
|
72
|
+
const created = await deps.host.entries.createEntry({
|
|
73
|
+
deps: {
|
|
74
|
+
...entriesWriteDeps(deps, input.workspaceId),
|
|
75
|
+
onWritten: (entry) => extractAndStoreInstanceRefs(deps, input.workspaceId, entry),
|
|
76
|
+
},
|
|
77
|
+
input: {
|
|
78
|
+
actorId: input.actor.principalId,
|
|
79
|
+
workspaceId: input.workspaceId,
|
|
80
|
+
type: WIDGET_CONTENT_TYPE,
|
|
81
|
+
slug,
|
|
82
|
+
title: input.title,
|
|
83
|
+
fieldsJson: buildWidgetInstanceFieldsJson({ payload: { widgetType: input.widgetType, config: input.config, status: "active" } }),
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
if (!created.ok)
|
|
87
|
+
throw created.error;
|
|
88
|
+
return { instance: toWidgetInstanceEntry({ entry: created.value.entry }) };
|
|
89
|
+
}
|
|
90
|
+
/** REQ-05/06: update via the chokepoint, rejecting a stale `baseVersion` with a typed conflict. */
|
|
91
|
+
export async function updateWidgetInstance(required, _optional = {}) {
|
|
92
|
+
const { deps, input } = required;
|
|
93
|
+
await requireWidgetPermission({
|
|
94
|
+
authorize: deps.authorize,
|
|
95
|
+
actor: input.actor,
|
|
96
|
+
workspaceId: input.workspaceId,
|
|
97
|
+
permission: "widgets.update",
|
|
98
|
+
});
|
|
99
|
+
return withEntryLock({ key: `${input.workspaceId}::${input.widgetInstanceId}`, fn: async () => {
|
|
100
|
+
const current = await deps.entryRepo.findById({ workspaceId: input.workspaceId, id: input.widgetInstanceId });
|
|
101
|
+
if (!current || current.type !== WIDGET_CONTENT_TYPE) {
|
|
102
|
+
throw new WidgetInstanceNotFoundError({ message: `widget instance '${input.widgetInstanceId}' was not found` });
|
|
103
|
+
}
|
|
104
|
+
const currentPayload = parseWidgetInstancePayload({ fieldsJson: current.fieldsJson });
|
|
105
|
+
// `currentPayload.widgetType` is typed `WidgetTypeKey` but only ASSERTED to be one —
|
|
106
|
+
// `parseWidgetInstancePayload` casts decoded, stored JSON (`entry-payload.ts`), so
|
|
107
|
+
// `findWidgetTypeRegistration` is the correct, honestly-partial accessor here too.
|
|
108
|
+
const registration = findWidgetTypeRegistration({ raw: currentPayload.widgetType });
|
|
109
|
+
if (!registration) {
|
|
110
|
+
throw new WidgetTypeUnregisteredError({ message: `widget type '${currentPayload.widgetType}' is not registered`, widgetType: currentPayload.widgetType });
|
|
111
|
+
}
|
|
112
|
+
const validation = validateWidgetConfig({ schema: registration.configSchema, config: input.config });
|
|
113
|
+
if (!validation.valid) {
|
|
114
|
+
throw new WidgetConfigValidationError({ message: `config for widget type '${currentPayload.widgetType}' failed schema validation (REQ-02)`, fieldErrors: validation.fieldErrors });
|
|
115
|
+
}
|
|
116
|
+
const result = await deps.host.entries.updateEntry({
|
|
117
|
+
deps: {
|
|
118
|
+
...entriesWriteDeps(deps, input.workspaceId),
|
|
119
|
+
onWritten: (entry) => extractAndStoreInstanceRefs(deps, input.workspaceId, entry),
|
|
120
|
+
},
|
|
121
|
+
input: {
|
|
122
|
+
actorId: input.actor.principalId,
|
|
123
|
+
workspaceId: input.workspaceId,
|
|
124
|
+
id: input.widgetInstanceId,
|
|
125
|
+
title: input.title,
|
|
126
|
+
fieldsJson: buildWidgetInstanceFieldsJson({ payload: { widgetType: currentPayload.widgetType, config: input.config, status: currentPayload.status } }),
|
|
127
|
+
expectedVersion: input.baseVersion,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
if (!result.ok) {
|
|
131
|
+
if (deps.host.entries.isVersionConflict(result.error)) {
|
|
132
|
+
const latest = await deps.entryRepo.findById({ workspaceId: input.workspaceId, id: input.widgetInstanceId });
|
|
133
|
+
throw new WidgetVersionConflictError({ message: result.error.message, currentVersion: latest?.version ?? current.version });
|
|
134
|
+
}
|
|
135
|
+
throw result.error;
|
|
136
|
+
}
|
|
137
|
+
return { instance: toWidgetInstanceEntry({ entry: result.value.entry }) };
|
|
138
|
+
} });
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Publish-content's widget write: create-or-update at a caller-given id, with the same permission,
|
|
142
|
+
* type-registration and config checks as {@link createWidgetInstance}/{@link updateWidgetInstance},
|
|
143
|
+
* through Jini's `importEntry` (the id-keeping chokepoint). Always writes an `active` instance.
|
|
144
|
+
*/
|
|
145
|
+
export async function importWidgetInstance(required, _optional = {}) {
|
|
146
|
+
const { deps, input } = required;
|
|
147
|
+
await requireWidgetPermission({
|
|
148
|
+
authorize: deps.authorize,
|
|
149
|
+
actor: input.actor,
|
|
150
|
+
workspaceId: input.workspaceId,
|
|
151
|
+
permission: input.expectedVersion === undefined ? "widgets.create" : "widgets.update",
|
|
152
|
+
});
|
|
153
|
+
const registration = findWidgetTypeRegistration({ raw: input.widgetType });
|
|
154
|
+
if (!registration) {
|
|
155
|
+
throw new WidgetTypeUnregisteredError({ message: `widget type '${input.widgetType}' is not registered (REQ-03)`, widgetType: input.widgetType });
|
|
156
|
+
}
|
|
157
|
+
const validation = validateWidgetConfig({ schema: registration.configSchema, config: input.config });
|
|
158
|
+
if (!validation.valid) {
|
|
159
|
+
throw new WidgetConfigValidationError({ message: `config for widget type '${input.widgetType}' failed schema validation (REQ-02)`, fieldErrors: validation.fieldErrors });
|
|
160
|
+
}
|
|
161
|
+
await ensureWidgetContentTypesRegistered({ deps, workspaceId: input.workspaceId });
|
|
162
|
+
return withEntryLock({ key: `${input.workspaceId}::${input.id}`, fn: async () => {
|
|
163
|
+
const current = await deps.entryRepo.findById({ workspaceId: input.workspaceId, id: input.id });
|
|
164
|
+
const result = await deps.host.entries.importEntry({
|
|
165
|
+
deps: {
|
|
166
|
+
...entriesWriteDeps(deps, input.workspaceId),
|
|
167
|
+
onWritten: (entry) => extractAndStoreInstanceRefs(deps, input.workspaceId, entry),
|
|
168
|
+
},
|
|
169
|
+
input: {
|
|
170
|
+
actorId: input.actor.principalId,
|
|
171
|
+
workspaceId: input.workspaceId,
|
|
172
|
+
id: input.id,
|
|
173
|
+
type: WIDGET_CONTENT_TYPE,
|
|
174
|
+
slug: input.slug,
|
|
175
|
+
title: input.title,
|
|
176
|
+
status: current?.status ?? "draft",
|
|
177
|
+
fieldsJson: buildWidgetInstanceFieldsJson({ payload: { widgetType: input.widgetType, config: input.config, status: "active" } }),
|
|
178
|
+
publishedAt: current?.publishedAt ?? null,
|
|
179
|
+
expectedVersion: input.expectedVersion,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
if (!result.ok)
|
|
183
|
+
throw result.error;
|
|
184
|
+
return { instance: toWidgetInstanceEntry({ entry: result.value.entry }) };
|
|
185
|
+
} });
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Deleting a widget = moving it to the Trash (the injected `remove`). UNCONDITIONAL, per ADR-047 §7
|
|
189
|
+
* and feature.spec.md EC-07: a widget still placed somewhere can be trashed, and every placement
|
|
190
|
+
* then renders the REQ-28 placeholder until it is restored. Only a purge from the Trash removes it
|
|
191
|
+
* for good.
|
|
192
|
+
*
|
|
193
|
+
* Never parses the payload, so a widget whose `fields_json` is corrupt can still be deleted, and
|
|
194
|
+
* never touches its outgoing `entry_refs` (a restore needs them).
|
|
195
|
+
*
|
|
196
|
+
* @throws WidgetInstanceNotFoundError when the widget is missing, already trashed, or not a widget.
|
|
197
|
+
* @throws WidgetVersionConflictError when the row changed between the read and the move.
|
|
198
|
+
* @complexity O(1): one read, one `remove` call (and one more read only on a version conflict).
|
|
199
|
+
*/
|
|
200
|
+
export async function trashWidgetInstance(required, _optional = {}) {
|
|
201
|
+
const { deps, input } = required;
|
|
202
|
+
await requireWidgetPermission({
|
|
203
|
+
authorize: deps.authorize,
|
|
204
|
+
actor: input.actor,
|
|
205
|
+
workspaceId: input.workspaceId,
|
|
206
|
+
permission: "widgets.delete",
|
|
207
|
+
});
|
|
208
|
+
return withEntryLock({ key: `${input.workspaceId}::${input.widgetInstanceId}`, fn: async () => {
|
|
209
|
+
const notFound = () => new WidgetInstanceNotFoundError({ message: `widget instance '${input.widgetInstanceId}' was not found` });
|
|
210
|
+
const current = await deps.entryRepo.findById({ workspaceId: input.workspaceId, id: input.widgetInstanceId });
|
|
211
|
+
if (!current || current.type !== WIDGET_CONTENT_TYPE)
|
|
212
|
+
throw notFound();
|
|
213
|
+
const removed = await deps.remove({
|
|
214
|
+
workspaceId: input.workspaceId,
|
|
215
|
+
id: current.id,
|
|
216
|
+
display: { title: current.title, subtitle: current.slug },
|
|
217
|
+
at: clockNowIso({ clock: deps.clock }),
|
|
218
|
+
expectedVersion: current.version,
|
|
219
|
+
actor: { principalId: input.actor.principalId, pluginId: input.actor.pluginId ?? null },
|
|
220
|
+
});
|
|
221
|
+
if (removed.ok)
|
|
222
|
+
return { widgetInstanceId: current.id, version: removed.version };
|
|
223
|
+
if (removed.reason === "not-found")
|
|
224
|
+
throw notFound();
|
|
225
|
+
const fresh = await deps.entryRepo.findById({ workspaceId: input.workspaceId, id: input.widgetInstanceId });
|
|
226
|
+
if (!fresh)
|
|
227
|
+
throw notFound();
|
|
228
|
+
throw new WidgetVersionConflictError({ message: `widget instance '${input.widgetInstanceId}' changed while it was being deleted — reload and try again`, currentVersion: fresh.version });
|
|
229
|
+
} });
|
|
230
|
+
}
|
|
231
|
+
/** The payload statuses the old delete ladder left behind: `trash` (step 1) and `purged` (its
|
|
232
|
+
* "delete permanently", which never removed the row). */
|
|
233
|
+
const LEGACY_DELETED_STATUSES = new Set(["trash", "purged"]);
|
|
234
|
+
/** Who the Trash records as having moved an adopted widget there. */
|
|
235
|
+
const ADOPTION_ACTOR = { principalId: "system" };
|
|
236
|
+
/** The status a restore brings an adopted widget back to — recorded as its Trash row's prior marker. */
|
|
237
|
+
const ADOPTED_WIDGET_PRIOR_STATUS = "active";
|
|
238
|
+
/**
|
|
239
|
+
* One-time, idempotent boot step (owner decision 6): every widget the old delete ladder left with
|
|
240
|
+
* payload status `trash` or `purged` goes into the Trash, so it gets the same 60 days as anything
|
|
241
|
+
* else deleted today.
|
|
242
|
+
*
|
|
243
|
+
* The payload is NOT touched: while the widget is in the Trash it keeps saying `trash`/`purged`,
|
|
244
|
+
* because an older site build that knows nothing about `entries.deleted_at` may still read it and
|
|
245
|
+
* would otherwise show the widget as live. The Trash row records `"active"` as its prior marker;
|
|
246
|
+
* restoring it runs {@link restoreWidgetPriorStatus}, which applies that status to the payload.
|
|
247
|
+
*
|
|
248
|
+
* A second run finds nothing: an adopted widget is in the Trash, so the entries reads no longer
|
|
249
|
+
* return it. A widget whose payload does not parse is skipped; nothing about it says it was deleted.
|
|
250
|
+
*
|
|
251
|
+
* @returns the id and title of each widget it moved to the Trash (the boot step logs them).
|
|
252
|
+
* @throws when the Trash refuses a widget (it changed under the adoption) — boot logs it and the
|
|
253
|
+
* next boot retries the rest.
|
|
254
|
+
* @complexity O(w) over the workspace's live widgets: one list, then one `remove` per adopted widget.
|
|
255
|
+
*/
|
|
256
|
+
export async function adoptLegacyTrashedWidgets(required, _optional = {}) {
|
|
257
|
+
const { deps, input } = required;
|
|
258
|
+
const widgets = await deps.entryRepo.listByWorkspace({ workspaceId: input.workspaceId }, { type: WIDGET_CONTENT_TYPE });
|
|
259
|
+
const adopted = [];
|
|
260
|
+
for (const widget of widgets) {
|
|
261
|
+
let status;
|
|
262
|
+
try {
|
|
263
|
+
status = parseWidgetInstancePayload({ fieldsJson: widget.fieldsJson }).status;
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (!LEGACY_DELETED_STATUSES.has(status))
|
|
269
|
+
continue;
|
|
270
|
+
const removed = await deps.remove({
|
|
271
|
+
workspaceId: input.workspaceId,
|
|
272
|
+
id: widget.id,
|
|
273
|
+
display: { title: widget.title, subtitle: widget.slug },
|
|
274
|
+
at: clockNowIso({ clock: deps.clock }),
|
|
275
|
+
expectedVersion: widget.version,
|
|
276
|
+
actor: ADOPTION_ACTOR,
|
|
277
|
+
priorMarker: ADOPTED_WIDGET_PRIOR_STATUS,
|
|
278
|
+
});
|
|
279
|
+
if (!removed.ok)
|
|
280
|
+
throw new Error(`widget '${widget.id}' could not be moved to the Trash: ${removed.reason}`);
|
|
281
|
+
adopted.push({ id: widget.id, title: widget.title });
|
|
282
|
+
}
|
|
283
|
+
return { adopted };
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Runs as a widget comes back from the Trash with a recorded prior status — only an adopted legacy
|
|
287
|
+
* widget has one (see {@link adoptLegacyTrashedWidgets}). Sets the payload's `trash`/`purged` status
|
|
288
|
+
* back to `active` and re-derives the widget's outgoing refs (the old purge had retracted them), in
|
|
289
|
+
* the restore's own transaction.
|
|
290
|
+
*
|
|
291
|
+
* Leaves the widget alone when there is nothing to fix: a prior status other than `active`, a
|
|
292
|
+
* payload that does not parse (a restore must still bring a corrupt widget back byte-identical), or
|
|
293
|
+
* a payload already live.
|
|
294
|
+
*
|
|
295
|
+
* @throws WidgetInstanceNotFoundError when the widget does not read back (the restore just cleared
|
|
296
|
+
* its marker, so this means the row is not a widget) — the restore rolls back with it.
|
|
297
|
+
* @complexity O(1): one read and at most one `updateEntry`.
|
|
298
|
+
*/
|
|
299
|
+
export async function restoreWidgetPriorStatus(required, _optional = {}) {
|
|
300
|
+
const { deps, input } = required;
|
|
301
|
+
if (input.priorStatus !== ADOPTED_WIDGET_PRIOR_STATUS)
|
|
302
|
+
return;
|
|
303
|
+
const widget = await deps.entryRepo.findById({ workspaceId: input.workspaceId, id: input.widgetInstanceId });
|
|
304
|
+
if (!widget || widget.type !== WIDGET_CONTENT_TYPE) {
|
|
305
|
+
throw new WidgetInstanceNotFoundError({ message: `widget instance '${input.widgetInstanceId}' was not found` });
|
|
306
|
+
}
|
|
307
|
+
let payload;
|
|
308
|
+
try {
|
|
309
|
+
payload = parseWidgetInstancePayload({ fieldsJson: widget.fieldsJson });
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (!LEGACY_DELETED_STATUSES.has(payload.status))
|
|
315
|
+
return;
|
|
316
|
+
const updated = await deps.host.entries.updateEntry({
|
|
317
|
+
deps: {
|
|
318
|
+
...entriesWriteDeps(deps, input.workspaceId),
|
|
319
|
+
onWritten: (entry) => extractAndStoreInstanceRefs(deps, input.workspaceId, entry),
|
|
320
|
+
},
|
|
321
|
+
input: {
|
|
322
|
+
actorId: ADOPTION_ACTOR.principalId,
|
|
323
|
+
workspaceId: input.workspaceId,
|
|
324
|
+
id: widget.id,
|
|
325
|
+
fieldsJson: buildWidgetInstanceFieldsJson({ payload: { ...payload, status: ADOPTED_WIDGET_PRIOR_STATUS } }),
|
|
326
|
+
expectedVersion: widget.version,
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
if (!updated.ok)
|
|
330
|
+
throw updated.error;
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=write-service.js.map
|