@platforma-open/milaboratories.immune-assay-data.model 1.5.1 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bundle.js CHANGED
@@ -4,6 +4,113 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["block-model"] = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
+ /**
8
+ * BlockStorage - Typed storage abstraction for block persistent data.
9
+ *
10
+ * This module provides:
11
+ * - A typed structure for block storage with versioning and plugin support
12
+ * - Utility functions for manipulating storage
13
+ * - Handler interfaces for model-level customization
14
+ *
15
+ * @module block_storage
16
+ */
17
+ // =============================================================================
18
+ // Core Types
19
+ // =============================================================================
20
+ /**
21
+ * Discriminator key for BlockStorage format detection.
22
+ * This unique hash-based key identifies data as BlockStorage vs legacy formats.
23
+ */
24
+ const BLOCK_STORAGE_KEY = '__pl_a7f3e2b9__';
25
+ /**
26
+ * Current BlockStorage schema version.
27
+ * Increment this when the storage structure itself changes (not block state migrations).
28
+ */
29
+ const BLOCK_STORAGE_SCHEMA_VERSION = 'v1';
30
+ /**
31
+ * Default data version for new blocks without migrations.
32
+ * Unique identifier ensures blocks are created via DataModel API.
33
+ */
34
+ const DATA_MODEL_DEFAULT_VERSION = '__pl_v1_d4e8f2a1__';
35
+ /**
36
+ * Type guard to check if a value is a valid BlockStorage object.
37
+ * Checks for the discriminator key and valid schema version.
38
+ */
39
+ function isBlockStorage(value) {
40
+ if (value === null || typeof value !== 'object')
41
+ return false;
42
+ const obj = value;
43
+ const schemaVersion = obj[BLOCK_STORAGE_KEY];
44
+ // Currently only 'v1' is valid, but this allows future versions
45
+ return schemaVersion === 'v1'; // Add more versions as schema evolves
46
+ }
47
+ // =============================================================================
48
+ // Factory Functions
49
+ // =============================================================================
50
+ /**
51
+ * Creates a BlockStorage with the given initial data
52
+ *
53
+ * @param initialData - The initial data value (defaults to empty object)
54
+ * @param version - The initial data version key (defaults to DATA_MODEL_DEFAULT_VERSION)
55
+ * @returns A new BlockStorage instance with discriminator key
56
+ */
57
+ function createBlockStorage(initialData = {}, version = DATA_MODEL_DEFAULT_VERSION) {
58
+ return {
59
+ [BLOCK_STORAGE_KEY]: BLOCK_STORAGE_SCHEMA_VERSION,
60
+ __dataVersion: version,
61
+ __data: initialData,
62
+ };
63
+ }
64
+ /**
65
+ * Normalizes raw storage data to BlockStorage format.
66
+ * If the input is already a BlockStorage, returns it as-is.
67
+ * If the input is legacy format (raw state), wraps it in BlockStorage structure.
68
+ *
69
+ * @param raw - Raw storage data (may be legacy format or BlockStorage)
70
+ * @returns Normalized BlockStorage
71
+ */
72
+ function normalizeBlockStorage(raw) {
73
+ if (isBlockStorage(raw)) {
74
+ const storage = raw;
75
+ return {
76
+ ...storage,
77
+ // Fix for early released version where __dataVersion was a number
78
+ __dataVersion: typeof storage.__dataVersion === 'number'
79
+ ? DATA_MODEL_DEFAULT_VERSION
80
+ : storage.__dataVersion,
81
+ };
82
+ }
83
+ // Legacy format: raw is the state directly
84
+ return createBlockStorage(raw);
85
+ }
86
+ // =============================================================================
87
+ // Data Access & Update Functions
88
+ // =============================================================================
89
+ /**
90
+ * Gets the data from BlockStorage
91
+ *
92
+ * @param storage - The BlockStorage instance
93
+ * @returns The data value
94
+ */
95
+ function getStorageData(storage) {
96
+ return storage.__data;
97
+ }
98
+ /**
99
+ * Updates the data in BlockStorage (immutable)
100
+ *
101
+ * @param storage - The current BlockStorage
102
+ * @param payload - The update payload with operation and value
103
+ * @returns A new BlockStorage with updated data
104
+ */
105
+ function updateStorageData(storage, payload) {
106
+ switch (payload.operation) {
107
+ case 'update-data':
108
+ return { ...storage, __data: payload.value };
109
+ default:
110
+ throw new Error(`Unknown storage operation: ${payload.operation}`);
111
+ }
112
+ }
113
+
7
114
  //
8
115
  // Helpers
9
116
  //
@@ -4625,6 +4732,9 @@
4625
4732
  var canonicalizeExports = requireCanonicalize();
4626
4733
  var canonicalize = /*@__PURE__*/getDefaultExportFromCjs(canonicalizeExports);
4627
4734
 
4735
+ function stringifyJson(value) {
4736
+ return JSON.stringify(value);
4737
+ }
4628
4738
  function canonicalizeJson(value) {
4629
4739
  return canonicalize(value);
4630
4740
  }
@@ -4696,6 +4806,9 @@
4696
4806
  Visibility: 'pl7.app/table/visibility',
4697
4807
  },
4698
4808
  Trace: 'pl7.app/trace',
4809
+ VDJ: {
4810
+ IsAssemblingFeature: 'pl7.app/vdj/isAssemblingFeature',
4811
+ },
4699
4812
  };
4700
4813
  const ValueTypeSchema = z.enum(['Int', 'Long', 'Float', 'Double', 'String']);
