@platforma-sdk/model 1.61.1 → 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 +17 -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 +16 -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 +6 -2
  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 +8 -4
  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 +37 -11
  32. package/dist/plugin_model.cjs.map +1 -1
  33. package/dist/plugin_model.d.ts +80 -39
  34. package/dist/plugin_model.js +37 -11
  35. package/dist/plugin_model.js.map +1 -1
  36. package/dist/render/api.cjs +13 -24
  37. package/dist/render/api.cjs.map +1 -1
  38. package/dist/render/api.d.ts +11 -14
  39. package/dist/render/api.js +13 -24
  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 +9 -9
  59. package/src/block_model.ts +47 -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 +1 -0
  66. package/src/platforma.ts +14 -2
  67. package/src/plugin_handle.ts +24 -6
  68. package/src/plugin_model.ts +252 -84
  69. package/src/render/api.ts +50 -51
  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
@@ -19,13 +19,7 @@ import { createColumnSnapshot } from "./column_snapshot";
19
19
  import type { ColumnSnapshotProvider, ColumnSource } from "./column_snapshot_provider";
20
20
  import { ArrayColumnProvider, toColumnSnapshotProvider } from "./column_snapshot_provider";
21
21
 
22
- import type { GlobalCfgRenderCtxMethods } from "../render/internal";
23
-
24
- /** Subset of render context methods needed for spec frame operations. */
25
- type SpecFrameCtx = Pick<
26
- GlobalCfgRenderCtxMethods,
27
- "createSpecFrame" | "specFrameDiscoverColumns" | "disposeSpecFrame"
28
- >;
22
+ import type { PFrameSpecDriver, PoolEntry, SpecFrameHandle } from "@milaboratories/pl-model-common";
29
23
 
30
24
  // --- FindColumnsOptions ---
31
25
 
