mechanica-shared 2.0.0-alpha.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.
@@ -0,0 +1,301 @@
1
+ import { Document, isCollection, isMap, isScalar, isSeq, parse } from "yaml";
2
+ //#region src/page-format.ts
3
+ /** Thrown by {@link parsePage} with the 1-based source line of the problem. */
4
+ var PageParseError = class PageParseError extends Error {
5
+ line;
6
+ constructor(message, line) {
7
+ super(`${message} (line ${line})`);
8
+ this.line = line;
9
+ this.name = "PageParseError";
10
+ Object.setPrototypeOf(this, PageParseError.prototype);
11
+ }
12
+ };
13
+ var CLOSE_BARE = /^:::[ \t]*$/;
14
+ var CLOSE_LABELED = /^:::[ \t]*\/[ \t]*([A-Za-z][\w-]*)?[ \t]*$/;
15
+ var OPEN = /^:::[ \t]+([A-Za-z][\w-]*)[ \t]*(.*)$/;
16
+ var FIELD = /^@([A-Za-z][\w.-]*)[ \t]*$/;
17
+ /** Classify a line as a structural token, or null when it's head/region content. */
18
+ function classify(line) {
19
+ if (/^[ \t]/.test(line)) return null;
20
+ let m;
21
+ if (CLOSE_BARE.test(line)) return { kind: "close" };
22
+ if (m = CLOSE_LABELED.exec(line)) return {
23
+ kind: "close",
24
+ id: m[1] || void 0
25
+ };
26
+ if (m = OPEN.exec(line)) return {
27
+ kind: "open",
28
+ blockId: m[1],
29
+ rest: m[2]
30
+ };
31
+ if (m = FIELD.exec(line)) return {
32
+ kind: "field",
33
+ name: m[1]
34
+ };
35
+ return null;
36
+ }
37
+ /** Leading run of backticks/tildes that opens or closes a Markdown code fence. */
38
+ function matchFence(line) {
39
+ const m = /^[ \t]*([`~]{3,})/.exec(line);
40
+ return m ? m[1] : null;
41
+ }
42
+ /** Strip the common leading indentation from a block of lines (blank lines ignored). */
43
+ function dedent(lines) {
44
+ let min = Infinity;
45
+ for (const line of lines) {
46
+ if (line.trim() === "") continue;
47
+ min = Math.min(min, /^[ \t]*/.exec(line)[0].length);
48
+ }
49
+ if (!isFinite(min) || min === 0) return lines.slice();
50
+ return lines.map((line) => line.trim() === "" ? line.trimStart() : line.slice(min));
51
+ }
52
+ /** Set `data[a.b.c] = value`, creating intermediate objects. */
53
+ function setPath(data, path, value) {
54
+ const parts = path.split(".");
55
+ let target = data;
56
+ for (let i = 0; i < parts.length - 1; i++) {
57
+ const key = parts[i];
58
+ if (typeof target[key] !== "object" || target[key] === null) target[key] = {};
59
+ target = target[key];
60
+ }
61
+ target[parts[parts.length - 1]] = value;
62
+ }
63
+ /** Parse a `.page.md` document into a {@link PageDoc}. */
64
+ function parsePage(text, options) {
65
+ const lines = text.replace(/\r\n/g, "\n").split("\n");
66
+ let i = 0;
67
+ let envelope = {};
68
+ if (lines[0] === "---") {
69
+ let j = 1;
70
+ while (j < lines.length && lines[j] !== "---") j++;
71
+ if (j >= lines.length) throw new PageParseError("Unterminated frontmatter (missing closing ---)", 1);
72
+ try {
73
+ envelope = parse(lines.slice(1, j).join("\n")) ?? {};
74
+ } catch (error) {
75
+ throw new PageParseError(`Invalid frontmatter YAML: ${error.message}`, 1);
76
+ }
77
+ i = j + 1;
78
+ }
79
+ const root = [];
80
+ const stack = [];
81
+ const counter = { n: 0 };
82
+ const flushHead = (frame, line) => {
83
+ if (frame.headBuf.length === 0) return;
84
+ const yamlText = dedent(frame.headBuf).join("\n");
85
+ frame.headBuf = [];
86
+ if (yamlText.trim() === "") return;
87
+ let parsed;
88
+ try {
89
+ parsed = parse(yamlText);
90
+ } catch (error) {
91
+ throw new PageParseError(`Invalid block head YAML: ${error.message}`, line);
92
+ }
93
+ if (parsed && typeof parsed === "object") Object.assign(frame.block.data, parsed);
94
+ };
95
+ const finalizeRegion = (frame) => {
96
+ const region = frame.region;
97
+ const body = dedent(region.lines);
98
+ while (body.length && body[0].trim() === "") body.shift();
99
+ while (body.length && body[body.length - 1].trim() === "") body.pop();
100
+ const raw = body.join("\n");
101
+ const value = options?.richText?.isRichText(frame.block.blockId, region.name) ? options.richText.toBlocks(raw) : raw;
102
+ setPath(frame.block.data, region.name, value);
103
+ frame.region = null;
104
+ };
105
+ for (; i < lines.length; i++) {
106
+ const line = lines[i];
107
+ const frame = stack[stack.length - 1];
108
+ if (frame?.region) {
109
+ const region = frame.region;
110
+ const fence = matchFence(line);
111
+ if (region.fence !== null) {
112
+ region.lines.push(line);
113
+ if (fence && fence.length >= region.fence.length && line.trimStart().startsWith(region.fence)) region.fence = null;
114
+ continue;
115
+ }
116
+ if (fence) {
117
+ region.lines.push(line);
118
+ region.fence = fence;
119
+ continue;
120
+ }
121
+ if (line.startsWith("\\:::") || /^\\@[A-Za-z]/.test(line)) {
122
+ region.lines.push(line.slice(1));
123
+ continue;
124
+ }
125
+ if (classify(line) === null) {
126
+ region.lines.push(line);
127
+ continue;
128
+ }
129
+ finalizeRegion(frame);
130
+ }
131
+ const token = classify(line);
132
+ if (token === null) {
133
+ if (!frame) {
134
+ if (line.trim() === "") continue;
135
+ throw new PageParseError(`Content outside any block: "${line.trim()}"`, i + 1);
136
+ }
137
+ frame.headBuf.push(line);
138
+ continue;
139
+ }
140
+ if (token.kind === "open") {
141
+ if (frame) flushHead(frame, i + 1);
142
+ const { id, slot, v } = parseAttrs(token.rest, i + 1);
143
+ const block = {
144
+ id: id ?? `auto${++counter.n}`,
145
+ blockId: token.blockId,
146
+ data: {}
147
+ };
148
+ if (v !== void 0) block.v = v;
149
+ attach(root, frame, block, slot, i + 1);
150
+ stack.push({
151
+ block,
152
+ headBuf: [],
153
+ region: null
154
+ });
155
+ } else if (token.kind === "field") {
156
+ if (!frame) throw new PageParseError(`@${token.name} outside any block`, i + 1);
157
+ flushHead(frame, i + 1);
158
+ frame.region = {
159
+ name: token.name,
160
+ lines: [],
161
+ fence: null
162
+ };
163
+ } else {
164
+ if (!frame) throw new PageParseError("Close ::: with no open block", i + 1);
165
+ flushHead(frame, i + 1);
166
+ if (token.id && token.id !== frame.block.blockId) throw new PageParseError(`Labeled close "::: /${token.id}" does not match open block "::: ${frame.block.blockId}"`, i + 1);
167
+ stack.pop();
168
+ }
169
+ }
170
+ if (stack.length) throw new PageParseError(`Unclosed block "::: ${stack[stack.length - 1].block.blockId}"`, lines.length);
171
+ return {
172
+ ...typeof envelope.name === "string" ? { name: envelope.name } : {},
173
+ ...envelope.meta !== void 0 ? { meta: envelope.meta } : {},
174
+ ...typeof envelope.order === "number" ? { order: envelope.order } : {},
175
+ ...envelope.orderAfter != null ? { orderAfter: envelope.orderAfter } : {},
176
+ ...typeof envelope.path === "string" ? { path: envelope.path } : {},
177
+ data: envelope.data ?? {},
178
+ content: root
179
+ };
180
+ }
181
+ /** Parse the `#id` and `key=value` attributes after a block id on an open fence. */
182
+ function parseAttrs(rest, line) {
183
+ const out = {};
184
+ for (const part of rest.trim().split(/[ \t]+/).filter(Boolean)) if (part.startsWith("#")) out.id = part.slice(1);
185
+ else if (part.startsWith("slot=")) out.slot = part.slice(5);
186
+ else if (/^v=\d+$/.test(part)) out.v = Number(part.slice(2));
187
+ else throw new PageParseError(`Unknown block attribute "${part}"`, line);
188
+ return out;
189
+ }
190
+ /** Place a freshly-opened block into its parent slot (or the page root). */
191
+ function attach(root, parent, block, slot, line) {
192
+ if (!parent) {
193
+ if (slot) throw new PageParseError("A root-level block cannot have a slot", line);
194
+ root.push(block);
195
+ return;
196
+ }
197
+ const owner = parent.block;
198
+ if (slot) {
199
+ if (owner.children == null) owner.children = {};
200
+ if (Array.isArray(owner.children)) throw new PageParseError(`Block "::: ${owner.blockId}" mixes default and named slots`, line);
201
+ const map = owner.children;
202
+ (map[slot] ??= []).push(block);
203
+ } else {
204
+ if (owner.children == null) owner.children = [];
205
+ if (!Array.isArray(owner.children)) throw new PageParseError(`Block "::: ${owner.blockId}" mixes default and named slots`, line);
206
+ owner.children.push(block);
207
+ }
208
+ }
209
+ /** A top-level string prop becomes an `@field` region past this length, or on any newline. */
210
+ var REGION_THRESHOLD = 80;
211
+ var FLOW_MAX = 72;
212
+ /** Serialize a {@link PageDoc} into canonical `.page.md` text. */
213
+ function serializePage(doc, options) {
214
+ const envelope = {};
215
+ if (doc.name !== void 0) envelope.name = doc.name;
216
+ if (doc.meta !== void 0) envelope.meta = doc.meta;
217
+ envelope.data = doc.data ?? {};
218
+ if (doc.order !== void 0) envelope.order = doc.order;
219
+ if (doc.orderAfter != null) envelope.orderAfter = doc.orderAfter;
220
+ if (doc.path !== void 0) envelope.path = doc.path;
221
+ const out = [
222
+ "---",
223
+ emitYaml(envelope),
224
+ "---",
225
+ ""
226
+ ];
227
+ for (const block of doc.content) emitBlock(block, void 0, out, options);
228
+ return out.join("\n").replace(/\n+$/, "") + "\n";
229
+ }
230
+ function emitBlock(block, slot, out, options) {
231
+ let open = `::: ${block.blockId}`;
232
+ if (block.id) open += ` #${block.id}`;
233
+ if (slot) open += ` slot=${slot}`;
234
+ if (block.v !== void 0) open += ` v=${block.v}`;
235
+ out.push(open);
236
+ const head = {};
237
+ const regions = [];
238
+ for (const [key, value] of Object.entries(block.data)) if (options?.richText?.isRichText(block.blockId, key) && Array.isArray(value)) regions.push([key, options.richText.toMarkdown(value)]);
239
+ else if (typeof value === "string" && (value.includes("\n") || value.length > REGION_THRESHOLD)) regions.push([key, value]);
240
+ else head[key] = value;
241
+ if (Object.keys(head).length) out.push(emitYaml(head));
242
+ for (const [name, value] of regions) {
243
+ out.push(`@${name}`);
244
+ out.push(escapeRegion(value));
245
+ }
246
+ let hasChildren = false;
247
+ const children = block.children;
248
+ if (Array.isArray(children)) {
249
+ for (const child of children) emitBlock(child, void 0, out, options);
250
+ hasChildren = children.length > 0;
251
+ } else if (children && typeof children === "object") for (const [slotName, list] of Object.entries(children)) {
252
+ for (const child of list) emitBlock(child, slotName, out, options);
253
+ if (list.length) hasChildren = true;
254
+ }
255
+ out.push(hasChildren ? `::: /${block.blockId}` : ":::");
256
+ out.push("");
257
+ }
258
+ /** Backslash-escape any line-initial structural token in raw region text (outside code fences). */
259
+ function escapeRegion(text) {
260
+ let fence = null;
261
+ return text.split("\n").map((line) => {
262
+ const mark = matchFence(line);
263
+ if (fence !== null) {
264
+ if (mark && mark.length >= fence.length && line.trimStart().startsWith(fence)) fence = null;
265
+ return line;
266
+ }
267
+ if (mark) {
268
+ fence = mark;
269
+ return line;
270
+ }
271
+ return classify(line) !== null ? "\\" + line : line;
272
+ }).join("\n");
273
+ }
274
+ /**
275
+ * Emit a value as YAML. The root mapping always stays block style (one prop per
276
+ * line); only nested collections collapse to flow when short and all-scalar.
277
+ */
278
+ function emitYaml(value) {
279
+ const doc = new Document(value);
280
+ const root = doc.contents;
281
+ if (isSeq(root)) for (const item of root.items) applyFlow(item);
282
+ else if (isMap(root)) for (const pair of root.items) applyFlow(pair.value);
283
+ return doc.toString({ lineWidth: 0 }).replace(/\n$/, "");
284
+ }
285
+ function applyFlow(node) {
286
+ if (isSeq(node)) {
287
+ for (const item of node.items) if (isCollection(item)) applyFlow(item);
288
+ node.flow = node.items.length === 0 || node.items.every(isScalar) && inlineLen(node) <= FLOW_MAX;
289
+ } else if (isMap(node)) {
290
+ for (const pair of node.items) if (isCollection(pair.value)) applyFlow(pair.value);
291
+ node.flow = node.items.length === 0 || node.items.every((pair) => isScalar(pair.value)) && inlineLen(node) <= FLOW_MAX;
292
+ }
293
+ }
294
+ function inlineLen(node) {
295
+ if (isScalar(node)) return String(node.value ?? "null").length + 2;
296
+ if (isSeq(node)) return 4 + node.items.reduce((sum, item) => sum + inlineLen(item) + 2, 0);
297
+ if (isMap(node)) return 4 + node.items.reduce((sum, pair) => sum + String(pair.key?.value ?? pair.key).length + 2 + inlineLen(pair.value) + 2, 0);
298
+ return 0;
299
+ }
300
+ //#endregion
301
+ export { PageParseError, parsePage, serializePage };
@@ -0,0 +1,34 @@
1
+ import { registerAlias, type SchemaItem } from 'compact-json-schema';
2
+ declare module 'compact-json-schema' {
3
+ interface SchemaAnnotations {
4
+ format?: string;
5
+ }
6
+ }
7
+ /**
8
+ * A built-in or user-defined editable field type. The *runtime* half lives here
9
+ * (the compact-json-schema alias + a default value); the editor component half
10
+ * is attached separately in the plugin via `defineFieldType`.
11
+ */
12
+ export interface FieldType {
13
+ /** Format name, e.g. `'image'`. Used as the compact-json-schema alias. */
14
+ name: string;
15
+ /** The compact-json-schema definition this alias expands to. */
16
+ schema: SchemaItem;
17
+ /** Default value, or a factory returning a fresh one. */
18
+ default?: unknown | (() => unknown);
19
+ }
20
+ /** The field types registered by default. */
21
+ export declare const builtinFields: FieldType[];
22
+ export type RegisterAlias = typeof registerAlias;
23
+ /**
24
+ * Register field types as compact-json-schema aliases and record their default
25
+ * values. Call once per runtime before unfolding any block/data schema.
26
+ *
27
+ * @param register Override the alias registrar (defaults to compact-json-schema's).
28
+ * @param fields Field set to register (defaults to {@link builtinFields}).
29
+ */
30
+ export declare function registerFieldSchemas(register?: RegisterAlias, fields?: FieldType[]): void;
31
+ /** Resolve the default value for a registered field format, or `undefined`. */
32
+ export declare function getFieldDefault(format: string): unknown;
33
+ /** Whether {@link registerFieldSchemas} has run in this runtime. */
34
+ export declare function areFieldSchemasRegistered(): boolean;
@@ -0,0 +1,85 @@
1
+ import type { Block } from './types';
2
+ /**
3
+ * Substitute `{{ a.b }}` placeholders in an HTML string with data values. Used to
4
+ * template the `<head>` (title, meta, Open Graph, …) from `defineData` values and
5
+ * the current page. Resolved values are HTML-escaped.
6
+ */
7
+ export declare function passDataToHTML(html: string, data: any): string;
8
+ export interface PageState {
9
+ content: any[];
10
+ data: Record<string, any>;
11
+ page?: {
12
+ title?: string;
13
+ path?: string;
14
+ meta?: Record<string, unknown>;
15
+ /** Set on paginated variants: which chunk of the page's paginated query this is. */
16
+ pagination?: {
17
+ page: number;
18
+ pageCount?: number;
19
+ };
20
+ };
21
+ }
22
+ /** What `render` may return: bare HTML, or HTML plus the queries it resolved. */
23
+ export type RenderResult = string | {
24
+ html: string;
25
+ query?: Record<string, unknown>;
26
+ };
27
+ export interface GeneratePageOptions {
28
+ /** The index.html template. */
29
+ index: string;
30
+ /** Block metadata keyed by blockId. */
31
+ blocksMap: Map<string, Block>;
32
+ /** The page to render. */
33
+ state: PageState;
34
+ /** Declared data entries (with unfolded schemas) for default-filling. */
35
+ dataEntries: {
36
+ id: string;
37
+ props: any;
38
+ }[];
39
+ /** Site-level data merged under page data. */
40
+ projectData?: Record<string, any>;
41
+ baseUrl?: string;
42
+ path?: string;
43
+ /** Rewrite `/assets/` to this base when set. */
44
+ assetsUrl?: string;
45
+ /**
46
+ * Extra `<link>` tags for this page's content, injected before `</head>` —
47
+ * used by the export to preload the block chunks/CSS the page uses (blocks
48
+ * are code-split out of the client entry).
49
+ */
50
+ pageLinks?: (content: any[]) => string[];
51
+ /**
52
+ * Render the page state to HTML (provided by the SSR bundle). May also
53
+ * return the query results resolved during the render — they're baked into
54
+ * `window.state.query` so the client hydrates them synchronously.
55
+ */
56
+ render: (state: any, path: string) => Promise<RenderResult> | RenderResult;
57
+ }
58
+ /** Render a single page into the index template with serialized state. */
59
+ export declare function generatePage(options: GeneratePageOptions): Promise<{
60
+ html: string;
61
+ query: Record<string, unknown>;
62
+ }>;
63
+ /**
64
+ * Serialize runtime state for embedding in an inline `<script>`. Plain JSON is
65
+ * unsafe (a `</script>` in the data would close the tag early), so the few
66
+ * dangerous characters are escaped to their `\uXXXX` form — valid JSON/JS that
67
+ * `window.state` and the router's regex read back unchanged.
68
+ */
69
+ export declare function serializeState(state: unknown): string;
70
+ export interface GenerateProjectOptions extends Omit<GeneratePageOptions, 'state' | 'path'> {
71
+ pages: Array<{
72
+ content: any[];
73
+ data: Record<string, any>;
74
+ path: string;
75
+ page?: PageState['page'];
76
+ }>;
77
+ /** Map an asset path to its emitted path (and copy it). */
78
+ onFile?: (path: string) => string;
79
+ }
80
+ /** Render every page of a project, yielding the html, path and resolved queries. */
81
+ export declare function generateProject(options: GenerateProjectOptions): AsyncGenerator<{
82
+ html: string;
83
+ path: string;
84
+ query: Record<string, unknown>;
85
+ }>;
@@ -0,0 +1,7 @@
1
+ export type { Block, ContentBlock, DataEntry, DataScope, PageMeta, State, PageLink, } from './types';
2
+ export { type FieldType, type RegisterAlias, builtinFields, registerFieldSchemas, getFieldDefault, areFieldSchemasRegistered, } from './fields';
3
+ export { getDefaultValue, passDefaultValue, buildPreviewData, mergePreviewData, walkTree, walkSchema, getValueByPath, } from './schema';
4
+ export { generatePage, generateProject, passDataToHTML, serializeState, type GeneratePageOptions, type GenerateProjectOptions, type PageState, type RenderResult, } from './generate-page';
5
+ export { validateLinks, collectInternalLinks, normalizeInternalUrl, type LinkIssue, } from './validate-links';
6
+ export { migrateContent, findUnknownBlocks } from './migrate';
7
+ export { parseQueryKey, isPaginatedQuery, resolvePagesQuery, resolveQueryKey, type QuerySource, type QueryContext, type PageQueryItem, type PagesQueryArgs, type PaginatedPagesResult, } from './query-engine';
@@ -0,0 +1,19 @@
1
+ import type { Block, ContentBlock } from './types';
2
+ /**
3
+ * Upgrade placed blocks whose data was written with an older schema version.
4
+ *
5
+ * Each placed block records the schema version it was saved with (`v`, absent
6
+ * = 1). When a block type declares a newer `version`, its `migrate` hook runs
7
+ * with the stored data and the version it came from, then the block is
8
+ * stamped with the current version. Runs on load (dev state, export) so pages
9
+ * never render stale-shaped data; the upgrade persists with the next save.
10
+ *
11
+ * Returns whether anything changed.
12
+ */
13
+ export declare function migrateContent(content: ContentBlock[], blocksMap: Map<string, Block>): boolean;
14
+ /**
15
+ * Block ids referenced by the content tree that the block registry doesn't
16
+ * know (deleted or renamed block types). These render as nothing — callers
17
+ * should surface them (export warning, editor badge).
18
+ */
19
+ export declare function findUnknownBlocks(content: ContentBlock[], blocksMap: Map<string, Block>): string[];
@@ -0,0 +1,43 @@
1
+ import type { ContentBlock } from './types';
2
+ /**
3
+ * A parsed page document — the page envelope (everything but `content`) plus the
4
+ * content tree. This is the in-memory shape the `.page.md` codec maps to/from;
5
+ * it mirrors the dev store's `PageFile`.
6
+ */
7
+ export interface PageDoc {
8
+ name?: string;
9
+ meta?: Record<string, unknown>;
10
+ /** Page-scoped data overrides (defineData). */
11
+ data: Record<string, unknown>;
12
+ content: ContentBlock[];
13
+ order?: number;
14
+ orderAfter?: string | null;
15
+ path?: string;
16
+ }
17
+ /**
18
+ * Adapter that lets the codec store rich-text fields as Markdown on disk while
19
+ * keeping them as vuewrite `Block[]` JSON in page state. Supplied by the caller
20
+ * (dev server / CLI) so this module stays DOM- and vuewrite-free.
21
+ */
22
+ export interface RichTextCodec {
23
+ /** Whether a top-level prop of the given block is a rich-text field. */
24
+ isRichText(blockId: string, prop: string): boolean;
25
+ /** vuewrite `Block[]` → Markdown (written to disk). */
26
+ toMarkdown(blocks: unknown): string;
27
+ /** Markdown → vuewrite `Block[]` (loaded into state). */
28
+ toBlocks(markdown: string): unknown;
29
+ }
30
+ /** Options for {@link parsePage} / {@link serializePage}. */
31
+ export interface PageCodecOptions {
32
+ /** Rich-text ⇄ Markdown adapter; when omitted, regions stay plain strings. */
33
+ richText?: RichTextCodec;
34
+ }
35
+ /** Thrown by {@link parsePage} with the 1-based source line of the problem. */
36
+ export declare class PageParseError extends Error {
37
+ readonly line: number;
38
+ constructor(message: string, line: number);
39
+ }
40
+ /** Parse a `.page.md` document into a {@link PageDoc}. */
41
+ export declare function parsePage(text: string, options?: PageCodecOptions): PageDoc;
42
+ /** Serialize a {@link PageDoc} into canonical `.page.md` text. */
43
+ export declare function serializePage(doc: PageDoc, options?: PageCodecOptions): string;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The query engine: resolves the runtime's query keys (`usePages`,
3
+ * `usePagination`, `useFetch`) against an abstract {@link QuerySource}. One
4
+ * implementation, three callers — the dev server (live `.mech` store), the
5
+ * static export (resolved at build time and baked into `window.state.query`),
6
+ * and a future hosted backend (its own source). Pure and DOM/fs-free; the
7
+ * source supplies all I/O.
8
+ */
9
+ /** A page entry as the engine consumes it (requested data entries embedded). */
10
+ export interface PageQueryItem {
11
+ path: string;
12
+ name: string;
13
+ folderPath?: string | null;
14
+ order?: number;
15
+ orderAfter?: string | null;
16
+ [dataId: string]: unknown;
17
+ }
18
+ /** What a caller must supply for the engine to resolve queries. */
19
+ export interface QuerySource {
20
+ /** List all pages, with the requested page-scoped data entries embedded. */
21
+ listPages(options: {
22
+ data?: {
23
+ id: string;
24
+ }[];
25
+ }): PageQueryItem[];
26
+ /** Fetch external JSON (`useFetch`). Omit to disable fetch queries. */
27
+ fetchJson?(options: {
28
+ url: string;
29
+ } & Record<string, unknown>): Promise<unknown>;
30
+ }
31
+ /** Arguments of a `getPages` query (the JSON part of its key). */
32
+ export interface PagesQueryArgs {
33
+ /** Only pages inside this folder path (e.g. `'blog'`). */
34
+ folderName?: string;
35
+ /** Page-scoped data entries to embed in each result. */
36
+ data?: {
37
+ id: string;
38
+ }[];
39
+ /**
40
+ * Sort field: `'name'`, `'path'`, or a dotted path into an included data
41
+ * entry (e.g. `'postMeta.date'`). Default: the store's page order.
42
+ */
43
+ sort?: {
44
+ by: string;
45
+ dir?: 'asc' | 'desc';
46
+ };
47
+ /** Cap the number of results (non-paginated queries). */
48
+ limit?: number;
49
+ /** Split results into pages of this size — the query becomes paginated. */
50
+ pageSize?: number;
51
+ }
52
+ /** The shape a paginated `getPages` query resolves to. */
53
+ export interface PaginatedPagesResult {
54
+ items: PageQueryItem[];
55
+ /** Current page number (1-based, clamped to `pageCount`). */
56
+ page: number;
57
+ pageCount: number;
58
+ pageSize: number;
59
+ total: number;
60
+ }
61
+ /** Extra context for resolving a key (which paginated variant to slice). */
62
+ export interface QueryContext {
63
+ /** 1-based page number for paginated queries. Default 1. */
64
+ page?: number;
65
+ }
66
+ /** Split a query key (`"<type>.<json-args>"`) into its type and parsed args. */
67
+ export declare function parseQueryKey(key: string): {
68
+ type: string;
69
+ args: Record<string, unknown>;
70
+ };
71
+ /** Whether a key is a paginated `getPages` query (drives export page-splitting). */
72
+ export declare function isPaginatedQuery(key: string): boolean;
73
+ /**
74
+ * Resolve a `getPages` query: filter by folder, sort, and either cap (`limit`)
75
+ * or paginate (`pageSize`). Internal ordering fields are stripped from results.
76
+ */
77
+ export declare function resolvePagesQuery(source: QuerySource, args: PagesQueryArgs, context?: QueryContext): PageQueryItem[] | PaginatedPagesResult;
78
+ /**
79
+ * Resolve any query key against a source. Unknown types resolve to `{}` (the
80
+ * runtime containers keep their initial shape). `fetch` keys require the
81
+ * source to provide `fetchJson`.
82
+ */
83
+ export declare function resolveQueryKey(source: QuerySource, key: string, context?: QueryContext): Promise<unknown>;
@@ -0,0 +1,29 @@
1
+ import type { Block, ContentBlock } from './types';
2
+ /**
3
+ * Compute the default value for a (compact-unfolded) schema node, consulting the
4
+ * field registry for format-specific defaults (e.g. `image`, `richText`).
5
+ */
6
+ export declare function getDefaultValue(schema: any): any;
7
+ /**
8
+ * Fill missing values in `state` with schema defaults, recursing into objects.
9
+ * Returns `state` when present, otherwise a freshly generated default.
10
+ */
11
+ export declare function passDefaultValue(state: any, schema: any): any;
12
+ /** Deep-merge `patch` over `base`: plain objects merge, arrays and scalars replace. */
13
+ export declare function mergePreviewData(base: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown>;
14
+ /**
15
+ * Resolve the data a block should render with outside a page: schema defaults,
16
+ * overlaid with the block's authored `previewData`, overlaid with per-call
17
+ * overrides (e.g. the `?data=` payload of the preview route).
18
+ *
19
+ * @param props Unfolded (JSON-schema shaped) props schema, as on `Block.props`.
20
+ */
21
+ export declare function buildPreviewData(props: Record<string, unknown> | undefined, previewData?: Record<string, unknown>, overrides?: Record<string, unknown>): Record<string, unknown>;
22
+ /** Depth-first walk over a content tree, including array and named-slot children. */
23
+ export declare function walkTree(blocks: ContentBlock[], callback: (block: ContentBlock) => void): void;
24
+ type WalkSchemaCallback = (value: any, schema: any, key?: string, parent?: any, isRequired?: boolean) => void;
25
+ /** Walk a value alongside its schema, invoking `callback` for each described node. */
26
+ export declare function walkSchema(obj: any, schema: Block['props'] | any, callback: WalkSchemaCallback): void;
27
+ /** Resolve a dotted path within a data object (`'postMeta.date'`). */
28
+ export declare function getValueByPath(data: any, path: string): unknown;
29
+ export {};