@platforma-sdk/model 1.56.0 → 1.57.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.
@@ -10,7 +10,7 @@ import { getPlatformaInstance, isInUI, createAndRegisterRenderLambda } from "./i
10
10
  import type { DataModel } from "./block_migrations";
11
11
  import type { PlatformaV3 } from "./platforma";
12
12
  import type { InferRenderFunctionReturn, RenderFunction } from "./render";
13
- import { RenderCtx } from "./render";
13
+ import { BlockRenderCtx, PluginRenderCtx } from "./render";
14
14
  import type { PluginModel } from "./plugin_model";
15
15
  import type { RenderCtxBase } from "./render";
16
16
  import { PlatformaSDKVersion } from "./version";
@@ -192,8 +192,8 @@ export class BlockModelV3<
192
192
  outputs: {
193
193
  ...this.config.outputs,
194
194
  [key]: createAndRegisterRenderLambda({
195
- handle: `output#${key}`,
196
- lambda: () => cfgOrRf(new RenderCtx<Args, Data>()),
195
+ handle: `block-output#${key}`,
196
+ lambda: () => cfgOrRf(new BlockRenderCtx<Args, Data>()),
197
197
  ...flags,
198
198
  }),
199
199
  },
@@ -287,7 +287,7 @@ export class BlockModelV3<
287
287
  ...this.config,
288
288
  // Replace the default sections callback with the user-provided one
289
289
  sections: createAndRegisterRenderLambda(
290
- { handle: "sections", lambda: () => rf(new RenderCtx<Args, Data>()) },
290
+ { handle: "sections", lambda: () => rf(new BlockRenderCtx<Args, Data>()) },
291
291
  true,
292
292
  ),
293
293
  });
@@ -301,7 +301,7 @@ export class BlockModelV3<
301
301
  ...this.config,
302
302
  title: createAndRegisterRenderLambda({
303
303
  handle: "title",
304
- lambda: () => rf(new RenderCtx<Args, Data>()),
304
+ lambda: () => rf(new BlockRenderCtx<Args, Data>()),
305
305
  }),
306
306
  });
307
307
  }
@@ -313,7 +313,7 @@ export class BlockModelV3<
313
313
  ...this.config,
314
314
  subtitle: createAndRegisterRenderLambda({
315
315
  handle: "subtitle",
316
- lambda: () => rf(new RenderCtx<Args, Data>()),
316
+ lambda: () => rf(new BlockRenderCtx<Args, Data>()),
317
317
  }),
318
318
  });
319
319
  }
@@ -325,7 +325,7 @@ export class BlockModelV3<
325
325
  ...this.config,
326
326
  tags: createAndRegisterRenderLambda({
327
327
  handle: "tags",
328
- lambda: () => rf(new RenderCtx<Args, Data>()),
328
+ lambda: () => rf(new BlockRenderCtx<Args, Data>()),
329
329
  }),
330
330
  });
331
331
  }
@@ -458,6 +458,27 @@ export class BlockModelV3<
458
458
  derivePrerunArgsFromStorage(storageJson, argsFunction, prerunArgsFunction),
459
459
  });
460
460
 
461
+ // Register plugin input and output lambdas
462
+ const pluginOutputs: Record<string, ConfigRenderLambda> = {};
463
+ for (const [pluginId, { model, inputs }] of Object.entries(plugins)) {
464
+ // Wrap plugin param lambdas: close over BlockRenderCtx creation
465
+ const wrappedInputs: Record<string, () => unknown> = {};
466
+ for (const [paramKey, paramFn] of Object.entries(inputs)) {
467
+ wrappedInputs[paramKey] = () => paramFn(new BlockRenderCtx());
468
+ }
469
+
470
+ // Register plugin outputs (in config pack, evaluated by middle layer)
471
+ const outputs = model.outputs as Record<string, (ctx: PluginRenderCtx) => unknown>;
472
+ for (const [outputKey, outputFn] of Object.entries(outputs)) {
473
+ const key = `plugin-output#${pluginId}#${outputKey}`;
474
+ pluginOutputs[key] = createAndRegisterRenderLambda({
475
+ handle: key,
476
+ lambda: () => outputFn(new PluginRenderCtx(pluginId, wrappedInputs)),
477
+ });
478
+ }
479
+ }
480
+ const allOutputs = { ...this.config.outputs, ...pluginOutputs };
481
+
461
482
  const blockConfig: BlockConfigContainer = {
462
483
  v4: {
463
484
  configVersion: 4,
@@ -468,7 +489,7 @@ export class BlockModelV3<
468
489
  title: this.config.title,
469
490
  subtitle: this.config.subtitle,
470
491
  tags: this.config.tags,
471
- outputs: this.config.outputs,
492
+ outputs: allOutputs,
472
493
  enrichmentTargets: this.config.enrichmentTargets,
473
494
  featureFlags: this.config.featureFlags,
474
495
  blockLifecycleCallbacks: { ...BlockStorageFacadeHandles },
@@ -500,7 +521,7 @@ export class BlockModelV3<
500
521
  }),
