@preprio/toolkit 0.5.0-rc.5 → 0.5.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +133 -26
  2. package/dist/astro/index.cjs +126 -30
  3. package/dist/astro/index.js +130 -30
  4. package/dist/{chunk-3X55JL52.js → chunk-HVS5QLWG.js} +1 -1
  5. package/dist/{chunk-TG6N3C22.js → chunk-IPMB32AZ.js} +1 -1
  6. package/dist/{chunk-CMUFTCG3.js → chunk-JUXP5KRX.js} +1 -1
  7. package/dist/{chunk-74KY5EAM.js → chunk-KD3GK6TO.js} +2 -0
  8. package/dist/{chunk-SBLZANL5.js → chunk-SUAFASBB.js} +11 -3
  9. package/dist/codegen/index.cjs +82 -0
  10. package/dist/codegen/index.d.cts +70 -0
  11. package/dist/codegen/index.d.ts +70 -0
  12. package/dist/codegen/index.js +64 -0
  13. package/dist/index.cjs +133 -28
  14. package/dist/index.d.cts +14 -2
  15. package/dist/index.d.ts +14 -2
  16. package/dist/index.js +108 -6
  17. package/dist/{live-preview-H4643QYF.js → live-preview-DLXGXRE5.js} +2 -2
  18. package/dist/nextjs/components.cjs +11 -3
  19. package/dist/nextjs/components.js +2 -2
  20. package/dist/nextjs/index.cjs +124 -28
  21. package/dist/nextjs/index.js +128 -28
  22. package/dist/nextjs/live-action.js +3 -3
  23. package/dist/nextjs/live.js +1 -1
  24. package/dist/nuxt/chunk-BWPGO25E.js +340 -0
  25. package/dist/nuxt/index.cjs +1 -1
  26. package/dist/nuxt/index.js +1 -1
  27. package/dist/nuxt/{live-preview-O2ZTJNW4.js → live-preview-2RCU35W5.js} +6 -337
  28. package/dist/nuxt/live.cjs +1007 -911
  29. package/dist/nuxt/live.js +109 -5
  30. package/dist/react/components.cjs +11 -3
  31. package/dist/react/components.js +11 -3
  32. package/dist/sveltekit/chunk-BWPGO25E.js +340 -0
  33. package/dist/sveltekit/index.cjs +1 -1
  34. package/dist/sveltekit/index.js +1 -1
  35. package/dist/sveltekit/{live-preview-O2ZTJNW4.js → live-preview-2RCU35W5.js} +6 -337
  36. package/dist/sveltekit/live.cjs +1008 -909
  37. package/dist/sveltekit/live.js +118 -11
  38. package/package.json +11 -1
@@ -25,423 +25,764 @@ var __copyProps = (to, from, except, desc) => {
25
25
  };
26
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
27
 
