@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.
Files changed (205) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +14 -8
  3. package/dist/comments/errors.d.ts +23 -0
  4. package/dist/comments/errors.js +25 -0
  5. package/dist/comments/express/index.d.ts +2 -0
  6. package/dist/comments/express/index.js +2 -0
  7. package/dist/comments/express/submit-handler.d.ts +30 -0
  8. package/dist/comments/express/submit-handler.js +85 -0
  9. package/dist/comments/hooks.d.ts +39 -0
  10. package/dist/comments/hooks.js +41 -0
  11. package/dist/comments/index.d.ts +11 -0
  12. package/dist/comments/index.js +11 -0
  13. package/dist/comments/ingress.d.ts +57 -0
  14. package/dist/comments/ingress.js +173 -0
  15. package/dist/comments/module.d.ts +94 -0
  16. package/dist/comments/module.js +73 -0
  17. package/dist/comments/ports.d.ts +184 -0
  18. package/dist/comments/ports.js +2 -0
  19. package/dist/comments/repo.memory.d.ts +70 -0
  20. package/dist/comments/repo.memory.js +120 -0
  21. package/dist/comments/sanitize.d.ts +15 -0
  22. package/dist/comments/sanitize.js +31 -0
  23. package/dist/comments/spam.heuristic.d.ts +8 -0
  24. package/dist/comments/spam.heuristic.js +54 -0
  25. package/dist/comments/sql/index.d.ts +3 -0
  26. package/dist/comments/sql/index.js +3 -0
  27. package/dist/comments/sql/repo.d.ts +77 -0
  28. package/dist/comments/sql/repo.js +190 -0
  29. package/dist/comments/sql/repo.rows.d.ts +49 -0
  30. package/dist/comments/sql/repo.rows.js +71 -0
  31. package/dist/comments/types.d.ts +187 -0
  32. package/dist/comments/types.js +39 -0
  33. package/dist/comments/write-service.d.ts +121 -0
  34. package/dist/comments/write-service.js +144 -0
  35. package/dist/core/commands/command.d.ts +2 -9
  36. package/dist/core/commands/command.js +2 -9
  37. package/dist/core/index.d.ts +2 -1
  38. package/dist/core/index.js +1 -0
  39. package/dist/core/keyset-cursor.d.ts +25 -0
  40. package/dist/core/keyset-cursor.js +20 -0
  41. package/dist/core/ports.d.ts +30 -0
  42. package/dist/core/ports.js +0 -1
  43. package/dist/core/tools/args.d.ts +1 -46
  44. package/dist/core/tools/args.js +1 -44
  45. package/dist/media/index.d.ts +0 -3
  46. package/dist/media/index.js +1 -4
  47. package/dist/media/node.d.ts +5 -0
  48. package/dist/media/node.js +6 -0
  49. package/dist/redirects/capture.d.ts +80 -0
  50. package/dist/redirects/capture.js +66 -0
  51. package/dist/redirects/express/index.d.ts +27 -0
  52. package/dist/redirects/express/index.js +40 -0
  53. package/dist/redirects/hit-sink.d.ts +56 -0
  54. package/dist/redirects/hit-sink.js +44 -0
  55. package/dist/redirects/host-config/index.d.ts +40 -0
  56. package/dist/redirects/host-config/index.js +145 -0
  57. package/dist/redirects/index.d.ts +14 -0
  58. package/dist/redirects/index.js +14 -0
  59. package/dist/redirects/matcher.d.ts +58 -0
  60. package/dist/redirects/matcher.js +144 -0
  61. package/dist/redirects/order.d.ts +30 -0
  62. package/dist/redirects/order.js +26 -0
  63. package/dist/redirects/ports.d.ts +242 -0
  64. package/dist/redirects/ports.internal.d.ts +57 -0
  65. package/dist/redirects/ports.internal.js +14 -0
  66. package/dist/redirects/ports.js +2 -0
  67. package/dist/redirects/redirects.d.ts +226 -0
  68. package/dist/redirects/redirects.js +590 -0
  69. package/dist/redirects/referrer-alias.d.ts +31 -0
  70. package/dist/redirects/referrer-alias.js +33 -0
  71. package/dist/redirects/repo.memory.d.ts +51 -0
  72. package/dist/redirects/repo.memory.js +98 -0
  73. package/dist/redirects/reserved-destination.d.ts +58 -0
  74. package/dist/redirects/reserved-destination.js +59 -0
  75. package/dist/redirects/resolver.d.ts +100 -0
  76. package/dist/redirects/resolver.js +87 -0
  77. package/dist/redirects/spa/index.d.ts +30 -0
  78. package/dist/redirects/spa/index.js +27 -0
  79. package/dist/redirects/sql/index.d.ts +4 -0
  80. package/dist/redirects/sql/index.js +4 -0
  81. package/dist/redirects/sql/repo.d.ts +82 -0
  82. package/dist/redirects/sql/repo.js +143 -0
  83. package/dist/redirects/sql/repo.rows.d.ts +49 -0
  84. package/dist/redirects/sql/repo.rows.js +79 -0
  85. package/dist/redirects/sql/row-types.d.ts +34 -0
  86. package/dist/redirects/sql/row-types.js +2 -0
  87. package/dist/redirects/target-gate.d.ts +30 -0
  88. package/dist/redirects/target-gate.js +85 -0
  89. package/dist/redirects/types.d.ts +213 -0
  90. package/dist/redirects/types.js +42 -0
  91. package/dist/seo/absolute-url.d.ts +58 -0
  92. package/dist/seo/absolute-url.js +117 -0
  93. package/dist/seo/dom/index.d.ts +14 -0
  94. package/dist/seo/dom/index.js +52 -0
  95. package/dist/seo/errors.d.ts +35 -0
  96. package/dist/seo/errors.js +35 -0
  97. package/dist/seo/feed.d.ts +49 -0
  98. package/dist/seo/feed.js +61 -0
  99. package/dist/seo/index.d.ts +13 -0
  100. package/dist/seo/index.js +13 -0
  101. package/dist/seo/media.d.ts +57 -0
  102. package/dist/seo/media.js +134 -0
  103. package/dist/seo/page-head.d.ts +18 -0
  104. package/dist/seo/page-head.js +114 -0
  105. package/dist/seo/ports.d.ts +221 -0
  106. package/dist/seo/ports.js +27 -0
  107. package/dist/seo/seo.d.ts +73 -0
  108. package/dist/seo/seo.js +230 -0
  109. package/dist/seo/sitemap.d.ts +139 -0
  110. package/dist/seo/sitemap.js +217 -0
  111. package/dist/seo/types.d.ts +278 -0
  112. package/dist/seo/types.js +2 -0
  113. package/dist/seo/write-service.d.ts +79 -0
  114. package/dist/seo/write-service.js +273 -0
  115. package/dist/seo/xml-escape.d.ts +16 -0
  116. package/dist/seo/xml-escape.js +19 -0
  117. package/dist/settings/express/cms-adapter.d.ts +20 -0
  118. package/dist/settings/express/cms-adapter.js +124 -0
  119. package/dist/settings/express/contracts.d.ts +135 -0
  120. package/dist/settings/express/contracts.js +2 -0
  121. package/dist/settings/express/http.d.ts +10 -0
  122. package/dist/settings/express/http.js +211 -0
  123. package/dist/settings/express/index.d.ts +5 -0
  124. package/dist/settings/express/index.js +5 -0
  125. package/dist/settings/express/shared.d.ts +68 -0
  126. package/dist/settings/express/shared.js +54 -0
  127. package/dist/settings/express/stream.d.ts +24 -0
  128. package/dist/settings/express/stream.js +147 -0
  129. package/dist/settings/index.d.ts +3 -38
  130. package/dist/settings/index.js +3 -35
  131. package/dist/taxonomy/write-service.d.ts +2 -30
  132. package/dist/widgets/authorize-helper.d.ts +42 -0
  133. package/dist/widgets/authorize-helper.js +16 -0
  134. package/dist/widgets/concurrency.d.ts +12 -0
  135. package/dist/widgets/concurrency.js +44 -0
  136. package/dist/widgets/config-validation.d.ts +34 -0
  137. package/dist/widgets/config-validation.js +125 -0
  138. package/dist/widgets/embed-service.d.ts +133 -0
  139. package/dist/widgets/embed-service.js +460 -0
  140. package/dist/widgets/embed-validation.d.ts +23 -0
  141. package/dist/widgets/embed-validation.js +38 -0
  142. package/dist/widgets/entry-payload.d.ts +103 -0
  143. package/dist/widgets/entry-payload.js +160 -0
  144. package/dist/widgets/errors.d.ts +136 -0
  145. package/dist/widgets/errors.js +136 -0
  146. package/dist/widgets/host-ports.d.ts +149 -0
  147. package/dist/widgets/host-ports.js +2 -0
  148. package/dist/widgets/html/html-embeds.d.ts +247 -0
  149. package/dist/widgets/html/html-embeds.js +408 -0
  150. package/dist/widgets/html/index.d.ts +5 -0
  151. package/dist/widgets/html/index.js +4 -0
  152. package/dist/widgets/html/legacy-widget-markers.d.ts +8 -0
  153. package/dist/widgets/html/legacy-widget-markers.js +45 -0
  154. package/dist/widgets/html/resolve-html-page-embeds.d.ts +42 -0
  155. package/dist/widgets/html/resolve-html-page-embeds.js +57 -0
  156. package/dist/widgets/index.d.ts +20 -0
  157. package/dist/widgets/index.js +19 -0
  158. package/dist/widgets/markers/html-attributes.d.ts +98 -0
  159. package/dist/widgets/markers/html-attributes.js +216 -0
  160. package/dist/widgets/markers/index.d.ts +3 -0
  161. package/dist/widgets/markers/index.js +3 -0
  162. package/dist/widgets/markers/marker.d.ts +496 -0
  163. package/dist/widgets/markers/marker.js +780 -0
  164. package/dist/widgets/page-embed-types.d.ts +42 -0
  165. package/dist/widgets/page-embed-types.js +42 -0
  166. package/dist/widgets/ports.d.ts +142 -0
  167. package/dist/widgets/ports.js +2 -0
  168. package/dist/widgets/read-service.d.ts +81 -0
  169. package/dist/widgets/read-service.js +79 -0
  170. package/dist/widgets/region-area-service.d.ts +110 -0
  171. package/dist/widgets/region-area-service.js +180 -0
  172. package/dist/widgets/registry.d.ts +217 -0
  173. package/dist/widgets/registry.js +172 -0
  174. package/dist/widgets/repo.memory.d.ts +41 -0
  175. package/dist/widgets/repo.memory.js +48 -0
  176. package/dist/widgets/resolver-service.d.ts +209 -0
  177. package/dist/widgets/resolver-service.js +909 -0
  178. package/dist/widgets/resolvers/contact-form.d.ts +26 -0
  179. package/dist/widgets/resolvers/contact-form.js +54 -0
  180. package/dist/widgets/resolvers/create-core-resolvers.d.ts +34 -0
  181. package/dist/widgets/resolvers/create-core-resolvers.js +11 -0
  182. package/dist/widgets/resolvers/index.d.ts +65 -0
  183. package/dist/widgets/resolvers/index.js +155 -0
  184. package/dist/widgets/resolvers/menu.d.ts +42 -0
  185. package/dist/widgets/resolvers/menu.js +43 -0
  186. package/dist/widgets/resolvers/ports.d.ts +82 -0
  187. package/dist/widgets/resolvers/ports.js +2 -0
  188. package/dist/widgets/resolvers/recent-entries.d.ts +71 -0
  189. package/dist/widgets/resolvers/recent-entries.js +165 -0
  190. package/dist/widgets/resolvers/taxonomy.d.ts +12 -0
  191. package/dist/widgets/resolvers/taxonomy.js +34 -0
  192. package/dist/widgets/sql/index.d.ts +3 -0
  193. package/dist/widgets/sql/index.js +3 -0
  194. package/dist/widgets/sql/repo.d.ts +41 -0
  195. package/dist/widgets/sql/repo.js +82 -0
  196. package/dist/widgets/sql/tables.d.ts +13 -0
  197. package/dist/widgets/sql/tables.js +2 -0
  198. package/dist/widgets/types.d.ts +213 -0
  199. package/dist/widgets/types.js +17 -0
  200. package/dist/widgets/where-used.d.ts +41 -0
  201. package/dist/widgets/where-used.js +11 -0
  202. package/dist/widgets/write-service.d.ts +204 -0
  203. package/dist/widgets/write-service.js +332 -0
  204. package/dist/workspace/delete.d.ts +1 -1
  205. package/package.json +220 -11
