@platforma-sdk/model 1.54.13 → 1.56.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.
Files changed (120) hide show
  1. package/dist/bconfig/normalization.cjs +8 -1
  2. package/dist/bconfig/normalization.cjs.map +1 -1
  3. package/dist/bconfig/normalization.d.ts.map +1 -1
  4. package/dist/bconfig/normalization.js +8 -1
  5. package/dist/bconfig/normalization.js.map +1 -1
  6. package/dist/block_api_v3.d.ts +2 -2
  7. package/dist/block_api_v3.d.ts.map +1 -1
  8. package/dist/block_migrations.cjs +246 -214
  9. package/dist/block_migrations.cjs.map +1 -1
  10. package/dist/block_migrations.d.ts +180 -158
  11. package/dist/block_migrations.d.ts.map +1 -1
  12. package/dist/block_migrations.js +247 -214
  13. package/dist/block_migrations.js.map +1 -1
  14. package/dist/block_model.cjs +85 -35
  15. package/dist/block_model.cjs.map +1 -1
  16. package/dist/block_model.d.ts +66 -38
  17. package/dist/block_model.d.ts.map +1 -1
  18. package/dist/block_model.js +86 -36
  19. package/dist/block_model.js.map +1 -1
  20. package/dist/{builder.cjs → block_model_legacy.cjs} +2 -2
  21. package/dist/block_model_legacy.cjs.map +1 -0
  22. package/dist/{builder.d.ts → block_model_legacy.d.ts} +1 -1
  23. package/dist/block_model_legacy.d.ts.map +1 -0
  24. package/dist/{builder.js → block_model_legacy.js} +2 -2
  25. package/dist/block_model_legacy.js.map +1 -0
  26. package/dist/block_state_patch.d.ts +11 -1
  27. package/dist/block_state_patch.d.ts.map +1 -1
  28. package/dist/block_storage.cjs +126 -109
  29. package/dist/block_storage.cjs.map +1 -1
  30. package/dist/block_storage.d.ts +109 -112
  31. package/dist/block_storage.d.ts.map +1 -1
  32. package/dist/block_storage.js +126 -101
  33. package/dist/block_storage.js.map +1 -1
  34. package/dist/block_storage_callbacks.cjs +227 -0
  35. package/dist/block_storage_callbacks.cjs.map +1 -0
  36. package/dist/block_storage_callbacks.d.ts +113 -0
  37. package/dist/block_storage_callbacks.d.ts.map +1 -0
  38. package/dist/block_storage_callbacks.js +218 -0
  39. package/dist/block_storage_callbacks.js.map +1 -0
  40. package/dist/block_storage_facade.cjs +104 -0
  41. package/dist/block_storage_facade.cjs.map +1 -0
  42. package/dist/block_storage_facade.d.ts +168 -0
  43. package/dist/block_storage_facade.d.ts.map +1 -0
  44. package/dist/block_storage_facade.js +99 -0
  45. package/dist/block_storage_facade.js.map +1 -0
  46. package/dist/index.cjs +13 -14
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.ts +8 -3
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +6 -4
  51. package/dist/index.js.map +1 -1
  52. package/dist/package.json.cjs +1 -1
  53. package/dist/package.json.js +1 -1
  54. package/dist/platforma.d.ts +11 -4
  55. package/dist/platforma.d.ts.map +1 -1
  56. package/dist/plugin_model.cjs +171 -0
  57. package/dist/plugin_model.cjs.map +1 -0
  58. package/dist/plugin_model.d.ts +162 -0
  59. package/dist/plugin_model.d.ts.map +1 -0
  60. package/dist/plugin_model.js +169 -0
  61. package/dist/plugin_model.js.map +1 -0
  62. package/dist/render/api.cjs +20 -21
  63. package/dist/render/api.cjs.map +1 -1
  64. package/dist/render/api.d.ts +8 -8
  65. package/dist/render/api.d.ts.map +1 -1
  66. package/dist/render/api.js +20 -21
  67. package/dist/render/api.js.map +1 -1
  68. package/dist/render/internal.cjs.map +1 -1
  69. package/dist/render/internal.d.ts +2 -1
  70. package/dist/render/internal.d.ts.map +1 -1
  71. package/dist/render/internal.js.map +1 -1
  72. package/dist/version.cjs +4 -0
  73. package/dist/version.cjs.map +1 -1
  74. package/dist/version.d.ts +4 -0
  75. package/dist/version.d.ts.map +1 -1
  76. package/dist/version.js +4 -1
  77. package/dist/version.js.map +1 -1
  78. package/package.json +5 -5
  79. package/src/bconfig/normalization.ts +8 -1
  80. package/src/block_api_v3.ts +2 -2
  81. package/src/block_migrations.test.ts +141 -171
  82. package/src/block_migrations.ts +300 -285
  83. package/src/block_model.ts +205 -95
  84. package/src/{builder.ts → block_model_legacy.ts} +1 -1
  85. package/src/block_state_patch.ts +13 -1
  86. package/src/block_storage.test.ts +283 -95
  87. package/src/block_storage.ts +199 -188
  88. package/src/block_storage_callbacks.ts +326 -0
  89. package/src/block_storage_facade.ts +199 -0
  90. package/src/index.ts +7 -3
  91. package/src/platforma.ts +26 -7
  92. package/src/plugin_model.test.ts +168 -0
  93. package/src/plugin_model.ts +242 -0
  94. package/src/render/api.ts +26 -24
  95. package/src/render/internal.ts +3 -1
  96. package/src/typing.test.ts +1 -1
  97. package/src/version.ts +8 -0
  98. package/dist/block_storage_vm.cjs +0 -262
  99. package/dist/block_storage_vm.cjs.map +0 -1
  100. package/dist/block_storage_vm.d.ts +0 -59
  101. package/dist/block_storage_vm.d.ts.map +0 -1
  102. package/dist/block_storage_vm.js +0 -258
  103. package/dist/block_storage_vm.js.map +0 -1
  104. package/dist/branding.d.ts +0 -7
  105. package/dist/branding.d.ts.map +0 -1
  106. package/dist/builder.cjs.map +0 -1
  107. package/dist/builder.d.ts.map +0 -1
  108. package/dist/builder.js.map +0 -1
  109. package/dist/sdk_info.cjs +0 -10
  110. package/dist/sdk_info.cjs.map +0 -1
  111. package/dist/sdk_info.d.ts +0 -5
  112. package/dist/sdk_info.d.ts.map +0 -1
  113. package/dist/sdk_info.js +0 -8
  114. package/dist/sdk_info.js.map +0 -1
  115. package/dist/unionize.d.ts +0 -12
  116. package/dist/unionize.d.ts.map +0 -1
  117. package/src/block_storage_vm.ts +0 -346
  118. package/src/branding.ts +0 -4
  119. package/src/sdk_info.ts +0 -9
  120. package/src/unionize.ts +0 -12
