@sanity/workflow-engine-test 0.7.0 → 0.9.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/README.md +19 -14
- package/dist/index.cjs +64 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -57
- package/dist/index.d.ts +81 -57
- package/dist/index.js +66 -21
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,19 +17,24 @@ npm install -D @sanity/workflow-engine-test
|
|
|
17
17
|
|
|
18
18
|
A workflow stage can deploy **mutation guards** — `temp.system.guard` documents
|
|
19
19
|
that lock the documents it governs while the stage is active, and lift when it
|
|
20
|
-
exits.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
exits. The lake does not enforce this doc type yet: in production, guard
|
|
21
|
+
enforcement is the engine's own **optimistic** evaluation (advisory verdicts
|
|
22
|
+
and engine-side write denials), and the lake ACL is the only hard boundary.
|
|
23
|
+
The bench installs the _intended_ lake-side contract at its client's write
|
|
24
|
+
seam, so tests exercise the rejection semantics guards are designed to have
|
|
25
|
+
once the lake enforces them. There are two distinct things you test, and the
|
|
26
|
+
bench gives you a helper for each:
|
|
27
|
+
|
|
28
|
+
- **Write-time rejection (simulated)** — `bench.editDocument({documentId, patch})`
|
|
29
|
+
writes through the bench client, and a write a deployed guard denies throws
|
|
30
|
+
`MutationGuardDeniedError` — the rejection the lake is designed to apply to
|
|
31
|
+
_any_ client once guard enforcement ships; the bench reproduces it on the
|
|
27
32
|
client it mints.
|
|
28
33
|
- **Read-time preflight** — `bench.activeGuardsForDocument(id)` answers "_would_
|
|
29
34
|
a write be denied right now?" _without_ writing. This is the check a UI uses
|
|
30
35
|
to disable a button or show a lock ahead of time. It's a pure read over the
|
|
31
|
-
same evaluation path as
|
|
32
|
-
actually do — and it works against any client.
|
|
36
|
+
same evaluation path as the bench's write rejection, so its verdict matches
|
|
37
|
+
what a bench write would actually do — and it works against any client.
|
|
33
38
|
|
|
34
39
|
`bench.listGuards()` returns the deployed guard docs for inspection.
|
|
35
40
|
|
|
@@ -40,15 +45,15 @@ const bench = createBench({documents: [{_id: 'doc-1', _type: 'article', body: 'b
|
|
|
40
45
|
// Preflight: is the doc locked for this user right now?
|
|
41
46
|
const active = await bench.activeGuardsForDocument('doc-1')
|
|
42
47
|
|
|
43
|
-
//
|
|
44
|
-
await expect(bench.editDocument('doc-1', {body: 'edited'})).rejects.toThrow(
|
|
48
|
+
// Rejection: a guard-violating write throws (bench-simulated lake contract).
|
|
49
|
+
await expect(bench.editDocument({documentId: 'doc-1', patch: {body: 'edited'}})).rejects.toThrow(
|
|
45
50
|
MutationGuardDeniedError,
|
|
46
51
|
)
|
|
47
52
|
```
|
|
48
53
|
|
|
49
|
-
> To share one store across benches with
|
|
50
|
-
> bench's already-wired client: `createBench({client: other.client,
|
|
51
|
-
> `mutationGuard` rides along, so the second bench
|
|
54
|
+
> To share one store across benches with the write seam intact, pass another
|
|
55
|
+
> bench's already-wired client: `createBench({client: other.client, tag})`. The
|
|
56
|
+
> `mutationGuard` rides along, so the second bench denies the same locks. Only
|
|
52
57
|
> a bare hand-built `createTestClient()` opts out of write rejection — the
|
|
53
58
|
> read-time preflight needs no wiring either way.
|
|
54
59
|
|
package/dist/index.cjs
CHANGED
|
@@ -5014,7 +5014,7 @@ const DEFAULT_WORKFLOW_RESOURCE = {
|
|
|
5014
5014
|
type: "dataset",
|
|
5015
5015
|
id: "test.test"
|
|
5016
5016
|
}, ALL_ACCESS_USER = {
|
|
5017
|
-
kind: "
|
|
5017
|
+
kind: "person",
|
|
5018
5018
|
id: "bench-user",
|
|
5019
5019
|
roles: ["*"]
|
|
5020
5020
|
}, WILDCARD_GRANTS = [
|
|
@@ -5073,7 +5073,11 @@ function resolveBenchAccess(options) {
|
|
|
5073
5073
|
grants: options.grants ?? WILDCARD_GRANTS
|
|
5074
5074
|
};
|
|
5075
5075
|
}
|
|
5076
|
-
function accessFor(
|
|
5076
|
+
function accessFor({
|
|
5077
|
+
access,
|
|
5078
|
+
args,
|
|
5079
|
+
canTokenResolve
|
|
5080
|
+
}) {
|
|
5077
5081
|
if (args.access !== void 0) return args.access;
|
|
5078
5082
|
if (args.actor === void 0 && args.grants === void 0) return access;
|
|
5079
5083
|
const inheritedGrants = canTokenResolve ? void 0 : access.grants, grants = args.grants ?? inheritedGrants;
|
|
@@ -5086,10 +5090,14 @@ function stripAccessShortcuts(args) {
|
|
|
5086
5090
|
const rest = { ...args };
|
|
5087
5091
|
return delete rest.access, delete rest.actor, delete rest.grants, rest;
|
|
5088
5092
|
}
|
|
5089
|
-
function createEngineWrappers(
|
|
5093
|
+
function createEngineWrappers({
|
|
5094
|
+
scope,
|
|
5095
|
+
access,
|
|
5096
|
+
clock
|
|
5097
|
+
}) {
|
|
5090
5098
|
const canTokenResolve = "request" in scope.client, withAccess = (args) => ({
|
|
5091
5099
|
...scope,
|
|
5092
|
-
access: accessFor(access, args, canTokenResolve),
|
|
5100
|
+
access: accessFor({ access, args, canTokenResolve }),
|
|
5093
5101
|
clock,
|
|
5094
5102
|
...stripAccessShortcuts(args)
|
|
5095
5103
|
});
|
|
@@ -5121,8 +5129,8 @@ function createGuardHelpers(client) {
|
|
|
5121
5129
|
...as ? { identity: as.id } : {}
|
|
5122
5130
|
});
|
|
5123
5131
|
},
|
|
5124
|
-
editDocument: async (documentId, patch,
|
|
5125
|
-
const action =
|
|
5132
|
+
editDocument: async ({ documentId, patch, action: editAction, as }) => {
|
|
5133
|
+
const action = editAction ?? "update", writer = as ? client.withConfig({
|
|
5126
5134
|
accessControl: { currentUser: { id: as.id }, grants: WILDCARD_GRANTS }
|
|
5127
5135
|
}) : client;
|
|
5128
5136
|
return action === "delete" ? await writer.delete(documentId) : await writer.patch(documentId).set(patch).commit(), await client.getDocument(documentId);
|
|
@@ -5133,7 +5141,11 @@ function createQueryHelpers(scope) {
|
|
|
5133
5141
|
const withParams = (params) => params !== void 0 ? { params } : {};
|
|
5134
5142
|
return {
|
|
5135
5143
|
query: (groq, params) => workflowEngine.workflow.query({ ...scope, groq, ...withParams(params) }),
|
|
5136
|
-
queryInScope: (
|
|
5144
|
+
queryInScope: ({
|
|
5145
|
+
instanceId,
|
|
5146
|
+
groq,
|
|
5147
|
+
params
|
|
5148
|
+
}) => workflowEngine.workflow.queryInScope({ ...scope, instanceId, groq, ...withParams(params) })
|
|
5137
5149
|
};
|
|
5138
5150
|
}
|
|
5139
5151
|
function createReadHelpers(scope) {
|
|
@@ -5172,24 +5184,49 @@ function createReadHelpers(scope) {
|
|
|
5172
5184
|
{ ref: subjectRef, entry: entryName, tag }
|
|
5173
5185
|
)
|
|
5174
5186
|
),
|
|
5175
|
-
instancesByStage: async (workflowName, stage,
|
|
5176
|
-
const
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
);
|
|
5187
|
+
instancesByStage: async ({ workflowName, stage, openOnly }) => {
|
|
5188
|
+
const { query, params } = workflowEngine.instancesQuery({
|
|
5189
|
+
tag,
|
|
5190
|
+
filter: { definition: workflowName, stage, includeCompleted: !openOnly }
|
|
5191
|
+
});
|
|
5192
|
+
return client.fetch(query, params);
|
|
5182
5193
|
},
|
|
5183
5194
|
instancesForDocument: async (document2) => workflowEngine.workflow.instancesForDocument({ ...scope, document: document2 }),
|
|
5184
5195
|
snapshot: () => client.snapshot()
|
|
5185
5196
|
};
|
|
5186
5197
|
}
|
|
5198
|
+
function subjectField(documentId, opts) {
|
|
5199
|
+
return {
|
|
5200
|
+
type: "doc.ref",
|
|
5201
|
+
name: opts?.name ?? "subject",
|
|
5202
|
+
value: workflowEngine.gdrRef({
|
|
5203
|
+
res: DEFAULT_WORKFLOW_RESOURCE,
|
|
5204
|
+
documentId,
|
|
5205
|
+
type: opts?.type ?? "document"
|
|
5206
|
+
})
|
|
5207
|
+
};
|
|
5208
|
+
}
|
|
5209
|
+
function releaseField(releaseName, opts) {
|
|
5210
|
+
return {
|
|
5211
|
+
type: "release.ref",
|
|
5212
|
+
name: opts?.name ?? "release",
|
|
5213
|
+
value: {
|
|
5214
|
+
id: workflowEngine.gdrFromResource(DEFAULT_WORKFLOW_RESOURCE, `_.releases.${releaseName}`),
|
|
5215
|
+
type: "system.release",
|
|
5216
|
+
releaseName
|
|
5217
|
+
}
|
|
5218
|
+
};
|
|
5219
|
+
}
|
|
5187
5220
|
function createBench(options = {}) {
|
|
5188
5221
|
const tag = options.tag ?? "bench";
|
|
5189
5222
|
workflowEngine.validateTag(tag);
|
|
5190
|
-
const workflowResource = options.workflowResource ?? DEFAULT_WORKFLOW_RESOURCE, client = resolveBenchClient(options), scope = {
|
|
5223
|
+
const workflowResource = options.workflowResource ?? DEFAULT_WORKFLOW_RESOURCE, client = resolveBenchClient(options), scope = {
|
|
5224
|
+
client,
|
|
5225
|
+
tag,
|
|
5226
|
+
workflowResource
|
|
5227
|
+
}, access = resolveBenchAccess(options);
|
|
5191
5228
|
let frozenNow = options.now !== void 0 ? assertIsoInstant(options.now) : void 0;
|
|
5192
|
-
const clock = () => frozenNow ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5229
|
+
const clock = () => frozenNow ?? (/* @__PURE__ */ new Date()).toISOString(), engine = createEngineWrappers({ scope, access, clock }), reads = createReadHelpers(scope);
|
|
5193
5230
|
return {
|
|
5194
5231
|
client,
|
|
5195
5232
|
access,
|
|
@@ -5205,10 +5242,16 @@ function createBench(options = {}) {
|
|
|
5205
5242
|
throw new Error(`bench.advance: ms must be a finite number, got ${ms2}`);
|
|
5206
5243
|
frozenNow = new Date(Date.parse(frozenNow ?? (/* @__PURE__ */ new Date()).toISOString()) + ms2).toISOString();
|
|
5207
5244
|
},
|
|
5208
|
-
...
|
|
5209
|
-
...
|
|
5245
|
+
...engine,
|
|
5246
|
+
...reads,
|
|
5210
5247
|
...createGuardHelpers(client),
|
|
5211
|
-
...createQueryHelpers(scope)
|
|
5248
|
+
...createQueryHelpers(scope),
|
|
5249
|
+
completePendingEffect: async ({ effect, ...rest }) => {
|
|
5250
|
+
const match = (await reads.pendingEffects(rest.instanceId)).find((e) => e.name === effect);
|
|
5251
|
+
if (match === void 0)
|
|
5252
|
+
throw new Error(`completePendingEffect: no pending effect named "${effect}"`);
|
|
5253
|
+
return engine.completeEffect({ ...rest, effectKey: match._key });
|
|
5254
|
+
}
|
|
5212
5255
|
};
|
|
5213
5256
|
}
|
|
5214
5257
|
function assertIsoInstant(iso) {
|
|
@@ -5222,4 +5265,6 @@ exports.ALL_ACCESS_USER = ALL_ACCESS_USER;
|
|
|
5222
5265
|
exports.DEFAULT_WORKFLOW_RESOURCE = DEFAULT_WORKFLOW_RESOURCE;
|
|
5223
5266
|
exports.WILDCARD_GRANTS = WILDCARD_GRANTS;
|
|
5224
5267
|
exports.createBench = createBench;
|
|
5268
|
+
exports.releaseField = releaseField;
|
|
5269
|
+
exports.subjectField = subjectField;
|
|
5225
5270
|
//# sourceMappingURL=index.cjs.map
|