@platforma-sdk/model 1.51.9 → 1.52.3

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 (92) hide show
  1. package/dist/bconfig/lambdas.d.ts +26 -4
  2. package/dist/bconfig/lambdas.d.ts.map +1 -1
  3. package/dist/bconfig/v3.d.ts +4 -2
  4. package/dist/bconfig/v3.d.ts.map +1 -1
  5. package/dist/block_api_v3.d.ts +32 -0
  6. package/dist/block_api_v3.d.ts.map +1 -0
  7. package/dist/block_migrations.cjs +138 -0
  8. package/dist/block_migrations.cjs.map +1 -0
  9. package/dist/block_migrations.d.ts +79 -0
  10. package/dist/block_migrations.d.ts.map +1 -0
  11. package/dist/block_migrations.js +136 -0
  12. package/dist/block_migrations.js.map +1 -0
  13. package/dist/block_model.cjs +222 -0
  14. package/dist/block_model.cjs.map +1 -0
  15. package/dist/block_model.d.ts +132 -0
  16. package/dist/block_model.d.ts.map +1 -0
  17. package/dist/block_model.js +220 -0
  18. package/dist/block_model.js.map +1 -0
  19. package/dist/block_storage.cjs +244 -0
  20. package/dist/block_storage.cjs.map +1 -0
  21. package/dist/block_storage.d.ts +208 -0
  22. package/dist/block_storage.d.ts.map +1 -0
  23. package/dist/block_storage.js +225 -0
  24. package/dist/block_storage.js.map +1 -0
  25. package/dist/block_storage_vm.cjs +264 -0
  26. package/dist/block_storage_vm.cjs.map +1 -0
  27. package/dist/block_storage_vm.d.ts +67 -0
  28. package/dist/block_storage_vm.d.ts.map +1 -0
  29. package/dist/block_storage_vm.js +260 -0
  30. package/dist/block_storage_vm.js.map +1 -0
  31. package/dist/builder.cjs +9 -6
  32. package/dist/builder.cjs.map +1 -1
  33. package/dist/builder.d.ts +15 -30
  34. package/dist/builder.d.ts.map +1 -1
  35. package/dist/builder.js +10 -7
  36. package/dist/builder.js.map +1 -1
  37. package/dist/components/PFrameForGraphs.cjs.map +1 -1
  38. package/dist/components/PFrameForGraphs.d.ts +2 -2
  39. package/dist/components/PFrameForGraphs.d.ts.map +1 -1
  40. package/dist/components/PFrameForGraphs.js.map +1 -1
  41. package/dist/components/PlDataTable.cjs.map +1 -1
  42. package/dist/components/PlDataTable.d.ts +3 -3
  43. package/dist/components/PlDataTable.d.ts.map +1 -1
  44. package/dist/components/PlDataTable.js.map +1 -1
  45. package/dist/index.cjs +25 -0
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.d.ts +3 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +4 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/internal.cjs +38 -0
  52. package/dist/internal.cjs.map +1 -1
  53. package/dist/internal.d.ts +21 -0
  54. package/dist/internal.d.ts.map +1 -1
  55. package/dist/internal.js +36 -1
  56. package/dist/internal.js.map +1 -1
  57. package/dist/package.json.cjs +1 -1
  58. package/dist/package.json.js +1 -1
  59. package/dist/pframe_utils/columns.cjs.map +1 -1
  60. package/dist/pframe_utils/columns.d.ts +3 -3
  61. package/dist/pframe_utils/columns.d.ts.map +1 -1
  62. package/dist/pframe_utils/columns.js.map +1 -1
  63. package/dist/platforma.d.ts +18 -3
  64. package/dist/platforma.d.ts.map +1 -1
  65. package/dist/render/api.cjs +43 -16
  66. package/dist/render/api.cjs.map +1 -1
  67. package/dist/render/api.d.ts +19 -7
  68. package/dist/render/api.d.ts.map +1 -1
  69. package/dist/render/api.js +42 -17
  70. package/dist/render/api.js.map +1 -1
  71. package/dist/render/internal.cjs.map +1 -1
  72. package/dist/render/internal.d.ts +3 -1
  73. package/dist/render/internal.d.ts.map +1 -1
  74. package/dist/render/internal.js.map +1 -1
  75. package/package.json +7 -7
  76. package/src/bconfig/lambdas.ts +35 -4
  77. package/src/bconfig/v3.ts +12 -2
  78. package/src/block_api_v3.ts +49 -0
  79. package/src/block_migrations.ts +173 -0
  80. package/src/block_model.ts +440 -0
  81. package/src/block_storage.test.ts +258 -0
  82. package/src/block_storage.ts +365 -0
  83. package/src/block_storage_vm.ts +349 -0
  84. package/src/builder.ts +24 -59
  85. package/src/components/PFrameForGraphs.ts +2 -2
  86. package/src/components/PlDataTable.ts +3 -3
  87. package/src/index.ts +3 -0
  88. package/src/internal.ts +51 -0
  89. package/src/pframe_utils/columns.ts +3 -3
  90. package/src/platforma.ts +31 -5
  91. package/src/render/api.ts +52 -21
  92. package/src/render/internal.ts +3 -1
