@sanity/runtime-cli 17.15.0 → 17.16.0-app-functions.gab0267c

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.
Files changed (66) hide show
  1. package/README.md +19 -19
  2. package/dist/actions/blueprints/assets.d.ts +3 -3
  3. package/dist/actions/blueprints/assets.js +3 -3
  4. package/dist/actions/blueprints/blueprint.d.ts +20 -9
  5. package/dist/actions/blueprints/blueprint.js +25 -79
  6. package/dist/actions/blueprints/config.d.ts +1 -0
  7. package/dist/actions/blueprints/config.js +7 -1
  8. package/dist/actions/blueprints/pre-deploy.js +3 -3
  9. package/dist/actions/blueprints/process-assets.js +3 -2
  10. package/dist/actions/blueprints/stacks.d.ts +9 -3
  11. package/dist/actions/blueprints/stacks.js +11 -8
  12. package/dist/actions/blueprints/wait-for-operation.d.ts +1 -0
  13. package/dist/actions/blueprints/wait-for-operation.js +1 -1
  14. package/dist/baseCommands.js +3 -3
  15. package/dist/config.d.ts +1 -2
  16. package/dist/config.js +1 -2
  17. package/dist/cores/blueprints/deploy.d.ts +8 -0
  18. package/dist/cores/blueprints/deploy.js +12 -4
  19. package/dist/cores/blueprints/destroy.d.ts +14 -1
  20. package/dist/cores/blueprints/destroy.js +2 -1
  21. package/dist/cores/blueprints/doctor.js +2 -2
  22. package/dist/cores/blueprints/init.js +11 -2
  23. package/dist/cores/functions/test.js +43 -9
  24. package/dist/functions/artifacts.d.ts +13 -0
  25. package/dist/functions/artifacts.js +26 -0
  26. package/dist/functions/build.d.ts +15 -0
  27. package/dist/functions/build.js +27 -0
  28. package/dist/functions/contractPlugin.d.ts +7 -0
  29. package/dist/functions/contractPlugin.js +32 -0
  30. package/dist/functions/contracts/schema.d.ts +22 -0
  31. package/dist/functions/contracts/schema.js +99 -0
  32. package/dist/functions/contracts/validation.d.ts +3 -0
  33. package/dist/functions/contracts/validation.js +66 -0
  34. package/dist/functions/contracts.d.ts +20 -0
  35. package/dist/functions/contracts.js +119 -0
  36. package/dist/functions/deploy.d.ts +15 -0
  37. package/dist/functions/deploy.js +60 -0
  38. package/dist/functions/stack.d.ts +37 -0
  39. package/dist/functions/stack.js +148 -0
  40. package/dist/server/app.js +4 -4
  41. package/dist/server/static/components/app.css +8 -1
  42. package/dist/utils/blueprints/group-resources.d.ts +2 -1
  43. package/dist/utils/blueprints/group-resources.js +3 -3
  44. package/dist/utils/blueprints/resolve-deployed-resources.d.ts +2 -2
  45. package/dist/utils/blueprints/resolve-deployed-resources.js +4 -4
  46. package/dist/utils/bundles/prepare-asset.d.ts +3 -3
  47. package/dist/utils/bundles/prepare-asset.js +8 -7
  48. package/dist/utils/functions/child-process-wrapper.js +1 -1
  49. package/dist/utils/functions/child-runtime/durable.js +99 -22
  50. package/dist/utils/functions/child-runtime/invoke.d.ts +1 -0
  51. package/dist/utils/functions/child-runtime/invoke.js +26 -9
  52. package/dist/utils/functions/child-runtime/step-history.d.ts +15 -0
  53. package/dist/utils/functions/child-runtime/step-history.js +52 -0
  54. package/dist/utils/functions/extract-durable-config.js +1 -4
  55. package/dist/utils/functions/invoke/local.js +27 -11
  56. package/dist/utils/functions/invoke/nested.js +2 -1
  57. package/dist/utils/functions/invoke/resolve-invoke-type.d.ts +2 -2
  58. package/dist/utils/functions/invoke/resolve-invoke-type.js +2 -2
  59. package/dist/utils/functions/transpile/function.d.ts +7 -1
  60. package/dist/utils/functions/transpile/function.js +15 -9
  61. package/dist/utils/types.d.ts +24 -13
  62. package/dist/utils/types.js +1 -1
  63. package/oclif.manifest.json +1 -1
  64. package/package.json +24 -3
  65. package/dist/actions/blueprints/resolve.d.ts +0 -51
  66. package/dist/actions/blueprints/resolve.js +0 -52
