@platforma-sdk/model 1.81.1 → 1.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/block_migrations.cjs +92 -8
- package/dist/block_migrations.cjs.map +1 -1
- package/dist/block_migrations.d.ts +121 -32
- package/dist/block_migrations.d.ts.map +1 -1
- package/dist/block_migrations.js +92 -8
- package/dist/block_migrations.js.map +1 -1
- package/dist/block_model.cjs +69 -15
- package/dist/block_model.cjs.map +1 -1
- package/dist/block_model.d.ts +58 -22
- package/dist/block_model.d.ts.map +1 -1
- package/dist/block_model.js +71 -17
- package/dist/block_model.js.map +1 -1
- package/dist/block_storage_callbacks.cjs +194 -13
- package/dist/block_storage_callbacks.cjs.map +1 -1
- package/dist/block_storage_callbacks.js +192 -15
- package/dist/block_storage_callbacks.js.map +1 -1
- package/dist/block_storage_facade.cjs +4 -1
- package/dist/block_storage_facade.cjs.map +1 -1
- package/dist/block_storage_facade.d.ts +102 -0
- package/dist/block_storage_facade.d.ts.map +1 -1
- package/dist/block_storage_facade.js +4 -1
- package/dist/block_storage_facade.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.js +1 -1
- package/package.json +10 -9
- package/src/block_migrations.ts +205 -55
- package/src/block_model.ts +190 -59
- package/src/block_storage_callbacks.ts +294 -15
- package/src/block_storage_facade.ts +95 -0
- package/src/kind_reference.test.ts +134 -0
- package/src/template_init.test.ts +413 -0
- package/src/template_params.test.ts +135 -0
|
@@ -120,7 +120,16 @@ function migrateStorage(currentStorageJson, hooks) {
|
|
|
120
120
|
* @throws If initialDataFn or createPluginData throws
|
|
121
121
|
*/
|
|
122
122
|
function createInitialStorage(hooks) {
|
|
123
|
-
|
|
123
|
+
return assembleStorage(hooks.getDefaultBlockData(), hooks);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Wraps freshly created block data and freshly created plugin data into storage.
|
|
127
|
+
*
|
|
128
|
+
* Shared by the two ways a block's first storage comes into being — from defaults
|
|
129
|
+
* and from template params. Only the block's own data differs between them:
|
|
130
|
+
* plugins have no params channel, so they are always created at their defaults.
|
|
131
|
+
*/
|
|
132
|
+
function assembleStorage(blockData, hooks) {
|
|
124
133
|
const pluginRegistry = hooks.getPluginRegistry();
|
|
125
134
|
const plugins = {};
|
|
126
135
|
for (const handle of Object.keys(pluginRegistry)) {
|
|
@@ -132,57 +141,229 @@ function createInitialStorage(hooks) {
|
|
|
132
141
|
}
|
|
133
142
|
return (0, _milaboratories_pl_model_common.stringifyJson)({
|
|
134
143
|
[require_block_storage.BLOCK_STORAGE_KEY]: "v1",
|
|
135
|
-
__dataVersion:
|
|
136
|
-
__data:
|
|
144
|
+
__dataVersion: blockData.version,
|
|
145
|
+
__data: blockData.data,
|
|
137
146
|
__pluginRegistry: pluginRegistry,
|
|
138
147
|
__plugins: plugins
|
|
139
148
|
});
|
|
140
149
|
}
|
|
141
150
|
/**
|
|
151
|
+
* Check params against their kind's declared shape.
|
|
152
|
+
*
|
|
153
|
+
* The kind owns this rather than the block because the params contract belongs to the
|
|
154
|
+
* kind: many block versions implement one kind, and a per-block check would let them
|
|
155
|
+
* drift from each other and from the type.
|
|
156
|
+
*
|
|
157
|
+
* A parser rejects by throwing and accepts by returning the params to use, so its
|
|
158
|
+
* output — not the input — is what flows onward. That is what lets a kind strip keys
|
|
159
|
+
* it does not declare, which is the difference between a typo being ignored and a typo
|
|
160
|
+
* being reported.
|
|
161
|
+
*
|
|
162
|
+
* Every kind declares a parser, so every set of params that reaches here is checked;
|
|
163
|
+
* there is no pass-through path.
|
|
164
|
+
*
|
|
165
|
+
* @param value The params to check, references already in live form
|
|
166
|
+
* @param parseInitializationParams The kind's parser
|
|
167
|
+
*/
|
|
168
|
+
function validateTemplateParams(value, parseInitializationParams) {
|
|
169
|
+
try {
|
|
170
|
+
return { value: parseInitializationParams(value) };
|
|
171
|
+
} catch (e) {
|
|
172
|
+
return { error: `params do not match this block's kind: ${describeRejection(e)}` };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Render whatever a parser threw as one readable line.
|
|
177
|
+
*
|
|
178
|
+
* An error carrying an `issues` array is unpacked rather than printed: that is the
|
|
179
|
+
* shape zod (and several others) use, and its `message` is the whole issue list as
|
|
180
|
+
* JSON — technically complete and unreadable in a dialog. Duck-typed on purpose, since
|
|
181
|
+
* this package prescribes no schema library and takes no dependency on one; anything
|
|
182
|
+
* else falls back to its own message.
|
|
183
|
+
*/
|
|
184
|
+
function describeRejection(e) {
|
|
185
|
+
const issues = e.issues;
|
|
186
|
+
if (!Array.isArray(issues) || issues.length === 0) return messageOf(e);
|
|
187
|
+
return issues.map((issue) => {
|
|
188
|
+
const { path, message } = issue;
|
|
189
|
+
const what = typeof message === "string" ? message : "is invalid";
|
|
190
|
+
const where = Array.isArray(path) ? formatPath(path) : "";
|
|
191
|
+
return where === "" ? what : `${where}: ${what}`;
|
|
192
|
+
}).join("; ");
|
|
193
|
+
}
|
|
194
|
+
/** `["numbers", 0]` → `numbers[0]` — how the params are written, not how they parse. */
|
|
195
|
+
function formatPath(path) {
|
|
196
|
+
return path.reduce((acc, segment) => {
|
|
197
|
+
if (typeof segment === "number") return `${acc}[${segment}]`;
|
|
198
|
+
return acc === "" ? String(segment) : `${acc}.${String(segment)}`;
|
|
199
|
+
}, "");
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Check params that crossed into the model VM as text.
|
|
203
|
+
*
|
|
204
|
+
* The pre-flight entry point: a caller asks this before creating anything, once per
|
|
205
|
+
* template entry, so params a kind rejects are reported while there is still no
|
|
206
|
+
* project to half-build.
|
|
207
|
+
*
|
|
208
|
+
* The readable reference spelling is expanded here too, so what the kind checks is the shape it
|
|
209
|
+
* declared. The ids it sees are the file's own — no block exists yet — which is why a kind must
|
|
210
|
+
* not read meaning into a specific id.
|
|
211
|
+
*
|
|
212
|
+
* @param paramsJson The entry's params as JSON string
|
|
213
|
+
* @param parseInitializationParams The kind's parser
|
|
214
|
+
*/
|
|
215
|
+
function validateTemplateParamsJson(paramsJson, parseInitializationParams) {
|
|
216
|
+
let params;
|
|
217
|
+
try {
|
|
218
|
+
params = JSON.parse(paramsJson);
|
|
219
|
+
} catch (e) {
|
|
220
|
+
return { error: `params are not valid JSON: ${messageOf(e)}` };
|
|
221
|
+
}
|
|
222
|
+
const result = validateTemplateParams((0, _milaboratories_pl_model_common.expandTemplateRefs)(params), parseInitializationParams);
|
|
223
|
+
if (result.error !== void 0) return { error: result.error };
|
|
224
|
+
return {};
|
|
225
|
+
}
|
|
226
|
+
function messageOf(e) {
|
|
227
|
+
return e instanceof Error ? e.message : String(e);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Creates complete initial storage for a block being created from template params.
|
|
231
|
+
*
|
|
232
|
+
* The inverse of {@link deriveTemplateParamsFromStorage}: that projects storage
|
|
233
|
+
* into params, this builds storage from them. The params are handed to the block's
|
|
234
|
+
* init factory, whose output is versioned and wrapped exactly as
|
|
235
|
+
* {@link createInitialStorage} wraps the defaults — so a block created from a
|
|
236
|
+
* template is indistinguishable from one created in the UI and then edited.
|
|
237
|
+
*
|
|
238
|
+
* **This is where a template's references become the block's own**, and it is the only place in
|
|
239
|
+
* a template's life where a reference is recognized at all. Params travel from the file
|
|
240
|
+
* untouched — the engine carrying them neither marks a reference nor reads one, because
|
|
241
|
+
* recognizing one means knowing the reference system, and that knowledge is here.
|
|
242
|
+
*
|
|
243
|
+
* Two things happen, in this order. The readable spelling a person may have written
|
|
244
|
+
* (`{ block, name }`) is expanded into the `PlRef` it stands for. Then every reference naming
|
|
245
|
+
* an entry that has a block is repointed at it: `blockIds` maps each template-local entry id to
|
|
246
|
+
* the block id it was given, and an id it does not name is left alone, which is how a reference
|
|
247
|
+
* to an entry created later ends up naming nothing rather than naming the wrong block.
|
|
248
|
+
*
|
|
249
|
+
* Relocation happens before the kind's parser and before the factory, so both see the ids the
|
|
250
|
+
* block will actually hold. It is one step of this function rather than a callback of its own
|
|
251
|
+
* precisely because nothing else wants its result: every VM call re-instantiates the runtime
|
|
252
|
+
* and re-evaluates the whole model bundle, so a separate call would parse the block twice per
|
|
253
|
+
* entry to produce a value only the next line reads.
|
|
254
|
+
*
|
|
255
|
+
* Params arrive as JSON text because this runs across the model-VM boundary, where
|
|
256
|
+
* only strings pass. Anything the factory rejects is returned as an error rather
|
|
257
|
+
* than thrown: applying a hand-written template is expected to surface bad params,
|
|
258
|
+
* and the applier reports every entry's problem in one pass.
|
|
259
|
+
*
|
|
260
|
+
* @param paramsJson - The entry's params as JSON string, exactly as the file held them
|
|
261
|
+
* @param blockIdsJson - template-local entry id → assigned block id, as a JSON object
|
|
262
|
+
* @param hooks - The block's init factory plus plugin creation
|
|
263
|
+
* @returns The storage to write, or why the params could not produce any
|
|
264
|
+
*/
|
|
265
|
+
function createInitialStorageFromParams(paramsJson, blockIdsJson, hooks) {
|
|
266
|
+
let params;
|
|
267
|
+
try {
|
|
268
|
+
params = JSON.parse(paramsJson);
|
|
269
|
+
} catch (e) {
|
|
270
|
+
return { error: `params are not valid JSON: ${messageOf(e)}` };
|
|
271
|
+
}
|
|
272
|
+
let blockIds;
|
|
273
|
+
try {
|
|
274
|
+
blockIds = new Map(Object.entries(JSON.parse(blockIdsJson)));
|
|
275
|
+
} catch (e) {
|
|
276
|
+
return { error: `this block was not told which blocks the template's references should point at (${messageOf(e)}). The application applying the template is older than the block; rebuild or update it.` };
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
params = (0, _milaboratories_pl_model_common.relocateBlockIds)((0, _milaboratories_pl_model_common.expandTemplateRefs)(params), blockIds);
|
|
280
|
+
} catch (e) {
|
|
281
|
+
return { error: `this entry's references could not be relocated: ${messageOf(e)}` };
|
|
282
|
+
}
|
|
283
|
+
const checked = validateTemplateParams(params, hooks.parseInitializationParams);
|
|
284
|
+
if (checked.error !== void 0) return { error: checked.error };
|
|
285
|
+
try {
|
|
286
|
+
return { storageJson: assembleStorage(hooks.getBlockDataFromParams(checked.value), hooks) };
|
|
287
|
+
} catch (e) {
|
|
288
|
+
return { error: `init() threw on the given params: ${messageOf(e)}` };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
142
292
|
* Derives args from storage using the provided args function.
|
|
143
293
|
* This extracts data from storage and passes it to the block's args() function.
|
|
144
294
|
*
|
|
145
295
|
* @param storageJson - Storage as JSON string
|
|
146
|
-
* @param
|
|
296
|
+
* @param deriveArgs - The block's args derivation function
|
|
147
297
|
* @returns ArgsDeriveResult with derived args or error
|
|
148
298
|
*/
|
|
149
|
-
function deriveArgsFromStorage(storageJson,
|
|
299
|
+
function deriveArgsFromStorage(storageJson, deriveArgs) {
|
|
150
300
|
const { data } = normalizeStorage(storageJson);
|
|
151
301
|
try {
|
|
152
|
-
return { value:
|
|
302
|
+
return { value: deriveArgs(data) };
|
|
153
303
|
} catch (e) {
|
|
154
304
|
return { error: `args() threw: ${e instanceof Error ? e.message : String(e)}` };
|
|
155
305
|
}
|
|
156
306
|
}
|
|
157
307
|
/**
|
|
158
308
|
* Derives prerunArgs from storage.
|
|
159
|
-
* Uses
|
|
309
|
+
* Uses derivePrerunArgs if provided, otherwise falls back to deriveArgs.
|
|
160
310
|
*
|
|
161
311
|
* @param storageJson - Storage as JSON string
|
|
162
|
-
* @param
|
|
163
|
-
* @param
|
|
312
|
+
* @param deriveArgs - The block's args derivation function (fallback)
|
|
313
|
+
* @param derivePrerunArgs - Optional prerun args derivation function
|
|
164
314
|
* @returns ArgsDeriveResult with derived prerunArgs or error
|
|
165
315
|
*/
|
|
166
|
-
function derivePrerunArgsFromStorage(storageJson,
|
|
316
|
+
function derivePrerunArgsFromStorage(storageJson, deriveArgs, derivePrerunArgs) {
|
|
167
317
|
const { data } = normalizeStorage(storageJson);
|
|
168
|
-
if (
|
|
169
|
-
return { value:
|
|
318
|
+
if (derivePrerunArgs) try {
|
|
319
|
+
return { value: derivePrerunArgs(data) };
|
|
170
320
|
} catch (e) {
|
|
171
321
|
return { error: `prerunArgs() threw: ${e instanceof Error ? e.message : String(e)}` };
|
|
172
322
|
}
|
|
173
323
|
try {
|
|
174
|
-
return { value:
|
|
324
|
+
return { value: deriveArgs(data) };
|
|
175
325
|
} catch (e) {
|
|
176
326
|
return { error: `args() threw (fallback): ${e instanceof Error ? e.message : String(e)}` };
|
|
177
327
|
}
|
|
178
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Derives this block's template-entry params from storage.
|
|
331
|
+
*
|
|
332
|
+
* The inverse of the data model's `init`: `init` turns `params` into data, this
|
|
333
|
+
* turns data back into the params that would recreate it.
|
|
334
|
+
*
|
|
335
|
+
* The lambda returns ordinary live params — references as `PlRef`s, column identifiers as they
|
|
336
|
+
* are stored — and that is exactly what gets written. Nothing is marked, normalized or
|
|
337
|
+
* rewritten on the way out: a template holds what the block holds. Repointing those references
|
|
338
|
+
* at another project is the business of {@link createInitialStorageFromParams}, on the way back
|
|
339
|
+
* in, where the ids to point at are known.
|
|
340
|
+
*
|
|
341
|
+
* Every block declares the lambda, so every export produces params; a block with
|
|
342
|
+
* nothing worth restoring returns `{}` rather than declining.
|
|
343
|
+
*
|
|
344
|
+
* @param storageJson - Storage as JSON string
|
|
345
|
+
* @param deriveTemplateParams - The block's templateParams lambda
|
|
346
|
+
* @returns ArgsDeriveResult holding the params exactly as the block projected them
|
|
347
|
+
*/
|
|
348
|
+
function deriveTemplateParamsFromStorage(storageJson, deriveTemplateParams) {
|
|
349
|
+
const { data } = normalizeStorage(storageJson);
|
|
350
|
+
try {
|
|
351
|
+
return { value: deriveTemplateParams(data) };
|
|
352
|
+
} catch (e) {
|
|
353
|
+
return { error: `templateParams() threw: ${e instanceof Error ? e.message : String(e)}` };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
179
356
|
//#endregion
|
|
180
357
|
exports.BLOCK_STORAGE_KEY = require_block_storage.BLOCK_STORAGE_KEY;
|
|
181
358
|
exports.applyStorageUpdate = applyStorageUpdate;
|
|
182
359
|
exports.createInitialStorage = createInitialStorage;
|
|
360
|
+
exports.createInitialStorageFromParams = createInitialStorageFromParams;
|
|
183
361
|
exports.deriveArgsFromStorage = deriveArgsFromStorage;
|
|
184
362
|
exports.derivePrerunArgsFromStorage = derivePrerunArgsFromStorage;
|
|
363
|
+
exports.deriveTemplateParamsFromStorage = deriveTemplateParamsFromStorage;
|
|
185
364
|
exports.getStorageDebugView = getStorageDebugView;
|
|
186
365
|
exports.migrateStorage = migrateStorage;
|
|
366
|
+
exports.validateTemplateParams = validateTemplateParams;
|
|
367
|
+
exports.validateTemplateParamsJson = validateTemplateParamsJson;
|
|
187
368
|
|
|
188
369
|
//# sourceMappingURL=block_storage_callbacks.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block_storage_callbacks.cjs","names":["createBlockStorage","isBlockStorage","normalizeBlockStorage","getStorageData","updateStorageData","obj","migrateBlockStorage","BLOCK_STORAGE_KEY"],"sources":["../src/block_storage_callbacks.ts"],"sourcesContent":["/**\n * BlockStorage Callback Implementations - wired to facade callbacks in BlockModelV3.done().\n *\n * Provides pure functions for storage operations (migration, initialization,\n * args derivation, updates, debug views). Each function takes its dependencies\n * explicitly as parameters.\n *\n * @module block_storage_callbacks\n * @internal\n */\n\nimport {\n BLOCK_STORAGE_KEY,\n BLOCK_STORAGE_SCHEMA_VERSION,\n type BlockStorage,\n type MutateStoragePayload,\n type PluginRegistry,\n type VersionedData,\n createBlockStorage,\n getStorageData,\n isBlockStorage,\n migrateBlockStorage,\n normalizeBlockStorage,\n updateStorageData,\n} from \"./block_storage\";\nimport type { PluginHandle } from \"./plugin_handle\";\n\nimport { stringifyJson, type StringifiedJson } from \"@milaboratories/pl-model-common\";\nimport type { DataVersioned, TransferRecord } from \"./block_migrations\";\nimport type { StorageDebugView } from \"@milaboratories/pl-model-middle-layer\";\n\n// =============================================================================\n// Hook interfaces for dependency injection\n// =============================================================================\n\n/** Dependencies for storage migration */\nexport interface MigrationHooks {\n migrateBlockData: (versioned: DataVersioned<unknown>) => DataVersioned<unknown> & {\n transfers: TransferRecord;\n };\n getPluginRegistry: () => PluginRegistry;\n migratePluginData: (\n handle: PluginHandle,\n versioned: DataVersioned<unknown>,\n ) => DataVersioned<unknown> | undefined;\n createPluginData: (\n handle: PluginHandle,\n transfer?: DataVersioned<unknown>,\n ) => DataVersioned<unknown>;\n}\n\n/** Dependencies for initial storage creation */\nexport interface InitialStorageHooks {\n getDefaultBlockData: () => DataVersioned<unknown>;\n getPluginRegistry: () => PluginRegistry;\n createPluginData: (handle: PluginHandle) => DataVersioned<unknown>;\n}\n\n/**\n * Result of storage normalization\n */\nexport interface NormalizeStorageResult {\n /** The normalized BlockStorage object */\n storage: BlockStorage;\n /** The extracted data (what developers see) */\n data: unknown;\n}\n\n/**\n * Normalizes raw storage data and extracts state.\n * Handles all formats:\n * - New BlockStorage format (has discriminator)\n * - Legacy V1/V2 format ({ args, uiState })\n * - Raw V3 state (any other format)\n *\n * @param rawStorage - Raw data from blockStorage field (may be JSON string or object)\n * @returns Object with normalized storage and extracted state\n */\nfunction normalizeStorage(rawStorage: unknown): NormalizeStorageResult {\n // Handle undefined/null\n if (rawStorage === undefined || rawStorage === null) {\n const storage = createBlockStorage({});\n return { storage, data: {} };\n }\n\n // Parse JSON string if needed\n let parsed = rawStorage;\n if (typeof rawStorage === \"string\") {\n try {\n parsed = JSON.parse(rawStorage);\n } catch {\n // If parsing fails, treat string as the data\n const storage = createBlockStorage(rawStorage);\n return { storage, data: rawStorage };\n }\n }\n\n // Check for BlockStorage format (has discriminator)\n if (isBlockStorage(parsed)) {\n const storage = normalizeBlockStorage(parsed);\n return { storage, data: getStorageData(storage) };\n }\n\n // Check for legacy V1/V2 format: { args, uiState }\n if (isLegacyModelV1ApiFormat(parsed)) {\n // For legacy format, the whole object IS the data\n const storage = createBlockStorage(parsed);\n return { storage, data: parsed };\n }\n\n // Raw V3 data - wrap it\n const storage = createBlockStorage(parsed);\n return { storage, data: parsed };\n}\n\n/**\n * Applies a state update to existing storage.\n * Used when setData is called from the frontend.\n *\n * @param currentStorageJson - Current storage as JSON string (must be defined)\n * @param payload - Update payload with operation type and value\n * @returns Updated storage as StringifiedJson<BlockStorage>\n */\nexport function applyStorageUpdate(\n currentStorageJson: string,\n payload: MutateStoragePayload,\n): StringifiedJson<BlockStorage> {\n const { storage: currentStorage } = normalizeStorage(currentStorageJson);\n\n // Update data while preserving other storage fields (version, plugins)\n const updatedStorage = updateStorageData(currentStorage, payload);\n\n return stringifyJson(updatedStorage);\n}\n\n/**\n * Checks if data is in legacy Model API v1 format.\n * Legacy format has { args, uiState? } at top level without the BlockStorage discriminator.\n */\nfunction isLegacyModelV1ApiFormat(data: unknown): data is { args?: unknown } {\n if (data === null || typeof data !== \"object\") return false;\n if (isBlockStorage(data)) return false;\n\n const obj = data as Record<string, unknown>;\n return \"args\" in obj;\n}\n\n// =============================================================================\n// Facade Callback Implementations\n// =============================================================================\n\n/**\n * Gets storage debug view from raw storage data.\n * Returns structured debug info about the storage state.\n *\n * @param rawStorage - Raw data from blockStorage field (may be JSON string or object)\n * @returns JSON string with storage debug view\n */\nexport function getStorageDebugView(rawStorage: unknown): StringifiedJson<StorageDebugView> {\n const { storage } = normalizeStorage(rawStorage);\n const debugView: StorageDebugView = {\n dataVersion: storage.__dataVersion,\n data: storage.__data,\n };\n return stringifyJson(debugView);\n}\n\n// =============================================================================\n// Migration Support\n// =============================================================================\n\n/**\n * Result of storage migration.\n * Returned by __pl_storage_migrate callback.\n *\n * - Error result: { error: string } - serious failure (no context, etc.)\n * - Success result: { newStorageJson: StringifiedJson<BlockStorage>, info: string } - migration succeeded\n */\nexport type MigrationResult =\n | { error: string }\n | { error?: undefined; newStorageJson: StringifiedJson<BlockStorage>; info: string };\n\n/**\n * Runs storage migration using the provided hooks.\n * This is the main entry point for the middle layer to trigger migrations.\n *\n * @param currentStorageJson - Current storage as JSON string (or undefined)\n * @param hooks - Migration dependencies (block/plugin data migration and creation functions)\n * @returns MigrationResult\n */\nexport function migrateStorage(\n currentStorageJson: string | undefined,\n hooks: MigrationHooks,\n): MigrationResult {\n // Normalize current storage\n const { storage: currentStorage } = normalizeStorage(currentStorageJson);\n\n const newPluginRegistry = hooks.getPluginRegistry();\n\n // Perform atomic migration of block + all plugins\n const migrationResult = migrateBlockStorage(currentStorage, {\n migrateBlockData: hooks.migrateBlockData,\n migratePluginData: hooks.migratePluginData,\n newPluginRegistry,\n createPluginData: hooks.createPluginData,\n });\n\n if (!migrationResult.success) {\n return {\n error: `Migration failed at '${migrationResult.failedAt}': ${migrationResult.error}`,\n };\n }\n\n // Build info message\n const oldVersion = currentStorage.__dataVersion;\n const newVersion = migrationResult.storage.__dataVersion;\n const info =\n oldVersion === newVersion\n ? `No migration needed (${oldVersion})`\n : `Migrated ${oldVersion} -> ${newVersion}`;\n\n return {\n newStorageJson: stringifyJson(migrationResult.storage),\n info,\n };\n}\n\n// =============================================================================\n// Initial Storage Creation\n// =============================================================================\n\n/**\n * Creates complete initial storage (block data + all plugin data) atomically.\n *\n * @param hooks - Dependencies for creating initial block and plugin data\n * @returns Initial storage as branded JSON string\n * @throws If initialDataFn or createPluginData throws\n */\nexport function createInitialStorage(hooks: InitialStorageHooks): StringifiedJson<BlockStorage> {\n const blockDefault = hooks.getDefaultBlockData();\n const pluginRegistry = hooks.getPluginRegistry();\n\n const plugins: Record<PluginHandle, VersionedData<unknown>> = {};\n for (const handle of Object.keys(pluginRegistry) as PluginHandle[]) {\n const initial = hooks.createPluginData(handle);\n plugins[handle] = { __dataVersion: initial.version, __data: initial.data };\n }\n\n const storage: BlockStorage = {\n [BLOCK_STORAGE_KEY]: BLOCK_STORAGE_SCHEMA_VERSION,\n __dataVersion: blockDefault.version,\n __data: blockDefault.data,\n __pluginRegistry: pluginRegistry,\n __plugins: plugins,\n };\n return stringifyJson(storage);\n}\n\n// =============================================================================\n// Args Derivation from Storage\n// =============================================================================\n\n/**\n * Result of args derivation from storage.\n * Returned by __pl_args_derive and __pl_prerunArgs_derive callbacks.\n */\nexport type ArgsDeriveResult = { error: string } | { error?: undefined; value: unknown };\n\n/**\n * Derives args from storage using the provided args function.\n * This extracts data from storage and passes it to the block's args() function.\n *\n * @param storageJson - Storage as JSON string\n * @param argsFunction - The block's args derivation function\n * @returns ArgsDeriveResult with derived args or error\n */\nexport function deriveArgsFromStorage(\n storageJson: string,\n argsFunction: (data: unknown) => unknown,\n): ArgsDeriveResult {\n // Extract data from storage\n const { data } = normalizeStorage(storageJson);\n\n // Call the args function with extracted data\n try {\n const result = argsFunction(data);\n return { value: result };\n } catch (e) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n return { error: `args() threw: ${errorMsg}` };\n }\n}\n\n/**\n * Derives prerunArgs from storage.\n * Uses prerunArgsFunction if provided, otherwise falls back to argsFunction.\n *\n * @param storageJson - Storage as JSON string\n * @param argsFunction - The block's args derivation function (fallback)\n * @param prerunArgsFunction - Optional prerun args derivation function\n * @returns ArgsDeriveResult with derived prerunArgs or error\n */\nexport function derivePrerunArgsFromStorage(\n storageJson: string,\n argsFunction: (data: unknown) => unknown,\n prerunArgsFunction?: (data: unknown) => unknown,\n): ArgsDeriveResult {\n // Extract data from storage\n const { data } = normalizeStorage(storageJson);\n\n // Try prerunArgs function first if available\n if (prerunArgsFunction) {\n try {\n const result = prerunArgsFunction(data);\n return { value: result };\n } catch (e) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n return { error: `prerunArgs() threw: ${errorMsg}` };\n }\n }\n\n // Fall back to args function\n try {\n const result = argsFunction(data);\n return { value: result };\n } catch (e) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n return { error: `args() threw (fallback): ${errorMsg}` };\n }\n}\n\n// Export discriminator key and schema version for external checks\nexport { BLOCK_STORAGE_KEY, BLOCK_STORAGE_SCHEMA_VERSION };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA8EA,SAAS,iBAAiB,YAA6C;CAErE,IAAI,eAAe,KAAA,KAAa,eAAe,MAE7C,OAAO;EAAE,SADOA,sBAAAA,mBAAmB,CAAC,CACrB;EAAG,MAAM,CAAC;CAAE;CAI7B,IAAI,SAAS;CACb,IAAI,OAAO,eAAe,UACxB,IAAI;EACF,SAAS,KAAK,MAAM,UAAU;CAChC,QAAQ;EAGN,OAAO;GAAE,SADOA,sBAAAA,mBAAmB,UACpB;GAAG,MAAM;EAAW;CACrC;CAIF,IAAIC,sBAAAA,eAAe,MAAM,GAAG;EAC1B,MAAM,UAAUC,sBAAAA,sBAAsB,MAAM;EAC5C,OAAO;GAAE;GAAS,MAAMC,sBAAAA,eAAe,OAAO;EAAE;CAClD;CAGA,IAAI,yBAAyB,MAAM,GAGjC,OAAO;EAAE,SADOH,sBAAAA,mBAAmB,MACpB;EAAG,MAAM;CAAO;CAKjC,OAAO;EAAE,SADOA,sBAAAA,mBAAmB,MACpB;EAAG,MAAM;CAAO;AACjC;;;;;;;;;AAUA,SAAgB,mBACd,oBACA,SAC+B;CAC/B,MAAM,EAAE,SAAS,mBAAmB,iBAAiB,kBAAkB;CAKvE,QAAA,GAAA,gCAAA,cAAA,CAFuBI,sBAAAA,kBAAkB,gBAAgB,OAEvB,CAAC;AACrC;;;;;AAMA,SAAS,yBAAyB,MAA2C;CAC3E,IAAI,SAAS,QAAQ,OAAO,SAAS,UAAU,OAAO;CACtD,IAAIH,sBAAAA,eAAe,IAAI,GAAG,OAAO;CAGjC,OAAO,UAAUI;AACnB;;;;;;;;AAaA,SAAgB,oBAAoB,YAAwD;CAC1F,MAAM,EAAE,YAAY,iBAAiB,UAAU;CAK/C,QAAA,GAAA,gCAAA,cAAA,CAAqB;EAHnB,aAAa,QAAQ;EACrB,MAAM,QAAQ;CAEa,CAAC;AAChC;;;;;;;;;AAyBA,SAAgB,eACd,oBACA,OACiB;CAEjB,MAAM,EAAE,SAAS,mBAAmB,iBAAiB,kBAAkB;CAEvE,MAAM,oBAAoB,MAAM,kBAAkB;CAGlD,MAAM,kBAAkBC,sBAAAA,oBAAoB,gBAAgB;EAC1D,kBAAkB,MAAM;EACxB,mBAAmB,MAAM;EACzB;EACA,kBAAkB,MAAM;CAC1B,CAAC;CAED,IAAI,CAAC,gBAAgB,SACnB,OAAO,EACL,OAAO,wBAAwB,gBAAgB,SAAS,KAAK,gBAAgB,QAC/E;CAIF,MAAM,aAAa,eAAe;CAClC,MAAM,aAAa,gBAAgB,QAAQ;CAC3C,MAAM,OACJ,eAAe,aACX,wBAAwB,WAAW,KACnC,YAAY,WAAW,MAAM;CAEnC,OAAO;EACL,iBAAA,GAAA,gCAAA,cAAA,CAA8B,gBAAgB,OAAO;EACrD;CACF;AACF;;;;;;;;AAaA,SAAgB,qBAAqB,OAA2D;CAC9F,MAAM,eAAe,MAAM,oBAAoB;CAC/C,MAAM,iBAAiB,MAAM,kBAAkB;CAE/C,MAAM,UAAwD,CAAC;CAC/D,KAAK,MAAM,UAAU,OAAO,KAAK,cAAc,GAAqB;EAClE,MAAM,UAAU,MAAM,iBAAiB,MAAM;EAC7C,QAAQ,UAAU;GAAE,eAAe,QAAQ;GAAS,QAAQ,QAAQ;EAAK;CAC3E;CASA,QAAA,GAAA,gCAAA,cAAA,CAAqB;GANlBC,sBAAAA,oBAAAA;EACD,eAAe,aAAa;EAC5B,QAAQ,aAAa;EACrB,kBAAkB;EAClB,WAAW;CAEc,CAAC;AAC9B;;;;;;;;;AAoBA,SAAgB,sBACd,aACA,cACkB;CAElB,MAAM,EAAE,SAAS,iBAAiB,WAAW;CAG7C,IAAI;EAEF,OAAO,EAAE,OADM,aAAa,IACP,EAAE;CACzB,SAAS,GAAG;EAEV,OAAO,EAAE,OAAO,iBADC,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,IACd;CAC9C;AACF;;;;;;;;;;AAWA,SAAgB,4BACd,aACA,cACA,oBACkB;CAElB,MAAM,EAAE,SAAS,iBAAiB,WAAW;CAG7C,IAAI,oBACF,IAAI;EAEF,OAAO,EAAE,OADM,mBAAmB,IACb,EAAE;CACzB,SAAS,GAAG;EAEV,OAAO,EAAE,OAAO,uBADC,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,IACR;CACpD;CAIF,IAAI;EAEF,OAAO,EAAE,OADM,aAAa,IACP,EAAE;CACzB,SAAS,GAAG;EAEV,OAAO,EAAE,OAAO,4BADC,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,IACH;CACzD;AACF"}
|
|
1
|
+
{"version":3,"file":"block_storage_callbacks.cjs","names":["createBlockStorage","isBlockStorage","normalizeBlockStorage","getStorageData","updateStorageData","obj","migrateBlockStorage","BLOCK_STORAGE_KEY"],"sources":["../src/block_storage_callbacks.ts"],"sourcesContent":["/**\n * BlockStorage Callback Implementations - wired to facade callbacks in BlockModelV3.done().\n *\n * Provides pure functions for storage operations (migration, initialization,\n * args derivation, updates, debug views). Each function takes its dependencies\n * explicitly as parameters.\n *\n * @module block_storage_callbacks\n * @internal\n */\n\nimport {\n BLOCK_STORAGE_KEY,\n BLOCK_STORAGE_SCHEMA_VERSION,\n type BlockStorage,\n type MutateStoragePayload,\n type PluginRegistry,\n type VersionedData,\n createBlockStorage,\n getStorageData,\n isBlockStorage,\n migrateBlockStorage,\n normalizeBlockStorage,\n updateStorageData,\n} from \"./block_storage\";\nimport type { PluginHandle } from \"./plugin_handle\";\n\nimport {\n stringifyJson,\n expandTemplateRefs,\n relocateBlockIds,\n type StringifiedJson,\n} from \"@milaboratories/pl-model-common\";\nimport type { DataVersioned, TransferRecord } from \"./block_migrations\";\nimport type { StorageDebugView } from \"@milaboratories/pl-model-middle-layer\";\n\n// =============================================================================\n// Hook interfaces for dependency injection\n// =============================================================================\n\n/** Dependencies for storage migration */\nexport interface MigrationHooks {\n migrateBlockData: (versioned: DataVersioned<unknown>) => DataVersioned<unknown> & {\n transfers: TransferRecord;\n };\n getPluginRegistry: () => PluginRegistry;\n migratePluginData: (\n handle: PluginHandle,\n versioned: DataVersioned<unknown>,\n ) => DataVersioned<unknown> | undefined;\n createPluginData: (\n handle: PluginHandle,\n transfer?: DataVersioned<unknown>,\n ) => DataVersioned<unknown>;\n}\n\n/** Dependencies for initial storage creation */\nexport interface InitialStorageHooks {\n getDefaultBlockData: () => DataVersioned<unknown>;\n getPluginRegistry: () => PluginRegistry;\n createPluginData: (handle: PluginHandle) => DataVersioned<unknown>;\n}\n\n/**\n * Result of storage normalization\n */\nexport interface NormalizeStorageResult {\n /** The normalized BlockStorage object */\n storage: BlockStorage;\n /** The extracted data (what developers see) */\n data: unknown;\n}\n\n/**\n * Normalizes raw storage data and extracts state.\n * Handles all formats:\n * - New BlockStorage format (has discriminator)\n * - Legacy V1/V2 format ({ args, uiState })\n * - Raw V3 state (any other format)\n *\n * @param rawStorage - Raw data from blockStorage field (may be JSON string or object)\n * @returns Object with normalized storage and extracted state\n */\nfunction normalizeStorage(rawStorage: unknown): NormalizeStorageResult {\n // Handle undefined/null\n if (rawStorage === undefined || rawStorage === null) {\n const storage = createBlockStorage({});\n return { storage, data: {} };\n }\n\n // Parse JSON string if needed\n let parsed = rawStorage;\n if (typeof rawStorage === \"string\") {\n try {\n parsed = JSON.parse(rawStorage);\n } catch {\n // If parsing fails, treat string as the data\n const storage = createBlockStorage(rawStorage);\n return { storage, data: rawStorage };\n }\n }\n\n // Check for BlockStorage format (has discriminator)\n if (isBlockStorage(parsed)) {\n const storage = normalizeBlockStorage(parsed);\n return { storage, data: getStorageData(storage) };\n }\n\n // Check for legacy V1/V2 format: { args, uiState }\n if (isLegacyModelV1ApiFormat(parsed)) {\n // For legacy format, the whole object IS the data\n const storage = createBlockStorage(parsed);\n return { storage, data: parsed };\n }\n\n // Raw V3 data - wrap it\n const storage = createBlockStorage(parsed);\n return { storage, data: parsed };\n}\n\n/**\n * Applies a state update to existing storage.\n * Used when setData is called from the frontend.\n *\n * @param currentStorageJson - Current storage as JSON string (must be defined)\n * @param payload - Update payload with operation type and value\n * @returns Updated storage as StringifiedJson<BlockStorage>\n */\nexport function applyStorageUpdate(\n currentStorageJson: string,\n payload: MutateStoragePayload,\n): StringifiedJson<BlockStorage> {\n const { storage: currentStorage } = normalizeStorage(currentStorageJson);\n\n // Update data while preserving other storage fields (version, plugins)\n const updatedStorage = updateStorageData(currentStorage, payload);\n\n return stringifyJson(updatedStorage);\n}\n\n/**\n * Checks if data is in legacy Model API v1 format.\n * Legacy format has { args, uiState? } at top level without the BlockStorage discriminator.\n */\nfunction isLegacyModelV1ApiFormat(data: unknown): data is { args?: unknown } {\n if (data === null || typeof data !== \"object\") return false;\n if (isBlockStorage(data)) return false;\n\n const obj = data as Record<string, unknown>;\n return \"args\" in obj;\n}\n\n// =============================================================================\n// Facade Callback Implementations\n// =============================================================================\n\n/**\n * Gets storage debug view from raw storage data.\n * Returns structured debug info about the storage state.\n *\n * @param rawStorage - Raw data from blockStorage field (may be JSON string or object)\n * @returns JSON string with storage debug view\n */\nexport function getStorageDebugView(rawStorage: unknown): StringifiedJson<StorageDebugView> {\n const { storage } = normalizeStorage(rawStorage);\n const debugView: StorageDebugView = {\n dataVersion: storage.__dataVersion,\n data: storage.__data,\n };\n return stringifyJson(debugView);\n}\n\n// =============================================================================\n// Migration Support\n// =============================================================================\n\n/**\n * Result of storage migration.\n * Returned by __pl_storage_migrate callback.\n *\n * - Error result: { error: string } - serious failure (no context, etc.)\n * - Success result: { newStorageJson: StringifiedJson<BlockStorage>, info: string } - migration succeeded\n */\nexport type MigrationResult =\n | { error: string }\n | { error?: undefined; newStorageJson: StringifiedJson<BlockStorage>; info: string };\n\n/**\n * Runs storage migration using the provided hooks.\n * This is the main entry point for the middle layer to trigger migrations.\n *\n * @param currentStorageJson - Current storage as JSON string (or undefined)\n * @param hooks - Migration dependencies (block/plugin data migration and creation functions)\n * @returns MigrationResult\n */\nexport function migrateStorage(\n currentStorageJson: string | undefined,\n hooks: MigrationHooks,\n): MigrationResult {\n // Normalize current storage\n const { storage: currentStorage } = normalizeStorage(currentStorageJson);\n\n const newPluginRegistry = hooks.getPluginRegistry();\n\n // Perform atomic migration of block + all plugins\n const migrationResult = migrateBlockStorage(currentStorage, {\n migrateBlockData: hooks.migrateBlockData,\n migratePluginData: hooks.migratePluginData,\n newPluginRegistry,\n createPluginData: hooks.createPluginData,\n });\n\n if (!migrationResult.success) {\n return {\n error: `Migration failed at '${migrationResult.failedAt}': ${migrationResult.error}`,\n };\n }\n\n // Build info message\n const oldVersion = currentStorage.__dataVersion;\n const newVersion = migrationResult.storage.__dataVersion;\n const info =\n oldVersion === newVersion\n ? `No migration needed (${oldVersion})`\n : `Migrated ${oldVersion} -> ${newVersion}`;\n\n return {\n newStorageJson: stringifyJson(migrationResult.storage),\n info,\n };\n}\n\n// =============================================================================\n// Initial Storage Creation\n// =============================================================================\n\n/**\n * Creates complete initial storage (block data + all plugin data) atomically.\n *\n * @param hooks - Dependencies for creating initial block and plugin data\n * @returns Initial storage as branded JSON string\n * @throws If initialDataFn or createPluginData throws\n */\nexport function createInitialStorage(hooks: InitialStorageHooks): StringifiedJson<BlockStorage> {\n return assembleStorage(hooks.getDefaultBlockData(), hooks);\n}\n\n/**\n * Wraps freshly created block data and freshly created plugin data into storage.\n *\n * Shared by the two ways a block's first storage comes into being — from defaults\n * and from template params. Only the block's own data differs between them:\n * plugins have no params channel, so they are always created at their defaults.\n */\nfunction assembleStorage(\n blockData: DataVersioned<unknown>,\n hooks: Omit<InitialStorageHooks, \"getDefaultBlockData\">,\n): StringifiedJson<BlockStorage> {\n const pluginRegistry = hooks.getPluginRegistry();\n\n const plugins: Record<PluginHandle, VersionedData<unknown>> = {};\n for (const handle of Object.keys(pluginRegistry) as PluginHandle[]) {\n const initial = hooks.createPluginData(handle);\n plugins[handle] = { __dataVersion: initial.version, __data: initial.data };\n }\n\n const storage: BlockStorage = {\n [BLOCK_STORAGE_KEY]: BLOCK_STORAGE_SCHEMA_VERSION,\n __dataVersion: blockData.version,\n __data: blockData.data,\n __pluginRegistry: pluginRegistry,\n __plugins: plugins,\n };\n return stringifyJson(storage);\n}\n\n/** Dependencies for creating storage from a template entry's params. */\nexport interface ParamsStorageHooks extends Omit<InitialStorageHooks, \"getDefaultBlockData\"> {\n /** The block's init factory, called with the entry's params. */\n getBlockDataFromParams: (params: unknown) => DataVersioned<unknown>;\n /**\n * The kind's runtime params check. Applied before the factory sees anything, and its\n * output is what the factory gets.\n */\n parseInitializationParams: (value: unknown) => unknown;\n}\n\n/** Result of checking params against their kind: the params to use, or why they lost. */\nexport type TemplateParamsValidationResult =\n | { error: string }\n | { error?: undefined; value: unknown };\n\n/**\n * Check params against their kind's declared shape.\n *\n * The kind owns this rather than the block because the params contract belongs to the\n * kind: many block versions implement one kind, and a per-block check would let them\n * drift from each other and from the type.\n *\n * A parser rejects by throwing and accepts by returning the params to use, so its\n * output — not the input — is what flows onward. That is what lets a kind strip keys\n * it does not declare, which is the difference between a typo being ignored and a typo\n * being reported.\n *\n * Every kind declares a parser, so every set of params that reaches here is checked;\n * there is no pass-through path.\n *\n * @param value The params to check, references already in live form\n * @param parseInitializationParams The kind's parser\n */\nexport function validateTemplateParams(\n value: unknown,\n parseInitializationParams: (value: unknown) => unknown,\n): TemplateParamsValidationResult {\n try {\n return { value: parseInitializationParams(value) };\n } catch (e) {\n // A rejection is an expected outcome for a hand-written file, so it is reported,\n // not thrown.\n return { error: `params do not match this block's kind: ${describeRejection(e)}` };\n }\n}\n\n/** One `{ path, message }` entry of a schema library's error. */\ntype IssueLike = { path?: unknown; message?: unknown };\n\n/**\n * Render whatever a parser threw as one readable line.\n *\n * An error carrying an `issues` array is unpacked rather than printed: that is the\n * shape zod (and several others) use, and its `message` is the whole issue list as\n * JSON — technically complete and unreadable in a dialog. Duck-typed on purpose, since\n * this package prescribes no schema library and takes no dependency on one; anything\n * else falls back to its own message.\n */\nfunction describeRejection(e: unknown): string {\n const issues = (e as { issues?: unknown }).issues;\n if (!Array.isArray(issues) || issues.length === 0) return messageOf(e);\n\n return issues\n .map((issue) => {\n const { path, message } = issue as IssueLike;\n const what = typeof message === \"string\" ? message : \"is invalid\";\n const where = Array.isArray(path) ? formatPath(path) : \"\";\n return where === \"\" ? what : `${where}: ${what}`;\n })\n .join(\"; \");\n}\n\n/** `[\"numbers\", 0]` → `numbers[0]` — how the params are written, not how they parse. */\nfunction formatPath(path: readonly unknown[]): string {\n return path.reduce<string>((acc, segment) => {\n if (typeof segment === \"number\") return `${acc}[${segment}]`;\n return acc === \"\" ? String(segment) : `${acc}.${String(segment)}`;\n }, \"\");\n}\n\n/**\n * Result of the `__pl_params_validate` callback.\n *\n * Carries no params back. The check is a pre-flight — its answer is \"may this entry be\n * applied\", and the params that actually reach the block are produced by\n * {@link createInitialStorageFromParams}, which parses again. One authoritative\n * producer, rather than two values that could differ.\n */\nexport type InitializationParamsValidateCallbackResult = { error: string } | { error?: undefined };\n\n/**\n * Check params that crossed into the model VM as text.\n *\n * The pre-flight entry point: a caller asks this before creating anything, once per\n * template entry, so params a kind rejects are reported while there is still no\n * project to half-build.\n *\n * The readable reference spelling is expanded here too, so what the kind checks is the shape it\n * declared. The ids it sees are the file's own — no block exists yet — which is why a kind must\n * not read meaning into a specific id.\n *\n * @param paramsJson The entry's params as JSON string\n * @param parseInitializationParams The kind's parser\n */\nexport function validateTemplateParamsJson(\n paramsJson: string,\n parseInitializationParams: (value: unknown) => unknown,\n): InitializationParamsValidateCallbackResult {\n let params: unknown;\n try {\n params = JSON.parse(paramsJson);\n } catch (e) {\n return { error: `params are not valid JSON: ${messageOf(e)}` };\n }\n\n // Expanded before the kind sees anything, or a hand-written file would be rejected by every\n // kind that declares a reference: `{ block, name }` is not a `PlRef` and no contract accepts\n // one. Expansion needs nothing but the params, so this stays a two-argument check — the ids\n // are not known yet and are not needed to answer the question this asks.\n const result = validateTemplateParams(expandTemplateRefs(params), parseInitializationParams);\n if (result.error !== undefined) return { error: result.error };\n return {};\n}\n\nfunction messageOf(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n/**\n * Result of building initial storage from params.\n * Returned by the `__pl_storage_initialFromParams` callback.\n */\nexport type ParamsStorageResult =\n | { error: string }\n | { error?: undefined; storageJson: StringifiedJson<BlockStorage> };\n\n/**\n * Creates complete initial storage for a block being created from template params.\n *\n * The inverse of {@link deriveTemplateParamsFromStorage}: that projects storage\n * into params, this builds storage from them. The params are handed to the block's\n * init factory, whose output is versioned and wrapped exactly as\n * {@link createInitialStorage} wraps the defaults — so a block created from a\n * template is indistinguishable from one created in the UI and then edited.\n *\n * **This is where a template's references become the block's own**, and it is the only place in\n * a template's life where a reference is recognized at all. Params travel from the file\n * untouched — the engine carrying them neither marks a reference nor reads one, because\n * recognizing one means knowing the reference system, and that knowledge is here.\n *\n * Two things happen, in this order. The readable spelling a person may have written\n * (`{ block, name }`) is expanded into the `PlRef` it stands for. Then every reference naming\n * an entry that has a block is repointed at it: `blockIds` maps each template-local entry id to\n * the block id it was given, and an id it does not name is left alone, which is how a reference\n * to an entry created later ends up naming nothing rather than naming the wrong block.\n *\n * Relocation happens before the kind's parser and before the factory, so both see the ids the\n * block will actually hold. It is one step of this function rather than a callback of its own\n * precisely because nothing else wants its result: every VM call re-instantiates the runtime\n * and re-evaluates the whole model bundle, so a separate call would parse the block twice per\n * entry to produce a value only the next line reads.\n *\n * Params arrive as JSON text because this runs across the model-VM boundary, where\n * only strings pass. Anything the factory rejects is returned as an error rather\n * than thrown: applying a hand-written template is expected to surface bad params,\n * and the applier reports every entry's problem in one pass.\n *\n * @param paramsJson - The entry's params as JSON string, exactly as the file held them\n * @param blockIdsJson - template-local entry id → assigned block id, as a JSON object\n * @param hooks - The block's init factory plus plugin creation\n * @returns The storage to write, or why the params could not produce any\n */\nexport function createInitialStorageFromParams(\n paramsJson: string,\n blockIdsJson: string,\n hooks: ParamsStorageHooks,\n): ParamsStorageResult {\n let params: unknown;\n try {\n params = JSON.parse(paramsJson);\n } catch (e) {\n return { error: `params are not valid JSON: ${messageOf(e)}` };\n }\n\n // Read separately from the params, and reported separately, because the two fail for\n // completely different reasons. Params are the file's and a bad one is the author's mistake;\n // this argument is the caller's, so the only way it arrives unreadable is a caller that does\n // not send it — a middle layer older than this block, which is a build to refresh rather than\n // anything to fix in the template. Folding both into one message sent the reader to the file.\n let blockIds: Map<string, string>;\n try {\n blockIds = new Map(Object.entries(JSON.parse(blockIdsJson) as Record<string, string>));\n } catch (e) {\n return {\n error:\n `this block was not told which blocks the template's references should point at ` +\n `(${messageOf(e)}). The application applying the template is older than the block; ` +\n `rebuild or update it.`,\n };\n }\n\n try {\n // Spelling first, ids second. Expansion turns `{ block, name }` into a `PlRef` naming an\n // ENTRY, which is exactly what relocation then repoints — so both spellings reach the\n // block through the same step and cannot diverge in how they behave.\n params = relocateBlockIds(expandTemplateRefs(params), blockIds);\n } catch (e) {\n return { error: `this entry's references could not be relocated: ${messageOf(e)}` };\n }\n\n // Checked here too, not only in the caller's pre-flight pass. The pre-flight is\n // about reporting every bad entry before anything is created; this is about the\n // factory never being handed a value the kind rejects, whichever path got here.\n const checked = validateTemplateParams(params, hooks.parseInitializationParams);\n if (checked.error !== undefined) return { error: checked.error };\n\n try {\n return { storageJson: assembleStorage(hooks.getBlockDataFromParams(checked.value), hooks) };\n } catch (e) {\n return { error: `init() threw on the given params: ${messageOf(e)}` };\n }\n}\n\n// =============================================================================\n// Args Derivation from Storage\n// =============================================================================\n\n/**\n * Result of args derivation from storage.\n * Returned by __pl_args_derive and __pl_prerunArgs_derive callbacks.\n */\nexport type ArgsDeriveResult = { error: string } | { error?: undefined; value: unknown };\n\n/**\n * Derives args from storage using the provided args function.\n * This extracts data from storage and passes it to the block's args() function.\n *\n * @param storageJson - Storage as JSON string\n * @param deriveArgs - The block's args derivation function\n * @returns ArgsDeriveResult with derived args or error\n */\nexport function deriveArgsFromStorage(\n storageJson: string,\n deriveArgs: (data: unknown) => unknown,\n): ArgsDeriveResult {\n // Extract data from storage\n const { data } = normalizeStorage(storageJson);\n\n // Call the args function with extracted data\n try {\n const result = deriveArgs(data);\n return { value: result };\n } catch (e) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n return { error: `args() threw: ${errorMsg}` };\n }\n}\n\n/**\n * Derives prerunArgs from storage.\n * Uses derivePrerunArgs if provided, otherwise falls back to deriveArgs.\n *\n * @param storageJson - Storage as JSON string\n * @param deriveArgs - The block's args derivation function (fallback)\n * @param derivePrerunArgs - Optional prerun args derivation function\n * @returns ArgsDeriveResult with derived prerunArgs or error\n */\nexport function derivePrerunArgsFromStorage(\n storageJson: string,\n deriveArgs: (data: unknown) => unknown,\n derivePrerunArgs?: (data: unknown) => unknown,\n): ArgsDeriveResult {\n // Extract data from storage\n const { data } = normalizeStorage(storageJson);\n\n // Try prerunArgs function first if available\n if (derivePrerunArgs) {\n try {\n const result = derivePrerunArgs(data);\n return { value: result };\n } catch (e) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n return { error: `prerunArgs() threw: ${errorMsg}` };\n }\n }\n\n // Fall back to args function\n try {\n const result = deriveArgs(data);\n return { value: result };\n } catch (e) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n return { error: `args() threw (fallback): ${errorMsg}` };\n }\n}\n\n// =============================================================================\n// Template Entry Derivation from Storage\n// =============================================================================\n\n/**\n * Derives this block's template-entry params from storage.\n *\n * The inverse of the data model's `init`: `init` turns `params` into data, this\n * turns data back into the params that would recreate it.\n *\n * The lambda returns ordinary live params — references as `PlRef`s, column identifiers as they\n * are stored — and that is exactly what gets written. Nothing is marked, normalized or\n * rewritten on the way out: a template holds what the block holds. Repointing those references\n * at another project is the business of {@link createInitialStorageFromParams}, on the way back\n * in, where the ids to point at are known.\n *\n * Every block declares the lambda, so every export produces params; a block with\n * nothing worth restoring returns `{}` rather than declining.\n *\n * @param storageJson - Storage as JSON string\n * @param deriveTemplateParams - The block's templateParams lambda\n * @returns ArgsDeriveResult holding the params exactly as the block projected them\n */\nexport function deriveTemplateParamsFromStorage<TP extends (data: unknown) => unknown>(\n storageJson: string,\n deriveTemplateParams: TP,\n): ArgsDeriveResult {\n const { data } = normalizeStorage(storageJson);\n\n try {\n return { value: deriveTemplateParams(data) };\n } catch (e) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n return { error: `templateParams() threw: ${errorMsg}` };\n }\n}\n\n// Export discriminator key and schema version for external checks\nexport { BLOCK_STORAGE_KEY, BLOCK_STORAGE_SCHEMA_VERSION };\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BLOCK_STORAGE_KEY, createBlockStorage, getStorageData, isBlockStorage, migrateBlockStorage, normalizeBlockStorage, updateStorageData } from "./block_storage.js";
|
|
2
|
-
import { stringifyJson } from "@milaboratories/pl-model-common";
|
|
2
|
+
import { expandTemplateRefs, relocateBlockIds, stringifyJson } from "@milaboratories/pl-model-common";
|
|
3
3
|
//#region src/block_storage_callbacks.ts
|
|
4
4
|
/**
|
|
5
5
|
* BlockStorage Callback Implementations - wired to facade callbacks in BlockModelV3.done().
|
|
@@ -120,7 +120,16 @@ function migrateStorage(currentStorageJson, hooks) {
|
|
|
120
120
|
* @throws If initialDataFn or createPluginData throws
|
|
121
121
|
*/
|
|
122
122
|
function createInitialStorage(hooks) {
|
|
123
|
-
|
|
123
|
+
return assembleStorage(hooks.getDefaultBlockData(), hooks);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Wraps freshly created block data and freshly created plugin data into storage.
|
|
127
|
+
*
|
|
128
|
+
* Shared by the two ways a block's first storage comes into being — from defaults
|
|
129
|
+
* and from template params. Only the block's own data differs between them:
|
|
130
|
+
* plugins have no params channel, so they are always created at their defaults.
|
|
131
|
+
*/
|
|
132
|
+
function assembleStorage(blockData, hooks) {
|
|
124
133
|
const pluginRegistry = hooks.getPluginRegistry();
|
|
125
134
|
const plugins = {};
|
|
126
135
|
for (const handle of Object.keys(pluginRegistry)) {
|
|
@@ -132,51 +141,219 @@ function createInitialStorage(hooks) {
|
|
|
132
141
|
}
|
|
133
142
|
return stringifyJson({
|
|
134
143
|
[BLOCK_STORAGE_KEY]: "v1",
|
|
135
|
-
__dataVersion:
|
|
136
|
-
__data:
|
|
144
|
+
__dataVersion: blockData.version,
|
|
145
|
+
__data: blockData.data,
|
|
137
146
|
__pluginRegistry: pluginRegistry,
|
|
138
147
|
__plugins: plugins
|
|
139
148
|
});
|
|
140
149
|
}
|
|
141
150
|
/**
|
|
151
|
+
* Check params against their kind's declared shape.
|
|
152
|
+
*
|
|
153
|
+
* The kind owns this rather than the block because the params contract belongs to the
|
|
154
|
+
* kind: many block versions implement one kind, and a per-block check would let them
|
|
155
|
+
* drift from each other and from the type.
|
|
156
|
+
*
|
|
157
|
+
* A parser rejects by throwing and accepts by returning the params to use, so its
|
|
158
|
+
* output — not the input — is what flows onward. That is what lets a kind strip keys
|
|
159
|
+
* it does not declare, which is the difference between a typo being ignored and a typo
|
|
160
|
+
* being reported.
|
|
161
|
+
*
|
|
162
|
+
* Every kind declares a parser, so every set of params that reaches here is checked;
|
|
163
|
+
* there is no pass-through path.
|
|
164
|
+
*
|
|
165
|
+
* @param value The params to check, references already in live form
|
|
166
|
+
* @param parseInitializationParams The kind's parser
|
|
167
|
+
*/
|
|
168
|
+
function validateTemplateParams(value, parseInitializationParams) {
|
|
169
|
+
try {
|
|
170
|
+
return { value: parseInitializationParams(value) };
|
|
171
|
+
} catch (e) {
|
|
172
|
+
return { error: `params do not match this block's kind: ${describeRejection(e)}` };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Render whatever a parser threw as one readable line.
|
|
177
|
+
*
|
|
178
|
+
* An error carrying an `issues` array is unpacked rather than printed: that is the
|
|
179
|
+
* shape zod (and several others) use, and its `message` is the whole issue list as
|
|
180
|
+
* JSON — technically complete and unreadable in a dialog. Duck-typed on purpose, since
|
|
181
|
+
* this package prescribes no schema library and takes no dependency on one; anything
|
|
182
|
+
* else falls back to its own message.
|
|
183
|
+
*/
|
|
184
|
+
function describeRejection(e) {
|
|
185
|
+
const issues = e.issues;
|
|
186
|
+
if (!Array.isArray(issues) || issues.length === 0) return messageOf(e);
|
|
187
|
+
return issues.map((issue) => {
|
|
188
|
+
const { path, message } = issue;
|
|
189
|
+
const what = typeof message === "string" ? message : "is invalid";
|
|
190
|
+
const where = Array.isArray(path) ? formatPath(path) : "";
|
|
191
|
+
return where === "" ? what : `${where}: ${what}`;
|
|
192
|
+
}).join("; ");
|
|
193
|
+
}
|
|
194
|
+
/** `["numbers", 0]` → `numbers[0]` — how the params are written, not how they parse. */
|
|
195
|
+
function formatPath(path) {
|
|
196
|
+
return path.reduce((acc, segment) => {
|
|
197
|
+
if (typeof segment === "number") return `${acc}[${segment}]`;
|
|
198
|
+
return acc === "" ? String(segment) : `${acc}.${String(segment)}`;
|
|
199
|
+
}, "");
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Check params that crossed into the model VM as text.
|
|
203
|
+
*
|
|
204
|
+
* The pre-flight entry point: a caller asks this before creating anything, once per
|
|
205
|
+
* template entry, so params a kind rejects are reported while there is still no
|
|
206
|
+
* project to half-build.
|
|
207
|
+
*
|
|
208
|
+
* The readable reference spelling is expanded here too, so what the kind checks is the shape it
|
|
209
|
+
* declared. The ids it sees are the file's own — no block exists yet — which is why a kind must
|
|
210
|
+
* not read meaning into a specific id.
|
|
211
|
+
*
|
|
212
|
+
* @param paramsJson The entry's params as JSON string
|
|
213
|
+
* @param parseInitializationParams The kind's parser
|
|
214
|
+
*/
|
|
215
|
+
function validateTemplateParamsJson(paramsJson, parseInitializationParams) {
|
|
216
|
+
let params;
|
|
217
|
+
try {
|
|
218
|
+
params = JSON.parse(paramsJson);
|
|
219
|
+
} catch (e) {
|
|
220
|
+
return { error: `params are not valid JSON: ${messageOf(e)}` };
|
|
221
|
+
}
|
|
222
|
+
const result = validateTemplateParams(expandTemplateRefs(params), parseInitializationParams);
|
|
223
|
+
if (result.error !== void 0) return { error: result.error };
|
|
224
|
+
return {};
|
|
225
|
+
}
|
|
226
|
+
function messageOf(e) {
|
|
227
|
+
return e instanceof Error ? e.message : String(e);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Creates complete initial storage for a block being created from template params.
|
|
231
|
+
*
|
|
232
|
+
* The inverse of {@link deriveTemplateParamsFromStorage}: that projects storage
|
|
233
|
+
* into params, this builds storage from them. The params are handed to the block's
|
|
234
|
+
* init factory, whose output is versioned and wrapped exactly as
|
|
235
|
+
* {@link createInitialStorage} wraps the defaults — so a block created from a
|
|
236
|
+
* template is indistinguishable from one created in the UI and then edited.
|
|
237
|
+
*
|
|
238
|
+
* **This is where a template's references become the block's own**, and it is the only place in
|
|
239
|
+
* a template's life where a reference is recognized at all. Params travel from the file
|
|
240
|
+
* untouched — the engine carrying them neither marks a reference nor reads one, because
|
|
241
|
+
* recognizing one means knowing the reference system, and that knowledge is here.
|
|
242
|
+
*
|
|
243
|
+
* Two things happen, in this order. The readable spelling a person may have written
|
|
244
|
+
* (`{ block, name }`) is expanded into the `PlRef` it stands for. Then every reference naming
|
|
245
|
+
* an entry that has a block is repointed at it: `blockIds` maps each template-local entry id to
|
|
246
|
+
* the block id it was given, and an id it does not name is left alone, which is how a reference
|
|
247
|
+
* to an entry created later ends up naming nothing rather than naming the wrong block.
|
|
248
|
+
*
|
|
249
|
+
* Relocation happens before the kind's parser and before the factory, so both see the ids the
|
|
250
|
+
* block will actually hold. It is one step of this function rather than a callback of its own
|
|
251
|
+
* precisely because nothing else wants its result: every VM call re-instantiates the runtime
|
|
252
|
+
* and re-evaluates the whole model bundle, so a separate call would parse the block twice per
|
|
253
|
+
* entry to produce a value only the next line reads.
|
|
254
|
+
*
|
|
255
|
+
* Params arrive as JSON text because this runs across the model-VM boundary, where
|
|
256
|
+
* only strings pass. Anything the factory rejects is returned as an error rather
|
|
257
|
+
* than thrown: applying a hand-written template is expected to surface bad params,
|
|
258
|
+
* and the applier reports every entry's problem in one pass.
|
|
259
|
+
*
|
|
260
|
+
* @param paramsJson - The entry's params as JSON string, exactly as the file held them
|
|
261
|
+
* @param blockIdsJson - template-local entry id → assigned block id, as a JSON object
|
|
262
|
+
* @param hooks - The block's init factory plus plugin creation
|
|
263
|
+
* @returns The storage to write, or why the params could not produce any
|
|
264
|
+
*/
|
|
265
|
+
function createInitialStorageFromParams(paramsJson, blockIdsJson, hooks) {
|
|
266
|
+
let params;
|
|
267
|
+
try {
|
|
268
|
+
params = JSON.parse(paramsJson);
|
|
269
|
+
} catch (e) {
|
|
270
|
+
return { error: `params are not valid JSON: ${messageOf(e)}` };
|
|
271
|
+
}
|
|
272
|
+
let blockIds;
|
|
273
|
+
try {
|
|
274
|
+
blockIds = new Map(Object.entries(JSON.parse(blockIdsJson)));
|
|
275
|
+
} catch (e) {
|
|
276
|
+
return { error: `this block was not told which blocks the template's references should point at (${messageOf(e)}). The application applying the template is older than the block; rebuild or update it.` };
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
params = relocateBlockIds(expandTemplateRefs(params), blockIds);
|
|
280
|
+
} catch (e) {
|
|
281
|
+
return { error: `this entry's references could not be relocated: ${messageOf(e)}` };
|
|
282
|
+
}
|
|
283
|
+
const checked = validateTemplateParams(params, hooks.parseInitializationParams);
|
|
284
|
+
if (checked.error !== void 0) return { error: checked.error };
|
|
285
|
+
try {
|
|
286
|
+
return { storageJson: assembleStorage(hooks.getBlockDataFromParams(checked.value), hooks) };
|
|
287
|
+
} catch (e) {
|
|
288
|
+
return { error: `init() threw on the given params: ${messageOf(e)}` };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
142
292
|
* Derives args from storage using the provided args function.
|
|
143
293
|
* This extracts data from storage and passes it to the block's args() function.
|
|
144
294
|
*
|
|
145
295
|
* @param storageJson - Storage as JSON string
|
|
146
|
-
* @param
|
|
296
|
+
* @param deriveArgs - The block's args derivation function
|
|
147
297
|
* @returns ArgsDeriveResult with derived args or error
|
|
148
298
|
*/
|
|
149
|
-
function deriveArgsFromStorage(storageJson,
|
|
299
|
+
function deriveArgsFromStorage(storageJson, deriveArgs) {
|
|
150
300
|
const { data } = normalizeStorage(storageJson);
|
|
151
301
|
try {
|
|
152
|
-
return { value:
|
|
302
|
+
return { value: deriveArgs(data) };
|
|
153
303
|
} catch (e) {
|
|
154
304
|
return { error: `args() threw: ${e instanceof Error ? e.message : String(e)}` };
|
|
155
305
|
}
|
|
156
306
|
}
|
|
157
307
|
/**
|
|
158
308
|
* Derives prerunArgs from storage.
|
|
159
|
-
* Uses
|
|
309
|
+
* Uses derivePrerunArgs if provided, otherwise falls back to deriveArgs.
|
|
160
310
|
*
|
|
161
311
|
* @param storageJson - Storage as JSON string
|
|
162
|
-
* @param
|
|
163
|
-
* @param
|
|
312
|
+
* @param deriveArgs - The block's args derivation function (fallback)
|
|
313
|
+
* @param derivePrerunArgs - Optional prerun args derivation function
|
|
164
314
|
* @returns ArgsDeriveResult with derived prerunArgs or error
|
|
165
315
|
*/
|
|
166
|
-
function derivePrerunArgsFromStorage(storageJson,
|
|
316
|
+
function derivePrerunArgsFromStorage(storageJson, deriveArgs, derivePrerunArgs) {
|
|
167
317
|
const { data } = normalizeStorage(storageJson);
|
|
168
|
-
if (
|
|
169
|
-
return { value:
|
|
318
|
+
if (derivePrerunArgs) try {
|
|
319
|
+
return { value: derivePrerunArgs(data) };
|
|
170
320
|
} catch (e) {
|
|
171
321
|
return { error: `prerunArgs() threw: ${e instanceof Error ? e.message : String(e)}` };
|
|
172
322
|
}
|
|
173
323
|
try {
|
|
174
|
-
return { value:
|
|
324
|
+
return { value: deriveArgs(data) };
|
|
175
325
|
} catch (e) {
|
|
176
326
|
return { error: `args() threw (fallback): ${e instanceof Error ? e.message : String(e)}` };
|
|
177
327
|
}
|
|
178
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Derives this block's template-entry params from storage.
|
|
331
|
+
*
|
|
332
|
+
* The inverse of the data model's `init`: `init` turns `params` into data, this
|
|
333
|
+
* turns data back into the params that would recreate it.
|
|
334
|
+
*
|
|
335
|
+
* The lambda returns ordinary live params — references as `PlRef`s, column identifiers as they
|
|
336
|
+
* are stored — and that is exactly what gets written. Nothing is marked, normalized or
|
|
337
|
+
* rewritten on the way out: a template holds what the block holds. Repointing those references
|
|
338
|
+
* at another project is the business of {@link createInitialStorageFromParams}, on the way back
|
|
339
|
+
* in, where the ids to point at are known.
|
|
340
|
+
*
|
|
341
|
+
* Every block declares the lambda, so every export produces params; a block with
|
|
342
|
+
* nothing worth restoring returns `{}` rather than declining.
|
|
343
|
+
*
|
|
344
|
+
* @param storageJson - Storage as JSON string
|
|
345
|
+
* @param deriveTemplateParams - The block's templateParams lambda
|
|
346
|
+
* @returns ArgsDeriveResult holding the params exactly as the block projected them
|
|
347
|
+
*/
|
|
348
|
+
function deriveTemplateParamsFromStorage(storageJson, deriveTemplateParams) {
|
|
349
|
+
const { data } = normalizeStorage(storageJson);
|
|
350
|
+
try {
|
|
351
|
+
return { value: deriveTemplateParams(data) };
|
|
352
|
+
} catch (e) {
|
|
353
|
+
return { error: `templateParams() threw: ${e instanceof Error ? e.message : String(e)}` };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
179
356
|
//#endregion
|
|
180
|
-
export { BLOCK_STORAGE_KEY, applyStorageUpdate, createInitialStorage, deriveArgsFromStorage, derivePrerunArgsFromStorage, getStorageDebugView, migrateStorage };
|
|
357
|
+
export { BLOCK_STORAGE_KEY, applyStorageUpdate, createInitialStorage, createInitialStorageFromParams, deriveArgsFromStorage, derivePrerunArgsFromStorage, deriveTemplateParamsFromStorage, getStorageDebugView, migrateStorage, validateTemplateParams, validateTemplateParamsJson };
|
|
181
358
|
|
|
182
359
|
//# sourceMappingURL=block_storage_callbacks.js.map
|