@frontera-sdk/workflow 1.51.0 → 1.51.1

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
@@ -71,6 +71,24 @@ await workflows.run('supplier-onboarding', {
71
71
  })
72
72
  ```
73
73
 
74
+ An input of type `files` takes several files. Put a list of `File` values under
75
+ that input and each file goes as its own part under the input's name, in list
76
+ order.
77
+
78
+ ```ts
79
+ await workflows.run('delivery-intake', {
80
+ input: {
81
+ carrier: 'globex-logistics',
82
+ scans: [new File([page1], 'page-1.pdf'), new File([page2], 'page-2.pdf')],
83
+ },
84
+ })
85
+ ```
86
+
87
+ The list must hold only `File` values. A list that mixes `File` values with
88
+ references to files that are already uploaded is not sent as file parts, and
89
+ the service refuses it. An input takes 20 files unless the workflow sets another limit, and
90
+ 50 at most.
91
+
74
92
  ## Author a workflow
75
93
 
76
94
  A definition is a plain object typed as `WorkflowDefinition`, so the step kinds,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontera-sdk/workflow",
3
- "version": "1.51.0",
3
+ "version": "1.51.1",
4
4
  "description": "Typed headless client for starting, inspecting, waiting on and cancelling Frontera workflow runs from a script or backend.",
5
5
  "keywords": [
6
6
  "frontera",
@@ -38,8 +38,8 @@
38
38
  "typecheck": "bunx tsc --noEmit"
39
39
  },
40
40
  "dependencies": {
41
- "@frontera-sdk/core": "1.51.0",
42
- "@frontera-sdk/functions": "1.51.0"
41
+ "@frontera-sdk/core": "1.51.1",
42
+ "@frontera-sdk/functions": "1.51.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/bun": "^1.3.14",
package/src/definition.ts CHANGED
@@ -140,12 +140,39 @@ export type AgentTrigger = {
140
140
  description?: string
141
141
  }
142
142
 
143
+ /** Started by something that happened in a connected Plugin account, such as a new email. */
144
+ export type PluginTrigger = {
145
+ kind: 'plugin'
146
+ description?: string
147
+ /** The Plugin install, the same reference a `plugin` node uses. */
148
+ resourceId: string
149
+ /** Event name the install's Plugin kind offers, e.g. `new_email`. */
150
+ event: string
151
+ /** Whose connection the trigger reads: the shared workspace account, or one named person's own account. */
152
+ account: { kind: 'workspace' } | { kind: 'user'; userId: string }
153
+ /** Provider-side selection, validated by the event's own schema. Gmail `new_email`: `{ labelId?: string }`. */
154
+ config?: Record<string, unknown>
155
+ /** JSONata over `$event`. A run starts only when the result is truthy. Absent: every event. */
156
+ filter?: string
157
+ /** Skip automatic replies and delivery failures. Default true. */
158
+ ignoreAutoReplies?: boolean
159
+ /** JSONata over `$event`, giving the run input object. Absent: the keys of `$event.data` that are declared inputs. */
160
+ input?: string
161
+ /** JSONata over `$event`, giving the subject pk. Required iff the definition has a subject. */
162
+ subject?: string
163
+ /** The declared list-of-files input that receives the event's attachments. Absent: attachments are not downloaded. */
164
+ attachmentsInput?: string
165
+ /** Most runs this trigger may start in any 24 hours, 1 to 5000. Events over it wait. Default 200. */
166
+ maxRunsPerDay?: number
167
+ }
168
+
143
169
  export type WorkflowTrigger =
144
170
  | ManualTrigger
145
171
  | CronTrigger
146
172
  | WorkflowCallTrigger
147
173
  | AgentTrigger
148
174
  | WebhookTrigger
175
+ | PluginTrigger
149
176
 
150
177
  // ─── Shared node settings ────────────────────────────────────────────────────
151
178
 
package/src/types.ts CHANGED
@@ -211,7 +211,8 @@ export interface VersionLineage {
211
211
  export interface RunRequest {
212
212
  /**
213
213
  * A `File` value here is sent as its own multipart part, named after the
214
- * `file` input it fills; everything else travels as JSON.
214
+ * `file` input it fills. A list of `File`s fills a `files` input: one part
215
+ * per file, all under that input's name. Everything else travels as JSON.
215
216
  */
216
217
  input?: Record<string, unknown>
217
218
  subject?: { pk: string }
@@ -307,24 +307,31 @@ export class WorkflowClient {
307
307
  }
308
308
  }
309
309
 
310
+ /** The `File`s one input value carries: itself, or a non-empty list of nothing but `File`s. */
311
+ function filesOf(value: unknown): File[] {
312
+ if (value instanceof File) return [value]
313
+ return Array.isArray(value) && value.length > 0 && value.every((item) => item instanceof File) ? value : []
314
+ }
315
+
310
316
  /**
311
- * The run body, JSON unless a `file` input carries an actual `File`.
317
+ * The run body, JSON unless a `file` or `files` input carries an actual `File`.
312
318
  *
313
- * A `file` input cannot travel as JSON, so one turns the whole request
314
- * multipart: the non-file fields go in an `input` part as JSON and each file
315
- * gets a part named after its field, which is the shape the service's
316
- * `readMultipartRun` expects. `subject` and `version` are serialised because a
317
- * multipart part is text — the route parses both back. The content type is
318
- * left alone deliberately: only `FormData` can generate the boundary, and
319
- * naming the type here would lose it.
319
+ * A `File` cannot travel as JSON, so one turns the whole request multipart:
320
+ * the non-file fields go in an `input` part as JSON and each file gets a part
321
+ * named after its field, which is the shape the service's `readMultipartRun`
322
+ * expects. A list of `File`s under one key, for a `files` input, is sent as
323
+ * that part name repeated, in list order. `subject` and `version` are
324
+ * serialised because a multipart part is text — the route parses both back.
325
+ * The content type is left alone deliberately: only `FormData` can generate
326
+ * the boundary, and naming the type here would lose it.
320
327
  */
321
328
  function runBody(request: RunRequest): RunRequest | FormData {
322
329
  const input = request.input ?? {}
323
- const files = Object.entries(input).filter((entry): entry is [string, File] => entry[1] instanceof File)
330
+ const files = Object.entries(input).flatMap(([field, value]) => filesOf(value).map((file) => [field, file] as const))
324
331
  if (files.length === 0) return request
325
332
 
326
333
  const form = new FormData()
327
- const rest = Object.fromEntries(Object.entries(input).filter(([, value]) => !(value instanceof File)))
334
+ const rest = Object.fromEntries(Object.entries(input).filter(([, value]) => filesOf(value).length === 0))
328
335
  form.append('input', JSON.stringify(rest))
329
336
  for (const [field, file] of files) form.append(field, file)
330
337
  if (request.subject) form.append('subject', JSON.stringify(request.subject))