@@ -0,0 +1,349 @@
1
+ /**
2
+ * BlockStorage VM Integration - Internal module for VM-based storage operations.
3
+ *
4
+ * This module auto-registers internal callbacks that the middle layer can invoke
5
+ * to perform storage transformations. Block developers never interact with these
6
+ * directly - they only see `state`.
7
+ *
8
+ * Registered callbacks (all prefixed with `__pl_` for internal SDK use):
9
+ * - `__pl_storage_normalize`: (rawStorage) => { storage, data }
10
+ * - `__pl_storage_applyUpdate`: (currentStorageJson, payload) => updatedStorageJson
11
+ * - `__pl_storage_getInfo`: (rawStorage) => JSON string with storage info
12
+ * - `__pl_storage_migrate`: (currentStorageJson) => MigrationResult
13
+ * - `__pl_args_derive`: (storageJson) => ArgsDeriveResult
14
+ * - `__pl_prerunArgs_derive`: (storageJson) => ArgsDeriveResult
15
+ *
16
+ * Callbacks registered by DataModel.registerCallbacks():
17
+ * - `__pl_data_initial`: () => initial data
18
+ * - `__pl_data_upgrade`: (versioned) => UpgradeResult
19
+ * - `__pl_storage_initial`: () => initial BlockStorage as JSON string
20
+ *
21
+ * @module block_storage_vm
22
+ * @internal
23
+ */
24
+
25
+ import {
26
+ BLOCK_STORAGE_KEY,
27
+ BLOCK_STORAGE_SCHEMA_VERSION,
28
+ type BlockStorage,
29
+ type MutateStoragePayload,
30
+ createBlockStorage,
31
+ getStorageData,
32
+ isBlockStorage,
33
+ updateStorageData,
34
+ } from './block_storage';
35
+ import { tryGetCfgRenderCtx, tryRegisterCallback } from './internal';
36
+
37
+ /**
38
+ * Result of storage normalization
39
+ */
40
+ export interface NormalizeStorageResult {
41
+ /** The normalized BlockStorage object */
42
+ storage: BlockStorage;
43
+ /** The extracted data (what developers see) */
44
+ data: unknown;
45
+ }
46
+
47
+ /**
48
+ * Normalizes raw storage data and extracts state.
49
+ * Handles all formats:
50
+ * - New BlockStorage format (has discriminator)
51
+ * - Legacy V1/V2 format ({ args, uiState })
52
+ * - Raw V3 state (any other format)
53
+ *
54
+ * @param rawStorage - Raw data from blockStorage field (may be JSON string or object)
55
+ * @returns Object with normalized storage and extracted state
56
+ */
57
+ function normalizeStorage(rawStorage: unknown): NormalizeStorageResult {
58
+ // Handle undefined/null
59
+ if (rawStorage === undefined || rawStorage === null) {
60
+ const storage = createBlockStorage({});
61
+ return { storage, data: {} };
62
+ }
63
+
64
+ // Parse JSON string if needed
65
+ let parsed = rawStorage;
66
+ if (typeof rawStorage === 'string') {
67
+ try {
68
+ parsed = JSON.parse(rawStorage);
69
+ } catch {
70
+ // If parsing fails, treat string as the data
71
+ const storage = createBlockStorage(rawStorage);
72
+ return { storage, data: rawStorage };
73
+ }
74
+ }
75
+
76
+ // Check for BlockStorage format (has discriminator)
77
+ if (isBlockStorage(parsed)) {
78
+ return { storage: parsed, data: getStorageData(parsed) };
79
+ }
80
+
81
+ // Check for legacy V1/V2 format: { args, uiState }
82
+ if (isLegacyModelV1ApiFormat(parsed)) {
83
+ // For legacy format, the whole object IS the data
84
+ const storage = createBlockStorage(parsed);
85
+ return { storage, data: parsed };
86
+ }
87
+
88
+ // Raw V3 data - wrap it
89
+ const storage = createBlockStorage(parsed);
90
+ return { storage, data: parsed };
91
+ }
92
+
93
+ /**
94
+ * Applies a state update to existing storage.
95
+ * Used when setData is called from the frontend.
96
+ *
97
+ * @param currentStorageJson - Current storage as JSON string (must be defined)
98
+ * @param newData - New data from application
99
+ * @returns Updated storage as JSON string
100
+ */
101
+ function applyStorageUpdate(currentStorageJson: string, payload: MutateStoragePayload): string {
102
+ const { storage: currentStorage } = normalizeStorage(currentStorageJson);
103
+
104
+ // Update data while preserving other storage fields (version, plugins)
105
+ const updatedStorage = updateStorageData(currentStorage, payload);
106
+
107
+ return JSON.stringify(updatedStorage);
108
+ }
109
+
110
+ /**
111
+ * Checks if data is in legacy Model API v1 format.
112
+ * Legacy format has { args, uiState? } at top level without the BlockStorage discriminator.
113
+ */
114
+ function isLegacyModelV1ApiFormat(data: unknown): data is { args?: unknown } {
115
+ if (data === null || typeof data !== 'object') return false;
116
+ if (isBlockStorage(data)) return false;
117
+
118
+ const obj = data as Record<string, unknown>;
119
+ return 'args' in obj;
120
+ }
121
+
122
+ // =============================================================================
123
+ // Auto-register internal callbacks when module is loaded in VM
124
+ // =============================================================================
125
+
126
+ // Register normalize callback
127
+ tryRegisterCallback('__pl_storage_normalize', (rawStorage: unknown) => {
128
+ return normalizeStorage(rawStorage);
129
+ });
130
+
131
+ // Register apply update callback (requires existing storage)
132
+ tryRegisterCallback('__pl_storage_applyUpdate', (currentStorageJson: string, payload: MutateStoragePayload) => {
133
+ return applyStorageUpdate(currentStorageJson, payload);
134
+ });
135
+
136
+ /**
137
+ * Storage info result returned by __pl_storage_getInfo callback.
138
+ */
139
+ export interface StorageInfo {
140
+ /** Current data version (1-based, starts at 1) */
141
+ dataVersion: number;
142
+ }
143
+
144
+ /**
145
+ * Gets storage info from raw storage data.
146
+ * Returns structured info about the storage state.
147
+ *
148
+ * @param rawStorage - Raw data from blockStorage field (may be JSON string or object)
149
+ * @returns JSON string with storage info
150
+ */
151
+ function getStorageInfo(rawStorage: unknown): string {
152
+ const { storage } = normalizeStorage(rawStorage);
153
+ const info: StorageInfo = {
154
+ dataVersion: storage.__dataVersion,
155
+ };
156
+ return JSON.stringify(info);
157
+ }
158
+
159
+ // Register get info callback
160
+ tryRegisterCallback('__pl_storage_getInfo', (rawStorage: unknown) => {
161
+ return getStorageInfo(rawStorage);
162
+ });
163
+
164
+ // =============================================================================
165
+ // Migration Support
166
+ // =============================================================================
167
+
168
+ /**
169
+ * Result of storage migration.
170
+ * Returned by __pl_storage_migrate callback.
171
+ *
172
+ * - Error result: { error: string } - serious failure (no context, etc.)
173
+ * - Success result: { newStorageJson: string, info: string, warn?: string } - migration succeeded or reset to initial
174
+ */
175
+ export type MigrationResult =
176
+ | { error: string }
177
+ | { error?: undefined; newStorageJson: string; info: string; warn?: string };
178
+
179
+ /** Result from Migrator.upgrade() */
180
+ interface UpgradeResult {
181
+ version: number;
182
+ data: unknown;
183
+ warning?: string;
184
+ }
185
+
186
+ /**
187
+ * Runs storage migration using the DataModel's upgrade callback.
188
+ * This is the main entry point for the middle layer to trigger migrations.
189
+ *
190
+ * Uses the '__pl_data_upgrade' callback registered by DataModel.registerCallbacks() which:
191
+ * - Handles all migration logic internally
192
+ * - Returns { version, data, warning? } - warning present if reset to initial data
193
+ *
194
+ * @param currentStorageJson - Current storage as JSON string (or undefined)
195
+ * @returns MigrationResult
196
+ */
197
+ function migrateStorage(currentStorageJson: string | undefined): MigrationResult {
198
+ // Get the callback registry context
199
+ const ctx = tryGetCfgRenderCtx();
200
+ if (ctx === undefined) {
201
+ return { error: 'Not in config rendering context' };
202
+ }
203
+
204
+ // Normalize storage to get current data and version
205
+ const { storage: currentStorage, data: currentData } = normalizeStorage(currentStorageJson);
206
+ const currentVersion = currentStorage.__dataVersion;
207
+
208
+ // Helper to create storage with given data and version
209
+ const createStorageJson = (data: unknown, version: number): string => {
210
+ return JSON.stringify({
211
+ ...currentStorage,
212
+ __dataVersion: version,
213
+ __data: data,
214
+ });
215
+ };
216
+
217
+ // Get the upgrade callback (registered by DataModel.registerCallbacks())
218
+ const upgradeCallback = ctx.callbackRegistry['__pl_data_upgrade'] as ((v: { version: number; data: unknown }) => UpgradeResult) | undefined;
219
+ if (typeof upgradeCallback !== 'function') {
220
+ return { error: '__pl_data_upgrade callback not found (DataModel not registered)' };
221
+ }
222
+
223
+ // Call the migrator's upgrade function
224
+ let result: UpgradeResult;
225
+ try {
226
+ result = upgradeCallback({ version: currentVersion, data: currentData });
227
+ } catch (e) {
228
+ const errorMsg = e instanceof Error ? e.message : String(e);
229
+ return { error: `upgrade() threw: ${errorMsg}` };
230
+ }
231
+
232
+ // Build info message
233
+ const info = result.version === currentVersion
234
+ ? `No migration needed (v${currentVersion})`
235
+ : result.warning
236
+ ? `Reset to initial data (v${result.version})`
237
+ : `Migrated v${currentVersion}→v${result.version}`;
238
+
239
+ return {
240
+ newStorageJson: createStorageJson(result.data, result.version),
241
+ info,
242
+ warn: result.warning,
243
+ };
244
+ }
245
+
246
+ // Register migrate callback
247
+ tryRegisterCallback('__pl_storage_migrate', (currentStorageJson: string | undefined) => {
248
+ return migrateStorage(currentStorageJson);
249
+ });
250
+
251
+ // =============================================================================
252
+ // Args Derivation from Storage
253
+ // =============================================================================
254
+
255
+ /**
256
+ * Result of args derivation from storage.
257
+ * Returned by __pl_args_derive and __pl_prerunArgs_derive callbacks.
258
+ */
259
+ export type ArgsDeriveResult =
260
+ | { error: string }
261
+ | { error?: undefined; value: unknown };
262
+
263
+ /**
264
+ * Derives args from storage using the registered 'args' callback.
265
+ * This extracts data from storage and passes it to the block's args() function.
266
+ *
267
+ * @param storageJson - Storage as JSON string
268
+ * @returns ArgsDeriveResult with derived args or error
269
+ */
270
+ function deriveArgsFromStorage(storageJson: string): ArgsDeriveResult {
271
+ const ctx = tryGetCfgRenderCtx();
272
+ if (ctx === undefined) {
273
+ return { error: 'Not in config rendering context' };
274
+ }
275
+
276
+ // Extract data from storage
277
+ const { data } = normalizeStorage(storageJson);
278
+
279
+ // Get the args callback (registered by BlockModelV3.args())
280
+ const argsCallback = ctx.callbackRegistry['args'] as ((data: unknown) => unknown) | undefined;
281
+ if (typeof argsCallback !== 'function') {
282
+ return { error: 'args callback not found' };
283
+ }
284
+
285
+ // Call the args callback with extracted data
286
+ try {
287
+ const result = argsCallback(data);
288
+ return { value: result };
289
+ } catch (e) {
290
+ const errorMsg = e instanceof Error ? e.message : String(e);
291
+ return { error: `args() threw: ${errorMsg}` };
292
+ }
293
+ }
294
+
295
+ // Register args derivation callback
296
+ tryRegisterCallback('__pl_args_derive', (storageJson: string) => {
297
+ return deriveArgsFromStorage(storageJson);
298
+ });
299
+
300
+ /**
301
+ * Derives prerunArgs from storage using the registered 'prerunArgs' callback.
302
+ * Falls back to 'args' callback if 'prerunArgs' is not defined.
303
+ *
304
+ * @param storageJson - Storage as JSON string
305
+ * @returns ArgsDeriveResult with derived prerunArgs or error
306
+ */
307
+ function derivePrerunArgsFromStorage(storageJson: string): ArgsDeriveResult {
308
+ const ctx = tryGetCfgRenderCtx();
309
+ if (ctx === undefined) {
310
+ return { error: 'Not in config rendering context' };
311
+ }
312
+
313
+ // Extract data from storage
314
+ const { data } = normalizeStorage(storageJson);
315
+
316
+ // Try prerunArgs callback first
317
+ const prerunArgsCallback = ctx.callbackRegistry['prerunArgs'] as ((data: unknown) => unknown) | undefined;
318
+ if (typeof prerunArgsCallback === 'function') {
319
+ try {
320
+ const result = prerunArgsCallback(data);
321
+ return { value: result };
322
+ } catch (e) {
323
+ const errorMsg = e instanceof Error ? e.message : String(e);
324
+ return { error: `prerunArgs() threw: ${errorMsg}` };
325
+ }
326
+ }
327
+
328
+ // Fall back to args callback
329
+ const argsCallback = ctx.callbackRegistry['args'] as ((data: unknown) => unknown) | undefined;
330
+ if (typeof argsCallback !== 'function') {
331
+ return { error: 'args callback not found (fallback from missing prerunArgs)' };
332
+ }
333
+
334
+ try {
335
+ const result = argsCallback(data);
336
+ return { value: result };
337
+ } catch (e) {
338
+ const errorMsg = e instanceof Error ? e.message : String(e);
339
+ return { error: `args() threw (fallback): ${errorMsg}` };
340
+ }
341
+ }
342
+
343
+ // Register prerunArgs derivation callback
344
+ tryRegisterCallback('__pl_prerunArgs_derive', (storageJson: string) => {
345
+ return derivePrerunArgsFromStorage(storageJson);
346
+ });
347
+
348
+ // Export discriminator key and schema version for external checks
349
+ export { BLOCK_STORAGE_KEY, BLOCK_STORAGE_SCHEMA_VERSION };
package/src/builder.ts CHANGED
@@ -1,24 +1,24 @@
1
- import type { BlockRenderingMode, BlockSection, AnyFunction, PlRef, BlockCodeKnownFeatureFlags, BlockConfigContainer, OutputWithStatus } from '@milaboratories/pl-model-common';
1
+ import type { BlockRenderingMode, BlockSection, AnyFunction, PlRef, BlockCodeKnownFeatureFlags, BlockConfigContainer } from '@milaboratories/pl-model-common';
2
2
  import type { Checked, ConfigResult, TypedConfig } from './config';
