@platforma-sdk/model 1.61.0 → 1.62.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 (74) hide show
  1. package/dist/block_model.cjs +19 -10
  2. package/dist/block_model.cjs.map +1 -1
  3. package/dist/block_model.d.ts +22 -5
  4. package/dist/block_model.js +18 -10
  5. package/dist/block_model.js.map +1 -1
  6. package/dist/columns/column_collection_builder.cjs +26 -14
  7. package/dist/columns/column_collection_builder.cjs.map +1 -1
  8. package/dist/columns/column_collection_builder.d.ts +9 -8
  9. package/dist/columns/column_collection_builder.js +26 -14
  10. package/dist/columns/column_collection_builder.js.map +1 -1
  11. package/dist/columns/ctx_column_sources.cjs.map +1 -1
  12. package/dist/columns/ctx_column_sources.d.ts +1 -1
  13. package/dist/columns/ctx_column_sources.js.map +1 -1
  14. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +93 -89
  15. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
  16. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts +2 -2
  17. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +93 -89
  18. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
  19. package/dist/components/PlDataTable/createPlDataTable/index.cjs.map +1 -1
  20. package/dist/components/PlDataTable/createPlDataTable/index.d.ts +2 -1
  21. package/dist/components/PlDataTable/createPlDataTable/index.js.map +1 -1
  22. package/dist/index.cjs +7 -0
  23. package/dist/index.d.ts +7 -3
  24. package/dist/index.js +4 -1
  25. package/dist/package.cjs +1 -1
  26. package/dist/package.js +1 -1
  27. package/dist/platforma.d.ts +9 -3
  28. package/dist/plugin_handle.cjs.map +1 -1
  29. package/dist/plugin_handle.d.ts +13 -7
  30. package/dist/plugin_handle.js.map +1 -1
  31. package/dist/plugin_model.cjs +84 -12
  32. package/dist/plugin_model.cjs.map +1 -1
  33. package/dist/plugin_model.d.ts +121 -41
  34. package/dist/plugin_model.js +84 -12
  35. package/dist/plugin_model.js.map +1 -1
  36. package/dist/render/api.cjs +17 -31
  37. package/dist/render/api.cjs.map +1 -1
  38. package/dist/render/api.d.ts +12 -18
  39. package/dist/render/api.js +17 -31
  40. package/dist/render/api.js.map +1 -1
  41. package/dist/render/internal.cjs.map +1 -1
  42. package/dist/render/internal.d.ts +3 -14
  43. package/dist/render/internal.js.map +1 -1
  44. package/dist/services/block_services.cjs +18 -0
  45. package/dist/services/block_services.cjs.map +1 -0
  46. package/dist/services/block_services.d.ts +18 -0
  47. package/dist/services/block_services.js +16 -0
  48. package/dist/services/block_services.js.map +1 -0
  49. package/dist/services/index.cjs +2 -0
  50. package/dist/services/index.d.ts +3 -0
  51. package/dist/services/index.js +2 -0
  52. package/dist/services/service_bridge.cjs +35 -0
  53. package/dist/services/service_bridge.cjs.map +1 -0
  54. package/dist/services/service_bridge.d.ts +18 -0
  55. package/dist/services/service_bridge.js +33 -0
  56. package/dist/services/service_bridge.js.map +1 -0
  57. package/dist/services/service_resolve.d.ts +13 -0
  58. package/package.json +6 -6
  59. package/src/block_model.ts +49 -14
  60. package/src/columns/column_collection_builder.test.ts +23 -2
  61. package/src/columns/column_collection_builder.ts +38 -30
  62. package/src/columns/ctx_column_sources.ts +2 -2
  63. package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +159 -153
  64. package/src/components/PlDataTable/createPlDataTable/index.ts +5 -4
  65. package/src/index.ts +2 -0
  66. package/src/platforma.ts +14 -2
  67. package/src/plugin_handle.ts +24 -6
  68. package/src/plugin_model.ts +321 -82
  69. package/src/render/api.ts +55 -57
  70. package/src/render/internal.ts +3 -38
  71. package/src/services/block_services.ts +17 -0
  72. package/src/services/index.ts +3 -0
  73. package/src/services/service_bridge.ts +71 -0
  74. package/src/services/service_resolve.ts +71 -0