@@ -0,0 +1,103 @@
1
+ import type { ContentTypeRepoPort } from "../content-types/index.js";
2
+ import type { WidgetHostPorts } from "./host-ports.js";
3
+ import type { Clock as ClockPort } from "@jini-ai/core/primitives";
4
+ import type { OutboxPort } from "../core/index.js";
5
+ import type { EntryRecord } from "../entries/index.js";
6
+ import type { WidgetAreaDoc, WidgetAreaEntry, WidgetInstanceEntry, WidgetInstanceStatus, WidgetPlacementNode, WidgetRegionKey, WidgetTypeKey } from "./types.js";
7
+ /**
8
+ * @file Storage-shape plumbing shared by `write-service.ts`/`region-area-service.ts` — how a
9
+ * widget instance's/`widget_area`'s data actually lives inside an `entries` row.
10
+ *
11
+ * Purpose (a real, disclosed deviation from the spec's literal storage-path wording — see the
12
+ * implementation report):
13
+ * `features/entries/write-service.ts`'s `updateEntry` can only change `title`/`fieldsJson` — it has
14
+ * no parameter to change `bodyJson` at all once an entry is created (confirmed by reading its
15
+ * `UpdateEntryRequired.input` shape and body). Widget config MUST be updatable (REQ-05/AC-04), so
16
+ * it cannot live in `bodyJson`.
17
+ *
18
+ * Owner namespace (2026-07-21, fixed): `features/entries/field-validation.ts`'s
19
+ * `validateFieldsAgainstSchema` now accepts an `owner` parameter instead of hardcoding `site`
20
+ * (a real, confirmed gap against ADR-022 §2 — see that file's header and the implementation
21
+ * report). Widget instances write under `fields.ext.widget.*` (`WIDGET_FIELD_NAMESPACE`) and
22
+ * `widget_area` entries under `fields.ext.widgets.*` (`WIDGET_AREA_FIELD_NAMESPACE`), matching
23
+ * REQ-01/REQ-11's literal namespacing — not a shared `site` bag.
24
+ * Since 2026-10-04 that namespace is a property of the registered content type (`owner` below), not
25
+ * a per-call `owner` argument: the entries chokepoint reads it off the type, so the generic entries
26
+ * routes can no longer validate a widget payload under `ext.site` (how two unreadable probe rows
27
+ * reached production on 2026-08-03).
28
+ *
29
+ * A widget instance's `config` shape is still polymorphic per `widgetType` (declared in
30
+ * `registry.ts`'s per-type `configSchema`, a JSON-Schema-like validator, not the generic entries
31
+ * fixed-field-list schema `validateFieldsAgainstSchema` checks) — the generic entries content-type
32
+ * field list genuinely cannot express "shape varies by widgetType" regardless of which `ext` owner
33
+ * it validates under, and widgets already runs its own `validateWidgetConfig` (REQ-02) against the
34
+ * real per-type schema before ever reaching this layer. So this file still stores the real
35
+ * config/doc data as one JSON-serialized string in a single required `payload` text field — now
36
+ * under the correct owner namespace — rather than mapping each config key to its own generic-entries
37
+ * field, which round-trips the exact `WidgetInstanceEntry.config`/`WidgetAreaEntry.doc` shapes the
38
+ * public contracts (`types.ts`) declare. `bodyJson` is left unused by this feature (REQ-11's
39
+ * `bodyJson.placements` placement is a separate, disclosed deviation, not fixed by this namespace
40
+ * change — see the implementation report).
41
+ */
42
+ export declare const WIDGET_PAYLOAD_FIELD = "payload";
43
+ interface WidgetInstancePayload {
44
+ widgetType: WidgetTypeKey;
45
+ /** `Record<string, unknown>`, not `JsonObject`, matching the stub-frozen `Create/UpdateWidgetInstanceInput.config` shape (`write-service.ts`) — cast to `JsonObject` only at the `WidgetInstanceEntry` read-model boundary below. */
46
+ config: Record<string, unknown>;
47
+ status: WidgetInstanceStatus;
48
+ }
49
+ interface WidgetAreaPayload {
50
+ regionKey: WidgetRegionKey;
51
+ doc: WidgetAreaDoc;
52
+ }
53
+ export declare function buildWidgetInstanceFieldsJson({ payload }: {
54
+ payload: WidgetInstancePayload;
55
+ }, _optional?: Record<string, never>): unknown;
56
+ export declare function parseWidgetInstancePayload({ fieldsJson }: {
57
+ fieldsJson: unknown;
58
+ }, _optional?: Record<string, never>): WidgetInstancePayload;
59
+ export declare function buildWidgetAreaFieldsJson({ payload }: {
60
+ payload: WidgetAreaPayload;
61
+ }, _optional?: Record<string, never>): unknown;
62
+ export declare function parseWidgetAreaPayload({ fieldsJson }: {
63
+ fieldsJson: unknown;
64
+ }, _optional?: Record<string, never>): WidgetAreaPayload;
65
+ export declare function toWidgetInstanceEntry({ entry }: {
66
+ entry: EntryRecord;
67
+ }, _optional?: Record<string, never>): WidgetInstanceEntry;
68
+ export declare function toWidgetAreaEntry({ entry }: {
69
+ entry: EntryRecord;
70
+ }, _optional?: Record<string, never>): WidgetAreaEntry;
71
+ /** Stable, workspace-unique slug for a region's `widget_area` entry — also the natural de-dup key REQ-13's idempotent seeding relies on. */
72
+ export declare function widgetAreaSlug({ regionKey }: {
73
+ regionKey: WidgetRegionKey;
74
+ }, _optional?: Record<string, never>): string;
75
+ export declare function emptyWidgetAreaDoc(_required: Record<string, never>, _optional?: Record<string, never>): WidgetAreaDoc;
76
+ export declare function areaDocWithPlacements(required: {
77
+ doc: WidgetAreaDoc;
78
+ placements: readonly WidgetPlacementNode[];
79
+ }, _optional?: Record<string, never>): WidgetAreaDoc;
80
+ /**
81
+ * Registers the `widget`/`widget_area` seeded content types (ADR-047 §1, REQ-11) in `workspaceId`
82
+ * if not already present — idempotent, safe to call before every write. Delegates to
83
+ * `features/content-types/write-service.ts`'s real `registerContentType` chokepoint (the same
84
+ * mechanism any Collections content type is registered through) rather than hand-constructing a
85
+ * `content_types` row, per this task's "compose real infra, don't reimplement" directive. Each
86
+ * type gets exactly one required `text`-kind field (`payload`) — see this file's header for why
87
+ * the real config/doc data is JSON-serialized into that one field rather than expressed as
88
+ * per-field scalar columns.
89
+ */
90
+ export declare function ensureWidgetContentTypesRegistered(required: {
91
+ deps: {
92
+ host: Pick<WidgetHostPorts, "contentTypes">;
93
+ contentTypeRepo: ContentTypeRepoPort;
94
+ clock: ClockPort;
95
+ ids: {
96
+ newId: () => string;
97
+ };
98
+ outbox: OutboxPort;
99
+ };
100
+ workspaceId: string;
101
+ }, _optional?: Record<string, never>): Promise<void>;
102
+ export {};
103
+ //# sourceMappingURL=entry-payload.d.ts.map
@@ -0,0 +1,160 @@
1
+ import { isNonArrayRecord } from "@jini-ai/core";
2
+ import { PRE_AUTHORIZED, WIDGETS_SYSTEM_ACTOR_ID } from "./authorize-helper.js";
3
+ import { WIDGET_AREA_CONTENT_TYPE, WIDGET_AREA_FIELD_NAMESPACE, WIDGET_CONTENT_TYPE, WIDGET_FIELD_NAMESPACE, } from "./types.js";
4
+ /**
5
+ * @file Storage-shape plumbing shared by `write-service.ts`/`region-area-service.ts` — how a
6
+ * widget instance's/`widget_area`'s data actually lives inside an `entries` row.
7
+ *
8
+ * Purpose (a real, disclosed deviation from the spec's literal storage-path wording — see the
9
+ * implementation report):
10
+ * `features/entries/write-service.ts`'s `updateEntry` can only change `title`/`fieldsJson` — it has
11
+ * no parameter to change `bodyJson` at all once an entry is created (confirmed by reading its
12
+ * `UpdateEntryRequired.input` shape and body). Widget config MUST be updatable (REQ-05/AC-04), so
13
+ * it cannot live in `bodyJson`.
14
+ *
15
+ * Owner namespace (2026-07-21, fixed): `features/entries/field-validation.ts`'s
16
+ * `validateFieldsAgainstSchema` now accepts an `owner` parameter instead of hardcoding `site`
17
+ * (a real, confirmed gap against ADR-022 §2 — see that file's header and the implementation
18
+ * report). Widget instances write under `fields.ext.widget.*` (`WIDGET_FIELD_NAMESPACE`) and
19
+ * `widget_area` entries under `fields.ext.widgets.*` (`WIDGET_AREA_FIELD_NAMESPACE`), matching
20
+ * REQ-01/REQ-11's literal namespacing — not a shared `site` bag.
21
+ * Since 2026-10-04 that namespace is a property of the registered content type (`owner` below), not
22
+ * a per-call `owner` argument: the entries chokepoint reads it off the type, so the generic entries
23
+ * routes can no longer validate a widget payload under `ext.site` (how two unreadable probe rows
24
+ * reached production on 2026-08-03).
25
+ *
26
+ * A widget instance's `config` shape is still polymorphic per `widgetType` (declared in
27
+ * `registry.ts`'s per-type `configSchema`, a JSON-Schema-like validator, not the generic entries
28
+ * fixed-field-list schema `validateFieldsAgainstSchema` checks) — the generic entries content-type
29
+ * field list genuinely cannot express "shape varies by widgetType" regardless of which `ext` owner
30
+ * it validates under, and widgets already runs its own `validateWidgetConfig` (REQ-02) against the
31
+ * real per-type schema before ever reaching this layer. So this file still stores the real
32
+ * config/doc data as one JSON-serialized string in a single required `payload` text field — now
33
+ * under the correct owner namespace — rather than mapping each config key to its own generic-entries
34
+ * field, which round-trips the exact `WidgetInstanceEntry.config`/`WidgetAreaEntry.doc` shapes the
35
+ * public contracts (`types.ts`) declare. `bodyJson` is left unused by this feature (REQ-11's
36
+ * `bodyJson.placements` placement is a separate, disclosed deviation, not fixed by this namespace
37
+ * change — see the implementation report).
38
+ */
39
+ export const WIDGET_PAYLOAD_FIELD = "payload";
40
+ /** Reads the single `payload` text field out of an entry's `fieldsJson.ext.<owner>` envelope. */
41
+ function readPayloadString(fieldsJson, owner) {
42
+ const fields = { value: fieldsJson };
43
+ if (!isNonArrayRecord(fields))
44
+ throw new Error("widgets: malformed fieldsJson (expected an object)");
45
+ const ext = { value: fields.value.ext };
46
+ if (!isNonArrayRecord(ext))
47
+ throw new Error("widgets: malformed fieldsJson (missing ext)");
48
+ const ownerBag = { value: ext.value[owner] };
49
+ if (!isNonArrayRecord(ownerBag) || typeof ownerBag.value[WIDGET_PAYLOAD_FIELD] !== "string") {
50
+ throw new Error(`widgets: malformed fieldsJson (missing fields.ext.${owner}.payload)`);
51
+ }
52
+ return ownerBag.value[WIDGET_PAYLOAD_FIELD];
53
+ }
54
+ function buildFieldsJson(payload, owner) {
55
+ return { ext: { [owner]: { [WIDGET_PAYLOAD_FIELD]: JSON.stringify(payload) } } };
56
+ }
57
+ export function buildWidgetInstanceFieldsJson({ payload }, _optional = {}) {
58
+ return buildFieldsJson(payload, WIDGET_FIELD_NAMESPACE);
59
+ }
60
+ export function parseWidgetInstancePayload({ fieldsJson }, _optional = {}) {
61
+ return JSON.parse(readPayloadString(fieldsJson, WIDGET_FIELD_NAMESPACE));
62
+ }
63
+ export function buildWidgetAreaFieldsJson({ payload }, _optional = {}) {
64
+ return buildFieldsJson(payload, WIDGET_AREA_FIELD_NAMESPACE);
65
+ }
66
+ export function parseWidgetAreaPayload({ fieldsJson }, _optional = {}) {
67
+ return JSON.parse(readPayloadString(fieldsJson, WIDGET_AREA_FIELD_NAMESPACE));
68
+ }
69
+ export function toWidgetInstanceEntry({ entry }, _optional = {}) {
70
+ const payload = parseWidgetInstancePayload({ fieldsJson: entry.fieldsJson });
71
+ return {
72
+ id: entry.id,
73
+ workspaceId: entry.workspaceId,
74
+ slug: entry.slug,
75
+ title: entry.title,
76
+ status: payload.status,
77
+ widgetType: payload.widgetType,
78
+ config: payload.config,
79
+ updatedAt: entry.updatedAt,
80
+ version: entry.version,
81
+ };
82
+ }
83
+ export function toWidgetAreaEntry({ entry }, _optional = {}) {
84
+ const payload = parseWidgetAreaPayload({ fieldsJson: entry.fieldsJson });
85
+ return {
86
+ id: entry.id,
87
+ workspaceId: entry.workspaceId,
88
+ regionKey: payload.regionKey,
89
+ doc: payload.doc,
90
+ updatedAt: entry.updatedAt,
91
+ version: entry.version,
92
+ };
93
+ }
94
+ /** Stable, workspace-unique slug for a region's `widget_area` entry — also the natural de-dup key REQ-13's idempotent seeding relies on. */
95
+ export function widgetAreaSlug({ regionKey }, _optional = {}) {
96
+ return `widget-area-${regionKey}`;
97
+ }
98
+ export function emptyWidgetAreaDoc(_required, _optional = {}) {
99
+ return { schemaVersion: 1, placements: [] };
100
+ }
101
+ export function areaDocWithPlacements(required, _optional = {}) {
102
+ return { schemaVersion: required.doc.schemaVersion, placements: required.placements };
103
+ }
104
+ /**
105
+ * Registers the `widget`/`widget_area` seeded content types (ADR-047 §1, REQ-11) in `workspaceId`
106
+ * if not already present — idempotent, safe to call before every write. Delegates to
107
+ * `features/content-types/write-service.ts`'s real `registerContentType` chokepoint (the same
108
+ * mechanism any Collections content type is registered through) rather than hand-constructing a
109
+ * `content_types` row, per this task's "compose real infra, don't reimplement" directive. Each
110
+ * type gets exactly one required `text`-kind field (`payload`) — see this file's header for why
111
+ * the real config/doc data is JSON-serialized into that one field rather than expressed as
112
+ * per-field scalar columns.
113
+ */
114
+ export async function ensureWidgetContentTypesRegistered(required, _optional = {}) {
115
+ const { deps, workspaceId } = required;
116
+ await ensureOneContentTypeRegistered(deps, workspaceId, WIDGET_CONTENT_TYPE, "Widget", WIDGET_FIELD_NAMESPACE);
117
+ await ensureOneContentTypeRegistered(deps, workspaceId, WIDGET_AREA_CONTENT_TYPE, "Widget Area", WIDGET_AREA_FIELD_NAMESPACE);
118
+ }
119
+ async function ensureOneContentTypeRegistered(deps, workspaceId, key, label, owner) {
120
+ const existing = await deps.contentTypeRepo.findByKey({ workspaceId, key });
121
+ if (existing)
122
+ return;
123
+ const result = await deps.host.contentTypes.registerContentType({
124
+ deps: {
125
+ repo: deps.contentTypeRepo,
126
+ clock: deps.clock,
127
+ ids: deps.ids,
128
+ authorize: PRE_AUTHORIZED,
129
+ indexProvisioner: deps.host.contentTypes.createIndexProvisioner(),
130
+ outbox: deps.host.contentTypes.toContentTypeOutbox({ outbox: deps.outbox, clock: deps.clock, idGen: deps.ids, workspaceId }),
131
+ },
132
+ input: {
133
+ actorId: WIDGETS_SYSTEM_ACTOR_ID,
134
+ // Not a human and not the assistant — this is the widgets subsystem seeding its own two
135
+ // content types at boot (REQ-13). Recording it as such keeps the audit trail able to say
136
+ // "nobody did this, the system did".
137
+ principalKind: "system",
138
+ workspaceId,
139
+ key,
140
+ label,
141
+ fields: [{ name: WIDGET_PAYLOAD_FIELD, kind: "text", required: true, queryable: false }],
142
+ // The envelope namespace belongs to the type (2026-10-04): every entry write, including the
143
+ // generic entries routes, now validates this type's payload under `ext.<owner>` with no
144
+ // caller passing it. `content-types/declared-owners.ts` restores it on SQL reads.
145
+ owner,
146
+ },
147
+ });
148
+ if (!result.ok) {
149
+ // S9 (web-high fix plan, 2026-09-24): two concurrent first-widget creates both saw `findByKey`
150
+ // return null above and both called `registerContentType`; the loser now gets
151
+ // `ContentTypeAlreadyExistsError` instead of silently overwriting the winner's row. That is
152
+ // exactly the outcome this function wants — a content type now exists — so treat it as success,
153
+ // UNLESS the key was tombstoned (`tombstoned: true`), which is a real, unrecoverable failure.
154
+ const existingError = deps.host.contentTypes.alreadyExists(result.error);
155
+ if (existingError && !existingError.tombstoned)
156
+ return;
157
+ throw result.error;
158
+ }
159
+ }
160
+ //# sourceMappingURL=entry-payload.js.map
@@ -0,0 +1,136 @@
1
+ /**
2
+ * @file Typed domain errors for `widgets` (SPEC-043).
3
+ *
4
+ * Purpose:
5
+ * One class per error this module originates; route handlers map these 1:1 to HTTP codes.
6
+ * Mirrors the `forms/errors.ts` / `PostConflictError`-style convention already used throughout
7
+ * this codebase.
8
+ *
9
+ * `this.name` fix (found during implementation, additive-only — no constructor signature
10
+ * changed): `class X extends Error {}` does NOT give an instance a `.name` of `"X"` on this
11
+ * runtime unless the constructor sets `this.name` explicitly (confirmed by
12
+ * `features/entries/errors.ts`'s own header, which documents and applies this exact fix already).
13
+ * Without it, `Error.prototype.toString()` — what `assert.rejects(fn, /ClassName/)` matches
14
+ * against — reads `"Error: <message>"` for every class here, so a certified test asserting
15
+ * `/WidgetConfigValidationError/` etc. could never pass regardless of which error actually threw.
16
+ */
17
+ /** `WIDGETS_TYPE_UNREGISTERED` (400) — `widgetType` is not present in the registry (REQ-03). */
18
+ export declare class WidgetTypeUnregisteredError extends Error {
19
+ readonly widgetType: string;
20
+ constructor({ message, widgetType }: {
21
+ message: string;
22
+ widgetType: string;
23
+ }, _optional?: Record<string, never>);
24
+ }
25
+ /** `WIDGETS_CONFIG_VALIDATION_ERROR` (400) — config fails the type's registered schema (REQ-02). */
26
+ export declare class WidgetConfigValidationError extends Error {
27
+ readonly fieldErrors: Array<{
28
+ field: string;
29
+ reason: string;
30
+ }>;
31
+ constructor({ message, fieldErrors }: {
32
+ message: string;
33
+ fieldErrors?: Array<{
34
+ field: string;
35
+ reason: string;
36
+ }>;
37
+ }, _optional?: Record<string, never>);
38
+ }
39
+ /** `WIDGETS_VERSION_CONFLICT` (409) — optimistic-concurrency mismatch (REQ-06). */
40
+ export declare class WidgetVersionConflictError extends Error {
41
+ readonly currentVersion: number;
42
+ constructor({ message, currentVersion }: {
43
+ message: string;
44
+ currentVersion: number;
45
+ }, _optional?: Record<string, never>);
46
+ }
47
+ /** `WIDGETS_AREA_CONFLICT` (409) — a `widget_area` mutation lost an OCC race (REQ-15). */
48
+ export declare class WidgetAreaConflictError extends Error {
49
+ readonly currentVersion: number;
50
+ constructor({ message, currentVersion }: {
51
+ message: string;
52
+ currentVersion: number;
53
+ }, _optional?: Record<string, never>);
54
+ }
55
+ /**
56
+ * `WIDGETS_REFERENCED` (409) — delete attempted without `.force` while the
57
+ * instance is still referenced by at least one placement (REQ-42).
58
+ */
59
+ export declare class WidgetReferencedError extends Error {
60
+ readonly referencingLocations: Array<{
61
+ kind: "region" | "embed";
62
+ entryId: string;
63
+ }>;
64
+ constructor({ message, referencingLocations }: {
65
+ message: string;
66
+ referencingLocations?: Array<{
67
+ kind: "region" | "embed";
68
+ entryId: string;
69
+ }>;
70
+ }, _optional?: Record<string, never>);
71
+ }
72
+ /**
73
+ * `WIDGETS_EMBED_GUARDRAIL_VIOLATION` (400) — a `widgetEmbed` mutation would
74
+ * create recursion or exceed the per-document embed count (REQ-19/20), raised
75
+ * identically regardless of whether the mutation originated from the live
76
+ * editor or the server-side/AI command path (INV-04).
77
+ */
78
+ export declare class WidgetEmbedGuardrailError extends Error {
79
+ readonly reason: "recursion" | "count-exceeded";
80
+ constructor({ message, reason }: {
81
+ message: string;
82
+ reason: "recursion" | "count-exceeded";
83
+ }, _optional?: Record<string, never>);
84
+ }
85
+ /** `WIDGETS_INSTANCE_NOT_FOUND` (404). */
86
+ export declare class WidgetInstanceNotFoundError extends Error {
87
+ constructor({ message }: {
88
+ message: string;
89
+ }, _optional?: Record<string, never>);
90
+ }
91
+ /** `WIDGETS_AREA_NOT_FOUND` (404). */
92
+ export declare class WidgetAreaNotFoundError extends Error {
93
+ constructor({ message }: {
94
+ message: string;
95
+ }, _optional?: Record<string, never>);
96
+ }
97
+ /**
98
+ * `WIDGETS_FORBIDDEN` (403) — a principal lacking the required flat `widgets.*` permission
99
+ * attempted a widget mutation or read (REQ-40/41, INV-07). Added during implementation: the
100
+ * original stub-era contract had no distinct forbidden-error class for this library (every other
101
+ * `write-service.ts`-shaped chokepoint in this codebase — `features/entries`, `features/
102
+ * content-types` — has its own `ForbiddenError`), so `write-service.ts`/`region-area-service.ts`
103
+ * had no typed way to signal an `authorize()` rejection distinctly from every other failure mode.
104
+ */
105
+ export declare class WidgetForbiddenError extends Error {
106
+ constructor({ message }: {
107
+ message: string;
108
+ }, _optional?: Record<string, never>);
109
+ }
110
+ /** `WIDGETS_EMBED_HOST_NOT_FOUND` (404) — no post, page, or content entry with that id in this workspace (or it is trashed). */
111
+ export declare class WidgetEmbedHostNotFoundError extends Error {
112
+ constructor({ message }: {
113
+ message: string;
114
+ }, _optional?: Record<string, never>);
115
+ }
116
+ /**
117
+ * `WIDGETS_EMBED_PLACEMENT_NOT_FOUND` (404) — `removeWidgetEmbed` was called with a `placementId`
118
+ * that matches no `widgetEmbed` node in the host's body (already removed, or never existed). Fixed
119
+ * during the C4d leftovers pass: `removeEmbedByPlacementId` is a no-op filter — before this class
120
+ * existed, a stale or bogus `placementId` fell through to `writeHostBody` unchanged and reported
121
+ * success while writing nothing.
122
+ */
123
+ export declare class WidgetEmbedPlacementNotFoundError extends Error {
124
+ constructor({ message }: {
125
+ message: string;
126
+ }, _optional?: Record<string, never>);
127
+ }
128
+ /** `WIDGETS_EMBED_HOST_UNSUPPORTED` (400) — the host exists but has no rich-text body a widgetEmbed node can live in. */
129
+ export declare class WidgetEmbedHostUnsupportedError extends Error {
130
+ readonly reason: "html-page" | "widget-area";
131
+ constructor({ message, reason }: {
132
+ message: string;
133
+ reason: "html-page" | "widget-area";
134
+ }, _optional?: Record<string, never>);
135
+ }
136
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1,136 @@
1
+ /**
2
+ * @file Typed domain errors for `widgets` (SPEC-043).
3
+ *
4
+ * Purpose:
5
+ * One class per error this module originates; route handlers map these 1:1 to HTTP codes.
6
+ * Mirrors the `forms/errors.ts` / `PostConflictError`-style convention already used throughout
7
+ * this codebase.
8
+ *
9
+ * `this.name` fix (found during implementation, additive-only — no constructor signature
10
+ * changed): `class X extends Error {}` does NOT give an instance a `.name` of `"X"` on this
11
+ * runtime unless the constructor sets `this.name` explicitly (confirmed by
12
+ * `features/entries/errors.ts`'s own header, which documents and applies this exact fix already).
13
+ * Without it, `Error.prototype.toString()` — what `assert.rejects(fn, /ClassName/)` matches
14
+ * against — reads `"Error: <message>"` for every class here, so a certified test asserting
15
+ * `/WidgetConfigValidationError/` etc. could never pass regardless of which error actually threw.
16
+ */
17
+ /** `WIDGETS_TYPE_UNREGISTERED` (400) — `widgetType` is not present in the registry (REQ-03). */
18
+ export class WidgetTypeUnregisteredError extends Error {
19
+ widgetType;
20
+ constructor({ message, widgetType }, _optional = {}) {
21
+ super(message);
22
+ this.widgetType = widgetType;
23
+ this.name = "WidgetTypeUnregisteredError";
24
+ }
25
+ }
26
+ /** `WIDGETS_CONFIG_VALIDATION_ERROR` (400) — config fails the type's registered schema (REQ-02). */
27
+ export class WidgetConfigValidationError extends Error {
28
+ fieldErrors;
29
+ constructor({ message, fieldErrors = [] }, _optional = {}) {
30
+ super(message);
31
+ this.fieldErrors = fieldErrors;
32
+ this.name = "WidgetConfigValidationError";
33
+ }
34
+ }
35
+ /** `WIDGETS_VERSION_CONFLICT` (409) — optimistic-concurrency mismatch (REQ-06). */
36
+ export class WidgetVersionConflictError extends Error {
37
+ currentVersion;
38
+ constructor({ message, currentVersion }, _optional = {}) {
39
+ super(message);
40
+ this.currentVersion = currentVersion;
41
+ this.name = "WidgetVersionConflictError";
42
+ }
43
+ }
44
+ /** `WIDGETS_AREA_CONFLICT` (409) — a `widget_area` mutation lost an OCC race (REQ-15). */
45
+ export class WidgetAreaConflictError extends Error {
46
+ currentVersion;
47
+ constructor({ message, currentVersion }, _optional = {}) {
48
+ super(message);
49
+ this.currentVersion = currentVersion;
50
+ this.name = "WidgetAreaConflictError";
51
+ }
52
+ }
53
+ /**
54
+ * `WIDGETS_REFERENCED` (409) — delete attempted without `.force` while the
55
+ * instance is still referenced by at least one placement (REQ-42).
56
+ */
57
+ export class WidgetReferencedError extends Error {
58
+ referencingLocations;
59
+ constructor({ message, referencingLocations = [] }, _optional = {}) {
60
+ super(message);
61
+ this.referencingLocations = referencingLocations;
62
+ this.name = "WidgetReferencedError";
63
+ }
64
+ }
65
+ /**
66
+ * `WIDGETS_EMBED_GUARDRAIL_VIOLATION` (400) — a `widgetEmbed` mutation would
67
+ * create recursion or exceed the per-document embed count (REQ-19/20), raised
68
+ * identically regardless of whether the mutation originated from the live
69
+ * editor or the server-side/AI command path (INV-04).
70
+ */
71
+ export class WidgetEmbedGuardrailError extends Error {
72
+ reason;
73
+ constructor({ message, reason }, _optional = {}) {
74
+ super(message);
75
+ this.reason = reason;
76
+ this.name = "WidgetEmbedGuardrailError";
77
+ }
78
+ }
79
+ /** `WIDGETS_INSTANCE_NOT_FOUND` (404). */
80
+ export class WidgetInstanceNotFoundError extends Error {
81
+ constructor({ message }, _optional = {}) {
82
+ super(message);
83
+ this.name = "WidgetInstanceNotFoundError";
84
+ }
85
+ }
86
+ /** `WIDGETS_AREA_NOT_FOUND` (404). */
87
+ export class WidgetAreaNotFoundError extends Error {
88
+ constructor({ message }, _optional = {}) {
89
+ super(message);
90
+ this.name = "WidgetAreaNotFoundError";
91
+ }
92
+ }
93
+ /**
94
+ * `WIDGETS_FORBIDDEN` (403) — a principal lacking the required flat `widgets.*` permission
95
+ * attempted a widget mutation or read (REQ-40/41, INV-07). Added during implementation: the
96
+ * original stub-era contract had no distinct forbidden-error class for this library (every other
97
+ * `write-service.ts`-shaped chokepoint in this codebase — `features/entries`, `features/
98
+ * content-types` — has its own `ForbiddenError`), so `write-service.ts`/`region-area-service.ts`
99
+ * had no typed way to signal an `authorize()` rejection distinctly from every other failure mode.
100
+ */
101
+ export class WidgetForbiddenError extends Error {
102
+ constructor({ message }, _optional = {}) {
103
+ super(message);
104
+ this.name = "WidgetForbiddenError";
105
+ }
106
+ }
107
+ /** `WIDGETS_EMBED_HOST_NOT_FOUND` (404) — no post, page, or content entry with that id in this workspace (or it is trashed). */
108
+ export class WidgetEmbedHostNotFoundError extends Error {
109
+ constructor({ message }, _optional = {}) {
110
+ super(message);
111
+ this.name = "WidgetEmbedHostNotFoundError";
112
+ }
113
+ }
114
+ /**
115
+ * `WIDGETS_EMBED_PLACEMENT_NOT_FOUND` (404) — `removeWidgetEmbed` was called with a `placementId`
116
+ * that matches no `widgetEmbed` node in the host's body (already removed, or never existed). Fixed
117
+ * during the C4d leftovers pass: `removeEmbedByPlacementId` is a no-op filter — before this class
118
+ * existed, a stale or bogus `placementId` fell through to `writeHostBody` unchanged and reported
119
+ * success while writing nothing.
120
+ */
121
+ export class WidgetEmbedPlacementNotFoundError extends Error {
122
+ constructor({ message }, _optional = {}) {
123
+ super(message);
124
+ this.name = "WidgetEmbedPlacementNotFoundError";
125
+ }
126
+ }
127
+ /** `WIDGETS_EMBED_HOST_UNSUPPORTED` (400) — the host exists but has no rich-text body a widgetEmbed node can live in. */
128
+ export class WidgetEmbedHostUnsupportedError extends Error {
129
+ reason;
130
+ constructor({ message, reason }, _optional = {}) {
131
+ super(message);
132
+ this.reason = reason;
133
+ this.name = "WidgetEmbedHostUnsupportedError";
134
+ }
135
+ }
136
+ //# sourceMappingURL=errors.js.map