3
3
  import { getImmediate } from './config';
4
4
  import { getPlatformaInstance, isInUI, tryRegisterCallback } from './internal';
5
5
  import type { Platforma, PlatformaApiVersion, PlatformaV1, PlatformaV2 } from './platforma';
6
- import type { InferRenderFunctionReturn, RenderFunction } from './render';
7
- import { RenderCtx } from './render';
6
+ import type { InferRenderFunctionReturn, RenderFunctionLegacy } from './render';
7
+ import { RenderCtxLegacy } from './render';
8
8
  import { PlatformaSDKVersion } from './version';
9
9
  import type {
10
10
  TypedConfigOrConfigLambda,
11
11
  ConfigRenderLambda,
12
12
  StdCtxArgsOnly,
13
13
  DeriveHref,
14
- ResolveCfgType,
15
- ExtractFunctionHandleReturn,
16
14
  ConfigRenderLambdaFlags,
15
+ InferOutputsFromConfigs,
17
16
  } from './bconfig';
18
17
  import {
19
18
  downgradeCfgOrLambda,
20
19
  isConfigLambda,
21
20
  } from './bconfig';
21
+ import type { PlatformaExtended } from './platforma';
22
22
 
23
23
  type SectionsExpectedType = readonly BlockSection[];
24
24
 
