vestjs-runtime 1.7.0 → 2.0.1

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 (136) hide show
  1. package/IsolateSerializer/package.json +12 -8
  2. package/README.md +3 -1
  3. package/dist/IsolateKeys-B21aPuBk.mjs +23 -0
  4. package/dist/IsolateKeys-B21aPuBk.mjs.map +1 -0
  5. package/dist/IsolateKeys-CCvALpZC.cjs +35 -0
  6. package/dist/IsolateKeys-CCvALpZC.cjs.map +1 -0
  7. package/dist/IsolateSerializer-B1hE3gmT.mjs +1004 -0
  8. package/dist/IsolateSerializer-B1hE3gmT.mjs.map +1 -0
  9. package/dist/IsolateSerializer-pbEf5gB2.cjs +1121 -0
  10. package/dist/IsolateSerializer-pbEf5gB2.cjs.map +1 -0
  11. package/dist/chunk-CLMFDpHK.mjs +18 -0
  12. package/dist/exports/IsolateSerializer.cjs +4 -0
  13. package/dist/exports/IsolateSerializer.mjs +4 -0
  14. package/dist/exports/test-utils.cjs +21 -0
  15. package/dist/exports/test-utils.cjs.map +1 -0
  16. package/dist/exports/test-utils.mjs +21 -0
  17. package/dist/exports/test-utils.mjs.map +1 -0
  18. package/dist/vestjs-runtime.cjs +153 -0
  19. package/dist/vestjs-runtime.cjs.map +1 -0
  20. package/dist/vestjs-runtime.mjs +117 -0
  21. package/dist/vestjs-runtime.mjs.map +1 -0
  22. package/docs/IsolateRegistry.docs.md +146 -0
  23. package/docs/Isolates.md +97 -0
  24. package/package.json +43 -88
  25. package/src/Bus.ts +46 -0
  26. package/src/Isolate/Isolate.ts +163 -0
  27. package/src/Isolate/IsolateFocused.ts +93 -0
  28. package/src/Isolate/IsolateIndexer.ts +42 -0
  29. package/src/Isolate/IsolateInspector.ts +93 -0
  30. package/src/Isolate/IsolateKeys.ts +18 -0
  31. package/src/Isolate/IsolateMutator.ts +165 -0
  32. package/src/Isolate/IsolateRegistry.ts +176 -0
  33. package/src/Isolate/IsolateReorderable.ts +11 -0
  34. package/src/Isolate/IsolateSelectors.ts +25 -0
  35. package/src/Isolate/IsolateStateMachine.ts +30 -0
  36. package/src/Isolate/IsolateStatus.ts +8 -0
  37. package/src/Isolate/IsolateTransient.ts +27 -0
  38. package/src/Isolate/IsolateTypes.ts +33 -0
  39. package/src/Isolate/__tests__/Isolate.test.ts +123 -0
  40. package/src/Isolate/__tests__/IsolateFocused.test.ts +199 -0
  41. package/src/Isolate/__tests__/IsolateInspector.test.ts +136 -0
  42. package/src/Isolate/__tests__/IsolateMutator.test.ts +164 -0
  43. package/src/Isolate/__tests__/IsolatePropagation.test.ts +170 -0
  44. package/src/Isolate/__tests__/IsolateReorderable.test.ts +111 -0
  45. package/src/Isolate/__tests__/IsolateSelectors.test.ts +72 -0
  46. package/src/Isolate/__tests__/IsolateStatus.test.ts +44 -0
  47. package/src/Isolate/__tests__/IsolateTransient.test.ts +58 -0
  48. package/src/Isolate/__tests__/__snapshots__/asyncIsolate.test.ts.snap +71 -0
  49. package/src/Isolate/__tests__/asyncIsolate.test.ts +85 -0
  50. package/src/IsolateWalker.ts +359 -0
  51. package/src/Orchestrator/RuntimeStates.ts +4 -0
  52. package/src/Reconciler.ts +178 -0
  53. package/src/RuntimeEvents.ts +9 -0
  54. package/src/VestRuntime.ts +421 -0
  55. package/src/__tests__/Bus.test.ts +57 -0
  56. package/src/__tests__/IsolateWalker.iterative.test.ts +77 -0
  57. package/src/__tests__/IsolateWalker.test.ts +418 -0
  58. package/src/__tests__/Reconciler.test.ts +193 -0
  59. package/src/__tests__/Reconciler.transient.test.ts +166 -0
  60. package/src/__tests__/VestRuntime.test.ts +212 -0
  61. package/src/__tests__/VestRuntimeStateMachine.test.ts +36 -0
  62. package/src/__tests__/vestjs-runtime.test.ts +19 -0
  63. package/src/errors/ErrorStrings.ts +6 -0
  64. package/src/exports/IsolateSerializer.ts +131 -0
  65. package/src/exports/__tests__/IsolateSerializer.test.ts +334 -0
  66. package/src/exports/__tests__/IsolateSerializer.transient.test.ts +101 -0
  67. package/src/exports/__tests__/__snapshots__/IsolateSerializer.test.ts.snap +5 -0
  68. package/src/exports/test-utils.ts +17 -0
  69. package/src/vestjs-runtime.ts +28 -0
  70. package/test-utils/package.json +12 -8
  71. package/types/Isolate-DChR7h5K.d.mts +58 -0
  72. package/types/Isolate-DChR7h5K.d.mts.map +1 -0
  73. package/types/Isolate-HYIh82M8.d.cts +58 -0
  74. package/types/Isolate-HYIh82M8.d.cts.map +1 -0
  75. package/types/IsolateSerializer-BCg01Px5.d.mts +13 -0
  76. package/types/IsolateSerializer-BCg01Px5.d.mts.map +1 -0
  77. package/types/IsolateSerializer-CQpP6A4m.d.cts +13 -0
  78. package/types/IsolateSerializer-CQpP6A4m.d.cts.map +1 -0
  79. package/types/exports/IsolateSerializer.d.cts +3 -0
  80. package/types/exports/IsolateSerializer.d.mts +3 -0
  81. package/types/exports/test-utils.d.cts +7 -0
  82. package/types/exports/test-utils.d.cts.map +1 -0
  83. package/types/exports/test-utils.d.mts +7 -0
  84. package/types/exports/test-utils.d.mts.map +1 -0
  85. package/types/vestjs-runtime.d.cts +372 -0
  86. package/types/vestjs-runtime.d.cts.map +1 -0
  87. package/types/vestjs-runtime.d.mts +370 -0
  88. package/types/vestjs-runtime.d.mts.map +1 -0
  89. package/types/vestjs-runtime.d.ts +351 -257
  90. package/vitest.config.ts +9 -17
  91. package/dist/cjs/IsolateSerializer.development.js +0 -135
  92. package/dist/cjs/IsolateSerializer.development.js.map +0 -1
  93. package/dist/cjs/IsolateSerializer.js +0 -6
  94. package/dist/cjs/IsolateSerializer.production.js +0 -2
  95. package/dist/cjs/IsolateSerializer.production.js.map +0 -1
  96. package/dist/cjs/package.json +0 -1
  97. package/dist/cjs/test-utils.development.js +0 -61
  98. package/dist/cjs/test-utils.development.js.map +0 -1
  99. package/dist/cjs/test-utils.js +0 -6
  100. package/dist/cjs/test-utils.production.js +0 -2
  101. package/dist/cjs/test-utils.production.js.map +0 -1
  102. package/dist/cjs/vestjs-runtime.development.js +0 -686
  103. package/dist/cjs/vestjs-runtime.development.js.map +0 -1
  104. package/dist/cjs/vestjs-runtime.js +0 -6
  105. package/dist/cjs/vestjs-runtime.production.js +0 -2
  106. package/dist/cjs/vestjs-runtime.production.js.map +0 -1
  107. package/dist/es/IsolateSerializer.development.js +0 -133
  108. package/dist/es/IsolateSerializer.development.js.map +0 -1
  109. package/dist/es/IsolateSerializer.production.js +0 -2
  110. package/dist/es/IsolateSerializer.production.js.map +0 -1
  111. package/dist/es/package.json +0 -1
  112. package/dist/es/test-utils.development.js +0 -59
  113. package/dist/es/test-utils.development.js.map +0 -1
  114. package/dist/es/test-utils.production.js +0 -2
  115. package/dist/es/test-utils.production.js.map +0 -1
  116. package/dist/es/vestjs-runtime.development.js +0 -675
  117. package/dist/es/vestjs-runtime.development.js.map +0 -1
  118. package/dist/es/vestjs-runtime.production.js +0 -2
  119. package/dist/es/vestjs-runtime.production.js.map +0 -1
  120. package/dist/umd/IsolateSerializer.development.js +0 -138
  121. package/dist/umd/IsolateSerializer.development.js.map +0 -1
  122. package/dist/umd/IsolateSerializer.production.js +0 -2
  123. package/dist/umd/IsolateSerializer.production.js.map +0 -1
  124. package/dist/umd/test-utils.development.js +0 -67
  125. package/dist/umd/test-utils.development.js.map +0 -1
  126. package/dist/umd/test-utils.production.js +0 -2
  127. package/dist/umd/test-utils.production.js.map +0 -1
  128. package/dist/umd/vestjs-runtime.development.js +0 -688
  129. package/dist/umd/vestjs-runtime.development.js.map +0 -1
  130. package/dist/umd/vestjs-runtime.production.js +0 -2
  131. package/dist/umd/vestjs-runtime.production.js.map +0 -1
  132. package/types/IsolateSerializer.d.ts +0 -42
  133. package/types/IsolateSerializer.d.ts.map +0 -1
  134. package/types/test-utils.d.ts +0 -37
  135. package/types/test-utils.d.ts.map +0 -1
  136. package/types/vestjs-runtime.d.ts.map +0 -1
