@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 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.9.0/src/commands/blueprints/add.ts)_
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.9.0/src/commands/blueprints/config.ts)_
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.9.0/src/commands/blueprints/deploy.ts)_
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.9.0/src/commands/blueprints/destroy.ts)_
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.9.0/src/commands/blueprints/doctor.ts)_
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.9.0/src/commands/blueprints/info.ts)_
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.9.0/src/commands/blueprints/init.ts)_
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.9.0/src/commands/blueprints/logs.ts)_
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.9.0/src/commands/blueprints/mint-deploy-token.ts)_
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.9.0/src/commands/blueprints/plan.ts)_
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.9.0/src/commands/blueprints/promote.ts)_
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.9.0/src/commands/blueprints/stacks.ts)_
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.9.0/src/commands/functions/add.ts)_
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.9.0/src/commands/functions/dev.ts)_
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.9.0/src/commands/functions/env/add.ts)_
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.9.0/src/commands/functions/env/list.ts)_
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.9.0/src/commands/functions/env/remove.ts)_
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.9.0/src/commands/functions/logs.ts)_
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.9.0/src/commands/functions/test.ts)_
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
 
@@ -73,6 +73,8 @@ async function preDeployStack(options) {
73
73
  };
74
74
  }
75
75
  spinner.stop().clear();
76
+ if (stack.changeCount === 0)
77
+ return { success: true };
76
78
  logger(styleText('dim', 'Stack deployment progress:'));
77
79
  logger('');
78
80
  const outcome = await waitForOperation({
@@ -48,6 +48,7 @@ type UpdateStackResponse = {
48
48
  error: null;
49
49
  stack: Stack & {
50
50
  operationId: string;
51
+ changeCount?: number;
51
52
  };
52
53
  } | {
53
54
  ok: false;
@@ -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
- return events
16
- .filter((event) => event.phase !== 'start')
17
- .map((event) => {
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
- return ` ${glyph} ${event.name} [${event.kind}]${duration}${detail}`;
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 stepEvents = [];
87
+ const durableJson = createDurableJsonCollector();
81
88
  const onStepEvent = plan.isDurable
82
89
  ? (event) => {
83
- stepEvents.push(event);
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
- log('Logs:');
151
- log(logs || '');
152
- logNestedRuns();
153
- if (response) {
154
- log('Response:');
155
- log(JSON.stringify(response, null, 2));
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
- return { success: true, json: { response, logs } };
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, logger: console},
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 {DurableStepEvent} from '../../types.js'
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: DurableStepEvent) => void
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, logger}, io) {
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 || 'step-run', 'run', () => fn({logger, attempt: 1})),
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
  }
@@ -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: DurableStepEvent) => void;
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: DurableStepEvent;
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: DurableStepEvent;
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 {};
@@ -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.9.0"
3151
+ "version": "17.10.0"
3145
3152
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sanity/runtime-cli",
3
3
  "description": "Sanity's Runtime CLI for Blueprints and Functions",
4
- "version": "17.9.0",
4
+ "version": "17.10.0",
5
5
  "author": "Sanity Runtime Team",
6
6
  "type": "module",
7
7
  "license": "MIT",