@@ -40,7 +34,9 @@ export interface FindColumnsOptions {
40
34
  // --- ColumnCollection ---
41
35
 
42
36
  /** Plain collection — no axis context, selector-based filtering only. */
43
- export interface ColumnCollection {
37
+ export interface ColumnCollection extends Disposable {
38
+ /** Release the underlying spec frame WASM resource. */
39
+ dispose(): void;
44
40
  /** Point lookup by provider-native ID. */
45
41
  getColumn(id: PObjectId): undefined | ColumnSnapshot<PObjectId>;
46
42
 
@@ -53,7 +49,9 @@ export interface ColumnCollection {
53
49
  // --- AnchoredColumnCollection ---
54
50
 
55
51
  /** Axis-aware column collection with anchored identity derivation. */
56
- export interface AnchoredColumnCollection {
52
+ export interface AnchoredColumnCollection extends Disposable {
53
+ /** Release the underlying spec frame WASM resource. */
54
+ dispose(): void;
57
55
  /** Point lookup by anchored ID. */
58
56
  getColumn(id: SUniversalPColumnId): undefined | ColumnSnapshot<SUniversalPColumnId>;
59
57
 
@@ -122,7 +120,7 @@ export interface AnchoredBuildOptions extends BuildOptions {
122
120
  export class ColumnCollectionBuilder {
123
121
  private readonly providers: ColumnSnapshotProvider[] = [];
124
122
 
125
- constructor(private readonly specFrameCtx: SpecFrameCtx) {}
123
+ constructor(private readonly specDriver: PFrameSpecDriver) {}
126
124
 
127
125
  /**
128
126
  * Register a column source. Sources added first take precedence for dedup.
@@ -178,14 +176,14 @@ export class ColumnCollectionBuilder {
178
176
  const anchorSpecs = resolveAnchorSpecs(options.anchors, columnMap);
179
177
  const idDeriver = new AnchoredIdDeriver(anchorSpecs);
180
178
 
181
- return new AnchoredColumnCollectionImpl(this.specFrameCtx, {
179
+ return new AnchoredColumnCollectionImpl(this.specDriver, {
182
180
  columns: columnMap,
183
181
  idDeriver,
184
182
  anchorSpecs,
185
183
  columnListComplete: allowPartial ? allComplete : false,
186
184
  });
187
185
  } else {
188
- return new ColumnCollectionImpl(this.specFrameCtx, {
186
+ return new ColumnCollectionImpl(this.specDriver, {
189
187
  columns: columnMap,
190
188
  columnListComplete: allowPartial ? allComplete : false,
191
189
  });
@@ -230,25 +228,30 @@ interface ColumnCollectionImplOptions {
230
228
  readonly columnListComplete?: boolean;
231
229
  }
232
230
 
233
- class ColumnCollectionImpl implements ColumnCollection {
231
+ class ColumnCollectionImpl implements ColumnCollection, Disposable {
234
232
  private readonly columns: Map<PObjectId, ColumnSnapshot<PObjectId>>;
235
- private readonly specFrameHandle: string;
233
+ private readonly specFrameEntry: PoolEntry<SpecFrameHandle>;
236
234
  public readonly columnListComplete: boolean;
237
235
 
238
236
  constructor(
239
- private readonly ctx: SpecFrameCtx,
237
+ private readonly specDriver: PFrameSpecDriver,
240
238
  options: ColumnCollectionImplOptions,
241
239
  ) {
242
240
  this.columns = options.columns;
243
241
  this.columnListComplete = options.columnListComplete ?? false;
244
- this.specFrameHandle = this.ctx.createSpecFrame(
245
- Array.from(this.columns.entries()).reduce(
246
- (acc, [id, col]) => ((acc[id] = col.spec), acc),
247
- {} as Record<string, PColumnSpec>,
248
- ),
242
+ this.specFrameEntry = this.specDriver.createSpecFrame(
243
+ Object.fromEntries(Array.from(this.columns.entries(), ([id, col]) => [id, col.spec])),
249
244
  );
250
245
  }
251
246
 
247
+ dispose(): void {
248
+ this.specFrameEntry.unref();
249
+ }
250
+
251
+ [Symbol.dispose](): void {
252
+ this.dispose();
253
+ }
254
+
252
255
  getColumn(id: PObjectId): undefined | ColumnSnapshot<PObjectId> {
253
256
  const col = this.columns.get(id);
254
257
  if (col === undefined) return undefined;
@@ -259,7 +262,7 @@ class ColumnCollectionImpl implements ColumnCollection {
259
262
  const includeColumns = options?.include ? toMultiColumnSelectors(options.include) : undefined;
260
263
  const excludeColumns = options?.exclude ? toMultiColumnSelectors(options.exclude) : undefined;
261
264
 
262
- const response = this.ctx.specFrameDiscoverColumns(this.specFrameHandle, {
265
+ const response = this.specDriver.discoverColumns(this.specFrameEntry.key, {
263
266
  includeColumns,
264
267
  excludeColumns,
265
268
  axes: [],
@@ -288,17 +291,17 @@ interface AnchoredColumnCollectionImplOptions extends ColumnCollectionImplOption
288
291
  readonly anchorSpecs: Record<string, PColumnSpec>;
289
292
  }
290
293
 
291
- class AnchoredColumnCollectionImpl implements AnchoredColumnCollection {
294
+ class AnchoredColumnCollectionImpl implements AnchoredColumnCollection, Disposable {
292
295
  private readonly columns: Map<PObjectId, ColumnSnapshot<PObjectId>>;
293
296
  private readonly idDeriver: AnchoredIdDeriver;
294
- private readonly specFrameHandle: string;
297
+ private readonly specFrameEntry: PoolEntry<SpecFrameHandle>;
295
298
  private readonly anchorAxes: ColumnAxesWithQualifications[];
296
299
  /** Reverse lookup: SUniversalPColumnId → PObjectId */
297
300
  private readonly idToOriginal: Map<SUniversalPColumnId, PObjectId>;
298
301
  public readonly columnListComplete: boolean;
299
302
 
300
303
  constructor(
301
- private readonly ctx: SpecFrameCtx,
304
+ private readonly specDriver: PFrameSpecDriver,
302
305
  options: AnchoredColumnCollectionImplOptions,
303
306
  ) {
304
307
  this.columns = options.columns;
@@ -306,11 +309,8 @@ class AnchoredColumnCollectionImpl implements AnchoredColumnCollection {
306
309
  this.columnListComplete = options.columnListComplete ?? false;
307
310
 
308
311
  // Create spec frame from all collected columns
309
- this.specFrameHandle = this.ctx.createSpecFrame(
310
- Array.from(this.columns.entries()).reduce(
311
- (acc, [id, col]) => ((acc[id] = col.spec), acc),
312
- {} as Record<string, PColumnSpec>,
313
- ),
312
+ this.specFrameEntry = this.specDriver.createSpecFrame(
313
+ Object.fromEntries(Array.from(this.columns.entries(), ([id, col]) => [id, col.spec])),
314
314
  );
315
315
 
316
316
  // Build anchor axes for discovery requests
@@ -327,6 +327,14 @@ class AnchoredColumnCollectionImpl implements AnchoredColumnCollection {
327
327
  );
328
328
  }
329
329
 
330
+ dispose(): void {
331
+ this.specFrameEntry.unref();
332
+ }
333
+
334
+ [Symbol.dispose](): void {
335
+ this.dispose();
336
+ }
337
+
330
338
  getColumn(id: SUniversalPColumnId): undefined | ColumnSnapshot<SUniversalPColumnId> {
331
339
  const origId = this.idToOriginal.get(id);
332
340
  if (origId === undefined) return undefined;
@@ -341,7 +349,7 @@ class AnchoredColumnCollectionImpl implements AnchoredColumnCollection {
341
349
  const includeColumns = options?.include ? toMultiColumnSelectors(options.include) : undefined;
342
350
  const excludeColumns = options?.exclude ? toMultiColumnSelectors(options.exclude) : undefined;
343
351
 
344
- const response = this.ctx.specFrameDiscoverColumns(this.specFrameHandle, {
352
+ const response = this.specDriver.discoverColumns(this.specFrameEntry.key, {
345
353
  includeColumns,
346
354
  excludeColumns,
347
355
  constraints,
@@ -17,8 +17,8 @@ import type { ValueOf } from "@milaboratories/helpers";
17
17
  *
18
18
  * Returns an array of providers suitable for `ColumnCollectionBuilder.addSource()`.
19
19
  */
20
- export function collectCtxColumnSnapshotProviders<A, U>(
21
- ctx: RenderCtxBase<A, U>,
20
+ export function collectCtxColumnSnapshotProviders<A, U, S>(
21
+ ctx: RenderCtxBase<A, U, S>,
22
22
  ): ColumnSnapshotProvider[] {
23
23
  const providers: ColumnSnapshotProvider[] = [];
24
24
 
@@ -41,6 +41,7 @@ import type { PlDataTableFilters, PlDataTableModel } from "../typesV5";
41
41
  import { upgradePlDataTableStateV2 } from "../state-migration";
42
42
  import type { PlDataTableStateV2 } from "../state-migration";
43
43
  import type { ColumnSource, MatchingMode } from "../../../columns";
44
+ import { Services, type RequireServices } from "@milaboratories/pl-model-common";
44
45
  import { ColumnCollectionBuilder } from "../../../columns";
45
46
  import { isColumnSnapshotProvider } from "../../../columns/column_snapshot_provider";
46
47
  import { collectCtxColumnSnapshotProviders } from "../../../columns/ctx_column_sources";
@@ -184,8 +185,8 @@ export type createPlDataTableOptionsV3 = {
184
185
  // | { annotation: Record<string, string> }
185
186
  // | { ids: Set<string> };
186
187
 
187
- export function createPlDataTableV3<A, U>(
188
- ctx: RenderCtxBase<A, U>,
188
+ export function createPlDataTableV3<A, U, S extends RequireServices<typeof Services.PFrameSpec>>(
189
+ ctx: RenderCtxBase<A, U, S>,
189
190
  options: createPlDataTableOptionsV3,
190
191
  ): PlDataTableModel | undefined {
191
192
  const providers = options.source
@@ -195,170 +196,175 @@ export function createPlDataTableV3<A, U>(
195
196
  if (providers.length === 0) return undefined;
196
197
 
197
198
  // Step 1: Build collection from sources
198
- const builder = new ColumnCollectionBuilder(ctx).addSources(providers);
199
+ const builder = new ColumnCollectionBuilder(ctx.services.pframeSpec).addSources(providers);
199
200
  const anchors = options.columns.anchors;
200
201
  const collection = isNil(anchors) ? builder.build() : builder.build({ anchors });
201
202
 
202
203
  if (!collection) return undefined;
204
+ try {
205
+ // Step 2: Get data columns, excluding annotation-hidden ones
206
+ const findOptions = options.columns
207
+ ? {
208
+ include: options.columns.include,
209
+ exclude: options.columns.exclude,
210
+ mode: options.columns.mode,
211
+ maxHops: options.columns.maxHops,
212
+ }
213
+ : undefined;
214
+ const findResult = collection.findColumns(findOptions);
215
+ const snapshots = findResult.map((v) => ("column" in v ? v.column : v));
216
+ const dataSnapshots = snapshots.filter((s) => !isColumnHidden(s.spec));
217
+ if (dataSnapshots.length === 0) return undefined;
218
+
219
+ // Convert snapshots to PColumn<PColumnDataUniversal>[]
220
+ const columns: PColumn<PColumnDataUniversal>[] = [];
221
+ for (const snap of dataSnapshots) {
222
+ if (!snap.data) return undefined;
223
+ const data = snap.data.get();
224
+ if (data === undefined) return undefined;
225
+ columns.push({ id: snap.id, spec: snap.spec, data });
226
+ }
203
227
 
204
- // Step 2: Get data columns, excluding annotation-hidden ones
205
- const findOptions = options.columns
206
- ? {
207
- include: options.columns.include,
208
- exclude: options.columns.exclude,
209
- mode: options.columns.mode,
210
- maxHops: options.columns.maxHops,
211
- }
212
- : undefined;
213
- const findResult = collection.findColumns(findOptions);
214
- const snapshots = findResult.map((v) => ("column" in v ? v.column : v));
215
- const dataSnapshots = snapshots.filter((s) => !isColumnHidden(s.spec));
216
- if (dataSnapshots.length === 0) return undefined;
217
-
218
- // Convert snapshots to PColumn<PColumnDataUniversal>[]
219
- const columns: PColumn<PColumnDataUniversal>[] = [];
220
- for (const snap of dataSnapshots) {
221
- if (!snap.data) return undefined;
222
- const data = snap.data.get();
223
- if (data === undefined) return undefined;
224
- columns.push({ id: snap.id, spec: snap.spec, data });
225
- }
228
+ // Step 3: Normalize table state
229
+ const tableStateNormalized = upgradePlDataTableStateV2(options.state);
230
+
231
+ // Step 4: Get label columns from result pool and match to data columns
232
+ const allLabelColumns = getAllLabelColumns(ctx.resultPool);
233
+ if (!allLabelColumns) return undefined;
226
234
 
227
- // Step 3: Normalize table state
228
- const tableStateNormalized = upgradePlDataTableStateV2(options.state);
229
-
230
- // Step 4: Get label columns from result pool and match to data columns
231
- const allLabelColumns = getAllLabelColumns(ctx.resultPool);
232
- if (!allLabelColumns) return undefined;
233
-
234
- const fullLabelColumns = getMatchingLabelColumns(
235
- columns.map(getColumnIdAndSpec),
236
- allLabelColumns,
237
- );
238
-
239
- const fullColumns = [...columns, ...fullLabelColumns];
240
-
241
- // Step 5: Build column ID set for filter/sorting validation
242
- const fullColumnsAxes = uniqueBy(
243
- fullColumns.flatMap((c) => c.spec.axesSpec.map((a) => getAxisId(a))),
244
- (a) => canonicalizeJson<AxisId>(a),
245
- );
246
- const fullColumnsIds: PTableColumnId[] = [
247
- ...fullColumnsAxes.map((a) => ({ type: "axis", id: a }) satisfies PTableColumnIdAxis),
248
- ...fullColumns.map((c) => ({ type: "column", id: c.id }) satisfies PTableColumnIdColumn),
249
- ];
250
- const fullColumnsIdsSet = new Set(fullColumnsIds.map((c) => canonicalizeJson<PTableColumnId>(c)));
251
- const isValidColumnId = (id: string): boolean =>
252
- fullColumnsIdsSet.has(id as CanonicalizedJson<PTableColumnId>);
253
-
254
- // Step 6: Filtering validation
255
- const stateFilters = tableStateNormalized.pTableParams.filters;
256
- const opsFilters = options?.filters ?? null;
257
- const filters: null | PlDataTableFilters =
258
- stateFilters != null && opsFilters != null
259
- ? { type: "and", filters: [stateFilters, opsFilters] }
260
- : (stateFilters ?? opsFilters);
261
- const filterColumns = filters ? collectFilterSpecColumns(filters) : [];
262
- const firstInvalidFilterColumn = filterColumns.find((col) => !isValidColumnId(col));
263
- if (firstInvalidFilterColumn)
264
- throw new Error(
265
- `Invalid filter column ${firstInvalidFilterColumn}: column reference does not match the table columns`,
235
+ const fullLabelColumns = getMatchingLabelColumns(
236
+ columns.map(getColumnIdAndSpec),
237
+ allLabelColumns,
266
238
  );
267
239
 
268
- // Step 7: Sorting validation
269
- const userSorting = tableStateNormalized.pTableParams.sorting;
270
- const sorting = (isEmpty(userSorting) ? options?.sorting : userSorting) ?? [];
271
- const firstInvalidSortingColumn = sorting.find(
272
- (s) => !isValidColumnId(canonicalizeJson<PTableColumnId>(s.column)),
273
- );
274
- if (firstInvalidSortingColumn)
275
- throw new Error(
276
- `Invalid sorting column ${JSON.stringify(firstInvalidSortingColumn.column)}: column reference does not match the table columns`,
240
+ const fullColumns = [...columns, ...fullLabelColumns];
241
+
242
+ // Step 5: Build column ID set for filter/sorting validation
243
+ const fullColumnsAxes = uniqueBy(
244
+ fullColumns.flatMap((c) => c.spec.axesSpec.map((a) => getAxisId(a))),
245
+ (a) => canonicalizeJson<AxisId>(a),
246
+ );
247
+ const fullColumnsIds: PTableColumnId[] = [
248
+ ...fullColumnsAxes.map((a) => ({ type: "axis", id: a }) satisfies PTableColumnIdAxis),
249
+ ...fullColumns.map((c) => ({ type: "column", id: c.id }) satisfies PTableColumnIdColumn),
250
+ ];
251
+ const fullColumnsIdsSet = new Set(
252
+ fullColumnsIds.map((c) => canonicalizeJson<PTableColumnId>(c)),
253
+ );
254
+ const isValidColumnId = (id: string): boolean =>
255
+ fullColumnsIdsSet.has(id as CanonicalizedJson<PTableColumnId>);
256
+
257
+ // Step 6: Filtering validation
258
+ const stateFilters = tableStateNormalized.pTableParams.filters;
259
+ const opsFilters = options?.filters ?? null;
260
+ const filters: null | PlDataTableFilters =
261
+ stateFilters != null && opsFilters != null
262
+ ? { type: "and", filters: [stateFilters, opsFilters] }
263
+ : (stateFilters ?? opsFilters);
264
+ const filterColumns = filters ? collectFilterSpecColumns(filters) : [];
265
+ const firstInvalidFilterColumn = filterColumns.find((col) => !isValidColumnId(col));
266
+ if (firstInvalidFilterColumn)
267
+ throw new Error(
268
+ `Invalid filter column ${firstInvalidFilterColumn}: column reference does not match the table columns`,
269
+ );
270
+
271
+ // Step 7: Sorting validation
272
+ const userSorting = tableStateNormalized.pTableParams.sorting;
273
+ const sorting = (isEmpty(userSorting) ? options?.sorting : userSorting) ?? [];
274
+ const firstInvalidSortingColumn = sorting.find(
275
+ (s) => !isValidColumnId(canonicalizeJson<PTableColumnId>(s.column)),
276
+ );
277
+ if (firstInvalidSortingColumn)
278
+ throw new Error(
279
+ `Invalid sorting column ${JSON.stringify(firstInvalidSortingColumn.column)}: column reference does not match the table columns`,
280
+ );
281
+
282
+ // Step 8: Build full table definition and handles
283
+ const coreJoinType = options?.coreJoinType ?? "full";
284
+ const fullDef = createPTableDef({
285
+ columns,
286
+ labelColumns: fullLabelColumns,
287
+ coreJoinType,
288
+ filters,
289
+ sorting,
290
+ coreColumnPredicate: options?.coreColumnPredicate,
291
+ });
292
+
293
+ const fullHandle = ctx.createPTableV2(fullDef);
294
+ const pframeHandle = ctx.createPFrame(fullColumns);
295
+ if (!fullHandle || !pframeHandle) return undefined;
296
+
297
+ // Step 9: Determine hidden columns
298
+ const hiddenColumns = new Set<PObjectId>(
299
+ ((): PObjectId[] => {
300
+ // Inner join works as a filter — all columns must be present
301
+ if (coreJoinType === "inner") return [];
302
+
303
+ const hiddenColIds = tableStateNormalized.pTableParams.hiddenColIds;
304
+ if (hiddenColIds) return hiddenColIds;
305
+
306
+ return columns.filter((c) => isColumnOptional(c.spec)).map((c) => c.id);
307
+ })(),
277
308
  );
278
309
 
279
- // Step 8: Build full table definition and handles
280
- const coreJoinType = options?.coreJoinType ?? "full";
281
- const fullDef = createPTableDef({
282
- columns,
283
- labelColumns: fullLabelColumns,
284
- coreJoinType,
285
- filters,
286
- sorting,
287
- coreColumnPredicate: options?.coreColumnPredicate,
288
- });
289
-
290
- const fullHandle = ctx.createPTableV2(fullDef);
291
- const pframeHandle = ctx.createPFrame(fullColumns);
292
- if (!fullHandle || !pframeHandle) return undefined;
293
-
294
- // Step 9: Determine hidden columns
295
- const hiddenColumns = new Set<PObjectId>(
296
- ((): PObjectId[] => {
297
- // Inner join works as a filter — all columns must be present
298
- if (coreJoinType === "inner") return [];
299
-
300
- const hiddenColIds = tableStateNormalized.pTableParams.hiddenColIds;
301
- if (hiddenColIds) return hiddenColIds;
302
-
303
- return columns.filter((c) => isColumnOptional(c.spec)).map((c) => c.id);
304
- })(),
305
- );
306
-
307
- // Preserve linker columns
308
- columns.filter((c) => isLinkerColumn(c.spec)).forEach((c) => hiddenColumns.delete(c.id));
309
-
310
- // Preserve core columns as they change the shape of join
311
- const coreColumnPredicate = options?.coreColumnPredicate;
312
- if (coreColumnPredicate) {
313
- const coreColumns = columns.flatMap((c) =>
314
- coreColumnPredicate(getColumnIdAndSpec(c)) ? [c.id] : [],
310
+ // Preserve linker columns
311
+ columns.filter((c) => isLinkerColumn(c.spec)).forEach((c) => hiddenColumns.delete(c.id));
312
+
313
+ // Preserve core columns as they change the shape of join
314
+ const coreColumnPredicate = options?.coreColumnPredicate;
315
+ if (coreColumnPredicate) {
316
+ const coreColumns = columns.flatMap((c) =>
317
+ coreColumnPredicate(getColumnIdAndSpec(c)) ? [c.id] : [],
318
+ );
319
+ coreColumns.forEach((c) => hiddenColumns.delete(c));
320
+ }
321
+
322
+ // Preserve sorted columns from being hidden
323
+ sorting
324
+ .map((s) => s.column)
325
+ .filter((c): c is PTableColumnIdColumn => c.type === "column")
326
+ .forEach((c) => hiddenColumns.delete(c.id));
327
+
328
+ // Preserve filter columns from being hidden
329
+ if (filters) {
330
+ collectFilterSpecColumns(filters)
331
+ .flatMap((c) => {
332
+ const obj = parseJson(c);
333
+ return obj.type === "column" ? [obj.id] : [];
334
+ })
335
+ .forEach((c) => hiddenColumns.delete(c));
336
+ }
337
+
338
+ // Step 10: Build visible table definition
339
+ const visibleColumns = columns.filter((c) => !hiddenColumns.has(c.id));
340
+ const visibleLabelColumns = getMatchingLabelColumns(
341
+ visibleColumns.map(getColumnIdAndSpec),
342
+ allLabelColumns,
315
343
  );
316
- coreColumns.forEach((c) => hiddenColumns.delete(c));
317
- }
318
344
 
319
- // Preserve sorted columns from being hidden
320
- sorting
321
- .map((s) => s.column)
322
- .filter((c): c is PTableColumnIdColumn => c.type === "column")
323
- .forEach((c) => hiddenColumns.delete(c.id));
324
-
325
- // Preserve filter columns from being hidden
326
- if (filters) {
327
- collectFilterSpecColumns(filters)
328
- .flatMap((c) => {
329
- const obj = parseJson(c);
330
- return obj.type === "column" ? [obj.id] : [];
331
- })
332
- .forEach((c) => hiddenColumns.delete(c));
345
+ if (!allPColumnsReady([...visibleColumns, ...visibleLabelColumns])) return undefined;
346
+
347
+ const visibleDef = createPTableDef({
348
+ columns: visibleColumns,
349
+ labelColumns: visibleLabelColumns,
350
+ coreJoinType,
351
+ filters,
352
+ sorting,
353
+ coreColumnPredicate,
354
+ });
355
+ const visibleHandle = ctx.createPTableV2(visibleDef);
356
+
357
+ if (!visibleHandle) return undefined;
358
+
359
+ return {
360
+ sourceId: tableStateNormalized.pTableParams.sourceId,
361
+ fullTableHandle: fullHandle,
362
+ fullPframeHandle: pframeHandle,
363
+ visibleTableHandle: visibleHandle,
364
+ } satisfies PlDataTableModel;
365
+ } finally {
366
+ collection.dispose();
333
367
  }
334
-
335
- // Step 10: Build visible table definition
336
- const visibleColumns = columns.filter((c) => !hiddenColumns.has(c.id));
337
- const visibleLabelColumns = getMatchingLabelColumns(
338
- visibleColumns.map(getColumnIdAndSpec),
339
- allLabelColumns,
340
- );
341
-
342
- if (!allPColumnsReady([...visibleColumns, ...visibleLabelColumns])) return undefined;
343
-
344
- const visibleDef = createPTableDef({
345
- columns: visibleColumns,
346
- labelColumns: visibleLabelColumns,
347
- coreJoinType,
348
- filters,
349
- sorting,
350
- coreColumnPredicate,
351
- });
352
- const visibleHandle = ctx.createPTableV2(visibleDef);
353
-
354
- if (!visibleHandle) return undefined;
355
-
356
- return {
357
- sourceId: tableStateNormalized.pTableParams.sourceId,
358
- fullTableHandle: fullHandle,
359
- fullPframeHandle: pframeHandle,
360
- visibleTableHandle: visibleHandle,
361
- } satisfies PlDataTableModel;
362
368
  }
363
369
 
364
370
  /** Normalize raw ColumnSource | ColumnSource[] into a flat list of sources. */
@@ -1,3 +1,4 @@
1
+ import { Services, type RequireServices } from "@milaboratories/pl-model-common";
1
2
  import type { RenderCtxBase } from "../../../render";
2
3
  import type { PlDataTableModel } from "../typesV5";
3
4
  import { createPlDataTableOptionsV2, createPlDataTableV2 } from "./createPlDataTableV2";
@@ -8,12 +9,12 @@ export function createPlDataTable<A, U>(
8
9
  ctx: RenderCtxBase<A, U>,
9
10
  options: { version: "v2" } & createPlDataTableOptionsV2,
10
11
  ): ReturnType<typeof createPlDataTableV2>;
11
- export function createPlDataTable<A, U>(
12
- ctx: RenderCtxBase<A, U>,
12
+ export function createPlDataTable<A, U, S extends RequireServices<typeof Services.PFrameSpec>>(
13
+ ctx: RenderCtxBase<A, U, S>,
13
14
  options: { version?: "v3" } & createPlDataTableOptionsV3,
14
15
  ): ReturnType<typeof createPlDataTableV3>;
15
- export function createPlDataTable<A, U>(
16
- ctx: RenderCtxBase<A, U>,
16
+ export function createPlDataTable<A, U, S extends RequireServices<typeof Services.PFrameSpec>>(
17
+ ctx: RenderCtxBase<A, U, S>,
17
18
  options:
18
19
  | ({ version: "v2" } & createPlDataTableOptionsV2)
19
20
  | ({ version?: "v3" } & createPlDataTableOptionsV3),
package/src/index.ts CHANGED
@@ -54,6 +54,7 @@ export * from "./block_api_v2";
54
54
  export * from "./filters";
55
55
  export * from "./annotations";
56
56
  export * from "./pframe_utils";
57
+ export * from "./services";
57
58
 
58
59
  // reexporting everything from SDK model
59
60
  export * from "@milaboratories/pl-model-common";
package/src/platforma.ts CHANGED
@@ -9,6 +9,7 @@ import type {
9
9
  OutputWithStatus,
10
10
  } from "@milaboratories/pl-model-common";
11
11
  import type { SdkInfo } from "./version";
12
+ import type { ServiceDispatch, UiServices as AllUiServices } from "@milaboratories/pl-model-common";
12
13
  import type { BlockStatePatch } from "./block_state_patch";
13
14
  import type { PluginRecord } from "./block_model";
14
15
  import type { PluginHandle, PluginFactoryLike } from "./plugin_handle";
@@ -54,13 +55,18 @@ export interface PlatformaV3<
54
55
  >,
55
56
  Href extends `/${string}` = `/${string}`,
56
57
  Plugins extends Record<string, unknown> = Record<string, unknown>,
58
+ UiServices extends Partial<AllUiServices> = Partial<AllUiServices>,
57
59
  >
58
60
  extends BlockApiV3<Data, Args, Outputs, Href>, DriverKit {
59
61
  /** Information about SDK version current platforma environment was compiled with. */
60
62
  readonly sdkInfo: SdkInfo;
61
63
  readonly apiVersion: 3;
64
+ /** Service dispatch — lists available services, their methods, and invokes them. */
65
+ readonly serviceDispatch: ServiceDispatch;
62
66
  /** @internal Type brand for plugin type inference. Not used at runtime. */
63
67
  readonly __pluginsBrand?: Plugins;
68
+ /** @internal Type brand for UI service type inference. Not used at runtime. */
69
+ readonly __uiServicesBrand?: UiServices;
64
70
  }
65
71
 
66
72
  export type Platforma<
@@ -148,7 +154,13 @@ export type InferPluginData<Pl, PluginId extends string> =
148
154
  * because PluginRecord doesn't carry Config (lost after factory.create()).
149
155
  */
150
156
  export type InferPluginHandles<T extends Record<string, unknown>> = {
151
- readonly [K in keyof T]: T[K] extends PluginRecord<infer Data, infer Params, infer Outputs>
152
- ? { handle: PluginHandle<PluginFactoryLike<Data, Params, Outputs>> }
157
+ readonly [K in keyof T]: T[K] extends PluginRecord<
158
+ infer Data,
159
+ infer Params,
160
+ infer Outputs,
161
+ infer ModelServices,
162
+ infer UiServices
163
+ >
164
+ ? { handle: PluginHandle<PluginFactoryLike<Data, Params, Outputs, ModelServices, UiServices>> }
153
165
  : never;
154
166
  };
@@ -23,37 +23,55 @@ export interface PluginFactoryLike<
23
23
  Data extends Record<string, unknown> = Record<string, unknown>,
24
24
  Params extends undefined | Record<string, unknown> = undefined | Record<string, unknown>,
25
25
  Outputs extends Record<string, unknown> = Record<string, unknown>,
26
+ ModelServices = unknown,
27
+ UiServices = unknown,
26
28
  > {
27
29
  readonly __types?: {
28
30
  data: Data;
29
31
  params: Params;
30
32
  outputs: Outputs;
33
+ modelServices: ModelServices;
34
+ uiServices: UiServices;
31
35
  };
32
36
  }
33
37
 
34
38
  /** Extract the Data type from a PluginFactoryLike phantom. */
35
39
  export type InferFactoryData<F extends PluginFactoryLike> = NonNullable<
36
- F extends PluginFactoryLike<infer D, any, any> ? D : Record<string, unknown>
40
+ F extends PluginFactoryLike<infer D, any, any, any, any> ? D : Record<string, unknown>
37
41
  >;
38
42
 
39
43
  /** Extract the Params type from a PluginFactoryLike phantom. */
40
44
  export type InferFactoryParams<F extends PluginFactoryLike> = NonNullable<
41
- F extends PluginFactoryLike<any, infer P, any> ? P : undefined
45
+ F extends PluginFactoryLike<any, infer P, any, any, any> ? P : undefined
42
46
  >;
43
47
 
44
48
  /** Extract the Outputs type from a PluginFactoryLike phantom. */
45
49
  export type InferFactoryOutputs<F extends PluginFactoryLike> = NonNullable<
46
- F extends PluginFactoryLike<any, any, infer O> ? O : Record<string, unknown>
50
+ F extends PluginFactoryLike<any, any, infer O, any, any> ? O : Record<string, unknown>
47
51
  >;
48
52
 
53
+ /** Extract the pre-resolved model services type from a PluginFactoryLike phantom. */
54
+ export type InferFactoryModelServices<F extends PluginFactoryLike> =
55
+ F extends PluginFactoryLike<any, any, any, infer ModelServices, any> ? ModelServices : {};
56
+
57
+ /** Extract the pre-resolved UI services type from a PluginFactoryLike phantom. */
58
+ export type InferFactoryUiServices<F extends PluginFactoryLike> =
59
+ F extends PluginFactoryLike<any, any, any, any, infer UiServices> ? UiServices : {};
60
+
49
61
  /**
50
62
  * Derive a typed PluginHandle from a PluginFactory type.
51
- * Normalizes the brand to only data/params/outputs (strips config) so handles
63
+ * Normalizes the brand to data/params/outputs/services (strips config) so handles
52
64
  * from InferPluginHandles match handles from InferPluginHandle.
53
65
  */
54
66
  export type InferPluginHandle<F extends PluginFactoryLike> = NonNullable<
55
- F extends PluginFactoryLike<infer Data, infer Params, infer Outputs>
56
- ? PluginHandle<PluginFactoryLike<Data, Params, Outputs>>
67
+ F extends PluginFactoryLike<
68
+ infer Data,
69
+ infer Params,
70
+ infer Outputs,
71
+ infer ModelServices,
72
+ infer UiServices
73
+ >
74
+ ? PluginHandle<PluginFactoryLike<Data, Params, Outputs, ModelServices, UiServices>>
57
75
  : PluginHandle
58
76
  >;
59
77