@@ -27,12 +27,6 @@ type SectionsCfgChecked<Cfg extends TypedConfig, Args, UiState> = Checked<
27
27
  ConfigResult<Cfg, StdCtxArgsOnly<Args, UiState>> extends SectionsExpectedType ? true : false
28
28
  >;
29
29
 
30
- // TODO (Unused type in code)
31
- // type SectionsRFChecked<RF extends Function> = Checked<
32
- // RF,
33
- // InferRenderFunctionReturn<RF> extends SectionsExpectedType ? true : false
34
- // >;
35
-
36
30
  type InputsValidExpectedType = boolean;
37
31
 
38
32
  type InputsValidCfgChecked<Cfg extends TypedConfig, Args, UiState> = Checked<
@@ -40,12 +34,6 @@ type InputsValidCfgChecked<Cfg extends TypedConfig, Args, UiState> = Checked<
40
34
  ConfigResult<Cfg, StdCtxArgsOnly<Args, UiState>> extends InputsValidExpectedType ? true : false
41
35
  >;
42
36
 
43
- // TODO (Unused type in code)
44
- // type InputsValidRFChecked<RF extends Function> = Checked<
45
- // RF,
46
- // InferRenderFunctionReturn<RF> extends InputsValidExpectedType ? true : false
47
- // >;
48
-
49
37
  type NoOb = Record<string, never>;