@@ -0,0 +1,93 @@
1
+ import {
2
+ asArray,
3
+ Maybe,
4
+ Nullish,
5
+ OneOrMoreOf,
6
+ noop,
7
+ isNotEmpty,
8
+ isStringValue,
9
+ } from 'vest-utils';
10
+
11
+ import { IsolateTransient } from './IsolateTransient';
12
+ import { TIsolate } from './Isolate';
13
+ import { IsolateKeys } from './IsolateKeys';
14
+
15
+ export const VestIsolateTypeFocused = 'Focused';
16
+
17
+ export enum FocusModes {
18
+ SKIP = 'skip',
19
+ ONLY = 'only',
20
+ }
21
+
22
+ export type FocusMatchExclusion = Maybe<OneOrMoreOf<string>>;
23
+
24
+ export type TIsolateFocused = TIsolate<IsolateFocusedPayload>;
25
+
26
+ type IsolateFocusedPayload = {
27
+ focusMode: FocusModes;
28
+ match: FocusMatchExclusion;
29
+ matchAll: boolean;
30
+ };
31
+
32
+ /**
33
+ * Creates a focused isolate.
34
+ * Focused isolates are transient because they only affect the current run
35
+ * and do not need to be preserved in history or appearing in the suite result.
36
+ */
37
+ export function IsolateFocused(
38
+ focusMode: FocusModes,
39
+ match?: true | FocusMatchExclusion,
40
+ ): TIsolateFocused | undefined {
41
+ const matchedFields = asArray(match).filter(isStringValue).filter(isNotEmpty);
42
+
43
+ const matchAll = match === true;
44
+
45
+ // If there are no fields to match and matchAll is false,
46
+ // skip creating the isolate entirely — it would be a no-op.
47
+ if (!isNotEmpty(matchedFields) && !matchAll) {
48
+ return undefined;
49
+ }
50
+
51
+ return IsolateTransient<IsolateFocusedPayload>(noop, VestIsolateTypeFocused, {
52
+ focusMode,
53
+ match: matchedFields,
54
+ matchAll,
55
+ });
56
+ }
57
+
58
+ export class FocusSelectors {
59
+ static isSkipFocused(
60
+ focus: Nullish<TIsolateFocused>,
61
+ fieldName?: string,
62
+ ): boolean {
63
+ if (!focus) return false;
64
+ const data = focus.data;
65
+ if (!data || data.focusMode !== FocusModes.SKIP) return false;
66
+ if (data.matchAll) return true;
67
+ return hasFocus(focus, fieldName);
68
+ }
69
+ static isOnlyFocused(
70
+ focus: Nullish<TIsolateFocused>,
71
+ fieldName?: string,
72
+ ): boolean {
73
+ if (!focus) return false;
74
+ const data = focus.data;
75
+ if (!data || data.focusMode !== FocusModes.ONLY) return false;
76
+ if (data.matchAll) return true;
77
+ return hasFocus(focus, fieldName);
78
+ }
79
+
80
+ static isIsolateFocused(isolate: TIsolate): isolate is TIsolateFocused {
81
+ return isolate[IsolateKeys.Type] === VestIsolateTypeFocused;
82
+ }
83
+ }
84
+
85
+ function hasFocus(
86
+ focus: Nullish<TIsolateFocused>,
87
+ fieldName?: string,
88
+ ): boolean {
89
+ const match = asArray(focus?.data?.match);
90
+ if (!match.length) return false;
91
+
92
+ return !fieldName || match.includes(fieldName as string);
93
+ }
@@ -0,0 +1,42 @@
1
+ import { isNullish, Nullable } from 'vest-utils';
2
+
3
+ import { TIsolate } from './IsolateTypes';
4
+
5
+ export function createHistoryIndex(
6
+ children: Nullable<TIsolate[]>,
7
+ ): Map<string, TIsolate | TIsolate[]> {
8
+ const index = new Map<string, TIsolate | TIsolate[]>();
9
+
10
+ if (!children) {
11
+ return index;
12
+ }
13
+
14
+ for (const child of children) {
15
+ addToIndex(index, child);
16
+ }
17
+
18
+ return index;
19
+ }
20
+
21
+ function addToIndex(
22
+ index: Map<string, TIsolate | TIsolate[]>,
23
+ isolate: TIsolate,
24
+ ): void {
25
+ const { key } = isolate;
26
+
27
+ if (isNullish(key)) {
28
+ return;
29
+ }
30
+
31
+ const existing = index.get(key);
32
+
33
+ if (existing) {
34
+ if (Array.isArray(existing)) {
35
+ existing.push(isolate);
36
+ } else {
37
+ index.set(key, [existing, isolate]);
38
+ }
39
+ } else {
40
+ index.set(key, isolate);
41
+ }
42
+ }
@@ -0,0 +1,93 @@
1
+ import { Nullable, isNotNullish, isNullish } from 'vest-utils';
2
+
3
+ import { TIsolate } from './Isolate';
4
+ import { IsolateStatus } from './IsolateStatus';
5
+
6
+ export class IsolateInspector {
7
+ static at(isolate: Nullable<TIsolate>, at: number): Nullable<TIsolate> {
8
+ if (isNullish(isolate)) {
9
+ return null;
10
+ }
11
+ return isolate.children?.[at] ?? null;
12
+ }
13
+
14
+ static cursor(isolate: Nullable<TIsolate>): number {
15
+ if (isNullish(isolate)) {
16
+ return 0;
17
+ }
18
+ return isolate.children?.length ?? 0;
19
+ }
20
+
21
+ static canReorder<I extends TIsolate>(isolate: Nullable<I>): boolean {
22
+ if (isNullish(isolate)) {
23
+ return false;
24
+ }
25
+
26
+ return IsolateInspector.allowsReorder(isolate.parent);
27
+ }
28
+
29
+ static allowsReorder<I extends Record<any, any>>(
30
+ isolate: Nullable<I>,
31
+ ): boolean {
32
+ return isolate?.allowReorder === true;
33
+ }
34
+
35
+ static usesKey(isolate: Nullable<TIsolate>): boolean {
36
+ if (isNullish(isolate)) {
37
+ return false;
38
+ }
39
+ return isNotNullish(isolate.key);
40
+ }
41
+
42
+ static getChildByKey(
43
+ isolate: Nullable<TIsolate>,
44
+ key: string,
45
+ ): Nullable<TIsolate> {
46
+ if (isNullish(isolate)) {
47
+ return null;
48
+ }
49
+ return isolate.keys?.[key] ?? null;
50
+ }
51
+
52
+ static getStatus(isolate: Nullable<TIsolate>): IsolateStatus {
53
+ if (isNullish(isolate)) {
54
+ return IsolateStatus.INITIAL;
55
+ }
56
+ return isolate.status ?? IsolateStatus.INITIAL;
57
+ }
58
+
59
+ static statusEquals(
60
+ isolate: Nullable<TIsolate>,
61
+ status: IsolateStatus,
62
+ ): boolean {
63
+ return IsolateInspector.getStatus(isolate) === status;
64
+ }
65
+
66
+ static isPending(isolate: Nullable<TIsolate>): boolean {
67
+ return IsolateInspector.statusEquals(isolate, IsolateStatus.PENDING);
68
+ }
69
+
70
+ static isHasPending(isolate: Nullable<TIsolate>): boolean {
71
+ return IsolateInspector.statusEquals(isolate, IsolateStatus.HAS_PENDING);
72
+ }
73
+
74
+ static hasPending(isolate: Nullable<TIsolate>): boolean {
75
+ return (
76
+ IsolateInspector.isPending(isolate) ||
77
+ IsolateInspector.isHasPending(isolate)
78
+ );
79
+ }
80
+
81
+ static hasActiveChildren(isolate: Nullable<TIsolate>): boolean {
82
+ if (isNullish(isolate) || isNullish(isolate.children)) {
83
+ return false;
84
+ }
85
+
86
+ // Check if ANY immediate child is currently PENDING or HAS_PENDING
87
+ return isolate.children.some(child => IsolateInspector.hasPending(child));
88
+ }
89
+
90
+ static getParent(isolate: Nullable<TIsolate>): Nullable<TIsolate> {
91
+ return isolate?.parent ?? null;
92
+ }
93
+ }
@@ -0,0 +1,18 @@
1
+ export enum IsolateKeys {
2
+ Type = '$type',
3
+ Keys = 'keys',
4
+ Key = 'key',
5
+ Parent = 'parent',
6
+ Data = 'data',
7
+ AllowReorder = 'allowReorder',
8
+ Transient = 'transient',
9
+ Status = 'status',
10
+ AbortController = 'abortController',
11
+ Children = 'children',
12
+ }
13
+
14
+ export const ExcludedFromDump = new Set([
15
+ IsolateKeys.AbortController,
16
+ IsolateKeys.Parent,
17
+ IsolateKeys.Keys,
18
+ ]);
@@ -0,0 +1,165 @@
1
+ import {
2
+ Nullable,
3
+ invariant,
4
+ isNullish,
5
+ makeResult,
6
+ Result,
7
+ isFailure,
8
+ } from 'vest-utils';
9
+
10
+ import { RuntimeApi } from '../VestRuntime';
11
+
12
+ import { IsolateStateMachine } from './IsolateStateMachine';
13
+ import { IsolateStatus } from './IsolateStatus';
14
+ import { IsolateInspector } from './IsolateInspector';
15
+
16
+ import { TIsolate } from './Isolate';
17
+
18
+ function bubbleUpPending(isolate: Nullable<TIsolate>): void {
19
+ if (isNullish(isolate)) {
20
+ return;
21
+ }
22
+ // If parent is already HAS_PENDING, we can stop the upward traversal.
23
+ if (IsolateInspector.isHasPending(isolate)) {
24
+ return;
25
+ }
26
+
27
+ const result = IsolateMutator.setHasPending(isolate);
28
+
29
+ if (isFailure(result)) {
30
+ return;
31
+ }
32
+
33
+ bubbleUpPending(isolate.parent);
34
+ }
35
+
36
+ function bubbleUpDone(isolate: Nullable<TIsolate>): void {
37
+ if (isNullish(isolate)) {
38
+ return;
39
+ }
40
+
41
+ if (!IsolateInspector.isHasPending(isolate)) {
42
+ return;
43
+ }
44
+
45
+ const result = IsolateMutator.setStatus(isolate, IsolateStatus.DONE, isolate);
46
+
47
+ if (isFailure(result)) {
48
+ return;
49
+ }
50
+
51
+ bubbleUpDone(isolate.parent);
52
+ }
53
+
54
+ export class IsolateMutator {
55
+ static setParent(isolate: TIsolate, parent: Nullable<TIsolate>): TIsolate {
56
+ isolate.parent = parent;
57
+ return isolate;
58
+ }
59
+
60
+ static saveOutput(isolate: TIsolate, output: any): TIsolate {
61
+ isolate.output = output;
62
+ return isolate;
63
+ }
64
+
65
+ static setKey(isolate: TIsolate, key: Nullable<string>): TIsolate {
66
+ isolate.key = key;
67
+ return isolate;
68
+ }
69
+
70
+ static addChild(isolate: TIsolate, child: TIsolate): void {
71
+ invariant(isolate);
72
+
73
+ isolate.children = isolate.children ?? [];
74
+
75
+ isolate.children.push(child);
76
+ IsolateMutator.setParent(child, isolate);
77
+
78
+ if (IsolateInspector.hasPending(child)) {
79
+ bubbleUpPending(isolate);
80
+ }
81
+ }
82
+
83
+ static removeChild(isolate: TIsolate, node: TIsolate): void {
84
+ isolate.children =
85
+ isolate.children?.filter(child => child !== node) ?? null;
86
+ }
87
+
88
+ static addChildKey(isolate: TIsolate, key: string, node: TIsolate): void {
89
+ invariant(isolate);
90
+
91
+ isolate.keys = isolate.keys ?? {};
92
+
93
+ isolate.keys[key] = node;
94
+ }
95
+
96
+ static slice(isolate: TIsolate, at: number): void {
97
+ if (isNullish(isolate.children)) {
98
+ return;
99
+ }
100
+ isolate.children.length = at;
101
+ }
102
+
103
+ static setData(isolate: TIsolate, data: any): void {
104
+ isolate.data = data;
105
+ }
106
+
107
+ static abort(isolate: TIsolate, reason?: string): void {
108
+ if (isNullish(isolate.abortController)) {
109
+ return;
110
+ }
111
+ isolate.abortController.abort(reason);
112
+ }
113
+
114
+ static setStatus(
115
+ isolate: TIsolate,
116
+ status: IsolateStatus,
117
+ payload?: any,
118
+ ): Result<IsolateStatus, string> {
119
+ if (isolate.status === status) {
120
+ return makeResult.Err(`Isolate status is already ${status}`);
121
+ }
122
+
123
+ isolate.status = IsolateStateMachine.staticTransition(
124
+ isolate.status ?? IsolateStatus.INITIAL,
125
+ status,
126
+ payload,
127
+ ) as IsolateStatus;
128
+
129
+ return makeResult.Ok(isolate.status);
130
+ }
131
+
132
+ static setPending(isolate: TIsolate): void {
133
+ const result = IsolateMutator.setStatus(isolate, IsolateStatus.PENDING);
134
+
135
+ if (isFailure(result)) {
136
+ return;
137
+ }
138
+
139
+ RuntimeApi.registerPending(isolate);
140
+
141
+ // Bubble up the HAS_PENDING status to all ancestors.
142
+ bubbleUpPending(isolate.parent);
143
+ }
144
+
145
+ static setHasPending(isolate: TIsolate): Result<IsolateStatus, string> {
146
+ return IsolateMutator.setStatus(isolate, IsolateStatus.HAS_PENDING);
147
+ }
148
+
149
+ static setDone(isolate: TIsolate): void {
150
+ const result = IsolateMutator.setStatus(
151
+ isolate,
152
+ IsolateStatus.DONE,
153
+ isolate,
154
+ );
155
+
156
+ if (isFailure(result)) {
157
+ return;
158
+ }
159
+
160
+ // Bubble up the DONE status to ancestors if no other children are pending.
161
+ bubbleUpDone(isolate.parent);
162
+
163
+ RuntimeApi.removePending(isolate);
164
+ }
165
+ }
@@ -0,0 +1,176 @@
1
+ import { Nullable, isEmptySet, isNotEmptySet } from 'vest-utils';
2
+
3
+ import { useAvailableRoot } from '../VestRuntime';
4
+ import { TIsolate } from './Isolate';
5
+
6
+ /**
7
+ * A registry index, mapping group keys (like field names) to sets of isolates.
8
+ */
9
+ export type RegistryIndex = Map<string, Set<TIsolate>>;
10
+
11
+ /**
12
+ * Configuration for a registry category.
13
+ */
14
+ export type RegistryCategoryConfig = {
15
+ predicate: (isolate: TIsolate) => boolean;
16
+ getKey: (isolate: TIsolate) => string;
17
+ };
18
+
19
+ /**
20
+ * Updates the registration of an isolate in all relevant indices based on provided predicates.
21
+ *
22
+ * @param isolate - The isolate node to update.
23
+ * @param predicates - A record of category keys and their corresponding predicate configurations.
24
+ */
25
+ export function useUpdateRegistry(
26
+ isolate: TIsolate,
27
+ predicates: Record<string, RegistryCategoryConfig>,
28
+ ) {
29
+ const root = useGetAvailableRoot();
30
+
31
+ if (!root) return;
32
+
33
+ for (const key in predicates) {
34
+ useUpdateCategoryInRegistry(root, key, predicates[key], isolate);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Retrieves isolates from the registry based on a category and an optional key.
40
+ */
41
+ export function useGetFromRegistry(
42
+ category: string,
43
+ key?: string,
44
+ ): Set<TIsolate> {
45
+ const root = useGetAvailableRoot();
46
+ if (!root) return new Set();
47
+
48
+ const index = useEnsureRegistryIndex(root, category);
49
+
50
+ if (key) {
51
+ return index.get(key) ?? new Set();
52
+ }
53
+
54
+ const allNodes = new Set<TIsolate>();
55
+ index.forEach(set => set.forEach(node => allNodes.add(node)));
56
+ return allNodes;
57
+ }
58
+
59
+ /**
60
+ * Checks if the registry contains any isolates for a given category and optional key.
61
+ */
62
+ export function useHasFromRegistry(category: string, key?: string): boolean {
63
+ return isNotEmptySet(useGetFromRegistry(category, key));
64
+ }
65
+
66
+ /**
67
+ * Removes all entries for a specific key from the registry.
68
+ */
69
+ export function useRemoveFieldFromRegistry(key: string): void {
70
+ const root = useGetAvailableRoot();
71
+
72
+ if (!root) return;
73
+
74
+ useForEachRegistryIndex(root, index => index.delete(key));
75
+ }
76
+
77
+ /**
78
+ * Clears all registry entries from the root isolate.
79
+ */
80
+ export function useClearRegistry(root: TIsolate) {
81
+ useForEachRegistryIndex(root, index => index.clear());
82
+ }
83
+
84
+ // Internal Helpers
85
+
86
+ const GENERIC_REGISTRY_KEY = '_generic';
87
+
88
+ function useGetAvailableRoot(): TIsolate | undefined {
89
+ try {
90
+ return useAvailableRoot();
91
+ } catch {
92
+ return undefined;
93
+ }
94
+ }
95
+
96
+ function useUpdateCategoryInRegistry(
97
+ root: TIsolate,
98
+ category: string,
99
+ config: RegistryCategoryConfig,
100
+ isolate: TIsolate,
101
+ ) {
102
+ const isMatching = config.predicate(isolate);
103
+ const index = useEnsureRegistryIndex(root, category);
104
+ const key = config.getKey(isolate);
105
+
106
+ if (isMatching) {
107
+ useAddNodeToRegistryIndex(index, key, isolate);
108
+ } else {
109
+ useRemoveNodeFromRegistryIndex(index, key, isolate);
110
+ }
111
+ }
112
+
113
+ function useEnsureRegistryIndex(
114
+ root: TIsolate,
115
+ category: string,
116
+ ): RegistryIndex {
117
+ const registryKey = `registry_${category}`;
118
+
119
+ if (!root.data[registryKey]) {
120
+ useDefineEnumerable(root.data, registryKey, new Map());
121
+ }
122
+
123
+ return root.data[registryKey];
124
+ }
125
+
126
+ function useForEachRegistryIndex(
127
+ root: TIsolate,
128
+ callback: (index: RegistryIndex) => void,
129
+ ) {
130
+ for (const key in root.data) {
131
+ if (key.startsWith('registry_')) {
132
+ callback(root.data[key]);
133
+ }
134
+ }
135
+ }
136
+
137
+ function useAddNodeToRegistryIndex(
138
+ index: RegistryIndex,
139
+ key: Nullable<string>,
140
+ isolate: TIsolate,
141
+ ) {
142
+ const indexKey = key ?? GENERIC_REGISTRY_KEY;
143
+ let entry = index.get(indexKey);
144
+
145
+ if (!entry) {
146
+ entry = new Set();
147
+ index.set(indexKey, entry);
148
+ }
149
+
150
+ entry.add(isolate);
151
+ }
152
+
153
+ function useRemoveNodeFromRegistryIndex(
154
+ index: RegistryIndex,
155
+ key: Nullable<string>,
156
+ isolate: TIsolate,
157
+ ) {
158
+ const indexKey = key ?? GENERIC_REGISTRY_KEY;
159
+ const entry = index.get(indexKey);
160
+
161
+ if (entry) {
162
+ entry.delete(isolate);
163
+ if (isEmptySet(entry)) {
164
+ index.delete(indexKey);
165
+ }
166
+ }
167
+ }
168
+
169
+ function useDefineEnumerable(obj: any, key: string, value: any) {
170
+ Object.defineProperty(obj, key, {
171
+ configurable: true,
172
+ enumerable: true,
173
+ value,
174
+ writable: true,
175
+ });
176
+ }
@@ -0,0 +1,11 @@
1
+ import { CB } from 'vest-utils';
2
+
3
+ import { Isolate } from './Isolate';
4
+
5
+ export function IsolateReorderable(
6
+ callback: CB,
7
+ type = 'Reorderable',
8
+ payload: Record<string, any> = {},
9
+ ) {
10
+ return Isolate.create(type, callback, { ...payload, allowReorder: true });
11
+ }
@@ -0,0 +1,25 @@
1
+ import { Maybe } from 'vest-utils';
2
+
3
+ import { TIsolate } from './Isolate';
4
+ import { IsolateKeys } from './IsolateKeys';
5
+
6
+ export function isIsolateType<I extends TIsolate>(
7
+ node: Maybe<TIsolate>,
8
+ type: string,
9
+ ): node is I {
10
+ return node?.[IsolateKeys.Type] === type;
11
+ }
12
+
13
+ export function isSameIsolateType<A extends TIsolate, B extends TIsolate>(
14
+ a: A,
15
+ b: B,
16
+ ): boolean {
17
+ return isIsolateType(a, b[IsolateKeys.Type]);
18
+ }
19
+
20
+ export function isSameIsolateIdentity<A extends TIsolate, B extends TIsolate>(
21
+ a: A,
22
+ b: B,
23
+ ): boolean {
24
+ return Object.is(a, b) || (isSameIsolateType(a, b) && a.key === b.key);
25
+ }
@@ -0,0 +1,30 @@
1
+ import { StateMachine, TStateMachine } from 'vest-utils';
2
+
3
+ import { TIsolate } from './Isolate';
4
+ import { IsolateInspector } from './IsolateInspector';
5
+
6
+ import { IsolateStatus } from './IsolateStatus';
7
+
8
+ const machine: TStateMachine<IsolateStatus> = {
9
+ initial: IsolateStatus.INITIAL,
10
+ states: {
11
+ [IsolateStatus.DONE]: {},
12
+ [IsolateStatus.INITIAL]: {
13
+ [IsolateStatus.PENDING]: IsolateStatus.PENDING,
14
+ [IsolateStatus.HAS_PENDING]: IsolateStatus.HAS_PENDING,
15
+ [IsolateStatus.DONE]: IsolateStatus.DONE,
16
+ },
17
+ [IsolateStatus.PENDING]: {
18
+ [IsolateStatus.DONE]: IsolateStatus.DONE,
19
+ },
20
+ [IsolateStatus.HAS_PENDING]: {
21
+ [IsolateStatus.DONE]: [
22
+ IsolateStatus.DONE,
23
+ (isolate: TIsolate) => !IsolateInspector.hasActiveChildren(isolate),
24
+ ],
25
+ [IsolateStatus.PENDING]: IsolateStatus.PENDING,
26
+ },
27
+ },
28
+ };
29
+
30
+ export const IsolateStateMachine = StateMachine(machine);
@@ -0,0 +1,8 @@
1
+ export const IsolateStatus = {
2
+ DONE: 'DONE',
3
+ HAS_PENDING: 'HAS_PENDING',
4
+ INITIAL: 'INITIAL',
5
+ PENDING: 'PENDING',
6
+ } as const;
7
+
8
+ export type IsolateStatus = (typeof IsolateStatus)[keyof typeof IsolateStatus];
@@ -0,0 +1,27 @@
1
+ import { CB } from 'vest-utils';
2
+
3
+ import { Isolate } from './Isolate';
4
+ import type { IsolatePayload, TIsolate } from './IsolateTypes';
5
+
6
+ /**
7
+ * Creates a transient isolate.
8
+ *
9
+ * Transient isolates are isolates that:
10
+ * 1. Do not persist in the history tree.
11
+ * 2. Are not reconciled with previous runs.
12
+ * 3. Do not appear in the serialized suite dump.
13
+ * 4. Do not interfere with the index of their siblings (they are skipped by the reconciler).
14
+ *
15
+ * This is useful for "structural" isolates that are used for control flow or grouping
16
+ * but do not hold state that needs to be preserved between runs, such as `focused` (skip/only) isolates.
17
+ */
18
+ export function IsolateTransient<Payload extends IsolatePayload>(
19
+ callback: CB,
20
+ type = 'Transient',
21
+ payload: Payload = {} as Payload,
22
+ ): TIsolate<Payload> {
23
+ return Isolate.create<Payload>(type, callback, {
24
+ ...payload,
25
+ transient: true,
26
+ });
27
+ }