@toolproof-npm/shared 0.1.104 → 0.1.106
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/_lib/utils/parallelization.d.ts +7 -0
- package/dist/_lib/utils/parallelization.js +79 -0
- package/dist/_lib/utils/statefulStrategyToStrategyRun.d.ts +7 -0
- package/dist/_lib/utils/statefulStrategyToStrategyRun.js +79 -0
- package/dist/_lib/utils/stepCreation.d.ts +2 -0
- package/dist/_lib/utils/stepCreation.js +58 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TerminalConst } from '../types.js';
|
|
2
|
+
import type { StatefulStrategyJson, StrategyRunJson, StrategyRunStatusJson } from '@toolproof-npm/schema';
|
|
3
|
+
export type GetNewIdentity = (identifiable: TerminalConst) => string | Promise<string>;
|
|
4
|
+
export declare function statefulStrategyToStrategyRun(statefulStrategy: StatefulStrategyJson, opts?: {
|
|
5
|
+
getNewIdentity: GetNewIdentity;
|
|
6
|
+
status?: StrategyRunStatusJson;
|
|
7
|
+
}): Promise<StrategyRunJson>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CONSTANTS } from '../../constants.js';
|
|
2
|
+
function getIndependentThreads(steps, strategyState) {
|
|
3
|
+
const getStepId = (s, i) => s?.execution?.identity || `step-${i}`;
|
|
4
|
+
const stepById = new Map(steps.map((s, i) => [getStepId(s, i), s]));
|
|
5
|
+
const adj = new Map(); // undirected graph for components
|
|
6
|
+
steps.forEach((step, i) => {
|
|
7
|
+
const id = getStepId(step, i);
|
|
8
|
+
if (!adj.has(id))
|
|
9
|
+
adj.set(id, []);
|
|
10
|
+
if (step.kind === CONSTANTS.STEPS.work && step.execution) {
|
|
11
|
+
const inputBindingMap = step.execution.roleBindings.inputBindingMap;
|
|
12
|
+
for (const inputRoleId of Object.keys(inputBindingMap)) {
|
|
13
|
+
const entry = strategyState?.[id]?.[inputRoleId];
|
|
14
|
+
if (entry?.kind === 'potential-input') {
|
|
15
|
+
const creatorId = entry.creationContext?.executionRef;
|
|
16
|
+
if (creatorId && stepById.has(creatorId)) {
|
|
17
|
+
// Dependency exists
|
|
18
|
+
adj.get(id).push(creatorId);
|
|
19
|
+
if (!adj.has(creatorId))
|
|
20
|
+
adj.set(creatorId, []);
|
|
21
|
+
adj.get(creatorId).push(id);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
const visited = new Set();
|
|
28
|
+
const components = [];
|
|
29
|
+
for (const [id] of adj) {
|
|
30
|
+
if (!visited.has(id)) {
|
|
31
|
+
const component = [];
|
|
32
|
+
const queue = [id];
|
|
33
|
+
visited.add(id);
|
|
34
|
+
while (queue.length > 0) {
|
|
35
|
+
const node = queue.shift();
|
|
36
|
+
component.push(node);
|
|
37
|
+
for (const neighbor of adj.get(node) || []) {
|
|
38
|
+
if (!visited.has(neighbor)) {
|
|
39
|
+
visited.add(neighbor);
|
|
40
|
+
queue.push(neighbor);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
components.push(component);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return components
|
|
48
|
+
.map((compIds) => {
|
|
49
|
+
return steps.filter((s, i) => {
|
|
50
|
+
const sId = getStepId(s, i);
|
|
51
|
+
return compIds.includes(sId);
|
|
52
|
+
});
|
|
53
|
+
})
|
|
54
|
+
.filter((group) => group.length > 0);
|
|
55
|
+
}
|
|
56
|
+
export async function statefulStrategyToStrategyRun(statefulStrategy, opts) {
|
|
57
|
+
if (!opts?.getNewIdentity) {
|
|
58
|
+
throw new Error('statefulStrategyToStrategyRun: opts.getNewIdentity is required');
|
|
59
|
+
}
|
|
60
|
+
const strategyRunIdentity = (await opts.getNewIdentity(CONSTANTS.TERMINALS.strategy_run));
|
|
61
|
+
const steps = statefulStrategy.statelessStrategy?.steps ?? [];
|
|
62
|
+
const strategyState = (statefulStrategy.strategyState ?? {});
|
|
63
|
+
// Algorithm to find what steps can run in parallel
|
|
64
|
+
const threadStepsGroups = getIndependentThreads(steps, strategyState);
|
|
65
|
+
const strategyThreadMap = {};
|
|
66
|
+
for (const group of threadStepsGroups) {
|
|
67
|
+
const threadIdentity = await opts.getNewIdentity(CONSTANTS.TERMINALS.strategy_thread);
|
|
68
|
+
strategyThreadMap[threadIdentity] = group;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
identity: strategyRunIdentity,
|
|
72
|
+
statefulStrategyRef: statefulStrategy.identity,
|
|
73
|
+
strategyState: strategyState,
|
|
74
|
+
strategyRunContext: {
|
|
75
|
+
status: opts.status ?? 'running',
|
|
76
|
+
},
|
|
77
|
+
strategyThreadMap,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TerminalConst } from '../types.js';
|
|
2
|
+
import type { StatefulStrategyJson, StrategyRunJson, StrategyRunStatusJson } from '@toolproof-npm/schema';
|
|
3
|
+
export type GetNewIdentity = (identifiable: TerminalConst) => string;
|
|
4
|
+
export declare function statefulStrategyToStrategyRun(statefulStrategy: StatefulStrategyJson, opts?: {
|
|
5
|
+
getNewIdentity: GetNewIdentity;
|
|
6
|
+
status?: StrategyRunStatusJson;
|
|
7
|
+
}): StrategyRunJson;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CONSTANTS } from '../../constants.js';
|
|
2
|
+
function getIndependentThreads(steps, strategyState) {
|
|
3
|
+
const getStepId = (s, i) => s?.execution?.identity || `step-${i}`;
|
|
4
|
+
const stepById = new Map(steps.map((s, i) => [getStepId(s, i), s]));
|
|
5
|
+
const adj = new Map(); // undirected graph for components
|
|
6
|
+
steps.forEach((step, i) => {
|
|
7
|
+
const id = getStepId(step, i);
|
|
8
|
+
if (!adj.has(id))
|
|
9
|
+
adj.set(id, []);
|
|
10
|
+
if (step.kind === CONSTANTS.STEPS.work && step.execution) {
|
|
11
|
+
const inputBindingMap = step.execution.roleBindings.inputBindingMap;
|
|
12
|
+
for (const inputRoleId of Object.keys(inputBindingMap)) {
|
|
13
|
+
const entry = strategyState?.[id]?.[inputRoleId];
|
|
14
|
+
if (entry?.kind === 'potential-input') {
|
|
15
|
+
const creatorId = entry.creationContext?.executionRef;
|
|
16
|
+
if (creatorId && stepById.has(creatorId)) {
|
|
17
|
+
// Dependency exists
|
|
18
|
+
adj.get(id).push(creatorId);
|
|
19
|
+
if (!adj.has(creatorId))
|
|
20
|
+
adj.set(creatorId, []);
|
|
21
|
+
adj.get(creatorId).push(id);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
const visited = new Set();
|
|
28
|
+
const components = [];
|
|
29
|
+
for (const [id] of adj) {
|
|
30
|
+
if (!visited.has(id)) {
|
|
31
|
+
const component = [];
|
|
32
|
+
const queue = [id];
|
|
33
|
+
visited.add(id);
|
|
34
|
+
while (queue.length > 0) {
|
|
35
|
+
const node = queue.shift();
|
|
36
|
+
component.push(node);
|
|
37
|
+
for (const neighbor of adj.get(node) || []) {
|
|
38
|
+
if (!visited.has(neighbor)) {
|
|
39
|
+
visited.add(neighbor);
|
|
40
|
+
queue.push(neighbor);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
components.push(component);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return components
|
|
48
|
+
.map((compIds) => {
|
|
49
|
+
return steps.filter((s, i) => {
|
|
50
|
+
const sId = getStepId(s, i);
|
|
51
|
+
return compIds.includes(sId);
|
|
52
|
+
});
|
|
53
|
+
})
|
|
54
|
+
.filter((group) => group.length > 0);
|
|
55
|
+
}
|
|
56
|
+
export function statefulStrategyToStrategyRun(statefulStrategy, opts) {
|
|
57
|
+
if (!opts?.getNewIdentity) {
|
|
58
|
+
throw new Error('statefulStrategyToStrategyRun: opts.getNewIdentity is required');
|
|
59
|
+
}
|
|
60
|
+
const strategyRunIdentity = opts.getNewIdentity(CONSTANTS.TERMINALS.strategy_run);
|
|
61
|
+
const steps = statefulStrategy.statelessStrategy?.steps ?? [];
|
|
62
|
+
const strategyState = (statefulStrategy.strategyState ?? {});
|
|
63
|
+
// Algorithm to find what steps can run in parallel
|
|
64
|
+
const threadStepsGroups = getIndependentThreads(steps, strategyState);
|
|
65
|
+
const strategyThreadMap = {};
|
|
66
|
+
threadStepsGroups.forEach((group) => {
|
|
67
|
+
const threadIdentity = opts.getNewIdentity(CONSTANTS.TERMINALS.strategy_thread);
|
|
68
|
+
strategyThreadMap[threadIdentity] = group;
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
identity: strategyRunIdentity,
|
|
72
|
+
statefulStrategyRef: statefulStrategy.identity,
|
|
73
|
+
strategyState: strategyState,
|
|
74
|
+
strategyRunContext: {
|
|
75
|
+
status: opts.status ?? 'running',
|
|
76
|
+
},
|
|
77
|
+
strategyThreadMap,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -6,3 +6,5 @@ export declare const createBranchStepFromJobPairs: (cases: {
|
|
|
6
6
|
whenJob: JobJson;
|
|
7
7
|
}[]) => Promise<BranchStepJson>;
|
|
8
8
|
export declare const cloneForStep: (forStep: ForStepJson) => Promise<ForStepJson>;
|
|
9
|
+
export declare const cloneWhileStep: (whileStep: WhileStepJson) => Promise<WhileStepJson>;
|
|
10
|
+
export declare const cloneBranchStep: (branchStep: BranchStepJson, casesOverride?: BranchStepJson["cases"]) => Promise<BranchStepJson>;
|
|
@@ -124,3 +124,61 @@ export const cloneForStep = async (forStep) => {
|
|
|
124
124
|
}
|
|
125
125
|
};
|
|
126
126
|
};
|
|
127
|
+
// DOC: Helper function to clone a WhileStep with new identities for steps and executions, preserving job references and bindings
|
|
128
|
+
export const cloneWhileStep = async (whileStep) => {
|
|
129
|
+
// Generate new WhileStep identity
|
|
130
|
+
const newWhileStepIdentity = await getNewIdentity(CONSTANTS.STEPS.while);
|
|
131
|
+
// Clone the "what" WorkStep
|
|
132
|
+
const originalWhatWorkStep = whileStep.case.what;
|
|
133
|
+
const newWhatWorkStepIdentity = await getNewIdentity(CONSTANTS.STEPS.work);
|
|
134
|
+
const newWhatExecutionIdentity = newWhatWorkStepIdentity.replace(CONSTANTS.STEPS.work.toUpperCase(), 'execution'.toUpperCase());
|
|
135
|
+
const newWhatExecution = {
|
|
136
|
+
identity: newWhatExecutionIdentity,
|
|
137
|
+
jobRef: originalWhatWorkStep.execution.jobRef,
|
|
138
|
+
roleBindings: {
|
|
139
|
+
inputBindingMap: { ...originalWhatWorkStep.execution.roleBindings.inputBindingMap },
|
|
140
|
+
outputBindingMap: { ...originalWhatWorkStep.execution.roleBindings.outputBindingMap }
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const newWhatWorkStep = {
|
|
144
|
+
identity: newWhatWorkStepIdentity,
|
|
145
|
+
kind: CONSTANTS.STEPS.work,
|
|
146
|
+
execution: newWhatExecution
|
|
147
|
+
};
|
|
148
|
+
// Clone the "when" WorkStep
|
|
149
|
+
const originalWhenWorkStep = whileStep.case.when;
|
|
150
|
+
const newWhenWorkStepIdentity = await getNewIdentity(CONSTANTS.STEPS.work);
|
|
151
|
+
const newWhenExecutionIdentity = newWhenWorkStepIdentity.replace(CONSTANTS.STEPS.work.toUpperCase(), 'execution'.toUpperCase());
|
|
152
|
+
const newWhenExecution = {
|
|
153
|
+
identity: newWhenExecutionIdentity,
|
|
154
|
+
jobRef: originalWhenWorkStep.execution.jobRef,
|
|
155
|
+
roleBindings: {
|
|
156
|
+
inputBindingMap: { ...originalWhenWorkStep.execution.roleBindings.inputBindingMap },
|
|
157
|
+
outputBindingMap: { ...originalWhenWorkStep.execution.roleBindings.outputBindingMap }
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const newWhenWorkStep = {
|
|
161
|
+
identity: newWhenWorkStepIdentity,
|
|
162
|
+
kind: CONSTANTS.STEPS.work,
|
|
163
|
+
execution: newWhenExecution
|
|
164
|
+
};
|
|
165
|
+
// Assemble the cloned WhileStep
|
|
166
|
+
return {
|
|
167
|
+
identity: newWhileStepIdentity,
|
|
168
|
+
kind: CONSTANTS.STEPS.while,
|
|
169
|
+
case: {
|
|
170
|
+
what: newWhatWorkStep,
|
|
171
|
+
when: newWhenWorkStep
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
// DOC: Helper function to clone a BranchStep with a new identity, optionally overriding its cases.
|
|
176
|
+
// Note: We intentionally do NOT clone executions/steps here because BranchStep (Option A) does not repeat cases.
|
|
177
|
+
export const cloneBranchStep = async (branchStep, casesOverride) => {
|
|
178
|
+
const newBranchStepIdentity = await getNewIdentity(CONSTANTS.STEPS.branch);
|
|
179
|
+
return {
|
|
180
|
+
identity: newBranchStepIdentity,
|
|
181
|
+
kind: CONSTANTS.STEPS.branch,
|
|
182
|
+
cases: casesOverride ?? branchStep.cases,
|
|
183
|
+
};
|
|
184
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * as TYPES from './_lib/types.js';
|
|
|
3
3
|
export * as UTILS from './_lib/utils/utils.js';
|
|
4
4
|
export * as RESOURCE_CREATION from './_lib/utils/resourceCreation.js';
|
|
5
5
|
export * as STEP_CREATION from './_lib/utils/stepCreation.js';
|
|
6
|
+
export * as PARALLELIZATION from './_lib/utils/parallelization.js';
|
|
6
7
|
export * as ROLE_RESOURCE_BINDINGS from './_lib/utils/roleResourceBinding.js';
|
|
7
8
|
export * as RESOURCE_CHAIN from './_lib/utils/resourceChain.js';
|
|
8
9
|
export * from './firebaseAdminHelpers.js';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * as TYPES from './_lib/types.js';
|
|
|
3
3
|
export * as UTILS from './_lib/utils/utils.js';
|
|
4
4
|
export * as RESOURCE_CREATION from './_lib/utils/resourceCreation.js';
|
|
5
5
|
export * as STEP_CREATION from './_lib/utils/stepCreation.js';
|
|
6
|
+
export * as PARALLELIZATION from './_lib/utils/parallelization.js';
|
|
6
7
|
export * as ROLE_RESOURCE_BINDINGS from './_lib/utils/roleResourceBinding.js';
|
|
7
8
|
export * as RESOURCE_CHAIN from './_lib/utils/resourceChain.js';
|
|
8
9
|
export * from './firebaseAdminHelpers.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toolproof-npm/shared",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.106",
|
|
4
4
|
"description": "Core library utilities for ToolProof",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"toolproof",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"typescript": "^5.9.3"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@toolproof-npm/schema": "^0.1.
|
|
59
|
+
"@toolproof-npm/schema": "^0.1.72",
|
|
60
60
|
"firebase-admin": "^13.6.0"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|