4701
4814
  const AnnotationJson = {
@@ -4725,6 +4838,7 @@
4725
4838
  [Annotation.Sequence.IsAnnotation]: z.boolean(),
4726
4839
  [Annotation.Table.OrderPriority]: z.number(),
4727
4840
  [Annotation.Trace]: z.record(z.string(), z.unknown()),
4841
+ [Annotation.VDJ.IsAssemblingFeature]: z.boolean(),
4728
4842
  };
4729
4843
  /// Helper function for reading plain annotation values
4730
4844
  function readAnnotation(spec, key) {
@@ -6100,10 +6214,15 @@
6100
6214
  }
6101
6215
  return result;
6102
6216
  };
6103
- // Checks if a result has all unique labels
6104
- const hasUniqueLabels = (result) => result !== undefined && new Set(result.map((c) => c.label)).size === values.length;
6217
+ const countUniqueLabels = (result) => result === undefined ? 0 : new Set(result.map((c) => c.label)).size;
6105
6218
  // Post-processing: try removing types one by one (lowest importance first) to minimize the label set
6219
+ // Accepts removal if it doesn't decrease the number of unique labels (cardinality)
6106
6220
  const minimizeTypeSet = (typeSet) => {
6221
+ const initialResult = calculate(typeSet);
6222
+ if (initialResult === undefined) {
6223
+ return typeSet;
6224
+ }
6225
+ const currentCardinality = countUniqueLabels(initialResult);
6107
6226
  // Get types sorted by importance ascending (lowest first), excluding forced elements
6108
6227
  const removableSorted = [...typeSet]
6109
6228
  .filter((t) => !forceTraceElements?.has(t.split('@')[0])
@@ -6113,7 +6232,7 @@
6113
6232
  const reducedSet = new Set(typeSet);
6114
6233
  reducedSet.delete(typeToRemove);
6115
6234
  const candidateResult = calculate(reducedSet);
6116
- if (hasUniqueLabels(candidateResult)) {
6235
+ if (candidateResult !== undefined && countUniqueLabels(candidateResult) >= currentCardinality) {
6117
6236
  typeSet.delete(typeToRemove);
6118
6237
  }
6119
6238
  }
@@ -6144,7 +6263,7 @@
6144
6263
  if (additionalType >= 0)
6145
6264
  currentSet.add(mainTypes[additionalType]);
6146
6265
  const candidateResult = calculate(currentSet);
6147
- if (hasUniqueLabels(candidateResult)) {
6266
+ if (candidateResult !== undefined && countUniqueLabels(candidateResult) === values.length) {
6148
6267
  minimizeTypeSet(currentSet);
6149
6268
  return calculate(currentSet);
6150
6269
  }
@@ -7260,28 +7379,19 @@
7260
7379
  }
7261
7380
  }
7262
7381
  /** Main entry point to the API available within model lambdas (like outputs, sections, etc..) */
7263
- class RenderCtx {
7382
+ class RenderCtxBase {
7264
7383
  ctx;
7265
7384
  constructor() {
7266
7385
  this.ctx = getCfgRenderCtx();
7267
7386
  }
7268
- _argsCache;
7269
- get args() {
7270
- if (this._argsCache === undefined) {
7271
- const raw = this.ctx.args;
7272
- const value = typeof raw === 'function' ? raw() : raw;
7273
- this._argsCache = { v: JSON.parse(value) };
7274
- }
7275
- return this._argsCache.v;
7276
- }
7277
- _uiStateCache;
7278
- get uiState() {
7279
- if (this._uiStateCache === undefined) {
7280
- const raw = this.ctx.uiState;
7387
+ _dataCache;
7388
+ get data() {
7389
+ if (this._dataCache === undefined) {
7390
+ const raw = this.ctx.data;
7281
7391
  const value = typeof raw === 'function' ? raw() : raw;
7282
- this._uiStateCache = { v: value ? JSON.parse(value) : {} };
7392
+ this._dataCache = { v: value ? JSON.parse(value) : {} };
7283
7393
  }
7284
- return this._uiStateCache.v;
7394
+ return this._dataCache.v;
7285
7395
  }
7286
7396
  // lazy rendering because this feature is rarely used
7287
7397
  _activeArgsCache;
@@ -7392,8 +7502,29 @@
7392
7502
  this.ctx.logError(msg);
7393
7503
  }
7394
7504
  }
7505
+ /** Render context for legacy v1/v2 blocks - provides backward compatibility */
7506
+ class RenderCtxLegacy extends RenderCtxBase {
7507
+ _argsCache;
7508
+ get args() {
7509
+ if (this._argsCache === undefined) {
7510
+ const raw = this.ctx.args;
7511
+ const value = typeof raw === 'function' ? raw() : raw;
7512
+ this._argsCache = { v: JSON.parse(value) };
7513
+ }
7514
+ return this._argsCache.v;
7515
+ }
7516
+ _uiStateCache;
7517
+ get uiState() {
7518
+ if (this._uiStateCache === undefined) {
7519
+ const raw = this.ctx.uiState;
7520
+ const value = typeof raw === 'function' ? raw() : raw;
7521
+ this._uiStateCache = { v: value ? JSON.parse(value) : {} };
7522
+ }
7523
+ return this._uiStateCache.v;
7524
+ }
7525
+ }
7395
7526
 
7396
- var version = "1.51.6";
7527
+ var version = "1.53.3";
7397
7528
 
7398
7529
  const PlatformaSDKVersion = version;
7399
7530
 
@@ -7437,7 +7568,7 @@
7437
7568
  output(key, cfgOrRf, flags = {}) {
7438
7569
  if (typeof cfgOrRf === 'function') {
7439
7570
  const handle = `output#${key}`;
7440
- tryRegisterCallback(handle, () => cfgOrRf(new RenderCtx()));
7571
+ tryRegisterCallback(handle, () => cfgOrRf(new RenderCtxLegacy()));
7441
7572
  return new BlockModel({
7442
7573
  ...this.config,
7443
7574
  outputs: {
@@ -7474,7 +7605,7 @@
7474
7605
  }
7475
7606
  argsValid(cfgOrRf) {
7476
7607
  if (typeof cfgOrRf === 'function') {
7477
- tryRegisterCallback('inputsValid', () => cfgOrRf(new RenderCtx()));
7608
+ tryRegisterCallback('inputsValid', () => cfgOrRf(new RenderCtxLegacy()));
7478
7609
  return new BlockModel({
7479
7610
  ...this.config,
7480
7611
  inputsValid: {
@@ -7495,7 +7626,7 @@
7495
7626
  return this.sections(getImmediate(arrOrCfgOrRf));
7496
7627
  }
7497
7628
  else if (typeof arrOrCfgOrRf === 'function') {
7498
- tryRegisterCallback('sections', () => arrOrCfgOrRf(new RenderCtx()));
7629
+ tryRegisterCallback('sections', () => arrOrCfgOrRf(new RenderCtxLegacy()));
7499
7630
  return new BlockModel({
7500
7631
  ...this.config,
7501
7632
  sections: {
@@ -7513,7 +7644,7 @@
7513
7644
  }
7514
7645
  /** Sets a rendering function to derive block title, shown for the block in the left blocks-overview panel. */
7515
7646
  title(rf) {
7516
- tryRegisterCallback('title', () => rf(new RenderCtx()));
7647
+ tryRegisterCallback('title', () => rf(new RenderCtxLegacy()));
7517
7648
  return new BlockModel({
7518
7649
  ...this.config,
7519
7650
  title: {
@@ -7523,7 +7654,7 @@
7523
7654
  });
7524
7655
  }
7525
7656
  subtitle(rf) {
7526
- tryRegisterCallback('subtitle', () => rf(new RenderCtx()));
7657
+ tryRegisterCallback('subtitle', () => rf(new RenderCtxLegacy()));
7527
7658
  return new BlockModel({
7528
7659
  ...this.config,
7529
7660
  subtitle: {
@@ -7533,7 +7664,7 @@
7533
7664
  });
7534
7665
  }
7535
7666
  tags(rf) {
7536
- tryRegisterCallback('tags', () => rf(new RenderCtx()));
7667
+ tryRegisterCallback('tags', () => rf(new RenderCtxLegacy()));
7537
7668
  return new BlockModel({
7538
7669
  ...this.config,
7539
7670
  tags: {
@@ -7600,7 +7731,10 @@
7600
7731
  if (this.config.initialArgs === undefined)
7601
7732
  throw new Error('Initial arguments not set.');
7602
7733
  const config = {
7734
+ v4: undefined,
7603
7735
  v3: {
7736
+ configVersion: 3,
7737
+ modelAPIVersion: 1,
7604
7738
  sdkVersion: PlatformaSDKVersion,
7605
7739
  renderingMode: this.config.renderingMode,
7606
7740
  initialArgs: this.config.initialArgs,
@@ -7640,10 +7774,473 @@
7640
7774
  }
7641
7775
  }
7642
7776
 
7777
+ /**
7778
+ * BlockStorage VM Integration - Internal module for VM-based storage operations.
7779
+ *
7780
+ * This module auto-registers internal callbacks that the middle layer can invoke
7781
+ * to perform storage transformations. Block developers never interact with these
7782
+ * directly - they only see `state`.
7783
+ *
7784
+ * Registered callbacks (all prefixed with `__pl_` for internal SDK use):
7785
+ * - `__pl_storage_applyUpdate`: (currentStorageJson, payload) => updatedStorageJson
7786
+ * - `__pl_storage_debugView`: (rawStorage) => JSON string with storage debug view
7787
+ * - `__pl_storage_migrate`: (currentStorageJson) => MigrationResult
7788
+ * - `__pl_args_derive`: (storageJson) => ArgsDeriveResult
7789
+ * - `__pl_prerunArgs_derive`: (storageJson) => ArgsDeriveResult
7790
+ *
7791
+ * Callbacks registered by DataModel.registerCallbacks():
7792
+ * - `__pl_data_initial`: () => initial data
7793
+ * - `__pl_data_upgrade`: (versioned) => DataMigrationResult
7794
+ * - `__pl_storage_initial`: () => initial BlockStorage as JSON string
7795
+ *
7796
+ * @module block_storage_vm
7797
+ * @internal
7798
+ */
7799
+ /**
7800
+ * Normalizes raw storage data and extracts state.
7801
+ * Handles all formats:
7802
+ * - New BlockStorage format (has discriminator)
7803
+ * - Legacy V1/V2 format ({ args, uiState })
7804
+ * - Raw V3 state (any other format)
7805
+ *
7806
+ * @param rawStorage - Raw data from blockStorage field (may be JSON string or object)
7807
+ * @returns Object with normalized storage and extracted state
7808
+ */
7809
+ function normalizeStorage(rawStorage) {
7810
+ // Handle undefined/null
7811
+ if (rawStorage === undefined || rawStorage === null) {
7812
+ const storage = createBlockStorage({});
7813
+ return { storage, data: {} };
7814
+ }
7815
+ // Parse JSON string if needed
7816
+ let parsed = rawStorage;
7817
+ if (typeof rawStorage === 'string') {
7818
+ try {
7819
+ parsed = JSON.parse(rawStorage);
7820
+ }
7821
+ catch {
7822
+ // If parsing fails, treat string as the data
7823
+ const storage = createBlockStorage(rawStorage);
7824
+ return { storage, data: rawStorage };
7825
+ }
7826
+ }
7827
+ // Check for BlockStorage format (has discriminator)
7828
+ if (isBlockStorage(parsed)) {
7829
+ const storage = normalizeBlockStorage(parsed);
7830
+ return { storage, data: getStorageData(storage) };
7831
+ }
7832
+ // Check for legacy V1/V2 format: { args, uiState }
7833
+ if (isLegacyModelV1ApiFormat(parsed)) {
7834
+ // For legacy format, the whole object IS the data
7835
+ const storage = createBlockStorage(parsed);
7836
+ return { storage, data: parsed };
7837
+ }
7838
+ // Raw V3 data - wrap it
7839
+ const storage = createBlockStorage(parsed);
7840
+ return { storage, data: parsed };
7841
+ }
7842
+ /**
7843
+ * Applies a state update to existing storage.
7844
+ * Used when setData is called from the frontend.
7845
+ *
7846
+ * @param currentStorageJson - Current storage as JSON string (must be defined)
7847
+ * @param newData - New data from application
7848
+ * @returns Updated storage as JSON string
7849
+ */
7850
+ function applyStorageUpdate(currentStorageJson, payload) {
7851
+ const { storage: currentStorage } = normalizeStorage(currentStorageJson);
7852
+ // Update data while preserving other storage fields (version, plugins)
7853
+ const updatedStorage = updateStorageData(currentStorage, payload);
7854
+ return JSON.stringify(updatedStorage);
7855
+ }
7856
+ /**
7857
+ * Checks if data is in legacy Model API v1 format.
7858
+ * Legacy format has { args, uiState? } at top level without the BlockStorage discriminator.
7859
+ */
7860
+ function isLegacyModelV1ApiFormat(data) {
7861
+ if (data === null || typeof data !== 'object')
7862
+ return false;
7863
+ if (isBlockStorage(data))
7864
+ return false;
7865
+ const obj = data;
7866
+ return 'args' in obj;
7867
+ }
7868
+ // =============================================================================
7869
+ // Auto-register internal callbacks when module is loaded in VM
7870
+ // =============================================================================
7871
+ // Register apply update callback (requires existing storage)
7872
+ tryRegisterCallback('__pl_storage_applyUpdate', (currentStorageJson, payload) => {
7873
+ return applyStorageUpdate(currentStorageJson, payload);
7874
+ });
7875
+ /**
7876
+ * Gets storage debug view from raw storage data.
7877
+ * Returns structured debug info about the storage state.
7878
+ *
7879
+ * @param rawStorage - Raw data from blockStorage field (may be JSON string or object)
7880
+ * @returns JSON string with storage debug view
7881
+ */
7882
+ function getStorageDebugView(rawStorage) {
7883
+ const { storage } = normalizeStorage(rawStorage);
7884
+ const debugView = {
7885
+ dataVersion: storage.__dataVersion,
7886
+ data: storage.__data,
7887
+ };
7888
+ return stringifyJson(debugView);
7889
+ }
7890
+ // Register debug view callback
7891
+ tryRegisterCallback('__pl_storage_debugView', (rawStorage) => {
7892
+ return getStorageDebugView(rawStorage);
7893
+ });
7894
+ /**
7895
+ * Runs storage migration using the DataModel's migrate callback.
7896
+ * This is the main entry point for the middle layer to trigger migrations.
7897
+ *
7898
+ * Uses the '__pl_data_upgrade' callback registered by DataModel.registerCallbacks() which:
7899
+ * - Handles all migration logic internally
7900
+ * - Returns { version, data, warning? } - warning present if reset to initial data
7901
+ *
7902
+ * @param currentStorageJson - Current storage as JSON string (or undefined)
7903
+ * @returns MigrationResult
7904
+ */
7905
+ function migrateStorage(currentStorageJson) {
7906
+ // Get the callback registry context
7907
+ const ctx = tryGetCfgRenderCtx();
7908
+ if (ctx === undefined) {
7909
+ return { error: 'Not in config rendering context' };
7910
+ }
7911
+ // Normalize storage to get current data and version
7912
+ const { storage: currentStorage, data: currentData } = normalizeStorage(currentStorageJson);
7913
+ const currentVersion = currentStorage.__dataVersion;
7914
+ // Helper to create storage with given data and version
7915
+ const createStorageJson = (data, version) => {
7916
+ return JSON.stringify({
7917
+ ...currentStorage,
7918
+ __dataVersion: version,
7919
+ __data: data,
7920
+ });
7921
+ };
7922
+ // Get the migrate callback (registered by DataModel.registerCallbacks())
7923
+ const migrateCallback = ctx.callbackRegistry['__pl_data_upgrade'];
7924
+ if (typeof migrateCallback !== 'function') {
7925
+ return { error: '__pl_data_upgrade callback not found (DataModel not registered)' };
7926
+ }
7927
+ // Call the migrator's migrate function
7928
+ let result;
7929
+ try {
7930
+ result = migrateCallback({ version: currentVersion, data: currentData });
7931
+ }
7932
+ catch (e) {
7933
+ const errorMsg = e instanceof Error ? e.message : String(e);
7934
+ return { error: `migrate() threw: ${errorMsg}` };
7935
+ }
7936
+ // Build info message
7937
+ const info = result.version === currentVersion
7938
+ ? `No migration needed (${currentVersion})`
7939
+ : result.warning
7940
+ ? `Reset to initial data (${result.version})`
7941
+ : `Migrated ${currentVersion}→${result.version}`;
7942
+ return {
7943
+ newStorageJson: createStorageJson(result.data, result.version),
7944
+ info,
7945
+ warn: result.warning,
7946
+ };
7947
+ }
7948
+ // Register migrate callback
7949
+ tryRegisterCallback('__pl_storage_migrate', (currentStorageJson) => {
7950
+ return migrateStorage(currentStorageJson);
7951
+ });
7952
+ /**
7953
+ * Derives args from storage using the registered 'args' callback.
7954
+ * This extracts data from storage and passes it to the block's args() function.
7955
+ *
7956
+ * @param storageJson - Storage as JSON string
7957
+ * @returns ArgsDeriveResult with derived args or error
7958
+ */
7959
+ function deriveArgsFromStorage(storageJson) {
7960
+ const ctx = tryGetCfgRenderCtx();
7961
+ if (ctx === undefined) {
7962
+ return { error: 'Not in config rendering context' };
7963
+ }
7964
+ // Extract data from storage
7965
+ const { data } = normalizeStorage(storageJson);
7966
+ // Get the args callback (registered by BlockModelV3.args())
7967
+ const argsCallback = ctx.callbackRegistry['args'];
7968
+ if (typeof argsCallback !== 'function') {
7969
+ return { error: 'args callback not found' };
7970
+ }
7971
+ // Call the args callback with extracted data
7972
+ try {
7973
+ const result = argsCallback(data);
7974
+ return { value: result };
7975
+ }
7976
+ catch (e) {
7977
+ const errorMsg = e instanceof Error ? e.message : String(e);
7978
+ return { error: `args() threw: ${errorMsg}` };
7979
+ }
7980
+ }
7981
+ // Register args derivation callback
7982
+ tryRegisterCallback('__pl_args_derive', (storageJson) => {
7983
+ return deriveArgsFromStorage(storageJson);
7984
+ });
7985
+ /**
7986
+ * Derives prerunArgs from storage using the registered 'prerunArgs' callback.
7987
+ * Falls back to 'args' callback if 'prerunArgs' is not defined.
7988
+ *
7989
+ * @param storageJson - Storage as JSON string
7990
+ * @returns ArgsDeriveResult with derived prerunArgs or error
7991
+ */
7992
+ function derivePrerunArgsFromStorage(storageJson) {
7993
+ const ctx = tryGetCfgRenderCtx();
7994
+ if (ctx === undefined) {
7995
+ return { error: 'Not in config rendering context' };
7996
+ }
7997
+ // Extract data from storage
7998
+ const { data } = normalizeStorage(storageJson);
7999
+ // Try prerunArgs callback first
8000
+ const prerunArgsCallback = ctx.callbackRegistry['prerunArgs'];
8001
+ if (typeof prerunArgsCallback === 'function') {
8002
+ try {
8003
+ const result = prerunArgsCallback(data);
8004
+ return { value: result };
8005
+ }
8006
+ catch (e) {
8007
+ const errorMsg = e instanceof Error ? e.message : String(e);
8008
+ return { error: `prerunArgs() threw: ${errorMsg}` };
8009
+ }
8010
+ }
8011
+ // Fall back to args callback
8012
+ const argsCallback = ctx.callbackRegistry['args'];
8013
+ if (typeof argsCallback !== 'function') {
8014
+ return { error: 'args callback not found (fallback from missing prerunArgs)' };
8015
+ }
8016
+ try {
8017
+ const result = argsCallback(data);
8018
+ return { value: result };
8019
+ }
8020
+ catch (e) {
8021
+ const errorMsg = e instanceof Error ? e.message : String(e);
8022
+ return { error: `args() threw (fallback): ${errorMsg}` };
8023
+ }
8024
+ }
8025
+ // Register prerunArgs derivation callback
8026
+ tryRegisterCallback('__pl_prerunArgs_derive', (storageJson) => {
8027
+ return derivePrerunArgsFromStorage(storageJson);
8028
+ });
8029
+
7643
8030
  function identity(x) {
7644
8031
  return x;
7645
8032
  }
7646
8033
 
8034
+ function getAllRelatedColumns(ctx, predicate) {
8035
+ // if current block doesn't produce own columns then use all columns from result pool
8036
+ const columns = new PColumnCollection();
8037
+ columns.addColumnProvider(ctx.resultPool);
8038
+ const allColumns = columns.getUniversalEntries(predicate, { dontWaitAllData: true, overrideLabelAnnotation: false }) ?? [];
8039
+ const allAxes = new Map(allColumns
8040
+ .flatMap((column) => getNormalizedAxesList(column.spec.axesSpec))
8041
+ .map((axisSpec) => {
8042
+ const axisId = getAxisId(axisSpec);
8043
+ return [canonicalizeJson(axisId), axisSpec];
8044
+ }));
8045
+ // additional columns are duplicates with extra fields in domains for compatibility if there are ones with partial match
8046
+ const extendedColumns = enrichCompatible(allAxes, allColumns);
8047
+ return extendedColumns;
8048
+ }
8049
+ function getRelatedColumns(ctx, { columns: rootColumns, predicate }) {
8050
+ // if current block has its own columns then take from result pool only compatible with them
8051
+ const columns = new PColumnCollection();
8052
+ columns.addColumnProvider(ctx.resultPool);
8053
+ columns.addColumns(rootColumns);
8054
+ // all possible axes from block columns
8055
+ const blockAxes = new Map();
8056
+ // axes from block columns and compatible result pool columns
8057
+ const allAxes = new Map();
8058
+ for (const c of rootColumns) {
8059
+ for (const spec of getNormalizedAxesList(c.spec.axesSpec)) {
8060
+ const aid = getAxisId(spec);
8061
+ blockAxes.set(canonicalizeJson(aid), spec);
8062
+ allAxes.set(canonicalizeJson(aid), spec);
8063
+ }
8064
+ }
8065
+ // all linker columns always go to pFrame - even it's impossible to use some of them they all are hidden
8066
+ const linkerColumns = columns.getUniversalEntries((spec) => predicate(spec) && isLinkerColumn(spec)) ?? [];
8067
+ const availableWithLinkersAxes = getAvailableWithLinkersAxes(linkerColumns, blockAxes);
8068
+ // all possible axes from connected linkers
8069
+ for (const item of availableWithLinkersAxes) {
8070
+ blockAxes.set(...item);
8071
+ allAxes.set(...item);
8072
+ }
8073
+ const blockAxesArr = Array.from(blockAxes.values());
8074
+ // all compatible with block columns but without label columns
8075
+ let compatibleWithoutLabels = (columns.getUniversalEntries((spec) => predicate(spec) && spec.axesSpec.some((axisSpec) => {
8076
+ const axisId = getAxisId(axisSpec);
8077
+ return blockAxesArr.some((selectorAxisSpec) => matchAxisId(getAxisId(selectorAxisSpec), axisId));
8078
+ }), { dontWaitAllData: true, overrideLabelAnnotation: false }) ?? []).filter((column) => !isLabelColumn(column.spec));
8079
+ // extend axes set for label columns request
8080
+ for (const c of compatibleWithoutLabels) {
8081
+ for (const spec of getNormalizedAxesList(c.spec.axesSpec)) {
8082
+ const aid = getAxisId(spec);
8083
+ allAxes.set(canonicalizeJson(aid), spec);
8084
+ }
8085
+ }
8086
+ const allAxesArr = Array.from(allAxes.values());
8087
+ // extend allowed columns - add columns thad doesn't have axes from block, but have all axes in 'allAxes' list (that means all axes from linkers or from 'hanging' of other selected columns)
8088
+ compatibleWithoutLabels = (columns.getUniversalEntries((spec) => predicate(spec) && spec.axesSpec.every((axisSpec) => {
8089
+ const axisId = getAxisId(axisSpec);
8090
+ return allAxesArr.some((selectorAxisSpec) => matchAxisId(getAxisId(selectorAxisSpec), axisId));
8091
+ }), { dontWaitAllData: true, overrideLabelAnnotation: false }) ?? []).filter((column) => !isLabelColumn(column.spec));
8092
+ // label columns must be compatible with full set of axes - block axes and axes from compatible columns from result pool
8093
+ const compatibleLabels = (columns.getUniversalEntries((spec) => predicate(spec) && spec.axesSpec.some((axisSpec) => {
8094
+ const axisId = getAxisId(axisSpec);
8095
+ return allAxesArr.some((selectorAxisSpec) => matchAxisId(getAxisId(selectorAxisSpec), axisId));
8096
+ }), { dontWaitAllData: true, overrideLabelAnnotation: false }) ?? []).filter((column) => isLabelColumn(column.spec));
8097
+ const compatible = [...compatibleWithoutLabels, ...compatibleLabels];
8098
+ // additional columns are duplicates with extra fields in domains for compatibility if there are ones with partial match
8099
+ const extendedColumns = enrichCompatible(blockAxes, compatible);
8100
+ return extendedColumns;
8101
+ }
8102
+
8103
+ /** Create id for column copy with added keys in axes domains */
8104
+ const colId = (id, domains) => {
8105
+ let wid = id.toString();
8106
+ domains?.forEach((domain) => {
8107
+ if (domain) {
8108
+ for (const [k, v] of Object.entries(domain)) {
8109
+ wid += k;
8110
+ wid += v;
8111
+ }
8112
+ }
8113
+ });
8114
+ return wid;
8115
+ };
8116
+ /** All combinations with 1 key from each list */
8117
+ function getKeysCombinations(idsLists) {
8118
+ if (!idsLists.length) {
8119
+ return [];
8120
+ }
8121
+ let result = [[]];
8122
+ idsLists.forEach((list) => {
8123
+ const nextResult = [];
8124
+ list.forEach((key) => {
8125
+ nextResult.push(...result.map((resultItem) => [...resultItem, key]));
8126
+ });
8127
+ result = nextResult;
8128
+ });
8129
+ return result;
8130
+ }
8131
+ function isHiddenFromGraphColumn(column) {
8132
+ return !!readAnnotationJson(column, Annotation.HideDataFromGraphs);
8133
+ }
8134
+ function isHiddenFromUIColumn(column) {
8135
+ return !!readAnnotationJson(column, Annotation.HideDataFromUi);
8136
+ }
8137
+ function getAvailableWithLinkersAxes(linkerColumns, blockAxes) {
8138
+ const linkerMap = LinkerMap.fromColumns(linkerColumns.map(getColumnIdAndSpec));
8139
+ const availableAxes = linkerMap.getReachableByLinkersAxesFromAxesNormalized([...blockAxes.values()], (linkerKeyId, sourceAxisId) => matchAxisId(sourceAxisId, linkerKeyId));
8140
+ return new Map(availableAxes.map((axisSpec) => {
8141
+ const id = getAxisId(axisSpec);
8142
+ return [canonicalizeJson(id), axisSpec];
8143
+ }));
8144
+ }
8145
+ /** Add columns with fully compatible axes created from partial compatible ones */
8146
+ function enrichCompatible(blockAxes, columns) {
8147
+ return columns.flatMap((column) => getAdditionalColumnsForColumn(blockAxes, column));
8148
+ }
8149
+ function getAdditionalColumnsForColumn(blockAxes, column) {
8150
+ const columnAxesIds = column.spec.axesSpec.map(getAxisId);
8151
+ if (columnAxesIds.every((id) => blockAxes.has(canonicalizeJson(id)))) {
8152
+ return [column]; // the column is compatible with its own domains without modifications
8153
+ }
8154
+ // options with different possible domains for every axis of secondary column
8155
+ const secondaryIdsOptions = columnAxesIds.map((id) => {
8156
+ const result = [];
8157
+ for (const [_, mainId] of blockAxes) {
8158
+ if (matchAxisId(mainId, id) && !matchAxisId(id, mainId)) {
8159
+ result.push(mainId);
8160
+ }
8161
+ }
8162
+ return result;
8163
+ });
8164
+ // all possible combinations of axes with added domains
8165
+ const secondaryIdsVariants = getKeysCombinations(secondaryIdsOptions);
8166
+ // sets of added to column domain fields
8167
+ const allAddedDomainValues = new Set();
8168
+ const addedNotToAllVariantsDomainValues = new Set();
8169
+ const addedByVariantsDomainValues = secondaryIdsVariants.map((idsList) => {
8170
+ const addedSet = new Set();
8171
+ idsList.map((axisId, idx) => {
8172
+ const d1 = column.spec.axesSpec[idx].domain;
8173
+ const d2 = axisId.domain;
8174
+ Object.entries(d2 ?? {}).forEach(([key, value]) => {
8175
+ if (d1?.[key] === undefined) {
8176
+ const item = JSON.stringify([key, value]);
8177
+ addedSet.add(item);
8178
+ allAddedDomainValues.add(item);
8179
+ }
8180
+ });
8181
+ return ({
8182
+ ...axisId,
8183
+ annotations: column.spec.axesSpec[idx].annotations,
8184
+ });
8185
+ });
8186
+ return addedSet;
8187
+ });
8188
+ [...allAddedDomainValues].forEach((addedPart) => {
8189
+ if (addedByVariantsDomainValues.some((s) => !s.has(addedPart))) {
8190
+ addedNotToAllVariantsDomainValues.add(addedPart);
8191
+ }
8192
+ });
8193
+ const additionalColumns = secondaryIdsVariants.map((idsList, idx) => {
8194
+ const id = colId(column.id, idsList.map((id) => id.domain));
8195
+ const label = readAnnotation(column.spec, Annotation.Label) ?? '';
8196
+ const labelDomainPart = ([...addedByVariantsDomainValues[idx]])
8197
+ .filter((str) => addedNotToAllVariantsDomainValues.has(str))
8198
+ .sort()
8199
+ .map((v) => JSON.parse(v)?.[1]) // use in labels only domain values, but sort them by key to save the same order in all column variants
8200
+ .join(' / ');
8201
+ const annotations = {
8202
+ ...column.spec.annotations,
8203
+ [Annotation.Graph.IsVirtual]: stringifyJson(true),
8204
+ };
8205
+ if (label || labelDomainPart) {
8206
+ annotations[Annotation.Label] = label && labelDomainPart ? label + ' / ' + labelDomainPart : label + labelDomainPart;
8207
+ }
8208
+ return {
8209
+ ...column,
8210
+ id: id,
8211
+ spec: {
8212
+ ...column.spec,
8213
+ axesSpec: idsList.map((axisId, idx) => ({
8214
+ ...axisId,
8215
+ annotations: column.spec.axesSpec[idx].annotations,
8216
+ })),
8217
+ annotations,
8218
+ },
8219
+ };
8220
+ });
8221
+ return [column, ...additionalColumns];
8222
+ }
8223
+ /**
8224
+ The aim of createPFrameForGraphs: to create pframe with block’s columns and all compatible columns from result pool
8225
+ (including linker columns and all label columns).
8226
+ Block’s columns are added to pframe as is.
8227
+ Other columns are added basing on set of axes of block’s columns, considering available with linker columns.
8228
+ Compatible columns must have at least one axis from block’s axes set. This axis of the compatible column from
8229
+ result pool must satisfy matchAxisId (it can have less domain keys than in block’s axis, but without conflicting values
8230
+ among existing ones).
8231
+ In requests to pframe (calculateTableData) columns must have strictly the same axes. For compatibility in case
8232
+ of partially matched axis we add to pframe a copy of this column with modified axis (with filled missed domains)
8233
+ and modified label (with added domain values in case if more than one copy with different domains exist).
8234
+ */
8235
+ function createPFrameForGraphs(ctx, blockColumns) {
8236
+ const suitableSpec = (spec) => !isHiddenFromUIColumn(spec) && !isHiddenFromGraphColumn(spec);
8237
+ // if current block doesn't produce own columns then use all columns from result pool
8238
+ if (!blockColumns) {
8239
+ return ctx.createPFrame(getAllRelatedColumns(ctx, suitableSpec));
8240
+ }
8241
+ return ctx.createPFrame(getRelatedColumns(ctx, { columns: blockColumns, predicate: suitableSpec }));
8242
+ }
8243
+
7647
8244
  function makeDefaultPTableParams() {
7648
8245
  return {
7649
8246
  sourceId: null,
@@ -7990,9 +8587,49 @@
7990
8587
  errors: z.lazy(() => ErrorShape.array()).optional(),
7991
8588
  });
7992
8589
 
8590
+ function getDefaultBlockLabel(data) {
8591
+ const parts = [];
8592
+ // Add file name if available
8593
+ if (data.fileName) {
8594
+ parts.push(data.fileName);
8595
+ }
8596
+ // Add similarity type label
8597
+ const similarityLabel = data.similarityType === 'alignment-score' ? 'BLOSUM' : 'Exact Match';
8598
+ if (similarityLabel) {
8599
+ parts.push(similarityLabel);
8600
+ }
8601
+ // Add identity threshold
8602
+ parts.push(`ident:${data.identity}`);
8603
+ // Add coverage threshold
8604
+ parts.push(`cov:${data.coverageThreshold}`);
8605
+ return parts.filter(Boolean).join(', ');
8606
+ }
8607
+
8608
+ function getColumns(ctx) {
8609
+ const anchor = ctx.args.datasetRef;
8610
+ if (anchor === undefined)
8611
+ return undefined;
8612
+ const anchorSpec = ctx.resultPool.getPColumnSpecByRef(anchor);
8613
+ if (anchorSpec === undefined)
8614
+ return undefined;
8615
+ // all clone properties
8616
+ const props = (ctx.resultPool.getAnchoredPColumns({ main: anchor }, [
8617
+ {
8618
+ axes: [{ anchor: 'main', idx: 1 }],
8619
+ },
8620
+ ]) ?? [])
8621
+ .filter((p) => p.spec.annotations?.['pl7.app/sequence/isAnnotation'] !== 'true');
8622
+ return {
8623
+ props: props,
8624
+ };
8625
+ }
7993
8626
  const model = BlockModel.create()
7994
8627
  .withArgs({
7995
- defaultBlockLabel: '',
8628
+ defaultBlockLabel: getDefaultBlockLabel({
8629
+ similarityType: 'alignment-score',
8630
+ identity: 0.9,
8631
+ coverageThreshold: 0.95,
8632
+ }),
7996
8633
  customBlockLabel: '',
7997
8634
  settings: {
7998
8635
  coverageThreshold: 0.95, // default value matching MMseqs2 default
@@ -8003,6 +8640,7 @@
8003
8640
  })
8004
8641
  .withUiState({
8005
8642
  tableState: createPlDataTableStateV2(),
8643
+ alignmentModel: {},
8006
8644
  })
8007
8645
  .argsValid((ctx) => ctx.args.datasetRef !== undefined
8008
8646
  && ctx.args.fileHandle !== undefined
@@ -8067,6 +8705,42 @@
8067
8705
  if (cols === undefined)
8068
8706
  return undefined;
8069
8707
  return createPlDataTableV2(ctx, cols, ctx.uiState.tableState);
8708
+ })
8709
+ .output('pf', (ctx) => {
8710
+ if (ctx.outputs?.resolve('emptyResults')?.getDataAsJson()) {
8711
+ return undefined;
8712
+ }
8713
+ const cols = ctx.outputs?.resolve('table')?.getPColumns();
8714
+ if (cols === undefined)
8715
+ return undefined;
8716
+ return createPFrameForGraphs(ctx, cols);
8717
+ })
8718
+ .output('assaySequenceSpec', (ctx) => {
8719
+ if (ctx.outputs?.resolve('emptyResults')?.getDataAsJson()) {
8720
+ return undefined;
8721
+ }
8722
+ const cols = ctx.outputs?.resolve('table')?.getPColumns();
8723
+ if (cols === undefined)
8724
+ return undefined;
8725
+ // Return only sequence column
8726
+ return cols.find((c) => c.spec.name === 'pl7.app/vdj/sequence'
8727
+ && c.spec.axesSpec[0].name === 'pl7.app/vdj/assay/sequenceId')?.spec;
8728
+ })
8729
+ .output('msaPf', (ctx) => {
8730
+ if (ctx.outputs?.resolve('emptyResults')?.getDataAsJson()) {
8731
+ return undefined;
8732
+ }
8733
+ const cols = ctx.outputs?.resolve('table')?.getPColumns();
8734
+ if (cols === undefined)
8735
+ return undefined;
8736
+ const msaCols = ctx.outputs?.resolve('assayLinkerPframe')?.getPColumns();
8737
+ if (!msaCols)
8738
+ return undefined;
8739
+ const columns = getColumns(ctx);
8740
+ if (columns === undefined) {
8741
+ return undefined;
8742
+ }
8743
+ return createPFrameForGraphs(ctx, [...msaCols, ...cols, ...columns.props]);
8070
8744
  })
8071
8745
  .output('isRunning', (ctx) => ctx.outputs?.getIsReadyOrError() === false)
8072
8746
  .title(() => 'Immune Assay Data')
@@ -8076,6 +8750,7 @@
8076
8750
  ]))
8077
8751
  .done(2);
8078
8752
 
8753
+ exports.getDefaultBlockLabel = getDefaultBlockLabel;
8079
8754
  exports.model = model;
8080
8755
 
8081
8756
  }));