@sanity/workflow-engine-test 0.11.0 → 0.12.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.
- package/CHANGELOG.md +30 -0
- package/README.md +7 -5
- package/dist/index.cjs +60 -6
- package/dist/index.d.cts +69 -6
- package/dist/index.d.ts +69 -6
- package/dist/index.js +61 -7
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @sanity/workflow-engine-test
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 092a0d4: **BREAKING:** runtime-supplied refs written into field state are gated on the deployment's declared resource surface.
|
|
8
|
+
- A `doc.ref` / `doc.refs` / `release.ref` value a caller or effect handler supplies — start `initialFields` input, an action's param-sourced op values, `editField` values, effect-completion ops — must target a declared resource: the `workflowResource` itself or a resource the `resourceClients` resolver serves. An off-surface ref throws the new `RefResourceUndeclaredError` and the commit aborts, instead of writing cleanly and failing later when something dereferences it. The check is advisory like every engine check; stored documents are unchanged.
|
|
9
|
+
- Definition-authored refs are exempt: literal `initialValue` seeds, `type: 'query'` texts, spawn `with` projections, and op values that bind no action param were validated (and alias-expanded) at deploy, so they materialize into field state without re-checking.
|
|
10
|
+
- Resource aliases stay a deploy-time abstraction: `deployDefinitions({definitions, resourceAliases?})` expands every `@<alias>:` reference to its physical GDR before fingerprinting, and nothing alias-shaped survives past deploy — the runtime holds no alias map, and an alias-shaped ref supplied to a runtime verb is rejected as malformed like any other non-GDR string.
|
|
11
|
+
- `createBench` takes `serveResources` (dataset siblings served — and thereby declared — straight from the bench's own store) plus a raw `resourceClients` resolver for anything beyond same-store siblings. The engine also exports `sameResource` alongside the existing GDR helpers.
|
|
12
|
+
- The CLI wires no `resourceClients`, so refs supplied at runtime through it (`start --field`, `fire-action` ref params) admit the workflow resource only.
|
|
13
|
+
|
|
14
|
+
- 5a1a9fe: New `startedInstanceFixture(bench)` — a real, minimal started instance built through the bench's actual verbs, for consumers that need a persisted-shape-valid instance document without caring about its workflow (hand-crafted instance stubs do not pass the engine's read parse).
|
|
15
|
+
- 1321ba5: The bench exposes `evaluateStart` — the engine's pre-flight of `startInstance`'s gates (the `start.allowed` verdict with its insight, plus the still-missing required inputs), delegating to the verb with the bench clock so `$now` reads the frozen instant.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- e683875: The bench `queryInScope` doc now describes the scope the engine actually binds: the caller-free rendered scope (every always-bound condition var, `$fields` with the open stage's overlay merged, `$assigned` at its caller-free `false`, and the author's pre-evaluated `$<predicate>` booleans) — the previous enumeration listed a `$subject` param that was never bound.
|
|
20
|
+
- Updated dependencies [f9389e5]
|
|
21
|
+
- Updated dependencies [d0c62ea]
|
|
22
|
+
- Updated dependencies [c3eed2e]
|
|
23
|
+
- Updated dependencies [df4bd80]
|
|
24
|
+
- Updated dependencies [092a0d4]
|
|
25
|
+
- Updated dependencies [5a1a9fe]
|
|
26
|
+
- Updated dependencies [e3a7ba2]
|
|
27
|
+
- Updated dependencies [30fed9e]
|
|
28
|
+
- Updated dependencies [1321ba5]
|
|
29
|
+
- Updated dependencies [e683875]
|
|
30
|
+
- Updated dependencies [a8ace4d]
|
|
31
|
+
- @sanity/workflow-engine@0.17.0
|
|
32
|
+
|
|
3
33
|
## 0.11.0
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -18,7 +18,9 @@ npm install -D @sanity/workflow-engine-test
|
|
|
18
18
|
`createBench()` builds an isolated bench: the real engine, a fake in-memory
|
|
19
19
|
Sanity client, an all-access default actor, and a bench-owned clock. Every
|
|
20
20
|
wrapped verb injects the bench's client, tag, workflow resource, access
|
|
21
|
-
default,
|
|
21
|
+
default, clock, and cross-resource resolver (`serveResources` sugar for
|
|
22
|
+
same-store dataset siblings, raw `resourceClients` for anything else), so a
|
|
23
|
+
test reads as the workflow it exercises:
|
|
22
24
|
|
|
23
25
|
- **Runtime verbs** — `deployDefinitions`, `deleteDefinition`, `startInstance`,
|
|
24
26
|
`fireAction`, `editField`, `completeEffect` (and `completePendingEffect`,
|
|
@@ -28,10 +30,10 @@ default, and clock, so a test reads as the workflow it exercises:
|
|
|
28
30
|
not wall-clock.
|
|
29
31
|
- **Reads and probes** — `getInstance`, `currentStage`, `activityStatus`,
|
|
30
32
|
`listPendingEffects`, `findPendingEffects`, `children`, `instancesForSubject`,
|
|
31
|
-
`instancesByStage`, `instancesForDocument`, `definitionsForDocument`,
|
|
32
|
-
plus the guard helpers below. Helpers sharing an
|
|
33
|
-
engine's own args objects and throw its typed
|
|
34
|
-
reads cannot drift.
|
|
33
|
+
`instancesByStage`, `instancesForDocument`, `definitionsForDocument`,
|
|
34
|
+
`evaluateStart`, `query`, plus the guard helpers below. Helpers sharing an
|
|
35
|
+
engine verb's name take the engine's own args objects and throw its typed
|
|
36
|
+
errors — bench reads and engine reads cannot drift.
|
|
35
37
|
|
|
36
38
|
```ts
|
|
37
39
|
const bench = createBench({now: '2026-01-01T00:00:00Z'})
|
package/dist/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: !0
|
|
5
5
|
});
|
|
6
6
|
|
|
7
|
-
var workflowEngine = require("@sanity/workflow-engine"), clientFakeForTest = require("@sanity-labs/client-fake-for-test");
|
|
7
|
+
var workflowEngine = require("@sanity/workflow-engine"), clientFakeForTest = require("@sanity-labs/client-fake-for-test"), define = require("@sanity/workflow-engine/define");
|
|
8
8
|
|
|
9
9
|
const DEFAULT_WORKFLOW_RESOURCE = {
|
|
10
10
|
type: "dataset",
|
|
@@ -327,6 +327,13 @@ function createReadHelpers(scope) {
|
|
|
327
327
|
clock: clock
|
|
328
328
|
} : {}
|
|
329
329
|
}),
|
|
330
|
+
evaluateStart: args => workflowEngine.workflow.evaluateStart({
|
|
331
|
+
...engineScope,
|
|
332
|
+
...args,
|
|
333
|
+
...clock !== void 0 ? {
|
|
334
|
+
clock: clock
|
|
335
|
+
} : {}
|
|
336
|
+
}),
|
|
330
337
|
snapshot: () => client.snapshot()
|
|
331
338
|
};
|
|
332
339
|
}
|
|
@@ -335,6 +342,9 @@ function createBenchEngine(bench, overrides = {}) {
|
|
|
335
342
|
return workflowEngine.createEngine({
|
|
336
343
|
client: bench.client,
|
|
337
344
|
workflowResource: bench.workflowResource,
|
|
345
|
+
...bench.resourceClients !== void 0 ? {
|
|
346
|
+
resourceClients: bench.resourceClients
|
|
347
|
+
} : {},
|
|
338
348
|
tag: bench.tag,
|
|
339
349
|
clock: bench.now,
|
|
340
350
|
executionContext: bench.executionContext,
|
|
@@ -365,6 +375,34 @@ function releaseField(releaseName, opts) {
|
|
|
365
375
|
};
|
|
366
376
|
}
|
|
367
377
|
|
|
378
|
+
async function startedInstanceFixture(bench, opts) {
|
|
379
|
+
const flow = define.defineWorkflow({
|
|
380
|
+
name: "fixture-flow",
|
|
381
|
+
title: "Fixture flow",
|
|
382
|
+
initialStage: "review",
|
|
383
|
+
stages: [ {
|
|
384
|
+
name: "review",
|
|
385
|
+
transitions: [ {
|
|
386
|
+
name: "go",
|
|
387
|
+
to: "done",
|
|
388
|
+
when: "false"
|
|
389
|
+
} ]
|
|
390
|
+
}, {
|
|
391
|
+
name: "done"
|
|
392
|
+
} ]
|
|
393
|
+
});
|
|
394
|
+
await bench.deployDefinitions({
|
|
395
|
+
definitions: [ flow ]
|
|
396
|
+
});
|
|
397
|
+
const {instance: instance} = await bench.startInstance({
|
|
398
|
+
definition: flow.name,
|
|
399
|
+
...opts?.instanceId !== void 0 ? {
|
|
400
|
+
instanceId: opts.instanceId
|
|
401
|
+
} : {}
|
|
402
|
+
});
|
|
403
|
+
return instance;
|
|
404
|
+
}
|
|
405
|
+
|
|
368
406
|
function createBench(options = {}) {
|
|
369
407
|
const tag = options.tag ?? BENCH_TAG;
|
|
370
408
|
workflowEngine.validateTag(tag);
|
|
@@ -376,10 +414,16 @@ function createBench(options = {}) {
|
|
|
376
414
|
documents: options.documents
|
|
377
415
|
} : {},
|
|
378
416
|
defaultAccess: access
|
|
379
|
-
}), identities = createIdentityDirectory(client),
|
|
417
|
+
}), identities = createIdentityDirectory(client), resourceClients = benchResourceClients({
|
|
418
|
+
client: client,
|
|
419
|
+
options: options
|
|
420
|
+
}), scope = {
|
|
380
421
|
client: client,
|
|
381
422
|
tag: tag,
|
|
382
423
|
workflowResource: workflowResource,
|
|
424
|
+
...resourceClients !== void 0 ? {
|
|
425
|
+
resourceClients: resourceClients
|
|
426
|
+
} : {},
|
|
383
427
|
executionContext: options.executionContext ?? {
|
|
384
428
|
kind: workflowEngine.EXECUTION_KINDS.test
|
|
385
429
|
}
|
|
@@ -395,10 +439,7 @@ function createBench(options = {}) {
|
|
|
395
439
|
clock: clock
|
|
396
440
|
});
|
|
397
441
|
return {
|
|
398
|
-
|
|
399
|
-
tag: tag,
|
|
400
|
-
workflowResource: workflowResource,
|
|
401
|
-
executionContext: scope.executionContext,
|
|
442
|
+
...scope,
|
|
402
443
|
access: access,
|
|
403
444
|
currentUser: access.actor,
|
|
404
445
|
grants: access.grants ?? [],
|
|
@@ -433,6 +474,17 @@ function createBench(options = {}) {
|
|
|
433
474
|
};
|
|
434
475
|
}
|
|
435
476
|
|
|
477
|
+
function benchResourceClients(args) {
|
|
478
|
+
const {client: client} = args, {serveResources: serveResources, resourceClients: resourceClients} = args.options;
|
|
479
|
+
if (serveResources === void 0 || serveResources.length === 0) return resourceClients;
|
|
480
|
+
const nonDataset = serveResources.find(served => served.type !== "dataset");
|
|
481
|
+
if (nonDataset !== void 0) throw new Error(`createBench: serveResources can only serve dataset siblings of the bench store — got ${nonDataset.type} "${nonDataset.id}". Pass resourceClients for other resource types.`);
|
|
482
|
+
return parsed => {
|
|
483
|
+
const target = workflowEngine.resourceFromParsed(parsed);
|
|
484
|
+
return serveResources.some(served => workflowEngine.sameResource(served, target)) ? client.withConfig(workflowEngine.datasetResourceParts(target.id)) : resourceClients?.(parsed);
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
436
488
|
function assertIsoInstant(iso) {
|
|
437
489
|
const ms = Date.parse(iso);
|
|
438
490
|
if (Number.isNaN(ms)) throw new Error(`bench clock: expected an ISO-8601 instant, got ${JSON.stringify(iso)}`);
|
|
@@ -455,4 +507,6 @@ exports.createBenchEngine = createBenchEngine;
|
|
|
455
507
|
|
|
456
508
|
exports.releaseField = releaseField;
|
|
457
509
|
|
|
510
|
+
exports.startedInstanceFixture = startedInstanceFixture;
|
|
511
|
+
|
|
458
512
|
exports.subjectField = subjectField;
|
package/dist/index.d.cts
CHANGED
|
@@ -15,6 +15,7 @@ import { DeployedDefinition } from "@sanity/workflow-engine";
|
|
|
15
15
|
import type { EditFieldArgs } from "@sanity/workflow-engine";
|
|
16
16
|
import { Engine } from "@sanity/workflow-engine";
|
|
17
17
|
import type { EvaluateArgs } from "@sanity/workflow-engine";
|
|
18
|
+
import { EvaluateStartArgs } from "@sanity/workflow-engine";
|
|
18
19
|
import { FindPendingEffectsArgs } from "@sanity/workflow-engine";
|
|
19
20
|
import type { FireActionArgs } from "@sanity/workflow-engine";
|
|
20
21
|
import type { Grant } from "@sanity/workflow-engine";
|
|
@@ -27,10 +28,13 @@ import { MutationGuardDoc } from "@sanity/workflow-engine";
|
|
|
27
28
|
import type { OperationArgs } from "@sanity/workflow-engine";
|
|
28
29
|
import { OperationResult } from "@sanity/workflow-engine";
|
|
29
30
|
import { PendingEffect } from "@sanity/workflow-engine";
|
|
31
|
+
import type { ResourceClientResolver } from "@sanity/workflow-engine";
|
|
30
32
|
import type { SetStageArgs } from "@sanity/workflow-engine";
|
|
33
|
+
import { StartEvaluation } from "@sanity/workflow-engine";
|
|
31
34
|
import type { StartInstanceArgs } from "@sanity/workflow-engine";
|
|
32
35
|
import type { TestClient } from "@sanity-labs/client-fake-for-test";
|
|
33
36
|
import type { WorkflowAccess } from "@sanity/workflow-engine";
|
|
37
|
+
import { WorkflowDefinition } from "@sanity/workflow-engine";
|
|
34
38
|
import { WorkflowDefinitionInput } from "@sanity/workflow-engine";
|
|
35
39
|
import { WorkflowEvaluation } from "@sanity/workflow-engine";
|
|
36
40
|
import { WorkflowInstance } from "@sanity/workflow-engine";
|
|
@@ -110,6 +114,9 @@ declare interface BenchState {
|
|
|
110
114
|
tag: string;
|
|
111
115
|
/** The workflow resource every wrapped call targets. */
|
|
112
116
|
workflowResource: WorkflowResource;
|
|
117
|
+
/** The cross-resource read routing every wrapped call carries, when the
|
|
118
|
+
* bench declared any — see {@link CreateBenchOptions.resourceClients}. */
|
|
119
|
+
resourceClients?: ResourceClientResolver;
|
|
113
120
|
/** The declared execution context stamped on every wrapped call's history
|
|
114
121
|
* entries (default `{kind: 'test'}`) — inherited by {@link createBenchEngine}. */
|
|
115
122
|
executionContext: DeclaredExecutionContext;
|
|
@@ -234,6 +241,23 @@ export declare interface CreateBenchOptions {
|
|
|
234
241
|
* URIs read as `dataset:test:test:<docId>`.
|
|
235
242
|
*/
|
|
236
243
|
workflowResource?: WorkflowResource;
|
|
244
|
+
/**
|
|
245
|
+
* Sibling datasets the bench serves (and thereby declares) from its own
|
|
246
|
+
* store — the common cross-resource case. Each listed resource routes to
|
|
247
|
+
* a `withConfig`-derived handle over the bench's store, so reads reach it
|
|
248
|
+
* and runtime-supplied refs into it are admitted. Only `dataset`
|
|
249
|
+
* resources can be siblings of the bench's store; anything else needs
|
|
250
|
+
* {@link CreateBenchOptions.resourceClients}.
|
|
251
|
+
*/
|
|
252
|
+
serveResources?: WorkflowResource[];
|
|
253
|
+
/**
|
|
254
|
+
* Cross-resource read routing for the bench engine scope — a served
|
|
255
|
+
* resource is also part of the declared surface for written refs. The
|
|
256
|
+
* escape hatch for resolver logic beyond same-store siblings; prefer
|
|
257
|
+
* {@link CreateBenchOptions.serveResources}, which is consulted first
|
|
258
|
+
* when both are given. Omit for single-resource benches.
|
|
259
|
+
*/
|
|
260
|
+
resourceClients?: ResourceClientResolver;
|
|
237
261
|
/**
|
|
238
262
|
* Freeze the bench clock at this ISO instant from construction, so
|
|
239
263
|
* `$now` and every engine timestamp are deterministic. Equivalent to
|
|
@@ -281,6 +305,7 @@ declare type EngineScope = {
|
|
|
281
305
|
client: TestClient;
|
|
282
306
|
tag: string;
|
|
283
307
|
workflowResource: WorkflowResource;
|
|
308
|
+
resourceClients?: ResourceClientResolver;
|
|
284
309
|
executionContext: DeclaredExecutionContext;
|
|
285
310
|
};
|
|
286
311
|
|
|
@@ -306,6 +331,20 @@ declare type EngineWrappers = {
|
|
|
306
331
|
guardsForDefinition: (definition: string) => Promise<MutationGuardDoc[]>;
|
|
307
332
|
};
|
|
308
333
|
|
|
334
|
+
/** The slice of a bench {@link startedInstanceFixture} drives — structural,
|
|
335
|
+
* so the fixture module needs no import of the bench itself. */
|
|
336
|
+
declare interface FixtureBench {
|
|
337
|
+
deployDefinitions: (args: {
|
|
338
|
+
definitions: WorkflowDefinition[];
|
|
339
|
+
}) => Promise<unknown>;
|
|
340
|
+
startInstance: (args: {
|
|
341
|
+
definition: string;
|
|
342
|
+
instanceId?: string;
|
|
343
|
+
}) => Promise<{
|
|
344
|
+
instance: WorkflowInstance;
|
|
345
|
+
}>;
|
|
346
|
+
}
|
|
347
|
+
|
|
309
348
|
declare type GuardHelpers = {
|
|
310
349
|
/** All deployed guard docs ({@link MutationGuardDoc}) in the store. */
|
|
311
350
|
listGuards: () => Promise<MutationGuardDoc[]>;
|
|
@@ -358,11 +397,13 @@ declare type QueryHelpers = {
|
|
|
358
397
|
) => Promise<T>;
|
|
359
398
|
/**
|
|
360
399
|
* Snapshot-aware GROQ for `instanceId` — runs against the same
|
|
361
|
-
* in-memory view the engine's filters see
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
400
|
+
* in-memory view the engine's filters see, with the caller-free
|
|
401
|
+
* rendered scope auto-bound: every always-bound condition var,
|
|
402
|
+
* `$fields` with the open stage's overlay merged, `$assigned` at its
|
|
403
|
+
* caller-free `false`, and the author's pre-evaluated `$<predicate>`
|
|
404
|
+
* booleans, ids in GDR URI form. Use to ask "what does the engine see
|
|
405
|
+
* for this workflow?" — useful for filter debugging, UIs that mirror
|
|
406
|
+
* engine state, etc.
|
|
366
407
|
*/
|
|
367
408
|
queryInScope: <T = unknown>({
|
|
368
409
|
instanceId,
|
|
@@ -438,11 +479,19 @@ declare type ReadHelpers = {
|
|
|
438
479
|
* The startable half of {@link ReadHelpers.instancesForDocument}: the
|
|
439
480
|
* latest deployed version of every definition applicable to the LOADED
|
|
440
481
|
* candidate document — startable, a required subject entry accepts its
|
|
441
|
-
* `_type`, `start.filter` passes. All matches, name
|
|
482
|
+
* `_type`, `start.filter` (browse-time-pure) passes. All matches, name
|
|
483
|
+
* ascending.
|
|
442
484
|
*/
|
|
443
485
|
definitionsForDocument: (
|
|
444
486
|
args: DefinitionsForDocumentArgs,
|
|
445
487
|
) => Promise<DeployedDefinition[]>;
|
|
488
|
+
/**
|
|
489
|
+
* Pre-flight `startInstance`'s gates for a definition + candidate
|
|
490
|
+
* `initialFields`: the `start.allowed` verdict (with its insight) plus the
|
|
491
|
+
* still-missing required inputs — the read a start surface renders before
|
|
492
|
+
* committing.
|
|
493
|
+
*/
|
|
494
|
+
evaluateStart: (args: EvaluateStartArgs) => Promise<StartEvaluation>;
|
|
446
495
|
/** Snapshot of all documents in the store — for inspecting the world. */
|
|
447
496
|
snapshot: () => readonly StoreDocument[];
|
|
448
497
|
};
|
|
@@ -459,6 +508,20 @@ export declare function releaseField(
|
|
|
459
508
|
},
|
|
460
509
|
): InitialFieldValue;
|
|
461
510
|
|
|
511
|
+
/**
|
|
512
|
+
* A REAL, minimal started instance — deployed and started through the bench's
|
|
513
|
+
* actual verbs, parked forever on its first stage. The fixture for consumers
|
|
514
|
+
* (adapter tests, validation tests) that need a persisted-shape-valid
|
|
515
|
+
* instance document without caring about its workflow: hand-crafted instance
|
|
516
|
+
* stubs do not pass the engine's read parse.
|
|
517
|
+
*/
|
|
518
|
+
export declare function startedInstanceFixture(
|
|
519
|
+
bench: FixtureBench,
|
|
520
|
+
opts?: {
|
|
521
|
+
instanceId?: string;
|
|
522
|
+
},
|
|
523
|
+
): Promise<WorkflowInstance>;
|
|
524
|
+
|
|
462
525
|
/** A document as stored in the bench's fake lake. */
|
|
463
526
|
declare type StoreDocument = {
|
|
464
527
|
_id: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { DeployedDefinition } from "@sanity/workflow-engine";
|
|
|
15
15
|
import type { EditFieldArgs } from "@sanity/workflow-engine";
|
|
16
16
|
import { Engine } from "@sanity/workflow-engine";
|
|
17
17
|
import type { EvaluateArgs } from "@sanity/workflow-engine";
|
|
18
|
+
import { EvaluateStartArgs } from "@sanity/workflow-engine";
|
|
18
19
|
import { FindPendingEffectsArgs } from "@sanity/workflow-engine";
|
|
19
20
|
import type { FireActionArgs } from "@sanity/workflow-engine";
|
|
20
21
|
import type { Grant } from "@sanity/workflow-engine";
|
|
@@ -27,10 +28,13 @@ import { MutationGuardDoc } from "@sanity/workflow-engine";
|
|
|
27
28
|
import type { OperationArgs } from "@sanity/workflow-engine";
|
|
28
29
|
import { OperationResult } from "@sanity/workflow-engine";
|
|
29
30
|
import { PendingEffect } from "@sanity/workflow-engine";
|
|
31
|
+
import type { ResourceClientResolver } from "@sanity/workflow-engine";
|
|
30
32
|
import type { SetStageArgs } from "@sanity/workflow-engine";
|
|
33
|
+
import { StartEvaluation } from "@sanity/workflow-engine";
|
|
31
34
|
import type { StartInstanceArgs } from "@sanity/workflow-engine";
|
|
32
35
|
import type { TestClient } from "@sanity-labs/client-fake-for-test";
|
|
33
36
|
import type { WorkflowAccess } from "@sanity/workflow-engine";
|
|
37
|
+
import { WorkflowDefinition } from "@sanity/workflow-engine";
|
|
34
38
|
import { WorkflowDefinitionInput } from "@sanity/workflow-engine";
|
|
35
39
|
import { WorkflowEvaluation } from "@sanity/workflow-engine";
|
|
36
40
|
import { WorkflowInstance } from "@sanity/workflow-engine";
|
|
@@ -110,6 +114,9 @@ declare interface BenchState {
|
|
|
110
114
|
tag: string;
|
|
111
115
|
/** The workflow resource every wrapped call targets. */
|
|
112
116
|
workflowResource: WorkflowResource;
|
|
117
|
+
/** The cross-resource read routing every wrapped call carries, when the
|
|
118
|
+
* bench declared any — see {@link CreateBenchOptions.resourceClients}. */
|
|
119
|
+
resourceClients?: ResourceClientResolver;
|
|
113
120
|
/** The declared execution context stamped on every wrapped call's history
|
|
114
121
|
* entries (default `{kind: 'test'}`) — inherited by {@link createBenchEngine}. */
|
|
115
122
|
executionContext: DeclaredExecutionContext;
|
|
@@ -234,6 +241,23 @@ export declare interface CreateBenchOptions {
|
|
|
234
241
|
* URIs read as `dataset:test:test:<docId>`.
|
|
235
242
|
*/
|
|
236
243
|
workflowResource?: WorkflowResource;
|
|
244
|
+
/**
|
|
245
|
+
* Sibling datasets the bench serves (and thereby declares) from its own
|
|
246
|
+
* store — the common cross-resource case. Each listed resource routes to
|
|
247
|
+
* a `withConfig`-derived handle over the bench's store, so reads reach it
|
|
248
|
+
* and runtime-supplied refs into it are admitted. Only `dataset`
|
|
249
|
+
* resources can be siblings of the bench's store; anything else needs
|
|
250
|
+
* {@link CreateBenchOptions.resourceClients}.
|
|
251
|
+
*/
|
|
252
|
+
serveResources?: WorkflowResource[];
|
|
253
|
+
/**
|
|
254
|
+
* Cross-resource read routing for the bench engine scope — a served
|
|
255
|
+
* resource is also part of the declared surface for written refs. The
|
|
256
|
+
* escape hatch for resolver logic beyond same-store siblings; prefer
|
|
257
|
+
* {@link CreateBenchOptions.serveResources}, which is consulted first
|
|
258
|
+
* when both are given. Omit for single-resource benches.
|
|
259
|
+
*/
|
|
260
|
+
resourceClients?: ResourceClientResolver;
|
|
237
261
|
/**
|
|
238
262
|
* Freeze the bench clock at this ISO instant from construction, so
|
|
239
263
|
* `$now` and every engine timestamp are deterministic. Equivalent to
|
|
@@ -281,6 +305,7 @@ declare type EngineScope = {
|
|
|
281
305
|
client: TestClient;
|
|
282
306
|
tag: string;
|
|
283
307
|
workflowResource: WorkflowResource;
|
|
308
|
+
resourceClients?: ResourceClientResolver;
|
|
284
309
|
executionContext: DeclaredExecutionContext;
|
|
285
310
|
};
|
|
286
311
|
|
|
@@ -306,6 +331,20 @@ declare type EngineWrappers = {
|
|
|
306
331
|
guardsForDefinition: (definition: string) => Promise<MutationGuardDoc[]>;
|
|
307
332
|
};
|
|
308
333
|
|
|
334
|
+
/** The slice of a bench {@link startedInstanceFixture} drives — structural,
|
|
335
|
+
* so the fixture module needs no import of the bench itself. */
|
|
336
|
+
declare interface FixtureBench {
|
|
337
|
+
deployDefinitions: (args: {
|
|
338
|
+
definitions: WorkflowDefinition[];
|
|
339
|
+
}) => Promise<unknown>;
|
|
340
|
+
startInstance: (args: {
|
|
341
|
+
definition: string;
|
|
342
|
+
instanceId?: string;
|
|
343
|
+
}) => Promise<{
|
|
344
|
+
instance: WorkflowInstance;
|
|
345
|
+
}>;
|
|
346
|
+
}
|
|
347
|
+
|
|
309
348
|
declare type GuardHelpers = {
|
|
310
349
|
/** All deployed guard docs ({@link MutationGuardDoc}) in the store. */
|
|
311
350
|
listGuards: () => Promise<MutationGuardDoc[]>;
|
|
@@ -358,11 +397,13 @@ declare type QueryHelpers = {
|
|
|
358
397
|
) => Promise<T>;
|
|
359
398
|
/**
|
|
360
399
|
* Snapshot-aware GROQ for `instanceId` — runs against the same
|
|
361
|
-
* in-memory view the engine's filters see
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
400
|
+
* in-memory view the engine's filters see, with the caller-free
|
|
401
|
+
* rendered scope auto-bound: every always-bound condition var,
|
|
402
|
+
* `$fields` with the open stage's overlay merged, `$assigned` at its
|
|
403
|
+
* caller-free `false`, and the author's pre-evaluated `$<predicate>`
|
|
404
|
+
* booleans, ids in GDR URI form. Use to ask "what does the engine see
|
|
405
|
+
* for this workflow?" — useful for filter debugging, UIs that mirror
|
|
406
|
+
* engine state, etc.
|
|
366
407
|
*/
|
|
367
408
|
queryInScope: <T = unknown>({
|
|
368
409
|
instanceId,
|
|
@@ -438,11 +479,19 @@ declare type ReadHelpers = {
|
|
|
438
479
|
* The startable half of {@link ReadHelpers.instancesForDocument}: the
|
|
439
480
|
* latest deployed version of every definition applicable to the LOADED
|
|
440
481
|
* candidate document — startable, a required subject entry accepts its
|
|
441
|
-
* `_type`, `start.filter` passes. All matches, name
|
|
482
|
+
* `_type`, `start.filter` (browse-time-pure) passes. All matches, name
|
|
483
|
+
* ascending.
|
|
442
484
|
*/
|
|
443
485
|
definitionsForDocument: (
|
|
444
486
|
args: DefinitionsForDocumentArgs,
|
|
445
487
|
) => Promise<DeployedDefinition[]>;
|
|
488
|
+
/**
|
|
489
|
+
* Pre-flight `startInstance`'s gates for a definition + candidate
|
|
490
|
+
* `initialFields`: the `start.allowed` verdict (with its insight) plus the
|
|
491
|
+
* still-missing required inputs — the read a start surface renders before
|
|
492
|
+
* committing.
|
|
493
|
+
*/
|
|
494
|
+
evaluateStart: (args: EvaluateStartArgs) => Promise<StartEvaluation>;
|
|
446
495
|
/** Snapshot of all documents in the store — for inspecting the world. */
|
|
447
496
|
snapshot: () => readonly StoreDocument[];
|
|
448
497
|
};
|
|
@@ -459,6 +508,20 @@ export declare function releaseField(
|
|
|
459
508
|
},
|
|
460
509
|
): InitialFieldValue;
|
|
461
510
|
|
|
511
|
+
/**
|
|
512
|
+
* A REAL, minimal started instance — deployed and started through the bench's
|
|
513
|
+
* actual verbs, parked forever on its first stage. The fixture for consumers
|
|
514
|
+
* (adapter tests, validation tests) that need a persisted-shape-valid
|
|
515
|
+
* instance document without caring about its workflow: hand-crafted instance
|
|
516
|
+
* stubs do not pass the engine's read parse.
|
|
517
|
+
*/
|
|
518
|
+
export declare function startedInstanceFixture(
|
|
519
|
+
bench: FixtureBench,
|
|
520
|
+
opts?: {
|
|
521
|
+
instanceId?: string;
|
|
522
|
+
},
|
|
523
|
+
): Promise<WorkflowInstance>;
|
|
524
|
+
|
|
462
525
|
/** A document as stored in the bench's fake lake. */
|
|
463
526
|
declare type StoreDocument = {
|
|
464
527
|
_id: string;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { guardsForResource, denyingGuards, MutationGuardDeniedError, workflow, instancesQuery, WORKFLOW_INSTANCE_TYPE, tagScopeFilter, effectOutputsMap, contextMap, createEngine, releaseRef, gdrRef, validateTag, EXECUTION_KINDS } from "@sanity/workflow-engine";
|
|
1
|
+
import { guardsForResource, denyingGuards, MutationGuardDeniedError, workflow, instancesQuery, WORKFLOW_INSTANCE_TYPE, tagScopeFilter, effectOutputsMap, contextMap, createEngine, releaseRef, gdrRef, validateTag, EXECUTION_KINDS, resourceFromParsed, sameResource, datasetResourceParts } from "@sanity/workflow-engine";
|
|
2
2
|
|
|
3
3
|
import { createTestClient, defaultAclPath, isDraftId, isVersionId } from "@sanity-labs/client-fake-for-test";
|
|
4
4
|
|
|
5
|
+
import { defineWorkflow } from "@sanity/workflow-engine/define";
|
|
6
|
+
|
|
5
7
|
const DEFAULT_WORKFLOW_RESOURCE = {
|
|
6
8
|
type: "dataset",
|
|
7
9
|
id: "test.test"
|
|
@@ -323,6 +325,13 @@ function createReadHelpers(scope) {
|
|
|
323
325
|
clock: clock
|
|
324
326
|
} : {}
|
|
325
327
|
}),
|
|
328
|
+
evaluateStart: args => workflow.evaluateStart({
|
|
329
|
+
...engineScope,
|
|
330
|
+
...args,
|
|
331
|
+
...clock !== void 0 ? {
|
|
332
|
+
clock: clock
|
|
333
|
+
} : {}
|
|
334
|
+
}),
|
|
326
335
|
snapshot: () => client.snapshot()
|
|
327
336
|
};
|
|
328
337
|
}
|
|
@@ -331,6 +340,9 @@ function createBenchEngine(bench, overrides = {}) {
|
|
|
331
340
|
return createEngine({
|
|
332
341
|
client: bench.client,
|
|
333
342
|
workflowResource: bench.workflowResource,
|
|
343
|
+
...bench.resourceClients !== void 0 ? {
|
|
344
|
+
resourceClients: bench.resourceClients
|
|
345
|
+
} : {},
|
|
334
346
|
tag: bench.tag,
|
|
335
347
|
clock: bench.now,
|
|
336
348
|
executionContext: bench.executionContext,
|
|
@@ -361,6 +373,34 @@ function releaseField(releaseName, opts) {
|
|
|
361
373
|
};
|
|
362
374
|
}
|
|
363
375
|
|
|
376
|
+
async function startedInstanceFixture(bench, opts) {
|
|
377
|
+
const flow = defineWorkflow({
|
|
378
|
+
name: "fixture-flow",
|
|
379
|
+
title: "Fixture flow",
|
|
380
|
+
initialStage: "review",
|
|
381
|
+
stages: [ {
|
|
382
|
+
name: "review",
|
|
383
|
+
transitions: [ {
|
|
384
|
+
name: "go",
|
|
385
|
+
to: "done",
|
|
386
|
+
when: "false"
|
|
387
|
+
} ]
|
|
388
|
+
}, {
|
|
389
|
+
name: "done"
|
|
390
|
+
} ]
|
|
391
|
+
});
|
|
392
|
+
await bench.deployDefinitions({
|
|
393
|
+
definitions: [ flow ]
|
|
394
|
+
});
|
|
395
|
+
const {instance: instance} = await bench.startInstance({
|
|
396
|
+
definition: flow.name,
|
|
397
|
+
...opts?.instanceId !== void 0 ? {
|
|
398
|
+
instanceId: opts.instanceId
|
|
399
|
+
} : {}
|
|
400
|
+
});
|
|
401
|
+
return instance;
|
|
402
|
+
}
|
|
403
|
+
|
|
364
404
|
function createBench(options = {}) {
|
|
365
405
|
const tag = options.tag ?? BENCH_TAG;
|
|
366
406
|
validateTag(tag);
|
|
@@ -372,10 +412,16 @@ function createBench(options = {}) {
|
|
|
372
412
|
documents: options.documents
|
|
373
413
|
} : {},
|
|
374
414
|
defaultAccess: access
|
|
375
|
-
}), identities = createIdentityDirectory(client),
|
|
415
|
+
}), identities = createIdentityDirectory(client), resourceClients = benchResourceClients({
|
|
416
|
+
client: client,
|
|
417
|
+
options: options
|
|
418
|
+
}), scope = {
|
|
376
419
|
client: client,
|
|
377
420
|
tag: tag,
|
|
378
421
|
workflowResource: workflowResource,
|
|
422
|
+
...resourceClients !== void 0 ? {
|
|
423
|
+
resourceClients: resourceClients
|
|
424
|
+
} : {},
|
|
379
425
|
executionContext: options.executionContext ?? {
|
|
380
426
|
kind: EXECUTION_KINDS.test
|
|
381
427
|
}
|
|
@@ -391,10 +437,7 @@ function createBench(options = {}) {
|
|
|
391
437
|
clock: clock
|
|
392
438
|
});
|
|
393
439
|
return {
|
|
394
|
-
|
|
395
|
-
tag: tag,
|
|
396
|
-
workflowResource: workflowResource,
|
|
397
|
-
executionContext: scope.executionContext,
|
|
440
|
+
...scope,
|
|
398
441
|
access: access,
|
|
399
442
|
currentUser: access.actor,
|
|
400
443
|
grants: access.grants ?? [],
|
|
@@ -429,10 +472,21 @@ function createBench(options = {}) {
|
|
|
429
472
|
};
|
|
430
473
|
}
|
|
431
474
|
|
|
475
|
+
function benchResourceClients(args) {
|
|
476
|
+
const {client: client} = args, {serveResources: serveResources, resourceClients: resourceClients} = args.options;
|
|
477
|
+
if (serveResources === void 0 || serveResources.length === 0) return resourceClients;
|
|
478
|
+
const nonDataset = serveResources.find(served => served.type !== "dataset");
|
|
479
|
+
if (nonDataset !== void 0) throw new Error(`createBench: serveResources can only serve dataset siblings of the bench store — got ${nonDataset.type} "${nonDataset.id}". Pass resourceClients for other resource types.`);
|
|
480
|
+
return parsed => {
|
|
481
|
+
const target = resourceFromParsed(parsed);
|
|
482
|
+
return serveResources.some(served => sameResource(served, target)) ? client.withConfig(datasetResourceParts(target.id)) : resourceClients?.(parsed);
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
432
486
|
function assertIsoInstant(iso) {
|
|
433
487
|
const ms = Date.parse(iso);
|
|
434
488
|
if (Number.isNaN(ms)) throw new Error(`bench clock: expected an ISO-8601 instant, got ${JSON.stringify(iso)}`);
|
|
435
489
|
return new Date(ms).toISOString();
|
|
436
490
|
}
|
|
437
491
|
|
|
438
|
-
export { ALL_ACCESS, ALL_ACCESS_USER, BENCH_TAG, DEFAULT_WORKFLOW_RESOURCE, WILDCARD_GRANTS, createBench, createBenchEngine, releaseField, subjectField };
|
|
492
|
+
export { ALL_ACCESS, ALL_ACCESS_USER, BENCH_TAG, DEFAULT_WORKFLOW_RESOURCE, WILDCARD_GRANTS, createBench, createBenchEngine, releaseField, startedInstanceFixture, subjectField };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/workflow-engine-test",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "In-memory test bench (createBench) for @sanity/workflow-engine.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@sanity-labs/client-fake-for-test": "^0.8.0",
|
|
46
|
-
"@sanity/workflow-engine": "0.
|
|
46
|
+
"@sanity/workflow-engine": "0.17.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@sanity/client": "^7.22.1",
|