501
522
  blockModelInfo: {
502
523
  outputs: Object.fromEntries(
503
- Object.entries(this.config.outputs).map(([key, value]) => [
524
+ Object.entries(allOutputs).map(([key, value]) => [
504
525
  key,
505
526
  {
506
527
  withStatus: Boolean(isConfigLambda(value) && value.withStatus),
@@ -218,8 +218,10 @@ describe("BlockStorage", () => {
218
218
  expect(getPluginData(storage, "chart1")).toEqual({ type: "bar" });
219
219
  });
220
220
 
221
- it("getPluginData should return undefined for missing plugin", () => {
222
- expect(getPluginData(baseStorage, "nonexistent")).toBeUndefined();
221
+ it("getPluginData should throw for missing plugin", () => {
222
+ expect(() => getPluginData(baseStorage, "nonexistent")).toThrow(
223
+ "Plugin 'nonexistent' not found in block storage",
224
+ );
223
225
  });
224
226
 
225
227
  it("should not modify original storage (immutability)", () => {
@@ -381,17 +381,17 @@ export function migrateBlockStorage(
381
381
  }
382
382
 
383
383
  /**
384
- * Gets plugin-specific data from BlockStorage (for UI)
384
+ * Gets plugin-specific data from block storage.
385
+ * Accepts raw storage (any format) and normalizes internally.
385
386
  *
386
- * @param storage - The BlockStorage instance
387
+ * @param rawStorage - Raw block storage (may be legacy format or BlockStorage)
387
388
  * @param pluginId - The plugin instance id
388
- * @returns The plugin data or undefined if not set
389
+ * @returns The plugin data
390
+ * @throws If pluginId is not found in storage
389
391
  */
390
- export function getPluginData<TData = unknown>(
391
- storage: BlockStorage,
392
- pluginId: string,
393
- ): TData | undefined {
392
+ export function getPluginData<TData = unknown>(rawStorage: unknown, pluginId: string): TData {
393
+ const storage = normalizeBlockStorage(rawStorage);
394
394
  const pluginEntry = storage.__plugins?.[pluginId];
395
- if (!pluginEntry) return undefined;
395
+ if (!pluginEntry) throw new Error(`Plugin '${pluginId}' not found in block storage`);
396
396
  return pluginEntry.__data as TData;
397
397
  }
package/src/index.ts CHANGED
@@ -15,7 +15,6 @@ export {
15
15
  } from "./block_migrations";
16
16
  export type { LegacyV1State } from "./block_migrations";
17
17
  export { PluginModel } from "./plugin_model";
18
- export type { PluginRenderCtx } from "./plugin_model";
19
18
  export * from "./bconfig";
20
19
  export * from "./components";
21
20
  export * from "./config";
@@ -9,29 +9,11 @@
9
9
 
10
10
  import type { DataModel } from "./block_migrations";
11
11
  import type { PluginName } from "./block_storage";
12
- import type { ResultPool } from "./render";
12
+ import type { PluginRenderCtx } from "./render";
13
13
 
14
14
  /** Symbol for internal builder creation method */
15
15
  const FROM_BUILDER = Symbol("fromBuilder");
16
16
 
17
- // =============================================================================
18
- // Plugin Render Context
19
- // =============================================================================
20
-
21
- /**
22
- * Context passed to plugin output functions.
23
- * Provides access to plugin's persistent data, params derived from block context,
24
- * and the result pool for accessing workflow outputs.
25
- */
26
- export interface PluginRenderCtx<Data, Params = undefined> {
27
- /** Plugin's persistent data */
28
- readonly data: Data;
29
- /** Params derived from block's RenderCtx */
30
- readonly params: Params;
31
- /** Result pool for accessing workflow outputs */
32
- readonly resultPool: ResultPool;
33
- }
34
-
35
17
  // =============================================================================
36
18
  // Plugin Type
37
19
  // =============================================================================
package/src/render/api.ts CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  AnchoredIdDeriver,
31
31
  collectSpecQueryColumns,
32
32
  ensurePColumn,
33
+ parseJson,
33
34
  extractAllColumns,
34
35
  isDataInfo,
35
36
  isPColumn,
@@ -48,6 +49,7 @@ import {
48
49
  import canonicalize from "canonicalize";
49
50
  import type { Optional } from "utility-types";
50
51
  import { getCfgRenderCtx } from "../internal";
52
+ import { getPluginData } from "../block_storage";
51
53
  import { TreeNodeAccessor, ifDef } from "./accessor";
52
54
  import type { FutureRef } from "./future";
53
55
  import type { AccessorHandle, GlobalCfgRenderCtx } from "./internal";
@@ -711,7 +713,7 @@ export abstract class RenderCtxBase<Args = unknown, Data = unknown> {
711
713
  }
712
714
 
713
715
  /** Main entry point to the API available within model lambdas (like outputs, sections, etc..) for v3+ blocks */
714
- export class RenderCtx<Args = unknown, Data = unknown> extends RenderCtxBase<Args, Data> {
716
+ export class BlockRenderCtx<Args = unknown, Data = unknown> extends RenderCtxBase<Args, Data> {
715
717
  private argsCache?: { v: Args | undefined };
716
718
  public get args(): Args | undefined {
717
719
  if (this.argsCache === undefined) {
@@ -751,8 +753,56 @@ export class RenderCtxLegacy<Args = unknown, UiState = unknown> extends RenderCt
751
753
  }
752
754
  }
753
755
 
756
+ /**
757
+ * Render context for plugin output functions.
758
+ * Reads plugin data from blockStorage and derives params from pre-wrapped input callbacks.
759
+ */
760
+ export class PluginRenderCtx<Data = unknown, Params = unknown> {
761
+ private readonly ctx: GlobalCfgRenderCtx;
762
+ private readonly pluginId: string;
763
+ private readonly wrappedInputs: Record<string, () => unknown>;
764
+
765
+ constructor(pluginId: string, wrappedInputs: Record<string, () => unknown>) {
766
+ this.ctx = getCfgRenderCtx();
767
+ this.pluginId = pluginId;
768
+ this.wrappedInputs = wrappedInputs;
769
+ }
770
+
771
+ private dataCache?: { v: Data };
772
+
773
+ /** Plugin's persistent data from blockStorage.__plugins.{pluginId}.__data */
774
+ public get data(): Data {
775
+ if (this.dataCache === undefined) {
776
+ const raw = this.ctx.blockStorage();
777
+ const pluginData = getPluginData<Data>(parseJson(raw), this.pluginId);
778
+ this.dataCache = { v: pluginData };
779
+ }
780
+ return this.dataCache.v;
781
+ }
782
+
783
+ private paramsCache?: { v: Params };
784
+
785
+ /** Params derived from block context via pre-wrapped input callbacks */
786
+ public get params(): Params {
787
+ if (this.paramsCache === undefined) {
788
+ const result: Record<string, unknown> = {};
789
+ for (const [key, fn] of Object.entries(this.wrappedInputs)) {
790
+ result[key] = fn();
791
+ }
792
+ this.paramsCache = { v: result as Params };
793
+ }
794
+ return this.paramsCache.v;
795
+ }
796
+
797
+ /** Result pool — same as block, from cfgRenderCtx methods */
798
+ public readonly resultPool = new ResultPool();
799
+ }
800
+
801
+ /** @deprecated Use BlockRenderCtx instead */
802
+ export type RenderCtx<Args = unknown, Data = unknown> = BlockRenderCtx<Args, Data>;
803
+
754
804
  export type RenderFunction<Args = unknown, State = unknown, Ret = unknown> = (
755
- rCtx: RenderCtx<Args, State>,
805
+ rCtx: BlockRenderCtx<Args, State>,
756
806
  ) => Ret;
757
807
 
758
808
  export type RenderFunctionLegacy<Args = unknown, State = unknown, Ret = unknown> = (