@@ -7,7 +7,8 @@
7
7
  * @module plugin_model
8
8
  */
9
9
 
10
- import type { BlockCodeKnownFeatureFlags } from "@milaboratories/pl-model-common";
10
+ import type { BlockCodeKnownFeatureFlags, OutputWithStatus } from "@milaboratories/pl-model-common";
11
+ import type { ResolveModelServices, ResolveUiServices } from "./services/service_resolve";
11
12
  import {
12
13
  type DataModel,
13
14
  DataModelBuilder,
@@ -22,6 +23,30 @@ import type { PluginRenderCtx } from "./render";
22
23
  /** Symbol for internal builder creation method */
23
24
  const FROM_BUILDER = Symbol("fromBuilder");
24
25
 
26
+ /** Output function signature for plugin render context. */
27
+ type PluginOutputFn<
28
+ Data extends PluginData,
29
+ Params extends PluginParams,
30
+ ModelServices,
31
+ UiServices,
32
+ T,
33
+ > = (
34
+ ctx: PluginRenderCtx<
35
+ PluginFactoryLike<Data, Params, Record<string, unknown>, ModelServices, UiServices>
36
+ >,
37
+ ) => T;
38
+
39
+ /** Mapped output functions for a plugin's outputs. */
40
+ type PluginOutputFns<
41
+ Data extends PluginData,
42
+ Params extends PluginParams,
43
+ Outputs extends PluginOutputs,
44
+ ModelServices,
45
+ UiServices,
46
+ > = {
47
+ [K in keyof Outputs]: PluginOutputFn<Data, Params, ModelServices, UiServices, Outputs[K]>;
48
+ };
49
+
25
50
  /** Symbol for internal plugin model creation — not accessible to external consumers */
26
51
  export const CREATE_PLUGIN_MODEL = Symbol("createPluginModel");
27
52
 
@@ -79,6 +104,11 @@ type PluginChainState = {
79
104
  recoverAtIndex?: number;
80
105
  };
81
106
 
107
+ /** Version → persisted data shape for each migration step (third generic of `PluginModel.define` when `data` is a `PluginDataModel`). */
108
+ export type PluginDataModelVersions<
109
+ M extends PluginDataModel<PluginData, Record<string, unknown>, unknown>,
110
+ > = M extends PluginDataModel<PluginData, infer Versions, unknown> ? Versions : never;
111
+
82
112
  /**
83
113
  * Builder for creating PluginDataModel with type-safe migrations.
84
114
  * Mirrors DataModelBuilder — same .from(), .migrate(), .recover(), .init() chain.
@@ -277,45 +307,68 @@ export class PluginInstance<
277
307
  Params extends PluginParams = undefined,
278
308
  Outputs extends PluginOutputs = PluginOutputs,
279
309
  TransferData = never,
310
+ ModelServices = unknown,
311
+ UiServices = unknown,
280
312
  > implements TransferTarget<Id, TransferData> {
281
313
  readonly id: Id;
282
314
  readonly transferVersion: string;
283
- /** @internal */
284
- readonly __transferBrand?: TransferData;
285
- private readonly factory: PluginModelFactory<Data, Params, Outputs, any, any>;
286
- private readonly config: any;
315
+ /** @internal Phantom for type inference; never set at runtime. */
316
+ readonly __instanceTypes?: {
317
+ data: Data;
318
+ params: Params;
319
+ outputs: Outputs;
320
+ modelServices: ModelServices;
321
+ uiServices: UiServices;
322
+ };
323
+ /** Bound closure that creates the PluginModel. Config is captured at factory.create() time. */
324
+ private readonly createPluginModel: () => PluginModel<
325
+ Data,
326
+ Params,
327
+ Outputs,
328
+ ModelServices,
329
+ UiServices
330
+ >;
287
331
 
288
332
  private constructor(
289
333
  id: Id,
290
- factory: PluginModelFactory<Data, Params, Outputs, any, any>,
334
+ createPluginModel: () => PluginModel<Data, Params, Outputs, ModelServices, UiServices>,
291
335
  transferVersion: string,
292
- config?: any,
293
336
  ) {
294
337
  this.id = id;
295
- this.factory = factory;
338
+ this.createPluginModel = createPluginModel;
296
339
  this.transferVersion = transferVersion;
297
- this.config = config;
298
340
  }
299
341
 
300
- /** @internal */
342
+ /** @internal Accepts concrete Config — binds it into a closure, avoiding Config variance issues. */
301
343
  static [FROM_BUILDER]<
302
344
  Id extends string,
303
345
  Data extends PluginData,
304
346
  Params extends PluginParams,
305
347
  Outputs extends PluginOutputs,
306
348
  TransferData,
349
+ Config extends PluginConfig = PluginConfig,
350
+ ModelServices = unknown,
351
+ UiServices = unknown,
307
352
  >(
308
353
  id: Id,
309
- factory: PluginModelFactory<Data, Params, Outputs, any, any>,
354
+ factory: PluginModelFactory<
355
+ Data,
356
+ Params,
357
+ Outputs,
358
+ Config,
359
+ Record<string, unknown>,
360
+ ModelServices,
361
+ UiServices
362
+ >,
310
363
  transferVersion: string,
311
- config?: any,
312
- ): PluginInstance<Id, Data, Params, Outputs, TransferData> {
313
- return new PluginInstance(id, factory, transferVersion, config);
364
+ config?: Config,
365
+ ): PluginInstance<Id, Data, Params, Outputs, TransferData, ModelServices, UiServices> {
366
+ return new PluginInstance(id, () => factory[CREATE_PLUGIN_MODEL](config), transferVersion);
314
367
  }
315
368
 
316
369
  /** @internal Create a PluginModel from this instance. Used by BlockModelV3.plugin(). */
317
- [CREATE_PLUGIN_MODEL](): PluginModel<Data, Params, Outputs> {
318
- return this.factory[CREATE_PLUGIN_MODEL](this.config);
370
+ [CREATE_PLUGIN_MODEL](): PluginModel<Data, Params, Outputs, ModelServices, UiServices> {
371
+ return this.createPluginModel();
319
372
  }
320
373
  }
321
374
 
@@ -327,15 +380,17 @@ export class PluginModel<
327
380
  Data extends PluginData = PluginData,
328
381
  Params extends PluginParams = undefined,
329
382
  Outputs extends PluginOutputs = PluginOutputs,
383
+ ModelServices = {},
384
+ UiServices = {},
330
385
  > {
331
386
  /** Globally unique plugin name */
332
387
  readonly name: PluginName;
333
388
  /** Data model instance for this plugin */
334
389
  readonly dataModel: DataModel<Data>;
335
390
  /** Output definitions - functions that compute outputs from plugin context */
336
- readonly outputs: {
337
- [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
338
- };
391
+ readonly outputs: PluginOutputFns<Data, Params, Outputs, ModelServices, UiServices>;
392
+ /** Per-output flags (e.g. withStatus) */
393
+ readonly outputFlags: Record<string, { withStatus: boolean }>;
339
394
  /** Feature flags declared by this plugin */
340
395
  readonly featureFlags?: BlockCodeKnownFeatureFlags;
341
396
  /** Create fresh default data. Config (if any) is captured at creation time. */
@@ -344,15 +399,15 @@ export class PluginModel<
344
399
  private constructor(options: {
345
400
  name: PluginName;
346
401
  dataModel: DataModel<Data>;
347
- outputs: {
348
- [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
349
- };
402
+ outputs: PluginOutputFns<Data, Params, Outputs, ModelServices, UiServices>;
403
+ outputFlags: Record<string, { withStatus: boolean }>;
350
404
  featureFlags?: BlockCodeKnownFeatureFlags;
351
405
  getDefaultData: () => DataVersioned<Data>;
352
406
  }) {
353
407
  this.name = options.name;
354
408
  this.dataModel = options.dataModel;
355
409
  this.outputs = options.outputs;
410
+ this.outputFlags = options.outputFlags;
356
411
  this.featureFlags = options.featureFlags;
357
412
  this.getDefaultData = options.getDefaultData;
358
413
  }
@@ -366,16 +421,17 @@ export class PluginModel<
366
421
  Data extends PluginData,
367
422
  Params extends PluginParams,
368
423
  Outputs extends PluginOutputs,
424
+ ModelServices = {},
425
+ UiServices = {},
369
426
  >(options: {
370
427
  name: PluginName;
371
428
  dataModel: DataModel<Data>;
372
- outputs: {
373
- [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
374
- };
429
+ outputs: PluginOutputFns<Data, Params, Outputs, ModelServices, UiServices>;
430
+ outputFlags: Record<string, { withStatus: boolean }>;
375
431
  featureFlags?: BlockCodeKnownFeatureFlags;
376
432
  getDefaultData: () => DataVersioned<Data>;
377
- }): PluginModel<Data, Params, Outputs> {
378
- return new PluginModel<Data, Params, Outputs>(options);
433
+ }): PluginModel<Data, Params, Outputs, ModelServices, UiServices> {
434
+ return new PluginModel<Data, Params, Outputs, ModelServices, UiServices>(options);
379
435
  }
380
436
 
381
437
  /**
@@ -399,11 +455,19 @@ export class PluginModel<
399
455
  Params extends PluginParams = undefined,
400
456
  Versions extends Record<string, unknown> = {},
401
457
  Config extends PluginConfig = undefined,
458
+ Flags extends BlockCodeKnownFeatureFlags = {},
402
459
  >(options: {
403
460
  name: PluginName;
404
461
  data: PluginDataModel<Data, Versions, Config>;
405
- featureFlags?: BlockCodeKnownFeatureFlags;
406
- }): PluginModelBuilder<Data, Params, {}, Config, Versions>;
462
+ featureFlags?: Flags;
463
+ }): PluginModelInitialBuilder<
464
+ Data,
465
+ Params,
466
+ Config,
467
+ Versions,
468
+ ResolveModelServices<Flags>,
469
+ ResolveUiServices<Flags>
470
+ >;
407
471
  /**
408
472
  * Creates a new PluginModelBuilder with a data model factory function (backward compatible).
409
473
  *
@@ -417,26 +481,34 @@ export class PluginModel<
417
481
  Data extends PluginData,
418
482
  Params extends PluginParams = undefined,
419
483
  Config extends PluginConfig = undefined,
484
+ Flags extends BlockCodeKnownFeatureFlags = {},
420
485
  >(options: {
421
486
  name: PluginName;
422
487
  data: (config?: Config) => DataModel<Data>;
423
- featureFlags?: BlockCodeKnownFeatureFlags;
424
- }): PluginModelBuilder<Data, Params, {}, Config, {}>;
488
+ featureFlags?: Flags;
489
+ }): PluginModelInitialBuilder<
490
+ Data,
491
+ Params,
492
+ Config,
493
+ {},
494
+ ResolveModelServices<Flags>,
495
+ ResolveUiServices<Flags>
496
+ >;
425
497
  static define(options: {
426
498
  name: PluginName;
427
499
  data: PluginDataModel<any, any, any> | ((config?: any) => DataModel<any>);
428
500
  featureFlags?: BlockCodeKnownFeatureFlags;
429
- }): PluginModelBuilder {
501
+ }): PluginModelInitialBuilder {
430
502
  if (options.data instanceof PluginDataModel) {
431
503
  const pdm = options.data;
432
- return PluginModelBuilder[FROM_BUILDER]({
504
+ return PluginModelInitialBuilder.create({
433
505
  name: options.name,
434
506
  dataFn: () => pdm.dataModel,
435
507
  getDefaultDataFn: (config: any) => pdm.getDefaultData(config),
436
508
  featureFlags: options.featureFlags,
437
509
  });
438
510
  }
439
- return PluginModelBuilder[FROM_BUILDER]({
511
+ return PluginModelInitialBuilder.create({
440
512
  name: options.name,
441
513
  dataFn: options.data,
442
514
  featureFlags: options.featureFlags,
@@ -451,13 +523,15 @@ export interface PluginFactory<
451
523
  Outputs extends PluginOutputs = PluginOutputs,
452
524
  Config extends PluginConfig = undefined,
453
525
  Versions extends Record<string, unknown> = {},
454
- > extends PluginFactoryLike<Data, Params, Outputs> {
526
+ ModelServices = {},
527
+ UiServices = {},
528
+ > extends PluginFactoryLike<Data, Params, Outputs, ModelServices, UiServices> {
455
529
  /** Create a named plugin instance, optionally with transfer at a specific version. */
456
530
  create<const Id extends string, const V extends string & keyof Versions = never>(options: {
457
531
  pluginId: Id;
458
532
  transferAt?: V;
459
533
  config?: Config;
460
- }): PluginInstance<Id, Data, Params, Outputs, Versions[V]>;
534
+ }): PluginInstance<Id, Data, Params, Outputs, Versions[V], ModelServices, UiServices>;
461
535
 
462
536
  /**
463
537
  * @internal Phantom field for structural type extraction.
@@ -467,6 +541,8 @@ export interface PluginFactory<
467
541
  data: Data;
468
542
  params: Params;
469
543
  outputs: Outputs;
544
+ modelServices: ModelServices;
545
+ uiServices: UiServices;
470
546
  config: Config;
471
547
  versions: Versions;
472
548
  };
@@ -478,28 +554,29 @@ class PluginModelFactory<
478
554
  Outputs extends PluginOutputs = PluginOutputs,
479
555
  Config extends PluginConfig = undefined,
480
556
  Versions extends Record<string, unknown> = {},
481
- > implements PluginFactory<Data, Params, Outputs, Config, Versions> {
557
+ ModelServices = {},
558
+ UiServices = {},
559
+ > implements PluginFactory<Data, Params, Outputs, Config, Versions, ModelServices, UiServices> {
482
560
  private readonly name: PluginName;
483
561
  private readonly dataFn: (config?: Config) => DataModel<Data>;
484
562
  private readonly getDefaultDataFn?: (config?: Config) => DataVersioned<Data>;
485
- readonly outputs: {
486
- [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
487
- };
563
+ readonly outputs: PluginOutputFns<Data, Params, Outputs, ModelServices, UiServices>;
564
+ private readonly outputFlags: Record<string, { withStatus: boolean }>;
488
565
  private readonly featureFlags?: BlockCodeKnownFeatureFlags;
489
566
 
490
567
  private constructor(options: {
491
568
  name: PluginName;
492
569
  dataFn: (config?: Config) => DataModel<Data>;
493
570
  getDefaultDataFn?: (config?: Config) => DataVersioned<Data>;
494
- outputs: {
495
- [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
496
- };
571
+ outputs: PluginOutputFns<Data, Params, Outputs, ModelServices, UiServices>;
572
+ outputFlags: Record<string, { withStatus: boolean }>;
497
573
  featureFlags?: BlockCodeKnownFeatureFlags;
498
574
  }) {
499
575
  this.name = options.name;
500
576
  this.dataFn = options.dataFn;
501
577
  this.getDefaultDataFn = options.getDefaultDataFn;
502
578
  this.outputs = options.outputs;
579
+ this.outputFlags = options.outputFlags;
503
580
  this.featureFlags = options.featureFlags;
504
581
  }
505
582
 
@@ -510,15 +587,16 @@ class PluginModelFactory<
510
587
  Outputs extends PluginOutputs,
511
588
  Config extends PluginConfig,
512
589
  Versions extends Record<string, unknown>,
590
+ ModelServices = {},
591
+ UiServices = {},
513
592
  >(options: {
514
593
  name: PluginName;
515
594
  dataFn: (config?: Config) => DataModel<Data>;
516
595
  getDefaultDataFn?: (config?: Config) => DataVersioned<Data>;
517
- outputs: {
518
- [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
519
- };
596
+ outputs: PluginOutputFns<Data, Params, Outputs, ModelServices, UiServices>;
597
+ outputFlags: Record<string, { withStatus: boolean }>;
520
598
  featureFlags?: BlockCodeKnownFeatureFlags;
521
- }): PluginModelFactory<Data, Params, Outputs, Config, Versions> {
599
+ }): PluginModelFactory<Data, Params, Outputs, Config, Versions, ModelServices, UiServices> {
522
600
  return new PluginModelFactory(options);
523
601
  }
524
602
 
@@ -526,24 +604,31 @@ class PluginModelFactory<
526
604
  pluginId: Id;
527
605
  transferAt?: V;
528
606
  config?: Config;
529
- }): PluginInstance<Id, Data, Params, Outputs, Versions[V]> {
607
+ }): PluginInstance<Id, Data, Params, Outputs, Versions[V], ModelServices, UiServices> {
530
608
  const transferVersion = options.transferAt ?? NO_TRANSFER_VERSION;
531
- return PluginInstance[FROM_BUILDER]<Id, Data, Params, Outputs, Versions[V]>(
532
- options.pluginId as Id,
533
- this as any,
534
- transferVersion,
535
- options.config,
536
- );
609
+ return PluginInstance[FROM_BUILDER]<
610
+ Id,
611
+ Data,
612
+ Params,
613
+ Outputs,
614
+ Versions[V],
615
+ Config,
616
+ ModelServices,
617
+ UiServices
618
+ >(options.pluginId as Id, this, transferVersion, options.config);
537
619
  }
538
620
 
539
621
  /** @internal Create a PluginModel from config. Config is captured in getDefaultData closure. */
540
- [CREATE_PLUGIN_MODEL](config?: Config): PluginModel<Data, Params, Outputs> {
622
+ [CREATE_PLUGIN_MODEL](
623
+ config?: Config,
624
+ ): PluginModel<Data, Params, Outputs, ModelServices, UiServices> {
541
625
  const dataModel = this.dataFn(config);
542
626
  const getDefaultDataFn = this.getDefaultDataFn;
543
- return PluginModel[FROM_BUILDER]<Data, Params, Outputs>({
627
+ return PluginModel[FROM_BUILDER]<Data, Params, Outputs, ModelServices, UiServices>({
544
628
  name: this.name,
545
629
  dataModel,
546
630
  outputs: this.outputs,
631
+ outputFlags: this.outputFlags,
547
632
  featureFlags: this.featureFlags,
548
633
  getDefaultData: getDefaultDataFn
549
634
  ? () => getDefaultDataFn(config)
@@ -577,32 +662,30 @@ class PluginModelBuilder<
577
662
  Outputs extends PluginOutputs = PluginOutputs,
578
663
  Config extends PluginConfig = undefined,
579
664
  Versions extends Record<string, unknown> = {},
665
+ ModelServices = {},
666
+ UiServices = {},
580
667
  > {
581
- private readonly name: PluginName;
582
- private readonly dataFn: (config?: Config) => DataModel<Data>;
583
- private readonly getDefaultDataFn?: (config?: Config) => DataVersioned<Data>;
584
- private readonly outputs: {
585
- [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
586
- };
587
- private readonly featureFlags?: BlockCodeKnownFeatureFlags;
588
-
589
- private constructor(options: {
668
+ protected readonly name: PluginName;
669
+ protected readonly dataFn: (config?: Config) => DataModel<Data>;
670
+ protected readonly getDefaultDataFn?: (config?: Config) => DataVersioned<Data>;
671
+ private readonly outputs: PluginOutputFns<Data, Params, Outputs, ModelServices, UiServices>;
672
+ private readonly outputFlags: Record<string, { withStatus: boolean }>;
673
+ protected readonly featureFlags?: BlockCodeKnownFeatureFlags;
674
+
675
+ protected constructor(options: {
590
676
  name: PluginName;
591
677
  dataFn: (config?: Config) => DataModel<Data>;
592
678
  getDefaultDataFn?: (config?: Config) => DataVersioned<Data>;
593
- outputs?: {
594
- [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
595
- };
679
+ outputs?: PluginOutputFns<Data, Params, Outputs, ModelServices, UiServices>;
680
+ outputFlags?: Record<string, { withStatus: boolean }>;
596
681
  featureFlags?: BlockCodeKnownFeatureFlags;
597
682
  }) {
598
683
  this.name = options.name;
599
684
  this.dataFn = options.dataFn;
600
685
  this.getDefaultDataFn = options.getDefaultDataFn;
601
686
  this.outputs =
602
- options.outputs ??
603
- ({} as {
604
- [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
605
- });
687
+ options.outputs ?? ({} as PluginOutputFns<Data, Params, Outputs, ModelServices, UiServices>);
688
+ this.outputFlags = options.outputFlags ?? {};
606
689
  this.featureFlags = options.featureFlags;
607
690
  }
608
691
 
@@ -613,15 +696,16 @@ class PluginModelBuilder<
613
696
  Outputs extends PluginOutputs,
614
697
  Config extends PluginConfig,
615
698
  Versions extends Record<string, unknown> = {},
699
+ ModelServices = {},
700
+ UiServices = {},
616
701
  >(options: {
617
702
  name: PluginName;
618
703
  dataFn: (config?: Config) => DataModel<Data>;
619
704
  getDefaultDataFn?: (config?: Config) => DataVersioned<Data>;
620
- outputs?: {
621
- [K in keyof Outputs]: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => Outputs[K];
622
- };
705
+ outputs?: PluginOutputFns<Data, Params, Outputs, ModelServices, UiServices>;
706
+ outputFlags?: Record<string, { withStatus: boolean }>;
623
707
  featureFlags?: BlockCodeKnownFeatureFlags;
624
- }): PluginModelBuilder<Data, Params, Outputs, Config, Versions> {
708
+ }): PluginModelBuilder<Data, Params, Outputs, Config, Versions, ModelServices, UiServices> {
625
709
  return new PluginModelBuilder(options);
626
710
  }
627
711
 
@@ -638,9 +722,29 @@ class PluginModelBuilder<
638
722
  */
639
723
  output<const Key extends string, T>(
640
724
  key: Key,
641
- fn: (ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>) => T,
642
- ): PluginModelBuilder<Data, Params, Outputs & { [K in Key]: T }, Config, Versions> {
643
- return new PluginModelBuilder<Data, Params, Outputs & { [K in Key]: T }, Config, Versions>({
725
+ fn: (
726
+ ctx: PluginRenderCtx<
727
+ PluginFactoryLike<Data, Params, Record<string, unknown>, ModelServices, UiServices>
728
+ >,
729
+ ) => T,
730
+ ): PluginModelBuilder<
731
+ Data,
732
+ Params,
733
+ Outputs & { [K in Key]: T },
734
+ Config,
735
+ Versions,
736
+ ModelServices,
737
+ UiServices
738
+ > {
739
+ return new PluginModelBuilder<
740
+ Data,
741
+ Params,
742
+ Outputs & { [K in Key]: T },
743
+ Config,
744
+ Versions,
745
+ ModelServices,
746
+ UiServices
747
+ >({
644
748
  name: this.name,
645
749
  dataFn: this.dataFn,
646
750
  getDefaultDataFn: this.getDefaultDataFn,
@@ -650,9 +754,72 @@ class PluginModelBuilder<
650
754
  [key]: fn,
651
755
  } as {
652
756
  [K in keyof (Outputs & { [P in Key]: T })]: (
653
- ctx: PluginRenderCtx<PluginFactoryLike<Data, Params>>,
757
+ ctx: PluginRenderCtx<
758
+ PluginFactoryLike<Data, Params, Record<string, unknown>, ModelServices, UiServices>
759
+ >,
654
760
  ) => (Outputs & { [P in Key]: T })[K];
655
761
  },
762
+ outputFlags: { ...this.outputFlags, [key]: { withStatus: false } },
763
+ });
764
+ }
765
+
766
+ /**
767
+ * Adds an output wrapped with status information to the plugin.
768
+ *
769
+ * The UI receives the full {@link OutputWithStatus} object instead of an unwrapped value,
770
+ * allowing it to distinguish between pending, success, and error states.
771
+ *
772
+ * @param key - Output name
773
+ * @param fn - Function that computes the output value from plugin context
774
+ * @returns PluginModel with the new status-wrapped output added
775
+ *
776
+ * @example
777
+ * .outputWithStatus('table', (ctx) => {
778
+ * const pCols = ctx.params.pFrame?.getPColumns();
779
+ * if (pCols === undefined) return undefined;
780
+ * return createPlDataTableV2(ctx, pCols, ctx.data.tableState);
781
+ * })
782
+ */
783
+ outputWithStatus<const Key extends string, T>(
784
+ key: Key,
785
+ fn: (
786
+ ctx: PluginRenderCtx<
787
+ PluginFactoryLike<Data, Params, Record<string, unknown>, ModelServices, UiServices>
788
+ >,
789
+ ) => T,
790
+ ): PluginModelBuilder<
791
+ Data,
792
+ Params,
793
+ Outputs & { [K in Key]: OutputWithStatus<T> },
794
+ Config,
795
+ Versions,
796
+ ModelServices,
797
+ UiServices
798
+ > {
799
+ return new PluginModelBuilder<
800
+ Data,
801
+ Params,
802
+ Outputs & { [K in Key]: OutputWithStatus<T> },
803
+ Config,
804
+ Versions,
805
+ ModelServices,
806
+ UiServices
807
+ >({
808
+ name: this.name,
809
+ dataFn: this.dataFn,
810
+ getDefaultDataFn: this.getDefaultDataFn,
811
+ featureFlags: this.featureFlags,
812
+ outputs: {
813
+ ...this.outputs,
814
+ [key]: fn,
815
+ } as {
816
+ [K in keyof (Outputs & { [P in Key]: OutputWithStatus<T> })]: (
817
+ ctx: PluginRenderCtx<
818
+ PluginFactoryLike<Data, Params, Record<string, unknown>, ModelServices, UiServices>
819
+ >,
820
+ ) => (Outputs & { [P in Key]: OutputWithStatus<T> })[K];
821
+ },
822
+ outputFlags: { ...this.outputFlags, [key]: { withStatus: true } },
656
823
  });
657
824
  }
658
825
 
@@ -669,12 +836,84 @@ class PluginModelBuilder<
669
836
  * // Create a named instance:
670
837
  * const table = myPlugin.create({ pluginId: 'mainTable', config: { ... } });
671
838
  */
672
- build(): PluginFactory<Data, Params, Outputs, Config, Versions> {
673
- return PluginModelFactory[FROM_BUILDER]<Data, Params, Outputs, Config, Versions>({
839
+ build(): PluginFactory<Data, Params, Outputs, Config, Versions, ModelServices, UiServices> {
840
+ return PluginModelFactory[FROM_BUILDER]<
841
+ Data,
842
+ Params,
843
+ Outputs,
844
+ Config,
845
+ Versions,
846
+ ModelServices,
847
+ UiServices
848
+ >({
674
849
  name: this.name,
675
850
  dataFn: this.dataFn,
676
851
  getDefaultDataFn: this.getDefaultDataFn,
677
852
  outputs: this.outputs,
853
+ outputFlags: this.outputFlags,
854
+ featureFlags: this.featureFlags,
855
+ });
856
+ }
857
+ }
858
+
859
+ /**
860
+ * Initial builder returned by PluginModel.define(). Extends PluginModelBuilder with .params().
861
+ * Once .params() or .output() is called, transitions to PluginModelBuilder (no second .params()).
862
+ */
863
+ class PluginModelInitialBuilder<
864
+ Data extends PluginData = PluginData,
865
+ Params extends PluginParams = undefined,
866
+ Config extends PluginConfig = undefined,
867
+ Versions extends Record<string, unknown> = {},
868
+ ModelServices = {},
869
+ UiServices = {},
870
+ > extends PluginModelBuilder<Data, Params, {}, Config, Versions, ModelServices, UiServices> {
871
+ /** @internal */
872
+ static create<
873
+ Data extends PluginData,
874
+ Config extends PluginConfig,
875
+ Versions extends Record<string, unknown> = {},
876
+ ModelServices = {},
877
+ UiServices = {},
878
+ >(options: {
879
+ name: PluginName;
880
+ dataFn: (config?: Config) => DataModel<Data>;
881
+ getDefaultDataFn?: (config?: Config) => DataVersioned<Data>;
882
+ featureFlags?: BlockCodeKnownFeatureFlags;
883
+ }): PluginModelInitialBuilder<Data, undefined, Config, Versions, ModelServices, UiServices> {
884
+ return new PluginModelInitialBuilder(options);
885
+ }
886
+
887
+ /**
888
+ * Sets the Params type for this plugin — the shape of data derived from the block's
889
+ * render context and passed into plugin output functions via `ctx.params`.
890
+ * Must be called before .output(). Available only on the initial builder.
891
+ *
892
+ * @example
893
+ * .params<{ title: string }>()
894
+ * .output('displayText', (ctx) => ctx.params.title)
895
+ */
896
+ params<P extends PluginParams>(): PluginModelBuilder<
897
+ Data,
898
+ P,
899
+ {},
900
+ Config,
901
+ Versions,
902
+ ModelServices,
903
+ UiServices
904
+ > {
905
+ return PluginModelBuilder[FROM_BUILDER]<
906
+ Data,
907
+ P,
908
+ {},
909
+ Config,
910
+ Versions,
911
+ ModelServices,
912
+ UiServices
913
+ >({
914
+ name: this.name,
915
+ dataFn: this.dataFn,
916
+ getDefaultDataFn: this.getDefaultDataFn,
678
917
  featureFlags: this.featureFlags,
679
918
  });
680
919
  }