@milaboratories/pl-model-common 1.15.9 → 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.
Files changed (44) hide show
  1. package/dist/{base32-encode.d.ts → base32_encode.d.ts} +1 -1
  2. package/dist/{base32-encode.d.ts.map → base32_encode.d.ts.map} +1 -1
  3. package/dist/bmodel/block_config.d.ts +42 -0
  4. package/dist/bmodel/block_config.d.ts.map +1 -0
  5. package/dist/bmodel/code.d.ts +11 -0
  6. package/dist/bmodel/code.d.ts.map +1 -0
  7. package/dist/bmodel/container.d.ts +46 -0
  8. package/dist/bmodel/container.d.ts.map +1 -0
  9. package/dist/bmodel/index.d.ts +6 -0
  10. package/dist/bmodel/index.d.ts.map +1 -0
  11. package/dist/bmodel/normalization.d.ts +10 -0
  12. package/dist/bmodel/normalization.d.ts.map +1 -0
  13. package/dist/{block.d.ts → bmodel/types.d.ts} +1 -1
  14. package/dist/bmodel/types.d.ts.map +1 -0
  15. package/dist/flags/block_flags.d.ts +20 -0
  16. package/dist/flags/block_flags.d.ts.map +1 -0
  17. package/dist/flags/flag_utils.d.ts +56 -0
  18. package/dist/flags/flag_utils.d.ts.map +1 -0
  19. package/dist/flags/index.d.ts +4 -0
  20. package/dist/flags/index.d.ts.map +1 -0
  21. package/dist/flags/type_utils.d.ts +35 -0
  22. package/dist/flags/type_utils.d.ts.map +1 -0
  23. package/dist/index.d.ts +3 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +732 -577
  28. package/dist/index.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/src/bmodel/block_config.ts +69 -0
  31. package/src/bmodel/code.ts +12 -0
  32. package/src/bmodel/container.ts +58 -0
  33. package/src/bmodel/index.ts +5 -0
  34. package/src/bmodel/normalization.ts +111 -0
  35. package/src/flags/block_flags.ts +43 -0
  36. package/src/flags/flag_utils.test.ts +162 -0
  37. package/src/flags/flag_utils.ts +104 -0
  38. package/src/flags/index.ts +3 -0
  39. package/src/flags/type_utils.ts +43 -0
  40. package/src/index.ts +3 -1
  41. package/src/plid.ts +1 -1
  42. package/dist/block.d.ts.map +0 -1
  43. /package/src/{base32-encode.ts → base32_encode.ts} +0 -0
  44. /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.15.9",
3
+ "version": "1.16.0",
4
4
  "description": "Platforma SDK Model",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",
@@ -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,5 @@
1
+ export * from './block_config';
2
+ export * from './code';
3
+ export * from './types';
4
+ export * from './container';
5
+ export * from './normalization';
@@ -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
+ }
@@ -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,3 @@
1
+ export * from './block_flags';
2
+ export * from './flag_utils';
3
+ export * from './type_utils';
@@ -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];
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from './block';
1
+ export * from './bmodel';
2
2
  export * from './block_state';
3
3
  export * from './utag';
4
4
  export * from './drivers';
@@ -11,3 +11,5 @@ export * from './value_or_error';
11
11
  export * from './plid';
12
12
  export * from './json';
13
13
  export * from './errors';
14
+ export * from './flags';
15
+ export * from './bmodel';
package/src/plid.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { base32Encode } from './base32-encode';
2
+ import { base32Encode } from './base32_encode';
3
3
 
4
4
  /** Number of raw bytes in the PlId. */
5
5
  export const PlIdBytes = 15;
@@ -1 +0,0 @@
1
- {"version":3,"file":"block.d.ts","sourceRoot":"","sources":["../src/block.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,OAAO,GAAG,kBAAkB,CAAC"}
File without changes
File without changes