@sanoma-ai/workflows 0.8.2 → 0.9.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.
- package/README.md +6 -19
- package/dist/apply.js +8 -8
- package/dist/apply.js.map +1 -1
- package/dist/client.d.ts +1 -13
- package/dist/client.js +2 -22
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +7 -46
- package/dist/config.js +13 -39
- package/dist/config.js.map +1 -1
- package/dist/cucumber.d.ts +4 -2
- package/dist/cucumber.js +8 -3
- package/dist/cucumber.js.map +1 -1
- package/dist/db.d.ts +0 -1
- package/dist/db.js +2 -12
- package/dist/db.js.map +1 -1
- package/dist/fake.d.ts +1 -9
- package/dist/fake.js +2 -12
- package/dist/fake.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/resource.d.ts +17 -0
- package/dist/resource.js +38 -0
- package/dist/resource.js.map +1 -1
- package/dist/run.d.ts +0 -2
- package/dist/run.js.map +1 -1
- package/dist/sandbox.d.ts +10 -38
- package/dist/sandbox.js +36 -66
- package/dist/sandbox.js.map +1 -1
- package/dist/scenario.d.ts +8 -18
- package/dist/scenario.js +60 -80
- package/dist/scenario.js.map +1 -1
- package/dist/scenarios.d.ts +1 -10
- package/dist/scenarios.js +2 -16
- package/dist/scenarios.js.map +1 -1
- package/dist/sites.d.ts +6 -0
- package/dist/sites.js +49 -10
- package/dist/sites.js.map +1 -1
- package/dist/source.d.ts +6 -0
- package/dist/source.js +13 -0
- package/dist/source.js.map +1 -1
- package/dist/steps.d.ts +3 -1
- package/dist/steps.js.map +1 -1
- package/dist/worker.d.ts +1 -7
- package/dist/worker.js +2 -3
- package/dist/worker.js.map +1 -1
- package/package.json +2 -2
- package/src/apply.ts +16 -8
- package/src/client.ts +2 -28
- package/src/config.ts +14 -91
- package/src/cucumber.ts +16 -3
- package/src/db.ts +2 -13
- package/src/fake.ts +1 -13
- package/src/index.ts +0 -1
- package/src/resource.ts +43 -0
- package/src/run.ts +0 -2
- package/src/sandbox.ts +38 -94
- package/src/scenario.ts +73 -100
- package/src/scenarios.ts +3 -18
- package/src/sites.ts +67 -11
- package/src/source.ts +14 -0
- package/src/steps.ts +3 -1
- package/src/worker.ts +2 -9
- package/dist/demo.d.ts +0 -39
- package/dist/demo.js +0 -87
- package/dist/demo.js.map +0 -1
- package/src/demo.ts +0 -118
package/dist/demo.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { DATA_TABLE } from "./db.js";
|
|
2
|
-
import { applyGivens } from "./sandbox.js";
|
|
3
|
-
import { loadScenarios } from "./scenario.js";
|
|
4
|
-
import { memorySecrets } from "./secret.js";
|
|
5
|
-
import { DATA_VENDOR, errorMessage } from "./shared.js";
|
|
6
|
-
/** Each instance's last reseed, by its resolution. */
|
|
7
|
-
const states = new WeakMap();
|
|
8
|
-
/** Which half a world's `Given` goes to: a collection's operation the database, anything else memory; a fault both, each refusing it. */
|
|
9
|
-
const halfOf = (g, resolved) => "fault" in g ? undefined : "op" in g && resolved.ops.get(g.op)?.vendor === DATA_VENDOR ? "database" : "memory";
|
|
10
|
-
/**
|
|
11
|
-
* Seeds the demo instance afresh: cancels the app's unfinished runs (`cancelUnfinished`: the last
|
|
12
|
-
* seed's moments among them), empties the collections' table, resets the instance's fakes,
|
|
13
|
-
* applies every world (a scenario with no `When`) to the instance (`applyGivens`: its records
|
|
14
|
-
* saved through the collections' driver, its vendor objects put in the fakes), then starts each
|
|
15
|
-
* moment as a sandbox run by its person, left undriven, so its first approval waits for them.
|
|
16
|
-
* `only` seeds one half (`SeedHalf`). `resolved` is the resolution the worker and `client` run
|
|
17
|
-
* on, made with `fakes: "instance"`. A world or a moment that fails is a problem in the state it
|
|
18
|
-
* returns, and the rest go on.
|
|
19
|
-
*/
|
|
20
|
-
export async function reseed(client, resolved, { only } = {}) {
|
|
21
|
-
const fakes = resolved.instanceFakes;
|
|
22
|
-
if (!fakes)
|
|
23
|
-
throw new Error('reseed seeds an instance on fakes: resolve its config with `fakes: "instance"`');
|
|
24
|
-
const into = (half) => only === undefined || only === half;
|
|
25
|
-
// Read first, so scenarios that cannot be read at all leave the instance as it was.
|
|
26
|
-
const { scenarios, errors } = await loadScenarios(resolved);
|
|
27
|
-
if (into("database")) {
|
|
28
|
-
// The last seed's moments, and whatever a visitor started since, end here: nothing is kept.
|
|
29
|
-
await client.cancelUnfinished();
|
|
30
|
-
if (resolved.dataStore)
|
|
31
|
-
await (await resolved.dataStore.db()).query(`TRUNCATE ${DATA_TABLE}`);
|
|
32
|
-
}
|
|
33
|
-
if (into("memory"))
|
|
34
|
-
for (const fake of fakes.values())
|
|
35
|
-
fake.reset();
|
|
36
|
-
const state = {
|
|
37
|
-
seededAt: new Date().toISOString(),
|
|
38
|
-
worlds: [],
|
|
39
|
-
moments: [],
|
|
40
|
-
problems: errors.map((e) => e.message),
|
|
41
|
-
};
|
|
42
|
-
const secrets = memorySecrets();
|
|
43
|
-
for (const world of scenarios.filter((s) => s.workflow === undefined)) {
|
|
44
|
-
// Each Given keeps its place in the world, so its call's idempotency key is the same in either half.
|
|
45
|
-
const kept = [...world.given.entries()].filter(([, g]) => {
|
|
46
|
-
const half = halfOf(g, resolved);
|
|
47
|
-
return half === undefined || into(half);
|
|
48
|
-
});
|
|
49
|
-
try {
|
|
50
|
-
await applyGivens({ name: world.name, given: kept.map(([, g]) => g) }, {
|
|
51
|
-
kind: "instance",
|
|
52
|
-
drivers: resolved.drivers,
|
|
53
|
-
fakes,
|
|
54
|
-
ops: resolved.ops,
|
|
55
|
-
call: (i, opId) => ({
|
|
56
|
-
idempotencyKey: `world:${world.name}:${kept[i][0]}`,
|
|
57
|
-
runId: `world:${world.name}`,
|
|
58
|
-
opId,
|
|
59
|
-
attempt: 1,
|
|
60
|
-
secrets,
|
|
61
|
-
// Who its records are by: whoever it says is acting, else the runtime's own agent.
|
|
62
|
-
by: world.startedBy ?? "scenarios",
|
|
63
|
-
}),
|
|
64
|
-
});
|
|
65
|
-
state.worlds.push(world.name);
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
state.problems.push(`world "${world.name}": ${errorMessage(err)}`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
for (const s of into("database") ? scenarios : []) {
|
|
72
|
-
if (!s.moment || s.workflow === undefined)
|
|
73
|
-
continue;
|
|
74
|
-
try {
|
|
75
|
-
const runId = await client.start(s.workflow, s.input, { sandbox: s.name, startedBy: s.moment.person });
|
|
76
|
-
state.moments.push({ ...s.moment, scenario: s.name, runId });
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
state.problems.push(`moment "${s.name}": ${errorMessage(err)}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
states.set(resolved, state);
|
|
83
|
-
return state;
|
|
84
|
-
}
|
|
85
|
-
/** What the instance's last `reseed` did, or undefined before its first. */
|
|
86
|
-
export const demoState = (resolved) => states.get(resolved);
|
|
87
|
-
//# sourceMappingURL=demo.js.map
|
package/dist/demo.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"demo.js","sourceRoot":"","sources":["../src/demo.ts"],"names":[],"mappings":"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"}
|
package/src/demo.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import type { SanomaClient } from "./client.ts";
|
|
2
|
-
import type { ResolvedConfig } from "./config.ts";
|
|
3
|
-
import { DATA_TABLE } from "./db.ts";
|
|
4
|
-
import { applyGivens } from "./sandbox.ts";
|
|
5
|
-
import { type Given, loadScenarios, type Moment } from "./scenario.ts";
|
|
6
|
-
import { memorySecrets } from "./secret.ts";
|
|
7
|
-
import { DATA_VENDOR, errorMessage } from "./shared.ts";
|
|
8
|
-
|
|
9
|
-
/*
|
|
10
|
-
* A demo: a company repo run on its connectors' fakes, with the world its scenarios describe.
|
|
11
|
-
* Re-exported from `@sanoma-ai/workflows/scenarios`, server-side, since it loads Cucumber.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/** What the last `reseed` of an instance did: what the demo's banner shows. */
|
|
15
|
-
export interface DemoState {
|
|
16
|
-
/** When it was seeded, as an ISO timestamp. */
|
|
17
|
-
seededAt: string;
|
|
18
|
-
/** The worlds applied, by scenario name. */
|
|
19
|
-
worlds: string[];
|
|
20
|
-
/** Each moment started and left waiting: who it is for, where they land, its scenario and its run. */
|
|
21
|
-
moments: (Moment & { scenario: string; runId: string })[];
|
|
22
|
-
/** What could not be done, one line each: a feature file that did not load, a world or a moment that failed. */
|
|
23
|
-
problems: string[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Each instance's last reseed, by its resolution. */
|
|
27
|
-
const states = new WeakMap<ResolvedConfig, DemoState>();
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* A half of a demo's seed (`reseed`'s `only`): what goes into its `database` (the runs cancelled,
|
|
31
|
-
* `sanoma_data` emptied, the worlds' collection records, the moments' runs), or into its `memory`
|
|
32
|
-
* (the instance's fakes reset, the worlds' vendor objects and calls). A copy of a seeded database
|
|
33
|
-
* holds the first half but not the second, which a process over it applies to its own fakes.
|
|
34
|
-
*/
|
|
35
|
-
export type SeedHalf = "database" | "memory";
|
|
36
|
-
|
|
37
|
-
/** Which half a world's `Given` goes to: a collection's operation the database, anything else memory; a fault both, each refusing it. */
|
|
38
|
-
const halfOf = (g: Given, resolved: ResolvedConfig): SeedHalf | undefined =>
|
|
39
|
-
"fault" in g ? undefined : "op" in g && resolved.ops.get(g.op)?.vendor === DATA_VENDOR ? "database" : "memory";
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Seeds the demo instance afresh: cancels the app's unfinished runs (`cancelUnfinished`: the last
|
|
43
|
-
* seed's moments among them), empties the collections' table, resets the instance's fakes,
|
|
44
|
-
* applies every world (a scenario with no `When`) to the instance (`applyGivens`: its records
|
|
45
|
-
* saved through the collections' driver, its vendor objects put in the fakes), then starts each
|
|
46
|
-
* moment as a sandbox run by its person, left undriven, so its first approval waits for them.
|
|
47
|
-
* `only` seeds one half (`SeedHalf`). `resolved` is the resolution the worker and `client` run
|
|
48
|
-
* on, made with `fakes: "instance"`. A world or a moment that fails is a problem in the state it
|
|
49
|
-
* returns, and the rest go on.
|
|
50
|
-
*/
|
|
51
|
-
export async function reseed(
|
|
52
|
-
client: SanomaClient,
|
|
53
|
-
resolved: ResolvedConfig,
|
|
54
|
-
{ only }: { only?: SeedHalf } = {},
|
|
55
|
-
): Promise<DemoState> {
|
|
56
|
-
const fakes = resolved.instanceFakes;
|
|
57
|
-
if (!fakes) throw new Error('reseed seeds an instance on fakes: resolve its config with `fakes: "instance"`');
|
|
58
|
-
const into = (half: SeedHalf) => only === undefined || only === half;
|
|
59
|
-
// Read first, so scenarios that cannot be read at all leave the instance as it was.
|
|
60
|
-
const { scenarios, errors } = await loadScenarios(resolved);
|
|
61
|
-
if (into("database")) {
|
|
62
|
-
// The last seed's moments, and whatever a visitor started since, end here: nothing is kept.
|
|
63
|
-
await client.cancelUnfinished();
|
|
64
|
-
if (resolved.dataStore) await (await resolved.dataStore.db()).query(`TRUNCATE ${DATA_TABLE}`);
|
|
65
|
-
}
|
|
66
|
-
if (into("memory")) for (const fake of fakes.values()) fake.reset();
|
|
67
|
-
const state: DemoState = {
|
|
68
|
-
seededAt: new Date().toISOString(),
|
|
69
|
-
worlds: [],
|
|
70
|
-
moments: [],
|
|
71
|
-
problems: errors.map((e) => e.message),
|
|
72
|
-
};
|
|
73
|
-
const secrets = memorySecrets();
|
|
74
|
-
for (const world of scenarios.filter((s) => s.workflow === undefined)) {
|
|
75
|
-
// Each Given keeps its place in the world, so its call's idempotency key is the same in either half.
|
|
76
|
-
const kept = [...world.given.entries()].filter(([, g]) => {
|
|
77
|
-
const half = halfOf(g, resolved);
|
|
78
|
-
return half === undefined || into(half);
|
|
79
|
-
});
|
|
80
|
-
try {
|
|
81
|
-
await applyGivens(
|
|
82
|
-
{ name: world.name, given: kept.map(([, g]) => g) },
|
|
83
|
-
{
|
|
84
|
-
kind: "instance",
|
|
85
|
-
drivers: resolved.drivers,
|
|
86
|
-
fakes,
|
|
87
|
-
ops: resolved.ops,
|
|
88
|
-
call: (i, opId) => ({
|
|
89
|
-
idempotencyKey: `world:${world.name}:${kept[i]![0]}`,
|
|
90
|
-
runId: `world:${world.name}`,
|
|
91
|
-
opId,
|
|
92
|
-
attempt: 1,
|
|
93
|
-
secrets,
|
|
94
|
-
// Who its records are by: whoever it says is acting, else the runtime's own agent.
|
|
95
|
-
by: world.startedBy ?? "scenarios",
|
|
96
|
-
}),
|
|
97
|
-
},
|
|
98
|
-
);
|
|
99
|
-
state.worlds.push(world.name);
|
|
100
|
-
} catch (err) {
|
|
101
|
-
state.problems.push(`world "${world.name}": ${errorMessage(err)}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
for (const s of into("database") ? scenarios : []) {
|
|
105
|
-
if (!s.moment || s.workflow === undefined) continue;
|
|
106
|
-
try {
|
|
107
|
-
const runId = await client.start(s.workflow, s.input, { sandbox: s.name, startedBy: s.moment.person });
|
|
108
|
-
state.moments.push({ ...s.moment, scenario: s.name, runId });
|
|
109
|
-
} catch (err) {
|
|
110
|
-
state.problems.push(`moment "${s.name}": ${errorMessage(err)}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
states.set(resolved, state);
|
|
114
|
-
return state;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** What the instance's last `reseed` did, or undefined before its first. */
|
|
118
|
-
export const demoState = (resolved: ResolvedConfig): DemoState | undefined => states.get(resolved);
|