@@ -1,5 +1,6 @@
1
1
  import {performance} from 'node:perf_hooks'
2
2
  import {inspect} from 'node:util'
3
+ import {createStepHistory} from './step-history.js'
3
4
 
4
5
  /**
5
6
  *
@@ -26,7 +27,9 @@ const formatArgs = (args) =>
26
27
  * @param io
27
28
  */
28
29
  export async function runDurable(handler, {context, event}, io) {
30
+ const history = createStepHistory()
29
31
  let currentStep = null
32
+
30
33
  const streamLog = (level, args) => {
31
34
  const date = new Date()
32
35
  const timestamp = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
@@ -41,7 +44,6 @@ export async function runDurable(handler, {context, event}, io) {
41
44
  })
42
45
  }
43
46
 
44
- // @todo: need to throw error for use of `console.log` in a durable function
45
47
  const logger = {
46
48
  log: (...a) => streamLog('info', a),
47
49
  info: (...a) => streamLog('info', a),
@@ -49,24 +51,57 @@ export async function runDurable(handler, {context, event}, io) {
49
51
  error: (...a) => streamLog('error', a),
50
52
  }
51
53
 
52
- const track = async (name, kind, produce) => {
53
- io.emit({name, kind, phase: 'start'})
54
- const startedAt = performance.now()
54
+ const emitFailure = (pending) => {
55
+ history.settle(pending)
56
+ io.emit(pending.event)
57
+ }
58
+
59
+ const settleUnretried = () => {
60
+ const pending = history.getPending()
61
+ if (pending !== null) {
62
+ emitFailure(pending)
63
+ }
64
+ }
65
+
66
+ const track = async (name, kind, produce, retry) => {
67
+ settleUnretried()
68
+ const {index, saved} = history.nextStep()
69
+
70
+ if (saved?.phase === 'complete') {
71
+ return saved.output
72
+ }
73
+
74
+ const maxAttempts = retry?.maxAttempts ?? 1
75
+ const retries = Math.max((retry?.maxAttempts ?? 1) - 1, 0)
76
+ const waitingOnRetry = typeof saved?.retryDelayMs === 'number'
77
+
78
+ if (saved?.phase === 'error' && !waitingOnRetry) {
79
+ throw saved.output
80
+ }
81
+
82
+ const attempt = (saved?.attempt ?? 0) + 1
55
83
  const prev = currentStep
56
84
  currentStep = name
57
85
 
86
+ io.emit({name, kind, phase: 'start'})
87
+ const startedAt = performance.now()
88
+
58
89
  try {
59
- const value = await produce()
60
- io.emit({
90
+ const value = await produce(attempt)
91
+ const completeEvent = {
61
92
  name,
62
93
  kind,
63
94
  phase: 'complete',
64
95
  output: value,
65
96
  durationMs: performance.now() - startedAt,
66
- })
97
+ }
98
+
99
+ history.recordSuccess(index, completeEvent)
100
+ io.emit(completeEvent)
101
+
67
102
  return value
68
103
  } catch (err) {
69
- io.emit({
104
+ const errorEvent = {
70
105
  name,
71
106
  kind,
72
107
  phase: 'error',
@@ -76,7 +111,21 @@ export async function runDurable(handler, {context, event}, io) {
76
111
  stack: err?.stack ?? '',
77
112
  },
78
113
  durationMs: performance.now() - startedAt,
114
+ attempt,
115
+ retries,
116
+ maxAttempts,
117
+ }
118
+
119
+ const retryPending = history.recordFailure(index, {
120
+ event: errorEvent,
121
+ error: err,
122
+ delayMs: retry?.delay ? durationToMs(retry.delay) : 0,
79
123
  })
124
+
125
+ if (!retryPending) {
126
+ io.emit(errorEvent)
127
+ }
128
+
80
129
  throw err
81
130
  } finally {
82
131
  currentStep = prev
@@ -84,12 +133,21 @@ export async function runDurable(handler, {context, event}, io) {
84
133
  }
85
134
 
86
135
  const step = {
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
- }),
136
+ run: ({name, handler: fn, retry}) =>
137
+ track(name || '', 'run', (attempt) => fn({logger, attempt}), retry),
138
+ delegate: ({name, handler, event, retry}) =>
139
+ track(
140
+ name || '',
141
+ 'delegate',
142
+ () => {
143
+ const fn = typeof handler === 'string' ? handler : handler?.name
144
+ if (!fn) {
145
+ throw new Error('No function name provided for delegation')
146
+ }
147
+ return io.invoke(fn, event?.data ?? {})
148
+ },
149
+ retry,
150
+ ),
93
151
  waitForCondition: ({name, initial, poller, next}) =>
94
152
  track(name || '', 'wait-for-condition', async () => {
95
153
  let state = initial
@@ -107,15 +165,34 @@ export async function runDurable(handler, {context, event}, io) {
107
165
  }
108
166
  return state
109
167
  }),
110
- delegate: ({name, handler, event}) =>
111
- track(name || '', 'delegate', () => {
112
- const fn = typeof handler === 'string' ? handler : handler?.name
113
- if (!fn) {
114
- throw new Error('No function name provided for delegation')
115
- }
116
- return io.invoke(fn, event)
168
+ wait: ({name, duration}) =>
169
+ track(name || '', 'wait', async () => {
170
+ await io.wait(durationToMs(duration))
171
+ return undefined
117
172
  }),
118
173
  }
119
174
 
120
- return handler({context, event, step, logger})
175
+ let result
176
+ let done = false
177
+ while (!done) {
178
+ history.startRun()
179
+ try {
180
+ result = await handler({context, event, step, logger})
181
+ settleUnretried()
182
+ done = true
183
+ } catch (err) {
184
+ const pending = history.getPending()
185
+ if (pending === null) {
186
+ throw err
187
+ }
188
+ if (err?.name === 'UnrecoverableError') {
189
+ emitFailure(pending)
190
+ throw err
191
+ }
192
+ io.emit({...pending.event, retryDelayMs: pending.delayMs})
193
+ await io.wait(pending.delayMs)
194
+ }
195
+ }
196
+
197
+ return result
121
198
  }
@@ -1,4 +1,5 @@
1
1
  export function createInvoker(): {
2
2
  invoke: (name: string, payload?: Record<string, unknown>) => Promise<unknown>
3
+ delegate: (name: string, payload?: Record<string, unknown>) => Promise<unknown>
3
4
  handleResponse: (msg: unknown) => void
4
5
  }
@@ -10,6 +10,17 @@ export function createInvoker() {
10
10
  const pending = new Map()
11
11
  let invokeSeq = 0
12
12
 
13
+ function sendInvokeRequest(name, payload = {}, metadata = {}) {
14
+ if (typeof name !== 'string' || name.length === 0) {
15
+ return Promise.reject(new Error('context.invoke requires a function name'))
16
+ }
17
+ const id = String(++invokeSeq)
18
+ return new Promise((resolve, reject) => {
19
+ pending.set(id, {resolve, reject})
20
+ process.send(JSON.stringify({type: 'invoke-request', id, name, payload, metadata}))
21
+ })
22
+ }
23
+
13
24
  /**
14
25
  * `context.invoke(name, payload)` — ask the parent to execute another blueprint
15
26
  * function and resolve with its return value. Sent over IPC and correlated by id.
@@ -18,14 +29,20 @@ export function createInvoker() {
18
29
  * @returns {Promise<unknown>}
19
30
  */
20
31
  function invoke(name, payload = {}) {
21
- if (typeof name !== 'string' || name.length === 0) {
22
- return Promise.reject(new Error('context.invoke requires a function name'))
23
- }
24
- const id = String(++invokeSeq)
25
- return new Promise((resolve, reject) => {
26
- pending.set(id, {resolve, reject})
27
- process.send(JSON.stringify({type: 'invoke-request', id, name, payload}))
28
- })
32
+ return sendInvokeRequest(name, payload)
33
+ }
34
+
35
+ /**
36
+ * durable `step.delegate(name, payload)` — ask the parent durable to execute another blueprint function
37
+ * and resolve with its return value. Sent over IPC and correlated by id. Adds an `origin` field to the message so the parent can
38
+ * distinguish between a normal invoke and a durable delegate for logging purposes.
39
+ * @param {string} name
40
+ * @param {Record<string, unknown>} [payload]
41
+ * @returns {Promise<unknown>}
42
+ */
43
+ function delegate(name, payload = {}) {
44
+ const metadata = {origin: 'step.delegate'}
45
+ return sendInvokeRequest(name, payload, metadata)
29
46
  }
30
47
 
31
48
  function handleResponse(msg) {
@@ -43,5 +60,5 @@ export function createInvoker() {
43
60
  }
44
61
  }
45
62
 
46
- return {invoke, handleResponse}
63
+ return {delegate, invoke, handleResponse}
47
64
  }
@@ -0,0 +1,15 @@
1
+ import type {DurableStepEvent} from './types'
2
+
3
+ type PendingRetry = {index: number; event: DurableStepEvent; delayMs: number}
4
+
5
+ export function createStepHistory(): {
6
+ startRun: () => void
7
+ nextStep: () => {index: number; saved: DurableStepEvent | undefined}
8
+ recordSuccess: (index: number, event: DurableStepEvent) => void
9
+ recordFailure: (
10
+ index: number,
11
+ failure: {event: DurableStepEvent; error: unknown; delayMs: number},
12
+ ) => boolean
13
+ getPending: () => PendingRetry | null
14
+ settle: (pending: PendingRetry) => void
15
+ }
@@ -0,0 +1,52 @@
1
+ const createStepHistory = () => {
2
+ const history = new Map([])
3
+ let cursor = 0
4
+ let pending = null
5
+
6
+ const startRun = () => {
7
+ cursor = 0
8
+ }
9
+
10
+ const nextStep = () => {
11
+ const index = cursor
12
+ cursor++
13
+ return {index, saved: history.get(index)}
14
+ }
15
+
16
+ const recordSuccess = (index, event) => {
17
+ history.set(index, event)
18
+ }
19
+
20
+ const recordFailure = (index, {event, error, delayMs}) => {
21
+ const retryable = event.attempt <= event.retries && error?.name !== 'UnrecoverableError'
22
+
23
+ if (retryable) {
24
+ history.set(index, {...event, output: error, retryDelayMs: delayMs})
25
+ pending = {index, event, delayMs}
26
+ } else {
27
+ history.set(index, {...event, output: error})
28
+ }
29
+ return retryable
30
+ }
31
+
32
+ const getPending = () => {
33
+ const item = pending
34
+ pending = null
35
+ return item
36
+ }
37
+
38
+ const settle = ({index, event}) => {
39
+ history.set(index, {...event, output: history.get(index).output})
40
+ }
41
+
42
+ return {
43
+ startRun,
44
+ nextStep,
45
+ recordSuccess,
46
+ recordFailure,
47
+ getPending,
48
+ settle,
49
+ }
50
+ }
51
+
52
+ export {createStepHistory}
@@ -5,7 +5,7 @@ import { SANITY_FUNCTION_DURABLE } from '../../constants.js';
5
5
  import { isLocalFunctionResource } from '../types.js';
6
6
  import { findFunctionEntryPoint } from './find-entry-point.js';
7
7
  import { transpileFunction } from './transpile/function.js';
8
- const DURABLE_CONFIG_KEYS = ['event', 'concurrency', 'debounce', 'debounceKey'];
8
+ const DURABLE_CONFIG_KEYS = ['event', 'concurrency', 'debounce'];
9
9
  /**
10
10
  * Extracts the blueprint config from the function source code and mutates it to the resource object.
11
11
  * This is used for durable functions that may not have a config defined in the blueprint.
@@ -47,9 +47,6 @@ export const extractDurableConfig = async (resource, blueprintFileDir) => {
47
47
  if (resource.debounce === undefined && config?.debounce !== undefined) {
48
48
  resource.debounce = config.debounce;
49
49
  }
50
- if (resource.debounceKey === undefined && config?.debounceKey !== undefined) {
51
- resource.debounceKey = config.debounceKey;
52
- }
53
50
  // Validate the resource after merging the config values
54
51
  const errors = validateDurableFunction(resource);
55
52
  if (errors.length > 0) {
@@ -64,10 +64,11 @@ export default async function invoke(resource, payload, context, options) {
64
64
  // Apply the GROQ rule before preparing a build so a filtered-out event skips
65
65
  // the (potentially expensive) transpile entirely.
66
66
  if (isGroqInvocation(resource)) {
67
- if (!isGroqContextOptions(context)) {
67
+ if (!('clientOptions' in context)) {
68
68
  throw new Error('GROQ-based functions require a context with clientOptions');
69
69
  }
70
- const { projectId, dataset } = context.clientOptions;
70
+ const projectId = 'projectId' in context.clientOptions ? context.clientOptions.projectId : undefined;
71
+ const dataset = 'dataset' in context.clientOptions ? context.clientOptions.dataset : undefined;
71
72
  if ('before' in payload) {
72
73
  filteredData = await applyGroqRule(resource, payload, projectId, dataset);
73
74
  const filter = resource.event && 'filter' in resource.event ? resource.event.filter : '';
@@ -156,14 +157,14 @@ export default async function invoke(resource, payload, context, options) {
156
157
  // Resolve a nested `context.invoke(name, payload)` by re-entering the invoke
157
158
  // pipeline via the supplied callback, then hand the result back to the child.
158
159
  async function handleNestedInvoke(msg) {
159
- const { id, name, payload: nestedPayload } = msg;
160
+ const { id, name, payload: nestedPayload, metadata } = msg;
160
161
  pauseTimer();
161
162
  const nestedStart = performance.now();
162
163
  try {
163
164
  if (!onNestedInvoke) {
164
165
  throw new Error('Nested context.invoke is not supported in this context');
165
166
  }
166
- const nested = await onNestedInvoke(name, nestedPayload ?? {}, context, depth + 1, callStack);
167
+ const nested = await onNestedInvoke(name, nestedPayload ?? {}, context, depth + 1, callStack, metadata);
167
168
  nestedTimings[`invoke:${name}`] =
168
169
  (nestedTimings[`invoke:${name}`] ?? 0) + (performance.now() - nestedStart);
169
170
  // Fire-and-forget contract: in production `context.invoke` returns once the
@@ -173,12 +174,22 @@ export default async function invoke(resource, payload, context, options) {
173
174
  // of `nested.error`. The error isn't lost: it travels back in the returned
174
175
  // result and is logged/streamed to the UI by the caller (app/test core).
175
176
  if (child && !child.killed) {
176
- const response = {
177
- type: 'invoke-response',
178
- id,
179
- ok: true,
180
- result: { json: nested.json },
181
- };
177
+ // step.delegate errors need to be surfaced to the caller for retry behavior and for the durable step to be marked as failed.
178
+ const origin = metadata?.origin || undefined;
179
+ const error = nested.error || undefined;
180
+ const response = origin === 'step.delegate' && error
181
+ ? {
182
+ type: 'invoke-response',
183
+ id,
184
+ ok: false,
185
+ error,
186
+ }
187
+ : {
188
+ type: 'invoke-response',
189
+ id,
190
+ ok: true,
191
+ result: { json: nested.json },
192
+ };
182
193
  child.send(JSON.stringify(response));
183
194
  }
184
195
  }
@@ -231,7 +242,12 @@ export default async function invoke(resource, payload, context, options) {
231
242
  }
232
243
  // Nested invoke request — handle without tearing down the child.
233
244
  if (msg?.type === 'invoke-request') {
234
- handleNestedInvoke({ id: msg.id, name: msg.name, payload: msg.payload });
245
+ handleNestedInvoke({
246
+ id: msg.id,
247
+ name: msg.name,
248
+ payload: msg.payload,
249
+ metadata: msg.metadata,
250
+ });
235
251
  return;
236
252
  }
237
253
  // Terminal result message.
@@ -28,7 +28,7 @@ export function assertNestedInvokeAllowed(name, depth, callStack) {
28
28
  * stack extended by this function, and normalize timings.
29
29
  */
30
30
  export async function runFunction(resource, payload, context, options) {
31
- const { forceColor, timeout, depth = 0, callStack = [], onNestedInvoke } = options;
31
+ const { forceColor, timeout, depth = 0, callStack = [], onNestedInvoke, onStepEvent } = options;
32
32
  assertNestedInvokeAllowed(resource.name, depth, callStack);
33
33
  const response = await invoke(resource, payload, context, {
34
34
  forceColor,
@@ -36,6 +36,7 @@ export async function runFunction(resource, payload, context, options) {
36
36
  onNestedInvoke,
37
37
  depth,
38
38
  callStack: [...callStack, resource.name],
39
+ onStepEvent,
39
40
  });
40
41
  return { ...response, timings: response.timings ?? {} };
41
42
  }
@@ -1,6 +1,6 @@
1
1
  import type { BlueprintResource } from '@sanity/blueprints';
2
2
  import type { Logger } from '../../logger.js';
3
- import type { EventType, FunctionGroqResource, FunctionResource, FunctionTestFlags, InvokeContextOptions, InvokePayloadOptions } from '../../types.js';
3
+ import type { EventType, FunctionGroqResource, FunctionResource, FunctionTestFlags, InvokeContextOptions, InvokePayloadOptions, ResolvedResource } from '../../types.js';
4
4
  import buildPayload from '../build-payload.js';
5
5
  import { fetchAsset, fetchDocument } from '../fetch-document.js';
6
6
  export type InvocationPlan = {
@@ -23,7 +23,7 @@ export type InputContext = {
23
23
  apiHost: string;
24
24
  token?: string;
25
25
  withUserToken?: boolean;
26
- resources: BlueprintResource[];
26
+ resolvedResources: ResolvedResource<BlueprintResource>[];
27
27
  blueprintFileDir: string;
28
28
  log: Logger;
29
29
  };
@@ -51,7 +51,7 @@ export const buildContext = (plan, input) => {
51
51
  if (plan.kind === 'plain') {
52
52
  return {
53
53
  clientOptions: { ...withToken },
54
- resources: input.resources,
54
+ resources: input.resolvedResources,
55
55
  blueprintFileDir: input.blueprintFileDir,
56
56
  };
57
57
  }
@@ -69,7 +69,7 @@ export const buildContext = (plan, input) => {
69
69
  '',
70
70
  functionResourceType: 'project',
71
71
  functionResourceId: input.projectId || '',
72
- resources: input.resources,
72
+ resources: input.resolvedResources,
73
73
  blueprintFileDir: input.blueprintFileDir,
74
74
  };
75
75
  };
@@ -1,10 +1,16 @@
1
+ import type { Plugin } from 'vite';
1
2
  import type { FunctionResource, InstallerType } from '../../types.js';
2
3
  export declare const resolveViteRoot: (entry: string) => string;
3
- export declare function transpileFunction({ resource, functionPath, entryPoint, installer, }: {
4
+ export declare function transpileFunction({ resource, functionPath, entryPoint, installer, workDir, outDir, outputFileName, bundle: bundleDependencies, plugins: extraPlugins, }: {
4
5
  resource: FunctionResource;
5
6
  functionPath: string;
6
7
  entryPoint: string;
7
8
  installer?: InstallerType;
9
+ workDir?: string;
10
+ outDir?: string;
11
+ outputFileName?: string;
12
+ bundle?: boolean;
13
+ plugins?: Plugin[];
8
14
  }): Promise<{
9
15
  type: string;
10
16
  outputDir: string;
@@ -13,7 +13,7 @@ export const resolveViteRoot = (entry) => {
13
13
  const nodeModulesDir = find.dir('node_modules', { cwd: entry });
14
14
  return nodeModulesDir ? path.dirname(nodeModulesDir) : entry;
15
15
  };
16
- export async function transpileFunction({ resource, functionPath, entryPoint, installer, }) {
16
+ export async function transpileFunction({ resource, functionPath, entryPoint, installer, workDir = cwd(), outDir, outputFileName = getOutputFilename(entryPoint), bundle: bundleDependencies, plugins: extraPlugins = [], }) {
17
17
  if (!resource.src)
18
18
  throw new Error('Resource src is required');
19
19
  if (!resource.name)
@@ -23,8 +23,8 @@ export async function transpileFunction({ resource, functionPath, entryPoint, in
23
23
  const stats = await stat(functionPath);
24
24
  const entryDir = stats.isFile() ? path.dirname(functionPath) : functionPath;
25
25
  const outputPathStart = performance.now();
26
- const outputDir = await getTranspileOutputPath(entryDir, resource.name);
27
- const outputFile = path.join(outputDir, getOutputFilename(entryPoint));
26
+ const outputDir = await getTranspileOutputPath(entryDir, resource.name, outDir);
27
+ const outputFile = path.join(outputDir, outputFileName);
28
28
  const fnRootDir = resolveViteRoot(entryDir);
29
29
  timings['transpile:setupOutput'] = performance.now() - outputPathStart;
30
30
  async function cleanupTmpDir() {
@@ -34,7 +34,8 @@ export async function transpileFunction({ resource, functionPath, entryPoint, in
34
34
  try {
35
35
  const viteStart = performance.now();
36
36
  // Always bundle the code if pnpm usage is detected or specified by the user
37
- const bundle = !!find.file('pnpm-workspace.yaml') || installer === 'pnpm';
37
+ const bundle = bundleDependencies ??
38
+ (!!find.file('pnpm-workspace.yaml', { cwd: workDir }) || installer === 'pnpm');
38
39
  const result = await viteBuild({
39
40
  root: fnRootDir,
40
41
  logLevel: 'silent',
@@ -49,7 +50,7 @@ export async function transpileFunction({ resource, functionPath, entryPoint, in
49
50
  input: entryPoint,
50
51
  output: {
51
52
  format: 'esm',
52
- entryFileNames: getOutputFilename(entryPoint),
53
+ entryFileNames: outputFileName,
53
54
  ...(bundle
54
55
  ? {}
55
56
  : {
@@ -71,7 +72,10 @@ export async function transpileFunction({ resource, functionPath, entryPoint, in
71
72
  conditions: ['node'],
72
73
  },
73
74
  },
74
- plugins: bundle ? [detectNativeModulesPlugin(), pnpmResolvePlugin(cwd())] : [],
75
+ plugins: [
76
+ ...(bundle ? [detectNativeModulesPlugin(), pnpmResolvePlugin(workDir)] : []),
77
+ ...extraPlugins,
78
+ ],
75
79
  });
76
80
  timings['transpile:build'] = performance.now() - viteStart;
77
81
  const verifyStart = performance.now();
@@ -81,7 +85,7 @@ export async function transpileFunction({ resource, functionPath, entryPoint, in
81
85
  await writeTranspiledPackageJson(entryDir, outputFile);
82
86
  timings['transpile:writePackage'] = performance.now() - pkgStart;
83
87
  const cleanupStart = performance.now();
84
- await cleanupSourceMaps(functionPath, outputDir);
88
+ await cleanupSourceMaps(entryDir, outputDir);
85
89
  timings['transpile:cleanupMaps'] = performance.now() - cleanupStart;
86
90
  timings.bundle = performance.now() - transpileStart;
87
91
  return {
@@ -148,8 +152,10 @@ async function writeTranspiledPackageJson(inputDir, outputFilePath) {
148
152
  const pkgJsonOutputPath = path.join(path.dirname(outputFilePath), 'package.json');
149
153
  await writeFile(pkgJsonOutputPath, JSON.stringify(bundled, null, 2));
150
154
  }
151
- async function getTranspileOutputPath(entryDir, fnName) {
152
- const tmpPath = path.resolve(entryDir, '.build', `function-${fnName}`);
155
+ async function getTranspileOutputPath(entryDir, fnName, outDir) {
156
+ const tmpPath = outDir
157
+ ? path.resolve(outDir)
158
+ : path.resolve(entryDir, '.build', `function-${fnName}`);
153
159
  await rm(tmpPath, { recursive: true, force: true }).catch(logCleanupFailure);
154
160
  await mkdir(tmpPath, { recursive: true });
155
161
  return tmpPath;
@@ -40,6 +40,11 @@ export interface BlueprintResourceRecord extends BlueprintResource {
40
40
  export interface DeployedResource extends BlueprintResourceRecord {
41
41
  externalId: string;
42
42
  }
43
+ /** Wrap a resource and its externalId if it has one. */
44
+ export interface ResolvedResource<R extends BlueprintResource> {
45
+ resource: R;
46
+ externalId?: string;
47
+ }
43
48
  export declare function isLocalFunctionResource(r: BlueprintResource): r is FunctionResource;
44
49
  export declare function hasEventSource(r: FunctionResource): r is EventFunctionResource;
45
50
  export declare function isScheduleEvent(e: unknown): e is BlueprintScheduledFunctionResource['event'];
@@ -134,8 +139,14 @@ export type InvokePayloadMetadata = Pick<InvokeGroqPayloadOptions, 'event' | 'be
134
139
  export type EventType = 'create' | 'update' | 'delete';
135
140
  export type NonGroqEventType = 'plain' | 'nested' | 'durable';
136
141
  export declare function isEventType(arg: string): arg is EventType;
142
+ interface BaseContextOptions {
143
+ /** All resources declared by the blueprint, used to populate `context.resources` at invoke time. */
144
+ resources?: ResolvedResource<BlueprintResource>[];
145
+ /** The directory where the blueprint manifest is located. */
146
+ blueprintFileDir: string;
147
+ }
137
148
  /** @internal */
138
- export interface InvokeGroqContextOptions {
149
+ export interface InvokeGroqContextOptions extends BaseContextOptions {
139
150
  clientOptions: {
140
151
  apiVersion?: string;
141
152
  dataset?: string;
@@ -151,20 +162,12 @@ export interface InvokeGroqContextOptions {
151
162
  functionResourceType: string;
152
163
  /** The resource ID of the function container; resource ID that houses the function. */
153
164
  functionResourceId: string;
154
- /** All resources declared by the blueprint, used to populate `context.resources` at invoke time. */
155
- resources?: BlueprintResource[];
156
- /** The directory where the blueprint manifest is located. */
157
- blueprintFileDir: string;
158
165
  }
159
166
  /** @internal */
160
- export interface InvokeScheduleContextOptions {
167
+ export interface InvokeScheduleContextOptions extends BaseContextOptions {
161
168
  clientOptions: {
162
169
  token?: string;
163
170
  };
164
- /** All resources declared by the blueprint, used to populate `context.resources` at invoke time. */
165
- resources?: BlueprintResource[];
166
- /** The directory where the blueprint manifest is located. */
167
- blueprintFileDir: string;
168
171
  }
169
172
  export type InvokeContextOptions = InvokeGroqContextOptions | InvokeScheduleContextOptions;
170
173
  export declare function isGroqContextOptions(context: InvokeContextOptions): context is InvokeGroqContextOptions;
@@ -187,12 +190,15 @@ export interface InvokeExecutionOptions {
187
190
  /** Don't wait the full duration during a wait durable event @internal */
188
191
  noWait?: boolean;
189
192
  }
193
+ export type NestedInvokeMetadata = {
194
+ origin?: 'step.delegate';
195
+ };
190
196
  /**
191
197
  * Re-entrant callback that executes a named function from the blueprint on behalf
192
198
  * of a nested `context.invoke(name, payload)` and returns its invocation result.
193
199
  * @internal
194
200
  */
195
- export type NestedInvoke = (name: string, payload: Record<string, unknown>, context: InvokeContextOptions, depth: number, callStack: string[]) => Promise<InvocationResponse & {
201
+ export type NestedInvoke = (name: string, payload: Record<string, unknown>, context: InvokeContextOptions, depth: number, callStack: string[], metadata?: NestedInvokeMetadata) => Promise<InvocationResponse & {
196
202
  timings: Record<string, number>;
197
203
  }>;
198
204
  /**
@@ -233,6 +239,7 @@ export type ChildToParentMessage = {
233
239
  name: string;
234
240
  payload: Record<string, unknown>;
235
241
  isDurable?: boolean;
242
+ metadata?: NestedInvokeMetadata;
236
243
  } | {
237
244
  type: 'result';
238
245
  json?: JsonInvocationResponse;
@@ -334,7 +341,7 @@ export type AssetPrepResult = {
334
341
  error: string;
335
342
  };
336
343
  export type PrepAssetFn<R extends BlueprintResource> = (options: {
337
- resource: R;
344
+ resolvedResource: ResolvedResource<R>;
338
345
  blueprintFileDir: string;
339
346
  installer?: InstallerType;
340
347
  }) => Promise<AssetPrepResult>;
@@ -351,7 +358,7 @@ export type PreDeployConfig = {
351
358
  prepareResource?: PrepResourceFn<BlueprintResource>;
352
359
  };
353
360
  export type AssetResource<R extends BlueprintResource> = {
354
- resource: R;
361
+ resource: ResolvedResource<R>;
355
362
  prepareAsset: PrepAssetFn<R>;
356
363
  };
357
364
  export type Warning = {
@@ -372,6 +379,10 @@ export interface DurableStepEvent {
372
379
  output?: unknown;
373
380
  error?: InvocationError;
374
381
  durationMs?: number;
382
+ retryDelayMs?: number;
383
+ attempt?: number;
384
+ retries?: number;
385
+ maxAttempts?: number;
375
386
  }
376
387
  export interface DurableLogEvent {
377
388
  kind: 'log';
@@ -38,7 +38,7 @@ export function isEventType(arg) {
38
38
  return ['create', 'update', 'delete'].includes(arg);
39
39
  }
40
40
  export function isGroqContextOptions(context) {
41
- return 'clientOptions' in context;
41
+ return 'eventResourceType' in context;
42
42
  }
43
43
  /** @internal */
44
44
  export var BlueprintParserErrorType;
@@ -3148,5 +3148,5 @@
3148
3148
  ]
3149
3149
  }
3150
3150
  },
3151
- "version": "17.15.0"
3151
+ "version": "17.16.0-app-functions.gab0267c"
3152
3152
  }