@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,173 @@
1
+ import { tryRegisterCallback } from './internal';
2
+ import { createBlockStorage } from './block_storage';
3
+
4
+ export type MigrationFn<From, To> = (prev: Readonly<From>) => To;
5
+
6
+ /** Versioned data wrapper for persistence */
7
+ export type Versioned<T> = {
8
+ version: number;
9
+ data: T;
10
+ };
11
+
12
+ /** Result of upgrade operation, may include warning if migration failed */
13
+ export type UpgradeResult<T> = Versioned<T> & {
14
+ warning?: string;
15
+ };
16
+
17
+ /** Internal builder for chaining migrations */
18
+ class DataModelBuilder<State> {
19
+ private readonly migrationSteps: Array<(x: unknown) => unknown>;
20
+
21
+ private constructor(steps: Array<(x: unknown) => unknown> = []) {
22
+ this.migrationSteps = steps;
23
+ }
24
+
25
+ /** Start a migration chain from an initial type */
26
+ static from<T = unknown>(): DataModelBuilder<T> {
27
+ return new DataModelBuilder<T>();
28
+ }
29
+
30
+ /** Add a migration step */
31
+ migrate<Next>(fn: MigrationFn<State, Next>): DataModelBuilder<Next> {
32
+ return new DataModelBuilder<Next>([...this.migrationSteps, fn as any]);
33
+ }
34
+
35
+ /** Finalize with initial data, creating the DataModel */
36
+ create<S>(
37
+ initialData: () => S,
38
+ ..._: [State] extends [S] ? [] : [never]
39
+ ): DataModel<S> {
40
+ return DataModel._fromBuilder<S>(this.migrationSteps, initialData);
41
+ }
42
+ }
43
+
44
+ /**
45
+ * DataModel defines the block's data structure, initial values, and migrations.
46
+ * Used by BlockModelV3 to manage data state.
47
+ *
48
+ * @example
49
+ * // Simple data model (no migrations)
50
+ * const dataModel = DataModel.create<BlockData>(() => ({
51
+ * numbers: [],
52
+ * labels: [],
53
+ * }));
54
+ *
55
+ * // Data model with migrations
56
+ * const dataModel = DataModel
57
+ * .from<V1>()
58
+ * .migrate((data) => ({ ...data, labels: [] })) // v1 → v2
59
+ * .migrate((data) => ({ ...data, description: '' })) // v2 → v3
60
+ * .create<BlockData>(() => ({ numbers: [], labels: [], description: '' }));
61
+ */
62
+ export class DataModel<State> {
63
+ private readonly steps: Array<(x: unknown) => unknown>;
64
+ private readonly _initialData: () => State;
65
+
66
+ private constructor(steps: Array<(x: unknown) => unknown>, initialData: () => State) {
67
+ this.steps = steps;
68
+ this._initialData = initialData;
69
+ }
70
+
71
+ /** Start a migration chain from an initial type */
72
+ static from<S>(): DataModelBuilder<S> {
73
+ return DataModelBuilder.from<S>();
74
+ }
75
+
76
+ /** Create a data model with just initial data (no migrations) */
77
+ static create<S>(initialData: () => S): DataModel<S> {
78
+ return new DataModel<S>([], initialData);
79
+ }
80
+
81
+ /** Create from builder (internal use) */
82
+ static _fromBuilder<S>(
83
+ steps: Array<(x: unknown) => unknown>,
84
+ initialData: () => S,
85
+ ): DataModel<S> {
86
+ return new DataModel<S>(steps, initialData);
87
+ }
88
+
89
+ /**
90
+ * Latest version number.
91
+ * Version 1 = initial state, each migration adds 1.
92
+ */
93
+ get version(): number {
94
+ return this.steps.length + 1;
95
+ }
96
+
97
+ /** Number of migration steps */
98
+ get migrationCount(): number {
99
+ return this.steps.length;
100
+ }
101
+
102
+ /** Get initial data */
103
+ initialData(): State {
104
+ return this._initialData();
105
+ }
106
+
107
+ /** Get default data wrapped with current version */
108
+ getDefaultData(): Versioned<State> {
109
+ return { version: this.version, data: this._initialData() };
110
+ }
111
+
112
+ /**
113
+ * Upgrade versioned data from any version to the latest.
114
+ * Applies only the migrations needed (skips already-applied ones).
115
+ * If a migration fails, returns default data with a warning.
116
+ */
117
+ upgrade(versioned: Versioned<unknown>): UpgradeResult<State> {
118
+ const { version: fromVersion, data } = versioned;
119
+
120
+ if (fromVersion > this.version) {
121
+ throw new Error(
122
+ `Cannot downgrade from version ${fromVersion} to ${this.version}`,
123
+ );
124
+ }
125
+
126
+ if (fromVersion === this.version) {
127
+ return { version: this.version, data: data as State };
128
+ }
129
+
130
+ // Apply migrations starting from (fromVersion - 1) index
131
+ // Version 1 -> no migrations applied yet -> start at index 0
132
+ // Version 2 -> migration[0] already applied -> start at index 1
133
+ const startIndex = fromVersion - 1;
134
+ const migrationsToApply = this.steps.slice(startIndex);
135
+
136
+ let currentData: unknown = data;
137
+ for (let i = 0; i < migrationsToApply.length; i++) {
138
+ const stepIndex = startIndex + i;
139
+ const fromVer = stepIndex + 1;
140
+ const toVer = stepIndex + 2;
141
+ try {
142
+ currentData = migrationsToApply[i](currentData);
143
+ } catch (error) {
144
+ const errorMessage = error instanceof Error ? error.message : String(error);
145
+ return {
146
+ ...this.getDefaultData(),
147
+ warning: `Migration v${fromVer}→v${toVer} failed: ${errorMessage}`,
148
+ };
149
+ }
150
+ }
151
+
152
+ return { version: this.version, data: currentData as State };
153
+ }
154
+
155
+ /**
156
+ * Register callbacks for use in the VM.
157
+ * Called by BlockModelV3.create() to set up internal callbacks.
158
+ *
159
+ * All callbacks are prefixed with `__pl_` to indicate internal SDK use:
160
+ * - `__pl_data_initial`: returns initial data for new blocks
161
+ * - `__pl_data_upgrade`: upgrades versioned data from any version to latest
162
+ * - `__pl_storage_initial`: returns initial BlockStorage as JSON string
163
+ */
164
+ registerCallbacks(): void {
165
+ tryRegisterCallback('__pl_data_initial', () => this._initialData());
166
+ tryRegisterCallback('__pl_data_upgrade', (versioned: Versioned<unknown>) => this.upgrade(versioned));
167
+ tryRegisterCallback('__pl_storage_initial', () => {
168
+ const { version, data } = this.getDefaultData();
169
+ const storage = createBlockStorage(data, version);
170
+ return JSON.stringify(storage);
171
+ });
172
+ }
173
+ }
@@ -0,0 +1,440 @@
1
+ import type {
2
+ BlockRenderingMode,
3
+ BlockSection,
4
+ OutputWithStatus,
5
+ PlRef,
6
+ BlockCodeKnownFeatureFlags,
7
+ BlockConfigContainer,
8
+ } from '@milaboratories/pl-model-common';
9
+ import { getPlatformaInstance, isInUI, createAndRegisterRenderLambda, createRenderLambda } from './internal';
10
+ import type { DataModel } from './block_migrations';
11
+ import type { PlatformaV3 } from './platforma';
12
+ import type { InferRenderFunctionReturn, RenderFunction } from './render';
13
+ import { RenderCtx } from './render';
14
+ import { PlatformaSDKVersion } from './version';
15
+ // Import storage VM integration (side-effect: registers internal callbacks)
16
+ import './block_storage_vm';
17
+ import type {
18
+ ConfigRenderLambda,
19
+ DeriveHref,
20
+ ConfigRenderLambdaFlags,
21
+ InferOutputsFromLambdas,
22
+ } from './bconfig';
23
+ import {
24
+ downgradeCfgOrLambda,
25
+ isConfigLambda,
26
+ } from './bconfig';
27
+ import type { PlatformaExtended } from './platforma';
28
+
29
+ type SectionsExpectedType = readonly BlockSection[];
30
+
31
+ type NoOb = Record<string, never>;
32
+
33
+ interface BlockModelV3Config<
34
+ Args,
35
+ OutputsCfg extends Record<string, ConfigRenderLambda>,
36
+ Data,
37
+ > {
38
+ renderingMode: BlockRenderingMode;
39
+ dataModel: DataModel<Data>;
40
+ outputs: OutputsCfg;
41
+ sections: ConfigRenderLambda;
42
+ title: ConfigRenderLambda | undefined;
43
+ subtitle: ConfigRenderLambda | undefined;
44
+ tags: ConfigRenderLambda | undefined;
45
+ enrichmentTargets: ConfigRenderLambda | undefined;
46
+ featureFlags: BlockCodeKnownFeatureFlags;
47
+ args: ConfigRenderLambda<Args> | undefined;
48
+ prerunArgs: ConfigRenderLambda<unknown> | undefined;
49
+ }
50
+
51
+ /** Options for creating a BlockModelV3 */
52
+ export interface BlockModelV3Options<Data extends Record<string, unknown>> {
53
+ /** The data model that defines initial data and migrations */
54
+ dataModel: DataModel<Data>;
55
+ /** Rendering mode for the block (default: 'Heavy') */
56
+ renderingMode?: BlockRenderingMode;
57
+ }
58
+
59
+ /** Main entry point that each block should use in it's "config" module. Don't forget
60
+ * to call {@link done()} at the end of configuration. Value returned by this builder must be
61
+ * exported as constant with name "platforma" from the "config" module.
62
+ * API version is 3 (for UI) and 2 (for model) */
63
+ export class BlockModelV3<
64
+ Args,
65
+ OutputsCfg extends Record<string, ConfigRenderLambda>,
66
+ Data extends Record<string, unknown> = Record<string, unknown>,
67
+ Href extends `/${string}` = '/',
68
+ > {
69
+ private constructor(
70
+ private readonly config: BlockModelV3Config<Args, OutputsCfg, Data>,
71
+ ) {}
72
+
73
+ public static readonly INITIAL_BLOCK_FEATURE_FLAGS: BlockCodeKnownFeatureFlags = {
74
+ supportsLazyState: true,
75
+ requiresUIAPIVersion: 3,
76
+ requiresModelAPIVersion: 2,
77
+ };
78
+
79
+ /**
80
+ * Creates a new BlockModelV3 builder with the specified data model and options.
81
+ *
82
+ * @example
83
+ * const dataModel = DataModel.create<BlockData>(() => ({ numbers: [], labels: [] }));
84
+ * BlockModelV3.create({ dataModel })
85
+ * .args((data) => ({ numbers: data.numbers }))
86
+ * .sections(() => [{ type: 'link', href: '/', label: 'Main' }])
87
+ * .done();
88
+ *
89
+ * @param options Configuration options including required data model
90
+ */
91
+ public static create<Data extends Record<string, unknown>>(
92
+ options: BlockModelV3Options<Data>,
93
+ ): BlockModelV3<NoOb, {}, Data> {
94
+ const { dataModel, renderingMode = 'Heavy' } = options;
95
+
96
+ // Register data model callbacks for VM use (initialData and upgrade)
97
+ dataModel.registerCallbacks();
98
+
99
+ return new BlockModelV3<NoOb, {}, Data>({
100
+ renderingMode,
101
+ dataModel,
102
+ outputs: {},
103
+ // Register default sections callback (returns empty array)
104
+ sections: createAndRegisterRenderLambda({ handle: 'sections', lambda: () => [] }, true),
105
+ title: undefined,
106
+ subtitle: undefined,
107
+ tags: undefined,
108
+ enrichmentTargets: undefined,
109
+ featureFlags: { ...BlockModelV3.INITIAL_BLOCK_FEATURE_FLAGS },
110
+ args: undefined,
111
+ prerunArgs: undefined,
112
+ });
113
+ }
114
+
115
+ /**
116
+ * Add output cell wrapped with additional status information to the configuration
117
+ *
118
+ * @param key output cell name, that can be later used to retrieve the rendered value
119
+ * @param rf callback calculating output value using context, that allows to access
120
+ * workflows outputs and interact with platforma drivers
121
+ * @param flags additional flags that may alter lambda rendering procedure
122
+ * */
123
+ public output<const Key extends string, const RF extends RenderFunction<Args, Data>>(
124
+ key: Key,
125
+ rf: RF,
126
+ flags: ConfigRenderLambdaFlags & { withStatus: true }
127
+ ): BlockModelV3<
128
+ Args,
129
+ OutputsCfg & { [K in Key]: ConfigRenderLambda<InferRenderFunctionReturn<RF>> & { withStatus: true } },
130
+ Data,
131
+ Href
132
+ >;
133
+ /**
134
+ * Add output cell to the configuration
135
+ *
136
+ * @param key output cell name, that can be later used to retrieve the rendered value
137
+ * @param rf callback calculating output value using context, that allows to access
138
+ * workflows outputs and interact with platforma drivers
139
+ * @param flags additional flags that may alter lambda rendering procedure
140
+ * */
141
+ public output<const Key extends string, const RF extends RenderFunction<Args, Data>>(
142
+ key: Key,
143
+ rf: RF,
144
+ flags?: ConfigRenderLambdaFlags
145
+ ): BlockModelV3<
146
+ Args,
147
+ OutputsCfg & { [K in Key]: ConfigRenderLambda<InferRenderFunctionReturn<RF>> },
148
+ Data,
149
+ Href
150
+ >;
151
+ public output(
152
+ key: string,
153
+ cfgOrRf: RenderFunction<Args, Data>,
154
+ flags: ConfigRenderLambdaFlags = {},
155
+ ): BlockModelV3<Args, OutputsCfg, Data, Href> {
156
+ return new BlockModelV3({
157
+ ...this.config,
158
+ outputs: {
159
+ ...this.config.outputs,
160
+ [key]: createAndRegisterRenderLambda({ handle: `output#${key}`, lambda: () => cfgOrRf(new RenderCtx()), ...flags }),
161
+ },
162
+ });
163
+ }
164
+
165
+ /** Shortcut for {@link output} with retentive flag set to true. */
166
+ public retentiveOutput<const Key extends string, const RF extends RenderFunction<Args, Data>>(
167
+ key: Key,
168
+ rf: RF,
169
+ ): BlockModelV3<
170
+ Args,
171
+ OutputsCfg & { [K in Key]: ConfigRenderLambda<InferRenderFunctionReturn<RF>> },
172
+ Data,
173
+ Href
174
+ > {
175
+ return this.output(key, rf, { retentive: true });
176
+ }
177
+
178
+ /** Shortcut for {@link output} with withStatus flag set to true. */
179
+ public outputWithStatus<const Key extends string, const RF extends RenderFunction<Args, Data>>(
180
+ key: Key,
181
+ rf: RF,
182
+ ) {
183
+ return this.output(key, rf, { withStatus: true });
184
+ }
185
+
186
+ /**
187
+ * Sets a function to derive block args from data.
188
+ * This is called during setData to compute the args that will be used for block execution.
189
+ *
190
+ * @example
191
+ * .args<BlockArgs>((data) => ({ numbers: data.numbers }))
192
+ *
193
+ * @example
194
+ * .args<BlockArgs>((data) => {
195
+ * if (data.numbers.length === 0) throw new Error('Numbers required'); // block not ready
196
+ * return { numbers: data.numbers };
197
+ * })
198
+ */
199
+ public args<Args>(lambda: (data: Data) => Args): BlockModelV3<Args, OutputsCfg, Data, Href> {
200
+ return new BlockModelV3<Args, OutputsCfg, Data, Href>({
201
+ ...this.config,
202
+ args: createAndRegisterRenderLambda<Args>({ handle: 'args', lambda }),
203
+ });
204
+ }
205
+
206
+ /**
207
+ * Sets a function to derive pre-run args from data (optional).
208
+ * This is called during setData to compute the args that will be used for staging/pre-run phase.
209
+ *
210
+ * If not defined, defaults to using the args() function result.
211
+ * If defined, uses its return value for the staging / prerun phase.
212
+ *
213
+ * The staging / prerun phase runs only if currentPrerunArgs differs from the executed
214
+ * version of prerunArgs (same comparison logic as currentArgs vs prodArgs).
215
+ *
216
+ * @example
217
+ * .prerunArgs((data) => ({ numbers: data.numbers }))
218
+ *
219
+ * @example
220
+ * .prerunArgs((data) => {
221
+ * // Return undefined to skip staging for this block
222
+ * if (!data.isReady) return undefined;
223
+ * return { numbers: data.numbers };
224
+ * })
225
+ */
226
+ public prerunArgs(fn: (data: Data) => unknown): BlockModelV3<Args, OutputsCfg, Data, Href> {
227
+ return new BlockModelV3<Args, OutputsCfg, Data, Href>({
228
+ ...this.config,
229
+ prerunArgs: createAndRegisterRenderLambda({ handle: 'prerunArgs', lambda: fn }),
230
+ });
231
+ }
232
+
233
+ /** Sets the lambda to generate list of sections in the left block overviews panel. */
234
+ public sections<
235
+ const Ret extends SectionsExpectedType,
236
+ const RF extends RenderFunction<Args, Data, Ret>,
237
+ >(rf: RF): BlockModelV3<Args, OutputsCfg, Data, DeriveHref<ReturnType<RF>>> {
238
+ return new BlockModelV3<Args, OutputsCfg, Data, DeriveHref<ReturnType<RF>>>({
239
+ ...this.config,
240
+ // Replace the default sections callback with the user-provided one
241
+ sections: createAndRegisterRenderLambda({ handle: 'sections', lambda: () => rf(new RenderCtx()) }, true),
242
+ });
243
+ }
244
+
245
+ /** Sets a rendering function to derive block title, shown for the block in the left blocks-overview panel. */
246
+ public title(
247
+ rf: RenderFunction<Args, Data, string>,
248
+ ): BlockModelV3<Args, OutputsCfg, Data, Href> {
249
+ return new BlockModelV3<Args, OutputsCfg, Data, Href>({
250
+ ...this.config,
251
+ title: createAndRegisterRenderLambda({ handle: 'title', lambda: () => rf(new RenderCtx()) }),
252
+ });
253
+ }
254
+
255
+ public subtitle(
256
+ rf: RenderFunction<Args, Data, string>,
257
+ ): BlockModelV3<Args, OutputsCfg, Data, Href> {
258
+ return new BlockModelV3<Args, OutputsCfg, Data, Href>({
259
+ ...this.config,
260
+ subtitle: createAndRegisterRenderLambda({ handle: 'subtitle', lambda: () => rf(new RenderCtx()) }),
261
+ });
262
+ }
263
+
264
+ public tags(
265
+ rf: RenderFunction<Args, Data, string[]>,
266
+ ): BlockModelV3<Args, OutputsCfg, Data, Href> {
267
+ return new BlockModelV3<Args, OutputsCfg, Data, Href>({
268
+ ...this.config,
269
+ tags: createAndRegisterRenderLambda({ handle: 'tags', lambda: () => rf(new RenderCtx()) }),
270
+ });
271
+ }
272
+
273
+ /** Sets or overrides feature flags for the block. */
274
+ public withFeatureFlags(flags: Partial<BlockCodeKnownFeatureFlags>): BlockModelV3<Args, OutputsCfg, Data, Href> {
275
+ return new BlockModelV3<Args, OutputsCfg, Data, Href>({
276
+ ...this.config,
277
+ featureFlags: { ...this.config.featureFlags, ...flags },
278
+ });
279
+ }
280
+
281
+ /**
282
+ * Defines how to derive list of upstream references this block is meant to enrich with its exports from block args.
283
+ * Influences dependency graph construction.
284
+ */
285
+ public enriches(
286
+ lambda: (args: Args) => PlRef[],
287
+ ): BlockModelV3<Args, OutputsCfg, Data, Href> {
288
+ return new BlockModelV3<Args, OutputsCfg, Data, Href>({
289
+ ...this.config,
290
+ enrichmentTargets: createAndRegisterRenderLambda({ handle: 'enrichmentTargets', lambda: lambda }),
291
+ });
292
+ }
293
+
294
+ /** Renders all provided block settings into a pre-configured platforma API
295
+ * instance, that can be used in frontend to interact with block data, and
296
+ * other features provided by the platforma to the block. */
297
+ public done(): PlatformaExtended<PlatformaV3<
298
+ Args,
299
+ InferOutputsFromLambdas<OutputsCfg>,
300
+ Data,
301
+ Href
302
+ >> {
303
+ return this.withFeatureFlags({
304
+ ...this.config.featureFlags,
305
+ })._done();
306
+ }
307
+
308
+ public _done(): PlatformaExtended<PlatformaV3<
309
+ Args,
310
+ InferOutputsFromLambdas<OutputsCfg>,
311
+ Data,
312
+ Href
313
+ >> {
314
+ if (this.config.args === undefined) throw new Error('Args rendering function not set.');
315
+
316
+ const apiVersion = 3;
317
+
318
+ const migrationCount = this.config.dataModel.migrationCount;
319
+
320
+ const blockConfig: BlockConfigContainer = {
321
+ v4: {
322
+ configVersion: 4,
323
+ modelAPIVersion: 2,
324
+ sdkVersion: PlatformaSDKVersion,
325
+ renderingMode: this.config.renderingMode,
326
+ args: this.config.args,
327
+ prerunArgs: this.config.prerunArgs,
328
+ // Reference to __pl_data_initial callback registered by DataModel
329
+ initialData: createRenderLambda({ handle: '__pl_data_initial' }),
330
+ sections: this.config.sections,
331
+ title: this.config.title,
332
+ outputs: this.config.outputs,
333
+ enrichmentTargets: this.config.enrichmentTargets,
334
+ featureFlags: this.config.featureFlags,
335
+ // Generate migration descriptors (indices for metadata)
336
+ migrations: migrationCount > 0
337
+ ? Array.from({ length: migrationCount }, (_, i) => ({ index: i }))
338
+ : undefined,
339
+ },
340
+
341
+ // fields below are added to allow previous desktop versions read generated configs
342
+ sdkVersion: PlatformaSDKVersion,
343
+ renderingMode: this.config.renderingMode,
344
+ sections: this.config.sections,
345
+ outputs: Object.fromEntries(
346
+ Object.entries(this.config.outputs).map(([key, value]) => [key, downgradeCfgOrLambda(value)]),
347
+ ),
348
+ };
349
+
350
+ globalThis.platformaApiVersion = apiVersion;
351
+
352
+ if (!isInUI())
353
+ // we are in the configuration rendering routine, not in actual UI
354
+ return { config: blockConfig } as any;
355
+ // normal operation inside the UI
356
+ else return {
357
+ ...getPlatformaInstance({ sdkVersion: PlatformaSDKVersion, apiVersion }),
358
+ blockModelInfo: {
359
+ outputs: Object.fromEntries(
360
+ Object.entries(this.config.outputs)
361
+ .map(([key, value]) => [key, {
362
+ withStatus: Boolean(isConfigLambda(value) && value.withStatus),
363
+ }]),
364
+ ),
365
+ },
366
+ } as any;
367
+ }
368
+ }
369
+
370
+ // Type tests for BlockModelV3
371
+
372
+ export type Expect<T extends true> = T;
373
+
374
+ export type Equal<X, Y> =
375
+ (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;
376
+
377
+ export type Merge<A, B> = {
378
+ [K in keyof A | keyof B]: K extends keyof B ? B[K] : K extends keyof A ? A[K] : never;
379
+ };
380
+
381
+ // Helper types for testing
382
+ type _TestArgs = { inputFile: string; threshold: number };
383
+ type _TestData = { selectedTab: string };
384
+ type _TestOutputs = { result: ConfigRenderLambda<string>; count: ConfigRenderLambda<number> };
385
+
386
+ // Test: Merge type works correctly
387
+ type _MergeTest1 = Expect<Equal<Merge<{ a: 1 }, { b: 2 }>, { a: 1; b: 2 }>>;
388
+ type _MergeTest2 = Expect<Equal<Merge<{ a: 1 }, { a: 2 }>, { a: 2 }>>;
389
+ type _MergeTest3 = Expect<Equal<Merge<{ a: 1; b: 1 }, { b: 2; c: 3 }>, { a: 1; b: 2; c: 3 }>>;
390
+
391
+ // Test: create() returns a BlockModelV3 instance
392
+ // Note: Due to function overloads, ReturnType uses the last overload signature.
393
+ // We verify the structure is correct using a simpler assignability test.
394
+ type _CreateResult = ReturnType<typeof BlockModelV3.create>;
395
+ type _CreateIsBlockModelV3 = _CreateResult extends BlockModelV3<infer _A, infer _O, infer _S> ? true : false;
396
+ type _CreateTest = Expect<_CreateIsBlockModelV3>;
397
+
398
+ // Test: BlockModelV3Config interface structure
399
+ type _ConfigTest = Expect<Equal<
400
+ BlockModelV3Config<_TestArgs, _TestOutputs, _TestData>,
401
+ {
402
+ renderingMode: BlockRenderingMode;
403
+ args: ConfigRenderLambda<_TestArgs> | undefined;
404
+ prerunArgs: ConfigRenderLambda<unknown> | undefined;
405
+ dataModel: DataModel<_TestData>;
406
+ outputs: _TestOutputs;
407
+ sections: ConfigRenderLambda;
408
+ title: ConfigRenderLambda | undefined;
409
+ subtitle: ConfigRenderLambda | undefined;
410
+ tags: ConfigRenderLambda | undefined;
411
+ enrichmentTargets: ConfigRenderLambda | undefined;
412
+ featureFlags: BlockCodeKnownFeatureFlags;
413
+ }
414
+ >>;
415
+
416
+ // Test: Default Href is '/'
417
+ type _HrefDefaultTest = BlockModelV3<_TestArgs, {}, _TestData> extends BlockModelV3<_TestArgs, {}, _TestData, '/'> ? true : false;
418
+ type _VerifyHrefDefault = Expect<_HrefDefaultTest>;
419
+
420
+ // Test: Custom Href can be specified
421
+ type _CustomHref = '/settings' | '/main';
422
+ type _HrefCustomBuilder = BlockModelV3<_TestArgs, {}, _TestData, _CustomHref>;
423
+ type _HrefCustomTest = _HrefCustomBuilder extends BlockModelV3<_TestArgs, {}, _TestData, _CustomHref> ? true : false;
424
+ type _VerifyHrefCustom = Expect<_HrefCustomTest>;
425
+
426
+ // Test: Output type accumulation with & intersection
427
+ type _OutputsAccumulation = { a: ConfigRenderLambda<string> } & { b: ConfigRenderLambda<number> };
428
+ type _VerifyOutputsHaveKeys = Expect<Equal<keyof _OutputsAccumulation, 'a' | 'b'>>;
429
+
430
+ // Test: Builder with all type parameters specified compiles
431
+ type _FullBuilder = BlockModelV3<_TestArgs, _TestOutputs, _TestData, '/main'>;
432
+ type _FullBuilderTest = _FullBuilder extends BlockModelV3<_TestArgs, _TestOutputs, _TestData, '/main'> ? true : false;
433
+ type _VerifyFullBuilder = Expect<_FullBuilderTest>;
434
+
435
+ // Test: InferOutputsFromLambdas maps outputs correctly
436
+ type _InferOutputsTest = InferOutputsFromLambdas<{ myOutput: ConfigRenderLambda<number> }>;
437
+ type _VerifyInferOutputs = Expect<Equal<
438
+ _InferOutputsTest,
439
+ { myOutput: OutputWithStatus<number> & { __unwrap: true } }
440
+ >>;