@jini-ai/cms 0.5.4 → 0.5.6
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/dist/consent/express/index.d.ts +53 -0
- package/dist/consent/express/index.js +159 -0
- package/dist/consent/express/response.d.ts +21 -0
- package/dist/consent/express/response.js +89 -0
- package/dist/consent/html/client.d.ts +6 -0
- package/dist/consent/html/client.js +135 -0
- package/dist/consent/html/gate.d.ts +29 -0
- package/dist/consent/html/gate.js +104 -0
- package/dist/consent/html/index.d.ts +4 -0
- package/dist/consent/html/index.js +3 -0
- package/dist/consent/html/render.d.ts +27 -0
- package/dist/consent/html/render.js +62 -0
- package/dist/consent/html/static.d.ts +7 -0
- package/dist/consent/html/static.js +25 -0
- package/dist/consent/html/styles.d.ts +3 -0
- package/dist/consent/html/styles.js +22 -0
- package/dist/consent/index.d.ts +3 -0
- package/dist/consent/index.js +3 -0
- package/dist/consent/node/index.d.ts +47 -0
- package/dist/consent/node/index.js +169 -0
- package/dist/consent/policy.d.ts +47 -0
- package/dist/consent/policy.js +62 -0
- package/dist/consent/types.d.ts +40 -0
- package/dist/consent/types.js +5 -0
- package/dist/content-types/agent-tools.js +1 -1
- package/dist/content-types/tool-registrations.d.ts +2 -0
- package/dist/content-types/tool-registrations.js +1 -0
- package/dist/content-types/write-service.d.ts +2 -0
- package/dist/content-types/write-service.js +3 -0
- package/dist/forms/descriptor-rules.d.ts +9 -0
- package/dist/forms/descriptor-rules.js +112 -0
- package/dist/forms/email-format.d.ts +7 -0
- package/dist/forms/email-format.js +11 -0
- package/dist/forms/express/handler.js +4 -8
- package/dist/forms/field-registry.d.ts +193 -0
- package/dist/forms/field-registry.js +231 -0
- package/dist/forms/forms.d.ts +8 -5
- package/dist/forms/forms.js +29 -46
- package/dist/forms/html/html-authoring.js +63 -13
- package/dist/forms/html/html-submission-adapter.d.ts +5 -5
- package/dist/forms/html/html-submission-adapter.js +6 -13
- package/dist/forms/html-render.d.ts +10 -3
- package/dist/forms/html-render.js +116 -14
- package/dist/forms/index.d.ts +1 -0
- package/dist/forms/index.js +1 -0
- package/dist/forms/repo.memory.js +4 -4
- package/dist/forms/submission-attempts.d.ts +1 -1
- package/dist/forms/submission-attempts.js +13 -2
- package/dist/forms/submission-email-validation.js +2 -4
- package/dist/forms/submit-service.d.ts +3 -0
- package/dist/forms/submit-service.js +38 -5
- package/dist/forms/types.d.ts +50 -2
- package/dist/listing/batch.d.ts +53 -0
- package/dist/listing/batch.js +57 -0
- package/dist/listing/html.d.ts +72 -0
- package/dist/listing/html.js +109 -0
- package/dist/listing/index.d.ts +5 -0
- package/dist/listing/index.js +5 -0
- package/dist/listing/paths.d.ts +32 -0
- package/dist/listing/paths.js +37 -0
- package/dist/listing/see-more-script.d.ts +16 -0
- package/dist/listing/see-more-script.js +62 -0
- package/dist/media/content-type-sniffer.d.ts +1 -1
- package/dist/media/content-type-sniffer.js +15 -0
- package/dist/media/image-transformer.sharp.js +4 -1
- package/dist/media/media-service.d.ts +4 -0
- package/dist/media/media-service.js +8 -0
- package/dist/media/node.d.ts +1 -1
- package/dist/media/node.js +1 -1
- package/dist/media/transform-registry.js +1 -1
- package/dist/media/transform-types.d.ts +2 -0
- package/dist/media/transform-types.js +3 -0
- package/dist/media/video-frames.ffmpeg.d.ts +15 -2
- package/dist/media/video-frames.ffmpeg.js +28 -13
- package/dist/navigation/agent-tools.js +20 -3
- package/dist/navigation/dropdown.d.ts +41 -0
- package/dist/navigation/dropdown.js +85 -0
- package/dist/navigation/index.d.ts +5 -1
- package/dist/navigation/index.js +4 -0
- package/dist/navigation/menu-html.d.ts +31 -0
- package/dist/navigation/menu-html.js +53 -0
- package/dist/navigation/menu-service.d.ts +9 -3
- package/dist/navigation/menu-service.js +15 -3
- package/dist/navigation/tool-registrations.d.ts +5 -0
- package/dist/navigation/tool-registrations.js +32 -2
- package/dist/navigation/types.d.ts +6 -0
- package/dist/redirects/capture.d.ts +2 -0
- package/dist/redirects/capture.js +4 -0
- package/dist/redirects/redirects.d.ts +8 -0
- package/dist/redirects/redirects.js +14 -0
- package/dist/routing/index.d.ts +5 -0
- package/dist/routing/index.js +2 -0
- package/dist/routing/ports.d.ts +51 -0
- package/dist/routing/routing.d.ts +138 -0
- package/dist/routing/routing.js +443 -0
- package/dist/routing/types.d.ts +164 -0
- package/dist/routing/types.js +2 -0
- package/dist/seo/media.d.ts +23 -0
- package/dist/seo/media.js +30 -5
- package/dist/seo/page-head.js +8 -1
- package/dist/seo/seo.d.ts +2 -0
- package/dist/seo/seo.js +11 -6
- package/dist/seo/types.d.ts +8 -0
- package/dist/seo/xml-escape.d.ts +1 -3
- package/dist/seo/xml-escape.js +1 -6
- package/dist/widgets/concurrency.d.ts +27 -1
- package/dist/widgets/concurrency.js +12 -23
- package/dist/widgets/config-validation.js +6 -0
- package/dist/widgets/gallery-resolver.d.ts +23 -0
- package/dist/widgets/gallery-resolver.js +80 -0
- package/dist/widgets/html/gallery-render.d.ts +24 -0
- package/dist/widgets/html/gallery-render.js +65 -0
- package/dist/widgets/html/html-embeds.d.ts +4 -0
- package/dist/widgets/html/html-embeds.js +7 -1
- package/dist/widgets/html/index.d.ts +3 -0
- package/dist/widgets/html/index.js +3 -0
- package/dist/widgets/html/server-fragment.d.ts +9 -0
- package/dist/widgets/html/server-fragment.js +38 -0
- package/dist/widgets/markers/gallery.d.ts +50 -0
- package/dist/widgets/markers/gallery.js +164 -0
- package/dist/widgets/markers/html-attributes.d.ts +6 -1
- package/dist/widgets/markers/html-attributes.js +24 -9
- package/dist/widgets/markers/index.d.ts +2 -0
- package/dist/widgets/markers/index.js +2 -0
- package/dist/widgets/markers/marker.d.ts +4 -0
- package/dist/widgets/markers/marker.js +6 -0
- package/dist/widgets/markers/media-reference.d.ts +11 -0
- package/dist/widgets/markers/media-reference.js +11 -0
- package/dist/widgets/page-embed-types.d.ts +1 -1
- package/dist/widgets/page-embed-types.js +1 -1
- package/dist/widgets/registry.d.ts +144 -10
- package/dist/widgets/registry.js +40 -0
- package/dist/widgets/resolver-service.d.ts +10 -0
- package/dist/widgets/resolver-service.js +73 -29
- package/dist/widgets/resolvers/create-core-resolvers.d.ts +3 -0
- package/dist/widgets/resolvers/create-core-resolvers.js +2 -0
- package/dist/widgets/resolvers/gallery.d.ts +12 -0
- package/dist/widgets/resolvers/gallery.js +33 -0
- package/dist/widgets/resolvers/index.d.ts +1 -0
- package/dist/widgets/resolvers/index.js +1 -0
- package/dist/widgets/resolvers/menu.d.ts +8 -0
- package/dist/widgets/resolvers/menu.js +21 -6
- package/dist/widgets/types.d.ts +3 -1
- package/package.json +51 -6
- package/dist/http/settings/cms-adapter.d.ts +0 -20
- package/dist/http/settings/cms-adapter.js +0 -124
- package/dist/http/settings/contracts.d.ts +0 -135
- package/dist/http/settings/contracts.js +0 -2
- package/dist/http/settings/http.d.ts +0 -10
- package/dist/http/settings/http.js +0 -211
- package/dist/http/settings/index.d.ts +0 -5
- package/dist/http/settings/index.js +0 -4
- package/dist/http/settings/shared.d.ts +0 -68
- package/dist/http/settings/shared.js +0 -54
- package/dist/http/settings/stream.d.ts +0 -24
- package/dist/http/settings/stream.js +0 -147
- package/dist/settings/agent-tools.d.ts +0 -10
- package/dist/settings/agent-tools.js +0 -204
- package/dist/settings/agent-value-write-tools.d.ts +0 -22
- package/dist/settings/agent-value-write-tools.js +0 -154
- package/dist/settings/agent-writable-preferences.d.ts +0 -67
- package/dist/settings/agent-writable-preferences.js +0 -132
- package/dist/settings/agent-write-denylist.d.ts +0 -19
- package/dist/settings/agent-write-denylist.js +0 -15
- package/dist/settings/change-feed.d.ts +0 -83
- package/dist/settings/change-feed.js +0 -61
- package/dist/settings/definitions-dispatch.d.ts +0 -56
- package/dist/settings/definitions-dispatch.js +0 -86
- package/dist/settings/dictionaries/index.d.ts +0 -48
- package/dist/settings/dictionaries/index.js +0 -69
- package/dist/settings/dictionaries/settings-dialog.ar.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.ar.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.bn.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.bn.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.de.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.de.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.es.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.es.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.fa.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.fa.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.fr.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.fr.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.hi.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.hi.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.hu.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.hu.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.id.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.id.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.it.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.it.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.ja.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.ja.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.ko.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.ko.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.pl.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.pl.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.pt-BR.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.pt-BR.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.ru.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.ru.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.th.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.th.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.tr.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.tr.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.uk.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.uk.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.ur.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.ur.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.zh-CN.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.zh-CN.js +0 -27
- package/dist/settings/dictionaries/settings-dialog.zh-TW.d.ts +0 -3
- package/dist/settings/dictionaries/settings-dialog.zh-TW.js +0 -27
- package/dist/settings/ensure-definitions.d.ts +0 -88
- package/dist/settings/ensure-definitions.js +0 -91
- package/dist/settings/errors.d.ts +0 -42
- package/dist/settings/errors.js +0 -46
- package/dist/settings/express/cms-adapter.d.ts +0 -20
- package/dist/settings/express/cms-adapter.js +0 -124
- package/dist/settings/express/contracts.d.ts +0 -135
- package/dist/settings/express/contracts.js +0 -2
- package/dist/settings/express/http.d.ts +0 -10
- package/dist/settings/express/http.js +0 -211
- package/dist/settings/express/shared.d.ts +0 -68
- package/dist/settings/express/shared.js +0 -54
- package/dist/settings/express/stream.d.ts +0 -24
- package/dist/settings/express/stream.js +0 -147
- package/dist/settings/ports.d.ts +0 -118
- package/dist/settings/principal-lookup.d.ts +0 -9
- package/dist/settings/principal-lookup.js +0 -2
- package/dist/settings/purge-service.d.ts +0 -54
- package/dist/settings/purge-service.js +0 -106
- package/dist/settings/repo.memory.d.ts +0 -119
- package/dist/settings/repo.memory.js +0 -199
- package/dist/settings/settings.d.ts +0 -139
- package/dist/settings/settings.js +0 -259
- package/dist/settings/tool-registrations.d.ts +0 -51
- package/dist/settings/tool-registrations.js +0 -250
- package/dist/settings/types.d.ts +0 -125
- package/dist/settings/types.js +0 -3
- package/dist/settings/ui-tab-definitions.d.ts +0 -41
- package/dist/settings/ui-tab-definitions.js +0 -148
- package/dist/settings/write-service.d.ts +0 -320
- package/dist/settings/write-service.js +0 -683
- /package/dist/{settings → routing}/ports.js +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { isNonArrayRecord } from "@jini-ai/core";
|
|
2
|
+
import { resolveGalleryEmbeds } from "./gallery-resolver.js";
|
|
3
|
+
import { isPlausibleMediaRefId } from "./markers/media-reference.js";
|
|
2
4
|
import { resolveTaxonomyEmbeds } from "./resolvers/taxonomy.js";
|
|
3
5
|
import { parseWidgetAreaPayload, parseWidgetInstancePayload } from "./entry-payload.js";
|
|
4
6
|
import { resolveWidgetType } from "./resolvers/index.js";
|
|
@@ -27,7 +29,7 @@ function collectWidgetEmbeds(root, out) {
|
|
|
27
29
|
if (!isNonArrayRecord(candidate))
|
|
28
30
|
continue;
|
|
29
31
|
if (isNonArrayRecord(attrs) && typeof attrs.value.widgetEntryId === "string" && typeof attrs.value.placementId === "string") {
|
|
30
|
-
out.push({ placementId: attrs.value.placementId, widgetEntryId: attrs.value.widgetEntryId });
|
|
32
|
+
out.push({ placementId: attrs.value.placementId, widgetEntryId: attrs.value.widgetEntryId, ...(isNonArrayRecord({ value: attrs.value.content }) ? { content: attrs.value.content } : {}) });
|
|
31
33
|
}
|
|
32
34
|
if (Array.isArray(candidate.value.content))
|
|
33
35
|
stack.push(candidate.value.content);
|
|
@@ -82,9 +84,19 @@ function groupWidgetInstancesByType(widgetRows, referencedIds) {
|
|
|
82
84
|
}
|
|
83
85
|
return byType;
|
|
84
86
|
}
|
|
85
|
-
async function resolveWidgetInstances(deps, workspaceId, referencedIds, context) {
|
|
87
|
+
async function resolveWidgetInstances(deps, workspaceId, referencedIds, context, overrides = [], originalIds = referencedIds) {
|
|
86
88
|
const widgetRows = referencedIds.size > 0 ? await deps.entryRepo.listByWorkspace({ workspaceId }, { type: WIDGET_CONTENT_TYPE }) : [];
|
|
87
|
-
const byType = groupWidgetInstancesByType(widgetRows,
|
|
89
|
+
const byType = groupWidgetInstancesByType(widgetRows, originalIds);
|
|
90
|
+
const rowsById = new Map(widgetRows.map(row => [row.id, row]));
|
|
91
|
+
for (const override of overrides) {
|
|
92
|
+
const row = rowsById.get(override.widgetEntryId);
|
|
93
|
+
const saved = row ? parseWidgetInstanceRow(row) : null;
|
|
94
|
+
if (!saved)
|
|
95
|
+
continue;
|
|
96
|
+
const instances = byType.get(saved.widgetType) ?? [];
|
|
97
|
+
instances.push({ ...saved, id: override.key, config: { ...saved.config, ...override.content } });
|
|
98
|
+
byType.set(saved.widgetType, instances);
|
|
99
|
+
}
|
|
88
100
|
const resolvedById = new Map();
|
|
89
101
|
for (const [typeKey, instances] of byType) {
|
|
90
102
|
const results = await resolveWidgetType({ typeKey, instances, context });
|
|
@@ -154,21 +166,27 @@ export async function resolvePageWidgets(required, _optional = {}) {
|
|
|
154
166
|
// handles them as "unresolved", not a crash), at most one `resolveWidgetType` call per distinct
|
|
155
167
|
// type present (REQ-24) — see `resolveWidgetInstances`'s own doc for why this is shared rather
|
|
156
168
|
// than inlined here.
|
|
157
|
-
const
|
|
169
|
+
const allPlacements = [...inlineEmbeds, ...[...regionPlacements.values()].flat()];
|
|
170
|
+
const originalIds = new Set(allPlacements.filter(placement => !placement.content).map(placement => placement.widgetEntryId));
|
|
171
|
+
const resolvedById = await resolveWidgetInstances(deps, input.workspaceId, referencedIds, context, inlineContentOverrides(allPlacements), originalIds);
|
|
158
172
|
// 6. Assemble region -> IR[] (missing/failed placements degrade to the REQ-28 placeholder).
|
|
159
173
|
const regions = {};
|
|
160
174
|
for (const [regionKey, placements] of regionPlacements) {
|
|
161
|
-
regions[regionKey] = placements.map((placement) => toRenderIr(resolvedById.get(placement.widgetEntryId)));
|
|
175
|
+
regions[regionKey] = placements.map((placement) => toRenderIr(resolvedById.get(placement.content ? placement.placementId : placement.widgetEntryId)));
|
|
162
176
|
}
|
|
163
177
|
// 7. Assemble placementId -> IR for inline embeds.
|
|
164
178
|
return { regions, inlineResolved: assembleInlineResolved(inlineEmbeds, resolvedById) };
|
|
165
179
|
}
|
|
180
|
+
/** Placement config merges before asset resolution; a repeated widget never shares overridden IR. */
|
|
181
|
+
function inlineContentOverrides(embeds) {
|
|
182
|
+
return embeds.flatMap(embed => embed.content ? [{ key: embed.placementId, widgetEntryId: embed.widgetEntryId, content: embed.content }] : []);
|
|
183
|
+
}
|
|
166
184
|
/** placementId -> IR for every inline embed; a missing/trashed/failed widget degrades to the REQ-28
|
|
167
185
|
* placeholder IR. Shared by {@link resolvePageWidgets} and {@link resolvePostContentWidgetContext}. */
|
|
168
186
|
function assembleInlineResolved(inlineEmbeds, resolvedById) {
|
|
169
187
|
const inlineResolved = new Map();
|
|
170
188
|
for (const embed of inlineEmbeds) {
|
|
171
|
-
inlineResolved.set(embed.placementId, toRenderIr(resolvedById.get(embed.widgetEntryId)));
|
|
189
|
+
inlineResolved.set(embed.placementId, toRenderIr(resolvedById.get(embed.content ? embed.placementId : embed.widgetEntryId)));
|
|
172
190
|
}
|
|
173
191
|
return inlineResolved;
|
|
174
192
|
}
|
|
@@ -270,8 +288,22 @@ async function resolveWidgetTypeEmbeds(refs, deps, context) {
|
|
|
270
288
|
const slugRefs = refs.filter((ref) => ref.id === null && ref.slug !== null);
|
|
271
289
|
const idBySlug = await resolveWidgetSlugsToIds(slugRefs, deps, context);
|
|
272
290
|
const ids = collectWidgetIdsToLoad(refs, idBySlug, context);
|
|
273
|
-
const
|
|
274
|
-
|
|
291
|
+
const overrides = refs.flatMap(ref => {
|
|
292
|
+
const content = ref.config?.content;
|
|
293
|
+
const widgetEntryId = ref.id ?? (ref.slug ? idBySlug.get(ref.slug) : undefined);
|
|
294
|
+
return widgetEntryId && ref.placementKey && isNonArrayRecord({ value: content })
|
|
295
|
+
? [{ key: ref.placementKey, widgetEntryId, content: content }] : [];
|
|
296
|
+
});
|
|
297
|
+
const originalIds = new Set(refs.filter(ref => !ref.placementKey).flatMap(ref => {
|
|
298
|
+
const id = ref.id ?? (ref.slug ? idBySlug.get(ref.slug) : undefined);
|
|
299
|
+
return id ? [id] : [];
|
|
300
|
+
}));
|
|
301
|
+
const resolvedRaw = await resolveWidgetInstances(deps, context.workspaceId, ids, context, overrides, originalIds);
|
|
302
|
+
const resolved = buildResolvedWidgetMap(refs, idBySlug, resolvedRaw);
|
|
303
|
+
for (const override of overrides)
|
|
304
|
+
if (resolvedRaw.has(override.key))
|
|
305
|
+
resolved.set(override.key, toRenderIr(resolvedRaw.get(override.key)));
|
|
306
|
+
return resolved;
|
|
275
307
|
}
|
|
276
308
|
/** A form slug/id is a synthetic contact-form instance, resolved through the widget pipeline. */
|
|
277
309
|
async function resolveFormTypeEmbeds(refs, _deps, context) {
|
|
@@ -283,19 +315,6 @@ async function resolveFormTypeEmbeds(refs, _deps, context) {
|
|
|
283
315
|
});
|
|
284
316
|
return new Map(keys.map((key) => [key, toRenderIr(results.get(key))]));
|
|
285
317
|
}
|
|
286
|
-
/** Mirrors `render.ts`'s own `MAX_MEDIA_REF_ID_LENGTH`/`PLAUSIBLE_MEDIA_REF_ID_PATTERN` shape check
|
|
287
|
-
* for an `assetId`/`transformName` that will eventually become a `/m/` URL path segment —
|
|
288
|
-
* duplicated here rather than imported for the same reason `core/entry-refs/extractor.ts`
|
|
289
|
-
* duplicates `html-embeds.ts`'s scan pattern instead of importing it: `widgets/` (this file) must
|
|
290
|
-
* not depend on `server/http/site/render.ts`, which sits above it in this codebase's layering. Both
|
|
291
|
-
* copies guard the SAME hazard — this copy stops a malformed id/name from being used as a
|
|
292
|
-
* repo-lookup key at resolve time; `render.ts`'s copy (`renderWidgetMediaImage`) re-checks
|
|
293
|
-
* defense-in-depth immediately before URL templating. */
|
|
294
|
-
const MAX_MEDIA_REF_ID_LENGTH = 200;
|
|
295
|
-
const PLAUSIBLE_MEDIA_REF_ID_PATTERN = /^[^\s/]+$/;
|
|
296
|
-
function isPlausibleMediaRefId(value) {
|
|
297
|
-
return value.length > 0 && value.length <= MAX_MEDIA_REF_ID_LENGTH && PLAUSIBLE_MEDIA_REF_ID_PATTERN.test(value);
|
|
298
|
-
}
|
|
299
318
|
/** The IR's `slug` for a resolved asset: `null` means "has none" (`MediaAssetRenderMeta`'s own
|
|
300
319
|
* contract), and a pre-backfill row stores that as `""`, so an empty slug is normalized to `null`
|
|
301
320
|
* rather than passed through. Shared by every media IR builder in this file. */
|
|
@@ -318,7 +337,7 @@ function mediaIrSlug(record) {
|
|
|
318
337
|
function parseMediaEmbedRef(ref, context, host) {
|
|
319
338
|
const assetId = ref.id ?? ref.slug;
|
|
320
339
|
const transformName = ref.variant ?? host.media.publicTransformName;
|
|
321
|
-
if (assetId === null || !isPlausibleMediaRefId(assetId) || !isPlausibleMediaRefId(transformName)) {
|
|
340
|
+
if (assetId === null || !isPlausibleMediaRefId({ value: assetId }) || !isPlausibleMediaRefId({ value: transformName })) {
|
|
322
341
|
console.warn('[widgets] resolveHtmlPageEmbeds: unresolved "media" reference — missing or invalid "id"/"slug"/"variant" in data-embed-config', { workspaceId: context.workspaceId });
|
|
323
342
|
return undefined;
|
|
324
343
|
}
|
|
@@ -378,8 +397,10 @@ async function resolveOneMediaEmbed(ref, mediaRepo, transformRepo, mediaContentT
|
|
|
378
397
|
// previously-rendered HTML — that used to be `record.id`'s whole reason for being the emitted
|
|
379
398
|
// value; now it is `media_slug_history` (S2a/S2b) keeping the OLD slug resolving too.
|
|
380
399
|
const { assetId: refAssetId, transformName } = parsed;
|
|
381
|
-
const record =
|
|
382
|
-
|
|
400
|
+
const record = ref.id === null && ref.slug !== null
|
|
401
|
+
? await mediaRepo.findBySlug({ workspaceId: context.workspaceId, slug: ref.slug })
|
|
402
|
+
: await host.media.findMediaByIdOrSlug({ deps: { mediaRepo }, input: { workspaceId: context.workspaceId, idOrSlug: refAssetId } });
|
|
403
|
+
if (!record || record.status !== "active") {
|
|
383
404
|
console.warn('[widgets] resolveHtmlPageEmbeds: unresolved "media" reference — no such asset', {
|
|
384
405
|
workspaceId: context.workspaceId,
|
|
385
406
|
assetId: refAssetId,
|
|
@@ -390,8 +411,8 @@ async function resolveOneMediaEmbed(ref, mediaRepo, transformRepo, mediaContentT
|
|
|
390
411
|
const contentType = mediaContentTypeStore
|
|
391
412
|
? (await mediaContentTypeStore.getMany({ workspaceId: context.workspaceId, sha256s: [record.source.sha256] })).get(record.source.sha256)
|
|
392
413
|
: undefined;
|
|
393
|
-
if (contentType?.startsWith("video/")) {
|
|
394
|
-
// Video
|
|
414
|
+
if (contentType?.startsWith("video/") || contentType?.startsWith("audio/")) {
|
|
415
|
+
// Video/audio need no image transform lookup: the shared native-player renderer points at
|
|
395
416
|
// `/m/{assetId}/original` (the byte-passthrough route — see that route's own doc for why
|
|
396
417
|
// video can't go through the image-transform pipeline below), so `transformName`/`version`
|
|
397
418
|
// would be unused props for this IR.
|
|
@@ -431,6 +452,10 @@ async function resolveMediaTypeEmbeds(refs, deps, context) {
|
|
|
431
452
|
}
|
|
432
453
|
return resolved;
|
|
433
454
|
}
|
|
455
|
+
/** Gallery widget adapter over the same media owner used by gallery HTML embeds. */
|
|
456
|
+
export function resolveGalleryWidgetMedia({ refs, deps, context }, _optional = {}) {
|
|
457
|
+
return resolveMediaTypeEmbeds(refs, deps, context);
|
|
458
|
+
}
|
|
434
459
|
/** Scans a TipTap-shaped `bodyJson` tree for every ref-based `image`/`media` node's `assetId`
|
|
435
460
|
* (ADR-027 §4's `{assetId, transformName}` shape) — both the legacy image-only ref shape and the
|
|
436
461
|
* generic `media` node (2026-09-11, `render.ts`'s `renderDocMedia`'s own doc) share this collection,
|
|
@@ -586,7 +611,7 @@ async function resolvePostContentWidgetContext(deps, bodyJson, context) {
|
|
|
586
611
|
if (inlineEmbeds.length === 0)
|
|
587
612
|
return { inlineWidgets: {} };
|
|
588
613
|
const referencedIds = new Set(inlineEmbeds.map((embed) => embed.widgetEntryId));
|
|
589
|
-
const resolvedById = await resolveWidgetInstances(deps, context.workspaceId, referencedIds, context);
|
|
614
|
+
const resolvedById = await resolveWidgetInstances(deps, context.workspaceId, referencedIds, context, inlineContentOverrides(inlineEmbeds), new Set(inlineEmbeds.filter(embed => !embed.content).map(embed => embed.widgetEntryId)));
|
|
590
615
|
const inlineResolved = assembleInlineResolved(inlineEmbeds, resolvedById);
|
|
591
616
|
return { inlineWidgets: Object.fromEntries(inlineResolved) };
|
|
592
617
|
}
|
|
@@ -845,7 +870,22 @@ export const HTML_EMBED_RESOLVERS = {
|
|
|
845
870
|
widget: resolveWidgetTypeEmbeds,
|
|
846
871
|
form: resolveFormTypeEmbeds,
|
|
847
872
|
taxonomy: (refs, deps) => resolveTaxonomyEmbeds({ refs, deps }),
|
|
848
|
-
media:
|
|
873
|
+
media: async (refs, deps, context) => {
|
|
874
|
+
const inline = refs.filter(ref => ref.config?.content !== undefined || ref.config?.items !== undefined || ref.config?.html !== undefined);
|
|
875
|
+
const ordinary = refs.filter(ref => !inline.includes(ref));
|
|
876
|
+
const media = await resolveMediaTypeEmbeds(ordinary, deps, context);
|
|
877
|
+
const content = await resolveGalleryEmbeds({
|
|
878
|
+
refs: inline.map(ref => ({ ...ref, config: (ref.config?.content ?? ref.config) })),
|
|
879
|
+
resolveMedia: ({ refs }) => resolveMediaTypeEmbeds(refs, deps, context),
|
|
880
|
+
}, { reportIssue: ({ key, messages }) => console.warn('[widgets] unresolved media content', { key, messages }) });
|
|
881
|
+
return new Map([...media, ...content]);
|
|
882
|
+
},
|
|
883
|
+
gallery: (refs, deps, context) => resolveGalleryEmbeds({
|
|
884
|
+
refs,
|
|
885
|
+
resolveMedia: ({ refs: mediaRefs }) => resolveMediaTypeEmbeds(mediaRefs, deps, context),
|
|
886
|
+
}, {
|
|
887
|
+
reportIssue: ({ key, messages }) => console.warn('[widgets] unresolved gallery embed', { workspaceId: context.workspaceId, key, messages }),
|
|
888
|
+
}),
|
|
849
889
|
post: resolvePostTypeEmbeds,
|
|
850
890
|
content: resolveContentTypeEmbeds,
|
|
851
891
|
};
|
|
@@ -898,8 +938,12 @@ export const HTML_EMBED_RESOLVERS = {
|
|
|
898
938
|
*
|
|
899
939
|
* `"featured-image"` (2026-10-05) joins for the identical reason: `static-render.ts`'s
|
|
900
940
|
* `injectFeaturedImage` resolves it (see `FEATURED_IMAGE_MARKER_TYPE`, `core/embeds/marker.ts`).
|
|
941
|
+
*
|
|
942
|
+
* `"post-author"` (2026-10-11) joins for the identical reason: `static-render.ts`'s
|
|
943
|
+
* `injectPostAuthor` fills the byline. Without it this stage warned once per post render while the
|
|
944
|
+
* byline rendered correctly.
|
|
901
945
|
*/
|
|
902
|
-
export const THEME_OWNED_MARKER_TYPES = new Set(["partial", "menu", "post-previews", "collection", "featured-image"]);
|
|
946
|
+
export const THEME_OWNED_MARKER_TYPES = new Set(["partial", "menu", "post-previews", "collection", "featured-image", "post-author"]);
|
|
903
947
|
/**
|
|
904
948
|
* `isPageEmbedType` (the "does this stage OWN the marker type" question, with its own doc) lives in
|
|
905
949
|
* the dependency-free `page-embed-types.ts` so `render.ts` and the theme render workers can ask it
|
|
@@ -2,6 +2,7 @@ import type { EntryDisplayListPort, EntryListExcludingTypesPort, CollectionListP
|
|
|
2
2
|
import type { EntryListPort } from "../../entries/index.js";
|
|
3
3
|
import type { NavMenuReadModel, ResolveTargetHrefFn } from "../../navigation/index.js";
|
|
4
4
|
import type { WidgetResolver, WidgetTypeKey } from "../types.js";
|
|
5
|
+
import { createGalleryWidgetResolver } from "./gallery.js";
|
|
5
6
|
import { type ContentTypeLookup } from "./recent-entries.js";
|
|
6
7
|
/**
|
|
7
8
|
* @file Assembles the real, DI'd v1 dynamic resolvers against real infrastructure deps.
|
|
@@ -14,6 +15,8 @@ import { type ContentTypeLookup } from "./recent-entries.js";
|
|
|
14
15
|
* test would otherwise need to construct.
|
|
15
16
|
*/
|
|
16
17
|
export interface CoreResolverDeps {
|
|
18
|
+
/** Media remains host-owned. Older hosts degrade nonempty galleries until this port is wired. */
|
|
19
|
+
galleryMedia?: Parameters<typeof createGalleryWidgetResolver>[0]["resolveMedia"];
|
|
17
20
|
menus: MenusPort;
|
|
18
21
|
collectionList: CollectionListPort;
|
|
19
22
|
resolveMenuTargetHref?: ResolveTargetHrefFn;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { createGalleryWidgetResolver } from "./gallery.js";
|
|
1
2
|
import { createContactFormResolver } from "./contact-form.js";
|
|
2
3
|
import { createMenuResolver } from "./menu.js";
|
|
3
4
|
import { createRecentEntriesResolver } from "./recent-entries.js";
|
|
4
5
|
export function createCoreResolvers(deps, _optional = {}) {
|
|
5
6
|
return {
|
|
7
|
+
gallery: createGalleryWidgetResolver({ resolveMedia: deps.galleryMedia ?? (async () => new Map()) }),
|
|
6
8
|
"recent-entries": createRecentEntriesResolver({ collectionList: deps.collectionList, entryList: deps.entryList, contentTypes: deps.contentTypes }),
|
|
7
9
|
menu: createMenuResolver({ menus: deps.menus, navMenuReadModel: deps.navMenuReadModel, ...(deps.resolveMenuTargetHref === undefined ? {} : { resolveTargetHref: deps.resolveMenuTargetHref }), publicOnly: true }),
|
|
8
10
|
"contact-form": createContactFormResolver({ formDefinitionRepo: deps.formDefinitionRepo }),
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WidgetResolver } from '../types.js';
|
|
2
|
+
import type { GalleryMediaResolver } from '../gallery-resolver.js';
|
|
3
|
+
/** Adapt gallery widget instances to the existing embed resolver; one deduplicated media batch.
|
|
4
|
+
* Empty starter widgets render a note, while malformed stored configs degrade per instance.
|
|
5
|
+
* @complexity O(w*i) time/space, with i<=50 images and a shared 50-asset budget.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createGalleryWidgetResolver({ resolveMedia }: {
|
|
8
|
+
resolveMedia: (required: Parameters<GalleryMediaResolver>[0] & {
|
|
9
|
+
context: Parameters<WidgetResolver['resolveMany']>[1];
|
|
10
|
+
}, optional?: Record<string, never>) => ReturnType<GalleryMediaResolver>;
|
|
11
|
+
}, _optional?: Record<string, never>): WidgetResolver;
|
|
12
|
+
//# sourceMappingURL=gallery.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { validateWidgetConfig } from '../config-validation.js';
|
|
2
|
+
import { getWidgetTypeRegistration } from '../registry.js';
|
|
3
|
+
import { resolveGalleryEmbeds } from '../gallery-resolver.js';
|
|
4
|
+
import { galleryMediaReferences, parseGalleryConfig } from '../markers/gallery.js';
|
|
5
|
+
/** Adapt gallery widget instances to the existing embed resolver; one deduplicated media batch.
|
|
6
|
+
* Empty starter widgets render a note, while malformed stored configs degrade per instance.
|
|
7
|
+
* @complexity O(w*i) time/space, with i<=50 images and a shared 50-asset budget.
|
|
8
|
+
*/
|
|
9
|
+
export function createGalleryWidgetResolver({ resolveMedia }, _optional = {}) {
|
|
10
|
+
return { async resolveMany(instances, context) {
|
|
11
|
+
const refs = [];
|
|
12
|
+
const results = new Map();
|
|
13
|
+
for (const instance of instances) {
|
|
14
|
+
const parsed = parseGalleryConfig({ config: instance.config }, { allowEmpty: true });
|
|
15
|
+
if (!parsed.config || !validateWidgetConfig({ schema: getWidgetTypeRegistration({ typeKey: instance.widgetType === 'media' ? 'media' : 'gallery' }).configSchema, config: instance.config }).valid) {
|
|
16
|
+
results.set(instance.id, { ok: false, reason: 'resolver-error' });
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (parsed.config.images.length === 0 && parsed.config.mode !== 'html') {
|
|
20
|
+
results.set(instance.id, { ok: true, ir: { componentId: instance.widgetType === 'media' ? 'media' : 'gallery', props: { config: instance.config, media: {} } }, dependencyKeys: [instance.id] });
|
|
21
|
+
}
|
|
22
|
+
else
|
|
23
|
+
refs.push({ type: instance.widgetType, id: instance.id, slug: null, name: null, variant: null, header: true, config: instance.config });
|
|
24
|
+
}
|
|
25
|
+
const resolved = await resolveGalleryEmbeds({ refs, resolveMedia: ({ refs }) => resolveMedia({ refs, context }) });
|
|
26
|
+
for (const ref of refs) {
|
|
27
|
+
const ir = resolved.get(ref.id);
|
|
28
|
+
results.set(ref.id, ir ? { ok: true, ir, dependencyKeys: [ref.id, ...galleryMediaReferences({ config: ref.config }).map(image => image.mediaId)] } : { ok: false, reason: 'resolver-error' });
|
|
29
|
+
}
|
|
30
|
+
return results;
|
|
31
|
+
} };
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=gallery.js.map
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import type { UUID } from "@jini-ai/core/primitives";
|
|
22
22
|
import { type CoreResolverDeps } from "./create-core-resolvers.js";
|
|
23
23
|
import type { WidgetInstanceView, WidgetResolveContext, WidgetResolveResult, WidgetResolver, WidgetTypeKey } from "../types.js";
|
|
24
|
+
export { createGalleryWidgetResolver } from "./gallery.js";
|
|
24
25
|
export { createRecentEntriesResolver } from "./recent-entries.js";
|
|
25
26
|
export { createMenuResolver } from "./menu.js";
|
|
26
27
|
export { createContactFormResolver } from "./contact-form.js";
|
|
@@ -6,6 +6,7 @@ import { createCoreResolvers } from "./create-core-resolvers.js";
|
|
|
6
6
|
// isolation (2026-08-18 widgets/resolvers deep-import triage), so each factory is re-exported here
|
|
7
7
|
// narrowly, one line per consumer:
|
|
8
8
|
// `createRecentEntriesResolver` — `widgets/__tests__/unit/resolvers-recent-entries.unit.test.ts`.
|
|
9
|
+
export { createGalleryWidgetResolver } from "./gallery.js";
|
|
9
10
|
export { createRecentEntriesResolver } from "./recent-entries.js";
|
|
10
11
|
// `createMenuResolver` — `widgets/__tests__/unit/resolvers-menu.unit.test.ts`.
|
|
11
12
|
export { createMenuResolver } from "./menu.js";
|
|
@@ -38,5 +38,13 @@ export interface MenuResolverDeps {
|
|
|
38
38
|
/** Host routing injection; callers without it retain the diagnostic placeholder documented above. */
|
|
39
39
|
resolveTargetHref?: ResolveTargetHrefFn;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve menu widgets through navigation's read model: stored HTML or resolved items by doc mode.
|
|
43
|
+
* HTML is trusted and balanced by navigation's save path (`mergeMenuHtml`), like static menus;
|
|
44
|
+
* forwarding it adds no second parser or sanitization policy. Read failures propagate to dispatch.
|
|
45
|
+
* @param deps - Navigation read/resolution ports and host routing/public-filter policy.
|
|
46
|
+
* @returns A resolver producing mode-specific menu IR, or existing typed config/target failures.
|
|
47
|
+
* @complexity One read per instance; item resolution/filtering scales with the selected item trees.
|
|
48
|
+
*/
|
|
41
49
|
export declare function createMenuResolver(deps: MenuResolverDeps, _optional?: Record<string, never>): WidgetResolver;
|
|
42
50
|
//# sourceMappingURL=menu.d.ts.map
|
|
@@ -9,6 +9,14 @@ function toMenuItemProps(items, publicOnly) {
|
|
|
9
9
|
}) : items;
|
|
10
10
|
return visible;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolve menu widgets through navigation's read model: stored HTML or resolved items by doc mode.
|
|
14
|
+
* HTML is trusted and balanced by navigation's save path (`mergeMenuHtml`), like static menus;
|
|
15
|
+
* forwarding it adds no second parser or sanitization policy. Read failures propagate to dispatch.
|
|
16
|
+
* @param deps - Navigation read/resolution ports and host routing/public-filter policy.
|
|
17
|
+
* @returns A resolver producing mode-specific menu IR, or existing typed config/target failures.
|
|
18
|
+
* @complexity One read per instance; item resolution/filtering scales with the selected item trees.
|
|
19
|
+
*/
|
|
12
20
|
export function createMenuResolver(deps, _optional = {}) {
|
|
13
21
|
const resolveTargetHref = deps.resolveTargetHref ?? DEFAULT_RESOLVE_TARGET_HREF;
|
|
14
22
|
return {
|
|
@@ -25,14 +33,21 @@ export function createMenuResolver(deps, _optional = {}) {
|
|
|
25
33
|
results.set(instance.id, { ok: false, reason: "target-disabled" });
|
|
26
34
|
continue;
|
|
27
35
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
let props;
|
|
37
|
+
if (menu.doc.mode === "html") {
|
|
38
|
+
props = { title: menu.title, mode: "html", html: menu.doc.html ?? "" };
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const items = await deps.menus.resolveMenuDoc({
|
|
42
|
+
doc: menu.doc,
|
|
43
|
+
context: { workspaceId: context.workspaceId },
|
|
44
|
+
resolveTargetHref,
|
|
45
|
+
});
|
|
46
|
+
props = { title: menu.title, items: toMenuItemProps(items, deps.publicOnly === true) };
|
|
47
|
+
}
|
|
33
48
|
results.set(instance.id, {
|
|
34
49
|
ok: true,
|
|
35
|
-
ir: { componentId: "menu", props
|
|
50
|
+
ir: { componentId: "menu", props },
|
|
36
51
|
dependencyKeys: [menu.id],
|
|
37
52
|
});
|
|
38
53
|
}
|
package/dist/widgets/types.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export declare const WIDGET_AREA_FIELD_NAMESPACE: "widgets";
|
|
|
47
47
|
* a bare `string` so a typo in a registration or a call site is a compile error,
|
|
48
48
|
* not a runtime `unknown-type` surprise.
|
|
49
49
|
*/
|
|
50
|
-
export type WidgetTypeKey = "text" | "social-links" | "recent-entries" | "menu" | "contact-form";
|
|
50
|
+
export type WidgetTypeKey = "text" | "social-links" | "recent-entries" | "menu" | "contact-form" | "gallery" | "media";
|
|
51
51
|
/**
|
|
52
52
|
* A widget type's declared capability class (ADR-047 Debate Fold-In Amendment
|
|
53
53
|
* 3). `static` types render validated config directly with no resolver;
|
|
@@ -121,6 +121,8 @@ export interface WidgetPlacementNode {
|
|
|
121
121
|
readonly placementId: UUID;
|
|
122
122
|
readonly widgetEntryId: UUID;
|
|
123
123
|
readonly enabled: boolean;
|
|
124
|
+
/** Optional per-placement config overlay, resolved before media lookup. */
|
|
125
|
+
readonly content?: JsonObject;
|
|
124
126
|
}
|
|
125
127
|
/** The full region-composition document stored in a `widget_area` entry's `bodyJson`. */
|
|
126
128
|
export interface WidgetAreaDoc {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/cms",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "Content-model capability for Jini-hosted products: framework-free contracts, ports, and domain services in `/core` and per-domain subpaths; Node-bound adapters in domain-specific subpaths.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
".": "universal",
|
|
17
17
|
"./core": "universal",
|
|
18
18
|
"./navigation": "universal",
|
|
19
|
+
"./routing": "universal",
|
|
19
20
|
"./media": "node",
|
|
20
21
|
"./settings": "universal",
|
|
21
22
|
"./workspace": "universal",
|
|
@@ -46,7 +47,12 @@
|
|
|
46
47
|
"./widgets/markers": "universal",
|
|
47
48
|
"./widgets/html": "node",
|
|
48
49
|
"./widgets/resolvers": "universal",
|
|
49
|
-
"./widgets/sql": "node"
|
|
50
|
+
"./widgets/sql": "node",
|
|
51
|
+
"./consent": "universal",
|
|
52
|
+
"./consent/html": "node",
|
|
53
|
+
"./consent/express": "node",
|
|
54
|
+
"./consent/node": "node",
|
|
55
|
+
"./listing": "universal"
|
|
50
56
|
},
|
|
51
57
|
"isolation": {
|
|
52
58
|
"domains": {
|
|
@@ -65,6 +71,9 @@
|
|
|
65
71
|
"navigation": [
|
|
66
72
|
"src/navigation"
|
|
67
73
|
],
|
|
74
|
+
"routing": [
|
|
75
|
+
"src/routing"
|
|
76
|
+
],
|
|
68
77
|
"presentation": [
|
|
69
78
|
"src/presentation"
|
|
70
79
|
],
|
|
@@ -94,6 +103,12 @@
|
|
|
94
103
|
],
|
|
95
104
|
"widgets": [
|
|
96
105
|
"src/widgets"
|
|
106
|
+
],
|
|
107
|
+
"consent": [
|
|
108
|
+
"src/consent"
|
|
109
|
+
],
|
|
110
|
+
"listing": [
|
|
111
|
+
"src/listing"
|
|
97
112
|
]
|
|
98
113
|
},
|
|
99
114
|
"forbidden": {
|
|
@@ -134,6 +149,11 @@
|
|
|
134
149
|
"import": "./dist/navigation/index.js",
|
|
135
150
|
"default": "./dist/navigation/index.js"
|
|
136
151
|
},
|
|
152
|
+
"./routing": {
|
|
153
|
+
"types": "./dist/routing/index.d.ts",
|
|
154
|
+
"import": "./dist/routing/index.js",
|
|
155
|
+
"default": "./dist/routing/index.js"
|
|
156
|
+
},
|
|
137
157
|
"./media": {
|
|
138
158
|
"types": "./dist/media/index.d.ts",
|
|
139
159
|
"import": "./dist/media/index.js",
|
|
@@ -288,6 +308,31 @@
|
|
|
288
308
|
"types": "./dist/widgets/sql/index.d.ts",
|
|
289
309
|
"import": "./dist/widgets/sql/index.js",
|
|
290
310
|
"default": "./dist/widgets/sql/index.js"
|
|
311
|
+
},
|
|
312
|
+
"./consent": {
|
|
313
|
+
"types": "./dist/consent/index.d.ts",
|
|
314
|
+
"import": "./dist/consent/index.js",
|
|
315
|
+
"default": "./dist/consent/index.js"
|
|
316
|
+
},
|
|
317
|
+
"./consent/html": {
|
|
318
|
+
"types": "./dist/consent/html/index.d.ts",
|
|
319
|
+
"import": "./dist/consent/html/index.js",
|
|
320
|
+
"default": "./dist/consent/html/index.js"
|
|
321
|
+
},
|
|
322
|
+
"./consent/express": {
|
|
323
|
+
"types": "./dist/consent/express/index.d.ts",
|
|
324
|
+
"import": "./dist/consent/express/index.js",
|
|
325
|
+
"default": "./dist/consent/express/index.js"
|
|
326
|
+
},
|
|
327
|
+
"./consent/node": {
|
|
328
|
+
"types": "./dist/consent/node/index.d.ts",
|
|
329
|
+
"import": "./dist/consent/node/index.js",
|
|
330
|
+
"default": "./dist/consent/node/index.js"
|
|
331
|
+
},
|
|
332
|
+
"./listing": {
|
|
333
|
+
"types": "./dist/listing/index.d.ts",
|
|
334
|
+
"import": "./dist/listing/index.js",
|
|
335
|
+
"default": "./dist/listing/index.js"
|
|
291
336
|
}
|
|
292
337
|
},
|
|
293
338
|
"files": [
|
|
@@ -319,18 +364,18 @@
|
|
|
319
364
|
"@types/better-sqlite3": "^7.6.13",
|
|
320
365
|
"@electric-sql/pglite": "0.5.8",
|
|
321
366
|
"jsdom": "^25.0.1",
|
|
322
|
-
"@jini-ai/core": "0.4.2",
|
|
323
367
|
"@jini-ai/http-kit": "0.4.1",
|
|
324
|
-
"@jini-ai/db": "0.3.
|
|
368
|
+
"@jini-ai/db": "0.3.2",
|
|
369
|
+
"@jini-ai/core": "0.4.3"
|
|
325
370
|
},
|
|
326
371
|
"dependencies": {
|
|
327
|
-
"@jini-ai/core": "^0.4.
|
|
372
|
+
"@jini-ai/core": "^0.4.3"
|
|
328
373
|
},
|
|
329
374
|
"peerDependencies": {
|
|
330
375
|
"sharp": "^0.35.3",
|
|
331
376
|
"express": "^4.21.0",
|
|
332
377
|
"kysely": "^0.29.6",
|
|
333
|
-
"@jini-ai/db": "^0.3.
|
|
378
|
+
"@jini-ai/db": "^0.3.2",
|
|
334
379
|
"parse5": "^7.3.0",
|
|
335
380
|
"@jini-ai/http-kit": "^0.4.0"
|
|
336
381
|
},
|
|
@@ -1,20 +0,0 @@
|
|
|
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
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { getEffective, resolveDefinition, set, clear, resetNamespace, registerDefinitions, NON_REGISTER_DEFINITION_OPS, parseNonRegisterDefinitionOp, collectChangedNamespaces, } from "../../settings/index.js";
|
|
2
|
-
/** Keep the CMS write chokepoint while translating its permission vocabulary to host policy. */
|
|
3
|
-
/** Bind the CMS ledger and write chokepoint without copying their domain logic.
|
|
4
|
-
* Effective reads scan definitions and resolve each namespace key; mutations inherit CMS transactions/errors.
|
|
5
|
-
* @complexity O(1) setup. Per operation: O(definitions + keys) reads, or O(batch size) writes.
|
|
6
|
-
* @example createCmsSettingsService({ deps: cmsPorts, permissions });
|
|
7
|
-
*/
|
|
8
|
-
export function createCmsSettingsService(required) {
|
|
9
|
-
const { deps: original, permissions } = required;
|
|
10
|
-
const permissionMap = new Map([
|
|
11
|
-
["settings.definitions.manage", permissions.manageDefinitions],
|
|
12
|
-
["settings.global.write", permissions.writeGlobal],
|
|
13
|
-
["settings.workspace.write", permissions.writeWorkspace],
|
|
14
|
-
["settings.user.self.write", permissions.writeUserSelf],
|
|
15
|
-
["settings.user.write", permissions.writeUserOther],
|
|
16
|
-
["settings.reset.global", permissions.reset.global],
|
|
17
|
-
["settings.reset.workspace", permissions.reset.workspace],
|
|
18
|
-
["settings.reset.user", permissions.reset.user],
|
|
19
|
-
]);
|
|
20
|
-
const deps = { ...original,
|
|
21
|
-
authorize: (input) => {
|
|
22
|
-
const permission = permissionMap.get(input.permission);
|
|
23
|
-
if (permission === undefined)
|
|
24
|
-
return Promise.resolve({ allowed: false, reason: "unknown_settings_permission" });
|
|
25
|
-
return original.authorize({ ...input, permission });
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
const repo = deps.repo;
|
|
29
|
-
const listDefinitions = async ({ workspaceId }) => {
|
|
30
|
-
const [platform, workspace] = await Promise.all([
|
|
31
|
-
repo.listActiveDefinitions({ workspaceId: null }), repo.listActiveDefinitions({ workspaceId }),
|
|
32
|
-
]);
|
|
33
|
-
return [...platform, ...workspace];
|
|
34
|
-
};
|
|
35
|
-
return {
|
|
36
|
-
listDefinitions,
|
|
37
|
-
effective: async ({ namespace, workspaceId, principalId }) => {
|
|
38
|
-
const defs = await listDefinitions({ workspaceId });
|
|
39
|
-
const keys = new Set(defs.filter((d) => d.namespace === namespace).map((d) => d.key));
|
|
40
|
-
const data = [];
|
|
41
|
-
for (const key of keys) {
|
|
42
|
-
const result = await getEffective({ repo }, { namespace, key, scopeContext: { workspaceId, principalId } });
|
|
43
|
-
if (result)
|
|
44
|
-
data.push({ key, value: result.value, sourceLayer: result.sourceLayer, defVersion: result.defVersion });
|
|
45
|
-
}
|
|
46
|
-
return data;
|
|
47
|
-
},
|
|
48
|
-
raw: async ({ namespace, key, workspaceId, principalId }) => {
|
|
49
|
-
const definition = await resolveDefinition({ repo }, { namespace, key, workspaceId });
|
|
50
|
-
if (!definition)
|
|
51
|
-
return null;
|
|
52
|
-
const settingId = definition.settingId;
|
|
53
|
-
const records = await Promise.all([
|
|
54
|
-
repo.getGlobalValue(settingId), repo.getWorkspaceValue({ workspaceId, settingId }),
|
|
55
|
-
principalId ? repo.getUserValue({ workspaceId, principalId, settingId }) : Promise.resolve(null),
|
|
56
|
-
]);
|
|
57
|
-
const values = records.map((r) => r?.state === "set" ? r.valueJson : null);
|
|
58
|
-
return { key: `${namespace}.${key}`, global: values[0], workspace: values[1], user: values[2], default: definition.defaultValue };
|
|
59
|
-
},
|
|
60
|
-
set: (input) => set({ deps, input }),
|
|
61
|
-
clear: (input) => clear({ deps, input }),
|
|
62
|
-
reset: async (input) => {
|
|
63
|
-
const definitions = await repo.listActiveDefinitions({ workspaceId: input.scope === "global" ? null : (input.workspaceId ?? input.authWorkspaceId ?? null) });
|
|
64
|
-
const keys = definitions.filter((d) => d.namespace === input.namespace).map((d) => d.key);
|
|
65
|
-
return resetNamespace({ deps, input }, keys);
|
|
66
|
-
},
|
|
67
|
-
definitions: ({ items, callerPrincipalId, authWorkspaceId }) => applyDefinitions({ deps, items, callerPrincipalId, authWorkspaceId }),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
/** Preserve register batching and ordered non-register writes over existing CMS operation contracts.
|
|
71
|
-
* Unknown operations return their name. Earlier non-register writes are not rolled back by later failures.
|
|
72
|
-
* @complexity O(item count) time/space plus CMS effects; this batch is not atomic.
|
|
73
|
-
*/
|
|
74
|
-
async function applyDefinitions(required) {
|
|
75
|
-
const { deps, items, callerPrincipalId, authWorkspaceId } = required;
|
|
76
|
-
const toRegister = [];
|
|
77
|
-
const applied = [];
|
|
78
|
-
for (const raw of items) {
|
|
79
|
-
const item = raw;
|
|
80
|
-
const op = String(item.op ?? "register");
|
|
81
|
-
const namespace = String(item.namespace ?? "");
|
|
82
|
-
const key = String(item.key ?? "");
|
|
83
|
-
const ownerKind = item.ownerKind;
|
|
84
|
-
const workspaceId = ownerKind === "site" ? authWorkspaceId : null;
|
|
85
|
-
const defaultValue = (item.defaultJson ?? null);
|
|
86
|
-
if (op === "register") {
|
|
87
|
-
toRegister.push({ namespace, key, ownerKind, workspaceId, schema: item.schemaJson,
|
|
88
|
-
defaultValue, scopes: Number(item.scopes), secret: Boolean(item.secret ?? false) });
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
const parsedOp = parseNonRegisterDefinitionOp({ op });
|
|
92
|
-
if (!parsedOp)
|
|
93
|
-
return { unknownOp: op };
|
|
94
|
-
const opItem = { namespace, key, ownerKind, workspaceId, defaultJson: defaultValue,
|
|
95
|
-
...(item.newNamespace === undefined ? {} : { newNamespace: item.newNamespace }),
|
|
96
|
-
...(item.newKey === undefined ? {} : { newKey: item.newKey }),
|
|
97
|
-
...(item.schemaJson === undefined ? {} : { schemaJson: item.schemaJson }),
|
|
98
|
-
...(item.coercionJson === undefined ? {} : { coercionJson: item.coercionJson }),
|
|
99
|
-
};
|
|
100
|
-
await NON_REGISTER_DEFINITION_OPS[parsedOp]({ ctx: { deps, callerPrincipalId, authWorkspaceId }, item: opItem });
|
|
101
|
-
}
|
|
102
|
-
applied.push({ key: `${namespace}.${key}`, op, status: "applied" });
|
|
103
|
-
}
|
|
104
|
-
if (toRegister.length)
|
|
105
|
-
await registerDefinitions({ deps, input: { definitions: toRegister, callerPrincipalId, authWorkspaceId } });
|
|
106
|
-
return { applied };
|
|
107
|
-
}
|
|
108
|
-
/** Adapt the ledger through CMS tenant/principal disclosure rules, returning names and cursors only.
|
|
109
|
-
* @complexity O(1) setup; collect is O(page size), with namespace lookup per visible revision.
|
|
110
|
-
* @example createCmsSettingsChangeFeed({ repo });
|
|
111
|
-
*/
|
|
112
|
-
export function createCmsSettingsChangeFeed(required) {
|
|
113
|
-
const { repo } = required;
|
|
114
|
-
return {
|
|
115
|
-
head: () => repo.maxRevisionSeq(),
|
|
116
|
-
collect: async ({ sinceSeq, limit, viewer }) => {
|
|
117
|
-
const revisions = await repo.listRevisionsSince({ sinceSeq, limit, workspaceId: viewer.workspaceId });
|
|
118
|
-
// Resolve afresh each batch: a rename must never leave a stale namespace cached forever.
|
|
119
|
-
const batch = await collectChangedNamespaces({ revisions, viewer, resolveNamespace: async ({ settingId }) => (await repo.findDefinitionBySettingId({ settingId }))?.namespace ?? null });
|
|
120
|
-
return { ...batch, examinedCount: revisions.length };
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
//# sourceMappingURL=cms-adapter.js.map
|