@toolproof-npm/shared 0.1.109 → 0.1.111
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.
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { CONSTANTS } from '@toolproof-npm/shared/constants';
|
|
2
2
|
import { getNewIdentity } from '@toolproof-npm/shared/server';
|
|
3
|
+
const mintNewRoleBindingMap = async (existingMap) => {
|
|
4
|
+
const newMap = {};
|
|
5
|
+
await Promise.all(Object.keys(existingMap).map(async (resourceRoleRef) => {
|
|
6
|
+
const resourceId = await getNewIdentity(CONSTANTS.TERMINALS.resource);
|
|
7
|
+
newMap[resourceRoleRef] = resourceId;
|
|
8
|
+
}));
|
|
9
|
+
return newMap;
|
|
10
|
+
};
|
|
3
11
|
// DOC: Helper function to construct an Execution object from a job
|
|
4
12
|
const createExecutionFromJob = async (job, executionRef) => {
|
|
5
13
|
const execIdentity = executionRef ?? await getNewIdentity(CONSTANTS.TERMINALS.execution);
|
|
@@ -89,7 +97,7 @@ export const cloneForStep = async (forStep) => {
|
|
|
89
97
|
jobRef: originalWhatWorkStep.execution.jobRef,
|
|
90
98
|
roleBindings: {
|
|
91
99
|
inputBindingMap: { ...originalWhatWorkStep.execution.roleBindings.inputBindingMap },
|
|
92
|
-
outputBindingMap:
|
|
100
|
+
outputBindingMap: await mintNewRoleBindingMap(originalWhatWorkStep.execution.roleBindings.outputBindingMap)
|
|
93
101
|
}
|
|
94
102
|
};
|
|
95
103
|
const newWhatWorkStep = {
|
|
@@ -106,7 +114,7 @@ export const cloneForStep = async (forStep) => {
|
|
|
106
114
|
jobRef: originalWhenWorkStep.execution.jobRef,
|
|
107
115
|
roleBindings: {
|
|
108
116
|
inputBindingMap: { ...originalWhenWorkStep.execution.roleBindings.inputBindingMap },
|
|
109
|
-
outputBindingMap:
|
|
117
|
+
outputBindingMap: await mintNewRoleBindingMap(originalWhenWorkStep.execution.roleBindings.outputBindingMap)
|
|
110
118
|
}
|
|
111
119
|
};
|
|
112
120
|
const newWhenWorkStep = {
|
|
@@ -137,7 +145,7 @@ export const cloneWhileStep = async (whileStep) => {
|
|
|
137
145
|
jobRef: originalWhatWorkStep.execution.jobRef,
|
|
138
146
|
roleBindings: {
|
|
139
147
|
inputBindingMap: { ...originalWhatWorkStep.execution.roleBindings.inputBindingMap },
|
|
140
|
-
outputBindingMap:
|
|
148
|
+
outputBindingMap: await mintNewRoleBindingMap(originalWhatWorkStep.execution.roleBindings.outputBindingMap)
|
|
141
149
|
}
|
|
142
150
|
};
|
|
143
151
|
const newWhatWorkStep = {
|
|
@@ -154,7 +162,7 @@ export const cloneWhileStep = async (whileStep) => {
|
|
|
154
162
|
jobRef: originalWhenWorkStep.execution.jobRef,
|
|
155
163
|
roleBindings: {
|
|
156
164
|
inputBindingMap: { ...originalWhenWorkStep.execution.roleBindings.inputBindingMap },
|
|
157
|
-
outputBindingMap:
|
|
165
|
+
outputBindingMap: await mintNewRoleBindingMap(originalWhenWorkStep.execution.roleBindings.outputBindingMap)
|
|
158
166
|
}
|
|
159
167
|
};
|
|
160
168
|
const newWhenWorkStep = {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TerminalConst } from '../types.js';
|
|
2
|
+
import type { ExecutionJson, StatefulStrategyJson, StrategyRunJson, StrategyRunStatusJson, StrategyStateJson } from '@toolproof-npm/schema';
|
|
3
|
+
export type GetNewIdentity = (identifiable: TerminalConst) => string | Promise<string>;
|
|
4
|
+
export declare function normalizeExecutionInputBindingMap(execution: ExecutionJson, strategyState: StrategyStateJson): ExecutionJson;
|
|
5
|
+
export declare function createStrategyRun(statefulStrategy: StatefulStrategyJson, opts?: {
|
|
6
|
+
getNewIdentity: GetNewIdentity;
|
|
7
|
+
status?: StrategyRunStatusJson;
|
|
8
|
+
}): Promise<StrategyRunJson>;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { CONSTANTS } from '../../constants.js';
|
|
2
|
+
export function normalizeExecutionInputBindingMap(execution, strategyState) {
|
|
3
|
+
const inputBindingMap = execution.roleBindings?.inputBindingMap ?? {};
|
|
4
|
+
const outputBindingMap = execution.roleBindings?.outputBindingMap ?? {};
|
|
5
|
+
const bucket = strategyState?.[execution.identity];
|
|
6
|
+
if (!bucket) {
|
|
7
|
+
return execution;
|
|
8
|
+
}
|
|
9
|
+
let changed = false;
|
|
10
|
+
const nextInputBindingMap = { ...inputBindingMap };
|
|
11
|
+
for (const [roleRef, currentResourceId] of Object.entries(inputBindingMap)) {
|
|
12
|
+
const entryIdentity = bucket?.[roleRef]?.identity;
|
|
13
|
+
if (typeof entryIdentity === 'string' && entryIdentity.length > 0 && entryIdentity !== currentResourceId) {
|
|
14
|
+
nextInputBindingMap[roleRef] = entryIdentity;
|
|
15
|
+
changed = true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (!changed) {
|
|
19
|
+
return execution;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
...execution,
|
|
23
|
+
roleBindings: {
|
|
24
|
+
inputBindingMap: nextInputBindingMap,
|
|
25
|
+
outputBindingMap,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function normalizeStepRoleBindings(step, strategyState) {
|
|
30
|
+
if (step.kind === CONSTANTS.STEPS.work) {
|
|
31
|
+
const work = step;
|
|
32
|
+
if (!work.execution)
|
|
33
|
+
return step;
|
|
34
|
+
const nextExecution = normalizeExecutionInputBindingMap(work.execution, strategyState);
|
|
35
|
+
if (nextExecution === work.execution)
|
|
36
|
+
return step;
|
|
37
|
+
return {
|
|
38
|
+
...work,
|
|
39
|
+
execution: nextExecution,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (step.kind === CONSTANTS.STEPS.for) {
|
|
43
|
+
const loop = step;
|
|
44
|
+
const whatExecution = loop.case?.what?.execution;
|
|
45
|
+
const whenExecution = loop.case?.when?.execution;
|
|
46
|
+
const nextWhatExecution = whatExecution
|
|
47
|
+
? normalizeExecutionInputBindingMap(whatExecution, strategyState)
|
|
48
|
+
: whatExecution;
|
|
49
|
+
const nextWhenExecution = whenExecution
|
|
50
|
+
? normalizeExecutionInputBindingMap(whenExecution, strategyState)
|
|
51
|
+
: whenExecution;
|
|
52
|
+
if (nextWhatExecution === whatExecution && nextWhenExecution === whenExecution)
|
|
53
|
+
return step;
|
|
54
|
+
return {
|
|
55
|
+
...loop,
|
|
56
|
+
case: {
|
|
57
|
+
...loop.case,
|
|
58
|
+
what: loop.case?.what
|
|
59
|
+
? {
|
|
60
|
+
...loop.case.what,
|
|
61
|
+
execution: nextWhatExecution,
|
|
62
|
+
}
|
|
63
|
+
: loop.case?.what,
|
|
64
|
+
when: loop.case?.when
|
|
65
|
+
? {
|
|
66
|
+
...loop.case.when,
|
|
67
|
+
execution: nextWhenExecution,
|
|
68
|
+
}
|
|
69
|
+
: loop.case?.when,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (step.kind === CONSTANTS.STEPS.while) {
|
|
74
|
+
const loop = step;
|
|
75
|
+
const whatExecution = loop.case?.what?.execution;
|
|
76
|
+
const whenExecution = loop.case?.when?.execution;
|
|
77
|
+
const nextWhatExecution = whatExecution
|
|
78
|
+
? normalizeExecutionInputBindingMap(whatExecution, strategyState)
|
|
79
|
+
: whatExecution;
|
|
80
|
+
const nextWhenExecution = whenExecution
|
|
81
|
+
? normalizeExecutionInputBindingMap(whenExecution, strategyState)
|
|
82
|
+
: whenExecution;
|
|
83
|
+
if (nextWhatExecution === whatExecution && nextWhenExecution === whenExecution)
|
|
84
|
+
return step;
|
|
85
|
+
return {
|
|
86
|
+
...loop,
|
|
87
|
+
case: {
|
|
88
|
+
...loop.case,
|
|
89
|
+
what: loop.case?.what
|
|
90
|
+
? {
|
|
91
|
+
...loop.case.what,
|
|
92
|
+
execution: nextWhatExecution,
|
|
93
|
+
}
|
|
94
|
+
: loop.case?.what,
|
|
95
|
+
when: loop.case?.when
|
|
96
|
+
? {
|
|
97
|
+
...loop.case.when,
|
|
98
|
+
execution: nextWhenExecution,
|
|
99
|
+
}
|
|
100
|
+
: loop.case?.when,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (step.kind === CONSTANTS.STEPS.branch) {
|
|
105
|
+
const branch = step;
|
|
106
|
+
const cases = branch.cases ?? [];
|
|
107
|
+
let changed = false;
|
|
108
|
+
const nextCases = cases.map((caseItem) => {
|
|
109
|
+
const whatExecution = caseItem?.what?.execution;
|
|
110
|
+
const whenExecution = caseItem?.when?.execution;
|
|
111
|
+
const nextWhatExecution = whatExecution
|
|
112
|
+
? normalizeExecutionInputBindingMap(whatExecution, strategyState)
|
|
113
|
+
: whatExecution;
|
|
114
|
+
const nextWhenExecution = whenExecution
|
|
115
|
+
? normalizeExecutionInputBindingMap(whenExecution, strategyState)
|
|
116
|
+
: whenExecution;
|
|
117
|
+
if (nextWhatExecution !== whatExecution || nextWhenExecution !== whenExecution) {
|
|
118
|
+
changed = true;
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
...caseItem,
|
|
122
|
+
what: caseItem?.what
|
|
123
|
+
? {
|
|
124
|
+
...caseItem.what,
|
|
125
|
+
execution: nextWhatExecution,
|
|
126
|
+
}
|
|
127
|
+
: caseItem?.what,
|
|
128
|
+
when: caseItem?.when
|
|
129
|
+
? {
|
|
130
|
+
...caseItem.when,
|
|
131
|
+
execution: nextWhenExecution,
|
|
132
|
+
}
|
|
133
|
+
: caseItem?.when,
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
if (!changed)
|
|
137
|
+
return step;
|
|
138
|
+
return {
|
|
139
|
+
...branch,
|
|
140
|
+
cases: nextCases,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return step;
|
|
144
|
+
}
|
|
145
|
+
function normalizeStatefulStrategyInputBindingMaps(statefulStrategy) {
|
|
146
|
+
const steps = statefulStrategy.statelessStrategy?.steps;
|
|
147
|
+
if (!steps || steps.length === 0)
|
|
148
|
+
return statefulStrategy;
|
|
149
|
+
const strategyState = (statefulStrategy.strategyState ?? {});
|
|
150
|
+
let changed = false;
|
|
151
|
+
const nextSteps = steps.map((step) => {
|
|
152
|
+
const nextStep = normalizeStepRoleBindings(step, strategyState);
|
|
153
|
+
if (nextStep !== step)
|
|
154
|
+
changed = true;
|
|
155
|
+
return nextStep;
|
|
156
|
+
});
|
|
157
|
+
if (!changed)
|
|
158
|
+
return statefulStrategy;
|
|
159
|
+
return {
|
|
160
|
+
...statefulStrategy,
|
|
161
|
+
statelessStrategy: {
|
|
162
|
+
...statefulStrategy.statelessStrategy,
|
|
163
|
+
steps: nextSteps,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function getIndependentThreads(steps, strategyState) {
|
|
168
|
+
const getStepId = (s, i) => s?.execution?.identity || `step-${i}`;
|
|
169
|
+
const stepById = new Map(steps.map((s, i) => [getStepId(s, i), s]));
|
|
170
|
+
const adj = new Map(); // undirected graph for components
|
|
171
|
+
steps.forEach((step, i) => {
|
|
172
|
+
const id = getStepId(step, i);
|
|
173
|
+
if (!adj.has(id))
|
|
174
|
+
adj.set(id, []);
|
|
175
|
+
if (step.kind === CONSTANTS.STEPS.work && step.execution) {
|
|
176
|
+
const inputBindingMap = step.execution.roleBindings.inputBindingMap;
|
|
177
|
+
for (const inputRoleId of Object.keys(inputBindingMap)) {
|
|
178
|
+
const entry = strategyState?.[id]?.[inputRoleId];
|
|
179
|
+
if (entry?.kind === 'potential-input') {
|
|
180
|
+
const creatorId = entry.creationContext?.executionRef;
|
|
181
|
+
if (creatorId && stepById.has(creatorId)) {
|
|
182
|
+
// Dependency exists
|
|
183
|
+
adj.get(id).push(creatorId);
|
|
184
|
+
if (!adj.has(creatorId))
|
|
185
|
+
adj.set(creatorId, []);
|
|
186
|
+
adj.get(creatorId).push(id);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
const visited = new Set();
|
|
193
|
+
const components = [];
|
|
194
|
+
for (const [id] of adj) {
|
|
195
|
+
if (!visited.has(id)) {
|
|
196
|
+
const component = [];
|
|
197
|
+
const queue = [id];
|
|
198
|
+
visited.add(id);
|
|
199
|
+
while (queue.length > 0) {
|
|
200
|
+
const node = queue.shift();
|
|
201
|
+
component.push(node);
|
|
202
|
+
for (const neighbor of adj.get(node) || []) {
|
|
203
|
+
if (!visited.has(neighbor)) {
|
|
204
|
+
visited.add(neighbor);
|
|
205
|
+
queue.push(neighbor);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
components.push(component);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return components
|
|
213
|
+
.map((compIds) => {
|
|
214
|
+
return steps.filter((s, i) => {
|
|
215
|
+
const sId = getStepId(s, i);
|
|
216
|
+
return compIds.includes(sId);
|
|
217
|
+
});
|
|
218
|
+
})
|
|
219
|
+
.filter((group) => group.length > 0);
|
|
220
|
+
}
|
|
221
|
+
export async function createStrategyRun(statefulStrategy, opts) {
|
|
222
|
+
if (!opts?.getNewIdentity) {
|
|
223
|
+
throw new Error('statefulStrategyToStrategyRun: opts.getNewIdentity is required');
|
|
224
|
+
}
|
|
225
|
+
// Normalize roleBindings.inputBindingMap to reflect the identities present in strategyState.
|
|
226
|
+
// This is intentionally a pure transformation (no mutation of the provided statefulStrategy object graph).
|
|
227
|
+
const normalizedStatefulStrategy = normalizeStatefulStrategyInputBindingMaps(statefulStrategy);
|
|
228
|
+
const strategyRunIdentity = (await opts.getNewIdentity(CONSTANTS.TERMINALS.strategy_run));
|
|
229
|
+
const steps = normalizedStatefulStrategy.statelessStrategy?.steps ?? [];
|
|
230
|
+
const strategyState = (normalizedStatefulStrategy.strategyState ?? {});
|
|
231
|
+
// Algorithm to find what steps can run in parallel
|
|
232
|
+
const threadStepsGroups = getIndependentThreads(steps, strategyState);
|
|
233
|
+
const strategyThreadMap = {};
|
|
234
|
+
for (const group of threadStepsGroups) {
|
|
235
|
+
const threadIdentity = await opts.getNewIdentity(CONSTANTS.TERMINALS.strategy_thread);
|
|
236
|
+
strategyThreadMap[threadIdentity] = group;
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
identity: strategyRunIdentity,
|
|
240
|
+
statefulStrategyRef: normalizedStatefulStrategy.identity,
|
|
241
|
+
strategyState: strategyState,
|
|
242
|
+
strategyRunContext: {
|
|
243
|
+
status: opts.status ?? 'running',
|
|
244
|
+
},
|
|
245
|
+
strategyThreadMap,
|
|
246
|
+
};
|
|
247
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +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
|
|
6
|
+
export * as STRATEGY_RUN_CREATION from './_lib/utils/strategyRunCreation.js';
|
|
7
7
|
export * as ROLE_RESOURCE_BINDINGS from './_lib/utils/roleResourceBinding.js';
|
|
8
8
|
export * as RESOURCE_CHAIN from './_lib/utils/resourceChain.js';
|
|
9
9
|
export * from './firebaseAdminHelpers.js';
|
package/dist/index.js
CHANGED
|
@@ -3,7 +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
|
|
6
|
+
export * as STRATEGY_RUN_CREATION from './_lib/utils/strategyRunCreation.js';
|
|
7
7
|
export * as ROLE_RESOURCE_BINDINGS from './_lib/utils/roleResourceBinding.js';
|
|
8
8
|
export * as RESOURCE_CHAIN from './_lib/utils/resourceChain.js';
|
|
9
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.111",
|
|
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.75",
|
|
60
60
|
"firebase-admin": "^13.6.0"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|