@sanity/runtime-cli 17.9.0 → 17.10.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 +21 -20
- package/dist/actions/blueprints/pre-deploy.js +2 -0
- package/dist/actions/blueprints/stacks.d.ts +1 -0
- package/dist/commands/functions/test.d.ts +1 -0
- package/dist/commands/functions/test.js +5 -0
- package/dist/cores/functions/test.js +41 -26
- package/dist/utils/functions/child-process-wrapper.js +3 -2
- package/dist/utils/functions/child-runtime/durable.d.ts +2 -2
- package/dist/utils/functions/child-runtime/durable.js +70 -3
- package/dist/utils/functions/durable-logs.d.ts +16 -0
- package/dist/utils/functions/durable-logs.js +52 -0
- package/dist/utils/functions/invoke/local.js +2 -1
- package/dist/utils/types.d.ts +18 -4
- package/oclif.manifest.json +8 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ EXAMPLES
|
|
|
90
90
|
$ sanity-run blueprints add function --name my-function --fn-type document-create --fn-type document-update --lang js
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
_See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
93
|
+
_See code: [src/commands/blueprints/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/blueprints/add.ts)_
|
|
94
94
|
|
|
95
95
|
## `sanity-run blueprints config`
|
|
96
96
|
|
|
@@ -127,7 +127,7 @@ EXAMPLES
|
|
|
127
127
|
$ sanity-run blueprints config --edit --project-id <projectId> --stack <name-or-id>
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
_See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
130
|
+
_See code: [src/commands/blueprints/config.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/blueprints/config.ts)_
|
|
131
131
|
|
|
132
132
|
## `sanity-run blueprints deploy`
|
|
133
133
|
|
|
@@ -183,7 +183,7 @@ EXAMPLES
|
|
|
183
183
|
$ sanity-run blueprints deploy --new-stack-name <new-name>
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
_See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
186
|
+
_See code: [src/commands/blueprints/deploy.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/blueprints/deploy.ts)_
|
|
187
187
|
|
|
188
188
|
## `sanity-run blueprints destroy`
|
|
189
189
|
|
|
@@ -221,7 +221,7 @@ EXAMPLES
|
|
|
221
221
|
$ sanity-run blueprints destroy --stack <name-or-id> --project-id <projectId> --force --no-wait
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
-
_See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
224
|
+
_See code: [src/commands/blueprints/destroy.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/blueprints/destroy.ts)_
|
|
225
225
|
|
|
226
226
|
## `sanity-run blueprints doctor`
|
|
227
227
|
|
|
@@ -253,7 +253,7 @@ EXAMPLES
|
|
|
253
253
|
$ sanity-run blueprints doctor --fix
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
-
_See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
256
|
+
_See code: [src/commands/blueprints/doctor.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/blueprints/doctor.ts)_
|
|
257
257
|
|
|
258
258
|
## `sanity-run blueprints info`
|
|
259
259
|
|
|
@@ -291,7 +291,7 @@ EXAMPLES
|
|
|
291
291
|
$ sanity-run blueprints info --organization-id <orgId> --stack <name-or-id>
|
|
292
292
|
```
|
|
293
293
|
|
|
294
|
-
_See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
294
|
+
_See code: [src/commands/blueprints/info.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/blueprints/info.ts)_
|
|
295
295
|
|
|
296
296
|
## `sanity-run blueprints init [DIR]`
|
|
297
297
|
|
|
@@ -355,7 +355,7 @@ EXAMPLES
|
|
|
355
355
|
$ sanity-run blueprints init old-stack --type <json|js|ts> --project-id <projectId> --stack-id <existingStackId>
|
|
356
356
|
```
|
|
357
357
|
|
|
358
|
-
_See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
358
|
+
_See code: [src/commands/blueprints/init.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/blueprints/init.ts)_
|
|
359
359
|
|
|
360
360
|
## `sanity-run blueprints logs`
|
|
361
361
|
|
|
@@ -401,7 +401,7 @@ EXAMPLES
|
|
|
401
401
|
$ sanity-run blueprints logs --before 2026-05-01T00:00:00Z
|
|
402
402
|
```
|
|
403
403
|
|
|
404
|
-
_See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
404
|
+
_See code: [src/commands/blueprints/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/blueprints/logs.ts)_
|
|
405
405
|
|
|
406
406
|
## `sanity-run blueprints mint-deploy-token`
|
|
407
407
|
|
|
@@ -446,7 +446,7 @@ EXAMPLES
|
|
|
446
446
|
$ sanity-run blueprints mint-deploy-token --organization-id <orgId>
|
|
447
447
|
```
|
|
448
448
|
|
|
449
|
-
_See code: [src/commands/blueprints/mint-deploy-token.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
449
|
+
_See code: [src/commands/blueprints/mint-deploy-token.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/blueprints/mint-deploy-token.ts)_
|
|
450
450
|
|
|
451
451
|
## `sanity-run blueprints plan`
|
|
452
452
|
|
|
@@ -479,7 +479,7 @@ EXAMPLES
|
|
|
479
479
|
$ sanity-run blueprints plan --organization-id <orgId> --stack <name-or-id>
|
|
480
480
|
```
|
|
481
481
|
|
|
482
|
-
_See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
482
|
+
_See code: [src/commands/blueprints/plan.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/blueprints/plan.ts)_
|
|
483
483
|
|
|
484
484
|
## `sanity-run blueprints promote`
|
|
485
485
|
|
|
@@ -515,7 +515,7 @@ EXAMPLES
|
|
|
515
515
|
$ sanity-run blueprints promote --new-stack-name <new-name>
|
|
516
516
|
```
|
|
517
517
|
|
|
518
|
-
_See code: [src/commands/blueprints/promote.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
518
|
+
_See code: [src/commands/blueprints/promote.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/blueprints/promote.ts)_
|
|
519
519
|
|
|
520
520
|
## `sanity-run blueprints stacks`
|
|
521
521
|
|
|
@@ -556,7 +556,7 @@ EXAMPLES
|
|
|
556
556
|
$ sanity-run blueprints stacks --organization-id <organizationId> --include-projects
|
|
557
557
|
```
|
|
558
558
|
|
|
559
|
-
_See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
559
|
+
_See code: [src/commands/blueprints/stacks.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/blueprints/stacks.ts)_
|
|
560
560
|
|
|
561
561
|
## `sanity-run functions add`
|
|
562
562
|
|
|
@@ -608,7 +608,7 @@ EXAMPLES
|
|
|
608
608
|
$ sanity-run functions add --name my-function --type document-create --type document-update --lang js
|
|
609
609
|
```
|
|
610
610
|
|
|
611
|
-
_See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
611
|
+
_See code: [src/commands/functions/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/functions/add.ts)_
|
|
612
612
|
|
|
613
613
|
## `sanity-run functions dev`
|
|
614
614
|
|
|
@@ -643,7 +643,7 @@ EXAMPLES
|
|
|
643
643
|
$ sanity-run functions dev --timeout 60
|
|
644
644
|
```
|
|
645
645
|
|
|
646
|
-
_See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
646
|
+
_See code: [src/commands/functions/dev.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/functions/dev.ts)_
|
|
647
647
|
|
|
648
648
|
## `sanity-run functions env add NAME KEY VALUE`
|
|
649
649
|
|
|
@@ -676,7 +676,7 @@ EXAMPLES
|
|
|
676
676
|
$ sanity-run functions env add --stack <name-or-id> MyFunction API_URL https://api.example.com/
|
|
677
677
|
```
|
|
678
678
|
|
|
679
|
-
_See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
679
|
+
_See code: [src/commands/functions/env/add.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/functions/env/add.ts)_
|
|
680
680
|
|
|
681
681
|
## `sanity-run functions env list NAME`
|
|
682
682
|
|
|
@@ -706,7 +706,7 @@ EXAMPLES
|
|
|
706
706
|
$ sanity-run functions env list --stack <name-or-id> MyFunction
|
|
707
707
|
```
|
|
708
708
|
|
|
709
|
-
_See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
709
|
+
_See code: [src/commands/functions/env/list.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/functions/env/list.ts)_
|
|
710
710
|
|
|
711
711
|
## `sanity-run functions env remove NAME KEY`
|
|
712
712
|
|
|
@@ -738,7 +738,7 @@ EXAMPLES
|
|
|
738
738
|
$ sanity-run functions env remove --stack <name-or-id> MyFunction API_URL
|
|
739
739
|
```
|
|
740
740
|
|
|
741
|
-
_See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
741
|
+
_See code: [src/commands/functions/env/remove.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/functions/env/remove.ts)_
|
|
742
742
|
|
|
743
743
|
## `sanity-run functions logs [NAME]`
|
|
744
744
|
|
|
@@ -778,7 +778,7 @@ EXAMPLES
|
|
|
778
778
|
$ sanity-run functions logs <name> --delete
|
|
779
779
|
```
|
|
780
780
|
|
|
781
|
-
_See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
781
|
+
_See code: [src/commands/functions/logs.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/functions/logs.ts)_
|
|
782
782
|
|
|
783
783
|
## `sanity-run functions test [NAME]`
|
|
784
784
|
|
|
@@ -790,7 +790,7 @@ USAGE
|
|
|
790
790
|
<value>] | | | --file-before <value> | --file-after <value> | --document-id-before <value> | --document-id-after
|
|
791
791
|
<value>] [--data-after <value> | | | | | | | ] [-e create|update|delete] [-t <value>] [-a <value>]
|
|
792
792
|
[--with-user-token] [--media-library-id <value> | [--project-id <value> | --organization-id <value>] | --dataset
|
|
793
|
-
<value>]
|
|
793
|
+
<value>] [--no-wait]
|
|
794
794
|
|
|
795
795
|
ARGUMENTS
|
|
796
796
|
[NAME] The name of the Sanity Function
|
|
@@ -812,6 +812,7 @@ FLAGS
|
|
|
812
812
|
--file-before=<value> Original document
|
|
813
813
|
--json Format output as json
|
|
814
814
|
--media-library-id=<value> Sanity Media Library ID to use
|
|
815
|
+
--no-wait Skip durable wait delays instead of sleeping
|
|
815
816
|
--organization-id=<value> Sanity organization ID used to scope Blueprint and Stack
|
|
816
817
|
--project-id=<value> Sanity project ID used to scope Blueprint and Stack
|
|
817
818
|
--with-user-token Prime access token from CLI config
|
|
@@ -835,7 +836,7 @@ EXAMPLES
|
|
|
835
836
|
$ sanity-run functions test <name> --event update --data-before '{ "title": "before" }' --data-after '{ "title": "after" }'
|
|
836
837
|
```
|
|
837
838
|
|
|
838
|
-
_See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v17.
|
|
839
|
+
_See code: [src/commands/functions/test.ts](https://github.com/sanity-io/runtime-cli/blob/v17.10.0/src/commands/functions/test.ts)_
|
|
839
840
|
|
|
840
841
|
## `sanity-run help [COMMAND]`
|
|
841
842
|
|
|
@@ -25,6 +25,7 @@ export default class TestCommand extends ResolvedCommand<typeof TestCommand> {
|
|
|
25
25
|
'document-id-after': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
26
26
|
'with-user-token': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
27
27
|
'media-library-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
28
|
+
'no-wait': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
28
29
|
};
|
|
29
30
|
run(): Promise<Record<string, unknown> | undefined>;
|
|
30
31
|
}
|
|
@@ -146,6 +146,11 @@ Provide test data via --data (inline JSON), --file (JSON file), or --document-id
|
|
|
146
146
|
exclusive: ['project-id', 'dataset'],
|
|
147
147
|
required: false,
|
|
148
148
|
}),
|
|
149
|
+
'no-wait': Flags.boolean({
|
|
150
|
+
description: 'Skip durable wait delays instead of sleeping',
|
|
151
|
+
default: false,
|
|
152
|
+
required: false,
|
|
153
|
+
}),
|
|
149
154
|
};
|
|
150
155
|
async run() {
|
|
151
156
|
const { name } = this.args;
|
|
@@ -3,6 +3,7 @@ import { testAction } from '../../actions/functions/test.js';
|
|
|
3
3
|
import config from '../../config.js';
|
|
4
4
|
import { STEP_PHASE_GLYPH } from '../../constants.js';
|
|
5
5
|
import { resolveDeployedResources } from '../../utils/blueprints/resolve-deployed-resources.js';
|
|
6
|
+
import { createDurableJsonCollector } from '../../utils/functions/durable-logs.js';
|
|
6
7
|
import { findFunctionInResources, getFunctionNames } from '../../utils/functions/find.js';
|
|
7
8
|
import { runFunction } from '../../utils/functions/invoke/nested.js';
|
|
8
9
|
import { buildContext, buildInvokeOptions, collectInvokeInput, defaultInvocationDeps, resolveInvocationPlan, } from '../../utils/functions/invoke/resolve-invoke-type.js';
|
|
@@ -12,19 +13,25 @@ import { isEventType, } from '../../utils/types.js';
|
|
|
12
13
|
* @param events
|
|
13
14
|
*/
|
|
14
15
|
function renderStepTimeline(events) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const lines = [];
|
|
17
|
+
for (const event of events) {
|
|
18
|
+
if (event.kind === 'log') {
|
|
19
|
+
lines.push(`${event.timestamp} ${event.level.toUpperCase()} ${event.message}`);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (event.phase === 'start')
|
|
23
|
+
continue;
|
|
18
24
|
const glyph = STEP_PHASE_GLYPH[event.phase] ?? '•';
|
|
19
25
|
const duration = typeof event.durationMs === 'number' ? ` (${Math.round(event.durationMs)}ms)` : '';
|
|
20
26
|
const detail = event.phase === 'error' && event.error ? ` ${event.error.message}` : '';
|
|
21
|
-
|
|
22
|
-
}
|
|
27
|
+
lines.push(` ${glyph} ${event.name} [${event.kind}]${duration}${detail}`);
|
|
28
|
+
}
|
|
29
|
+
return lines;
|
|
23
30
|
}
|
|
24
31
|
export async function functionTestCore(options) {
|
|
25
32
|
const { blueprint, log, error, args, flags, helpText } = options;
|
|
26
33
|
const { name: fnName } = args;
|
|
27
|
-
const { event, timeout, api, 'with-user-token': withUserToken } = flags;
|
|
34
|
+
const { event, timeout, api, 'with-user-token': withUserToken, 'no-wait': noWait } = flags;
|
|
28
35
|
const { 'project-id': projectId = blueprint?.projectId, 'organization-id': organizationId = blueprint?.organizationId, } = flags;
|
|
29
36
|
if (!fnName) {
|
|
30
37
|
const functionNames = getFunctionNames(blueprint.resources);
|
|
@@ -77,10 +84,17 @@ export async function functionTestCore(options) {
|
|
|
77
84
|
// Records each nested context.invoke() so they can be surfaced in order after the spinner resolves.
|
|
78
85
|
const nestedRuns = [];
|
|
79
86
|
let nestedOrder = 0;
|
|
80
|
-
const
|
|
87
|
+
const durableJson = createDurableJsonCollector();
|
|
81
88
|
const onStepEvent = plan.isDurable
|
|
82
89
|
? (event) => {
|
|
83
|
-
|
|
90
|
+
if (flags.json) {
|
|
91
|
+
durableJson.record(event);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (const line of renderStepTimeline([event])) {
|
|
95
|
+
if (line !== null)
|
|
96
|
+
log(line);
|
|
97
|
+
}
|
|
84
98
|
}
|
|
85
99
|
: undefined;
|
|
86
100
|
/**
|
|
@@ -120,6 +134,7 @@ export async function functionTestCore(options) {
|
|
|
120
134
|
depth: 0,
|
|
121
135
|
callStack: [fnName],
|
|
122
136
|
onStepEvent,
|
|
137
|
+
noWait,
|
|
123
138
|
});
|
|
124
139
|
if (error) {
|
|
125
140
|
spinner.fail('Function execution failed.');
|
|
@@ -131,12 +146,6 @@ export async function functionTestCore(options) {
|
|
|
131
146
|
logNestedRuns();
|
|
132
147
|
const errorMessage = error.stack || error.message || error.name;
|
|
133
148
|
const isTimeout = error.message?.includes('Timeout');
|
|
134
|
-
if (plan.isDurable && stepEvents.length > 0) {
|
|
135
|
-
log('Steps:');
|
|
136
|
-
for (const line of renderStepTimeline(stepEvents)) {
|
|
137
|
-
log(line);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
149
|
return {
|
|
141
150
|
success: false,
|
|
142
151
|
error: errorMessage,
|
|
@@ -147,20 +156,26 @@ export async function functionTestCore(options) {
|
|
|
147
156
|
};
|
|
148
157
|
}
|
|
149
158
|
spinner.succeed('Function execution succeeded.');
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (plan.isDurable && stepEvents.length > 0) {
|
|
158
|
-
log('Steps:');
|
|
159
|
-
for (const line of renderStepTimeline(stepEvents)) {
|
|
160
|
-
log(line);
|
|
159
|
+
if (!plan.isDurable) {
|
|
160
|
+
log('Logs:');
|
|
161
|
+
log(logs || '');
|
|
162
|
+
logNestedRuns();
|
|
163
|
+
if (response) {
|
|
164
|
+
log('Response:');
|
|
165
|
+
log(JSON.stringify(response, null, 2));
|
|
161
166
|
}
|
|
162
167
|
}
|
|
163
|
-
|
|
168
|
+
const durableResult = durableJson.result();
|
|
169
|
+
return {
|
|
170
|
+
success: true,
|
|
171
|
+
json: plan.isDurable
|
|
172
|
+
? {
|
|
173
|
+
response: response ?? null,
|
|
174
|
+
steps: durableResult.steps,
|
|
175
|
+
logs: durableResult.logs,
|
|
176
|
+
}
|
|
177
|
+
: { response, logs },
|
|
178
|
+
};
|
|
164
179
|
}
|
|
165
180
|
catch (error) {
|
|
166
181
|
return {
|
|
@@ -41,7 +41,7 @@ process.on('message', async (data) => {
|
|
|
41
41
|
|
|
42
42
|
// Otherwise this is the initial start message. Support the legacy untyped
|
|
43
43
|
// `{srcPath, payload}` shape as well as the new `{type: 'start', ...}`.
|
|
44
|
-
const {srcPath, payload, isDurable} = jsonData
|
|
44
|
+
const {srcPath, payload, isDurable, noWait} = jsonData
|
|
45
45
|
const {context, ...event} = payload
|
|
46
46
|
|
|
47
47
|
let logs = ''
|
|
@@ -83,9 +83,10 @@ process.on('message', async (data) => {
|
|
|
83
83
|
if (isDurable) {
|
|
84
84
|
json = await runDurable(
|
|
85
85
|
handler,
|
|
86
|
-
{context, event
|
|
86
|
+
{context, event},
|
|
87
87
|
{
|
|
88
88
|
emit: (e) => process.send(JSON.stringify({type: 'step-event', event: e})),
|
|
89
|
+
wait: (ms) => new Promise((resolve) => setTimeout(resolve, noWait ? 0 : ms)),
|
|
89
90
|
},
|
|
90
91
|
)
|
|
91
92
|
} else {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {DurableContext, DurableHandler, DurableLogger, GenericEvent} from '@sanity/functions'
|
|
2
|
-
import type {
|
|
2
|
+
import type {DurableRuntimeEvent} from '../../types.js'
|
|
3
3
|
|
|
4
4
|
type RunDurableArgs = {
|
|
5
5
|
context: DurableContext
|
|
@@ -8,7 +8,7 @@ type RunDurableArgs = {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
type DurableIO = {
|
|
11
|
-
emit: (event:
|
|
11
|
+
emit: (event: DurableRuntimeEvent) => void
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function runDurable(
|
|
@@ -1,17 +1,60 @@
|
|
|
1
1
|
import {performance} from 'node:perf_hooks'
|
|
2
|
+
import {inspect} from 'node:util'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param duration
|
|
7
|
+
* @return {number}
|
|
8
|
+
*/
|
|
9
|
+
const durationToMs = (duration = {}) => {
|
|
10
|
+
const {days = 0, hours = 0, minutes = 0, seconds = 0} = duration
|
|
11
|
+
return (((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param args
|
|
17
|
+
*/
|
|
18
|
+
const formatArgs = (args) =>
|
|
19
|
+
args.map((a) => (typeof a === 'string' ? a : inspect(a, {depth: null}))).join(' ')
|
|
2
20
|
|
|
3
21
|
/**
|
|
4
22
|
* Runs a durable function handler
|
|
5
23
|
* @param handler - callback being run
|
|
6
24
|
* @param context - data to send to the operation
|
|
7
25
|
* @param event - the step being run
|
|
8
|
-
* @param logger
|
|
9
26
|
* @param io
|
|
10
27
|
*/
|
|
11
|
-
export async function runDurable(handler, {context, event
|
|
28
|
+
export async function runDurable(handler, {context, event}, io) {
|
|
29
|
+
let currentStep = null
|
|
30
|
+
const streamLog = (level, args) => {
|
|
31
|
+
const date = new Date()
|
|
32
|
+
const timestamp = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
|
33
|
+
|
|
34
|
+
return io.emit({
|
|
35
|
+
timestamp,
|
|
36
|
+
kind: 'log',
|
|
37
|
+
phase: 'log',
|
|
38
|
+
step: currentStep,
|
|
39
|
+
level,
|
|
40
|
+
message: formatArgs(args),
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// @todo: need to throw error for use of `console.log` in a durable function
|
|
45
|
+
const logger = {
|
|
46
|
+
log: (...a) => streamLog('info', a),
|
|
47
|
+
info: (...a) => streamLog('info', a),
|
|
48
|
+
warn: (...a) => streamLog('warn', a),
|
|
49
|
+
error: (...a) => streamLog('error', a),
|
|
50
|
+
}
|
|
51
|
+
|
|
12
52
|
const track = async (name, kind, produce) => {
|
|
13
53
|
io.emit({name, kind, phase: 'start'})
|
|
14
54
|
const startedAt = performance.now()
|
|
55
|
+
const prev = currentStep
|
|
56
|
+
currentStep = name
|
|
57
|
+
|
|
15
58
|
try {
|
|
16
59
|
const value = await produce()
|
|
17
60
|
io.emit({
|
|
@@ -35,11 +78,35 @@ export async function runDurable(handler, {context, event, logger}, io) {
|
|
|
35
78
|
durationMs: performance.now() - startedAt,
|
|
36
79
|
})
|
|
37
80
|
throw err
|
|
81
|
+
} finally {
|
|
82
|
+
currentStep = prev
|
|
38
83
|
}
|
|
39
84
|
}
|
|
40
85
|
|
|
41
86
|
const step = {
|
|
42
|
-
run: ({name, handler: fn}) => track(name || '
|
|
87
|
+
run: ({name, handler: fn}) => track(name || '', 'run', () => fn({logger, attempt: 1})),
|
|
88
|
+
wait: ({name, duration}) =>
|
|
89
|
+
track(name || '', 'wait', async () => {
|
|
90
|
+
await io.wait(durationToMs(duration))
|
|
91
|
+
return undefined
|
|
92
|
+
}),
|
|
93
|
+
waitForCondition: ({name, initial, poller, next}) =>
|
|
94
|
+
track(name || '', 'wait-for-condition', async () => {
|
|
95
|
+
let state = initial
|
|
96
|
+
let attempt = 1
|
|
97
|
+
let polling = true
|
|
98
|
+
while (polling) {
|
|
99
|
+
state = await poller(state, {logger, attempt})
|
|
100
|
+
const decision = next(state, {attempt})
|
|
101
|
+
if (decision.shouldRetry) {
|
|
102
|
+
await io.wait(durationToMs(decision.delay))
|
|
103
|
+
attempt += 1
|
|
104
|
+
} else {
|
|
105
|
+
polling = false
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return state
|
|
109
|
+
}),
|
|
43
110
|
}
|
|
44
111
|
|
|
45
112
|
return handler({context, event, step, logger})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DurableLogEvent, DurableRuntimeEvent, DurableStepEvent } from '../types.js';
|
|
2
|
+
type DurableLogLine = Pick<DurableLogEvent, 'level' | 'message'>;
|
|
3
|
+
type DurableJsonStep = DurableStepEvent & {
|
|
4
|
+
logs: DurableLogLine[];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Records a durable log event
|
|
8
|
+
*/
|
|
9
|
+
export declare const createDurableJsonCollector: () => {
|
|
10
|
+
record: (event: DurableRuntimeEvent) => void;
|
|
11
|
+
result: () => {
|
|
12
|
+
steps: DurableJsonStep[];
|
|
13
|
+
logs: DurableLogLine[];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Records a durable log event
|
|
3
|
+
*/
|
|
4
|
+
export const createDurableJsonCollector = () => {
|
|
5
|
+
const steps = [];
|
|
6
|
+
const logLines = [];
|
|
7
|
+
const activeSteps = new Map();
|
|
8
|
+
function record(event) {
|
|
9
|
+
if (event.kind === 'log') {
|
|
10
|
+
const logLine = {
|
|
11
|
+
level: event.level,
|
|
12
|
+
message: event.message,
|
|
13
|
+
};
|
|
14
|
+
if (event.step === null) {
|
|
15
|
+
logLines.push(logLine);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const step = activeSteps.get(event.step);
|
|
19
|
+
if (step) {
|
|
20
|
+
step.logs.push(logLine);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
logLines.push(logLine);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (event.phase === 'start') {
|
|
27
|
+
const step = {
|
|
28
|
+
...event,
|
|
29
|
+
logs: [],
|
|
30
|
+
};
|
|
31
|
+
steps.push(step);
|
|
32
|
+
activeSteps.set(event.name, step);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const step = activeSteps.get(event.name);
|
|
36
|
+
if (!step)
|
|
37
|
+
return;
|
|
38
|
+
step.phase = event.phase;
|
|
39
|
+
step.durationMs = event.durationMs;
|
|
40
|
+
if (event.phase === 'complete') {
|
|
41
|
+
step.output = event.output;
|
|
42
|
+
}
|
|
43
|
+
if (event.phase === 'error') {
|
|
44
|
+
step.error = event.error;
|
|
45
|
+
}
|
|
46
|
+
activeSteps.delete(event.name);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
record,
|
|
50
|
+
result: () => ({ steps, logs: logLines }),
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -47,7 +47,7 @@ export default async function invoke(resource, payload, context, options) {
|
|
|
47
47
|
if (!resource.src) {
|
|
48
48
|
throw new Error(`Function resource "${resource.name}" is missing the 'src' property.`);
|
|
49
49
|
}
|
|
50
|
-
const { forceColor = true, timeout = 10, onNestedInvoke, depth = 0, callStack = [], onStepEvent = () => { }, } = options;
|
|
50
|
+
const { forceColor = true, timeout = 10, onNestedInvoke, depth = 0, callStack = [], onStepEvent = () => { }, noWait = false, } = options;
|
|
51
51
|
// `payload.payload` is the event body for every function type: the document for
|
|
52
52
|
// GROQ-based events, whatever the producer sent for the rest. The handler always
|
|
53
53
|
// receives it as `event.data` (see the `payload` built in `start()` below).
|
|
@@ -306,6 +306,7 @@ export default async function invoke(resource, payload, context, options) {
|
|
|
306
306
|
srcPath: functionPath,
|
|
307
307
|
payload,
|
|
308
308
|
isDurable: resource.type === SANITY_FUNCTION_DURABLE,
|
|
309
|
+
noWait,
|
|
309
310
|
};
|
|
310
311
|
child.send(JSON.stringify(startMessage));
|
|
311
312
|
}
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -162,7 +162,9 @@ export interface InvokeExecutionOptions {
|
|
|
162
162
|
/** Function names already on the invoke call stack (innermost last), used to detect cycles. @internal */
|
|
163
163
|
callStack?: string[];
|
|
164
164
|
/** Stream a durable step transition to the caller. @internal */
|
|
165
|
-
onStepEvent?: (event:
|
|
165
|
+
onStepEvent?: (event: DurableRuntimeEvent) => void;
|
|
166
|
+
/** Don't wait the full duration during a wait durable event @internal */
|
|
167
|
+
noWait?: boolean;
|
|
166
168
|
}
|
|
167
169
|
/**
|
|
168
170
|
* Re-entrant callback that executes a named function from the blueprint on behalf
|
|
@@ -181,6 +183,7 @@ export type ParentToChildMessage = {
|
|
|
181
183
|
srcPath: string;
|
|
182
184
|
payload: unknown;
|
|
183
185
|
isDurable?: boolean;
|
|
186
|
+
noWait?: boolean;
|
|
184
187
|
} | {
|
|
185
188
|
type: 'invoke-response';
|
|
186
189
|
id: string;
|
|
@@ -195,7 +198,7 @@ export type ParentToChildMessage = {
|
|
|
195
198
|
error: InvocationError;
|
|
196
199
|
} | {
|
|
197
200
|
type: 'step-event';
|
|
198
|
-
event:
|
|
201
|
+
event: DurableRuntimeEvent;
|
|
199
202
|
};
|
|
200
203
|
/**
|
|
201
204
|
* IPC messages sent from a function child process back up to the parent.
|
|
@@ -217,7 +220,7 @@ export type ChildToParentMessage = {
|
|
|
217
220
|
error?: InvocationError;
|
|
218
221
|
} | {
|
|
219
222
|
type: 'step-event';
|
|
220
|
-
event:
|
|
223
|
+
event: DurableRuntimeEvent;
|
|
221
224
|
};
|
|
222
225
|
/** @internal */
|
|
223
226
|
export interface InvocationResponse {
|
|
@@ -301,7 +304,7 @@ export type Warning = {
|
|
|
301
304
|
suggestions: string[];
|
|
302
305
|
};
|
|
303
306
|
/** @internal */
|
|
304
|
-
export type DurableOperationSteps = 'run';
|
|
307
|
+
export type DurableOperationSteps = 'run' | 'wait' | 'waitForCondition';
|
|
305
308
|
/** @internal */
|
|
306
309
|
export type DurableStepPhase = 'start' | 'complete' | 'error';
|
|
307
310
|
/**
|
|
@@ -315,6 +318,15 @@ export interface DurableStepEvent {
|
|
|
315
318
|
error?: InvocationError;
|
|
316
319
|
durationMs?: number;
|
|
317
320
|
}
|
|
321
|
+
export interface DurableLogEvent {
|
|
322
|
+
kind: 'log';
|
|
323
|
+
phase: 'log';
|
|
324
|
+
step: string | null;
|
|
325
|
+
level: 'info' | 'warn' | 'error';
|
|
326
|
+
message: string;
|
|
327
|
+
timestamp?: string;
|
|
328
|
+
}
|
|
329
|
+
export type DurableRuntimeEvent = DurableStepEvent | DurableLogEvent;
|
|
318
330
|
export interface FunctionTestFlags {
|
|
319
331
|
data?: string;
|
|
320
332
|
event?: string;
|
|
@@ -333,5 +345,7 @@ export interface FunctionTestFlags {
|
|
|
333
345
|
'document-id-before'?: string;
|
|
334
346
|
'document-id-after'?: string;
|
|
335
347
|
'media-library-id'?: string;
|
|
348
|
+
'no-wait'?: boolean;
|
|
349
|
+
json?: boolean;
|
|
336
350
|
}
|
|
337
351
|
export {};
|
package/oclif.manifest.json
CHANGED
|
@@ -2755,6 +2755,13 @@
|
|
|
2755
2755
|
"hasDynamicHelp": false,
|
|
2756
2756
|
"multiple": false,
|
|
2757
2757
|
"type": "option"
|
|
2758
|
+
},
|
|
2759
|
+
"no-wait": {
|
|
2760
|
+
"description": "Skip durable wait delays instead of sleeping",
|
|
2761
|
+
"name": "no-wait",
|
|
2762
|
+
"required": false,
|
|
2763
|
+
"allowNo": false,
|
|
2764
|
+
"type": "boolean"
|
|
2758
2765
|
}
|
|
2759
2766
|
},
|
|
2760
2767
|
"hasDynamicHelp": false,
|
|
@@ -3141,5 +3148,5 @@
|
|
|
3141
3148
|
]
|
|
3142
3149
|
}
|
|
3143
3150
|
},
|
|
3144
|
-
"version": "17.
|
|
3151
|
+
"version": "17.10.0"
|
|
3145
3152
|
}
|