@milaboratories/pl-model-common 1.15.8 → 1.16.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.
- package/dist/{base32-encode.d.ts → base32_encode.d.ts} +1 -1
- package/dist/{base32-encode.d.ts.map → base32_encode.d.ts.map} +1 -1
- package/dist/bmodel/block_config.d.ts +42 -0
- package/dist/bmodel/block_config.d.ts.map +1 -0
- package/dist/bmodel/code.d.ts +11 -0
- package/dist/bmodel/code.d.ts.map +1 -0
- package/dist/bmodel/container.d.ts +46 -0
- package/dist/bmodel/container.d.ts.map +1 -0
- package/dist/bmodel/index.d.ts +6 -0
- package/dist/bmodel/index.d.ts.map +1 -0
- package/dist/bmodel/normalization.d.ts +10 -0
- package/dist/bmodel/normalization.d.ts.map +1 -0
- package/dist/{block.d.ts → bmodel/types.d.ts} +1 -1
- package/dist/bmodel/types.d.ts.map +1 -0
- package/dist/drivers/pframe/table_calculate.d.ts +10 -1
- package/dist/drivers/pframe/table_calculate.d.ts.map +1 -1
- package/dist/drivers/pframe/table_common.d.ts +1 -0
- package/dist/drivers/pframe/table_common.d.ts.map +1 -1
- package/dist/flags/block_flags.d.ts +20 -0
- package/dist/flags/block_flags.d.ts.map +1 -0
- package/dist/flags/flag_utils.d.ts +56 -0
- package/dist/flags/flag_utils.d.ts.map +1 -0
- package/dist/flags/index.d.ts +4 -0
- package/dist/flags/index.d.ts.map +1 -0
- package/dist/flags/type_utils.d.ts +35 -0
- package/dist/flags/type_utils.d.ts.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +742 -572
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/bmodel/block_config.ts +69 -0
- package/src/bmodel/code.ts +12 -0
- package/src/bmodel/container.ts +58 -0
- package/src/bmodel/index.ts +5 -0
- package/src/bmodel/normalization.ts +111 -0
- package/src/drivers/pframe/table_calculate.ts +13 -1
- package/src/drivers/pframe/table_common.ts +15 -0
- package/src/flags/block_flags.ts +43 -0
- package/src/flags/flag_utils.test.ts +162 -0
- package/src/flags/flag_utils.ts +104 -0
- package/src/flags/index.ts +3 -0
- package/src/flags/type_utils.ts +43 -0
- package/src/index.ts +3 -1
- package/src/plid.ts +1 -1
- package/dist/block.d.ts.map +0 -1
- /package/src/{base32-encode.ts → base32_encode.ts} +0 -0
- /package/src/{block.ts → bmodel/types.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/pl-model-common",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Platforma SDK Model",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"eslint": "^9.25.1",
|
|
26
|
-
"typescript": "~5.
|
|
26
|
+
"typescript": "~5.6.3",
|
|
27
27
|
"vite": "^6.3.5",
|
|
28
28
|
"vitest": "^2.1.9",
|
|
29
29
|
"@milaboratories/build-configs": "1.0.4",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// TODO BroadActiveHandleDescriptor must be removed
|
|
2
|
+
|
|
3
|
+
import type { BlockCodeFeatureFlags } from '../flags';
|
|
4
|
+
import type { BlockCodeWithInfo, Code } from './code';
|
|
5
|
+
import type { BlockRenderingMode } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* BroadActiveHandleDescriptor = TypedConfigOrConfigLambda,
|
|
9
|
+
* NarrowActiveHandleDescriptor = ConfigRenderLambda
|
|
10
|
+
*/
|
|
11
|
+
export type BlockConfigV3Generic<
|
|
12
|
+
Args = unknown,
|
|
13
|
+
UiState = unknown,
|
|
14
|
+
BroadActiveHandleDescriptor = unknown,
|
|
15
|
+
NarrowActiveHandleDescriptor extends BroadActiveHandleDescriptor = BroadActiveHandleDescriptor,
|
|
16
|
+
Outputs extends Record<string, BroadActiveHandleDescriptor> = Record<string, BroadActiveHandleDescriptor>,
|
|
17
|
+
> = {
|
|
18
|
+
/** SDK version used by the block */
|
|
19
|
+
readonly sdkVersion: string;
|
|
20
|
+
|
|
21
|
+
/** Main rendering mode for the block */
|
|
22
|
+
readonly renderingMode: BlockRenderingMode;
|
|
23
|
+
|
|
24
|
+
/** Initial value for the args when block is added to the project */
|
|
25
|
+
readonly initialArgs: Args;
|
|
26
|
+
|
|
27
|
+
/** Initial value for the args when block is added to the project */
|
|
28
|
+
readonly initialUiState: UiState;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Config to determine whether the block can be executed with current
|
|
32
|
+
* arguments.
|
|
33
|
+
*
|
|
34
|
+
* Optional to support earlier SDK version configs.
|
|
35
|
+
* */
|
|
36
|
+
readonly inputsValid: BroadActiveHandleDescriptor;
|
|
37
|
+
|
|
38
|
+
/** Configuration to derive list of section for the left overview panel */
|
|
39
|
+
readonly sections: BroadActiveHandleDescriptor;
|
|
40
|
+
|
|
41
|
+
/** Lambda to derive block title */
|
|
42
|
+
readonly title?: NarrowActiveHandleDescriptor;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Lambda returning list of upstream blocks this block enriches with its exports,
|
|
46
|
+
* influences dependency graph construction
|
|
47
|
+
* */
|
|
48
|
+
readonly enrichmentTargets?: NarrowActiveHandleDescriptor;
|
|
49
|
+
|
|
50
|
+
/** Configuration for the output cells */
|
|
51
|
+
readonly outputs: Outputs;
|
|
52
|
+
|
|
53
|
+
/** Config code bundle */
|
|
54
|
+
readonly code?: Code;
|
|
55
|
+
|
|
56
|
+
/** Feature flags for the block Model and UI code. */
|
|
57
|
+
readonly featureFlags?: BlockCodeFeatureFlags;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type BlockConfigGeneric = BlockConfigV3Generic;
|
|
61
|
+
|
|
62
|
+
export function extractCodeWithInfo(cfg: BlockConfigV3Generic): BlockCodeWithInfo | undefined {
|
|
63
|
+
if (cfg.code === undefined) return undefined;
|
|
64
|
+
return {
|
|
65
|
+
code: cfg.code,
|
|
66
|
+
sdkVersion: cfg.sdkVersion,
|
|
67
|
+
featureFlags: cfg.featureFlags,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BlockCodeFeatureFlags } from '../flags';
|
|
2
|
+
|
|
3
|
+
export type Code = {
|
|
4
|
+
type: 'plain';
|
|
5
|
+
content: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type BlockCodeWithInfo = {
|
|
9
|
+
readonly code: Code;
|
|
10
|
+
readonly sdkVersion: string;
|
|
11
|
+
readonly featureFlags: BlockCodeFeatureFlags | undefined;
|
|
12
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { BlockConfigV3Generic } from './block_config';
|
|
2
|
+
import type { Code } from './code';
|
|
3
|
+
import type { BlockRenderingMode } from './types';
|
|
4
|
+
|
|
5
|
+
/** Container simplifying maintenance of forward and backward compatibility */
|
|
6
|
+
export type BlockConfigContainer = {
|
|
7
|
+
/** Actual config */
|
|
8
|
+
readonly v3: Omit<BlockConfigV3Generic, 'code'>;
|
|
9
|
+
|
|
10
|
+
/** Config code bundle */
|
|
11
|
+
readonly code?: Code;
|
|
12
|
+
|
|
13
|
+
//
|
|
14
|
+
// Fields below are used to read previous config versions
|
|
15
|
+
//
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* For backward compatibility
|
|
19
|
+
* @deprecated
|
|
20
|
+
*/
|
|
21
|
+
readonly sdkVersion?: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* For backward compatibility
|
|
25
|
+
* @deprecated
|
|
26
|
+
*/
|
|
27
|
+
readonly renderingMode?: BlockRenderingMode;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* For backward compatibility
|
|
31
|
+
* @deprecated
|
|
32
|
+
*/
|
|
33
|
+
readonly initialArgs?: unknown;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* For backward compatibility
|
|
37
|
+
* @deprecated
|
|
38
|
+
*/
|
|
39
|
+
readonly canRun?: unknown;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* For backward compatibility
|
|
43
|
+
* @deprecated
|
|
44
|
+
*/
|
|
45
|
+
readonly inputsValid?: unknown;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* For backward compatibility
|
|
49
|
+
* @deprecated
|
|
50
|
+
*/
|
|
51
|
+
readonly sections?: unknown;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* For backward compatibility
|
|
55
|
+
* @deprecated
|
|
56
|
+
*/
|
|
57
|
+
readonly outputs?: Record<string, unknown>;
|
|
58
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { BlockConfigGeneric } from './block_config';
|
|
2
|
+
import type { BlockConfigContainer } from './container';
|
|
3
|
+
|
|
4
|
+
function upgradeCfgOrLambda(data: any): any;
|
|
5
|
+
function upgradeCfgOrLambda(
|
|
6
|
+
data: any | undefined
|
|
7
|
+
): any | undefined;
|
|
8
|
+
function upgradeCfgOrLambda(
|
|
9
|
+
data: any | undefined,
|
|
10
|
+
): any | undefined {
|
|
11
|
+
if (data === undefined) return undefined;
|
|
12
|
+
if (typeof data === 'string') return { __renderLambda: true, handle: data, retentive: false };
|
|
13
|
+
return data;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Takes universal config, and converts it into latest config structure.
|
|
18
|
+
*
|
|
19
|
+
* **Important**: This operation is not meant to be executed recusively.
|
|
20
|
+
* In no circumstance result of this function should be persisted!
|
|
21
|
+
* */
|
|
22
|
+
export function extractConfigGeneric(cfg: BlockConfigContainer): BlockConfigGeneric {
|
|
23
|
+
if (cfg.v3 !== undefined) {
|
|
24
|
+
// version 3
|
|
25
|
+
const {
|
|
26
|
+
initialArgs,
|
|
27
|
+
initialUiState,
|
|
28
|
+
inputsValid,
|
|
29
|
+
outputs,
|
|
30
|
+
renderingMode,
|
|
31
|
+
sdkVersion,
|
|
32
|
+
featureFlags,
|
|
33
|
+
sections,
|
|
34
|
+
title,
|
|
35
|
+
enrichmentTargets,
|
|
36
|
+
} = cfg.v3;
|
|
37
|
+
const { code } = cfg;
|
|
38
|
+
return {
|
|
39
|
+
initialArgs,
|
|
40
|
+
initialUiState,
|
|
41
|
+
inputsValid,
|
|
42
|
+
outputs,
|
|
43
|
+
renderingMode,
|
|
44
|
+
sdkVersion,
|
|
45
|
+
featureFlags,
|
|
46
|
+
sections,
|
|
47
|
+
title,
|
|
48
|
+
code,
|
|
49
|
+
enrichmentTargets,
|
|
50
|
+
};
|
|
51
|
+
} else if (cfg.inputsValid !== undefined) {
|
|
52
|
+
// version 2
|
|
53
|
+
const { sdkVersion, renderingMode, outputs, inputsValid, sections, initialArgs, code } = cfg;
|
|
54
|
+
const fields = Object.keys(cfg);
|
|
55
|
+
if (
|
|
56
|
+
sdkVersion === undefined
|
|
57
|
+
|| renderingMode === undefined
|
|
58
|
+
|| outputs === undefined
|
|
59
|
+
|| inputsValid === undefined
|
|
60
|
+
|| sections === undefined
|
|
61
|
+
|| initialArgs === undefined
|
|
62
|
+
)
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Malformed config v2. SDK version ${sdkVersion}; Fields = ${fields.join(', ')}`,
|
|
65
|
+
);
|
|
66
|
+
return {
|
|
67
|
+
sdkVersion,
|
|
68
|
+
renderingMode,
|
|
69
|
+
initialArgs,
|
|
70
|
+
outputs: Object.fromEntries(
|
|
71
|
+
Object.entries(outputs).map(([key, value]) => [key, upgradeCfgOrLambda(value)]),
|
|
72
|
+
),
|
|
73
|
+
inputsValid: upgradeCfgOrLambda(inputsValid),
|
|
74
|
+
sections: upgradeCfgOrLambda(sections),
|
|
75
|
+
initialUiState: undefined,
|
|
76
|
+
code,
|
|
77
|
+
};
|
|
78
|
+
} else if (cfg.renderingMode !== undefined) {
|
|
79
|
+
// version 1
|
|
80
|
+
const { sdkVersion, canRun, renderingMode, outputs, sections, initialArgs, code } = cfg;
|
|
81
|
+
const fields = Object.keys(cfg);
|
|
82
|
+
if (
|
|
83
|
+
renderingMode === undefined
|
|
84
|
+
|| outputs === undefined
|
|
85
|
+
|| canRun === undefined
|
|
86
|
+
|| sections === undefined
|
|
87
|
+
|| initialArgs === undefined
|
|
88
|
+
)
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Malformed config v1. SDK version ${sdkVersion}; Fields = ${fields.join(', ')}`,
|
|
91
|
+
);
|
|
92
|
+
return {
|
|
93
|
+
sdkVersion: sdkVersion ?? 'unknown',
|
|
94
|
+
renderingMode,
|
|
95
|
+
initialArgs,
|
|
96
|
+
outputs: Object.fromEntries(
|
|
97
|
+
Object.entries(outputs).map(([key, value]) => [key, upgradeCfgOrLambda(value)]),
|
|
98
|
+
),
|
|
99
|
+
inputsValid: upgradeCfgOrLambda(canRun),
|
|
100
|
+
sections: upgradeCfgOrLambda(sections),
|
|
101
|
+
initialUiState: undefined,
|
|
102
|
+
code,
|
|
103
|
+
};
|
|
104
|
+
} else {
|
|
105
|
+
const { sdkVersion } = cfg;
|
|
106
|
+
const fields = Object.keys(cfg);
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Config format not supported: SDK = ${sdkVersion}; Fields = ${fields.join(', ')}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -339,6 +339,9 @@ export interface PTableDef<Col> {
|
|
|
339
339
|
/** Join tree to populate the PTable */
|
|
340
340
|
readonly src: JoinEntry<Col>;
|
|
341
341
|
|
|
342
|
+
/** Partition filters */
|
|
343
|
+
readonly partitionFilters: PTableRecordSingleValueFilterV2[];
|
|
344
|
+
|
|
342
345
|
/** Record filters */
|
|
343
346
|
readonly filters: PTableRecordFilter[];
|
|
344
347
|
|
|
@@ -347,7 +350,16 @@ export interface PTableDef<Col> {
|
|
|
347
350
|
}
|
|
348
351
|
|
|
349
352
|
/** Request to create and retrieve entirety of data of PTable. */
|
|
350
|
-
export type CalculateTableDataRequest<Col> =
|
|
353
|
+
export type CalculateTableDataRequest<Col> = {
|
|
354
|
+
/** Join tree to populate the PTable */
|
|
355
|
+
readonly src: JoinEntry<Col>;
|
|
356
|
+
|
|
357
|
+
/** Record filters */
|
|
358
|
+
readonly filters: PTableRecordFilter[];
|
|
359
|
+
|
|
360
|
+
/** Table sorting */
|
|
361
|
+
readonly sorting: PTableSorting[];
|
|
362
|
+
};
|
|
351
363
|
|
|
352
364
|
/** Response for {@link CalculateTableDataRequest} */
|
|
353
365
|
export type CalculateTableDataResponse = FullPTableColumnData[];
|
|
@@ -26,3 +26,18 @@ export type PTableColumnIdColumn = {
|
|
|
26
26
|
|
|
27
27
|
/** Unified PTable column identifier */
|
|
28
28
|
export type PTableColumnId = PTableColumnIdAxis | PTableColumnIdColumn;
|
|
29
|
+
|
|
30
|
+
export function getPTableColumnId(spec: PTableColumnSpec): PTableColumnId {
|
|
31
|
+
switch (spec.type) {
|
|
32
|
+
case 'axis':
|
|
33
|
+
return {
|
|
34
|
+
type: 'axis',
|
|
35
|
+
id: spec.id,
|
|
36
|
+
};
|
|
37
|
+
case 'column':
|
|
38
|
+
return {
|
|
39
|
+
type: 'column',
|
|
40
|
+
id: spec.id,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ArrayTypeUnion, Assert, Is, IsSubtypeOf } from './type_utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Block-specific feature flags. Define flags that are interpreted by the desktop app to select
|
|
5
|
+
* appropriate API to expose into Model and UI runtimes.
|
|
6
|
+
*
|
|
7
|
+
* Flags are split into two categories:
|
|
8
|
+
* - supports... - those flags tells the desktop app that the block supports certain APIs, but can without them as well
|
|
9
|
+
* - requires... - those flags tells the desktop app that the block requires certain APIs, and if desktop app doesn't support it, it can't be used in the block
|
|
10
|
+
*/
|
|
11
|
+
export type BlockCodeFeatureFlags = Record<`supports${string}`, boolean | number | undefined> & Record<`requires${string}`, boolean | number | undefined>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Known block flags. Flags are set during model compilation, see `BlockModel.create` for more details and for initial values.
|
|
15
|
+
*/
|
|
16
|
+
export type BlockCodeKnownFeatureFlags = {
|
|
17
|
+
readonly supportsLazyState?: boolean;
|
|
18
|
+
readonly requiresModelAPIVersion?: number;
|
|
19
|
+
readonly requiresUIAPIVersion?: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const AllSupportsFeatureFlags
|
|
23
|
+
= ['supportsLazyState'] as const;
|
|
24
|
+
|
|
25
|
+
export const AllRequiresFeatureFlags
|
|
26
|
+
= ['requiresUIAPIVersion', 'requiresModelAPIVersion'] as const;
|
|
27
|
+
|
|
28
|
+
//
|
|
29
|
+
// Assertions
|
|
30
|
+
//
|
|
31
|
+
|
|
32
|
+
// This assertion ensures that BlockConfigV3KnownFeatureFlags is a subtype of BlockConfigV3FeatureFlags.
|
|
33
|
+
// It will produce a compile-time error if there's a mismatch.
|
|
34
|
+
type _KnownFlagsAreValidFlags = Assert<IsSubtypeOf<BlockCodeKnownFeatureFlags, BlockCodeFeatureFlags>>;
|
|
35
|
+
|
|
36
|
+
// This check ensures that all keys in BlockConfigV3FeatureFlags are covered in the arrays above.
|
|
37
|
+
// It will produce a compile-time error if there's a mismatch.
|
|
38
|
+
type _AllFlagsAreCovered = Assert<
|
|
39
|
+
Is<
|
|
40
|
+
keyof BlockCodeKnownFeatureFlags,
|
|
41
|
+
ArrayTypeUnion<typeof AllRequiresFeatureFlags, typeof AllSupportsFeatureFlags>
|
|
42
|
+
>
|
|
43
|
+
>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { RuntimeCapabilities, IncompatibleFlagsError } from './flag_utils';
|
|
3
|
+
import type { BlockCodeFeatureFlags } from './block_flags';
|
|
4
|
+
|
|
5
|
+
describe('RuntimeCapabilities', () => {
|
|
6
|
+
describe('addSupportedRequirement', () => {
|
|
7
|
+
it('should add a supported requirement with default value true', () => {
|
|
8
|
+
const capabilities = new RuntimeCapabilities();
|
|
9
|
+
capabilities.addSupportedRequirement('requiresModelAPIVersion');
|
|
10
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: true };
|
|
11
|
+
expect(capabilities.checkCompatibility(blockFlags)).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should add a supported requirement with a specific boolean value', () => {
|
|
15
|
+
const capabilities = new RuntimeCapabilities();
|
|
16
|
+
capabilities.addSupportedRequirement('requiresModelAPIVersion', false);
|
|
17
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: false };
|
|
18
|
+
expect(capabilities.checkCompatibility(blockFlags)).toBe(true);
|
|
19
|
+
const blockFlags2: BlockCodeFeatureFlags = { requiresModelAPIVersion: true };
|
|
20
|
+
expect(capabilities.checkCompatibility(blockFlags2)).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should add a supported requirement with a specific number value', () => {
|
|
24
|
+
const capabilities = new RuntimeCapabilities();
|
|
25
|
+
capabilities.addSupportedRequirement('requiresModelAPIVersion', 2);
|
|
26
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: 2 };
|
|
27
|
+
expect(capabilities.checkCompatibility(blockFlags)).toBe(true);
|
|
28
|
+
const blockFlags2: BlockCodeFeatureFlags = { requiresModelAPIVersion: 3 };
|
|
29
|
+
expect(capabilities.checkCompatibility(blockFlags2)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should allow chaining', () => {
|
|
33
|
+
const capabilities = new RuntimeCapabilities();
|
|
34
|
+
capabilities.addSupportedRequirement('requiresModelAPIVersion').addSupportedRequirement('requiresUIAPIVersion');
|
|
35
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: true, requiresUIAPIVersion: true };
|
|
36
|
+
expect(capabilities.checkCompatibility(blockFlags)).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should support multiple values for the same requirement', () => {
|
|
40
|
+
const capabilities = new RuntimeCapabilities();
|
|
41
|
+
capabilities.addSupportedRequirement('requiresModelAPIVersion', 2);
|
|
42
|
+
capabilities.addSupportedRequirement('requiresModelAPIVersion', 3);
|
|
43
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: 2 };
|
|
44
|
+
expect(capabilities.checkCompatibility(blockFlags)).toBe(true);
|
|
45
|
+
const blockFlags2: BlockCodeFeatureFlags = { requiresModelAPIVersion: 3 };
|
|
46
|
+
expect(capabilities.checkCompatibility(blockFlags2)).toBe(true);
|
|
47
|
+
const blockFlags3: BlockCodeFeatureFlags = { requiresModelAPIVersion: 4 };
|
|
48
|
+
expect(capabilities.checkCompatibility(blockFlags3)).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('checkCompatibility and getIncompatibleFlags', () => {
|
|
53
|
+
it('should return compatible for undefined flags', () => {
|
|
54
|
+
const capabilities = new RuntimeCapabilities();
|
|
55
|
+
expect(capabilities.checkCompatibility(undefined)).toBe(true);
|
|
56
|
+
expect(capabilities.getIncompatibleFlags(undefined)).toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return compatible for empty flags object', () => {
|
|
60
|
+
const capabilities = new RuntimeCapabilities();
|
|
61
|
+
expect(capabilities.checkCompatibility({})).toBe(true);
|
|
62
|
+
expect(capabilities.getIncompatibleFlags({})).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should be compatible if requirements are met', () => {
|
|
66
|
+
const capabilities = new RuntimeCapabilities()
|
|
67
|
+
.addSupportedRequirement('requiresModelAPIVersion')
|
|
68
|
+
.addSupportedRequirement('requiresUIAPIVersion', 2);
|
|
69
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: true, requiresUIAPIVersion: 2, supportsSomething: true };
|
|
70
|
+
expect(capabilities.checkCompatibility(blockFlags)).toBe(true);
|
|
71
|
+
expect(capabilities.getIncompatibleFlags(blockFlags)).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should be incompatible if a requirement value is not met', () => {
|
|
75
|
+
const capabilities = new RuntimeCapabilities().addSupportedRequirement('requiresModelAPIVersion', 2);
|
|
76
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: 3 };
|
|
77
|
+
expect(capabilities.checkCompatibility(blockFlags)).toBe(false);
|
|
78
|
+
const incompatible = capabilities.getIncompatibleFlags(blockFlags);
|
|
79
|
+
expect(incompatible).toEqual(new Map([['requiresModelAPIVersion', 3]]));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should be incompatible if a requirement is not specified in runtime capabilities', () => {
|
|
83
|
+
const capabilities = new RuntimeCapabilities();
|
|
84
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: true };
|
|
85
|
+
expect(capabilities.checkCompatibility(blockFlags)).toBe(false);
|
|
86
|
+
const incompatible = capabilities.getIncompatibleFlags(blockFlags);
|
|
87
|
+
expect(incompatible).toEqual(new Map([['requiresModelAPIVersion', true]]));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should correctly identify multiple incompatible flags', () => {
|
|
91
|
+
const capabilities = new RuntimeCapabilities()
|
|
92
|
+
.addSupportedRequirement('requiresModelAPIVersion', true)
|
|
93
|
+
.addSupportedRequirement('requiresUIAPIVersion', 2);
|
|
94
|
+
|
|
95
|
+
const blockFlags: BlockCodeFeatureFlags = {
|
|
96
|
+
requiresModelAPIVersion: false,
|
|
97
|
+
requiresUIAPIVersion: 3,
|
|
98
|
+
requiresSomethingElse: true,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
expect(capabilities.checkCompatibility(blockFlags)).toBe(false);
|
|
102
|
+
const incompatible = capabilities.getIncompatibleFlags(blockFlags);
|
|
103
|
+
expect(incompatible).toEqual(new Map<string, number | boolean>([
|
|
104
|
+
['requiresModelAPIVersion', false],
|
|
105
|
+
['requiresUIAPIVersion', 3],
|
|
106
|
+
['requiresSomethingElse', true],
|
|
107
|
+
]));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should ignore non-requirement flags', () => {
|
|
111
|
+
const capabilities = new RuntimeCapabilities();
|
|
112
|
+
const blockFlags: BlockCodeFeatureFlags = { supportsSomething: true };
|
|
113
|
+
expect(capabilities.checkCompatibility(blockFlags)).toBe(true);
|
|
114
|
+
expect(capabilities.getIncompatibleFlags(blockFlags)).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should ignore undefined requirement flags in block', () => {
|
|
118
|
+
const capabilities = new RuntimeCapabilities();
|
|
119
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: undefined };
|
|
120
|
+
expect(capabilities.checkCompatibility(blockFlags)).toBe(true);
|
|
121
|
+
expect(capabilities.getIncompatibleFlags(blockFlags)).toBeUndefined();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should be incompatible if a requirement is not defined in capabilities', () => {
|
|
125
|
+
const capabilities = new RuntimeCapabilities(); // No requirements added.
|
|
126
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: 1 };
|
|
127
|
+
expect(capabilities.checkCompatibility(blockFlags)).toBe(false);
|
|
128
|
+
const incompatible = capabilities.getIncompatibleFlags(blockFlags);
|
|
129
|
+
expect(incompatible).toEqual(new Map([['requiresModelAPIVersion', 1]]));
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('throwIfIncompatible', () => {
|
|
134
|
+
it('should not throw if flags are compatible', () => {
|
|
135
|
+
const capabilities = new RuntimeCapabilities().addSupportedRequirement('requiresModelAPIVersion');
|
|
136
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: true };
|
|
137
|
+
expect(() => capabilities.throwIfIncompatible(blockFlags)).not.toThrow();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should throw IncompatibleFlagsError if flags are incompatible', () => {
|
|
141
|
+
const capabilities = new RuntimeCapabilities().addSupportedRequirement('requiresModelAPIVersion');
|
|
142
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: false };
|
|
143
|
+
expect(() => capabilities.throwIfIncompatible(blockFlags)).toThrow(IncompatibleFlagsError);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should throw with an error containing incompatible flags', () => {
|
|
147
|
+
const capabilities = new RuntimeCapabilities().addSupportedRequirement('requiresModelAPIVersion', 1);
|
|
148
|
+
const blockFlags: BlockCodeFeatureFlags = { requiresModelAPIVersion: 2 };
|
|
149
|
+
try {
|
|
150
|
+
capabilities.throwIfIncompatible(blockFlags);
|
|
151
|
+
// fail test if it doesn't throw
|
|
152
|
+
expect(true).toBe(false);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
expect(e).toBeInstanceOf(IncompatibleFlagsError);
|
|
155
|
+
if (e instanceof IncompatibleFlagsError) {
|
|
156
|
+
expect(e.incompatibleFlags).toEqual(new Map([['requiresModelAPIVersion', 2]]));
|
|
157
|
+
expect(e.message).toContain('requiresModelAPIVersion: 2');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { BlockCodeFeatureFlags, BlockCodeKnownFeatureFlags } from './block_flags';
|
|
2
|
+
import type { FilterKeysByPrefix } from './type_utils';
|
|
3
|
+
|
|
4
|
+
export function checkBlockFlag(flags: BlockCodeFeatureFlags | undefined, flag: keyof BlockCodeKnownFeatureFlags, flagValue: boolean | number = true): boolean {
|
|
5
|
+
if (flags === undefined) return false;
|
|
6
|
+
return flags[flag] === flagValue;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extracts all requirements from the feature flags.
|
|
11
|
+
* @param flags - The feature flags.
|
|
12
|
+
* @returns A set of requirements.
|
|
13
|
+
*/
|
|
14
|
+
export function extractAllRequirements(flags: BlockCodeFeatureFlags | undefined): Set<`requires${string}`> {
|
|
15
|
+
if (flags === undefined) return new Set();
|
|
16
|
+
return new Set(Object.entries(flags)
|
|
17
|
+
.filter(([key, value]) => key.startsWith('requires') && value === true)
|
|
18
|
+
.map(([key]) => key as `requires${string}`));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extracts all supports from the feature flags.
|
|
23
|
+
* @param flags - The feature flags.
|
|
24
|
+
* @returns A set of supports.
|
|
25
|
+
*/
|
|
26
|
+
export function extractAllSupports(flags: BlockCodeFeatureFlags | undefined): Set<`supports${string}`> {
|
|
27
|
+
if (flags === undefined) return new Set();
|
|
28
|
+
return new Set(Object.entries(flags)
|
|
29
|
+
.filter(([key, value]) => key.startsWith('supports') && value === true)
|
|
30
|
+
.map(([key]) => key as `supports${string}`));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class IncompatibleFlagsError extends Error {
|
|
34
|
+
name = 'IncompatibleFlagsError';
|
|
35
|
+
constructor(public readonly incompatibleFlags: Map<`requires${string}`, number | boolean>) {
|
|
36
|
+
super(`Some of the block requirements are not supported by the runtime: ${Array.from(incompatibleFlags.entries()).map(([key, value]) => `${key}: ${value}`).join(', ')}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A type that represents a supported requirement.
|
|
42
|
+
* @remarks
|
|
43
|
+
* This type is used to represent a supported requirement.
|
|
44
|
+
* It is a subtype of `BlockCodeKnownFeatureFlags` and is used to represent a supported requirement.
|
|
45
|
+
* It is used to represent a supported requirement.
|
|
46
|
+
*/
|
|
47
|
+
export type SupportedRequirement = FilterKeysByPrefix<BlockCodeKnownFeatureFlags, 'requires'>;
|
|
48
|
+
|
|
49
|
+
export class RuntimeCapabilities {
|
|
50
|
+
private readonly supportedRequirements: Map<`requires${string}`, Set<number | boolean>> = new Map();
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Adds a supported requirement to the runtime capabilities.
|
|
54
|
+
* @param requirement - The requirement.
|
|
55
|
+
* @param value - The value of the requirement. If not provided, defaults to true.
|
|
56
|
+
*/
|
|
57
|
+
public addSupportedRequirement(requirement: SupportedRequirement, value: number | boolean = true): this {
|
|
58
|
+
if (!this.supportedRequirements.has(requirement)) {
|
|
59
|
+
this.supportedRequirements.set(requirement, new Set());
|
|
60
|
+
}
|
|
61
|
+
this.supportedRequirements.get(requirement)!.add(value);
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns a map of incompatible flags. If the block flags are compatible, returns undefined.
|
|
67
|
+
* @param blockFlags - The block flags.
|
|
68
|
+
* @returns A map of incompatible flags, or undefined if the block flags are compatible.
|
|
69
|
+
*/
|
|
70
|
+
public getIncompatibleFlags(blockFlags: BlockCodeFeatureFlags | undefined): Map<`requires${string}`, number | boolean> | undefined {
|
|
71
|
+
if (blockFlags === undefined) return undefined;
|
|
72
|
+
const incompatibleFlags = new Map<`requires${string}`, number | boolean>();
|
|
73
|
+
for (const [key, value] of Object.entries(blockFlags)) {
|
|
74
|
+
if (key.startsWith('requires')) {
|
|
75
|
+
if (value === undefined) continue;
|
|
76
|
+
const supportedValues = this.supportedRequirements.get(key as `requires${string}`);
|
|
77
|
+
if (supportedValues === undefined || !supportedValues.has(value as number | boolean)) {
|
|
78
|
+
incompatibleFlags.set(key as `requires${string}`, value as number | boolean);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return incompatibleFlags.size === 0 ? undefined : incompatibleFlags;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Checks if the block flags are compatible with the runtime capabilities.
|
|
87
|
+
* @param blockFlags - The block flags.
|
|
88
|
+
* @returns True if the block flags are compatible, false otherwise.
|
|
89
|
+
*/
|
|
90
|
+
public checkCompatibility(blockFlags: BlockCodeFeatureFlags | undefined): boolean {
|
|
91
|
+
return this.getIncompatibleFlags(blockFlags) === undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Throws an error if the block flags are incompatible with the runtime capabilities.
|
|
96
|
+
* @param blockFlags - The block flags.
|
|
97
|
+
* @throws IncompatibleFlagsError if the block flags are incompatible with the runtime capabilities.
|
|
98
|
+
*/
|
|
99
|
+
public throwIfIncompatible(blockFlags: BlockCodeFeatureFlags | undefined) {
|
|
100
|
+
const incompatibleFlags = this.getIncompatibleFlags(blockFlags);
|
|
101
|
+
if (incompatibleFlags !== undefined)
|
|
102
|
+
throw new IncompatibleFlagsError(incompatibleFlags);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper to filter keys of a type by a prefix.
|
|
3
|
+
*/
|
|
4
|
+
export type FilterKeysByPrefix<T, P extends string> = keyof {
|
|
5
|
+
[K in keyof T as K extends `${P}${string}` ? K : never]: T[K];
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Helper to assert that two types are equal. This will cause a compile-time error if they are not.
|
|
10
|
+
* We use this to ensure all feature flags are accounted for in the arrays below.
|
|
11
|
+
*/
|
|
12
|
+
export type AssertKeysEqual<T, U> = (<V>() => V extends T ? 1 : 2) extends <V>() => V extends U ? 1 : 2
|
|
13
|
+
? unknown
|
|
14
|
+
: { error: 'Feature flag definitions are out of sync'; expected: T; actual: U };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Checks if two types are exactly equal.
|
|
18
|
+
* Returns 'true' if they are, 'false' otherwise.
|
|
19
|
+
*/
|
|
20
|
+
export type Is<T, U> = (<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2
|
|
21
|
+
? true
|
|
22
|
+
: false;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Checks if T is a subtype of U.
|
|
26
|
+
* Returns 'true' if it is, 'false' otherwise.
|
|
27
|
+
*/
|
|
28
|
+
export type IsSubtypeOf<T, U> = T extends U ? true : false;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Asserts that a condition is true at compile time.
|
|
32
|
+
* Causes a compile error if T is not 'true'.
|
|
33
|
+
*/
|
|
34
|
+
export type Assert<T extends true> = T;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Helper to create a union type of two array value types.
|
|
38
|
+
*/
|
|
39
|
+
export type ArrayTypeUnion<T extends readonly any[], U extends readonly any[]> = T[number] extends never
|
|
40
|
+
? U[number]
|
|
41
|
+
: U[number] extends never
|
|
42
|
+
? T[number]
|
|
43
|
+
: T[number] | U[number];
|