@platforma-open/milaboratories.vj-usage.model 2.1.5 → 2.1.6
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/.turbo/turbo-build.log +5 -5
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +6 -0
- package/dist/bundle.js +458 -30
- package/dist/bundle.js.map +1 -1
- package/dist/index.cjs +10 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +15 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/label.cjs +19 -0
- package/dist/label.cjs.map +1 -0
- package/dist/label.d.ts +7 -0
- package/dist/label.d.ts.map +1 -0
- package/dist/label.js +17 -0
- package/dist/label.js.map +1 -0
- package/dist/model.json +1 -1
- package/package.json +5 -5
- package/src/index.ts +14 -4
- package/src/label.ts +23 -0
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
|
//
|
|
@@ -4690,6 +4797,9 @@
|
|
|
4690
4797
|
Table: {
|
|
4691
4798
|
OrderPriority: 'pl7.app/table/orderPriority'},
|
|
4692
4799
|
Trace: 'pl7.app/trace',
|
|
4800
|
+
VDJ: {
|
|
4801
|
+
IsAssemblingFeature: 'pl7.app/vdj/isAssemblingFeature',
|
|
4802
|
+
},
|
|
4693
4803
|
};
|
|
4694
4804
|
const ValueTypeSchema = z.enum(['Int', 'Long', 'Float', 'Double', 'String']);
|
|
4695
4805
|
const AnnotationJson = {
|
|
@@ -4719,6 +4829,7 @@
|
|
|
4719
4829
|
[Annotation.Sequence.IsAnnotation]: z.boolean(),
|
|
4720
4830
|
[Annotation.Table.OrderPriority]: z.number(),
|
|
4721
4831
|
[Annotation.Trace]: z.record(z.string(), z.unknown()),
|
|
4832
|
+
[Annotation.VDJ.IsAssemblingFeature]: z.boolean(),
|
|
4722
4833
|
};
|
|
4723
4834
|
/// Helper function for reading plain annotation values
|
|
4724
4835
|
function readAnnotation(spec, key) {
|
|
@@ -6094,6 +6205,30 @@
|
|
|
6094
6205
|
}
|
|
6095
6206
|
return result;
|
|
6096
6207
|
};
|
|
6208
|
+
const countUniqueLabels = (result) => result === undefined ? 0 : new Set(result.map((c) => c.label)).size;
|
|
6209
|
+
// Post-processing: try removing types one by one (lowest importance first) to minimize the label set
|
|
6210
|
+
// Accepts removal if it doesn't decrease the number of unique labels (cardinality)
|
|
6211
|
+
const minimizeTypeSet = (typeSet) => {
|
|
6212
|
+
const initialResult = calculate(typeSet);
|
|
6213
|
+
if (initialResult === undefined) {
|
|
6214
|
+
return typeSet;
|
|
6215
|
+
}
|
|
6216
|
+
const currentCardinality = countUniqueLabels(initialResult);
|
|
6217
|
+
// Get types sorted by importance ascending (lowest first), excluding forced elements
|
|
6218
|
+
const removableSorted = [...typeSet]
|
|
6219
|
+
.filter((t) => !forceTraceElements?.has(t.split('@')[0])
|
|
6220
|
+
&& !(ops.includeNativeLabel && t === LabelTypeFull))
|
|
6221
|
+
.sort((a, b) => (importances.get(a) ?? 0) - (importances.get(b) ?? 0));
|
|
6222
|
+
for (const typeToRemove of removableSorted) {
|
|
6223
|
+
const reducedSet = new Set(typeSet);
|
|
6224
|
+
reducedSet.delete(typeToRemove);
|
|
6225
|
+
const candidateResult = calculate(reducedSet);
|
|
6226
|
+
if (candidateResult !== undefined && countUniqueLabels(candidateResult) >= currentCardinality) {
|
|
6227
|
+
typeSet.delete(typeToRemove);
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
return typeSet;
|
|
6231
|
+
};
|
|
6097
6232
|
if (mainTypes.length === 0) {
|
|
6098
6233
|
if (secondaryTypes.length !== 0)
|
|
6099
6234
|
throw new Error('Non-empty secondary types list while main types list is empty.');
|
|
@@ -6119,16 +6254,20 @@
|
|
|
6119
6254
|
if (additionalType >= 0)
|
|
6120
6255
|
currentSet.add(mainTypes[additionalType]);
|
|
6121
6256
|
const candidateResult = calculate(currentSet);
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
return
|
|
6257
|
+
if (candidateResult !== undefined && countUniqueLabels(candidateResult) === values.length) {
|
|
6258
|
+
minimizeTypeSet(currentSet);
|
|
6259
|
+
return calculate(currentSet);
|
|
6260
|
+
}
|
|
6125
6261
|
additionalType++;
|
|
6126
6262
|
if (additionalType >= mainTypes.length) {
|
|
6127
6263
|
includedTypes++;
|
|
6128
6264
|
additionalType = includedTypes;
|
|
6129
6265
|
}
|
|
6130
6266
|
}
|
|
6131
|
-
|
|
6267
|
+
// Fallback: include all types, then try to minimize
|
|
6268
|
+
const fallbackSet = new Set([...mainTypes, ...secondaryTypes]);
|
|
6269
|
+
minimizeTypeSet(fallbackSet);
|
|
6270
|
+
return calculate(fallbackSet, true);
|
|
6132
6271
|
}
|
|
6133
6272
|
|
|
6134
6273
|
const PCD_PREFIX = 'PColumnData/';
|
|
@@ -7231,28 +7370,19 @@
|
|
|
7231
7370
|
}
|
|
7232
7371
|
}
|
|
7233
7372
|
/** Main entry point to the API available within model lambdas (like outputs, sections, etc..) */
|
|
7234
|
-
class
|
|
7373
|
+
class RenderCtxBase {
|
|
7235
7374
|
ctx;
|
|
7236
7375
|
constructor() {
|
|
7237
7376
|
this.ctx = getCfgRenderCtx();
|
|
7238
7377
|
}
|
|
7239
|
-
|
|
7240
|
-
get
|
|
7241
|
-
if (this.
|
|
7242
|
-
const raw = this.ctx.
|
|
7378
|
+
_dataCache;
|
|
7379
|
+
get data() {
|
|
7380
|
+
if (this._dataCache === undefined) {
|
|
7381
|
+
const raw = this.ctx.data;
|
|
7243
7382
|
const value = typeof raw === 'function' ? raw() : raw;
|
|
7244
|
-
this.
|
|
7383
|
+
this._dataCache = { v: value ? JSON.parse(value) : {} };
|
|
7245
7384
|
}
|
|
7246
|
-
return this.
|
|
7247
|
-
}
|
|
7248
|
-
_uiStateCache;
|
|
7249
|
-
get uiState() {
|
|
7250
|
-
if (this._uiStateCache === undefined) {
|
|
7251
|
-
const raw = this.ctx.uiState;
|
|
7252
|
-
const value = typeof raw === 'function' ? raw() : raw;
|
|
7253
|
-
this._uiStateCache = { v: value ? JSON.parse(value) : {} };
|
|
7254
|
-
}
|
|
7255
|
-
return this._uiStateCache.v;
|
|
7385
|
+
return this._dataCache.v;
|
|
7256
7386
|
}
|
|
7257
7387
|
// lazy rendering because this feature is rarely used
|
|
7258
7388
|
_activeArgsCache;
|
|
@@ -7363,8 +7493,29 @@
|
|
|
7363
7493
|
this.ctx.logError(msg);
|
|
7364
7494
|
}
|
|
7365
7495
|
}
|
|
7496
|
+
/** Render context for legacy v1/v2 blocks - provides backward compatibility */
|
|
7497
|
+
class RenderCtxLegacy extends RenderCtxBase {
|
|
7498
|
+
_argsCache;
|
|
7499
|
+
get args() {
|
|
7500
|
+
if (this._argsCache === undefined) {
|
|
7501
|
+
const raw = this.ctx.args;
|
|
7502
|
+
const value = typeof raw === 'function' ? raw() : raw;
|
|
7503
|
+
this._argsCache = { v: JSON.parse(value) };
|
|
7504
|
+
}
|
|
7505
|
+
return this._argsCache.v;
|
|
7506
|
+
}
|
|
7507
|
+
_uiStateCache;
|
|
7508
|
+
get uiState() {
|
|
7509
|
+
if (this._uiStateCache === undefined) {
|
|
7510
|
+
const raw = this.ctx.uiState;
|
|
7511
|
+
const value = typeof raw === 'function' ? raw() : raw;
|
|
7512
|
+
this._uiStateCache = { v: value ? JSON.parse(value) : {} };
|
|
7513
|
+
}
|
|
7514
|
+
return this._uiStateCache.v;
|
|
7515
|
+
}
|
|
7516
|
+
}
|
|
7366
7517
|
|
|
7367
|
-
var version = "1.
|
|
7518
|
+
var version = "1.53.3";
|
|
7368
7519
|
|
|
7369
7520
|
const PlatformaSDKVersion = version;
|
|
7370
7521
|
|
|
@@ -7408,7 +7559,7 @@
|
|
|
7408
7559
|
output(key, cfgOrRf, flags = {}) {
|
|
7409
7560
|
if (typeof cfgOrRf === 'function') {
|
|
7410
7561
|
const handle = `output#${key}`;
|
|
7411
|
-
tryRegisterCallback(handle, () => cfgOrRf(new
|
|
7562
|
+
tryRegisterCallback(handle, () => cfgOrRf(new RenderCtxLegacy()));
|
|
7412
7563
|
return new BlockModel({
|
|
7413
7564
|
...this.config,
|
|
7414
7565
|
outputs: {
|
|
@@ -7445,7 +7596,7 @@
|
|
|
7445
7596
|
}
|
|
7446
7597
|
argsValid(cfgOrRf) {
|
|
7447
7598
|
if (typeof cfgOrRf === 'function') {
|
|
7448
|
-
tryRegisterCallback('inputsValid', () => cfgOrRf(new
|
|
7599
|
+
tryRegisterCallback('inputsValid', () => cfgOrRf(new RenderCtxLegacy()));
|
|
7449
7600
|
return new BlockModel({
|
|
7450
7601
|
...this.config,
|
|
7451
7602
|
inputsValid: {
|
|
@@ -7466,7 +7617,7 @@
|
|
|
7466
7617
|
return this.sections(getImmediate(arrOrCfgOrRf));
|
|
7467
7618
|
}
|
|
7468
7619
|
else if (typeof arrOrCfgOrRf === 'function') {
|
|
7469
|
-
tryRegisterCallback('sections', () => arrOrCfgOrRf(new
|
|
7620
|
+
tryRegisterCallback('sections', () => arrOrCfgOrRf(new RenderCtxLegacy()));
|
|
7470
7621
|
return new BlockModel({
|
|
7471
7622
|
...this.config,
|
|
7472
7623
|
sections: {
|
|
@@ -7484,7 +7635,7 @@
|
|
|
7484
7635
|
}
|
|
7485
7636
|
/** Sets a rendering function to derive block title, shown for the block in the left blocks-overview panel. */
|
|
7486
7637
|
title(rf) {
|
|
7487
|
-
tryRegisterCallback('title', () => rf(new
|
|
7638
|
+
tryRegisterCallback('title', () => rf(new RenderCtxLegacy()));
|
|
7488
7639
|
return new BlockModel({
|
|
7489
7640
|
...this.config,
|
|
7490
7641
|
title: {
|
|
@@ -7494,7 +7645,7 @@
|
|
|
7494
7645
|
});
|
|
7495
7646
|
}
|
|
7496
7647
|
subtitle(rf) {
|
|
7497
|
-
tryRegisterCallback('subtitle', () => rf(new
|
|
7648
|
+
tryRegisterCallback('subtitle', () => rf(new RenderCtxLegacy()));
|
|
7498
7649
|
return new BlockModel({
|
|
7499
7650
|
...this.config,
|
|
7500
7651
|
subtitle: {
|
|
@@ -7504,7 +7655,7 @@
|
|
|
7504
7655
|
});
|
|
7505
7656
|
}
|
|
7506
7657
|
tags(rf) {
|
|
7507
|
-
tryRegisterCallback('tags', () => rf(new
|
|
7658
|
+
tryRegisterCallback('tags', () => rf(new RenderCtxLegacy()));
|
|
7508
7659
|
return new BlockModel({
|
|
7509
7660
|
...this.config,
|
|
7510
7661
|
tags: {
|
|
@@ -7571,7 +7722,10 @@
|
|
|
7571
7722
|
if (this.config.initialArgs === undefined)
|
|
7572
7723
|
throw new Error('Initial arguments not set.');
|
|
7573
7724
|
const config = {
|
|
7725
|
+
v4: undefined,
|
|
7574
7726
|
v3: {
|
|
7727
|
+
configVersion: 3,
|
|
7728
|
+
modelAPIVersion: 1,
|
|
7575
7729
|
sdkVersion: PlatformaSDKVersion,
|
|
7576
7730
|
renderingMode: this.config.renderingMode,
|
|
7577
7731
|
initialArgs: this.config.initialArgs,
|
|
@@ -7611,6 +7765,259 @@
|
|
|
7611
7765
|
}
|
|
7612
7766
|
}
|
|
7613
7767
|
|
|
7768
|
+
/**
|
|
7769
|
+
* BlockStorage VM Integration - Internal module for VM-based storage operations.
|
|
7770
|
+
*
|
|
7771
|
+
* This module auto-registers internal callbacks that the middle layer can invoke
|
|
7772
|
+
* to perform storage transformations. Block developers never interact with these
|
|
7773
|
+
* directly - they only see `state`.
|
|
7774
|
+
*
|
|
7775
|
+
* Registered callbacks (all prefixed with `__pl_` for internal SDK use):
|
|
7776
|
+
* - `__pl_storage_applyUpdate`: (currentStorageJson, payload) => updatedStorageJson
|
|
7777
|
+
* - `__pl_storage_debugView`: (rawStorage) => JSON string with storage debug view
|
|
7778
|
+
* - `__pl_storage_migrate`: (currentStorageJson) => MigrationResult
|
|
7779
|
+
* - `__pl_args_derive`: (storageJson) => ArgsDeriveResult
|
|
7780
|
+
* - `__pl_prerunArgs_derive`: (storageJson) => ArgsDeriveResult
|
|
7781
|
+
*
|
|
7782
|
+
* Callbacks registered by DataModel.registerCallbacks():
|
|
7783
|
+
* - `__pl_data_initial`: () => initial data
|
|
7784
|
+
* - `__pl_data_upgrade`: (versioned) => DataMigrationResult
|
|
7785
|
+
* - `__pl_storage_initial`: () => initial BlockStorage as JSON string
|
|
7786
|
+
*
|
|
7787
|
+
* @module block_storage_vm
|
|
7788
|
+
* @internal
|
|
7789
|
+
*/
|
|
7790
|
+
/**
|
|
7791
|
+
* Normalizes raw storage data and extracts state.
|
|
7792
|
+
* Handles all formats:
|
|
7793
|
+
* - New BlockStorage format (has discriminator)
|
|
7794
|
+
* - Legacy V1/V2 format ({ args, uiState })
|
|
7795
|
+
* - Raw V3 state (any other format)
|
|
7796
|
+
*
|
|
7797
|
+
* @param rawStorage - Raw data from blockStorage field (may be JSON string or object)
|
|
7798
|
+
* @returns Object with normalized storage and extracted state
|
|
7799
|
+
*/
|
|
7800
|
+
function normalizeStorage(rawStorage) {
|
|
7801
|
+
// Handle undefined/null
|
|
7802
|
+
if (rawStorage === undefined || rawStorage === null) {
|
|
7803
|
+
const storage = createBlockStorage({});
|
|
7804
|
+
return { storage, data: {} };
|
|
7805
|
+
}
|
|
7806
|
+
// Parse JSON string if needed
|
|
7807
|
+
let parsed = rawStorage;
|
|
7808
|
+
if (typeof rawStorage === 'string') {
|
|
7809
|
+
try {
|
|
7810
|
+
parsed = JSON.parse(rawStorage);
|
|
7811
|
+
}
|
|
7812
|
+
catch {
|
|
7813
|
+
// If parsing fails, treat string as the data
|
|
7814
|
+
const storage = createBlockStorage(rawStorage);
|
|
7815
|
+
return { storage, data: rawStorage };
|
|
7816
|
+
}
|
|
7817
|
+
}
|
|
7818
|
+
// Check for BlockStorage format (has discriminator)
|
|
7819
|
+
if (isBlockStorage(parsed)) {
|
|
7820
|
+
const storage = normalizeBlockStorage(parsed);
|
|
7821
|
+
return { storage, data: getStorageData(storage) };
|
|
7822
|
+
}
|
|
7823
|
+
// Check for legacy V1/V2 format: { args, uiState }
|
|
7824
|
+
if (isLegacyModelV1ApiFormat(parsed)) {
|
|
7825
|
+
// For legacy format, the whole object IS the data
|
|
7826
|
+
const storage = createBlockStorage(parsed);
|
|
7827
|
+
return { storage, data: parsed };
|
|
7828
|
+
}
|
|
7829
|
+
// Raw V3 data - wrap it
|
|
7830
|
+
const storage = createBlockStorage(parsed);
|
|
7831
|
+
return { storage, data: parsed };
|
|
7832
|
+
}
|
|
7833
|
+
/**
|
|
7834
|
+
* Applies a state update to existing storage.
|
|
7835
|
+
* Used when setData is called from the frontend.
|
|
7836
|
+
*
|
|
7837
|
+
* @param currentStorageJson - Current storage as JSON string (must be defined)
|
|
7838
|
+
* @param newData - New data from application
|
|
7839
|
+
* @returns Updated storage as JSON string
|
|
7840
|
+
*/
|
|
7841
|
+
function applyStorageUpdate(currentStorageJson, payload) {
|
|
7842
|
+
const { storage: currentStorage } = normalizeStorage(currentStorageJson);
|
|
7843
|
+
// Update data while preserving other storage fields (version, plugins)
|
|
7844
|
+
const updatedStorage = updateStorageData(currentStorage, payload);
|
|
7845
|
+
return JSON.stringify(updatedStorage);
|
|
7846
|
+
}
|
|
7847
|
+
/**
|
|
7848
|
+
* Checks if data is in legacy Model API v1 format.
|
|
7849
|
+
* Legacy format has { args, uiState? } at top level without the BlockStorage discriminator.
|
|
7850
|
+
*/
|
|
7851
|
+
function isLegacyModelV1ApiFormat(data) {
|
|
7852
|
+
if (data === null || typeof data !== 'object')
|
|
7853
|
+
return false;
|
|
7854
|
+
if (isBlockStorage(data))
|
|
7855
|
+
return false;
|
|
7856
|
+
const obj = data;
|
|
7857
|
+
return 'args' in obj;
|
|
7858
|
+
}
|
|
7859
|
+
// =============================================================================
|
|
7860
|
+
// Auto-register internal callbacks when module is loaded in VM
|
|
7861
|
+
// =============================================================================
|
|
7862
|
+
// Register apply update callback (requires existing storage)
|
|
7863
|
+
tryRegisterCallback('__pl_storage_applyUpdate', (currentStorageJson, payload) => {
|
|
7864
|
+
return applyStorageUpdate(currentStorageJson, payload);
|
|
7865
|
+
});
|
|
7866
|
+
/**
|
|
7867
|
+
* Gets storage debug view from raw storage data.
|
|
7868
|
+
* Returns structured debug info about the storage state.
|
|
7869
|
+
*
|
|
7870
|
+
* @param rawStorage - Raw data from blockStorage field (may be JSON string or object)
|
|
7871
|
+
* @returns JSON string with storage debug view
|
|
7872
|
+
*/
|
|
7873
|
+
function getStorageDebugView(rawStorage) {
|
|
7874
|
+
const { storage } = normalizeStorage(rawStorage);
|
|
7875
|
+
const debugView = {
|
|
7876
|
+
dataVersion: storage.__dataVersion,
|
|
7877
|
+
data: storage.__data,
|
|
7878
|
+
};
|
|
7879
|
+
return stringifyJson(debugView);
|
|
7880
|
+
}
|
|
7881
|
+
// Register debug view callback
|
|
7882
|
+
tryRegisterCallback('__pl_storage_debugView', (rawStorage) => {
|
|
7883
|
+
return getStorageDebugView(rawStorage);
|
|
7884
|
+
});
|
|
7885
|
+
/**
|
|
7886
|
+
* Runs storage migration using the DataModel's migrate callback.
|
|
7887
|
+
* This is the main entry point for the middle layer to trigger migrations.
|
|
7888
|
+
*
|
|
7889
|
+
* Uses the '__pl_data_upgrade' callback registered by DataModel.registerCallbacks() which:
|
|
7890
|
+
* - Handles all migration logic internally
|
|
7891
|
+
* - Returns { version, data, warning? } - warning present if reset to initial data
|
|
7892
|
+
*
|
|
7893
|
+
* @param currentStorageJson - Current storage as JSON string (or undefined)
|
|
7894
|
+
* @returns MigrationResult
|
|
7895
|
+
*/
|
|
7896
|
+
function migrateStorage(currentStorageJson) {
|
|
7897
|
+
// Get the callback registry context
|
|
7898
|
+
const ctx = tryGetCfgRenderCtx();
|
|
7899
|
+
if (ctx === undefined) {
|
|
7900
|
+
return { error: 'Not in config rendering context' };
|
|
7901
|
+
}
|
|
7902
|
+
// Normalize storage to get current data and version
|
|
7903
|
+
const { storage: currentStorage, data: currentData } = normalizeStorage(currentStorageJson);
|
|
7904
|
+
const currentVersion = currentStorage.__dataVersion;
|
|
7905
|
+
// Helper to create storage with given data and version
|
|
7906
|
+
const createStorageJson = (data, version) => {
|
|
7907
|
+
return JSON.stringify({
|
|
7908
|
+
...currentStorage,
|
|
7909
|
+
__dataVersion: version,
|
|
7910
|
+
__data: data,
|
|
7911
|
+
});
|
|
7912
|
+
};
|
|
7913
|
+
// Get the migrate callback (registered by DataModel.registerCallbacks())
|
|
7914
|
+
const migrateCallback = ctx.callbackRegistry['__pl_data_upgrade'];
|
|
7915
|
+
if (typeof migrateCallback !== 'function') {
|
|
7916
|
+
return { error: '__pl_data_upgrade callback not found (DataModel not registered)' };
|
|
7917
|
+
}
|
|
7918
|
+
// Call the migrator's migrate function
|
|
7919
|
+
let result;
|
|
7920
|
+
try {
|
|
7921
|
+
result = migrateCallback({ version: currentVersion, data: currentData });
|
|
7922
|
+
}
|
|
7923
|
+
catch (e) {
|
|
7924
|
+
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
7925
|
+
return { error: `migrate() threw: ${errorMsg}` };
|
|
7926
|
+
}
|
|
7927
|
+
// Build info message
|
|
7928
|
+
const info = result.version === currentVersion
|
|
7929
|
+
? `No migration needed (${currentVersion})`
|
|
7930
|
+
: result.warning
|
|
7931
|
+
? `Reset to initial data (${result.version})`
|
|
7932
|
+
: `Migrated ${currentVersion}→${result.version}`;
|
|
7933
|
+
return {
|
|
7934
|
+
newStorageJson: createStorageJson(result.data, result.version),
|
|
7935
|
+
info,
|
|
7936
|
+
warn: result.warning,
|
|
7937
|
+
};
|
|
7938
|
+
}
|
|
7939
|
+
// Register migrate callback
|
|
7940
|
+
tryRegisterCallback('__pl_storage_migrate', (currentStorageJson) => {
|
|
7941
|
+
return migrateStorage(currentStorageJson);
|
|
7942
|
+
});
|
|
7943
|
+
/**
|
|
7944
|
+
* Derives args from storage using the registered 'args' callback.
|
|
7945
|
+
* This extracts data from storage and passes it to the block's args() function.
|
|
7946
|
+
*
|
|
7947
|
+
* @param storageJson - Storage as JSON string
|
|
7948
|
+
* @returns ArgsDeriveResult with derived args or error
|
|
7949
|
+
*/
|
|
7950
|
+
function deriveArgsFromStorage(storageJson) {
|
|
7951
|
+
const ctx = tryGetCfgRenderCtx();
|
|
7952
|
+
if (ctx === undefined) {
|
|
7953
|
+
return { error: 'Not in config rendering context' };
|
|
7954
|
+
}
|
|
7955
|
+
// Extract data from storage
|
|
7956
|
+
const { data } = normalizeStorage(storageJson);
|
|
7957
|
+
// Get the args callback (registered by BlockModelV3.args())
|
|
7958
|
+
const argsCallback = ctx.callbackRegistry['args'];
|
|
7959
|
+
if (typeof argsCallback !== 'function') {
|
|
7960
|
+
return { error: 'args callback not found' };
|
|
7961
|
+
}
|
|
7962
|
+
// Call the args callback with extracted data
|
|
7963
|
+
try {
|
|
7964
|
+
const result = argsCallback(data);
|
|
7965
|
+
return { value: result };
|
|
7966
|
+
}
|
|
7967
|
+
catch (e) {
|
|
7968
|
+
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
7969
|
+
return { error: `args() threw: ${errorMsg}` };
|
|
7970
|
+
}
|
|
7971
|
+
}
|
|
7972
|
+
// Register args derivation callback
|
|
7973
|
+
tryRegisterCallback('__pl_args_derive', (storageJson) => {
|
|
7974
|
+
return deriveArgsFromStorage(storageJson);
|
|
7975
|
+
});
|
|
7976
|
+
/**
|
|
7977
|
+
* Derives prerunArgs from storage using the registered 'prerunArgs' callback.
|
|
7978
|
+
* Falls back to 'args' callback if 'prerunArgs' is not defined.
|
|
7979
|
+
*
|
|
7980
|
+
* @param storageJson - Storage as JSON string
|
|
7981
|
+
* @returns ArgsDeriveResult with derived prerunArgs or error
|
|
7982
|
+
*/
|
|
7983
|
+
function derivePrerunArgsFromStorage(storageJson) {
|
|
7984
|
+
const ctx = tryGetCfgRenderCtx();
|
|
7985
|
+
if (ctx === undefined) {
|
|
7986
|
+
return { error: 'Not in config rendering context' };
|
|
7987
|
+
}
|
|
7988
|
+
// Extract data from storage
|
|
7989
|
+
const { data } = normalizeStorage(storageJson);
|
|
7990
|
+
// Try prerunArgs callback first
|
|
7991
|
+
const prerunArgsCallback = ctx.callbackRegistry['prerunArgs'];
|
|
7992
|
+
if (typeof prerunArgsCallback === 'function') {
|
|
7993
|
+
try {
|
|
7994
|
+
const result = prerunArgsCallback(data);
|
|
7995
|
+
return { value: result };
|
|
7996
|
+
}
|
|
7997
|
+
catch (e) {
|
|
7998
|
+
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
7999
|
+
return { error: `prerunArgs() threw: ${errorMsg}` };
|
|
8000
|
+
}
|
|
8001
|
+
}
|
|
8002
|
+
// Fall back to args callback
|
|
8003
|
+
const argsCallback = ctx.callbackRegistry['args'];
|
|
8004
|
+
if (typeof argsCallback !== 'function') {
|
|
8005
|
+
return { error: 'args callback not found (fallback from missing prerunArgs)' };
|
|
8006
|
+
}
|
|
8007
|
+
try {
|
|
8008
|
+
const result = argsCallback(data);
|
|
8009
|
+
return { value: result };
|
|
8010
|
+
}
|
|
8011
|
+
catch (e) {
|
|
8012
|
+
const errorMsg = e instanceof Error ? e.message : String(e);
|
|
8013
|
+
return { error: `args() threw (fallback): ${errorMsg}` };
|
|
8014
|
+
}
|
|
8015
|
+
}
|
|
8016
|
+
// Register prerunArgs derivation callback
|
|
8017
|
+
tryRegisterCallback('__pl_prerunArgs_derive', (storageJson) => {
|
|
8018
|
+
return derivePrerunArgsFromStorage(storageJson);
|
|
8019
|
+
});
|
|
8020
|
+
|
|
7614
8021
|
function getAllRelatedColumns(ctx, predicate) {
|
|
7615
8022
|
// if current block doesn't produce own columns then use all columns from result pool
|
|
7616
8023
|
const columns = new PColumnCollection();
|
|
@@ -7901,13 +8308,32 @@
|
|
|
7901
8308
|
errors: z.lazy(() => ErrorShape.array()).optional(),
|
|
7902
8309
|
});
|
|
7903
8310
|
|
|
8311
|
+
function getDefaultBlockLabel(data) {
|
|
8312
|
+
const parts = [];
|
|
8313
|
+
// Add dataset name
|
|
8314
|
+
if (data.datasetLabel) {
|
|
8315
|
+
parts.push(data.datasetLabel);
|
|
8316
|
+
}
|
|
8317
|
+
// Add allele/gene
|
|
8318
|
+
parts.push(data.allele ? 'Allele' : 'Gene');
|
|
8319
|
+
// Add chain info for single-cell datasets
|
|
8320
|
+
if (data.isSingleCell && data.chainLabel) {
|
|
8321
|
+
parts.push(data.chainLabel);
|
|
8322
|
+
}
|
|
8323
|
+
return parts.join(' - ');
|
|
8324
|
+
}
|
|
8325
|
+
|
|
7904
8326
|
const model = BlockModel.create()
|
|
7905
8327
|
.withArgs({
|
|
8328
|
+
defaultBlockLabel: getDefaultBlockLabel({
|
|
8329
|
+
allele: false,
|
|
8330
|
+
isSingleCell: false,
|
|
8331
|
+
}),
|
|
8332
|
+
customBlockLabel: '',
|
|
7906
8333
|
scChain: 'A',
|
|
7907
8334
|
allele: false,
|
|
7908
8335
|
})
|
|
7909
8336
|
.withUiState({
|
|
7910
|
-
blockTitle: 'V/J Usage',
|
|
7911
8337
|
weightedFlag: true,
|
|
7912
8338
|
vUsagePlotState: {
|
|
7913
8339
|
title: 'V Usage',
|
|
@@ -7920,7 +8346,7 @@
|
|
|
7920
8346
|
},
|
|
7921
8347
|
},
|
|
7922
8348
|
jUsagePlotState: {
|
|
7923
|
-
title: '
|
|
8349
|
+
title: 'J Usage',
|
|
7924
8350
|
template: 'heatmapClustered',
|
|
7925
8351
|
currentTab: null,
|
|
7926
8352
|
layersSettings: {
|
|
@@ -7971,7 +8397,8 @@
|
|
|
7971
8397
|
return createPFrameForGraphs(ctx, pCols);
|
|
7972
8398
|
})
|
|
7973
8399
|
.output('isRunning', (ctx) => ctx.outputs?.getIsReadyOrError() === false)
|
|
7974
|
-
.title((
|
|
8400
|
+
.title(() => 'V/J Usage')
|
|
8401
|
+
.subtitle((ctx) => ctx.args.customBlockLabel || ctx.args.defaultBlockLabel)
|
|
7975
8402
|
.sections((_) => [
|
|
7976
8403
|
{ type: 'link', href: '/', label: 'V Gene Usage' },
|
|
7977
8404
|
{ type: 'link', href: '/jUsage', label: 'J Gene Usage' },
|
|
@@ -7979,6 +8406,7 @@
|
|
|
7979
8406
|
])
|
|
7980
8407
|
.done(2);
|
|
7981
8408
|
|
|
8409
|
+
exports.getDefaultBlockLabel = getDefaultBlockLabel;
|
|
7982
8410
|
exports.model = model;
|
|
7983
8411
|
|
|
7984
8412
|
}));
|