@jini-ai/cms 0.5.2 → 0.5.4
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 +15 -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/forms/delete-submission.d.ts +38 -0
- package/dist/forms/delete-submission.js +25 -0
- package/dist/forms/duplicate-slug.d.ts +81 -0
- package/dist/forms/duplicate-slug.js +115 -0
- package/dist/forms/errors.d.ts +53 -0
- package/dist/forms/errors.js +25 -0
- package/dist/forms/express/handler.d.ts +59 -0
- package/dist/forms/express/handler.js +217 -0
- package/dist/forms/express/index.d.ts +2 -0
- package/dist/forms/express/index.js +2 -0
- package/dist/forms/fields-json.d.ts +14 -0
- package/dist/forms/fields-json.js +11 -0
- package/dist/forms/forms.d.ts +59 -0
- package/dist/forms/forms.js +213 -0
- package/dist/forms/html/authoring-adapter.d.ts +34 -0
- package/dist/forms/html/authoring-adapter.js +48 -0
- package/dist/forms/html/html-authoring.d.ts +12 -0
- package/dist/forms/html/html-authoring.js +133 -0
- package/dist/forms/html/html-submission-adapter.d.ts +10 -0
- package/dist/forms/html/html-submission-adapter.js +15 -0
- package/dist/forms/html/index.d.ts +4 -0
- package/dist/forms/html/index.js +4 -0
- package/dist/forms/html-render.d.ts +33 -0
- package/dist/forms/html-render.js +31 -0
- package/dist/forms/index.d.ts +17 -0
- package/dist/forms/index.js +17 -0
- package/dist/forms/notify-subscriber.d.ts +32 -0
- package/dist/forms/notify-subscriber.js +60 -0
- package/dist/forms/ports.d.ts +168 -0
- package/dist/forms/ports.js +2 -0
- package/dist/forms/rate-limit-key.d.ts +8 -0
- package/dist/forms/rate-limit-key.js +7 -0
- package/dist/forms/repo.memory.d.ts +120 -0
- package/dist/forms/repo.memory.js +188 -0
- package/dist/forms/sql/index.d.ts +5 -0
- package/dist/forms/sql/index.js +5 -0
- package/dist/forms/sql/repo.d.ts +94 -0
- package/dist/forms/sql/repo.js +175 -0
- package/dist/forms/sql/repo.rows.d.ts +33 -0
- package/dist/forms/sql/repo.rows.js +64 -0
- package/dist/forms/sql/submission-ip-retention-repo.d.ts +9 -0
- package/dist/forms/sql/submission-ip-retention-repo.js +40 -0
- package/dist/forms/sql/tables.d.ts +32 -0
- package/dist/forms/sql/tables.js +2 -0
- package/dist/forms/submission-attempts.d.ts +70 -0
- package/dist/forms/submission-attempts.js +70 -0
- package/dist/forms/submission-email-validation.d.ts +8 -0
- package/dist/forms/submission-email-validation.js +34 -0
- package/dist/forms/submission-ip-retention.d.ts +30 -0
- package/dist/forms/submission-ip-retention.js +27 -0
- package/dist/forms/submit-service.d.ts +78 -0
- package/dist/forms/submit-service.js +109 -0
- package/dist/forms/types.d.ts +49 -0
- package/dist/forms/types.js +2 -0
- package/dist/forms/write-service.d.ts +83 -0
- package/dist/forms/write-service.js +271 -0
- 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 -12
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { Clock, JsonObject } from "@jini-ai/core/primitives";
|
|
2
|
+
import type { OutboxPort } from "../core/ports.js";
|
|
3
|
+
import type { createEntry, updateEntry, importEntry, toEntryOutbox } from "../entries/index.js";
|
|
4
|
+
import type { registerContentType, toContentTypeOutbox, IndexProvisionerPort } from "../content-types/index.js";
|
|
5
|
+
import type { findMediaByIdOrSlug, getLatestTransformDefinition } from "../media/index.js";
|
|
6
|
+
import type { EntryRefRow } from "./ports.js";
|
|
7
|
+
/** Host-owned post snapshot: only the fields the widgets services read or forward. */
|
|
8
|
+
export interface PostRecord {
|
|
9
|
+
id: string;
|
|
10
|
+
workspaceId: string;
|
|
11
|
+
title: string;
|
|
12
|
+
slug: string;
|
|
13
|
+
bodyJson: JsonObject;
|
|
14
|
+
status: "draft" | "published";
|
|
15
|
+
kind: "post" | "page";
|
|
16
|
+
bodyFormat: "doc" | "html";
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
version: number;
|
|
19
|
+
deletedAt?: string | null;
|
|
20
|
+
publishAt?: string | null;
|
|
21
|
+
templateChoice?: string | null;
|
|
22
|
+
overridesThemePage?: boolean | null;
|
|
23
|
+
ext?: JsonObject;
|
|
24
|
+
}
|
|
25
|
+
/** Host-owned blob content-type lookup; absent sha256 keys mean the blob has not been sniffed yet.
|
|
26
|
+
* Widgets only consumes batch reads; the host retains storage and write ownership. */
|
|
27
|
+
export interface MediaContentTypeStorePort {
|
|
28
|
+
getMany(required: {
|
|
29
|
+
workspaceId: string;
|
|
30
|
+
sha256s: readonly string[];
|
|
31
|
+
}): Promise<Map<string, string>>;
|
|
32
|
+
}
|
|
33
|
+
/** Reads stay host-owned; post writes below receive this same bound repository. */
|
|
34
|
+
export interface PostRepoPort {
|
|
35
|
+
findById(required: {
|
|
36
|
+
workspaceId: string;
|
|
37
|
+
id: string;
|
|
38
|
+
}): Promise<PostRecord | null>;
|
|
39
|
+
findBySlug(required: {
|
|
40
|
+
workspaceId: string;
|
|
41
|
+
slug: string;
|
|
42
|
+
}): Promise<PostRecord | null>;
|
|
43
|
+
}
|
|
44
|
+
export type ForgetRemovedPostFn = (required: {
|
|
45
|
+
workspaceId: string;
|
|
46
|
+
id: string;
|
|
47
|
+
}) => Promise<void>;
|
|
48
|
+
export type BeforeSaveHookPort = (entry: {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly workspaceId: string;
|
|
51
|
+
readonly title: string;
|
|
52
|
+
readonly slug: string;
|
|
53
|
+
readonly status: PostRecord["status"];
|
|
54
|
+
readonly bodyJson: Readonly<Record<string, unknown>>;
|
|
55
|
+
readonly ext: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
56
|
+
}) => Promise<JsonObject>;
|
|
57
|
+
type PostWriteDeps = {
|
|
58
|
+
repo: PostRepoPort;
|
|
59
|
+
clock: Clock;
|
|
60
|
+
outbox: OutboxPort;
|
|
61
|
+
beforeSaveHook?: BeforeSaveHookPort | undefined;
|
|
62
|
+
};
|
|
63
|
+
type PostAttribution = {
|
|
64
|
+
actorId?: string;
|
|
65
|
+
delegatedByWorkspaceId?: string | null | undefined;
|
|
66
|
+
delegatedById?: string | null | undefined;
|
|
67
|
+
};
|
|
68
|
+
/** Required host behavior. No sibling-domain value enters the widgets runtime closure. */
|
|
69
|
+
export interface WidgetHostPorts {
|
|
70
|
+
entries: {
|
|
71
|
+
createEntry: typeof createEntry;
|
|
72
|
+
updateEntry: typeof updateEntry;
|
|
73
|
+
importEntry: typeof importEntry;
|
|
74
|
+
toEntryOutbox: typeof toEntryOutbox;
|
|
75
|
+
isVersionConflict(error: unknown): boolean;
|
|
76
|
+
};
|
|
77
|
+
contentTypes: {
|
|
78
|
+
registerContentType: typeof registerContentType;
|
|
79
|
+
toContentTypeOutbox: typeof toContentTypeOutbox;
|
|
80
|
+
createIndexProvisioner(): IndexProvisionerPort;
|
|
81
|
+
alreadyExists(error: unknown): {
|
|
82
|
+
tombstoned: boolean;
|
|
83
|
+
} | null;
|
|
84
|
+
};
|
|
85
|
+
posts: {
|
|
86
|
+
isTrashed(post: PostRecord): boolean;
|
|
87
|
+
isVersionConflict(error: unknown): error is Error & {
|
|
88
|
+
currentVersion: number;
|
|
89
|
+
};
|
|
90
|
+
isNotFound(error: unknown): boolean;
|
|
91
|
+
updatePost(required: {
|
|
92
|
+
deps: PostWriteDeps;
|
|
93
|
+
input: PostAttribution & {
|
|
94
|
+
workspaceId: string;
|
|
95
|
+
id: string;
|
|
96
|
+
title: string;
|
|
97
|
+
slug: string;
|
|
98
|
+
status: PostRecord["status"];
|
|
99
|
+
bodyJson: JsonObject;
|
|
100
|
+
expectedVersion: number;
|
|
101
|
+
};
|
|
102
|
+
}, optional?: Record<string, never>): Promise<{
|
|
103
|
+
post: PostRecord;
|
|
104
|
+
}>;
|
|
105
|
+
restorePostForward(required: {
|
|
106
|
+
deps: PostWriteDeps & {
|
|
107
|
+
forgetRemoved: ForgetRemovedPostFn;
|
|
108
|
+
};
|
|
109
|
+
input: PostAttribution & {
|
|
110
|
+
prior: PostRecord;
|
|
111
|
+
};
|
|
112
|
+
}, optional?: Record<string, never>): Promise<unknown>;
|
|
113
|
+
findPublishedPostById(required: {
|
|
114
|
+
deps: {
|
|
115
|
+
repo: PostRepoPort;
|
|
116
|
+
};
|
|
117
|
+
input: {
|
|
118
|
+
workspaceId: string;
|
|
119
|
+
id: string;
|
|
120
|
+
};
|
|
121
|
+
}, optional?: Record<string, never>): Promise<PostRecord | null>;
|
|
122
|
+
findPublishedPostBySlug(required: {
|
|
123
|
+
deps: {
|
|
124
|
+
repo: PostRepoPort;
|
|
125
|
+
};
|
|
126
|
+
input: {
|
|
127
|
+
workspaceId: string;
|
|
128
|
+
slug: string;
|
|
129
|
+
};
|
|
130
|
+
}, optional?: Record<string, never>): Promise<PostRecord | null>;
|
|
131
|
+
};
|
|
132
|
+
media: {
|
|
133
|
+
findMediaByIdOrSlug: typeof findMediaByIdOrSlug;
|
|
134
|
+
getLatestTransformDefinition: typeof getLatestTransformDefinition;
|
|
135
|
+
publicTransformName: string;
|
|
136
|
+
};
|
|
137
|
+
entryRefs: {
|
|
138
|
+
extract(required: {
|
|
139
|
+
workspaceId: string;
|
|
140
|
+
sourceEntryId: string;
|
|
141
|
+
sourceEntryType: string;
|
|
142
|
+
bodyJson: unknown;
|
|
143
|
+
fieldsExt: Record<string, unknown>;
|
|
144
|
+
}, optional?: Record<string, never>): readonly EntryRefRow[];
|
|
145
|
+
};
|
|
146
|
+
slugify(text: string): string;
|
|
147
|
+
}
|
|
148
|
+
export {};
|
|
149
|
+
//# sourceMappingURL=host-ports.d.ts.map
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The embed-reference view of a `"html"`-format Page's body (SPEC-047 Slice 2, generalized
|
|
3
|
+
* 2026-08-07 per `project-tovu-generic-embed-contract`, rewired onto the shared marker parser
|
|
4
|
+
* 2026-08-10).
|
|
5
|
+
*
|
|
6
|
+
* Purpose:
|
|
7
|
+
* A TipTap `widgetEmbed` node has a structured home (`bodyJson`) to carry a `{placementId,
|
|
8
|
+
* widgetEntryId}` attrs object. A Page's body is a free-form HTML string with no such tree, so an
|
|
9
|
+
* embed there has to be a literal markup convention instead:
|
|
10
|
+
* `<div data-embed-config='{"type":"widget","id":"{widgetEntryId}"}'></div>` (or `"type":"media"`,
|
|
11
|
+
* `"post"`, or any future type token), anywhere in the page's `body_html`.
|
|
12
|
+
*
|
|
13
|
+
* **This file no longer owns a pattern.** Locating and parsing a marker is `core/embeds/marker.ts`'s
|
|
14
|
+
* single job; this module is only the adapter that projects a parsed marker into the
|
|
15
|
+
* {@link PageHtmlEmbedRef} shape `resolver-service.ts` resolves against. Before the 2026-08-10
|
|
16
|
+
* unification there were four regexes for one concept — this one, `static-render.ts`'s,
|
|
17
|
+
* `entry-refs/extractor.ts`'s deliberate second copy, and the theme-slot pair — and they had already
|
|
18
|
+
* drifted: this one required an EMPTY `<div></div>`, while the theme path deliberately matched a
|
|
19
|
+
* marker WITH authored fallback content inside it. Sharing the parser is what makes "what render.ts
|
|
20
|
+
* embeds" and "what entry_refs indexes" incapable of disagreeing, rather than merely tested for
|
|
21
|
+
* agreement after the fact.
|
|
22
|
+
*
|
|
23
|
+
* **Why `type` is a free string, not a closed union.** A closed union would mean this file's own
|
|
24
|
+
* type signature has to change every time a new embed type is added — exactly the coupling the
|
|
25
|
+
* generic contract exists to remove. Adding a fourth type is a new resolver registration
|
|
26
|
+
* (`resolver-service.ts`'s `HTML_EMBED_RESOLVERS`), never a scanner change. Unknown types flow
|
|
27
|
+
* through untouched; "is this type known" is a resolution-time question, never a scan-time one.
|
|
28
|
+
*
|
|
29
|
+
* **Inner content is now allowed, and that is a widening.** The old empty-div requirement meant any
|
|
30
|
+
* content inside a placeholder kept it from matching at all — it rendered verbatim and inert. The
|
|
31
|
+
* shared parser captures inner content instead, because a theme marker's authored content is a real
|
|
32
|
+
* fallback that must survive when nothing resolves. For most types the consequence is that
|
|
33
|
+
* {@link substituteHtmlEmbeds} replaces the WHOLE element, fallback content included, exactly as it
|
|
34
|
+
* already replaced the whole empty div: an html Page's embed placeholder is scaffolding for the
|
|
35
|
+
* resolved widget, not a nav that must survive a failed lookup. Unresolved still degrades to the
|
|
36
|
+
* REQ-28 placeholder via the caller's own `resolve`, never to a crash and never to raw marker markup
|
|
37
|
+
* reaching a visitor.
|
|
38
|
+
*
|
|
39
|
+
* **`"media"`, `"post"`, and `"content"` are wrapper-preserving.** An author who wraps one of these
|
|
40
|
+
* markers in their own element — `<div style="max-width:600px"
|
|
41
|
+
* data-embed-config='{"type":"media","id":"..."}'></div>`, or `<main class="page-body"
|
|
42
|
+
* data-embed-config='{"type":"content","id":"..."}'></main>` — expects that element and its own
|
|
43
|
+
* attributes (style, class, id, aria-*) to survive; only the marker's CONTENT should become the
|
|
44
|
+
* resolved markup. See {@link WRAPPER_PRESERVING_EMBED_TYPES}'s own doc for the per-type reasoning,
|
|
45
|
+
* and {@link substituteHtmlEmbeds}'s own doc for the one tag-nesting exception `"media"` alone needs.
|
|
46
|
+
*
|
|
47
|
+
* **`"widget"` (and any other type this stage resolves) keeps its element ONLY if the author gave it
|
|
48
|
+
* attributes (2026-09-16, direct owner ask: "regular attributes that somebody puts on a
|
|
49
|
+
* data-embed-config should work on the rendered tag").** A BARE `<div
|
|
50
|
+
* data-embed-config='{"type":"widget","id":"w1"}'></div>` is still whole-element replaced,
|
|
51
|
+
* byte-identical to before — `"widget"`'s resolved root tag is not a small, closed set (see
|
|
52
|
+
* {@link WRAPPER_PRESERVING_EMBED_TYPES}'s own doc for why it still cannot unconditionally join the
|
|
53
|
+
* wrapper-preserving group), but an author who wrote `<div style="max-width:600px"
|
|
54
|
+
* data-embed-config='{"type":"widget","id":"w1"}'></div>` plainly wants that `style` to survive.
|
|
55
|
+
* {@link withElementKeptIfAttributed} is the shared primitive for this rule.
|
|
56
|
+
*
|
|
57
|
+
* **`"media"`'s own attributes (2026-09-16) are partitioned, not simply kept-or-dropped.** See
|
|
58
|
+
* {@link MEDIA_ELEMENT_ATTRIBUTE_NAMES} and {@link substituteHtmlEmbeds}'s own doc.
|
|
59
|
+
*
|
|
60
|
+
* **Why attribute VALUES are not re-validated here for injection safety.** A Page's `body_html` is
|
|
61
|
+
* already unescaped, trusted-ish markup by the time it reaches this module (see
|
|
62
|
+
* `update-html.ts`'s own disclosure) — nothing this file does widens that trust boundary. The one
|
|
63
|
+
* thing this module owns defensively is bounding how much work an adversarial/oversized page can
|
|
64
|
+
* demand at render/index time (`MAX_HTML_EMBEDS_PER_PAGE`) and never using a captured id for
|
|
65
|
+
* anything other than a map/repo lookup key (never re-emitted into HTML unescaped, never
|
|
66
|
+
* interpolated into a query) — see `resolver-service.ts`'s `resolveHtmlPageEmbeds` and
|
|
67
|
+
* `extractor.ts`'s html-ref collector, both of which only ever use the id that way.
|
|
68
|
+
*
|
|
69
|
+
* **`slug`, an author-memorable alternative to `id` (2026-08-31).** The owner's own complaint —
|
|
70
|
+
* `{"type":"widget","id":"29721c44-a811-444f-b7b5-e61a9918a3a9"}` is not something a person can
|
|
71
|
+
* type from memory — is real, and this is the fix: `{"type":"widget","slug":"contact-form"}`.
|
|
72
|
+
* `slug` is projected here exactly like `name`/`variant` already are (a raw, unvalidated config
|
|
73
|
+
* key; deciding whether it is USABLE is a resolver-side concern), but it is a genuinely different
|
|
74
|
+
* mechanism from the dormant `name` field above, not a rename of it. `name` was scaffolded
|
|
75
|
+
* (2026-08-05/07 generic-embed-contract design, `project-tovu-generic-embed-contract` in
|
|
76
|
+
* `ADS-memory/`) for a fuzzy, POSSIBLY-AMBIGUOUS match — "exactly one match resolves; zero or >1
|
|
77
|
+
* degrades to the placeholder, never a guess" — and no resolver in this codebase has ever
|
|
78
|
+
* implemented that ambiguity guard. `slug` needs no such guard: it targets a column
|
|
79
|
+
* (`entries.slug`) carrying a real database `UNIQUE(workspace_id, type, slug)` index, so "more than
|
|
80
|
+
* one match" cannot occur by construction, not merely by convention. Reusing `name`'s never-shipped,
|
|
81
|
+
* ambiguity-hedged semantics for a value that is unique by database constraint would be modeling a
|
|
82
|
+
* problem this feature does not have.
|
|
83
|
+
*
|
|
84
|
+
* **`id` stays authoritative.** When a marker carries both, `id` wins and `slug` is not even
|
|
85
|
+
* consulted — this is a strict widening of the pre-existing "the id resolves" rule (a marker with no
|
|
86
|
+
* `slug` key behaves byte-identically to before), not a new fallback CHAIN where a present-but-broken
|
|
87
|
+
* `id` triggers a `slug` retry; nothing in this codebase's history committed to that stronger contract
|
|
88
|
+
* and building it would add a second resolution path for no requirement in hand. See
|
|
89
|
+
* `resolver-service.ts`'s `resolveWidgetTypeEmbeds` for where `slug` is actually resolved — today
|
|
90
|
+
* that is `"widget"` only (`entries.slug` is unique per `(workspaceId, type)`; `posts.slug` is
|
|
91
|
+
* equally unique and could gain the identical treatment cheaply in a follow-up, but is intentionally
|
|
92
|
+
* left untouched by this change; `media` has no `slug` column at all and cannot support this without
|
|
93
|
+
* a schema migration).
|
|
94
|
+
*/
|
|
95
|
+
import { type MarkerAttribute } from "../markers/marker.js";
|
|
96
|
+
/**
|
|
97
|
+
* One embed reference found in a Page's `body_html`. `type` is deliberately a free string (see this
|
|
98
|
+
* file's header) — the scanner never validates it against a known-type list. `id`/`slug`/`name`/
|
|
99
|
+
* `variant` are `null` when the corresponding CONFIG KEY is absent or not a string (or, for `id`/
|
|
100
|
+
* `slug`, out of {@link MAX_EMBED_ID_LENGTH} bounds) — the scanner reports what is literally written
|
|
101
|
+
* in the markup; deciding whether an absent/invalid key makes the reference resolvable is a
|
|
102
|
+
* resolver-side concern (`resolver-service.ts`), not a scanner-side one.
|
|
103
|
+
*
|
|
104
|
+
* Kept as a narrow projection of `EmbedMarker.config` rather than replaced by it: every resolver in
|
|
105
|
+
* `HTML_EMBED_RESOLVERS` reads exactly these five fields, and `null`-for-absent is the shape their
|
|
106
|
+
* skip branches already test. A resolver needing a new key reads it off the marker config directly
|
|
107
|
+
* — this interface is the compatibility seam, not a ceiling.
|
|
108
|
+
*/
|
|
109
|
+
export interface PageHtmlEmbedRef {
|
|
110
|
+
readonly type: string;
|
|
111
|
+
readonly id: string | null;
|
|
112
|
+
/** See this file's header for why this is NOT the same mechanism as `name` below, despite the
|
|
113
|
+
* similar shape — `slug` targets a database-unique column, `name` a never-implemented fuzzy match. */
|
|
114
|
+
readonly slug: string | null;
|
|
115
|
+
readonly name: string | null;
|
|
116
|
+
readonly variant: string | null;
|
|
117
|
+
/**
|
|
118
|
+
* `false` only when the config key is the literal JSON boolean `false`; every other case
|
|
119
|
+
* (absent, `true`, or any non-boolean value) is `true` (2026-09-04). Today read only by the
|
|
120
|
+
* `"content"` resolver (`resolver-service.ts`'s `resolveContentTypeEmbeds`), which threads it
|
|
121
|
+
* into the `"post-content"` IR's `props.header` so `render.ts`'s `renderWidgetPostContent` can
|
|
122
|
+
* skip its `.post-detail-header` (`<h1>` + date byline) wrapper — previously unconditional
|
|
123
|
+
* application code a template had no way to opt out of, forcing a `display:none` CSS workaround
|
|
124
|
+
* for a homepage doc-format row that does not want a dated byline. `true`-by-default is load-
|
|
125
|
+
* bearing: no existing template (a `kind:"post"` detail page, the listing page, `about`, `docs`)
|
|
126
|
+
* carries a `header` key, and this default is what keeps every one of them rendering byte-
|
|
127
|
+
* identically to before this field existed. Not read by `"widget"`/`"media"`/the legacy `"post"`
|
|
128
|
+
* type — this is a `"content"`-marker-specific concern, not a general marker attribute.
|
|
129
|
+
*/
|
|
130
|
+
readonly header: boolean;
|
|
131
|
+
readonly mode?: "html";
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Resource bound (Programmer workflow step 5a4) mirroring `embed-service.ts`'s
|
|
135
|
+
* `DEFAULT_MAX_EMBEDS_PER_DOCUMENT` for the TipTap path — same policy, same value, applied here
|
|
136
|
+
* because an html Page has no equivalent write-time guardrail (`PagesHtmlDocumentStore.write()`
|
|
137
|
+
* accepts any string; `embed-validation.ts`'s `validateWidgetEmbedMutation` is never in this path).
|
|
138
|
+
* Without a cap, a page carrying thousands of embed placeholders would grow `entry_refs` and the
|
|
139
|
+
* render-time resolved-id set unboundedly. `scanHtmlEmbeds` truncates AT this many refs; occurrences
|
|
140
|
+
* beyond the cap simply resolve to the same REQ-28 placeholder every other unresolved reference
|
|
141
|
+
* does (see `substituteHtmlEmbeds`'s own doc for why it does not need a matching count check).
|
|
142
|
+
*
|
|
143
|
+
* **This is a render/index-time resource guard, NOT a write-time invariant.** Nothing stops
|
|
144
|
+
* `pages_write_html`/the admin HTML editor from storing a page with 5,000 embed placeholders in
|
|
145
|
+
* `body_html` — `PagesHtmlDocumentStore.write()` has no count check of its own, unlike
|
|
146
|
+
* `embed-validation.ts`'s `validateWidgetEmbedMutation` (REQ-20) on the TipTap path, which rejects
|
|
147
|
+
* an over-limit mutation before it is ever persisted. This constant only bounds how much of an
|
|
148
|
+
* already-stored oversized page gets resolved/indexed on any single render or reindex pass — it
|
|
149
|
+
* does not guarantee such a page can never exist. A write-time cap would need its own design
|
|
150
|
+
* decision (reject vs. truncate vs. warn the operator) that has not been made; do not read this
|
|
151
|
+
* constant as implying one already has been.
|
|
152
|
+
*/
|
|
153
|
+
export declare const MAX_HTML_EMBEDS_PER_PAGE = 50;
|
|
154
|
+
/**
|
|
155
|
+
* Every embed reference in `html`, in document order, truncated at
|
|
156
|
+
* {@link MAX_HTML_EMBEDS_PER_PAGE}. Pure — no I/O, never throws. Shared by the render-time resolver
|
|
157
|
+
* (`resolver-service.ts`'s `resolveHtmlPageEmbeds`, which needs to know what to batch-load) and
|
|
158
|
+
* anything that needs to enumerate a page's embed set without rendering it.
|
|
159
|
+
*
|
|
160
|
+
* A marker whose config does not parse is simply absent from the result — render-time stays
|
|
161
|
+
* forgiving (warn + degrade, never throw), and the unparseable marker's own markup survives
|
|
162
|
+
* untouched into the output because {@link substituteHtmlEmbeds} cannot see it either. The LOUD
|
|
163
|
+
* treatment of `rejected` belongs to `core/entry-refs/extractor.ts` (the integrity path a dropped
|
|
164
|
+
* reference would let a delete proceed against) and to the write chokepoint, not here.
|
|
165
|
+
*
|
|
166
|
+
* @complexity O(n) over `html`'s length for the shared scan, plus O(min(k, cap)) to build the
|
|
167
|
+
* returned array, where k is the number of markers.
|
|
168
|
+
* @overallScore 100
|
|
169
|
+
*/
|
|
170
|
+
export declare function scanHtmlEmbeds({ html }: {
|
|
171
|
+
html: string;
|
|
172
|
+
}, _optional?: Record<string, never>): PageHtmlEmbedRef[];
|
|
173
|
+
/**
|
|
174
|
+
* Attribute names D2 (2026-09-16 embed-attributes plan) says mean something on a rendered
|
|
175
|
+
* `<video>`/`<img>` and NOTHING on a `<div>`/other wrapper tag — so when a `"media"` marker is
|
|
176
|
+
* authored on a non-self-rendering wrapper, an attribute in this set moves off the wrapper and onto
|
|
177
|
+
* the resolved media element; every other authored attribute (`class`, `style`, `id`, `data-*`,
|
|
178
|
+
* `aria-*`, an unknown name) stays on the wrapper. See {@link substituteHtmlEmbeds}'s own doc for how
|
|
179
|
+
* this partition is used, and `render.ts`'s merge helper for how `EmbedOccurrence.elementAttributes`
|
|
180
|
+
* actually lands on the rendered tag (D3's precedence rules).
|
|
181
|
+
*
|
|
182
|
+
* `controls` is listed here (it means nothing on a `<div>` either) even though it is ALSO read
|
|
183
|
+
* separately into {@link EmbedOccurrence.controls} — see {@link resolveMarkerControls}'s own doc for
|
|
184
|
+
* why a raw `controls` attribute is never itself re-emitted onto the rendered element.
|
|
185
|
+
*/
|
|
186
|
+
export declare const MEDIA_ELEMENT_ATTRIBUTE_NAMES: ReadonlySet<string>;
|
|
187
|
+
/**
|
|
188
|
+
* One marker occurrence's forwarded attribute data (2026-09-16 embed-attributes plan), handed to
|
|
189
|
+
* `resolve` alongside its {@link PageHtmlEmbedRef} — the per-occurrence half of "attributes on the
|
|
190
|
+
* marker element reach the rendered output" that a `PageHtmlEmbedRef` alone cannot carry (it is keyed
|
|
191
|
+
* by config content, not by which literal marker element authored it; `entry_refs`/the resolver both
|
|
192
|
+
* intentionally do not need presentation data on that shape).
|
|
193
|
+
*/
|
|
194
|
+
export interface EmbedOccurrence {
|
|
195
|
+
/**
|
|
196
|
+
* Only meaningful for `"media"`: on a wrapper, the authored attributes D2
|
|
197
|
+
* ({@link MEDIA_ELEMENT_ATTRIBUTE_NAMES}) says belong on `<video>`/`<img>`, not the wrapper; on a
|
|
198
|
+
* marker authored directly on a self-rendering tag ({@link SELF_RENDERING_MEDIA_TAGS}), every
|
|
199
|
+
* authored attribute. Empty for every other type.
|
|
200
|
+
*/
|
|
201
|
+
readonly elementAttributes: readonly MarkerAttribute[];
|
|
202
|
+
/**
|
|
203
|
+
* `false` only when the marker's own `controls` attribute resolves to off — see
|
|
204
|
+
* {@link resolveMarkerControls}. `true` (the pre-existing default) for every non-`"media"` type,
|
|
205
|
+
* where the field is unused.
|
|
206
|
+
*/
|
|
207
|
+
readonly controls: boolean;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Replaces every embed placeholder in `html` with `resolve(ref, occurrence)`'s return value.
|
|
211
|
+
* `resolve` is synchronous and pure from this function's own perspective — the caller is expected
|
|
212
|
+
* to have already batch-resolved every ref it cares about (mirroring how `render.ts`'s
|
|
213
|
+
* `renderDocNode` receives an already-resolved `inlineResolved` map rather than doing its own I/O);
|
|
214
|
+
* a ref `resolve` doesn't recognize (unknown type, never loaded, or beyond
|
|
215
|
+
* {@link MAX_HTML_EMBEDS_PER_PAGE}) is `resolve`'s own call to degrade safely, not something this
|
|
216
|
+
* function special-cases — every caller here (`render.ts`'s `renderHtmlPageBody`) already returns
|
|
217
|
+
* the REQ-28 placeholder for an unresolved ref, which is exactly what "no cap-aware branch needed
|
|
218
|
+
* here" relies on.
|
|
219
|
+
*
|
|
220
|
+
* `resolve` returning `undefined` means **leave this marker exactly as authored**, forwarded straight
|
|
221
|
+
* to `substituteMarkers`' own invariant of the same name. That is not a convenience: since the
|
|
222
|
+
* 2026-08-10 unification this stage SEES markers it does not own (a theme's `partial`/`menu`), and a
|
|
223
|
+
* later stage resolves them. Substituting anything at all over one of those — including the REQ-28
|
|
224
|
+
* placeholder — silently deletes a nav. Which types this stage owns is `resolver-service.ts`'s
|
|
225
|
+
* `isPageEmbedType` to answer; this function only carries the answer through.
|
|
226
|
+
*
|
|
227
|
+
* Per-type handling (2026-09-16):
|
|
228
|
+
* - **`"media"`**: {@link planMediaMarkerAttributes} computes the occurrence AND the wrapper split.
|
|
229
|
+
* On the marker's own self-rendering tag ({@link SELF_RENDERING_MEDIA_TAGS}) the replacement is
|
|
230
|
+
* returned as-is (no wrapper, unchanged from before). On any other tag: if nothing moved, the
|
|
231
|
+
* wrapper is rebuilt via the byte-identical {@link withInnerContentFinal} path; otherwise via
|
|
232
|
+
* {@link withInnerContentAndAttributes} with only the reduced wrapper attribute list.
|
|
233
|
+
* - **`"post"`/`"content"`** ({@link WRAPPER_PRESERVING_EMBED_TYPES}): spliced via
|
|
234
|
+
* {@link spliceWrapperPreservingReplacement}, occurrence attributes always empty.
|
|
235
|
+
* - **Every other type** (`"widget"`, and any type this stage does not itself own but still
|
|
236
|
+
* resolves): {@link withElementKeptIfAttributed} — a bare marker keeps the pre-existing
|
|
237
|
+
* whole-element replace; an attributed one keeps its element around the resolved output.
|
|
238
|
+
*
|
|
239
|
+
* @complexity O(n) over `html`'s length for the shared scan, plus O(k) per `"media"` marker in its own
|
|
240
|
+
* attribute count (one two-way partition), O(1) per marker otherwise.
|
|
241
|
+
* @overallScore 100
|
|
242
|
+
*/
|
|
243
|
+
export declare function substituteHtmlEmbeds({ html, resolve }: {
|
|
244
|
+
html: string;
|
|
245
|
+
resolve: (ref: PageHtmlEmbedRef, occurrence: EmbedOccurrence) => string | undefined;
|
|
246
|
+
}, _optional?: Record<string, never>): string;
|
|
247
|
+
//# sourceMappingURL=html-embeds.d.ts.map
|