@@ -6,19 +6,23 @@ import type {
6
6
  BlockCodeKnownFeatureFlags,
7
7
  BlockConfigContainer,
8
8
  } from "@milaboratories/pl-model-common";
9
- import {
10
- getPlatformaInstance,
11
- isInUI,
12
- createAndRegisterRenderLambda,
13
- createRenderLambda,
14
- } from "./internal";
9
+ import { getPlatformaInstance, isInUI, createAndRegisterRenderLambda } from "./internal";
15
10
  import type { DataModel } from "./block_migrations";
16
11
  import type { PlatformaV3 } from "./platforma";
17
12
  import type { InferRenderFunctionReturn, RenderFunction } from "./render";
18
13
  import { RenderCtx } from "./render";
14
+ import type { PluginModel } from "./plugin_model";
15
+ import type { RenderCtxBase } from "./render";
19
16
  import { PlatformaSDKVersion } from "./version";
20
- // Import storage VM integration (side-effect: registers internal callbacks)
21
- import "./block_storage_vm";
17
+ import {
18
+ applyStorageUpdate,
19
+ getStorageDebugView,
20
+ migrateStorage,
21
+ createInitialStorage,
22
+ deriveArgsFromStorage,
23
+ derivePrerunArgsFromStorage,
24
+ } from "./block_storage_callbacks";
25
+ import { type PluginName } from "./block_storage";
22
26
  import type {
23
27
  ConfigRenderLambda,
24
28
  DeriveHref,
@@ -27,12 +31,44 @@ import type {
27
31
  } from "./bconfig";
28
32
  import { downgradeCfgOrLambda, isConfigLambda } from "./bconfig";
29
33
  import type { PlatformaExtended } from "./platforma";
34
+ import {
35
+ BLOCK_STORAGE_FACADE_VERSION,
36
+ BlockStorageFacadeCallbacks,
37
+ BlockStorageFacadeHandles,
38
+ registerFacadeCallbacks,
39
+ } from "./block_storage_facade";
30
40
 
31
41
  type SectionsExpectedType = readonly BlockSection[];
32
42
 
33
43
  type NoOb = Record<string, never>;
34
44
 
35
- interface BlockModelV3Config<Args, OutputsCfg extends Record<string, ConfigRenderLambda>, Data> {
45
+ /**
46
+ * Per-property lambdas for deriving plugin params from block render context.
47
+ * Each property is a function that receives the block's RenderCtxBase and returns the param value.
48
+ */
49
+ export type ParamsInput<Params, BArgs = unknown, BData = unknown> = {
50
+ [K in keyof Params]: (ctx: RenderCtxBase<BArgs, BData>) => Params[K];
51
+ };
52
+
53
+ /**
54
+ * Type-erased version of ParamsInput for internal storage.
55
+ */
56
+ type ParamsInputErased = Record<string, (ctx: RenderCtxBase) => unknown>;
57
+
58
+ /**
59
+ * Registered plugin: model + param derivation lambdas.
60
+ * Type parameters are carried by PluginModel generic.
61
+ */
62
+ export type PluginInstance<Data = unknown, Params = unknown, Outputs = unknown> = {
63
+ readonly model: PluginModel<Data, Params, Outputs>;
64
+ readonly inputs: ParamsInputErased;
65
+ };
66
+
67
+ interface BlockModelV3Config<
68
+ OutputsCfg extends Record<string, ConfigRenderLambda>,
69
+ Data,
70
+ Plugins extends Record<string, PluginInstance> = {},
71
+ > {
36
72
  renderingMode: BlockRenderingMode;
37
73
  dataModel: DataModel<Data>;
38
74
  outputs: OutputsCfg;
@@ -42,16 +78,9 @@ interface BlockModelV3Config<Args, OutputsCfg extends Record<string, ConfigRende
42
78
  tags: ConfigRenderLambda | undefined;
43
79
  enrichmentTargets: ConfigRenderLambda | undefined;
44
80
  featureFlags: BlockCodeKnownFeatureFlags;
45
- args: ConfigRenderLambda<Args> | undefined;
46
- prerunArgs: ConfigRenderLambda<unknown> | undefined;
47
- }
48
-
49
- /** Options for creating a BlockModelV3 */
50
- export interface BlockModelV3Options<Data extends Record<string, unknown>> {
51
- /** The data model that defines initial data and migrations */
52
- dataModel: DataModel<Data>;
53
- /** Rendering mode for the block (default: 'Heavy') */
54
- renderingMode?: BlockRenderingMode;
81
+ argsFunction: ((data: unknown) => unknown) | undefined;
82
+ prerunArgsFunction: ((data: unknown) => unknown) | undefined;
83
+ plugins: Plugins;
55
84
  }
56
85
 
57
86
  /** Main entry point that each block should use in it's "config" module. Don't forget
@@ -63,45 +92,37 @@ export class BlockModelV3<
63
92
  OutputsCfg extends Record<string, ConfigRenderLambda>,
64
93
  Data extends Record<string, unknown> = Record<string, unknown>,
65
94
  Href extends `/${string}` = "/",
95
+ Plugins extends Record<string, PluginInstance> = {},
66
96
  > {
67
- private constructor(private readonly config: BlockModelV3Config<Args, OutputsCfg, Data>) {}
97
+ private constructor(private readonly config: BlockModelV3Config<OutputsCfg, Data, Plugins>) {}
68
98
 
69
99
  public static readonly INITIAL_BLOCK_FEATURE_FLAGS: BlockCodeKnownFeatureFlags = {
70
100
  supportsLazyState: true,
71
101
  requiresUIAPIVersion: 3,
72
- requiresModelAPIVersion: 2,
73
- requiresCreatePTableV2: true,
102
+ requiresModelAPIVersion: BLOCK_STORAGE_FACADE_VERSION,
103
+ requiresCreatePTable: 2,
74
104
  };
75
105
 
76
106
  /**
77
- * Creates a new BlockModelV3 builder with the specified data model and options.
107
+ * Creates a new BlockModelV3 builder with the specified data model.
78
108
  *
79
109
  * @example
80
- * const Version = defineDataVersions({ V1: DATA_MODEL_DEFAULT_VERSION });
81
- *
82
- * type VersionedData = { [Version.V1]: BlockData };
83
- *
84
- * const dataModel = new DataModelBuilder<VersionedData>()
85
- * .from(Version.V1)
110
+ * const dataModel = new DataModelBuilder()
111
+ * .from<BlockData>(DATA_MODEL_DEFAULT_VERSION)
86
112
  * .init(() => ({ numbers: [], labels: [] }));
87
113
  *
88
- * BlockModelV3.create({ dataModel })
114
+ * BlockModelV3.create(dataModel)
89
115
  * .args((data) => ({ numbers: data.numbers }))
90
116
  * .sections(() => [{ type: 'link', href: '/', label: 'Main' }])
91
117
  * .done();
92
118
  *
93
- * @param options Configuration options including required data model
119
+ * @param dataModel The data model that defines initial data and migrations
94
120
  */
95
121
  public static create<Data extends Record<string, unknown>>(
96
- options: BlockModelV3Options<Data>,
122
+ dataModel: DataModel<Data>,
97
123
  ): BlockModelV3<NoOb, {}, Data> {
98
- const { dataModel, renderingMode = "Heavy" } = options;
99
-
100
- // Register data model callbacks for VM use (initialData and upgrade)
101
- dataModel.registerCallbacks();
102
-
103
124
  return new BlockModelV3<NoOb, {}, Data>({
104
- renderingMode,
125
+ renderingMode: "Heavy",
105
126
  dataModel,
106
127
  outputs: {},
107
128
  // Register default sections callback (returns empty array)
@@ -111,8 +132,9 @@ export class BlockModelV3<
111
132
  tags: undefined,
112
133
  enrichmentTargets: undefined,
113
134
  featureFlags: { ...BlockModelV3.INITIAL_BLOCK_FEATURE_FLAGS },
114
- args: undefined,
115
- prerunArgs: undefined,
135
+ argsFunction: undefined,
136
+ prerunArgsFunction: undefined,
137
+ plugins: {},
116
138
  });
117
139
  }
118
140
 
@@ -124,7 +146,7 @@ export class BlockModelV3<
124
146
  * workflows outputs and interact with platforma drivers
125
147
  * @param flags additional flags that may alter lambda rendering procedure
126
148
  * */
127
- public output<const Key extends string, const RF extends RenderFunction<Args, Data>>(
149
+ public output<const Key extends string, const RF extends RenderFunction<Args, Data, unknown>>(
128
150
  key: Key,
129
151
  rf: RF,
130
152
  flags: ConfigRenderLambdaFlags & { withStatus: true },
@@ -136,7 +158,8 @@ export class BlockModelV3<
136
158
  };
137
159
  },
138
160
  Data,
139
- Href
161
+ Href,
162
+ Plugins
140
163
  >;
141
164
  /**
142
165
  * Add output cell to the configuration
@@ -146,7 +169,7 @@ export class BlockModelV3<
146
169
  * workflows outputs and interact with platforma drivers
147
170
  * @param flags additional flags that may alter lambda rendering procedure
148
171
  * */
149
- public output<const Key extends string, const RF extends RenderFunction<Args, Data>>(
172
+ public output<const Key extends string, const RF extends RenderFunction<Args, Data, unknown>>(
150
173
  key: Key,
151
174
  rf: RF,
152
175
  flags?: ConfigRenderLambdaFlags,
@@ -156,20 +179,21 @@ export class BlockModelV3<
156
179
  [K in Key]: ConfigRenderLambda<InferRenderFunctionReturn<RF>>;
157
180
  },
158
181
  Data,
159
- Href
182
+ Href,
183
+ Plugins
160
184
  >;
161
185
  public output(
162
186
  key: string,
163
- cfgOrRf: RenderFunction<Args, Data>,
187
+ cfgOrRf: RenderFunction<Args, Data, unknown>,
164
188
  flags: ConfigRenderLambdaFlags = {},
165
- ): BlockModelV3<Args, OutputsCfg, Data, Href> {
189
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
166
190
  return new BlockModelV3({
167
191
  ...this.config,
168
192
  outputs: {
169
193
  ...this.config.outputs,
170
194
  [key]: createAndRegisterRenderLambda({
171
195
  handle: `output#${key}`,
172
- lambda: () => cfgOrRf(new RenderCtx()),
196
+ lambda: () => cfgOrRf(new RenderCtx<Args, Data>()),
173
197
  ...flags,
174
198
  }),
175
199
  },
@@ -177,7 +201,10 @@ export class BlockModelV3<
177
201
  }
178
202
 
179
203
  /** Shortcut for {@link output} with retentive flag set to true. */
180
- public retentiveOutput<const Key extends string, const RF extends RenderFunction<Args, Data>>(
204
+ public retentiveOutput<
205
+ const Key extends string,
206
+ const RF extends RenderFunction<Args, Data, unknown>,
207
+ >(
181
208
  key: Key,
182
209
  rf: RF,
183
210
  ): BlockModelV3<
@@ -186,16 +213,17 @@ export class BlockModelV3<
186
213
  [K in Key]: ConfigRenderLambda<InferRenderFunctionReturn<RF>>;
187
214
  },
188
215
  Data,
189
- Href
216
+ Href,
217
+ Plugins
190
218
  > {
191
219
  return this.output(key, rf, { retentive: true });
192
220
  }
193
221
 
194
222
  /** Shortcut for {@link output} with withStatus flag set to true. */
195
- public outputWithStatus<const Key extends string, const RF extends RenderFunction<Args, Data>>(
196
- key: Key,
197
- rf: RF,
198
- ) {
223
+ public outputWithStatus<
224
+ const Key extends string,
225
+ const RF extends RenderFunction<Args, Data, unknown>,
226
+ >(key: Key, rf: RF) {
199
227
  return this.output(key, rf, { withStatus: true });
200
228
  }
201
229
 
@@ -212,10 +240,12 @@ export class BlockModelV3<
212
240
  * return { numbers: data.numbers };
213
241
  * })
214
242
  */
215
- public args<Args>(lambda: (data: Data) => Args): BlockModelV3<Args, OutputsCfg, Data, Href> {
216
- return new BlockModelV3<Args, OutputsCfg, Data, Href>({
243
+ public args<Args>(
244
+ lambda: (data: Data) => Args,
245
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
246
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
217
247
  ...this.config,
218
- args: createAndRegisterRenderLambda<Args>({ handle: "args", lambda }),
248
+ argsFunction: lambda as (data: unknown) => unknown,
219
249
  });
220
250
  }
221
251
 
@@ -239,13 +269,12 @@ export class BlockModelV3<
239
269
  * return { numbers: data.numbers };
240
270
  * })
241
271
  */
242
- public prerunArgs(fn: (data: Data) => unknown): BlockModelV3<Args, OutputsCfg, Data, Href> {
243
- return new BlockModelV3<Args, OutputsCfg, Data, Href>({
272
+ public prerunArgs(
273
+ fn: (data: Data) => unknown,
274
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
275
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
244
276
  ...this.config,
245
- prerunArgs: createAndRegisterRenderLambda({
246
- handle: "prerunArgs",
247
- lambda: fn,
248
- }),
277
+ prerunArgsFunction: fn as (data: unknown) => unknown,
249
278
  });
250
279
  }
251
280
 
@@ -253,48 +282,50 @@ export class BlockModelV3<
253
282
  public sections<
254
283
  const Ret extends SectionsExpectedType,
255
284
  const RF extends RenderFunction<Args, Data, Ret>,
256
- >(rf: RF): BlockModelV3<Args, OutputsCfg, Data, DeriveHref<ReturnType<RF>>> {
257
- return new BlockModelV3<Args, OutputsCfg, Data, DeriveHref<ReturnType<RF>>>({
285
+ >(rf: RF): BlockModelV3<Args, OutputsCfg, Data, DeriveHref<ReturnType<RF>>, Plugins> {
286
+ return new BlockModelV3<Args, OutputsCfg, Data, DeriveHref<ReturnType<RF>>, Plugins>({
258
287
  ...this.config,
259
288
  // Replace the default sections callback with the user-provided one
260
289
  sections: createAndRegisterRenderLambda(
261
- { handle: "sections", lambda: () => rf(new RenderCtx()) },
290
+ { handle: "sections", lambda: () => rf(new RenderCtx<Args, Data>()) },
262
291
  true,
263
292
  ),
264
293
  });
265
294
  }
266
295
 
267
296
  /** Sets a rendering function to derive block title, shown for the block in the left blocks-overview panel. */
268
- public title(rf: RenderFunction<Args, Data, string>): BlockModelV3<Args, OutputsCfg, Data, Href> {
269
- return new BlockModelV3<Args, OutputsCfg, Data, Href>({
297
+ public title(
298
+ rf: RenderFunction<Args, Data, string>,
299
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
300
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
270
301
  ...this.config,
271
302
  title: createAndRegisterRenderLambda({
272
303
  handle: "title",
273
- lambda: () => rf(new RenderCtx()),
304
+ lambda: () => rf(new RenderCtx<Args, Data>()),
274
305
  }),
275
306
  });
276
307
  }
277
308
 
278
309
  public subtitle(
279
310
  rf: RenderFunction<Args, Data, string>,
280
- ): BlockModelV3<Args, OutputsCfg, Data, Href> {
281
- return new BlockModelV3<Args, OutputsCfg, Data, Href>({
311
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
312
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
282
313
  ...this.config,
283
314
  subtitle: createAndRegisterRenderLambda({
284
315
  handle: "subtitle",
285
- lambda: () => rf(new RenderCtx()),
316
+ lambda: () => rf(new RenderCtx<Args, Data>()),
286
317
  }),
287
318
  });
288
319
  }
289
320
 
290
321
  public tags(
291
322
  rf: RenderFunction<Args, Data, string[]>,
292
- ): BlockModelV3<Args, OutputsCfg, Data, Href> {
293
- return new BlockModelV3<Args, OutputsCfg, Data, Href>({
323
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
324
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
294
325
  ...this.config,
295
326
  tags: createAndRegisterRenderLambda({
296
327
  handle: "tags",
297
- lambda: () => rf(new RenderCtx()),
328
+ lambda: () => rf(new RenderCtx<Args, Data>()),
298
329
  }),
299
330
  });
300
331
  }
@@ -302,8 +333,8 @@ export class BlockModelV3<
302
333
  /** Sets or overrides feature flags for the block. */
303
334
  public withFeatureFlags(
304
335
  flags: Partial<BlockCodeKnownFeatureFlags>,
305
- ): BlockModelV3<Args, OutputsCfg, Data, Href> {
306
- return new BlockModelV3<Args, OutputsCfg, Data, Href>({
336
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
337
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
307
338
  ...this.config,
308
339
  featureFlags: { ...this.config.featureFlags, ...flags },
309
340
  });
@@ -313,8 +344,10 @@ export class BlockModelV3<
313
344
  * Defines how to derive list of upstream references this block is meant to enrich with its exports from block args.
314
345
  * Influences dependency graph construction.
315
346
  */
316
- public enriches(lambda: (args: Args) => PlRef[]): BlockModelV3<Args, OutputsCfg, Data, Href> {
317
- return new BlockModelV3<Args, OutputsCfg, Data, Href>({
347
+ public enriches(
348
+ lambda: (args: Args) => PlRef[],
349
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
350
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
318
351
  ...this.config,
319
352
  enrichmentTargets: createAndRegisterRenderLambda({
320
353
  handle: "enrichmentTargets",
@@ -323,11 +356,57 @@ export class BlockModelV3<
323
356
  });
324
357
  }
325
358
 
359
+ /**
360
+ * Registers a plugin instance with the block.
361
+ *
362
+ * Plugins are UI components with their own model logic and persistent state.
363
+ * Each plugin must have a unique pluginId within the block.
364
+ *
365
+ * @param pluginId - Unique identifier for this plugin instance within the block
366
+ * @param plugin - Configured PluginModel instance (created via factory.create(config))
367
+ * @param params - Per-property lambdas deriving plugin params from block RenderCtx
368
+ *
369
+ * @example
370
+ * .plugin('mainTable', dataTablePlugin.create({ defaultOps: {...} }), {
371
+ * columns: (ctx) => ctx.outputs?.resolve("data")?.getPColumns(),
372
+ * sourceId: (ctx) => ctx.data.selectedSource,
373
+ * })
374
+ */
375
+ public plugin<const PluginId extends string, PluginData, PluginParams, PluginOutputs>(
376
+ pluginId: PluginId,
377
+ plugin: PluginModel<PluginData, PluginParams, PluginOutputs>,
378
+ params?: ParamsInput<PluginParams, Args, Data>,
379
+ ): BlockModelV3<
380
+ Args,
381
+ OutputsCfg,
382
+ Data,
383
+ Href,
384
+ Plugins & { [K in PluginId]: PluginInstance<PluginData, PluginParams, PluginOutputs> }
385
+ > {
386
+ // Validate pluginId uniqueness
387
+ if (pluginId in this.config.plugins) {
388
+ throw new Error(`Plugin '${pluginId}' already registered`);
389
+ }
390
+
391
+ const instance: PluginInstance<PluginData, PluginParams, PluginOutputs> = {
392
+ model: plugin,
393
+ inputs: (params ?? {}) as ParamsInputErased,
394
+ };
395
+
396
+ return new BlockModelV3({
397
+ ...this.config,
398
+ plugins: {
399
+ ...this.config.plugins,
400
+ [pluginId]: instance,
401
+ },
402
+ });
403
+ }
404
+
326
405
  /** Renders all provided block settings into a pre-configured platforma API
327
406
  * instance, that can be used in frontend to interact with block data, and
328
407
  * other features provided by the platforma to the block. */
329
408
  public done(): PlatformaExtended<
330
- PlatformaV3<Args, InferOutputsFromLambdas<OutputsCfg>, Data, Href>
409
+ PlatformaV3<Data, Args, InferOutputsFromLambdas<OutputsCfg>, Href, Plugins>
331
410
  > {
332
411
  return this.withFeatureFlags({
333
412
  ...this.config.featureFlags,
@@ -335,34 +414,64 @@ export class BlockModelV3<
335
414
  }
336
415
 
337
416
  public _done(): PlatformaExtended<
338
- PlatformaV3<Args, InferOutputsFromLambdas<OutputsCfg>, Data, Href>
417
+ PlatformaV3<Data, Args, InferOutputsFromLambdas<OutputsCfg>, Href, Plugins>
339
418
  > {
340
- if (this.config.args === undefined) throw new Error("Args rendering function not set.");
419
+ if (this.config.argsFunction === undefined) throw new Error("Args rendering function not set.");
341
420
 
342
421
  const apiVersion = 3;
343
422
 
344
- const migrationCount = this.config.dataModel.migrationCount;
423
+ // Build plugin registry
424
+ const { plugins } = this.config;
425
+ const pluginRegistry: Record<string, PluginName> = {};
426
+ for (const [pluginId, { model }] of Object.entries(plugins)) {
427
+ pluginRegistry[pluginId] = model.name;
428
+ }
429
+
430
+ const { dataModel, argsFunction, prerunArgsFunction } = this.config;
431
+
432
+ function getPlugin(pluginId: string): PluginInstance {
433
+ const plugin = plugins[pluginId];
434
+ if (!plugin) throw new Error(`Plugin model not found for '${pluginId}'`);
435
+ return plugin;
436
+ }
437
+
438
+ // Register ALL facade callbacks here, with dependencies captured via closures
439
+ registerFacadeCallbacks({
440
+ [BlockStorageFacadeCallbacks.StorageApplyUpdate]: applyStorageUpdate,
441
+ [BlockStorageFacadeCallbacks.StorageDebugView]: getStorageDebugView,
442
+ [BlockStorageFacadeCallbacks.StorageMigrate]: (currentStorageJson) =>
443
+ migrateStorage(currentStorageJson, {
444
+ migrateBlockData: (v) => dataModel.migrate(v),
445
+ getPluginRegistry: () => pluginRegistry,
446
+ migratePluginData: (pluginId, v) => getPlugin(pluginId).model.dataModel.migrate(v),
447
+ createPluginData: (pluginId) => getPlugin(pluginId).model.dataModel.getDefaultData(),
448
+ }),
449
+ [BlockStorageFacadeCallbacks.StorageInitial]: () =>
450
+ createInitialStorage({
451
+ getDefaultBlockData: () => dataModel.getDefaultData(),
452
+ getPluginRegistry: () => pluginRegistry,
453
+ createPluginData: (pluginId) => getPlugin(pluginId).model.dataModel.getDefaultData(),
454
+ }),
455
+ [BlockStorageFacadeCallbacks.ArgsDerive]: (storageJson) =>
456
+ deriveArgsFromStorage(storageJson, argsFunction),
457
+ [BlockStorageFacadeCallbacks.PrerunArgsDerive]: (storageJson) =>
458
+ derivePrerunArgsFromStorage(storageJson, argsFunction, prerunArgsFunction),
459
+ });
345
460
 
346
461
  const blockConfig: BlockConfigContainer = {
347
462
  v4: {
348
463
  configVersion: 4,
349
- modelAPIVersion: 2,
464
+ modelAPIVersion: BLOCK_STORAGE_FACADE_VERSION,
350
465
  sdkVersion: PlatformaSDKVersion,
351
466
  renderingMode: this.config.renderingMode,
352
- args: this.config.args,
353
- prerunArgs: this.config.prerunArgs,
354
- // Reference to __pl_data_initial callback registered by DataModel
355
- initialData: createRenderLambda({ handle: "__pl_data_initial" }),
356
467
  sections: this.config.sections,
357
468
  title: this.config.title,
469
+ subtitle: this.config.subtitle,
470
+ tags: this.config.tags,
358
471
  outputs: this.config.outputs,
359
472
  enrichmentTargets: this.config.enrichmentTargets,
360
473
  featureFlags: this.config.featureFlags,
361
- // Generate migration descriptors (indices for metadata)
362
- migrations:
363
- migrationCount > 0
364
- ? Array.from({ length: migrationCount }, (_, i) => ({ index: i }))
365
- : undefined,
474
+ blockLifecycleCallbacks: { ...BlockStorageFacadeHandles },
366
475
  },
367
476
 
368
477
  // fields below are added to allow previous desktop versions read generated configs
@@ -438,11 +547,11 @@ type _CreateTest = Expect<_CreateIsBlockModelV3>;
438
547
  // Test: BlockModelV3Config interface structure
439
548
  type _ConfigTest = Expect<
440
549
  Equal<
441
- BlockModelV3Config<_TestArgs, _TestOutputs, _TestData>,
550
+ BlockModelV3Config<_TestOutputs, _TestData>,
442
551
  {
443
552
  renderingMode: BlockRenderingMode;
444
- args: ConfigRenderLambda<_TestArgs> | undefined;
445
- prerunArgs: ConfigRenderLambda<unknown> | undefined;
553
+ argsFunction: ((data: unknown) => unknown) | undefined;
554
+ prerunArgsFunction: ((data: unknown) => unknown) | undefined;
446
555
  dataModel: DataModel<_TestData>;
447
556
  outputs: _TestOutputs;
448
557
  sections: ConfigRenderLambda;
@@ -451,6 +560,7 @@ type _ConfigTest = Expect<
451
560
  tags: ConfigRenderLambda | undefined;
452
561
  enrichmentTargets: ConfigRenderLambda | undefined;
453
562
  featureFlags: BlockCodeKnownFeatureFlags;
563
+ plugins: {};
454
564
  }
455
565
  >
456
566
  >;
@@ -70,7 +70,7 @@ export class BlockModel<
70
70
  supportsLazyState: true,
71
71
  requiresUIAPIVersion: 1,
72
72
  requiresModelAPIVersion: 1,
73
- requiresCreatePTableV2: true,
73
+ requiresCreatePTable: 2,
74
74
  };
75
75
  }
76
76
 
@@ -1,6 +1,18 @@
1
- import type { Unionize } from "./unionize";
2
1
  import type { BlockOutputsBase, BlockState } from "@milaboratories/pl-model-common";
3
2
 
3
+ /** Patch for the structural object */
4
+ export type Patch<K, V> = {
5
+ /** Field name to patch */
6
+ readonly key: K;
7
+ /** New value for the field */
8
+ readonly value: V;
9
+ };
10
+
11
+ /** Creates union type of all possible shallow patches for the given structure */
12
+ export type Unionize<T extends Record<string, unknown>> = {
13
+ [K in keyof T]: Patch<K, T[K]>;
14
+ }[keyof T];
15
+
4
16
  /** Patch for the BlockState, pushed by onStateUpdates method in SDK. */
5
17
  export type BlockStatePatch<
6
18
  Args = unknown,