@protoyard/blueprint 0.0.0-stage → 1.1.1

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 (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1145 -2
  3. package/dist/aggregate.d.ts +83 -0
  4. package/dist/aggregate.js +268 -0
  5. package/dist/app-model.d.ts +76 -0
  6. package/dist/app-model.js +287 -0
  7. package/dist/archetypes/index.d.ts +1 -0
  8. package/dist/archetypes/index.js +1 -0
  9. package/dist/archetypes/schema.d.ts +126 -0
  10. package/dist/archetypes/schema.js +77 -0
  11. package/dist/audit.d.ts +52 -0
  12. package/dist/audit.js +149 -0
  13. package/dist/authoring/deck-lint.d.ts +32 -0
  14. package/dist/authoring/deck-lint.js +567 -0
  15. package/dist/authoring/index.d.ts +1 -0
  16. package/dist/authoring/index.js +1 -0
  17. package/dist/authoring/select-deck-layouts.d.ts +50 -0
  18. package/dist/authoring/select-deck-layouts.js +213 -0
  19. package/dist/authoring-apis.d.ts +13 -0
  20. package/dist/authoring-apis.js +13 -0
  21. package/dist/authoring-validation.d.ts +65 -0
  22. package/dist/authoring-validation.js +267 -0
  23. package/dist/avatar.d.ts +25 -0
  24. package/dist/avatar.js +30 -0
  25. package/dist/brand.d.ts +70 -0
  26. package/dist/brand.js +294 -0
  27. package/dist/brief/archetypes.d.ts +28 -0
  28. package/dist/brief/archetypes.js +798 -0
  29. package/dist/brief/content-synthesize.d.ts +31 -0
  30. package/dist/brief/content-synthesize.js +210 -0
  31. package/dist/brief/deck-outlines.d.ts +96 -0
  32. package/dist/brief/deck-outlines.js +561 -0
  33. package/dist/brief/draft-bodies.d.ts +29 -0
  34. package/dist/brief/draft-bodies.js +224 -0
  35. package/dist/brief/fields.d.ts +19 -0
  36. package/dist/brief/fields.js +229 -0
  37. package/dist/brief/index.d.ts +8 -0
  38. package/dist/brief/index.js +8 -0
  39. package/dist/brief/kind-detection.d.ts +69 -0
  40. package/dist/brief/kind-detection.js +353 -0
  41. package/dist/brief/limits.d.ts +117 -0
  42. package/dist/brief/limits.js +217 -0
  43. package/dist/brief/page-doc-outlines.d.ts +80 -0
  44. package/dist/brief/page-doc-outlines.js +485 -0
  45. package/dist/brief/synthesize.d.ts +59 -0
  46. package/dist/brief/synthesize.js +511 -0
  47. package/dist/brief/text.d.ts +13 -0
  48. package/dist/brief/text.js +59 -0
  49. package/dist/canvas-screen.d.ts +31 -0
  50. package/dist/canvas-screen.js +33 -0
  51. package/dist/canvas-seed.d.ts +58 -0
  52. package/dist/canvas-seed.js +82 -0
  53. package/dist/computed/evaluate.d.ts +11 -0
  54. package/dist/computed/evaluate.js +101 -0
  55. package/dist/computed/index.d.ts +4 -0
  56. package/dist/computed/index.js +3 -0
  57. package/dist/computed/public.d.ts +14 -0
  58. package/dist/computed/public.js +19 -0
  59. package/dist/computed/schema.d.ts +10 -0
  60. package/dist/computed/schema.js +140 -0
  61. package/dist/computed/types.d.ts +70 -0
  62. package/dist/computed/types.js +1 -0
  63. package/dist/computed/validate.d.ts +6 -0
  64. package/dist/computed/validate.js +146 -0
  65. package/dist/csv-guard.d.ts +15 -0
  66. package/dist/csv-guard.js +29 -0
  67. package/dist/csv.d.ts +85 -0
  68. package/dist/csv.js +288 -0
  69. package/dist/files.d.ts +35 -0
  70. package/dist/files.js +246 -0
  71. package/dist/form-schema.d.ts +124 -0
  72. package/dist/form-schema.js +300 -0
  73. package/dist/generator-version.d.ts +11 -0
  74. package/dist/generator-version.js +51 -0
  75. package/dist/generator.d.ts +34 -0
  76. package/dist/generator.js +445 -0
  77. package/dist/identity.d.ts +34 -0
  78. package/dist/identity.js +91 -0
  79. package/dist/import/csv-shape.d.ts +40 -0
  80. package/dist/import/csv-shape.js +265 -0
  81. package/dist/import/json-shape.d.ts +55 -0
  82. package/dist/import/json-shape.js +491 -0
  83. package/dist/import/limits.d.ts +13 -0
  84. package/dist/import/limits.js +14 -0
  85. package/dist/import/sqlite-schema.d.ts +36 -0
  86. package/dist/import/sqlite-schema.js +729 -0
  87. package/dist/index.d.ts +60 -0
  88. package/dist/index.js +58 -0
  89. package/dist/kinds/anchors.d.ts +18 -0
  90. package/dist/kinds/anchors.js +46 -0
  91. package/dist/kinds/bodies.d.ts +6 -0
  92. package/dist/kinds/bodies.js +32 -0
  93. package/dist/kinds/brand-runtime.d.ts +19 -0
  94. package/dist/kinds/brand-runtime.js +35 -0
  95. package/dist/kinds/deck.d.ts +8 -0
  96. package/dist/kinds/deck.js +34 -0
  97. package/dist/kinds/doc.d.ts +19 -0
  98. package/dist/kinds/doc.js +41 -0
  99. package/dist/kinds/embed/index.d.ts +39 -0
  100. package/dist/kinds/embed/index.js +91 -0
  101. package/dist/kinds/envelope.d.ts +159 -0
  102. package/dist/kinds/envelope.js +78 -0
  103. package/dist/kinds/index.d.ts +11 -0
  104. package/dist/kinds/index.js +11 -0
  105. package/dist/kinds/kind.d.ts +36 -0
  106. package/dist/kinds/kind.js +55 -0
  107. package/dist/kinds/limits.d.ts +64 -0
  108. package/dist/kinds/limits.js +190 -0
  109. package/dist/kinds/page-doc-runtime.d.ts +37 -0
  110. package/dist/kinds/page-doc-runtime.js +55 -0
  111. package/dist/kinds/page.d.ts +16 -0
  112. package/dist/kinds/page.js +67 -0
  113. package/dist/kinds/parse.d.ts +18 -0
  114. package/dist/kinds/parse.js +27 -0
  115. package/dist/kinds/runtime.d.ts +128 -0
  116. package/dist/kinds/runtime.js +206 -0
  117. package/dist/kinds/services.d.ts +49 -0
  118. package/dist/kinds/services.js +249 -0
  119. package/dist/kinds/testing.d.ts +9 -0
  120. package/dist/kinds/testing.js +16 -0
  121. package/dist/migrations.d.ts +97 -0
  122. package/dist/migrations.js +351 -0
  123. package/dist/motion.d.ts +50 -0
  124. package/dist/motion.js +52 -0
  125. package/dist/notifications.d.ts +19 -0
  126. package/dist/notifications.js +177 -0
  127. package/dist/patch.d.ts +122 -0
  128. package/dist/patch.js +322 -0
  129. package/dist/policy.d.ts +23 -0
  130. package/dist/policy.js +105 -0
  131. package/dist/runtime-definition.d.ts +39 -0
  132. package/dist/runtime-definition.js +74 -0
  133. package/dist/scenarios.d.ts +9 -0
  134. package/dist/scenarios.js +49 -0
  135. package/dist/schema/blueprint.schema.json +10191 -0
  136. package/dist/schema-json.d.ts +6 -0
  137. package/dist/schema-json.js +36 -0
  138. package/dist/schema.d.ts +6348 -0
  139. package/dist/schema.js +1269 -0
  140. package/dist/search.d.ts +16 -0
  141. package/dist/search.js +96 -0
  142. package/dist/services.d.ts +89 -0
  143. package/dist/services.js +898 -0
  144. package/dist/share.d.ts +78 -0
  145. package/dist/share.js +234 -0
  146. package/dist/sheet-screen.d.ts +49 -0
  147. package/dist/sheet-screen.js +91 -0
  148. package/dist/storage.d.ts +43 -0
  149. package/dist/storage.js +179 -0
  150. package/dist/stream.d.ts +26 -0
  151. package/dist/stream.js +173 -0
  152. package/dist/untrusted.d.ts +63 -0
  153. package/dist/untrusted.js +502 -0
  154. package/dist/validation-tasks.d.ts +178 -0
  155. package/dist/validation-tasks.js +199 -0
  156. package/dist/validation.d.ts +35 -0
  157. package/dist/validation.js +312 -0
  158. package/dist/variants.d.ts +112 -0
  159. package/dist/variants.js +283 -0
  160. package/dist/workflows/index.d.ts +3 -0
  161. package/dist/workflows/index.js +3 -0
  162. package/dist/workflows/schema.d.ts +50 -0
  163. package/dist/workflows/schema.js +34 -0
  164. package/dist/workflows/transition.d.ts +15 -0
  165. package/dist/workflows/transition.js +25 -0
  166. package/dist/workflows/validate.d.ts +17 -0
  167. package/dist/workflows/validate.js +140 -0
  168. package/package.json +120 -3
@@ -0,0 +1,249 @@
1
+ import { FormBlockSchema } from '@protoyard/blocks';
2
+ import { BlockActionError, isReducibleBlock, parseStateKey, reduceBlockState, restoreBlockState, stateKey, } from '@protoyard/blocks/interactive';
3
+ import { ValidationError } from '@protoyard/core';
4
+ import { createFormServices } from '@protoyard/pages';
5
+ import { z } from 'zod';
6
+ import { KIND_CAPABILITIES } from "./runtime.js";
7
+ /** Bounds on how far the document walk goes (documents are already bounded by the loader). */
8
+ const MAX_WALK_NODES = 20_000;
9
+ /**
10
+ * Finds a block by id anywhere in a document body (slides, sections, columns,
11
+ * tabs, reveal panels…). Interactive blocks always carry an id, and the
12
+ * document is the one the runtime was defined from, so a caller can never
13
+ * substitute its own block definition.
14
+ */
15
+ export function findDocumentBlock(doc, blockId) {
16
+ const stack = [doc[doc.kind]];
17
+ let seen = 0;
18
+ while (stack.length > 0 && seen < MAX_WALK_NODES) {
19
+ const node = stack.pop();
20
+ seen += 1;
21
+ if (Array.isArray(node)) {
22
+ for (const item of node)
23
+ stack.push(item);
24
+ continue;
25
+ }
26
+ if (node === null || typeof node !== 'object')
27
+ continue;
28
+ const record = node;
29
+ if (typeof record.type === 'string' && record.id === blockId)
30
+ return record;
31
+ for (const key of Object.keys(record))
32
+ stack.push(record[key]);
33
+ }
34
+ return null;
35
+ }
36
+ /** A form block's fields as a strict zod schema for one submission. */
37
+ export function formResponseSchema(form) {
38
+ const shape = {};
39
+ for (const field of form.fields) {
40
+ let schema;
41
+ switch (field.type) {
42
+ case 'number': {
43
+ let n = z.number().finite();
44
+ if (field.min !== undefined)
45
+ n = n.min(field.min);
46
+ if (field.max !== undefined)
47
+ n = n.max(field.max);
48
+ schema = n;
49
+ break;
50
+ }
51
+ case 'boolean':
52
+ schema = z.boolean();
53
+ break;
54
+ case 'enum':
55
+ schema = z.enum((field.options ?? []).map((option) => option.value));
56
+ break;
57
+ case 'tags':
58
+ schema = z
59
+ .array(z.string().trim().min(1).max(64))
60
+ .max(field.maxTags ?? 20)
61
+ .refine((tags) => new Set(tags).size === tags.length, 'Tags must be unique');
62
+ break;
63
+ default: {
64
+ let text = z
65
+ .string()
66
+ .trim()
67
+ .max(field.maxLength ?? (field.type === 'longText' ? 4_000 : 400));
68
+ if (field.required)
69
+ text = text.min(1, `${field.label} is required`);
70
+ if (field.type === 'email')
71
+ schema = text.pipe(z.email().or(z.literal('')));
72
+ else if (field.type === 'url')
73
+ schema = text.pipe(z.url({ protocol: /^https?$/ }).or(z.literal('')));
74
+ else
75
+ schema = text;
76
+ }
77
+ }
78
+ shape[field.name] = field.required ? schema : schema.optional();
79
+ }
80
+ return z.strictObject(shape);
81
+ }
82
+ /** A refused form submission: the values do not fit the form, or the form does not exist. */
83
+ export class FormResponseError extends Error {
84
+ formId;
85
+ /** The first message per field name; `''` holds form-level issues such as unknown fields. */
86
+ issues;
87
+ constructor(formId, message, issues = {}) {
88
+ super(message);
89
+ this.name = 'FormResponseError';
90
+ this.formId = formId;
91
+ this.issues = issues;
92
+ }
93
+ }
94
+ function collectFormBlocks(doc) {
95
+ const forms = [];
96
+ const seen = new Set();
97
+ const stack = [doc[doc.kind]];
98
+ let n = 0;
99
+ while (stack.length > 0 && n < MAX_WALK_NODES) {
100
+ const node = stack.pop();
101
+ n += 1;
102
+ if (Array.isArray(node)) {
103
+ for (const item of node)
104
+ stack.push(item);
105
+ continue;
106
+ }
107
+ if (node === null || typeof node !== 'object')
108
+ continue;
109
+ const record = node;
110
+ if (record.type === 'form') {
111
+ const parsed = FormBlockSchema.safeParse(record);
112
+ if (parsed.success && parsed.data.id && !seen.has(parsed.data.id)) {
113
+ seen.add(parsed.data.id);
114
+ forms.push(parsed.data);
115
+ }
116
+ }
117
+ for (const key of Object.keys(record))
118
+ stack.push(record[key]);
119
+ }
120
+ return forms;
121
+ }
122
+ /** The kind services for a runtime defined by `defineKindRuntime(doc, …)`. */
123
+ export async function createKindServices(runtime, doc, options = {}) {
124
+ const { session, environment } = runtime;
125
+ const db = runtime.db;
126
+ const documentId = options.documentId ?? runtime.manifest.storage.namespace;
127
+ stateKey(documentId, 'probe'); // validates the document id once
128
+ const cache = new Map();
129
+ const listeners = new Map();
130
+ const notify = (key, state) => {
131
+ for (const listener of [...(listeners.get(key) ?? [])]) {
132
+ try {
133
+ listener(state);
134
+ }
135
+ catch {
136
+ // A subscriber's failure is its own; the stored state stands.
137
+ }
138
+ }
139
+ };
140
+ const refresh = async () => {
141
+ const rows = await db.table('blockStates').toArray();
142
+ const before = new Set(cache.keys());
143
+ cache.clear();
144
+ for (const row of rows) {
145
+ if (row.documentId === documentId && typeof row.key === 'string')
146
+ cache.set(row.key, row.state);
147
+ }
148
+ for (const key of new Set([...before, ...cache.keys()]))
149
+ notify(key, cache.get(key));
150
+ };
151
+ const actingPersona = () => {
152
+ const principal = session.principal();
153
+ if (!principal)
154
+ throw new Error('No active persona. Load a scenario first.');
155
+ return principal.persona.id;
156
+ };
157
+ const blockStates = {
158
+ read: (key) => cache.get(key),
159
+ async dispatch(key, _block, action) {
160
+ const parts = parseStateKey(key);
161
+ if (!parts || parts.documentId !== documentId) {
162
+ throw new BlockActionError(key, 'This block does not belong to this document.');
163
+ }
164
+ const block = findDocumentBlock(doc, parts.blockId);
165
+ if (!block || !isReducibleBlock(block)) {
166
+ throw new BlockActionError(key, `No interactive block "${parts.blockId}" in this document.`);
167
+ }
168
+ session.assert(KIND_CAPABILITIES.interact, block);
169
+ // Poll and quiz answers are per viewer: the viewer is the active persona, never the caller's value.
170
+ const viewerAction = (block.type === 'poll' || block.type === 'quiz') &&
171
+ action !== null &&
172
+ typeof action === 'object'
173
+ ? { ...action, viewer: actingPersona() }
174
+ : action;
175
+ const state = await environment.run('blocks.dispatch', () => db.transaction('rw', ['blockStates'], async () => {
176
+ const stored = await db.table('blockStates').get(key);
177
+ const current = restoreBlockState(block, stored?.state);
178
+ const result = reduceBlockState(block, current, viewerAction);
179
+ if (!result.ok)
180
+ throw new BlockActionError(key, result.error);
181
+ await db.table('blockStates').put({
182
+ key,
183
+ documentId,
184
+ blockId: parts.blockId,
185
+ state: result.state,
186
+ updatedAt: runtime.clock.now().toISOString(),
187
+ });
188
+ return result.state;
189
+ }));
190
+ // Only a committed write reaches the cache and the subscribers.
191
+ cache.set(key, state);
192
+ notify(key, state);
193
+ return state;
194
+ },
195
+ subscribe(key, listener) {
196
+ let set = listeners.get(key);
197
+ if (!set) {
198
+ set = new Set();
199
+ listeners.set(key, set);
200
+ }
201
+ set.add(listener);
202
+ return () => {
203
+ set.delete(listener);
204
+ };
205
+ },
206
+ refresh,
207
+ };
208
+ const formBlocks = collectFormBlocks(doc);
209
+ const pageForms = createFormServices(runtime, formBlocks, { documentId });
210
+ const responses = {
211
+ async submit(formId, values) {
212
+ // createFormServices asserts form:submit (KIND_CAPABILITIES.respond).
213
+ try {
214
+ return await pageForms.submit(formId, values);
215
+ }
216
+ catch (error) {
217
+ if (error instanceof FormResponseError)
218
+ throw error;
219
+ if (error instanceof ValidationError) {
220
+ throw new FormResponseError(formId, error.message || 'Check the highlighted fields.', {
221
+ ...error.issues,
222
+ });
223
+ }
224
+ const message = error instanceof Error ? error.message : 'Form submission failed.';
225
+ if (/No form|Permission denied/i.test(message)) {
226
+ throw new FormResponseError(formId, message);
227
+ }
228
+ throw error;
229
+ }
230
+ },
231
+ async list(formId) {
232
+ if (formId !== undefined)
233
+ return pageForms.list(formId);
234
+ const rows = [];
235
+ for (const form of formBlocks) {
236
+ if (!form.id)
237
+ continue;
238
+ rows.push(...(await pageForms.list(form.id)));
239
+ }
240
+ return rows.sort((a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : a.id.localeCompare(b.id));
241
+ },
242
+ };
243
+ await refresh();
244
+ // Scenario loads, resets and snapshot restores replace the tables: reload the cache.
245
+ const offScenario = runtime.scenarios.subscribe(() => {
246
+ void refresh().catch(() => undefined);
247
+ });
248
+ return { documentId, blockStates, responses, destroy: offScenario };
249
+ }
@@ -0,0 +1,9 @@
1
+ import type { z } from 'zod';
2
+ import type { ContentKind } from './kind.ts';
3
+ /**
4
+ * Test-only: validates a content kind against `schema` until the returned
5
+ * function runs. Lets envelope tests exercise deck, page and doc before their
6
+ * engine schemas are wired. Refuses to run outside Vitest, and it is not
7
+ * exported from the package entry.
8
+ */
9
+ export declare function withKindBodySchemaForTesting(kind: ContentKind, schema: z.ZodType): () => void;
@@ -0,0 +1,16 @@
1
+ import { setKindBodyOverride } from "./bodies.js";
2
+ /**
3
+ * Test-only: validates a content kind against `schema` until the returned
4
+ * function runs. Lets envelope tests exercise deck, page and doc before their
5
+ * engine schemas are wired. Refuses to run outside Vitest, and it is not
6
+ * exported from the package entry.
7
+ */
8
+ export function withKindBodySchemaForTesting(kind, schema) {
9
+ const env = globalThis.process
10
+ ?.env;
11
+ if (!env?.VITEST) {
12
+ throw new Error('withKindBodySchemaForTesting is only available under Vitest.');
13
+ }
14
+ setKindBodyOverride(kind, schema);
15
+ return () => setKindBodyOverride(kind, undefined);
16
+ }
@@ -0,0 +1,97 @@
1
+ import type { DatabaseVersion } from '@protoyard/core';
2
+ import { type Blueprint, type BlueprintField } from './schema.ts';
3
+ /**
4
+ * JSON-serializable upgrade steps. Persist these with the blueprint history
5
+ * and turn them into a Dexie `upgrade` via `applyUpgradeOps` /
6
+ * `toDatabaseVersion` so migrations stay data, not opaque functions.
7
+ */
8
+ export type UpgradeOp = {
9
+ op: 'backfill';
10
+ table: string;
11
+ /** Row property to set when missing (`undefined`). */
12
+ field: string;
13
+ value: unknown;
14
+ } | {
15
+ op: 'renameField';
16
+ table: string;
17
+ from: string;
18
+ to: string;
19
+ } | {
20
+ /** Copy every row from `from` into `to` (table rename). */
21
+ op: 'copyTable';
22
+ from: string;
23
+ to: string;
24
+ } | {
25
+ /** Replace existing values after an explicitly allowed incompatible schema change. */
26
+ op: 'resetField';
27
+ table: string;
28
+ field: string;
29
+ value: unknown;
30
+ };
31
+ export interface DiffBlueprintsOptions {
32
+ /**
33
+ * Allow dropping tables or fields (Dexie store entry `null` for a removed
34
+ * table). Without this, drops throw `MigrationError`.
35
+ */
36
+ allowDrop?: boolean;
37
+ /** Schema version of `prev`; the result uses `fromVersion + 1` (default 1). */
38
+ fromVersion?: number;
39
+ /**
40
+ * Explicit renames that cannot live in the strict blueprint schema.
41
+ * - `tables`: next table name → previous table name
42
+ * - `fields`: `${table}.${nextRowProperty}` → previous row property
43
+ */
44
+ renamedFrom?: {
45
+ tables?: Record<string, string>;
46
+ fields?: Record<string, string>;
47
+ };
48
+ }
49
+ /** Result of comparing two blueprints: the next Dexie version plus its ops. */
50
+ export interface BlueprintMigration {
51
+ version: number;
52
+ stores: Record<string, string | null>;
53
+ ops: readonly UpgradeOp[];
54
+ /** Dexie upgrade callback built from `ops` (same steps every run). */
55
+ upgrade?: (tx: unknown) => Promise<void>;
56
+ }
57
+ export declare class MigrationError extends Error {
58
+ readonly code: 'drop-requires-allow' | 'rename-source-missing';
59
+ constructor(code: MigrationError['code'], message: string);
60
+ }
61
+ /** Row property stored for a field (`customerId`, tags multi-entry → `tags`, …). */
62
+ export declare function fieldProperty(field: BlueprintField): string;
63
+ /**
64
+ * Default written when a field is added to an existing table. Prefers the
65
+ * field's declared `default`, otherwise a kind-appropriate empty value so
66
+ * old rows stay readable.
67
+ */
68
+ export declare function defaultForField(field: BlueprintField): unknown;
69
+ /**
70
+ * Diff two blueprints into the next `DatabaseVersion` (plus serializable ops).
71
+ * Returns `null` when stores and data shape are unchanged (no version bump).
72
+ *
73
+ * Classifies: add entity, add field (backfill default), add index (store
74
+ * string only), rename via `renamedFrom`, drop (needs `allowDrop` → `null`).
75
+ */
76
+ export declare function diffBlueprints(prev: Blueprint, next: Blueprint, options?: DiffBlueprintsOptions): BlueprintMigration | null;
77
+ /**
78
+ * Runs serializable upgrade ops against a Dexie transaction. Safe to call
79
+ * multiple times on the same ops list (backfill only fills `undefined`).
80
+ */
81
+ export declare function applyUpgradeOps(tx: unknown, ops: readonly UpgradeOp[]): Promise<void>;
82
+ /** Core `DatabaseVersion` ready to append to a prototype's `database.versions`. */
83
+ export declare function toDatabaseVersion(migration: BlueprintMigration): DatabaseVersion;
84
+ /**
85
+ * Builds the full version list: previous versions plus the diff as the tip.
86
+ * When `diffBlueprints` returns null, returns `previous` unchanged.
87
+ */
88
+ export declare function appendMigration(previous: readonly DatabaseVersion[], migration: BlueprintMigration | null): DatabaseVersion[];
89
+ /**
90
+ * Every database version of a blueprint, rebuilt from its `history`: version 1
91
+ * holds the oldest entry's stores, then each step appends the diff against the
92
+ * previous blueprint (steps that change nothing add no version). Deterministic,
93
+ * so generated prototypes and `protoyard validate` agree without running code.
94
+ */
95
+ export declare function deriveDatabaseVersions(blueprint: Blueprint): DatabaseVersion[];
96
+ /** The latest schema version `deriveDatabaseVersions` produces. */
97
+ export declare function latestSchemaVersion(blueprint: Blueprint): number;