50
38
 
51
39
  /** Main entry point that each block should use in it's "config" module. Don't forget
@@ -126,7 +114,7 @@ export class BlockModel<
126
114
  * workflows outputs and interact with platforma drivers
127
115
  * @param flags additional flags that may alter lambda rendering procedure
128
116
  * */
129
- public output<const Key extends string, const RF extends RenderFunction<Args, UiState>>(
117
+ public output<const Key extends string, const RF extends RenderFunctionLegacy<Args, UiState>>(
130
118
  key: Key,
131
119
  rf: RF,
132
120
  flags: ConfigRenderLambdaFlags & { withStatus: true }
@@ -144,7 +132,7 @@ export class BlockModel<
144
132
  * workflows outputs and interact with platforma drivers
145
133
  * @param flags additional flags that may alter lambda rendering procedure
146
134
  * */
147
- public output<const Key extends string, const RF extends RenderFunction<Args, UiState>>(
135
+ public output<const Key extends string, const RF extends RenderFunctionLegacy<Args, UiState>>(
148
136
  key: Key,
149
137
  rf: RF,
150
138
  flags?: ConfigRenderLambdaFlags
@@ -161,7 +149,7 @@ export class BlockModel<
161
149
  ): BlockModel<Args, OutputsCfg, UiState, Href> {
162
150
  if (typeof cfgOrRf === 'function') {
163
151
  const handle = `output#${key}`;
164
- tryRegisterCallback(handle, () => cfgOrRf(new RenderCtx()));
152
+ tryRegisterCallback(handle, () => cfgOrRf(new RenderCtxLegacy()));
165
153
  return new BlockModel({
166
154
  ...this.config,
167
155
  outputs: {
@@ -185,7 +173,7 @@ export class BlockModel<
185
173
  }
186
174
 
187
175
  /** Shortcut for {@link output} with retentive flag set to true. */
188
- public retentiveOutput<const Key extends string, const RF extends RenderFunction<Args, UiState>>(
176
+ public retentiveOutput<const Key extends string, const RF extends RenderFunctionLegacy<Args, UiState>>(
189
177
  key: Key,
190
178
  rf: RF,
191
179
  ) {
@@ -193,7 +181,7 @@ export class BlockModel<
193
181
  }
194
182
 
195
183
  /** Shortcut for {@link output} with withStatus flag set to true. */
196
- public outputWithStatus<const Key extends string, const RF extends RenderFunction<Args, UiState>>(
184
+ public outputWithStatus<const Key extends string, const RF extends RenderFunctionLegacy<Args, UiState>>(
197
185
  key: Key,
198
186
  rf: RF,
199
187
  ) {
@@ -201,7 +189,7 @@ export class BlockModel<
201
189
  }
202
190
 
203
191
  /** Shortcut for {@link output} with retentive and withStatus flags set to true. */
204
- public retentiveOutputWithStatus<const Key extends string, const RF extends RenderFunction<Args, UiState>>(
192
+ public retentiveOutputWithStatus<const Key extends string, const RF extends RenderFunctionLegacy<Args, UiState>>(
205
193
  key: Key,
206
194
  rf: RF,
207
195
  ) {
@@ -214,14 +202,14 @@ export class BlockModel<
214
202
  cfg: Cfg & InputsValidCfgChecked<Cfg, Args, UiState>
215
203
  ): BlockModel<Args, OutputsCfg, UiState, Href>;
216
204
  /** Sets custom configuration predicate on the block args at which block can be executed */
217
- public argsValid<RF extends RenderFunction<Args, UiState, boolean>>(
205
+ public argsValid<RF extends RenderFunctionLegacy<Args, UiState, boolean>>(
218
206
  rf: RF
219
207
  ): BlockModel<Args, OutputsCfg, UiState, Href>;
220
208
  public argsValid(
221
209
  cfgOrRf: TypedConfig | AnyFunction,
222
210
  ): BlockModel<Args, OutputsCfg, UiState, `/${string}`> {
223
211
  if (typeof cfgOrRf === 'function') {
224
- tryRegisterCallback('inputsValid', () => cfgOrRf(new RenderCtx()));
212
+ tryRegisterCallback('inputsValid', () => cfgOrRf(new RenderCtxLegacy()));
225
213
  return new BlockModel<Args, OutputsCfg, UiState>({
226
214
  ...this.config,
227
215
  inputsValid: {
@@ -245,7 +233,7 @@ export class BlockModel<
245
233
  /** Sets the config to generate list of section in the left block overviews panel */
246
234
  public sections<
247
235
  const Ret extends SectionsExpectedType,
248
- const RF extends RenderFunction<Args, UiState, Ret>,
236
+ const RF extends RenderFunctionLegacy<Args, UiState, Ret>,
249
237
  >(rf: RF): BlockModel<Args, OutputsCfg, UiState, DeriveHref<ReturnType<RF>>>;
250
238
  public sections<const Cfg extends TypedConfig>(
251
239
  cfg: Cfg & SectionsCfgChecked<Cfg, Args, UiState>
@@ -261,7 +249,7 @@ export class BlockModel<
261
249
  if (Array.isArray(arrOrCfgOrRf)) {
262
250
  return this.sections(getImmediate(arrOrCfgOrRf));
263
251
  } else if (typeof arrOrCfgOrRf === 'function') {
264
- tryRegisterCallback('sections', () => arrOrCfgOrRf(new RenderCtx()));
252
+ tryRegisterCallback('sections', () => arrOrCfgOrRf(new RenderCtxLegacy()));
265
253
  return new BlockModel<Args, OutputsCfg, UiState>({
266
254
  ...this.config,
267
255
  sections: {
@@ -279,9 +267,9 @@ export class BlockModel<
279
267
 
280
268
  /** Sets a rendering function to derive block title, shown for the block in the left blocks-overview panel. */
281
269
  public title(
282
- rf: RenderFunction<Args, UiState, string>,
270
+ rf: RenderFunctionLegacy<Args, UiState, string>,
283
271
  ): BlockModel<Args, OutputsCfg, UiState, Href> {
284
- tryRegisterCallback('title', () => rf(new RenderCtx()));
272
+ tryRegisterCallback('title', () => rf(new RenderCtxLegacy()));
285
273
  return new BlockModel<Args, OutputsCfg, UiState, Href>({
286
274
  ...this.config,
287
275
  title: {
@@ -292,9 +280,9 @@ export class BlockModel<
292
280
  }
293
281
 
294
282
  public subtitle(
295
- rf: RenderFunction<Args, UiState, string>,
283
+ rf: RenderFunctionLegacy<Args, UiState, string>,
296
284
  ): BlockModel<Args, OutputsCfg, UiState, Href> {
297
- tryRegisterCallback('subtitle', () => rf(new RenderCtx()));
285
+ tryRegisterCallback('subtitle', () => rf(new RenderCtxLegacy()));
298
286
  return new BlockModel<Args, OutputsCfg, UiState, Href>({
299
287
  ...this.config,
300
288
  subtitle: {
@@ -305,9 +293,9 @@ export class BlockModel<
305
293
  }
306
294
 
307
295
  public tags(
308
- rf: RenderFunction<Args, UiState, string[]>,
296
+ rf: RenderFunctionLegacy<Args, UiState, string[]>,
309
297
  ): BlockModel<Args, OutputsCfg, UiState, Href> {
310
- tryRegisterCallback('tags', () => rf(new RenderCtx()));
298
+ tryRegisterCallback('tags', () => rf(new RenderCtxLegacy()));
311
299
  return new BlockModel<Args, OutputsCfg, UiState, Href>({
312
300
  ...this.config,
313
301
  tags: {
@@ -407,7 +395,10 @@ export class BlockModel<
407
395
  if (this.config.initialArgs === undefined) throw new Error('Initial arguments not set.');
408
396
 
409
397
  const config: BlockConfigContainer = {
398
+ v4: undefined,
410
399
  v3: {
400
+ configVersion: 3,
401
+ modelAPIVersion: 1,
411
402
  sdkVersion: PlatformaSDKVersion,
412
403
  renderingMode: this.config.renderingMode,
413
404
  initialArgs: this.config.initialArgs,
@@ -452,29 +443,3 @@ export class BlockModel<
452
443
  };
453
444
  }
454
445
  }
455
-
456
- export type InferOutputType<CfgOrFH, Args, UiState> = CfgOrFH extends TypedConfig
457
- ? ResolveCfgType<CfgOrFH, Args, UiState>
458
- : CfgOrFH extends ConfigRenderLambda
459
- ? ExtractFunctionHandleReturn<CfgOrFH>
460
- : never;
461
-
462
- type InferOutputsFromConfigs<
463
- Args,
464
- OutputsCfg extends Record<string, TypedConfigOrConfigLambda>,
465
- UiState,
466
- > = {
467
- [Key in keyof OutputsCfg]:
468
- & OutputWithStatus<InferOutputType<OutputsCfg[Key], Args, UiState>>
469
- & { __unwrap: (OutputsCfg[Key] extends { withStatus: true } ? false : true) };
470
- };
471
-
472
- export type PlatformaExtended<Pl extends Platforma = Platforma> = Pl & {
473
- blockModelInfo: BlockModelInfo;
474
- };
475
-
476
- export type BlockModelInfo = {
477
- outputs: Record<string, {
478
- withStatus: boolean;
479
- }>;
480
- };
@@ -17,7 +17,7 @@ import {
17
17
  readAnnotationJson,
18
18
  stringifyJson,
19
19
  } from '@milaboratories/pl-model-common';
20
- import type { PColumnDataUniversal, PColumnEntryUniversal, PColumnEntryWithLabel, RenderCtx } from '../render';
20
+ import type { PColumnDataUniversal, PColumnEntryUniversal, PColumnEntryWithLabel, RenderCtxBase } from '../render';
21
21
  import { getAllRelatedColumns, getRelatedColumns } from '../pframe_utils/columns';
22
22
 
23
23
  /** Create id for column copy with added keys in axes domains */
@@ -179,7 +179,7 @@ function getAdditionalColumnsForColumn<T extends Omit<PColumn<PColumnDataUnivers
179
179
  and modified label (with added domain values in case if more than one copy with different domains exist).
180
180
  */
181
181
  export function createPFrameForGraphs<A, U>(
182
- ctx: RenderCtx<A, U>,
182
+ ctx: RenderCtxBase<A, U>,
183
183
  blockColumns?: PColumn<PColumnDataUniversal>[],
184
184
  ): PFrameHandle | undefined {
185
185
  const suitableSpec = (spec: PColumnSpec) => !isHiddenFromUIColumn(spec) && !isHiddenFromGraphColumn(spec);
@@ -34,7 +34,7 @@ import type {
34
34
  AxisLabelProvider,
35
35
  ColumnProvider,
36
36
  PColumnDataUniversal,
37
- RenderCtx,
37
+ RenderCtxBase,
38
38
  TreeNodeAccessor,
39
39
  } from '../render';
40
40
  import {
@@ -631,7 +631,7 @@ export function isColumnOptional(spec: { annotations?: Annotation }): boolean {
631
631
  * @returns PlAgDataTableV2 table source
632
632
  */
633
633
  export function createPlDataTableV2<A, U>(
634
- ctx: RenderCtx<A, U>,
634
+ ctx: RenderCtxBase<A, U>,
635
635
  columns: PColumn<PColumnDataUniversal>[],
636
636
  tableState: PlDataTableStateV2 | undefined,
637
637
  ops?: CreatePlDataTableOps,
@@ -765,7 +765,7 @@ export function createPlDataTableV2<A, U>(
765
765
 
766
766
  /** Create sheet entries for PlDataTable */
767
767
  export function createPlDataTableSheet<A, U>(
768
- ctx: RenderCtx<A, U>,
768
+ ctx: RenderCtxBase<A, U>,
769
769
  axis: AxisSpec,
770
770
  values: (string | number)[],
771
771
  ): PlDataTableSheet {
package/src/index.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export * from './block_state_patch';
2
2
  export * from './block_state_util';
3
+ export * from './block_storage';
3
4
  export * from './builder';
5
+ export { BlockModelV3 } from './block_model';
6
+ export { DataModel } from './block_migrations';
4
7
  export * from './bconfig';
5
8
  export * from './components';
6
9
  export * from './config';
package/src/internal.ts CHANGED
@@ -2,6 +2,7 @@ import type { OutputWithStatus } from '@milaboratories/pl-model-common';
2
2
  import { } from './global';
3
3
  import type { Platforma, PlatformaApiVersion } from './platforma';
4
4
  import type { FutureHandle, GlobalCfgRenderCtx } from './render/internal';
5
+ import type { ConfigRenderLambda, ConfigRenderLambdaFlags } from './bconfig';
5
6
 
6
7
  /** Utility code helping to identify whether the code is running in actual UI environment */
7
8
  export function isInUI() {
@@ -41,6 +42,56 @@ export function tryRegisterCallback(key: string, callback: (...args: any[]) => a
41
42
  return true;
42
43
  }
43
44
 
45
+ /**
46
+ * Registers a callback, replacing any existing callback with the same key.
47
+ * Use this for callbacks that have a default value but can be overridden.
48
+ *
49
+ * @param key - The callback registry key
50
+ * @param callback - The callback function to register
51
+ * @returns true if registered, false if not in render context
52
+ */
53
+ export function replaceCallback(key: string, callback: (...args: any[]) => any): boolean {
54
+ const ctx = tryGetCfgRenderCtx();
55
+ if (ctx === undefined) return false;
56
+ ctx.callbackRegistry[key] = callback;
57
+ return true;
58
+ }
59
+
60
+ //
61
+ // ConfigRenderLambda helpers
62
+ //
63
+
64
+ /** Options for creating a ConfigRenderLambda descriptor */
65
+ export type CreateLambdaOptions = ConfigRenderLambdaFlags & {
66
+ /** The callback registry key */
67
+ handle: string;
68
+ };
69
+
70
+ /** Creates a ConfigRenderLambda descriptor without registering a callback. */
71
+ export function createRenderLambda<T = unknown>(opts: CreateLambdaOptions): ConfigRenderLambda<T> {
72
+ const { handle, ...flags } = opts;
73
+ return {
74
+ __renderLambda: true,
75
+ handle,
76
+ ...flags,
77
+ } as ConfigRenderLambda<T>;
78
+ }
79
+
80
+ /** Registers a callback and returns a ConfigRenderLambda descriptor. */
81
+ export function createAndRegisterRenderLambda<T = unknown>(opts: CreateLambdaOptions & {
82
+ lambda: (...args: any[]) => any;
83
+ }, replace?: boolean): ConfigRenderLambda<T> {
84
+ const { handle, lambda, ...flags } = opts;
85
+
86
+ if (replace) {
87
+ replaceCallback(handle, lambda);
88
+ } else {
89
+ tryRegisterCallback(handle, lambda);
90
+ }
91
+
92
+ return createRenderLambda<T>({ handle, ...flags });
93
+ }
94
+
44
95
  const futureResolves = new Map<string, ((value: unknown) => void)[]>();
45
96
 
46
97
  export function registerFutureAwait(handle: FutureHandle, onResolve: (value: unknown) => void) {