@caretcms/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +191 -0
- package/dist/browser-runtime.d.ts +12 -0
- package/dist/browser-runtime.d.ts.map +1 -0
- package/dist/browser-runtime.js +298 -0
- package/dist/browser-runtime.js.map +1 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +343 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +61 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +80 -0
- package/dist/loader.js.map +1 -0
- package/dist/providers/storage/filesystem.d.ts +7 -0
- package/dist/providers/storage/filesystem.d.ts.map +1 -0
- package/dist/providers/storage/filesystem.js +6 -0
- package/dist/providers/storage/filesystem.js.map +1 -0
- package/dist/providers/uploads/local.d.ts +6 -0
- package/dist/providers/uploads/local.d.ts.map +1 -0
- package/dist/providers/uploads/local.js +6 -0
- package/dist/providers/uploads/local.js.map +1 -0
- package/dist/runtime/auth/cookie-utils.d.ts +3 -0
- package/dist/runtime/auth/cookie-utils.d.ts.map +1 -0
- package/dist/runtime/auth/cookie-utils.js +33 -0
- package/dist/runtime/auth/cookie-utils.js.map +1 -0
- package/dist/runtime/auth/demo-session.d.ts +24 -0
- package/dist/runtime/auth/demo-session.d.ts.map +1 -0
- package/dist/runtime/auth/demo-session.js +42 -0
- package/dist/runtime/auth/demo-session.js.map +1 -0
- package/dist/runtime/auth/rate-limiter.d.ts +26 -0
- package/dist/runtime/auth/rate-limiter.d.ts.map +1 -0
- package/dist/runtime/auth/rate-limiter.js +65 -0
- package/dist/runtime/auth/rate-limiter.js.map +1 -0
- package/dist/runtime/auth/session.d.ts +14 -0
- package/dist/runtime/auth/session.d.ts.map +1 -0
- package/dist/runtime/auth/session.js +128 -0
- package/dist/runtime/auth/session.js.map +1 -0
- package/dist/runtime/bind.d.ts +14 -0
- package/dist/runtime/bind.d.ts.map +1 -0
- package/dist/runtime/bind.js +19 -0
- package/dist/runtime/bind.js.map +1 -0
- package/dist/runtime/config.d.ts +20 -0
- package/dist/runtime/config.d.ts.map +1 -0
- package/dist/runtime/config.js +40 -0
- package/dist/runtime/config.js.map +1 -0
- package/dist/runtime/content.d.ts +28 -0
- package/dist/runtime/content.d.ts.map +1 -0
- package/dist/runtime/content.js +37 -0
- package/dist/runtime/content.js.map +1 -0
- package/dist/runtime/index.d.ts +14 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +16 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/middleware.d.ts +14 -0
- package/dist/runtime/middleware.d.ts.map +1 -0
- package/dist/runtime/middleware.js +80 -0
- package/dist/runtime/middleware.js.map +1 -0
- package/dist/runtime/mutations/contracts.d.ts +91 -0
- package/dist/runtime/mutations/contracts.d.ts.map +1 -0
- package/dist/runtime/mutations/contracts.js +320 -0
- package/dist/runtime/mutations/contracts.js.map +1 -0
- package/dist/runtime/mutations/engine.d.ts +23 -0
- package/dist/runtime/mutations/engine.d.ts.map +1 -0
- package/dist/runtime/mutations/engine.js +255 -0
- package/dist/runtime/mutations/engine.js.map +1 -0
- package/dist/runtime/providers.d.ts +9 -0
- package/dist/runtime/providers.d.ts.map +1 -0
- package/dist/runtime/providers.js +44 -0
- package/dist/runtime/providers.js.map +1 -0
- package/dist/runtime/request-context.d.ts +11 -0
- package/dist/runtime/request-context.d.ts.map +1 -0
- package/dist/runtime/request-context.js +16 -0
- package/dist/runtime/request-context.js.map +1 -0
- package/dist/runtime/rewrite.d.ts +13 -0
- package/dist/runtime/rewrite.d.ts.map +1 -0
- package/dist/runtime/rewrite.js +253 -0
- package/dist/runtime/rewrite.js.map +1 -0
- package/dist/runtime/routes/_helpers.d.ts +17 -0
- package/dist/runtime/routes/_helpers.d.ts.map +1 -0
- package/dist/runtime/routes/_helpers.js +38 -0
- package/dist/runtime/routes/_helpers.js.map +1 -0
- package/dist/runtime/routes/admin-entry.d.ts +3 -0
- package/dist/runtime/routes/admin-entry.d.ts.map +1 -0
- package/dist/runtime/routes/admin-entry.js +170 -0
- package/dist/runtime/routes/admin-entry.js.map +1 -0
- package/dist/runtime/routes/admin.d.ts +3 -0
- package/dist/runtime/routes/admin.d.ts.map +1 -0
- package/dist/runtime/routes/admin.js +189 -0
- package/dist/runtime/routes/admin.js.map +1 -0
- package/dist/runtime/routes/auth-login.d.ts +4 -0
- package/dist/runtime/routes/auth-login.d.ts.map +1 -0
- package/dist/runtime/routes/auth-login.js +71 -0
- package/dist/runtime/routes/auth-login.js.map +1 -0
- package/dist/runtime/routes/auth-logout.d.ts +4 -0
- package/dist/runtime/routes/auth-logout.d.ts.map +1 -0
- package/dist/runtime/routes/auth-logout.js +47 -0
- package/dist/runtime/routes/auth-logout.js.map +1 -0
- package/dist/runtime/routes/auth-session.d.ts +3 -0
- package/dist/runtime/routes/auth-session.d.ts.map +1 -0
- package/dist/runtime/routes/auth-session.js +8 -0
- package/dist/runtime/routes/auth-session.js.map +1 -0
- package/dist/runtime/routes/collection-list.d.ts +3 -0
- package/dist/runtime/routes/collection-list.d.ts.map +1 -0
- package/dist/runtime/routes/collection-list.js +420 -0
- package/dist/runtime/routes/collection-list.js.map +1 -0
- package/dist/runtime/routes/collections-metadata.d.ts +3 -0
- package/dist/runtime/routes/collections-metadata.d.ts.map +1 -0
- package/dist/runtime/routes/collections-metadata.js +17 -0
- package/dist/runtime/routes/collections-metadata.js.map +1 -0
- package/dist/runtime/routes/editor-assets.d.ts +3 -0
- package/dist/runtime/routes/editor-assets.d.ts.map +1 -0
- package/dist/runtime/routes/editor-assets.js +73 -0
- package/dist/runtime/routes/editor-assets.js.map +1 -0
- package/dist/runtime/routes/entries.d.ts +3 -0
- package/dist/runtime/routes/entries.d.ts.map +1 -0
- package/dist/runtime/routes/entries.js +96 -0
- package/dist/runtime/routes/entries.js.map +1 -0
- package/dist/runtime/routes/history.d.ts +4 -0
- package/dist/runtime/routes/history.d.ts.map +1 -0
- package/dist/runtime/routes/history.js +54 -0
- package/dist/runtime/routes/history.js.map +1 -0
- package/dist/runtime/routes/mutate.d.ts +4 -0
- package/dist/runtime/routes/mutate.d.ts.map +1 -0
- package/dist/runtime/routes/mutate.js +38 -0
- package/dist/runtime/routes/mutate.js.map +1 -0
- package/dist/runtime/routes/schema.d.ts +3 -0
- package/dist/runtime/routes/schema.d.ts.map +1 -0
- package/dist/runtime/routes/schema.js +58 -0
- package/dist/runtime/routes/schema.js.map +1 -0
- package/dist/runtime/routes/studio-home.d.ts +3 -0
- package/dist/runtime/routes/studio-home.d.ts.map +1 -0
- package/dist/runtime/routes/studio-home.js +99 -0
- package/dist/runtime/routes/studio-home.js.map +1 -0
- package/dist/runtime/routes/theme-stylesheet.d.ts +3 -0
- package/dist/runtime/routes/theme-stylesheet.d.ts.map +1 -0
- package/dist/runtime/routes/theme-stylesheet.js +32 -0
- package/dist/runtime/routes/theme-stylesheet.js.map +1 -0
- package/dist/runtime/routes/upload.d.ts +3 -0
- package/dist/runtime/routes/upload.d.ts.map +1 -0
- package/dist/runtime/routes/upload.js +33 -0
- package/dist/runtime/routes/upload.js.map +1 -0
- package/dist/runtime/sanitize-html.d.ts +8 -0
- package/dist/runtime/sanitize-html.d.ts.map +1 -0
- package/dist/runtime/sanitize-html.js +86 -0
- package/dist/runtime/sanitize-html.js.map +1 -0
- package/dist/runtime/schema/template.d.ts +15 -0
- package/dist/runtime/schema/template.d.ts.map +1 -0
- package/dist/runtime/schema/template.js +38 -0
- package/dist/runtime/schema/template.js.map +1 -0
- package/dist/runtime/schema-registry.d.ts +17 -0
- package/dist/runtime/schema-registry.d.ts.map +1 -0
- package/dist/runtime/schema-registry.js +16 -0
- package/dist/runtime/schema-registry.js.map +1 -0
- package/dist/runtime/storage/filesystem-adapter.d.ts +36 -0
- package/dist/runtime/storage/filesystem-adapter.d.ts.map +1 -0
- package/dist/runtime/storage/filesystem-adapter.js +291 -0
- package/dist/runtime/storage/filesystem-adapter.js.map +1 -0
- package/dist/runtime/storage/filesystem-upload-handler.d.ts +11 -0
- package/dist/runtime/storage/filesystem-upload-handler.d.ts.map +1 -0
- package/dist/runtime/storage/filesystem-upload-handler.js +20 -0
- package/dist/runtime/storage/filesystem-upload-handler.js.map +1 -0
- package/dist/runtime/storage/image-validation.d.ts +28 -0
- package/dist/runtime/storage/image-validation.d.ts.map +1 -0
- package/dist/runtime/storage/image-validation.js +94 -0
- package/dist/runtime/storage/image-validation.js.map +1 -0
- package/dist/runtime/storage/in-memory-adapter.d.ts +29 -0
- package/dist/runtime/storage/in-memory-adapter.d.ts.map +1 -0
- package/dist/runtime/storage/in-memory-adapter.js +110 -0
- package/dist/runtime/storage/in-memory-adapter.js.map +1 -0
- package/dist/runtime/storage/quota-upload-handler.d.ts +27 -0
- package/dist/runtime/storage/quota-upload-handler.d.ts.map +1 -0
- package/dist/runtime/storage/quota-upload-handler.js +34 -0
- package/dist/runtime/storage/quota-upload-handler.js.map +1 -0
- package/dist/runtime/storage/session-overlay-adapter.d.ts +33 -0
- package/dist/runtime/storage/session-overlay-adapter.d.ts.map +1 -0
- package/dist/runtime/storage/session-overlay-adapter.js +121 -0
- package/dist/runtime/storage/session-overlay-adapter.js.map +1 -0
- package/dist/runtime/themes/preset-tokens.d.ts +17 -0
- package/dist/runtime/themes/preset-tokens.d.ts.map +1 -0
- package/dist/runtime/themes/preset-tokens.js +47 -0
- package/dist/runtime/themes/preset-tokens.js.map +1 -0
- package/dist/runtime/utils.d.ts +6 -0
- package/dist/runtime/utils.d.ts.map +1 -0
- package/dist/runtime/utils.js +29 -0
- package/dist/runtime/utils.js.map +1 -0
- package/dist/runtime/views/studio-layout.d.ts +25 -0
- package/dist/runtime/views/studio-layout.d.ts.map +1 -0
- package/dist/runtime/views/studio-layout.js +108 -0
- package/dist/runtime/views/studio-layout.js.map +1 -0
- package/dist/schema-utils.d.ts +16 -0
- package/dist/schema-utils.d.ts.map +1 -0
- package/dist/schema-utils.js +62 -0
- package/dist/schema-utils.js.map +1 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/editor-runtime.d.ts +6 -0
- package/editor-runtime.js +2 -0
- package/package.json +88 -0
- package/static/cms/admin-entry.css +207 -0
- package/static/cms/admin-entry.js +1083 -0
- package/static/cms/editor/config.js +118 -0
- package/static/cms/editor/content-map.css +141 -0
- package/static/cms/editor/content-map.js +172 -0
- package/static/cms/editor/guards.js +72 -0
- package/static/cms/editor/helpers.js +51 -0
- package/static/cms/editor/image-edit.js +151 -0
- package/static/cms/editor/image-utils.js +84 -0
- package/static/cms/editor/image.css +133 -0
- package/static/cms/editor/link-follow.js +144 -0
- package/static/cms/editor/link-ui.css +199 -0
- package/static/cms/editor/linkify.js +55 -0
- package/static/cms/editor/panel.css +91 -0
- package/static/cms/editor/panel.js +70 -0
- package/static/cms/editor/rich-toolbar.css +67 -0
- package/static/cms/editor/rich-toolbar.js +200 -0
- package/static/cms/editor/sanitize.js +89 -0
- package/static/cms/editor/save-queue.js +166 -0
- package/static/cms/editor/section-controls/activation.js +10 -0
- package/static/cms/editor/section-controls/api.js +87 -0
- package/static/cms/editor/section-controls/constants.js +24 -0
- package/static/cms/editor/section-controls/index.js +622 -0
- package/static/cms/editor/section-controls/model.js +76 -0
- package/static/cms/editor/section-controls/mutations.js +112 -0
- package/static/cms/editor/section-controls/page-context.js +34 -0
- package/static/cms/editor/section-controls/pickers.js +196 -0
- package/static/cms/editor/section-controls/reorder.js +92 -0
- package/static/cms/editor/section-controls/selection.js +54 -0
- package/static/cms/editor/section-controls/spacing-drag.js +83 -0
- package/static/cms/editor/section-controls/ui-elements.js +54 -0
- package/static/cms/editor/section-controls/utils.js +35 -0
- package/static/cms/editor/section-controls.css +349 -0
- package/static/cms/editor/section-controls.js +1 -0
- package/static/cms/editor/security.js +48 -0
- package/static/cms/editor/sync.js +72 -0
- package/static/cms/editor/text-edit.js +151 -0
- package/static/cms/editor/text-link-interactions.js +254 -0
- package/static/cms/editor/toast.css +50 -0
- package/static/cms/editor/toast.js +29 -0
- package/static/cms/editor/tokens-and-text.css +93 -0
- package/static/cms/editor/toolbar.css +249 -0
- package/static/cms/editor/toolbar.js +117 -0
- package/static/cms/editor.css +12 -0
- package/static/cms/editor.js +116 -0
- package/static/cms/studio-login.css +300 -0
- package/static/cms/studio-tokens.css +85 -0
- package/static/cms/studio.css +437 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { setNestedValue } from "../utils.js";
|
|
2
|
+
import { parseMutationCommand, isRecord, } from "./contracts.js";
|
|
3
|
+
/**
|
|
4
|
+
* Per-key chained-promise serializer. Used so an entire
|
|
5
|
+
* `getRevision → writeEntry → bumpRevision` sequence runs atomically
|
|
6
|
+
* for a given entry — without this, two concurrent writes against the
|
|
7
|
+
* same `expectedRevision` can both pass the conflict check and silently
|
|
8
|
+
* overwrite each other.
|
|
9
|
+
*/
|
|
10
|
+
const mutationLocks = new Map();
|
|
11
|
+
function withLock(key, task) {
|
|
12
|
+
const previous = mutationLocks.get(key) ?? Promise.resolve();
|
|
13
|
+
const next = previous.then(task, task);
|
|
14
|
+
mutationLocks.set(key, next.finally(() => {
|
|
15
|
+
if (mutationLocks.get(key) === next) {
|
|
16
|
+
mutationLocks.delete(key);
|
|
17
|
+
}
|
|
18
|
+
}));
|
|
19
|
+
return next;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Acquire multiple keys' locks before running task. Keys are deduped + sorted
|
|
23
|
+
* so two callers asking for overlapping sets always queue in the same order,
|
|
24
|
+
* avoiding AB-BA deadlocks. Used by multi-entry mutations (reorder) so they
|
|
25
|
+
* compose with single-entry mutations on the same entries.
|
|
26
|
+
*/
|
|
27
|
+
function withLocks(keys, task) {
|
|
28
|
+
const sorted = [...new Set(keys)].sort();
|
|
29
|
+
const acquire = (idx) => {
|
|
30
|
+
if (idx === sorted.length)
|
|
31
|
+
return task();
|
|
32
|
+
return withLock(sorted[idx], () => acquire(idx + 1));
|
|
33
|
+
};
|
|
34
|
+
return acquire(0);
|
|
35
|
+
}
|
|
36
|
+
const entryKey = (collection, id) => `entry::${collection}::${id}`;
|
|
37
|
+
const collectionKey = (collection) => `collection::${collection}`;
|
|
38
|
+
function fail(status, error, extra) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
status,
|
|
42
|
+
body: {
|
|
43
|
+
error,
|
|
44
|
+
...extra,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async function ensureExpectedRevisionMatches(adapter, collection, id, expectedRevision) {
|
|
49
|
+
const currentRevision = await adapter.getRevision(collection, id);
|
|
50
|
+
if (expectedRevision !== undefined && expectedRevision !== currentRevision) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
result: fail(409, "Revision conflict", { currentRevision }),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return { ok: true, currentRevision };
|
|
57
|
+
}
|
|
58
|
+
async function applySaveField(adapter, command) {
|
|
59
|
+
const { collection, id, field, value, expectedRevision } = command;
|
|
60
|
+
return withLock(entryKey(collection, id), async () => {
|
|
61
|
+
const revision = await ensureExpectedRevisionMatches(adapter, collection, id, expectedRevision);
|
|
62
|
+
if (!revision.ok)
|
|
63
|
+
return revision.result;
|
|
64
|
+
const before = (await adapter.getEntry(collection, id))?.data ?? {};
|
|
65
|
+
const current = { ...before };
|
|
66
|
+
await adapter.appendHistory(collection, id, {
|
|
67
|
+
ts: Date.now(),
|
|
68
|
+
action: "save",
|
|
69
|
+
data: before,
|
|
70
|
+
});
|
|
71
|
+
try {
|
|
72
|
+
setNestedValue(current, field, value);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const message = error instanceof Error ? error.message : "Set failed";
|
|
76
|
+
return fail(400, message);
|
|
77
|
+
}
|
|
78
|
+
await adapter.writeEntry(collection, id, current);
|
|
79
|
+
const nextRevision = await adapter.bumpRevision(collection, id);
|
|
80
|
+
return { ok: true, body: { ok: true, revision: nextRevision } };
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async function applyPutEntry(adapter, command) {
|
|
84
|
+
const { collection, id, data, expectedRevision } = command;
|
|
85
|
+
return withLock(entryKey(collection, id), async () => {
|
|
86
|
+
const revision = await ensureExpectedRevisionMatches(adapter, collection, id, expectedRevision);
|
|
87
|
+
if (!revision.ok)
|
|
88
|
+
return revision.result;
|
|
89
|
+
const before = (await adapter.getEntry(collection, id))?.data ?? null;
|
|
90
|
+
if (before !== null) {
|
|
91
|
+
await adapter.appendHistory(collection, id, {
|
|
92
|
+
ts: Date.now(),
|
|
93
|
+
action: "put",
|
|
94
|
+
data: before,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
await adapter.writeEntry(collection, id, data);
|
|
98
|
+
const nextRevision = await adapter.bumpRevision(collection, id);
|
|
99
|
+
return { ok: true, body: { ok: true, revision: nextRevision } };
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
async function applyDeleteEntry(adapter, command) {
|
|
103
|
+
const { collection, id, expectedRevision } = command;
|
|
104
|
+
return withLock(entryKey(collection, id), async () => {
|
|
105
|
+
const revision = await ensureExpectedRevisionMatches(adapter, collection, id, expectedRevision);
|
|
106
|
+
if (!revision.ok)
|
|
107
|
+
return revision.result;
|
|
108
|
+
const before = (await adapter.getEntry(collection, id))?.data ?? null;
|
|
109
|
+
if (before !== null) {
|
|
110
|
+
await adapter.appendHistory(collection, id, {
|
|
111
|
+
ts: Date.now(),
|
|
112
|
+
action: "delete",
|
|
113
|
+
data: before,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
await adapter.deleteEntry(collection, id);
|
|
117
|
+
const nextRevision = await adapter.bumpRevision(collection, id);
|
|
118
|
+
return { ok: true, body: { ok: true, revision: nextRevision } };
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async function applyReorderEntries(adapter, command) {
|
|
122
|
+
const { collection, items } = command;
|
|
123
|
+
// Hold the per-entry locks for every participating entry, plus a
|
|
124
|
+
// collection-level lock that serializes reorder against itself. Without the
|
|
125
|
+
// per-entry locks, a concurrent save_field on one of these entries can
|
|
126
|
+
// interleave a revision bump between two of ours and tear the revision map.
|
|
127
|
+
const keys = [collectionKey(collection), ...items.map((item) => entryKey(collection, item.id))];
|
|
128
|
+
return withLocks(keys, async () => {
|
|
129
|
+
const currentById = new Map();
|
|
130
|
+
for (const item of items) {
|
|
131
|
+
const entry = await adapter.getEntry(collection, item.id);
|
|
132
|
+
if (!entry)
|
|
133
|
+
return fail(404, "Entry not found", { conflictId: item.id });
|
|
134
|
+
const revision = await adapter.getRevision(collection, item.id);
|
|
135
|
+
if (item.expectedRevision !== undefined && item.expectedRevision !== revision) {
|
|
136
|
+
return fail(409, "Revision conflict", {
|
|
137
|
+
conflictId: item.id,
|
|
138
|
+
currentRevision: revision,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
currentById.set(item.id, { data: entry.data, revision });
|
|
142
|
+
}
|
|
143
|
+
const revisions = {};
|
|
144
|
+
for (const item of items) {
|
|
145
|
+
const current = currentById.get(item.id);
|
|
146
|
+
if (!current)
|
|
147
|
+
return fail(500, "Failed to load reorder item");
|
|
148
|
+
await adapter.appendHistory(collection, item.id, {
|
|
149
|
+
ts: Date.now(),
|
|
150
|
+
action: "reorder",
|
|
151
|
+
data: current.data,
|
|
152
|
+
});
|
|
153
|
+
const nextData = { ...current.data, order: item.order };
|
|
154
|
+
await adapter.writeEntry(collection, item.id, nextData);
|
|
155
|
+
revisions[item.id] = await adapter.bumpRevision(collection, item.id);
|
|
156
|
+
}
|
|
157
|
+
return { ok: true, body: { ok: true, revisions } };
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async function applyUpdatePageLayout(adapter, command) {
|
|
161
|
+
const { collection, id, sections, expectedRevision } = command;
|
|
162
|
+
return withLock(entryKey(collection, id), async () => {
|
|
163
|
+
const revision = await ensureExpectedRevisionMatches(adapter, collection, id, expectedRevision);
|
|
164
|
+
if (!revision.ok)
|
|
165
|
+
return revision.result;
|
|
166
|
+
const current = (await adapter.getEntry(collection, id))?.data ?? {};
|
|
167
|
+
await adapter.appendHistory(collection, id, {
|
|
168
|
+
ts: Date.now(),
|
|
169
|
+
action: "put",
|
|
170
|
+
data: current,
|
|
171
|
+
});
|
|
172
|
+
const layout = current.layout && isRecord(current.layout)
|
|
173
|
+
? { ...current.layout }
|
|
174
|
+
: {};
|
|
175
|
+
layout.sections = sections.map((section) => ({
|
|
176
|
+
id: section.id,
|
|
177
|
+
key: section.key,
|
|
178
|
+
enabled: section.enabled,
|
|
179
|
+
spacing_y: section.spacing_y,
|
|
180
|
+
}));
|
|
181
|
+
current.layout = layout;
|
|
182
|
+
await adapter.writeEntry(collection, id, current);
|
|
183
|
+
const nextRevision = await adapter.bumpRevision(collection, id);
|
|
184
|
+
return { ok: true, body: { ok: true, revision: nextRevision } };
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async function applyCreateCollection(adapter, command) {
|
|
188
|
+
const { id, label, description, icon, creatable, orderable, schema } = command;
|
|
189
|
+
// Check if collection already exists
|
|
190
|
+
const exists = await adapter.isKnownCollection(id);
|
|
191
|
+
if (exists) {
|
|
192
|
+
return fail(409, `Collection "${id}" already exists`);
|
|
193
|
+
}
|
|
194
|
+
const metadata = {
|
|
195
|
+
id,
|
|
196
|
+
label,
|
|
197
|
+
description,
|
|
198
|
+
icon,
|
|
199
|
+
creatable: creatable ?? true,
|
|
200
|
+
orderable: orderable ?? false,
|
|
201
|
+
schema,
|
|
202
|
+
created_at: Date.now(),
|
|
203
|
+
updated_at: Date.now(),
|
|
204
|
+
};
|
|
205
|
+
try {
|
|
206
|
+
await adapter.createCollection(metadata);
|
|
207
|
+
return { ok: true, body: { ok: true } };
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const message = error instanceof Error ? error.message : "Failed to create collection";
|
|
211
|
+
return fail(500, message);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async function applyDeleteCollection(adapter, command) {
|
|
215
|
+
const { id } = command;
|
|
216
|
+
// Check if collection exists
|
|
217
|
+
const exists = await adapter.isKnownCollection(id);
|
|
218
|
+
if (!exists) {
|
|
219
|
+
return fail(404, `Collection "${id}" not found`);
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
await adapter.deleteCollection(id);
|
|
223
|
+
return { ok: true, body: { ok: true } };
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
const message = error instanceof Error ? error.message : "Failed to delete collection";
|
|
227
|
+
return fail(500, message);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
export async function executeMutation(adapter, input) {
|
|
231
|
+
const parsed = await parseMutationCommand(adapter, input);
|
|
232
|
+
if (!parsed.ok) {
|
|
233
|
+
return fail(400, "Invalid mutation command", { issues: parsed.issues });
|
|
234
|
+
}
|
|
235
|
+
const command = parsed.command;
|
|
236
|
+
switch (command.type) {
|
|
237
|
+
case "save_field":
|
|
238
|
+
return applySaveField(adapter, command);
|
|
239
|
+
case "put_entry":
|
|
240
|
+
return applyPutEntry(adapter, command);
|
|
241
|
+
case "delete_entry":
|
|
242
|
+
return applyDeleteEntry(adapter, command);
|
|
243
|
+
case "reorder_entries":
|
|
244
|
+
return applyReorderEntries(adapter, command);
|
|
245
|
+
case "update_page_layout":
|
|
246
|
+
return applyUpdatePageLayout(adapter, command);
|
|
247
|
+
case "create_collection":
|
|
248
|
+
return applyCreateCollection(adapter, command);
|
|
249
|
+
case "delete_collection":
|
|
250
|
+
return applyDeleteCollection(adapter, command);
|
|
251
|
+
default:
|
|
252
|
+
return fail(400, "Unsupported mutation type");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/runtime/mutations/engine.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,oBAAoB,EACpB,QAAQ,GAGT,MAAM,gBAAgB,CAAC;AAExB;;;;;;GAMG;AACH,MAAM,aAAa,GAAG,IAAI,GAAG,EAA4B,CAAC;AAE1D,SAAS,QAAQ,CAAI,GAAW,EAAE,IAAsB;IACtD,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAC7D,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACvC,aAAa,CAAC,GAAG,CACf,GAAG,EACH,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE;QAChB,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;YACpC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IACF,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,SAAS,SAAS,CAAI,IAAc,EAAE,IAAsB;IAC1D,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,CAAC,GAAW,EAAc,EAAE;QAC1C,IAAI,GAAG,KAAK,MAAM,CAAC,MAAM;YAAE,OAAO,IAAI,EAAE,CAAC;QACzC,OAAO,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC,CAAC;IACF,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,QAAQ,GAAG,CAAC,UAAkB,EAAE,EAAU,EAAE,EAAE,CAAC,UAAU,UAAU,KAAK,EAAE,EAAE,CAAC;AACnF,MAAM,aAAa,GAAG,CAAC,UAAkB,EAAE,EAAE,CAAC,eAAe,UAAU,EAAE,CAAC;AAa1E,SAAS,IAAI,CAAC,MAAc,EAAE,KAAa,EAAE,KAAkC;IAC7E,OAAO;QACL,EAAE,EAAE,KAAK;QACT,MAAM;QACN,IAAI,EAAE;YACJ,KAAK;YACL,GAAG,KAAK;SACT;KACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,6BAA6B,CAC1C,OAAuB,EACvB,UAAkB,EAClB,EAAU,EACV,gBAAoC;IAEpC,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAClE,IAAI,gBAAgB,KAAK,SAAS,IAAI,gBAAgB,KAAK,eAAe,EAAE,CAAC;QAC3E,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,mBAAmB,EAAE,EAAE,eAAe,EAAE,CAAC;SAC5D,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;AACvC,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,OAAuB,EACvB,OAA4D;IAE5D,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC;IAEnE,OAAO,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,QAAQ,GAAG,MAAM,6BAA6B,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAChG,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,QAAQ,CAAC,MAAM,CAAC;QAEzC,MAAM,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;QACpE,MAAM,OAAO,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;QAE9B,MAAM,OAAO,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,EAAE;YAC1C,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;YACd,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,MAAM;SACb,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,cAAc,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC;YACtE,OAAO,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC5B,CAAC;QAED,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;QAClD,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAChE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,OAAuB,EACvB,OAA2D;IAE3D,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC;IAC3D,OAAO,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,QAAQ,GAAG,MAAM,6BAA6B,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAChG,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,QAAQ,CAAC,MAAM,CAAC;QAEzC,MAAM,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;QACtE,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,OAAO,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,EAAE;gBAC1C,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;gBACd,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,MAAM;aACb,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAC/C,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAChE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,gBAAgB,CAC7B,OAAuB,EACvB,OAA8D;IAE9D,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC;IACrD,OAAO,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,QAAQ,GAAG,MAAM,6BAA6B,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAChG,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,QAAQ,CAAC,MAAM,CAAC;QAEzC,MAAM,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;QACtE,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,OAAO,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,EAAE;gBAC1C,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;gBACd,MAAM,EAAE,QAAQ;gBAChB,IAAI,EAAE,MAAM;aACb,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,CAAC,WAAW,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAC1C,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAChE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,mBAAmB,CAChC,OAAuB,EACvB,OAAiE;IAEjE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IACtC,iEAAiE;IACjE,4EAA4E;IAC5E,uEAAuE;IACvE,4EAA4E;IAC5E,MAAM,IAAI,GAAG,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAChG,OAAO,SAAS,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE;QAChC,MAAM,WAAW,GAAG,IAAI,GAAG,EAA+D,CAAC;QAE3F,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YAC1D,IAAI,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC,GAAG,EAAE,iBAAiB,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YACzE,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YAChE,IAAI,IAAI,CAAC,gBAAgB,KAAK,SAAS,IAAI,IAAI,CAAC,gBAAgB,KAAK,QAAQ,EAAE,CAAC;gBAC9E,OAAO,IAAI,CAAC,GAAG,EAAE,mBAAmB,EAAE;oBACpC,UAAU,EAAE,IAAI,CAAC,EAAE;oBACnB,eAAe,EAAE,QAAQ;iBAC1B,CAAC,CAAC;YACL,CAAC;YACD,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC3D,CAAC;QAED,MAAM,SAAS,GAA2B,EAAE,CAAC;QAC7C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACzC,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC,GAAG,EAAE,6BAA6B,CAAC,CAAC;YAE9D,MAAM,OAAO,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE;gBAC/C,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;gBACd,MAAM,EAAE,SAAS;gBACjB,IAAI,EAAE,OAAO,CAAC,IAAI;aACnB,CAAC,CAAC;YAEH,MAAM,QAAQ,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;YACxD,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;YACxD,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;QAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,OAAuB,EACvB,OAAoE;IAEpE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC;IAC/D,OAAO,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,EAAE,KAAK,IAAI,EAAE;QACnD,MAAM,QAAQ,GAAG,MAAM,6BAA6B,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAChG,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,QAAQ,CAAC,MAAM,CAAC;QAEzC,MAAM,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;QACrE,MAAM,OAAO,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,EAAE;YAC1C,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;YACd,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,OAAO;SACd,CAAC,CAAC;QAEH,MAAM,MAAM,GACV,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC;YACxC,CAAC,CAAC,EAAE,GAAI,OAAO,CAAC,MAAkC,EAAE;YACpD,CAAC,CAAC,EAAE,CAAC;QACT,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAC3C,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC,CAAC,CAAC;QACJ,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;QAExB,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;QAClD,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAChE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,OAAuB,EACvB,OAAmE;IAEnE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAE/E,qCAAqC;IACrC,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;IACnD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,kBAAkB,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,QAAQ,GAAG;QACf,EAAE;QACF,KAAK;QACL,WAAW;QACX,IAAI;QACJ,SAAS,EAAE,SAAS,IAAI,IAAI;QAC5B,SAAS,EAAE,SAAS,IAAI,KAAK;QAC7B,MAAM;QACN,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;QACtB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;KACvB,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACzC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC;QACvF,OAAO,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,OAAuB,EACvB,OAAmE;IAEnE,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC;IAEvB,6BAA6B;IAC7B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;IACnD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;IACnD,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QACnC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC;QACvF,OAAO,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAuB,EAAE,KAAc;IAC3E,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC1D,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,IAAI,CAAC,GAAG,EAAE,0BAA0B,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAC/B,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,YAAY;YACf,OAAO,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC1C,KAAK,WAAW;YACd,OAAO,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACzC,KAAK,cAAc;YACjB,OAAO,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5C,KAAK,iBAAiB;YACpB,OAAO,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC/C,KAAK,oBAAoB;YACvB,OAAO,qBAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACjD,KAAK,mBAAmB;YACtB,OAAO,qBAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACjD,KAAK,mBAAmB;YACtB,OAAO,qBAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACjD;YACE,OAAO,IAAI,CAAC,GAAG,EAAE,2BAA2B,CAAC,CAAC;IAClD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { StorageAdapter, UploadHandler } from "../types.js";
|
|
2
|
+
type RuntimeServices = {
|
|
3
|
+
adapter: StorageAdapter;
|
|
4
|
+
uploadHandler: UploadHandler;
|
|
5
|
+
};
|
|
6
|
+
export declare function getRuntimeServices(): Promise<RuntimeServices>;
|
|
7
|
+
export declare function __setRuntimeServicesForTests(services: Partial<RuntimeServices> | null): void;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/runtime/providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAQjE,KAAK,eAAe,GAAG;IACrB,OAAO,EAAE,cAAc,CAAC;IACxB,aAAa,EAAE,aAAa,CAAC;CAC9B,CAAC;AAoCF,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,eAAe,CAAC,CAiBnE;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,GACxC,IAAI,CAGN"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as runtimeProviders from "virtual:caretcms/providers";
|
|
2
|
+
let runtimeServicesPromise = null;
|
|
3
|
+
let testServicesOverride = null;
|
|
4
|
+
const providerModule = runtimeProviders;
|
|
5
|
+
async function createAdapter() {
|
|
6
|
+
if (testServicesOverride?.adapter) {
|
|
7
|
+
return testServicesOverride.adapter;
|
|
8
|
+
}
|
|
9
|
+
const configuredAdapter = await providerModule?.loadConfiguredStorage?.();
|
|
10
|
+
if (configuredAdapter) {
|
|
11
|
+
return configuredAdapter;
|
|
12
|
+
}
|
|
13
|
+
throw new Error("[caretcms] No storage provider was configured for the active runtime mode.");
|
|
14
|
+
}
|
|
15
|
+
async function createUploadHandler() {
|
|
16
|
+
if (testServicesOverride?.uploadHandler) {
|
|
17
|
+
return testServicesOverride.uploadHandler;
|
|
18
|
+
}
|
|
19
|
+
const configuredUploadHandler = await providerModule?.loadConfiguredUploadHandler?.();
|
|
20
|
+
if (configuredUploadHandler) {
|
|
21
|
+
return configuredUploadHandler;
|
|
22
|
+
}
|
|
23
|
+
throw new Error("[caretcms] No upload provider was configured for the active runtime mode.");
|
|
24
|
+
}
|
|
25
|
+
export async function getRuntimeServices() {
|
|
26
|
+
if (!runtimeServicesPromise) {
|
|
27
|
+
const inflight = Promise.all([createAdapter(), createUploadHandler()]).then(([adapter, uploadHandler]) => ({
|
|
28
|
+
adapter,
|
|
29
|
+
uploadHandler,
|
|
30
|
+
}));
|
|
31
|
+
inflight.catch(() => {
|
|
32
|
+
if (runtimeServicesPromise === inflight) {
|
|
33
|
+
runtimeServicesPromise = null;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
runtimeServicesPromise = inflight;
|
|
37
|
+
}
|
|
38
|
+
return runtimeServicesPromise;
|
|
39
|
+
}
|
|
40
|
+
export function __setRuntimeServicesForTests(services) {
|
|
41
|
+
testServicesOverride = services;
|
|
42
|
+
runtimeServicesPromise = null;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/runtime/providers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,gBAAgB,MAAM,4BAA4B,CAAC;AAY/D,IAAI,sBAAsB,GAAoC,IAAI,CAAC;AACnE,IAAI,oBAAoB,GAAoC,IAAI,CAAC;AACjE,MAAM,cAAc,GAAG,gBAAkC,CAAC;AAE1D,KAAK,UAAU,aAAa;IAC1B,IAAI,oBAAoB,EAAE,OAAO,EAAE,CAAC;QAClC,OAAO,oBAAoB,CAAC,OAAO,CAAC;IACtC,CAAC;IAED,MAAM,iBAAiB,GAAG,MAAM,cAAc,EAAE,qBAAqB,EAAE,EAAE,CAAC;IAC1E,IAAI,iBAAiB,EAAE,CAAC;QACtB,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IAED,MAAM,IAAI,KAAK,CACb,4EAA4E,CAC7E,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,mBAAmB;IAChC,IAAI,oBAAoB,EAAE,aAAa,EAAE,CAAC;QACxC,OAAO,oBAAoB,CAAC,aAAa,CAAC;IAC5C,CAAC;IAED,MAAM,uBAAuB,GAAG,MAAM,cAAc,EAAE,2BAA2B,EAAE,EAAE,CAAC;IACtF,IAAI,uBAAuB,EAAE,CAAC;QAC5B,OAAO,uBAAuB,CAAC;IACjC,CAAC;IAED,MAAM,IAAI,KAAK,CACb,2EAA2E,CAC5E,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,aAAa,EAAE,EAAE,mBAAmB,EAAE,CAAC,CAAC,CAAC,IAAI,CACzE,CAAC,CAAC,OAAO,EAAE,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7B,OAAO;YACP,aAAa;SACd,CAAC,CACH,CAAC;QACF,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE;YAClB,IAAI,sBAAsB,KAAK,QAAQ,EAAE,CAAC;gBACxC,sBAAsB,GAAG,IAAI,CAAC;YAChC,CAAC;QACH,CAAC,CAAC,CAAC;QACH,sBAAsB,GAAG,QAAQ,CAAC;IACpC,CAAC;IAED,OAAO,sBAAsB,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,QAAyC;IAEzC,oBAAoB,GAAG,QAAQ,CAAC;IAChC,sBAAsB,GAAG,IAAI,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { StorageAdapter, UploadHandler } from "../types.js";
|
|
2
|
+
export interface CaretRequestContext {
|
|
3
|
+
adapter: StorageAdapter;
|
|
4
|
+
uploadHandler: UploadHandler;
|
|
5
|
+
sessionId: string | null;
|
|
6
|
+
demoMode: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function runWithRequestContext<T>(context: CaretRequestContext, fn: () => Promise<T>): Promise<T>;
|
|
9
|
+
export declare function getRequestContext(): CaretRequestContext | undefined;
|
|
10
|
+
export declare function requireRequestContext(): CaretRequestContext;
|
|
11
|
+
//# sourceMappingURL=request-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../src/runtime/request-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,cAAc,CAAC;IACxB,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAID,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,OAAO,EAAE,mBAAmB,EAC5B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAEZ;AAED,wBAAgB,iBAAiB,IAAI,mBAAmB,GAAG,SAAS,CAEnE;AAED,wBAAgB,qBAAqB,IAAI,mBAAmB,CAQ3D"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
const storage = new AsyncLocalStorage();
|
|
3
|
+
export function runWithRequestContext(context, fn) {
|
|
4
|
+
return storage.run(context, fn);
|
|
5
|
+
}
|
|
6
|
+
export function getRequestContext() {
|
|
7
|
+
return storage.getStore();
|
|
8
|
+
}
|
|
9
|
+
export function requireRequestContext() {
|
|
10
|
+
const ctx = storage.getStore();
|
|
11
|
+
if (!ctx) {
|
|
12
|
+
throw new Error("[caretcms] No live request context found. Ensure the CMS middleware runs before page rendering (it registers with order: 'pre').");
|
|
13
|
+
}
|
|
14
|
+
return ctx;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=request-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../../src/runtime/request-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAUrD,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAuB,CAAC;AAE7D,MAAM,UAAU,qBAAqB,CACnC,OAA4B,EAC5B,EAAoB;IAEpB,OAAO,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,OAAO,CAAC,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC/B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,kIAAkI,CACnI,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response-rewriting engine for attribute-first CMS (ADR-005).
|
|
3
|
+
*
|
|
4
|
+
* Scans rendered HTML for data-caret and data-caret-scope attributes,
|
|
5
|
+
* batch-loads stored overrides from the StorageAdapter, and replaces
|
|
6
|
+
* element content (text) or src (images) with stored values.
|
|
7
|
+
*
|
|
8
|
+
* v1 scope: text-only leaf nodes + <img> src swaps.
|
|
9
|
+
* Elements with nested child markup are skipped (editor warns at runtime).
|
|
10
|
+
*/
|
|
11
|
+
import type { StorageAdapter } from "../types.js";
|
|
12
|
+
export declare function rewriteCaretAttributes(html: string, adapter: StorageAdapter): Promise<string>;
|
|
13
|
+
//# sourceMappingURL=rewrite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rewrite.d.ts","sourceRoot":"","sources":["../../src/runtime/rewrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAa,MAAM,aAAa,CAAC;AAyL7D,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,MAAM,CAAC,CAyHjB"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response-rewriting engine for attribute-first CMS (ADR-005).
|
|
3
|
+
*
|
|
4
|
+
* Scans rendered HTML for data-caret and data-caret-scope attributes,
|
|
5
|
+
* batch-loads stored overrides from the StorageAdapter, and replaces
|
|
6
|
+
* element content (text) or src (images) with stored values.
|
|
7
|
+
*
|
|
8
|
+
* v1 scope: text-only leaf nodes + <img> src swaps.
|
|
9
|
+
* Elements with nested child markup are skipped (editor warns at runtime).
|
|
10
|
+
*/
|
|
11
|
+
import { sanitizeHtml } from "./sanitize-html.js";
|
|
12
|
+
// --- Patterns ---
|
|
13
|
+
// Matches any element with data-caret attribute.
|
|
14
|
+
// Captures: tag name, full opening tag, data-caret value, content between tags, closing tag
|
|
15
|
+
//
|
|
16
|
+
// Stateful (g flag) regexes are constructed per-call inside rewriteCaretAttributes
|
|
17
|
+
// to avoid cross-request lastIndex contamination if the loop ever yields.
|
|
18
|
+
// Text-content elements: always emitted with an explicit closing tag, so we
|
|
19
|
+
// capture the inner content. <img> is handled separately by IMG_ELEMENT_PATTERN
|
|
20
|
+
// because it is a void element with no closing tag and (in standard HTML5
|
|
21
|
+
// output) no self-closing slash — it would never match this pattern.
|
|
22
|
+
const CARET_ELEMENT_PATTERN = /<(h[1-6]|p|span|small|strong|em|a|li|td|th|label|button|figcaption|blockquote|dt|dd|summary|caption|legend)\b([^>]*?\bdata-caret\s*=\s*"([^"]*)"[^>]*?)(?:\/>|>([\s\S]*?)<\/\1>)/;
|
|
23
|
+
// Void <img> with a data-caret binding. Closes on a bare `>` or a self-closing
|
|
24
|
+
// `/>` — never on a closing tag. Captures: 1 = attribute string, 2 = data-caret value.
|
|
25
|
+
const IMG_ELEMENT_PATTERN = /<img\b([^>]*?\bdata-caret\s*=\s*"([^"]*)"[^>]*?)\/?>/;
|
|
26
|
+
const TAG_PATTERN = /<\/?([a-zA-Z][\w:-]*)\b[^>]*>/;
|
|
27
|
+
const SCOPE_ATTR_RE = /\bdata-caret-scope\s*=\s*"([^"]+)"/i;
|
|
28
|
+
const RICH_ATTR_RE = /\bdata-caret-rich\b/i;
|
|
29
|
+
// Matches <img ... data-caret="..." ... src="..." ...> (self-closing or not)
|
|
30
|
+
// We need to find and replace the src attribute within the img tag
|
|
31
|
+
const IMG_SRC_RE = /\bsrc\s*=\s*"([^"]*)"/;
|
|
32
|
+
const VOID_TAGS = new Set([
|
|
33
|
+
"area",
|
|
34
|
+
"base",
|
|
35
|
+
"br",
|
|
36
|
+
"col",
|
|
37
|
+
"embed",
|
|
38
|
+
"hr",
|
|
39
|
+
"img",
|
|
40
|
+
"input",
|
|
41
|
+
"link",
|
|
42
|
+
"meta",
|
|
43
|
+
"param",
|
|
44
|
+
"source",
|
|
45
|
+
"track",
|
|
46
|
+
"wbr",
|
|
47
|
+
]);
|
|
48
|
+
function parseScope(tagHtml) {
|
|
49
|
+
const match = SCOPE_ATTR_RE.exec(tagHtml);
|
|
50
|
+
if (!match)
|
|
51
|
+
return null;
|
|
52
|
+
const parts = match[1].split("::");
|
|
53
|
+
if (parts.length !== 2)
|
|
54
|
+
return null;
|
|
55
|
+
return { collection: parts[0], id: parts[1] };
|
|
56
|
+
}
|
|
57
|
+
function popFrame(stack, tagName) {
|
|
58
|
+
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
59
|
+
if (stack[i].tagName === tagName) {
|
|
60
|
+
stack.splice(i, 1);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function advanceScopeStack(html, untilIndex, state, tagRe) {
|
|
66
|
+
tagRe.lastIndex = state.cursor;
|
|
67
|
+
let match;
|
|
68
|
+
while ((match = tagRe.exec(html)) !== null) {
|
|
69
|
+
if (match.index >= untilIndex)
|
|
70
|
+
break;
|
|
71
|
+
const tagHtml = match[0];
|
|
72
|
+
const tagName = match[1].toLowerCase();
|
|
73
|
+
const isClosing = tagHtml.startsWith("</");
|
|
74
|
+
const isSelfClosing = !isClosing && (tagHtml.endsWith("/>") || VOID_TAGS.has(tagName));
|
|
75
|
+
if (isClosing) {
|
|
76
|
+
popFrame(state.stack, tagName);
|
|
77
|
+
state.cursor = tagRe.lastIndex;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (!isSelfClosing) {
|
|
81
|
+
state.stack.push({
|
|
82
|
+
tagName,
|
|
83
|
+
scope: parseScope(tagHtml),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
state.cursor = tagRe.lastIndex;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function findNearestScope(stack) {
|
|
90
|
+
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
91
|
+
if (stack[i].scope)
|
|
92
|
+
return stack[i].scope;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
function resolveBinding(caretValue, scope) {
|
|
97
|
+
const parts = caretValue.split("::");
|
|
98
|
+
if (parts.length === 3) {
|
|
99
|
+
return { collection: parts[0], id: parts[1], field: parts[2] };
|
|
100
|
+
}
|
|
101
|
+
if (parts.length === 1 && !caretValue.includes("::")) {
|
|
102
|
+
// Field-only — resolve from nearest scope
|
|
103
|
+
if (!scope)
|
|
104
|
+
return null;
|
|
105
|
+
return { collection: scope.collection, id: scope.id, field: caretValue };
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
// --- Content detection ---
|
|
110
|
+
function hasChildMarkup(content) {
|
|
111
|
+
// If content contains any HTML tags, it has child markup
|
|
112
|
+
return /<[a-zA-Z]/.test(content);
|
|
113
|
+
}
|
|
114
|
+
// --- Entry cache ---
|
|
115
|
+
async function batchLoadEntries(adapter, keys) {
|
|
116
|
+
const entries = new Map();
|
|
117
|
+
const promises = [...keys].map(async (key) => {
|
|
118
|
+
const [collection, id] = key.split("::");
|
|
119
|
+
const entry = await adapter.getEntry(collection, id);
|
|
120
|
+
if (entry)
|
|
121
|
+
entries.set(key, entry);
|
|
122
|
+
});
|
|
123
|
+
await Promise.all(promises);
|
|
124
|
+
return entries;
|
|
125
|
+
}
|
|
126
|
+
function getNestedValue(data, path) {
|
|
127
|
+
const keys = path.split(".");
|
|
128
|
+
let current = data;
|
|
129
|
+
for (const key of keys) {
|
|
130
|
+
if (current === null || current === undefined)
|
|
131
|
+
return undefined;
|
|
132
|
+
if (typeof current !== "object")
|
|
133
|
+
return undefined;
|
|
134
|
+
current = current[key];
|
|
135
|
+
}
|
|
136
|
+
return typeof current === "string" ? current : undefined;
|
|
137
|
+
}
|
|
138
|
+
// --- Main rewrite ---
|
|
139
|
+
export async function rewriteCaretAttributes(html, adapter) {
|
|
140
|
+
// 1. Find all data-caret bindings and collect unique entry keys
|
|
141
|
+
const bindings = [];
|
|
142
|
+
const entryKeys = new Set();
|
|
143
|
+
const addBinding = (caretValue, attrs, content, isImg, scope, fullMatch, index) => {
|
|
144
|
+
const resolved = resolveBinding(caretValue, scope);
|
|
145
|
+
if (!resolved)
|
|
146
|
+
return;
|
|
147
|
+
entryKeys.add(`${resolved.collection}::${resolved.id}`);
|
|
148
|
+
bindings.push({
|
|
149
|
+
...resolved,
|
|
150
|
+
fullMatch,
|
|
151
|
+
contentStart: index,
|
|
152
|
+
contentEnd: index + fullMatch.length,
|
|
153
|
+
isImg,
|
|
154
|
+
isRich: !isImg && RICH_ATTR_RE.test(attrs),
|
|
155
|
+
hasChildMarkup: !isImg && hasChildMarkup(content),
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
// Each pass walks the document independently with its own scope stack, since
|
|
159
|
+
// void <img> and text elements are matched by separate patterns. Discovery
|
|
160
|
+
// order does not matter: replacements are applied in reverse index order below.
|
|
161
|
+
const discover = (pattern, onMatch) => {
|
|
162
|
+
const re = new RegExp(pattern.source, "gi");
|
|
163
|
+
const tagRe = new RegExp(TAG_PATTERN.source, "g");
|
|
164
|
+
const scopeState = { stack: [], cursor: 0 };
|
|
165
|
+
let match;
|
|
166
|
+
while ((match = re.exec(html)) !== null) {
|
|
167
|
+
advanceScopeStack(html, match.index, scopeState, tagRe);
|
|
168
|
+
onMatch(match, findNearestScope(scopeState.stack));
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
// Text-content elements: 1 = tag, 2 = attrs, 3 = data-caret value, 4 = content.
|
|
172
|
+
discover(CARET_ELEMENT_PATTERN, (match, scope) => {
|
|
173
|
+
addBinding(match[3], match[2], match[4] ?? "", false, scope, match[0], match.index);
|
|
174
|
+
});
|
|
175
|
+
// Void <img>: 1 = attrs, 2 = data-caret value (no content).
|
|
176
|
+
discover(IMG_ELEMENT_PATTERN, (match, scope) => {
|
|
177
|
+
addBinding(match[2], match[1], "", true, scope, match[0], match.index);
|
|
178
|
+
});
|
|
179
|
+
if (bindings.length === 0)
|
|
180
|
+
return html;
|
|
181
|
+
// 2. Batch-load entries
|
|
182
|
+
const entries = await batchLoadEntries(adapter, entryKeys);
|
|
183
|
+
if (entries.size === 0)
|
|
184
|
+
return html;
|
|
185
|
+
// 3. Apply replacements (reverse order to preserve indices)
|
|
186
|
+
let result = html;
|
|
187
|
+
const sorted = [...bindings].sort((a, b) => b.contentStart - a.contentStart);
|
|
188
|
+
for (const binding of sorted) {
|
|
189
|
+
// Skip elements with nested child markup (unless rich text, which expects HTML content)
|
|
190
|
+
if (binding.hasChildMarkup && !binding.isRich)
|
|
191
|
+
continue;
|
|
192
|
+
const entryKey = `${binding.collection}::${binding.id}`;
|
|
193
|
+
const entry = entries.get(entryKey);
|
|
194
|
+
if (!entry)
|
|
195
|
+
continue;
|
|
196
|
+
const storedValue = getNestedValue(entry.data, binding.field);
|
|
197
|
+
if (storedValue === undefined)
|
|
198
|
+
continue;
|
|
199
|
+
if (binding.isImg) {
|
|
200
|
+
// Replace src attribute value in the img tag
|
|
201
|
+
const imgTag = binding.fullMatch;
|
|
202
|
+
const srcMatch = IMG_SRC_RE.exec(imgTag);
|
|
203
|
+
if (srcMatch) {
|
|
204
|
+
// Function replacement (not a string) so `$`-sequences in the stored
|
|
205
|
+
// value — `$&`, `$1`, `` $` `` — are inserted literally instead of being
|
|
206
|
+
// interpreted as String.prototype.replace substitution patterns.
|
|
207
|
+
const newImgTag = imgTag.replace(srcMatch[0], () => `src="${escapeAttr(storedValue)}"`);
|
|
208
|
+
result =
|
|
209
|
+
result.slice(0, binding.contentStart) +
|
|
210
|
+
newImgTag +
|
|
211
|
+
result.slice(binding.contentEnd);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
// Replace text content between tags
|
|
216
|
+
// Find the opening tag end and closing tag start within the full match
|
|
217
|
+
const openTagEnd = binding.fullMatch.indexOf(">") + 1;
|
|
218
|
+
const closeTagStart = binding.fullMatch.lastIndexOf("</");
|
|
219
|
+
// `>=` so an empty bound element (`<h1 data-caret="x"></h1>`) — a valid
|
|
220
|
+
// placeholder awaiting its first override — is filled too. When content
|
|
221
|
+
// is empty the two indices are equal and the slices still reconstruct
|
|
222
|
+
// `<tag …>` + value + `</tag>` correctly.
|
|
223
|
+
if (openTagEnd > 0 && closeTagStart >= openTagEnd) {
|
|
224
|
+
const before = binding.fullMatch.slice(0, openTagEnd);
|
|
225
|
+
const after = binding.fullMatch.slice(closeTagStart);
|
|
226
|
+
const injected = binding.isRich
|
|
227
|
+
? sanitizeHtml(storedValue)
|
|
228
|
+
: escapeHtml(storedValue);
|
|
229
|
+
const newMatch = before + injected + after;
|
|
230
|
+
result =
|
|
231
|
+
result.slice(0, binding.contentStart) +
|
|
232
|
+
newMatch +
|
|
233
|
+
result.slice(binding.contentEnd);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
// --- Escaping ---
|
|
240
|
+
function escapeHtml(str) {
|
|
241
|
+
return str
|
|
242
|
+
.replace(/&/g, "&")
|
|
243
|
+
.replace(/</g, "<")
|
|
244
|
+
.replace(/>/g, ">");
|
|
245
|
+
}
|
|
246
|
+
function escapeAttr(str) {
|
|
247
|
+
return str
|
|
248
|
+
.replace(/&/g, "&")
|
|
249
|
+
.replace(/"/g, """)
|
|
250
|
+
.replace(/</g, "<")
|
|
251
|
+
.replace(/>/g, ">");
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=rewrite.js.map
|