28
- // src/core/utils.ts
29
- function getTrustedParentOrigin() {
30
- return trustedParentOrigin;
31
- }
32
- function sendPreprEvent(event, data, options) {
33
- if (typeof window !== "undefined") {
34
- const message = {
35
- name: "prepr_preview_bar",
36
- event,
37
- ...data
38
- };
39
- window.dispatchEvent(
40
- new CustomEvent("prepr_preview_bar", { detail: message })
41
- );
42
- if (window.parent && window.parent !== window) {
43
- if (trustedParentOrigin) {
44
- window.parent.postMessage(message, trustedParentOrigin);
45
- } else if (options?.allowUntrustedTarget) {
46
- window.parent.postMessage(message, "*");
28
+ // src/core/live-preview/query-document.ts
29
+ function tokenize(source) {
30
+ const tokens = [];
31
+ let i = 0;
32
+ const n = source.length;
33
+ while (i < n) {
34
+ const ch = source[i];
35
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "," || ch === "\uFEFF") {
36
+ i++;
37
+ } else if (ch === "#") {
38
+ while (i < n && source[i] !== "\n" && source[i] !== "\r") i++;
39
+ } else if (ch === "." && source.startsWith("...", i)) {
40
+ tokens.push({ type: "punct", value: "..." });
41
+ i += 3;
42
+ } else if (PUNCT.has(ch)) {
43
+ tokens.push({ type: "punct", value: ch });
44
+ i++;
45
+ } else if (NAME_START.test(ch)) {
46
+ const start = i;
47
+ while (i < n && NAME_CONTINUE.test(source[i])) i++;
48
+ tokens.push({ type: "name", value: source.slice(start, i) });
49
+ } else if (ch === '"') {
50
+ if (source.startsWith('"""', i)) {
51
+ i += 3;
52
+ while (i < n && !source.startsWith('"""', i)) {
53
+ i += source.startsWith('\\"""', i) ? 4 : 1;
54
+ }
55
+ if (i >= n) throw new SyntaxError("Unterminated block string");
56
+ i += 3;
57
+ } else {
58
+ i++;
59
+ while (i < n && source[i] !== '"') {
60
+ if (source[i] === "\n" || source[i] === "\r") {
61
+ throw new SyntaxError("Unterminated string");
62
+ }
63
+ i += source[i] === "\\" ? 2 : 1;
64
+ }
65
+ if (i >= n) throw new SyntaxError("Unterminated string");
66
+ i++;
47
67
  }
68
+ tokens.push({ type: "value" });
69
+ } else if (ch === "-" || ch >= "0" && ch <= "9") {
70
+ i++;
71
+ while (i < n && /[0-9.eE+-]/.test(source[i])) i++;
72
+ tokens.push({ type: "value" });
73
+ } else {
74
+ throw new SyntaxError(`Unexpected character "${ch}"`);
48
75
  }
49
76
  }
77
+ return tokens;
50
78
  }
51
- function createScopedLogger(scopeName) {
52
- return new DebugLogger({
53
- prefix: `[Prepr][${scopeName}]`
54
- });
55
- }
56
- var trustedParentOrigin, DebugLogger, globalDebugLogger;
57
- var init_utils = __esm({
58
- "src/core/utils.ts"() {
59
- "use strict";
60
- trustedParentOrigin = null;
61
- DebugLogger = class {
62
- constructor(options) {
63
- this.options = {
64
- prefix: "[Prepr]",
65
- ...options
66
- };
67
- }
68
- // A local `enabled` wins; otherwise defer to the global logger so scoped
69
- // loggers created before initDebugLogger() still pick up the setting.
70
- isEnabled() {
71
- if (this.options.enabled !== void 0) {
72
- return this.options.enabled;
73
- }
74
- return globalDebugLogger?.options?.enabled ?? false;
75
- }
76
- log(message, ...args) {
77
- if (!this.isEnabled()) return;
78
- const prefix = this.options.prefix;
79
- console.log(`${prefix} ${message}`, ...args);
80
- }
81
- warn(message, ...args) {
82
- if (!this.isEnabled()) return;
83
- const prefix = this.options.prefix;
84
- console.warn(`${prefix} ${message}`, ...args);
85
- }
86
- error(message, ...args) {
87
- if (!this.isEnabled()) return;
88
- const prefix = this.options.prefix;
89
- console.error(`${prefix} ${message}`, ...args);
79
+ function parseSource(source, operationName) {
80
+ const tokens = tokenize(source);
81
+ let pos = 0;
82
+ const peek = () => tokens[pos];
83
+ const isPunct = (value) => {
84
+ const t = tokens[pos];
85
+ return t?.type === "punct" && t.value === value;
86
+ };
87
+ const expectPunct = (value) => {
88
+ if (!isPunct(value)) throw new SyntaxError(`Expected "${value}"`);
89
+ pos++;
90
+ };
91
+ const expectName = () => {
92
+ const t = tokens[pos];
93
+ if (t?.type !== "name") throw new SyntaxError("Expected a name");
94
+ pos++;
95
+ return t.value;
96
+ };
97
+ const skipParens = () => {
98
+ expectPunct("(");
99
+ let depth = 1;
100
+ while (depth > 0) {
101
+ const t = tokens[pos++];
102
+ if (!t) throw new SyntaxError("Unbalanced parentheses");
103
+ if (t.type === "punct" && t.value === "(") depth++;
104
+ else if (t.type === "punct" && t.value === ")") depth--;
105
+ }
106
+ };
107
+ const skipDirectives = () => {
108
+ while (isPunct("@")) {
109
+ pos++;
110
+ expectName();
111
+ if (isPunct("(")) skipParens();
112
+ }
113
+ };
114
+ const parseSelectionSet = () => {
115
+ expectPunct("{");
116
+ const selections = [];
117
+ while (!isPunct("}")) {
118
+ if (!peek()) throw new SyntaxError("Unterminated selection set");
119
+ selections.push(parseSelection());
120
+ }
121
+ pos++;
122
+ return selections;
123
+ };
124
+ const parseSelection = () => {
125
+ if (isPunct("...")) {
126
+ pos++;
127
+ const t = peek();
128
+ if (t?.type === "name" && t.value !== "on") {
129
+ pos++;
130
+ skipDirectives();
131
+ return { kind: "spread", name: t.value };
90
132
  }
91
- };
92
- globalDebugLogger = null;
93
- }
94
- });
95
-
96
- // src/core/live-preview/index-snapshot.ts
97
- function indexSnapshot(data) {
98
- const index = /* @__PURE__ */ new Map();
99
- const visited = /* @__PURE__ */ new WeakSet();
100
- const path = [];
101
- function walk(node) {
102
- if (node === null || typeof node !== "object") return;
103
- if (visited.has(node)) return;
104
- visited.add(node);
105
- if (Array.isArray(node)) {
106
- for (let i = 0; i < node.length; i++) {
107
- path.push(i);
108
- walk(node[i]);
109
- path.pop();
133
+ let on = null;
134
+ if (t?.type === "name") {
135
+ pos++;
136
+ on = expectName();
110
137
  }
111
- return;
138
+ skipDirectives();
139
+ return { kind: "inline", on, selections: parseSelectionSet() };
112
140
  }
113
- const record = node;
114
- if (typeof record._id === "string") {
115
- const paths = index.get(record._id);
116
- if (paths) paths.push([...path]);
117
- else index.set(record._id, [[...path]]);
118
- } else if (typeof record.__typename === "string") {
119
- debug.log("item without _id will not live-update", {
120
- __typename: record.__typename
121
- });
141
+ let name = expectName();
142
+ const key = name;
143
+ if (isPunct(":")) {
144
+ pos++;
145
+ name = expectName();
122
146
  }
123
- for (const key in record) {
124
- if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
125
- path.push(key);
126
- walk(record[key]);
127
- path.pop();
147
+ let hasArgs = false;
148
+ if (isPunct("(")) {
149
+ hasArgs = true;
150
+ skipParens();
151
+ }
152
+ skipDirectives();
153
+ const selections = isPunct("{") ? parseSelectionSet() : null;
154
+ return { kind: "field", name, key, hasArgs, selections };
155
+ };
156
+ const fragments = /* @__PURE__ */ new Map();
157
+ const operations = [];
158
+ while (pos < tokens.length) {
159
+ if (isPunct("{")) {
160
+ operations.push({ name: null, selections: parseSelectionSet() });
161
+ continue;
162
+ }
163
+ const keyword = expectName();
164
+ if (keyword === "fragment") {
165
+ const name = expectName();
166
+ if (expectName() !== "on") throw new SyntaxError('Expected "on"');
167
+ const on = expectName();
168
+ skipDirectives();
169
+ fragments.set(name, { on, selections: parseSelectionSet() });
170
+ } else if (keyword === "query" || keyword === "mutation" || keyword === "subscription") {
171
+ const t = peek();
172
+ let name = null;
173
+ if (t?.type === "name") name = expectName();
174
+ if (isPunct("(")) skipParens();
175
+ skipDirectives();
176
+ operations.push({ name, selections: parseSelectionSet() });
177
+ } else {
178
+ throw new SyntaxError(`Unexpected "${keyword}"`);
128
179
  }
129
180
  }
130
- walk(data);
131
- return index;
132
- }
133
- var debug;
134
- var init_index_snapshot = __esm({
135
- "src/core/live-preview/index-snapshot.ts"() {
136
- "use strict";
137
- init_utils();
138
- debug = createScopedLogger("live-preview");
139
- }
140
- });
141
-
142
- // src/core/live-preview/field-name.ts
143
- function graphqlFieldName(apiId) {
144
- return apiId.replace(UPPERCASE_BOUNDARY, "$1_").toLowerCase();
181
+ return { operation: pickOperation(operations, operationName), fragments };
145
182
  }
146
- function fieldKeyIn(record, apiId) {
147
- if (Object.prototype.hasOwnProperty.call(record, apiId)) return apiId;
148
- const name = graphqlFieldName(apiId);
149
- return Object.prototype.hasOwnProperty.call(record, name) ? name : void 0;
183
+ function pickOperation(operations, operationName) {
184
+ const op = operationName ? operations.find((o) => o.name === operationName) : operations[0];
185
+ if (!op) throw new SyntaxError("The document has no operation");
186
+ return op.selections;
150
187
  }
151
- function plainTextFromHtml(html) {
152
- return html.replace(TAG_PATTERN, "");
153
- }
154
- function isTextEntry(record) {
155
- const typename = record.__typename;
156
- if (typeof typename === "string") return typename === "Text";
157
- return "html" in record || "text" in record && "format" in record;
188
+ function fromAstSelections(set) {
189
+ return set.selections.map((s) => {
190
+ if (s.kind === "FragmentSpread")
191
+ return { kind: "spread", name: s.name.value };
192
+ if (s.kind === "InlineFragment") {
193
+ return {
194
+ kind: "inline",
195
+ on: s.typeCondition?.name.value ?? null,
196
+ selections: fromAstSelections(s.selectionSet)
197
+ };
198
+ }
199
+ const name = s.name.value;
200
+ return {
201
+ kind: "field",
202
+ name,
203
+ key: s.alias?.value ?? name,
204
+ hasArgs: Boolean(s.arguments?.length),
205
+ selections: s.selectionSet ? fromAstSelections(s.selectionSet) : null
206
+ };
207
+ });
158
208
  }
159
- var UPPERCASE_BOUNDARY, TEXT_BODY_FIELDS, TAG_PATTERN;
160
- var init_field_name = __esm({
161
- "src/core/live-preview/field-name.ts"() {
162
- "use strict";
163
- UPPERCASE_BOUNDARY = /(.)(?=[A-Z])/g;
164
- TEXT_BODY_FIELDS = [
165
- { name: "body", plain: false },
166
- { name: "html", plain: false },
167
- { name: "text", plain: true }
168
- ];
169
- TAG_PATTERN = /<[^>]+>/g;
209
+ function fromAst(doc, operationName) {
210
+ const fragments = /* @__PURE__ */ new Map();
211
+ const operations = [];
212
+ for (const def of doc.definitions ?? []) {
213
+ if (def.kind === "FragmentDefinition" && def.name && def.typeCondition && def.selectionSet) {
214
+ fragments.set(def.name.value, {
215
+ on: def.typeCondition.name.value,
216
+ selections: fromAstSelections(def.selectionSet)
217
+ });
218
+ } else if (def.kind === "OperationDefinition" && def.selectionSet) {
219
+ operations.push({
220
+ name: def.name?.value ?? null,
221
+ selections: fromAstSelections(def.selectionSet)
222
+ });
223
+ }
170
224
  }
171
- });
172
-
173
- // src/core/live-preview/patch.ts
174
- function isPlainObject(value) {
175
- return value !== null && typeof value === "object" && !Array.isArray(value);
225
+ return { operation: pickOperation(operations, operationName), fragments };
176
226
  }
177
- function mergeObject(current, incoming) {
178
- let changed = false;
179
- const next = { ...current };
180
- for (const key of Object.keys(current)) {
181
- if (!(key in incoming) || incoming[key] === void 0) continue;
182
- const value = isPlainObject(current[key]) && isPlainObject(incoming[key]) ? mergeObject(current[key], incoming[key]) : incoming[key];
183
- if (value !== current[key]) {
184
- next[key] = value;
185
- changed = true;
227
+ function readQueryDocument(query, operationName) {
228
+ const cacheKey = operationName ?? "";
229
+ if (typeof query === "object" && query !== null && query.kind === "Document") {
230
+ let byName = astCache.get(query);
231
+ if (!byName) astCache.set(query, byName = /* @__PURE__ */ new Map());
232
+ let parsed2 = byName.get(cacheKey);
233
+ if (!parsed2) {
234
+ parsed2 = fromAst(
235
+ query,
236
+ operationName
237
+ );
238
+ byName.set(cacheKey, parsed2);
186
239
  }
240
+ return parsed2;
187
241
  }
188
- return changed ? next : current;
189
- }
190
- function shapeLike(template, incoming) {
191
- const out = {};
192
- for (const key of Object.keys(template)) {
193
- const value = incoming[key];
194
- out[key] = value === void 0 ? null : isPlainObject(template[key]) && isPlainObject(value) ? shapeLike(template[key], value) : value;
242
+ const source = String(query);
243
+ const sourceKey = `${cacheKey}
244
+ ${source}`;
245
+ let parsed = sourceCache.get(sourceKey);
246
+ if (!parsed) {
247
+ parsed = parseSource(source, operationName);
248
+ if (sourceCache.size >= SOURCE_CACHE_LIMIT) {
249
+ sourceCache.delete(sourceCache.keys().next().value);
250
+ }
251
+ sourceCache.set(sourceKey, parsed);
195
252
  }
196
- return out;
253
+ return parsed;
197
254
  }
198
- function mergeStructured(current, incoming) {
199
- if (incoming === null || Array.isArray(incoming) && !incoming.length) {
200
- if (Array.isArray(current)) return current.length ? [] : current;
201
- return null;
202
- }
203
- const list = Array.isArray(incoming) ? incoming : [incoming];
204
- if (current === null || current === void 0) return incoming;
205
- if (Array.isArray(current)) {
206
- const template = current.find(isPlainObject);
207
- return list.map((entry) => {
208
- const match = current.find(
209
- (c) => isPlainObject(c) && c._id !== void 0 && c._id === entry._id
210
- );
211
- if (isPlainObject(match)) return mergeObject(match, entry);
212
- return isPlainObject(template) ? shapeLike(template, entry) : entry;
213
- });
255
+ function collectFields(selections, typename, doc, options = {}) {
256
+ const cacheKey = `${options.strict ? "s" : "l"}:${typename ?? ""}`;
257
+ let cached = fieldsCache.get(selections);
258
+ if (!cached) {
259
+ cached = /* @__PURE__ */ new Map();
260
+ fieldsCache.set(selections, cached);
214
261
  }
215
- if (isPlainObject(current)) return mergeObject(current, list[0]);
216
- return current;
217
- }
218
- function isPatchable(current) {
219
- return current === null || typeof current === "string" || typeof current === "number" || typeof current === "boolean";
220
- }
221
- function cloneNode(node) {
222
- return Array.isArray(node) ? [...node] : { ...node };
262
+ const hit = cached.get(cacheKey);
263
+ if (hit) return hit;
264
+ const fields = resolveFields(selections, typename, doc, options);
265
+ cached.set(cacheKey, fields);
266
+ return fields;
223
267
  }
224
- function patchSnapshot(snapshot, index, itemId, field, value) {
225
- const paths = index.get(itemId);
226
- if (!paths?.length) return snapshot;
227
- let root = snapshot;
228
- let changed = false;
229
- const markChanged = () => {
230
- changed = true;
268
+ function resolveFields(selections, typename, doc, options) {
269
+ const exact = /* @__PURE__ */ new Map();
270
+ const loose = /* @__PURE__ */ new Map();
271
+ const merged = /* @__PURE__ */ new Map();
272
+ const walkedMatched = /* @__PURE__ */ new Set();
273
+ const walkedLoose = /* @__PURE__ */ new Set();
274
+ const add = (target, s) => {
275
+ let field = target.get(s.key);
276
+ if (!field) {
277
+ field = { name: s.name, key: s.key, hasArgs: s.hasArgs, selections: [] };
278
+ target.set(s.key, field);
279
+ merged.set(field, /* @__PURE__ */ new Set());
280
+ }
281
+ const done = merged.get(field);
282
+ if (s.selections && !done.has(s.selections)) {
283
+ done.add(s.selections);
284
+ field.selections = [...field.selections, ...s.selections];
285
+ }
231
286
  };
232
- for (const path of paths) {
233
- for (const [target, targetValue] of expandTextBody(
234
- root,
235
- path,
236
- field,
237
- value
238
- )) {
239
- root = patchAtPath(root, path, target, targetValue, markChanged);
287
+ const applies = (on, list) => conditionMatches(on, typename) || typename !== void 0 && typeConditions(list, doc).includes(typename);
288
+ const walk2 = (list, matched) => {
289
+ for (const s of list) {
290
+ if (s.kind === "field") {
291
+ add(matched ? exact : loose, s);
292
+ } else if (s.kind === "inline") {
293
+ walk2(s.selections, matched && applies(s.on, s.selections));
294
+ } else {
295
+ const fragment = doc.fragments.get(s.name);
296
+ if (!fragment) continue;
297
+ const asMatched = matched && applies(fragment.on, fragment.selections);
298
+ if (walkedMatched.has(fragment)) continue;
299
+ if (!asMatched && walkedLoose.has(fragment)) continue;
300
+ (asMatched ? walkedMatched : walkedLoose).add(fragment);
301
+ walk2(fragment.selections, asMatched);
302
+ }
303
+ }
304
+ };
305
+ walk2(selections, true);
306
+ if (!options.strict) {
307
+ for (const [key, field] of loose) {
308
+ if (!exact.has(key)) exact.set(key, field);
240
309
  }
241
310
  }
242
- return changed ? root : snapshot;
311
+ return exact;
243
312
  }
244
- function expandTextBody(root, path, field, value) {
245
- const segments = field.split(".");
246
- if (segments[segments.length - 1] !== "body" || typeof value !== "string") {
247
- return [[field, value]];
248
- }
249
- let node = root;
250
- for (const step of path) {
251
- node = node[step];
252
- }
253
- for (const segment of segments.slice(0, -1)) {
254
- if (node === null || typeof node !== "object") return [[field, value]];
255
- const record2 = node;
256
- node = INDEX_SEGMENT.test(segment) ? record2[Number(segment)] : record2[fieldKeyIn(record2, segment) ?? segment];
257
- }
258
- if (node === null || typeof node !== "object" || Array.isArray(node)) {
259
- return [[field, value]];
260
- }
261
- const record = node;
262
- if (!isTextEntry(record)) return [[field, value]];
263
- const prefix = segments.slice(0, -1);
264
- const writes = [];
265
- for (const { name, plain } of TEXT_BODY_FIELDS) {
266
- if (!(name in record)) continue;
267
- writes.push([
268
- [...prefix, name].join("."),
269
- plain ? plainTextFromHtml(value) : value
270
- ]);
271
- }
272
- return writes.length ? writes : [[field, value]];
313
+ function conditionMatches(on, typename) {
314
+ return on === null || typename === void 0 || on === typename;
273
315
  }
274
- function patchAtPath(root, path, field, value, markChanged) {
275
- const segments = field.split(".");
276
- if (segments.some((s) => FORBIDDEN_SEGMENTS.has(s))) return root;
277
- let node = root;
278
- for (const step of path) {
279
- node = node[step];
280
- }
281
- const fullPath = [...path];
282
- for (const segment of segments) {
283
- let step = segment;
284
- if (INDEX_SEGMENT.test(segment)) {
285
- step = Number(segment);
286
- } else if (node !== null && typeof node === "object") {
287
- step = fieldKeyIn(node, segment) ?? segment;
316
+ function typeConditions(selections, doc) {
317
+ const hit = conditionsCache.get(selections);
318
+ if (hit) return hit;
319
+ const names = /* @__PURE__ */ new Set();
320
+ const seen = /* @__PURE__ */ new Set();
321
+ const walk2 = (list) => {
322
+ for (const s of list) {
323
+ if (s.kind === "inline") {
324
+ if (s.on) names.add(s.on);
325
+ walk2(s.selections);
326
+ } else if (s.kind === "spread") {
327
+ const fragment = doc.fragments.get(s.name);
328
+ if (!fragment || seen.has(fragment)) continue;
329
+ seen.add(fragment);
330
+ names.add(fragment.on);
331
+ walk2(fragment.selections);
332
+ }
288
333
  }
289
- fullPath.push(step);
290
- node = node !== null && typeof node === "object" ? node[step] : void 0;
291
- }
292
- return writeAtPath(root, fullPath, value, markChanged);
334
+ };
335
+ walk2(selections);
336
+ const conditions = [...names];
337
+ conditionsCache.set(selections, conditions);
338
+ return conditions;
293
339
  }
294
- function writeAtPath(root, fullPath, value, markChanged = () => {
295
- }) {
296
- if (!fullPath.length) return root;
297
- if (fullPath.some((s) => FORBIDDEN_SEGMENTS.has(String(s)))) return root;
298
- const last = fullPath.length - 1;
299
- let node = root;
300
- for (let i = 0; i < last; i++) {
301
- if (node === null || typeof node !== "object") return root;
302
- node = node[fullPath[i]];
303
- }
304
- if (node === null || typeof node !== "object") return root;
305
- const container = node;
306
- const key = fullPath[last];
307
- if (!(key in container)) return root;
308
- let next;
309
- if (value === null || typeof value === "object") {
310
- next = mergeStructured(container[key], value);
311
- } else if (!isPatchable(container[key])) {
312
- return root;
313
- } else {
314
- next = keepEditLink(container[key], value);
315
- }
316
- if (container[key] === next) return root;
317
- const newRoot = cloneNode(root);
318
- let cursor = newRoot;
319
- for (let i = 0; i < last; i++) {
320
- const child = cloneNode(cursor[fullPath[i]]);
321
- cursor[fullPath[i]] = child;
322
- cursor = child;
340
+ var NAME_START, NAME_CONTINUE, PUNCT, astCache, sourceCache, SOURCE_CACHE_LIMIT, fieldsCache, conditionsCache;
341
+ var init_query_document = __esm({
342
+ "src/core/live-preview/query-document.ts"() {
343
+ "use strict";
344
+ NAME_START = /[_A-Za-z]/;
345
+ NAME_CONTINUE = /[_0-9A-Za-z]/;
346
+ PUNCT = /* @__PURE__ */ new Set([
347
+ "!",
348
+ "$",
349
+ "&",
350
+ "(",
351
+ ")",
352
+ ":",
353
+ "=",
354
+ "@",
355
+ "[",
356
+ "]",
357
+ "{",
358
+ "|",
359
+ "}"
360
+ ]);
361
+ astCache = /* @__PURE__ */ new WeakMap();
362
+ sourceCache = /* @__PURE__ */ new Map();
363
+ SOURCE_CACHE_LIMIT = 200;
364
+ fieldsCache = /* @__PURE__ */ new WeakMap();
365
+ conditionsCache = /* @__PURE__ */ new WeakMap();
323
366
  }
324
- cursor[key] = next;
325
- markChanged();
326
- return newRoot;
367
+ });
368
+
369
+ // src/core/utils.ts
370
+ function getTrustedParentOrigin() {
371
+ return trustedParentOrigin;
327
372
  }
328
- function listKeysOf(entry) {
329
- if (!isPlainObject(entry)) return [];
330
- const keys = [];
331
- if (typeof entry._id === "string") keys.push(entry._id);
332
- const first = Array.isArray(entry.items) ? entry.items[0] : null;
333
- if (isPlainObject(first) && typeof first._id === "string") {
334
- keys.push(first._id);
373
+ function sendPreprEvent(event, data, options) {
374
+ if (typeof window !== "undefined") {
375
+ const message = {
376
+ name: "prepr_preview_bar",
377
+ event,
378
+ ...data
379
+ };
380
+ window.dispatchEvent(
381
+ new CustomEvent("prepr_preview_bar", { detail: message })
382
+ );
383
+ if (window.parent && window.parent !== window) {
384
+ if (trustedParentOrigin) {
385
+ window.parent.postMessage(message, trustedParentOrigin);
386
+ } else if (options?.allowUntrustedTarget) {
387
+ window.parent.postMessage(message, "*");
388
+ }
389
+ }
335
390
  }
336
- return keys;
337
391
  }
338
- function buildText(entry, template) {
339
- const body = typeof entry.body === "string" ? entry.body : "";
340
- const full = {
341
- __typename: "Text",
342
- _id: entry._id,
343
- body,
344
- html: body,
345
- text: plainTextFromHtml(body),
346
- format: entry.format ?? null
347
- };
348
- if (!template) return full;
349
- const out = {};
350
- for (const key of Object.keys(template)) {
351
- out[key] = key in full ? full[key] : null;
352
- }
353
- return out;
392
+ function createScopedLogger(scopeName) {
393
+ return new DebugLogger({
394
+ prefix: `[Prepr][${scopeName}]`
395
+ });
354
396
  }
355
- function syncList(current, entries, build) {
356
- if (current === null || current === void 0) {
357
- const built = syncList([], entries, build);
358
- return built.length ? built : current;
397
+ var trustedParentOrigin, DebugLogger, globalDebugLogger;
398
+ var init_utils = __esm({
399
+ "src/core/utils.ts"() {
400
+ "use strict";
401
+ trustedParentOrigin = null;
402
+ DebugLogger = class {
403
+ constructor(options) {
404
+ this.options = {
405
+ prefix: "[Prepr]",
406
+ ...options
407
+ };
408
+ }
409
+ // A local `enabled` wins; otherwise defer to the global logger so scoped
410
+ // loggers created before initDebugLogger() still pick up the setting.
411
+ isEnabled() {
412
+ if (this.options.enabled !== void 0) {
413
+ return this.options.enabled;
414
+ }
415
+ return globalDebugLogger?.options?.enabled ?? false;
416
+ }
417
+ log(message, ...args) {
418
+ if (!this.isEnabled()) return;
419
+ const prefix = this.options.prefix;
420
+ console.log(`${prefix} ${message}`, ...args);
421
+ }
422
+ warn(message, ...args) {
423
+ if (!this.isEnabled()) return;
424
+ const prefix = this.options.prefix;
425
+ console.warn(`${prefix} ${message}`, ...args);
426
+ }
427
+ error(message, ...args) {
428
+ if (!this.isEnabled()) return;
429
+ const prefix = this.options.prefix;
430
+ console.error(`${prefix} ${message}`, ...args);
431
+ }
432
+ };
433
+ globalDebugLogger = null;
359
434
  }
360
- if (!Array.isArray(current)) return current;
361
- const byKey = /* @__PURE__ */ new Map();
362
- const templates = /* @__PURE__ */ new Map();
363
- for (const c of current) {
364
- for (const key of listKeysOf(c)) byKey.set(key, c);
365
- if (isPlainObject(c) && typeof c.__typename === "string") {
366
- if (!templates.has(c.__typename)) templates.set(c.__typename, c);
435
+ });
436
+
437
+ // src/core/live-preview/index-snapshot.ts
438
+ function indexSnapshot(data) {
439
+ const index = /* @__PURE__ */ new Map();
440
+ const visited = /* @__PURE__ */ new WeakSet();
441
+ const path = [];
442
+ function walk2(node) {
443
+ if (node === null || typeof node !== "object") return;
444
+ if (visited.has(node)) return;
445
+ visited.add(node);
446
+ if (Array.isArray(node)) {
447
+ for (let i = 0; i < node.length; i++) {
448
+ path.push(i);
449
+ walk2(node[i]);
450
+ path.pop();
451
+ }
452
+ return;
367
453
  }
368
- }
369
- if (!entries.length) return current.length ? [] : current;
370
- if (current.length && !byKey.size) return current;
371
- if (current.length && !entries.some((e) => byKey.has(e._id))) return current;
372
- const next = [];
373
- for (const entry of entries) {
374
- const existing = byKey.get(entry._id);
375
- const built = existing === void 0 && entry.fields ? build?.(entry) : void 0;
376
- if (existing !== void 0) {
377
- next.push(existing);
378
- } else if (built) {
379
- next.push(built);
380
- } else if (entry.__typename === "Text") {
381
- next.push(buildText(entry, templates.get("Text")));
454
+ const record = node;
455
+ if (typeof record._id === "string") {
456
+ const paths = index.get(record._id);
457
+ if (paths) paths.push([...path]);
458
+ else index.set(record._id, [[...path]]);
459
+ } else if (typeof record.__typename === "string") {
460
+ debug.log("item without _id will not live-update", {
461
+ __typename: record.__typename
462
+ });
463
+ }
464
+ for (const key in record) {
465
+ if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
466
+ path.push(key);
467
+ walk2(record[key]);
468
+ path.pop();
382
469
  }
383
470
  }
384
- const same = next.length === current.length && next.every((n, i) => n === current[i]);
385
- return same ? current : next;
386
- }
387
- function syncListSnapshot(snapshot, index, itemId, field, entries) {
388
- const paths = index.get(itemId);
389
- if (!paths?.length) return snapshot;
390
- const fieldPath = field.split(".");
391
- if (fieldPath.some((s) => FORBIDDEN_SEGMENTS.has(s))) return snapshot;
392
- let root = snapshot;
393
- for (const path of paths) {
394
- root = syncListAtPath(root, [...path, ...fieldPath], entries);
395
- }
396
- return root;
397
- }
398
- function syncListAtPath(root, fullPath, entries, build) {
399
- if (!fullPath.length) return root;
400
- if (fullPath.some((s) => FORBIDDEN_SEGMENTS.has(String(s)))) return root;
401
- let node = root;
402
- for (const key of fullPath) {
403
- node = node !== null && typeof node === "object" ? node[key] : void 0;
404
- }
405
- const next = syncList(node, entries, build);
406
- if (next === node) return root;
407
- const newRoot = cloneNode(root);
408
- let cursor = newRoot;
409
- for (let i = 0; i < fullPath.length - 1; i++) {
410
- const child = cloneNode(cursor[fullPath[i]]);
411
- cursor[fullPath[i]] = child;
412
- cursor = child;
413
- }
414
- cursor[fullPath[fullPath.length - 1]] = next;
415
- return newRoot;
416
- }
417
- function keepEditLink(current, value) {
418
- if (typeof current !== "string" || typeof value !== "string") return value;
419
- const { encoded } = (0, import_stega.vercelStegaSplit)(current);
420
- if (!encoded || (0, import_stega.vercelStegaSplit)(value).encoded) return value;
421
- return value + encoded;
471
+ walk2(data);
472
+ return index;
422
473
  }
423
- var import_stega, INDEX_SEGMENT, FORBIDDEN_SEGMENTS;
424
- var init_patch = __esm({
425
- "src/core/live-preview/patch.ts"() {
474
+ var debug;
475
+ var init_index_snapshot = __esm({
476
+ "src/core/live-preview/index-snapshot.ts"() {
426
477
  "use strict";
427
- import_stega = require("@vercel/stega");
428
- init_field_name();
429
- INDEX_SEGMENT = /^\d+$/;
430
- FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
478
+ init_utils();
479
+ debug = createScopedLogger("live-preview");
431
480
  }
432
481
  });
433
482
 
434
- // src/core/live-preview/live-store.ts
435
- function withPatch(log2, patch) {
436
- let previous = -1;
437
- for (let i = log2.length - 1; i >= 0; i--) {
438
- const p = log2[i];
439
- if (p.type === patch.type && p.itemId === patch.itemId && p.field === patch.field) {
440
- previous = i;
441
- break;
442
- }
443
- }
444
- if (previous === -1) return [...log2, patch];
483
+ // src/core/live-preview/field-name.ts
484
+ function graphqlFieldName(apiId) {
485
+ return apiId.replace(UPPERCASE_BOUNDARY, "$1_").toLowerCase();
486
+ }
487
+ function fieldKeyIn(record, apiId) {
488
+ if (Object.prototype.hasOwnProperty.call(record, apiId)) return apiId;
489
+ const name = graphqlFieldName(apiId);
490
+ return Object.prototype.hasOwnProperty.call(record, name) ? name : void 0;
491
+ }
492
+ function plainTextFromHtml(html) {
493
+ return html.replace(TAG_PATTERN, "");
494
+ }
495
+ function isTextEntry(record) {
496
+ const typename = record.__typename;
497
+ if (typeof typename === "string") return typename === "Text";
498
+ return "html" in record || "text" in record && "format" in record;
499
+ }
500
+ var UPPERCASE_BOUNDARY, TEXT_BODY_FIELDS, TAG_PATTERN;
501
+ var init_field_name = __esm({
502
+ "src/core/live-preview/field-name.ts"() {
503
+ "use strict";
504
+ UPPERCASE_BOUNDARY = /(.)(?=[A-Z])/g;
505
+ TEXT_BODY_FIELDS = [
506
+ { name: "body", plain: false },
507
+ { name: "html", plain: false },
508
+ { name: "text", plain: true }
509
+ ];
510
+ TAG_PATTERN = /<[^>]+>/g;
511
+ }
512
+ });
513
+
514
+ // src/core/live-preview/patch.ts
515
+ function isPlainObject(value) {
516
+ return value !== null && typeof value === "object" && !Array.isArray(value);
517
+ }
518
+ function mergeObject(current, incoming) {
519
+ let changed = false;
520
+ const next = { ...current };
521
+ for (const key of Object.keys(current)) {
522
+ if (!(key in incoming) || incoming[key] === void 0) continue;
523
+ const value = isPlainObject(current[key]) && isPlainObject(incoming[key]) ? mergeObject(current[key], incoming[key]) : incoming[key];
524
+ if (value !== current[key]) {
525
+ next[key] = value;
526
+ changed = true;
527
+ }
528
+ }
529
+ return changed ? next : current;
530
+ }
531
+ function shapeLike(template, incoming) {
532
+ const out = {};
533
+ for (const key of Object.keys(template)) {
534
+ const value = incoming[key];
535
+ out[key] = value === void 0 ? null : isPlainObject(template[key]) && isPlainObject(value) ? shapeLike(template[key], value) : value;
536
+ }
537
+ return out;
538
+ }
539
+ function mergeStructured(current, incoming) {
540
+ if (incoming === null || Array.isArray(incoming) && !incoming.length) {
541
+ if (Array.isArray(current)) return current.length ? [] : current;
542
+ return null;
543
+ }
544
+ const list = Array.isArray(incoming) ? incoming : [incoming];
545
+ if (current === null || current === void 0) return incoming;
546
+ if (Array.isArray(current)) {
547
+ const template = current.find(isPlainObject);
548
+ return list.map((entry) => {
549
+ const match = current.find(
550
+ (c) => isPlainObject(c) && c._id !== void 0 && c._id === entry._id
551
+ );
552
+ if (isPlainObject(match)) return mergeObject(match, entry);
553
+ return isPlainObject(template) ? shapeLike(template, entry) : entry;
554
+ });
555
+ }
556
+ if (isPlainObject(current)) return mergeObject(current, list[0]);
557
+ return current;
558
+ }
559
+ function isPatchable(current) {
560
+ return current === null || typeof current === "string" || typeof current === "number" || typeof current === "boolean";
561
+ }
562
+ function cloneNode(node) {
563
+ return Array.isArray(node) ? [...node] : { ...node };
564
+ }
565
+ function patchSnapshot(snapshot, index, itemId, field, value) {
566
+ const paths = index.get(itemId);
567
+ if (!paths?.length) return snapshot;
568
+ let root = snapshot;
569
+ let changed = false;
570
+ const markChanged = () => {
571
+ changed = true;
572
+ };
573
+ for (const path of paths) {
574
+ for (const [target, targetValue] of expandTextBody(
575
+ root,
576
+ path,
577
+ field,
578
+ value
579
+ )) {
580
+ root = patchAtPath(root, path, target, targetValue, markChanged);
581
+ }
582
+ }
583
+ return changed ? root : snapshot;
584
+ }
585
+ function expandTextBody(root, path, field, value) {
586
+ const segments = field.split(".");
587
+ if (segments[segments.length - 1] !== "body" || typeof value !== "string") {
588
+ return [[field, value]];
589
+ }
590
+ let node = root;
591
+ for (const step of path) {
592
+ node = node[step];
593
+ }
594
+ for (const segment of segments.slice(0, -1)) {
595
+ if (node === null || typeof node !== "object") return [[field, value]];
596
+ const record2 = node;
597
+ node = INDEX_SEGMENT.test(segment) ? record2[Number(segment)] : record2[fieldKeyIn(record2, segment) ?? segment];
598
+ }
599
+ if (node === null || typeof node !== "object" || Array.isArray(node)) {
600
+ return [[field, value]];
601
+ }
602
+ const record = node;
603
+ if (!isTextEntry(record)) return [[field, value]];
604
+ const prefix = segments.slice(0, -1);
605
+ const writes = [];
606
+ for (const { name, plain } of TEXT_BODY_FIELDS) {
607
+ if (!(name in record)) continue;
608
+ writes.push([
609
+ [...prefix, name].join("."),
610
+ plain ? plainTextFromHtml(value) : value
611
+ ]);
612
+ }
613
+ return writes.length ? writes : [[field, value]];
614
+ }
615
+ function patchAtPath(root, path, field, value, markChanged) {
616
+ const segments = field.split(".");
617
+ if (segments.some((s) => FORBIDDEN_SEGMENTS.has(s))) return root;
618
+ let node = root;
619
+ for (const step of path) {
620
+ node = node[step];
621
+ }
622
+ const fullPath = [...path];
623
+ for (const segment of segments) {
624
+ let step = segment;
625
+ if (INDEX_SEGMENT.test(segment)) {
626
+ step = Number(segment);
627
+ } else if (node !== null && typeof node === "object") {
628
+ step = fieldKeyIn(node, segment) ?? segment;
629
+ }
630
+ fullPath.push(step);
631
+ node = node !== null && typeof node === "object" ? node[step] : void 0;
632
+ }
633
+ return writeAtPath(root, fullPath, value, markChanged);
634
+ }
635
+ function writeAtPath(root, fullPath, value, markChanged = () => {
636
+ }) {
637
+ if (!fullPath.length) return root;
638
+ if (fullPath.some((s) => FORBIDDEN_SEGMENTS.has(String(s)))) return root;
639
+ const last = fullPath.length - 1;
640
+ let node = root;
641
+ for (let i = 0; i < last; i++) {
642
+ if (node === null || typeof node !== "object") return root;
643
+ node = node[fullPath[i]];
644
+ }
645
+ if (node === null || typeof node !== "object") return root;
646
+ const container = node;
647
+ const key = fullPath[last];
648
+ if (!(key in container)) return root;
649
+ let next;
650
+ if (value === null || typeof value === "object") {
651
+ next = mergeStructured(container[key], value);
652
+ } else if (!isPatchable(container[key])) {
653
+ return root;
654
+ } else {
655
+ next = keepEditLink(container[key], value);
656
+ }
657
+ if (container[key] === next) return root;
658
+ const newRoot = cloneNode(root);
659
+ let cursor = newRoot;
660
+ for (let i = 0; i < last; i++) {
661
+ const child = cloneNode(cursor[fullPath[i]]);
662
+ cursor[fullPath[i]] = child;
663
+ cursor = child;
664
+ }
665
+ cursor[key] = next;
666
+ markChanged();
667
+ return newRoot;
668
+ }
669
+ function listKeysOf(entry) {
670
+ if (!isPlainObject(entry)) return [];
671
+ const keys = [];
672
+ if (typeof entry._id === "string") keys.push(entry._id);
673
+ const first = Array.isArray(entry.items) ? entry.items[0] : null;
674
+ if (isPlainObject(first) && typeof first._id === "string") {
675
+ keys.push(first._id);
676
+ }
677
+ return keys;
678
+ }
679
+ function buildText(entry, template) {
680
+ const body = typeof entry.body === "string" ? entry.body : "";
681
+ const full = {
682
+ __typename: "Text",
683
+ _id: entry._id,
684
+ body,
685
+ html: body,
686
+ text: plainTextFromHtml(body),
687
+ format: entry.format ?? null
688
+ };
689
+ if (!template) return full;
690
+ const out = {};
691
+ for (const key of Object.keys(template)) {
692
+ out[key] = key in full ? full[key] : null;
693
+ }
694
+ return out;
695
+ }
696
+ function syncList(current, entries, build) {
697
+ if (current === null || current === void 0) {
698
+ const built = syncList([], entries, build);
699
+ return built.length ? built : current;
700
+ }
701
+ if (!Array.isArray(current)) return current;
702
+ const byKey = /* @__PURE__ */ new Map();
703
+ const templates = /* @__PURE__ */ new Map();
704
+ for (const c of current) {
705
+ for (const key of listKeysOf(c)) byKey.set(key, c);
706
+ if (isPlainObject(c) && typeof c.__typename === "string") {
707
+ if (!templates.has(c.__typename)) templates.set(c.__typename, c);
708
+ }
709
+ }
710
+ if (!entries.length) return current.length ? [] : current;
711
+ if (current.length && !byKey.size) return current;
712
+ if (current.length && !entries.some((e) => byKey.has(e._id))) return current;
713
+ const next = [];
714
+ for (const entry of entries) {
715
+ const existing = byKey.get(entry._id);
716
+ const built = existing === void 0 && entry.fields ? build?.(entry) : void 0;
717
+ if (existing !== void 0) {
718
+ next.push(existing);
719
+ } else if (built) {
720
+ next.push(built);
721
+ } else if (entry.__typename === "Text") {
722
+ next.push(buildText(entry, templates.get("Text")));
723
+ }
724
+ }
725
+ const same = next.length === current.length && next.every((n, i) => n === current[i]);
726
+ return same ? current : next;
727
+ }
728
+ function syncListSnapshot(snapshot, index, itemId, field, entries) {
729
+ const paths = index.get(itemId);
730
+ if (!paths?.length) return snapshot;
731
+ const fieldPath = field.split(".");
732
+ if (fieldPath.some((s) => FORBIDDEN_SEGMENTS.has(s))) return snapshot;
733
+ let root = snapshot;
734
+ for (const path of paths) {
735
+ root = syncListAtPath(root, [...path, ...fieldPath], entries);
736
+ }
737
+ return root;
738
+ }
739
+ function syncListAtPath(root, fullPath, entries, build) {
740
+ if (!fullPath.length) return root;
741
+ if (fullPath.some((s) => FORBIDDEN_SEGMENTS.has(String(s)))) return root;
742
+ let node = root;
743
+ for (const key of fullPath) {
744
+ node = node !== null && typeof node === "object" ? node[key] : void 0;
745
+ }
746
+ const next = syncList(node, entries, build);
747
+ if (next === node) return root;
748
+ const newRoot = cloneNode(root);
749
+ let cursor = newRoot;
750
+ for (let i = 0; i < fullPath.length - 1; i++) {
751
+ const child = cloneNode(cursor[fullPath[i]]);
752
+ cursor[fullPath[i]] = child;
753
+ cursor = child;
754
+ }
755
+ cursor[fullPath[fullPath.length - 1]] = next;
756
+ return newRoot;
757
+ }
758
+ function keepEditLink(current, value) {
759
+ if (typeof current !== "string" || typeof value !== "string") return value;
760
+ const { encoded } = (0, import_stega2.vercelStegaSplit)(current);
761
+ if (!encoded || (0, import_stega2.vercelStegaSplit)(value).encoded) return value;
762
+ return value + encoded;
763
+ }
764
+ var import_stega2, INDEX_SEGMENT, FORBIDDEN_SEGMENTS;
765
+ var init_patch = __esm({
766
+ "src/core/live-preview/patch.ts"() {
767
+ "use strict";
768
+ import_stega2 = require("@vercel/stega");
769
+ init_field_name();
770
+ INDEX_SEGMENT = /^\d+$/;
771
+ FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
772
+ }
773
+ });
774
+
775
+ // src/core/live-preview/live-store.ts
776
+ function withPatch(log2, patch) {
777
+ let previous = -1;
778
+ for (let i = log2.length - 1; i >= 0; i--) {
779
+ const p = log2[i];
780
+ if (p.type === patch.type && p.itemId === patch.itemId && p.field === patch.field) {
781
+ previous = i;
782
+ break;
783
+ }
784
+ }
785
+ if (previous === -1) return [...log2, patch];
445
786
  const since = log2.slice(previous + 1);
446
787
  const listPrefix = `${patch.field}.`;
447
788
  const dependsOnPosition = since.some(
@@ -624,537 +965,196 @@ function isAllowedEditorOrigin(origin, allowed) {
624
965
  if (!url.hostname.endsWith(suffix)) return false;
625
966
  const label = url.hostname.slice(0, -suffix.length);
626
967
  return label.length > 0 && !label.includes(".");
627
- }
628
- var debug3, EDITOR_BASE_DOMAIN;
629
- var init_iframe_bridge = __esm({
630
- "src/core/iframe-bridge.ts"() {
631
- "use strict";
632
- init_utils();
633
- debug3 = createScopedLogger("iframe-bridge");
634
- EDITOR_BASE_DOMAIN = "prepr.io";
635
- }
636
- });
637
-
638
- // src/core/live-preview/validate.ts
639
- function isScalar(v) {
640
- return typeof v === "string" || typeof v === "number" || typeof v === "boolean";
641
- }
642
- function isSafeLinkValue(value) {
643
- const stripped = value.replace(URL_IGNORED_ANYWHERE, "");
644
- let start = 0;
645
- while (start < stripped.length && stripped.charCodeAt(start) <= 32) start++;
646
- return !SCRIPT_SCHEME.test(stripped.slice(start));
647
- }
648
- function isUrlKey(key) {
649
- return URL_KEY.test(key);
650
- }
651
- function isSafeKeyValue(key, value) {
652
- if (typeof value !== "string") return true;
653
- if (key === "url") return SAFE_URL.test(value);
654
- return !isUrlKey(key) || isSafeLinkValue(value);
655
- }
656
- function isLiveValue(v, depth = 0) {
657
- if (v === null || isScalar(v)) return true;
658
- if (depth >= MAX_DEPTH) return false;
659
- if (Array.isArray(v)) {
660
- return depth === 0 && v.length <= MAX_ENTRIES && v.every((entry) => isPlainRecord(entry) && isLiveValue(entry, depth + 1));
661
- }
662
- if (!isPlainRecord(v)) return false;
663
- const keys = Object.keys(v);
664
- if (keys.length > MAX_ENTRIES) return false;
665
- return keys.every((key) => {
666
- if (FORBIDDEN_KEYS.has(key)) return false;
667
- const value = v[key];
668
- if (!isSafeKeyValue(key, value)) return false;
669
- return !Array.isArray(value) && isLiveValue(value, depth + 1);
670
- });
671
- }
672
- function isLiveFieldChange(field, value) {
673
- if (!isLiveValue(value)) return false;
674
- const name = field.slice(field.lastIndexOf(".") + 1);
675
- return typeof value !== "string" || !isUrlKey(name) || isSafeLinkValue(value);
676
- }
677
- function parseListEntries(value) {
678
- if (!Array.isArray(value) || value.length > MAX_LIST_ENTRIES) return null;
679
- const list = [];
680
- for (const raw of value) {
681
- if (!isPlainRecord(raw) || typeof raw._id !== "string" || !raw._id) {
682
- return null;
683
- }
684
- const entry = { _id: raw._id };
685
- if (typeof raw.__typename === "string") entry.__typename = raw.__typename;
686
- if (typeof raw.body === "string") entry.body = raw.body;
687
- if (typeof raw.format === "string" || raw.format === null) {
688
- entry.format = raw.format;
689
- }
690
- if (typeof raw.typeName === "string" && raw.typeName.length <= 200) {
691
- entry.typeName = raw.typeName;
692
- }
693
- if (raw.fields !== void 0) {
694
- if (!isPlainRecord(raw.fields) || !isFieldValue(raw.fields, 0)) {
695
- return null;
696
- }
697
- entry.fields = raw.fields;
698
- }
699
- list.push(entry);
700
- }
701
- return list;
702
- }
703
- function isPlainRecord(v) {
704
- return v !== null && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype;
705
- }
706
- function isFieldValue(v, depth) {
707
- if (v === null || isScalar(v)) return true;
708
- if (depth >= MAX_FIELD_DEPTH) return false;
709
- if (Array.isArray(v)) {
710
- return v.length <= MAX_ENTRIES && v.every(
711
- (entry) => isScalar(entry) || isPlainRecord(entry) && isFieldValue(entry, depth + 1)
712
- );
713
- }
714
- if (!isPlainRecord(v)) return false;
715
- const keys = Object.keys(v);
716
- if (keys.length > MAX_FIELD_KEYS) return false;
717
- return keys.every((key) => {
718
- if (FORBIDDEN_KEYS.has(key)) return false;
719
- const value = v[key];
720
- if (!isSafeKeyValue(key, value)) return false;
721
- return isFieldValue(value, depth + 1);
722
- });
723
- }
724
- function parseEditTarget(raw) {
725
- if (!isPlainRecord(raw)) return void 0;
726
- const { item, field } = raw;
727
- if (typeof item !== "string" || !item || item.length > MAX_EDIT_LENGTH) {
728
- return void 0;
729
- }
730
- if (typeof field !== "string" || !field || field.length > MAX_EDIT_LENGTH) {
731
- return void 0;
732
- }
733
- return { item, field };
734
- }
735
- var MAX_DEPTH, MAX_ENTRIES, FORBIDDEN_KEYS, SAFE_URL, URL_KEY, URL_IGNORED_ANYWHERE, SCRIPT_SCHEME, MAX_LIST_ENTRIES, MAX_FIELD_DEPTH, MAX_FIELD_KEYS, MAX_EDIT_LENGTH;
736
- var init_validate = __esm({
737
- "src/core/live-preview/validate.ts"() {
738
- "use strict";
739
- MAX_DEPTH = 3;
740
- MAX_ENTRIES = 50;
741
- FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
742
- SAFE_URL = /^https?:\/\//i;
743
- URL_KEY = /^(?:url|href|src|link)$|(?:_url|Url)$/;
744
- URL_IGNORED_ANYWHERE = /[\t\n\r]/g;
745
- SCRIPT_SCHEME = /^(?:javascript|vbscript|data):/i;
746
- MAX_LIST_ENTRIES = 200;
747
- MAX_FIELD_DEPTH = 6;
748
- MAX_FIELD_KEYS = 100;
749
- MAX_EDIT_LENGTH = 1e3;
750
- }
751
- });
752
-
753
- // src/core/live-preview/bridge-events.ts
754
- function createLiveBridge(store2, options = {}) {
755
- let parentOrigin = null;
756
- const onMessage = (evt) => {
757
- const data = evt?.data;
758
- if (data?.event === "prepr:initVE" && !parentOrigin) {
759
- if (isAllowedEditorOrigin(evt.origin, options.allowedEditorOrigins?.())) {
760
- parentOrigin = evt.origin;
761
- } else {
762
- debug4.warn("ignored live preview handshake from origin", evt.origin);
763
- }
764
- return;
765
- }
766
- const trusted = parentOrigin ?? getTrustedParentOrigin();
767
- if (!trusted || evt.origin !== trusted) return;
768
- if (data?.event === "prepr:fieldChange") {
769
- if (typeof data.itemId === "string" && typeof data.field === "string" && data.field && isLiveFieldChange(data.field, data.value)) {
770
- const edit = parseEditTarget(data.edit);
771
- if (edit) store2.pushPatch(data.itemId, data.field, data.value, edit);
772
- else store2.pushPatch(data.itemId, data.field, data.value);
773
- } else {
774
- debug4.log("dropped malformed fieldChange", data);
775
- }
776
- return;
777
- }
778
- if (data?.event === "prepr:listChange") {
779
- const list = parseListEntries(data.entries);
780
- if (typeof data.itemId === "string" && typeof data.field === "string" && data.field && list) {
781
- store2.pushList(data.itemId, data.field, list);
782
- } else {
783
- debug4.log("dropped malformed listChange", data);
784
- }
785
- return;
786
- }
787
- if (data?.event === "prepr:itemChanged") {
788
- if (typeof data.itemId === "string") {
789
- store2.invalidate(data.itemId);
790
- } else {
791
- debug4.log("dropped malformed itemChanged", data);
792
- }
793
- }
794
- };
795
- return {
796
- start() {
797
- window.addEventListener("message", onMessage);
798
- sendPreprEvent(
799
- "live_ready",
800
- options.strictMode?.() ? { strictMode: true } : void 0,
801
- { allowUntrustedTarget: true }
802
- );
803
- },
804
- stop() {
805
- window.removeEventListener("message", onMessage);
806
- }
807
- };
808
- }
809
- var debug4;
810
- var init_bridge_events = __esm({
811
- "src/core/live-preview/bridge-events.ts"() {
968
+ }
969
+ var debug3, EDITOR_BASE_DOMAIN;
970
+ var init_iframe_bridge = __esm({
971
+ "src/core/iframe-bridge.ts"() {
812
972
  "use strict";
813
973
  init_utils();
814
- init_iframe_bridge();
815
- init_validate();
816
- debug4 = createScopedLogger("live-preview:bridge");
974
+ debug3 = createScopedLogger("iframe-bridge");
975
+ EDITOR_BASE_DOMAIN = "prepr.io";
817
976
  }
818
977
  });
819
978
 
820
- // src/core/live-preview/query-document.ts
821
- function tokenize(source) {
822
- const tokens = [];
823
- let i = 0;
824
- const n = source.length;
825
- while (i < n) {
826
- const ch = source[i];
827
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "," || ch === "\uFEFF") {
828
- i++;
829
- } else if (ch === "#") {
830
- while (i < n && source[i] !== "\n" && source[i] !== "\r") i++;
831
- } else if (ch === "." && source.startsWith("...", i)) {
832
- tokens.push({ type: "punct", value: "..." });
833
- i += 3;
834
- } else if (PUNCT.has(ch)) {
835
- tokens.push({ type: "punct", value: ch });
836
- i++;
837
- } else if (NAME_START.test(ch)) {
838
- const start = i;
839
- while (i < n && NAME_CONTINUE.test(source[i])) i++;
840
- tokens.push({ type: "name", value: source.slice(start, i) });
841
- } else if (ch === '"') {
842
- if (source.startsWith('"""', i)) {
843
- i += 3;
844
- while (i < n && !source.startsWith('"""', i)) {
845
- i += source.startsWith('\\"""', i) ? 4 : 1;
846
- }
847
- if (i >= n) throw new SyntaxError("Unterminated block string");
848
- i += 3;
849
- } else {
850
- i++;
851
- while (i < n && source[i] !== '"') {
852
- if (source[i] === "\n" || source[i] === "\r") {
853
- throw new SyntaxError("Unterminated string");
854
- }
855
- i += source[i] === "\\" ? 2 : 1;
856
- }
857
- if (i >= n) throw new SyntaxError("Unterminated string");
858
- i++;
859
- }
860
- tokens.push({ type: "value" });
861
- } else if (ch === "-" || ch >= "0" && ch <= "9") {
862
- i++;
863
- while (i < n && /[0-9.eE+-]/.test(source[i])) i++;
864
- tokens.push({ type: "value" });
865
- } else {
866
- throw new SyntaxError(`Unexpected character "${ch}"`);
867
- }
868
- }
869
- return tokens;
979
+ // src/core/live-preview/validate.ts
980
+ function isScalar(v) {
981
+ return typeof v === "string" || typeof v === "number" || typeof v === "boolean";
870
982
  }
871
- function parseSource(source, operationName) {
872
- const tokens = tokenize(source);
873
- let pos = 0;
874
- const peek = () => tokens[pos];
875
- const isPunct = (value) => {
876
- const t = tokens[pos];
877
- return t?.type === "punct" && t.value === value;
878
- };
879
- const expectPunct = (value) => {
880
- if (!isPunct(value)) throw new SyntaxError(`Expected "${value}"`);
881
- pos++;
882
- };
883
- const expectName = () => {
884
- const t = tokens[pos];
885
- if (t?.type !== "name") throw new SyntaxError("Expected a name");
886
- pos++;
887
- return t.value;
888
- };
889
- const skipParens = () => {
890
- expectPunct("(");
891
- let depth = 1;
892
- while (depth > 0) {
893
- const t = tokens[pos++];
894
- if (!t) throw new SyntaxError("Unbalanced parentheses");
895
- if (t.type === "punct" && t.value === "(") depth++;
896
- else if (t.type === "punct" && t.value === ")") depth--;
897
- }
898
- };
899
- const skipDirectives = () => {
900
- while (isPunct("@")) {
901
- pos++;
902
- expectName();
903
- if (isPunct("(")) skipParens();
904
- }
905
- };
906
- const parseSelectionSet = () => {
907
- expectPunct("{");
908
- const selections = [];
909
- while (!isPunct("}")) {
910
- if (!peek()) throw new SyntaxError("Unterminated selection set");
911
- selections.push(parseSelection());
912
- }
913
- pos++;
914
- return selections;
915
- };
916
- const parseSelection = () => {
917
- if (isPunct("...")) {
918
- pos++;
919
- const t = peek();
920
- if (t?.type === "name" && t.value !== "on") {
921
- pos++;
922
- skipDirectives();
923
- return { kind: "spread", name: t.value };
924
- }
925
- let on = null;
926
- if (t?.type === "name") {
927
- pos++;
928
- on = expectName();
929
- }
930
- skipDirectives();
931
- return { kind: "inline", on, selections: parseSelectionSet() };
932
- }
933
- let name = expectName();
934
- const key = name;
935
- if (isPunct(":")) {
936
- pos++;
937
- name = expectName();
938
- }
939
- let hasArgs = false;
940
- if (isPunct("(")) {
941
- hasArgs = true;
942
- skipParens();
943
- }
944
- skipDirectives();
945
- const selections = isPunct("{") ? parseSelectionSet() : null;
946
- return { kind: "field", name, key, hasArgs, selections };
947
- };
948
- const fragments = /* @__PURE__ */ new Map();
949
- const operations = [];
950
- while (pos < tokens.length) {
951
- if (isPunct("{")) {
952
- operations.push({ name: null, selections: parseSelectionSet() });
953
- continue;
954
- }
955
- const keyword = expectName();
956
- if (keyword === "fragment") {
957
- const name = expectName();
958
- if (expectName() !== "on") throw new SyntaxError('Expected "on"');
959
- const on = expectName();
960
- skipDirectives();
961
- fragments.set(name, { on, selections: parseSelectionSet() });
962
- } else if (keyword === "query" || keyword === "mutation" || keyword === "subscription") {
963
- const t = peek();
964
- let name = null;
965
- if (t?.type === "name") name = expectName();
966
- if (isPunct("(")) skipParens();
967
- skipDirectives();
968
- operations.push({ name, selections: parseSelectionSet() });
969
- } else {
970
- throw new SyntaxError(`Unexpected "${keyword}"`);
971
- }
972
- }
973
- return { operation: pickOperation(operations, operationName), fragments };
983
+ function isSafeLinkValue(value) {
984
+ const stripped = value.replace(URL_IGNORED_ANYWHERE, "");
985
+ let start = 0;
986
+ while (start < stripped.length && stripped.charCodeAt(start) <= 32) start++;
987
+ return !SCRIPT_SCHEME.test(stripped.slice(start));
974
988
  }
975
- function pickOperation(operations, operationName) {
976
- const op = operationName ? operations.find((o) => o.name === operationName) : operations[0];
977
- if (!op) throw new SyntaxError("The document has no operation");
978
- return op.selections;
989
+ function isUrlKey(key) {
990
+ return URL_KEY.test(key);
979
991
  }
980
- function fromAstSelections(set) {
981
- return set.selections.map((s) => {
982
- if (s.kind === "FragmentSpread")
983
- return { kind: "spread", name: s.name.value };
984
- if (s.kind === "InlineFragment") {
985
- return {
986
- kind: "inline",
987
- on: s.typeCondition?.name.value ?? null,
988
- selections: fromAstSelections(s.selectionSet)
989
- };
990
- }
991
- const name = s.name.value;
992
- return {
993
- kind: "field",
994
- name,
995
- key: s.alias?.value ?? name,
996
- hasArgs: Boolean(s.arguments?.length),
997
- selections: s.selectionSet ? fromAstSelections(s.selectionSet) : null
998
- };
999
- });
992
+ function isSafeKeyValue(key, value) {
993
+ if (typeof value !== "string") return true;
994
+ if (key === "url") return SAFE_URL.test(value);
995
+ return !isUrlKey(key) || isSafeLinkValue(value);
1000
996
  }
1001
- function fromAst(doc, operationName) {
1002
- const fragments = /* @__PURE__ */ new Map();
1003
- const operations = [];
1004
- for (const def of doc.definitions ?? []) {
1005
- if (def.kind === "FragmentDefinition" && def.name && def.typeCondition && def.selectionSet) {
1006
- fragments.set(def.name.value, {
1007
- on: def.typeCondition.name.value,
1008
- selections: fromAstSelections(def.selectionSet)
1009
- });
1010
- } else if (def.kind === "OperationDefinition" && def.selectionSet) {
1011
- operations.push({
1012
- name: def.name?.value ?? null,
1013
- selections: fromAstSelections(def.selectionSet)
1014
- });
1015
- }
997
+ function isLiveValue(v, depth = 0) {
998
+ if (v === null || isScalar(v)) return true;
999
+ if (depth >= MAX_DEPTH) return false;
1000
+ if (Array.isArray(v)) {
1001
+ return depth === 0 && v.length <= MAX_ENTRIES && v.every((entry) => isPlainRecord(entry) && isLiveValue(entry, depth + 1));
1016
1002
  }
1017
- return { operation: pickOperation(operations, operationName), fragments };
1003
+ if (!isPlainRecord(v)) return false;
1004
+ const keys = Object.keys(v);
1005
+ if (keys.length > MAX_ENTRIES) return false;
1006
+ return keys.every((key) => {
1007
+ if (FORBIDDEN_KEYS.has(key)) return false;
1008
+ const value = v[key];
1009
+ if (!isSafeKeyValue(key, value)) return false;
1010
+ return !Array.isArray(value) && isLiveValue(value, depth + 1);
1011
+ });
1018
1012
  }
1019
- function readQueryDocument(query, operationName) {
1020
- const cacheKey = operationName ?? "";
1021
- if (typeof query === "object" && query !== null && query.kind === "Document") {
1022
- let byName = astCache.get(query);
1023
- if (!byName) astCache.set(query, byName = /* @__PURE__ */ new Map());
1024
- let parsed2 = byName.get(cacheKey);
1025
- if (!parsed2) {
1026
- parsed2 = fromAst(
1027
- query,
1028
- operationName
1029
- );
1030
- byName.set(cacheKey, parsed2);
1013
+ function isLiveFieldChange(field, value) {
1014
+ if (!isLiveValue(value)) return false;
1015
+ const name = field.slice(field.lastIndexOf(".") + 1);
1016
+ return typeof value !== "string" || !isUrlKey(name) || isSafeLinkValue(value);
1017
+ }
1018
+ function parseListEntries(value) {
1019
+ if (!Array.isArray(value) || value.length > MAX_LIST_ENTRIES) return null;
1020
+ const list = [];
1021
+ for (const raw of value) {
1022
+ if (!isPlainRecord(raw) || typeof raw._id !== "string" || !raw._id) {
1023
+ return null;
1024
+ }
1025
+ const entry = { _id: raw._id };
1026
+ if (typeof raw.__typename === "string") entry.__typename = raw.__typename;
1027
+ if (typeof raw.body === "string") entry.body = raw.body;
1028
+ if (typeof raw.format === "string" || raw.format === null) {
1029
+ entry.format = raw.format;
1031
1030
  }
1032
- return parsed2;
1033
- }
1034
- const source = String(query);
1035
- const sourceKey = `${cacheKey}
1036
- ${source}`;
1037
- let parsed = sourceCache.get(sourceKey);
1038
- if (!parsed) {
1039
- parsed = parseSource(source, operationName);
1040
- if (sourceCache.size >= SOURCE_CACHE_LIMIT) {
1041
- sourceCache.delete(sourceCache.keys().next().value);
1031
+ if (typeof raw.typeName === "string" && raw.typeName.length <= 200) {
1032
+ entry.typeName = raw.typeName;
1042
1033
  }
1043
- sourceCache.set(sourceKey, parsed);
1034
+ if (raw.fields !== void 0) {
1035
+ if (!isPlainRecord(raw.fields) || !isFieldValue(raw.fields, 0)) {
1036
+ return null;
1037
+ }
1038
+ entry.fields = raw.fields;
1039
+ }
1040
+ list.push(entry);
1044
1041
  }
1045
- return parsed;
1042
+ return list;
1046
1043
  }
1047
- function collectFields(selections, typename, doc, options = {}) {
1048
- const cacheKey = `${options.strict ? "s" : "l"}:${typename ?? ""}`;
1049
- let cached = fieldsCache.get(selections);
1050
- if (!cached) {
1051
- cached = /* @__PURE__ */ new Map();
1052
- fieldsCache.set(selections, cached);
1044
+ function isPlainRecord(v) {
1045
+ return v !== null && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype;
1046
+ }
1047
+ function isFieldValue(v, depth) {
1048
+ if (v === null || isScalar(v)) return true;
1049
+ if (depth >= MAX_FIELD_DEPTH) return false;
1050
+ if (Array.isArray(v)) {
1051
+ return v.length <= MAX_ENTRIES && v.every(
1052
+ (entry) => isScalar(entry) || isPlainRecord(entry) && isFieldValue(entry, depth + 1)
1053
+ );
1053
1054
  }
1054
- const hit = cached.get(cacheKey);
1055
- if (hit) return hit;
1056
- const fields = resolveFields(selections, typename, doc, options);
1057
- cached.set(cacheKey, fields);
1058
- return fields;
1055
+ if (!isPlainRecord(v)) return false;
1056
+ const keys = Object.keys(v);
1057
+ if (keys.length > MAX_FIELD_KEYS) return false;
1058
+ return keys.every((key) => {
1059
+ if (FORBIDDEN_KEYS.has(key)) return false;
1060
+ const value = v[key];
1061
+ if (!isSafeKeyValue(key, value)) return false;
1062
+ return isFieldValue(value, depth + 1);
1063
+ });
1059
1064
  }
1060
- function resolveFields(selections, typename, doc, options) {
1061
- const exact = /* @__PURE__ */ new Map();
1062
- const loose = /* @__PURE__ */ new Map();
1063
- const merged = /* @__PURE__ */ new Map();
1064
- const walkedMatched = /* @__PURE__ */ new Set();
1065
- const walkedLoose = /* @__PURE__ */ new Set();
1066
- const add = (target, s) => {
1067
- let field = target.get(s.key);
1068
- if (!field) {
1069
- field = { name: s.name, key: s.key, hasArgs: s.hasArgs, selections: [] };
1070
- target.set(s.key, field);
1071
- merged.set(field, /* @__PURE__ */ new Set());
1072
- }
1073
- const done = merged.get(field);
1074
- if (s.selections && !done.has(s.selections)) {
1075
- done.add(s.selections);
1076
- field.selections = [...field.selections, ...s.selections];
1065
+ function parseEditTarget(raw) {
1066
+ if (!isPlainRecord(raw)) return void 0;
1067
+ const { item, field } = raw;
1068
+ if (typeof item !== "string" || !item || item.length > MAX_EDIT_LENGTH) {
1069
+ return void 0;
1070
+ }
1071
+ if (typeof field !== "string" || !field || field.length > MAX_EDIT_LENGTH) {
1072
+ return void 0;
1073
+ }
1074
+ return { item, field };
1075
+ }
1076
+ var MAX_DEPTH, MAX_ENTRIES, FORBIDDEN_KEYS, SAFE_URL, URL_KEY, URL_IGNORED_ANYWHERE, SCRIPT_SCHEME, MAX_LIST_ENTRIES, MAX_FIELD_DEPTH, MAX_FIELD_KEYS, MAX_EDIT_LENGTH;
1077
+ var init_validate = __esm({
1078
+ "src/core/live-preview/validate.ts"() {
1079
+ "use strict";
1080
+ MAX_DEPTH = 3;
1081
+ MAX_ENTRIES = 50;
1082
+ FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
1083
+ SAFE_URL = /^https?:\/\//i;
1084
+ URL_KEY = /^(?:url|href|src|link)$|(?:_url|Url)$/;
1085
+ URL_IGNORED_ANYWHERE = /[\t\n\r]/g;
1086
+ SCRIPT_SCHEME = /^(?:javascript|vbscript|data):/i;
1087
+ MAX_LIST_ENTRIES = 200;
1088
+ MAX_FIELD_DEPTH = 6;
1089
+ MAX_FIELD_KEYS = 100;
1090
+ MAX_EDIT_LENGTH = 1e3;
1091
+ }
1092
+ });
1093
+
1094
+ // src/core/live-preview/bridge-events.ts
1095
+ function createLiveBridge(store2, options = {}) {
1096
+ let parentOrigin = null;
1097
+ const onMessage = (evt) => {
1098
+ const data = evt?.data;
1099
+ if (data?.event === "prepr:initVE" && !parentOrigin) {
1100
+ if (isAllowedEditorOrigin(evt.origin, options.allowedEditorOrigins?.())) {
1101
+ parentOrigin = evt.origin;
1102
+ } else {
1103
+ debug4.warn("ignored live preview handshake from origin", evt.origin);
1104
+ }
1105
+ return;
1077
1106
  }
1078
- };
1079
- const applies = (on, list) => conditionMatches(on, typename) || typename !== void 0 && typeConditions(list, doc).includes(typename);
1080
- const walk = (list, matched) => {
1081
- for (const s of list) {
1082
- if (s.kind === "field") {
1083
- add(matched ? exact : loose, s);
1084
- } else if (s.kind === "inline") {
1085
- walk(s.selections, matched && applies(s.on, s.selections));
1107
+ const trusted = parentOrigin ?? getTrustedParentOrigin();
1108
+ if (!trusted || evt.origin !== trusted) return;
1109
+ if (data?.event === "prepr:fieldChange") {
1110
+ if (typeof data.itemId === "string" && typeof data.field === "string" && data.field && isLiveFieldChange(data.field, data.value)) {
1111
+ const edit = parseEditTarget(data.edit);
1112
+ if (edit) store2.pushPatch(data.itemId, data.field, data.value, edit);
1113
+ else store2.pushPatch(data.itemId, data.field, data.value);
1086
1114
  } else {
1087
- const fragment = doc.fragments.get(s.name);
1088
- if (!fragment) continue;
1089
- const asMatched = matched && applies(fragment.on, fragment.selections);
1090
- if (walkedMatched.has(fragment)) continue;
1091
- if (!asMatched && walkedLoose.has(fragment)) continue;
1092
- (asMatched ? walkedMatched : walkedLoose).add(fragment);
1093
- walk(fragment.selections, asMatched);
1115
+ debug4.log("dropped malformed fieldChange", data);
1094
1116
  }
1117
+ return;
1095
1118
  }
1096
- };
1097
- walk(selections, true);
1098
- if (!options.strict) {
1099
- for (const [key, field] of loose) {
1100
- if (!exact.has(key)) exact.set(key, field);
1119
+ if (data?.event === "prepr:listChange") {
1120
+ const list = parseListEntries(data.entries);
1121
+ if (typeof data.itemId === "string" && typeof data.field === "string" && data.field && list) {
1122
+ store2.pushList(data.itemId, data.field, list);
1123
+ } else {
1124
+ debug4.log("dropped malformed listChange", data);
1125
+ }
1126
+ return;
1101
1127
  }
1102
- }
1103
- return exact;
1104
- }
1105
- function conditionMatches(on, typename) {
1106
- return on === null || typename === void 0 || on === typename;
1107
- }
1108
- function typeConditions(selections, doc) {
1109
- const hit = conditionsCache.get(selections);
1110
- if (hit) return hit;
1111
- const names = /* @__PURE__ */ new Set();
1112
- const seen = /* @__PURE__ */ new Set();
1113
- const walk = (list) => {
1114
- for (const s of list) {
1115
- if (s.kind === "inline") {
1116
- if (s.on) names.add(s.on);
1117
- walk(s.selections);
1118
- } else if (s.kind === "spread") {
1119
- const fragment = doc.fragments.get(s.name);
1120
- if (!fragment || seen.has(fragment)) continue;
1121
- seen.add(fragment);
1122
- names.add(fragment.on);
1123
- walk(fragment.selections);
1128
+ if (data?.event === "prepr:itemChanged") {
1129
+ if (typeof data.itemId === "string") {
1130
+ store2.invalidate(data.itemId);
1131
+ } else {
1132
+ debug4.log("dropped malformed itemChanged", data);
1124
1133
  }
1125
1134
  }
1126
1135
  };
1127
- walk(selections);
1128
- const conditions = [...names];
1129
- conditionsCache.set(selections, conditions);
1130
- return conditions;
1136
+ return {
1137
+ start() {
1138
+ window.addEventListener("message", onMessage);
1139
+ sendPreprEvent(
1140
+ "live_ready",
1141
+ options.strictMode?.() ? { strictMode: true } : void 0,
1142
+ { allowUntrustedTarget: true }
1143
+ );
1144
+ },
1145
+ stop() {
1146
+ window.removeEventListener("message", onMessage);
1147
+ }
1148
+ };
1131
1149
  }
1132
- var NAME_START, NAME_CONTINUE, PUNCT, astCache, sourceCache, SOURCE_CACHE_LIMIT, fieldsCache, conditionsCache;
1133
- var init_query_document = __esm({
1134
- "src/core/live-preview/query-document.ts"() {
1150
+ var debug4;
1151
+ var init_bridge_events = __esm({
1152
+ "src/core/live-preview/bridge-events.ts"() {
1135
1153
  "use strict";
1136
- NAME_START = /[_A-Za-z]/;
1137
- NAME_CONTINUE = /[_0-9A-Za-z]/;
1138
- PUNCT = /* @__PURE__ */ new Set([
1139
- "!",
1140
- "$",
1141
- "&",
1142
- "(",
1143
- ")",
1144
- ":",
1145
- "=",
1146
- "@",
1147
- "[",
1148
- "]",
1149
- "{",
1150
- "|",
1151
- "}"
1152
- ]);
1153
- astCache = /* @__PURE__ */ new WeakMap();
1154
- sourceCache = /* @__PURE__ */ new Map();
1155
- SOURCE_CACHE_LIMIT = 200;
1156
- fieldsCache = /* @__PURE__ */ new WeakMap();
1157
- conditionsCache = /* @__PURE__ */ new WeakMap();
1154
+ init_utils();
1155
+ init_iframe_bridge();
1156
+ init_validate();
1157
+ debug4 = createScopedLogger("live-preview:bridge");
1158
1158
  }
1159
1159
  });
1160
1160
 
@@ -1265,7 +1265,7 @@ function applyPatch(root, index, patch, doc, result) {
1265
1265
  for (const target of targets) {
1266
1266
  const pushed = plain.has(target) && typeof patch.value === "string" ? plainTextFromHtml(patch.value) : patch.value;
1267
1267
  let value = keepEditLink(readAtPath(root, target), pushed);
1268
- if (patch.edit && typeof value === "string" && !(0, import_stega2.vercelStegaSplit)(value).encoded) {
1268
+ if (patch.edit && typeof value === "string" && !(0, import_stega3.vercelStegaSplit)(value).encoded) {
1269
1269
  value = editLinksFor(result, patch.edit.item)?.encode(
1270
1270
  value,
1271
1271
  patch.edit.field
@@ -1332,7 +1332,7 @@ function resolveTargets(root, doc, ownerPath, segments) {
1332
1332
  let current = root;
1333
1333
  const out = [];
1334
1334
  const plain = /* @__PURE__ */ new Set();
1335
- const walk = (node, sel, i, path) => {
1335
+ const walk2 = (node, sel, i, path) => {
1336
1336
  if (i === segments.length) {
1337
1337
  out.push(path);
1338
1338
  return;
@@ -1342,10 +1342,10 @@ function resolveTargets(root, doc, ownerPath, segments) {
1342
1342
  if (INDEX_SEGMENT2.test(segment)) {
1343
1343
  const position = Number(segment);
1344
1344
  if (position < node.length) {
1345
- walk(node[position], sel, i + 1, [...path, position]);
1345
+ walk2(node[position], sel, i + 1, [...path, position]);
1346
1346
  }
1347
1347
  } else if (segment === "items") {
1348
- walk(node, sel, i + 1, path);
1348
+ walk2(node, sel, i + 1, path);
1349
1349
  }
1350
1350
  return;
1351
1351
  }
@@ -1378,11 +1378,11 @@ function resolveTargets(root, doc, ownerPath, segments) {
1378
1378
  current = writeAtPath(current, fieldPath, value);
1379
1379
  }
1380
1380
  }
1381
- walk(value, field.selections, i + 1, fieldPath);
1381
+ walk2(value, field.selections, i + 1, fieldPath);
1382
1382
  }
1383
- if (!matched && segment === "items") walk(node, sel, i + 1, path);
1383
+ if (!matched && segment === "items") walk2(node, sel, i + 1, path);
1384
1384
  };
1385
- walk(owner, selections, 0, ownerPath);
1385
+ walk2(owner, selections, 0, ownerPath);
1386
1386
  return { root: current, targets: out, plain };
1387
1387
  }
1388
1388
  function resolveTypename(doc, selections, typename, typeName) {
@@ -1501,11 +1501,11 @@ function listKeysIn(result) {
1501
1501
  if (!byType) {
1502
1502
  byType = /* @__PURE__ */ new Map();
1503
1503
  const seen = /* @__PURE__ */ new WeakSet();
1504
- const walk = (node) => {
1504
+ const walk2 = (node) => {
1505
1505
  if (node === null || typeof node !== "object" || seen.has(node)) return;
1506
1506
  seen.add(node);
1507
1507
  if (Array.isArray(node)) {
1508
- node.forEach(walk);
1508
+ node.forEach(walk2);
1509
1509
  return;
1510
1510
  }
1511
1511
  const record = node;
@@ -1516,10 +1516,10 @@ function listKeysIn(result) {
1516
1516
  if (!keys) byType.set(type, keys = /* @__PURE__ */ new Set());
1517
1517
  keys.add(key);
1518
1518
  }
1519
- walk(value);
1519
+ walk2(value);
1520
1520
  }
1521
1521
  };
1522
- walk(result);
1522
+ walk2(result);
1523
1523
  listKeysCache.set(result, byType);
1524
1524
  }
1525
1525
  return byType.get(typename) ?? NO_KEYS;
@@ -1536,7 +1536,7 @@ function editLinksFor(result, item) {
1536
1536
  if (!template) return null;
1537
1537
  return {
1538
1538
  encode(text, field) {
1539
- if ((0, import_stega2.vercelStegaSplit)(text).encoded) return text;
1539
+ if ((0, import_stega3.vercelStegaSplit)(text).encoded) return text;
1540
1540
  let href;
1541
1541
  try {
1542
1542
  href = new URL(template.href);
@@ -1549,7 +1549,7 @@ function editLinksFor(result, item) {
1549
1549
  );
1550
1550
  href.searchParams.set("field", field);
1551
1551
  const link = href.toString().replace(/%2E/gi, ".").replace(/%2F/gi, "/");
1552
- return (0, import_stega2.vercelStegaCombine)(text, { origin: template.origin, href: link });
1552
+ return (0, import_stega3.vercelStegaCombine)(text, { origin: template.origin, href: link });
1553
1553
  }
1554
1554
  };
1555
1555
  }
@@ -1558,12 +1558,12 @@ function findEditLink(root) {
1558
1558
  if (linkCache.has(root)) return linkCache.get(root) ?? null;
1559
1559
  let found = null;
1560
1560
  const seen = /* @__PURE__ */ new WeakSet();
1561
- const walk = (node) => {
1561
+ const walk2 = (node) => {
1562
1562
  if (found) return;
1563
1563
  if (typeof node === "string") {
1564
- if (!(0, import_stega2.vercelStegaSplit)(node).encoded) return;
1564
+ if (!(0, import_stega3.vercelStegaSplit)(node).encoded) return;
1565
1565
  try {
1566
- const decoded = (0, import_stega2.vercelStegaDecode)(node);
1566
+ const decoded = (0, import_stega3.vercelStegaDecode)(node);
1567
1567
  if (typeof decoded?.origin === "string" && typeof decoded.href === "string" && EDIT_PATH.test(decoded.href)) {
1568
1568
  found = { origin: decoded.origin, href: decoded.href };
1569
1569
  }
@@ -1573,17 +1573,17 @@ function findEditLink(root) {
1573
1573
  }
1574
1574
  if (node === null || typeof node !== "object" || seen.has(node)) return;
1575
1575
  seen.add(node);
1576
- for (const value of Object.values(node)) walk(value);
1576
+ for (const value of Object.values(node)) walk2(value);
1577
1577
  };
1578
- walk(root);
1578
+ walk2(root);
1579
1579
  linkCache.set(root, found);
1580
1580
  return found;
1581
1581
  }
1582
- var import_stega2, debug5, MAX_LOG_ENTRIES2, MAX_LOG_SOURCE_LENGTH, INDEX_SEGMENT2, FORBIDDEN_SEGMENTS2, ENVELOPE_KEYS, NON_ALPHANUMERIC, typeKey, NO_KEYS, listKeysCache, EDIT_PATH, ITEM_ID, linkCache;
1582
+ var import_stega3, debug5, MAX_LOG_ENTRIES2, MAX_LOG_SOURCE_LENGTH, INDEX_SEGMENT2, FORBIDDEN_SEGMENTS2, ENVELOPE_KEYS, NON_ALPHANUMERIC, typeKey, NO_KEYS, listKeysCache, EDIT_PATH, ITEM_ID, linkCache;
1583
1583
  var init_normalize_query = __esm({
1584
1584
  "src/core/live-preview/normalize-query.ts"() {
1585
1585
  "use strict";
1586
- import_stega2 = require("@vercel/stega");
1586
+ import_stega3 = require("@vercel/stega");
1587
1587
  init_utils();
1588
1588
  init_field_name();
1589
1589
  init_index_snapshot();
@@ -1631,6 +1631,102 @@ __export(live_exports, {
1631
1631
  });
1632
1632
  module.exports = __toCommonJS(live_exports);
1633
1633
 
1634
+ // src/core/live-preview/edit-links.ts
1635
+ var import_stega = require("@vercel/stega");
1636
+ init_query_document();
1637
+ var ENTRY_PARAM = "entry";
1638
+ var TOP_LEVEL_ENTRY = /^[A-Za-z0-9_]+\.\d+$/;
1639
+ function entryIdOf(element) {
1640
+ if (!isRecord(element)) return void 0;
1641
+ if (typeof element._id === "string") return element._id;
1642
+ if (element.__typename !== "Assets" || !Array.isArray(element.items)) {
1643
+ return void 0;
1644
+ }
1645
+ const first = element.items[0];
1646
+ return isRecord(first) && typeof first._id === "string" ? first._id : void 0;
1647
+ }
1648
+ function isRecord(value) {
1649
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1650
+ }
1651
+ function annotate(value, ctx) {
1652
+ if (!ctx.entryId) return value;
1653
+ const { cleaned, encoded } = (0, import_stega.vercelStegaSplit)(value);
1654
+ if (!encoded) return value;
1655
+ let payload;
1656
+ try {
1657
+ payload = (0, import_stega.vercelStegaDecode)(encoded);
1658
+ } catch {
1659
+ return value;
1660
+ }
1661
+ if (typeof payload?.href !== "string") return value;
1662
+ let url;
1663
+ try {
1664
+ url = new URL(payload.href);
1665
+ } catch {
1666
+ return value;
1667
+ }
1668
+ if (url.searchParams.has(ENTRY_PARAM)) return value;
1669
+ url.searchParams.set(ENTRY_PARAM, ctx.entryId);
1670
+ const itemId = decodeURIComponent(url.pathname.split("/").pop() ?? "");
1671
+ if (!url.searchParams.has("field") && ctx.entryField && TOP_LEVEL_ENTRY.test(ctx.entryField) && itemId === ctx.entryOwnerId) {
1672
+ url.searchParams.set("field", ctx.entryField);
1673
+ }
1674
+ return (0, import_stega.vercelStegaCombine)(
1675
+ cleaned,
1676
+ { ...payload, href: url.toString() },
1677
+ false
1678
+ );
1679
+ }
1680
+ function walk(value, selections, ctx) {
1681
+ if (typeof value === "string") return annotate(value, ctx);
1682
+ if (Array.isArray(value)) {
1683
+ let changed2 = false;
1684
+ const next2 = value.map((element, index) => {
1685
+ const path = [...ctx.path, String(index)];
1686
+ const entryId = ctx.inAssets ? void 0 : entryIdOf(element);
1687
+ const entry = entryId ? {
1688
+ entryId,
1689
+ entryField: ctx.ownerId ? path.join(".") : void 0,
1690
+ entryOwnerId: ctx.ownerId,
1691
+ inAssets: isRecord(element) && element._id === void 0
1692
+ } : {};
1693
+ const out = walk(element, selections, { ...ctx, ...entry, path });
1694
+ if (out !== element) changed2 = true;
1695
+ return out;
1696
+ });
1697
+ return changed2 ? next2 : value;
1698
+ }
1699
+ if (!isRecord(value)) return value;
1700
+ const id = typeof value._id === "string" ? value._id : void 0;
1701
+ const typename = typeof value.__typename === "string" ? value.__typename : void 0;
1702
+ const fields = ctx.doc && selections ? collectFields(selections, typename, ctx.doc) : null;
1703
+ let changed = false;
1704
+ const next = {};
1705
+ for (const [key, child] of Object.entries(value)) {
1706
+ const field = fields?.get(key);
1707
+ const name = field?.name ?? key;
1708
+ const out = walk(child, field?.selections ?? null, {
1709
+ ...ctx,
1710
+ ownerId: id ?? ctx.ownerId,
1711
+ path: id ? [name] : [...ctx.path, name]
1712
+ });
1713
+ if (out !== child) changed = true;
1714
+ next[key] = out;
1715
+ }
1716
+ return changed ? next : value;
1717
+ }
1718
+ function annotateEditLinks(result, query, operationName) {
1719
+ let doc = null;
1720
+ if (query !== void 0) {
1721
+ try {
1722
+ doc = readQueryDocument(query, operationName);
1723
+ } catch {
1724
+ doc = null;
1725
+ }
1726
+ }
1727
+ return walk(result, doc?.operation ?? null, { doc, path: [] });
1728
+ }
1729
+
1634
1730
  // src/core/live-preview/test-reset.ts
1635
1731
  var resets = /* @__PURE__ */ new Set();
1636
1732
  function onTestReset(reset) {
@@ -1647,6 +1743,15 @@ var allowedEditorOrigins;
1647
1743
  var strictMode;
1648
1744
  var logListeners = /* @__PURE__ */ new Set();
1649
1745
  var startListeners = /* @__PURE__ */ new Set();
1746
+ function constant(value) {
1747
+ return {
1748
+ subscribe(run) {
1749
+ run(value);
1750
+ return () => {
1751
+ };
1752
+ }
1753
+ };
1754
+ }
1650
1755
  function isFramed() {
1651
1756
  return typeof window !== "undefined" && window.self !== window.top;
1652
1757
  }
@@ -1680,21 +1785,15 @@ function whenLive(listener) {
1680
1785
  return () => startListeners.delete(listener);
1681
1786
  }
1682
1787
  function normalizedQuery(query, data, options = {}) {
1683
- if (!options.enabled || !isFramed()) {
1684
- return {
1685
- subscribe(run) {
1686
- run(data);
1687
- return () => {
1688
- };
1689
- }
1690
- };
1691
- }
1788
+ if (!options.enabled) return constant(data);
1789
+ const annotated = annotateEditLinks(data, query);
1790
+ if (!isFramed()) return constant(annotated);
1692
1791
  if (options.strictMode) strictMode = true;
1693
1792
  if (options.allowedEditorOrigins) {
1694
1793
  allowedEditorOrigins = [...options.allowedEditorOrigins];
1695
1794
  }
1696
1795
  startLive();
1697
- const current = () => log.length && applyLog ? applyLog(query, data, log) : data;
1796
+ const current = () => log.length && applyLog ? applyLog(query, annotated, log) : annotated;
1698
1797
  return {
1699
1798
  subscribe(run) {
1700
1799
  run(current());