@formfeed/sdk 0.3.11 → 0.3.13

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
@@ -18,11 +18,19 @@ npm install @formfeed/sdk
18
18
  ```ts
19
19
  import { Formfeed } from '@formfeed/sdk';
20
20
 
21
- const client = new Formfeed({ apiKey: process.env.FORMFEED_API_KEY!, region: 'eu' });
21
+ const client = new Formfeed({
22
+ apiKey: process.env.FORMFEED_API_KEY!,
23
+ region: 'eu',
24
+ });
22
25
 
23
26
  const render = await client.renders.create({
24
27
  template: 'invoice-de',
25
- data: { invoice: { number: '2026-0042', lines: [{ description: 'Consulting', qty: 8, price: 120 }] } },
28
+ data: {
29
+ invoice: {
30
+ number: '2026-0042',
31
+ lines: [{ description: 'Consulting', qty: 8, price: 120 }],
32
+ },
33
+ },
26
34
  });
27
35
  console.log(render.download_url, render.page_count);
28
36
 
@@ -32,7 +40,11 @@ const pdf = await client.renders.download(render); // the bytes
32
40
  ## Async renders and batches
33
41
 
34
42
  ```ts
35
- const queued = await client.renders.create({ template: 'invoice-de', data, mode: 'async' });
43
+ const queued = await client.renders.create({
44
+ template: 'invoice-de',
45
+ data,
46
+ mode: 'async',
47
+ });
36
48
  const done = await client.renders.waitFor(queued.id);
37
49
 
38
50
  const job = await client.renders.batch({
@@ -58,14 +70,23 @@ await client.templates.create({
58
70
  publish: true,
59
71
  });
60
72
 
61
- const filled = await client.renders.create({ template: 'offer', output: 'docx', data }); // or output: 'pdf'
73
+ const filled = await client.renders.create({
74
+ template: 'offer',
75
+ output: 'docx',
76
+ data,
77
+ }); // or output: 'pdf'
62
78
 
63
79
  // a new version with a new document; without `file` the latest document is kept
64
- await client.templates.versions.create('offer', { file: { data: await readFile('offer-v2.docx'), name: 'offer.docx' } });
80
+ await client.templates.versions.create('offer', {
81
+ file: { data: await readFile('offer-v2.docx'), name: 'offer.docx' },
82
+ });
65
83
  const docx = await client.templates.versions.file('offer', 'latest'); // the document's bytes
66
84
 
67
85
  // any office document to PDF (Word, Excel, PowerPoint, OpenDocument, RTF)
68
- const pdf = await client.pdf.convert({ file: { data: await readFile('report.xlsx'), name: 'report.xlsx' } }, { single_page_sheets: true });
86
+ const pdf = await client.pdf.convert(
87
+ { file: { data: await readFile('report.xlsx'), name: 'report.xlsx' } },
88
+ { single_page_sheets: true },
89
+ );
69
90
  ```
70
91
 
71
92
  ## Webhooks
@@ -74,7 +95,11 @@ const pdf = await client.pdf.convert({ file: { data: await readFile('report.xlsx
74
95
  import { parseWebhookEvent } from '@formfeed/sdk';
75
96
 
76
97
  // pass the raw request body, not re-serialised JSON; throws when the signature does not match
77
- const event = await parseWebhookEvent(secret, request.headers.get('webhook-signature'), rawBody);
98
+ const event = await parseWebhookEvent(
99
+ secret,
100
+ request.headers.get('webhook-signature'),
101
+ rawBody,
102
+ );
78
103
  if (event.type === 'render.completed') console.log(event.data); // the render object
79
104
  ```
80
105
 
package/index.cjs CHANGED
@@ -40,7 +40,10 @@ function multipart(fields, file) {
40
40
  form.set("file", blob, name);
41
41
  for (const [key, value] of Object.entries(fields)) {
42
42
  if (value === void 0) continue;
43
- form.set(key, typeof value === "string" ? value : typeof value === "object" ? JSON.stringify(value) : String(value));
43
+ form.set(
44
+ key,
45
+ typeof value === "string" ? value : typeof value === "object" ? JSON.stringify(value) : String(value)
46
+ );
44
47
  }
45
48
  return form;
46
49
  }
@@ -70,7 +73,10 @@ function randomKey() {
70
73
  if (c?.randomUUID) return c.randomUUID();
71
74
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
72
75
  }
73
- var withKey = (options) => ({ ...options, idempotencyKey: options.idempotencyKey ?? randomKey() });
76
+ var withKey = (options) => ({
77
+ ...options,
78
+ idempotencyKey: options.idempotencyKey ?? randomKey()
79
+ });
74
80
  var idOf = (source) => typeof source === "string" ? source : source.id;
75
81
  var Formfeed = class {
76
82
  baseUrl;
@@ -80,14 +86,28 @@ var Formfeed = class {
80
86
  timeoutMs;
81
87
  extraHeaders;
82
88
  constructor(options) {
83
- if (!options.apiKey) throw new FormfeedError("invalid_request", "apiKey is required", 0);
89
+ if (!options.apiKey)
90
+ throw new FormfeedError("invalid_request", "apiKey is required", 0);
84
91
  this.apiKey = options.apiKey;
85
- this.baseUrl = (options.baseUrl ?? hosts[options.region ?? "eu"]).replace(/\/$/, "");
92
+ this.baseUrl = (options.baseUrl ?? hosts[options.region ?? "eu"]).replace(
93
+ /\/$/,
94
+ ""
95
+ );
86
96
  this.fetchImpl = options.fetch ?? globalThis.fetch;
87
97
  this.maxRetries = options.maxRetries ?? 3;
88
98
  this.timeoutMs = options.timeoutMs ?? 12e4;
89
- this.extraHeaders = Object.fromEntries(Object.entries(options.headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]));
90
- if (!this.fetchImpl) throw new FormfeedError("invalid_request", "fetch is not available; pass options.fetch", 0);
99
+ this.extraHeaders = Object.fromEntries(
100
+ Object.entries(options.headers ?? {}).map(([name, value]) => [
101
+ name.toLowerCase(),
102
+ value
103
+ ])
104
+ );
105
+ if (!this.fetchImpl)
106
+ throw new FormfeedError(
107
+ "invalid_request",
108
+ "fetch is not available; pass options.fetch",
109
+ 0
110
+ );
91
111
  }
92
112
  renders = {
93
113
  /** Renders a template, HTML or URL. Sync by default; `mode: 'async'` returns a queued render. */
@@ -95,16 +115,26 @@ var Formfeed = class {
95
115
  ...options,
96
116
  idempotencyKey: options.idempotencyKey ?? randomKey()
97
117
  }),
98
- get: (id, options = {}) => this.request("GET", `/renders/${encodeURIComponent(id)}`, void 0, options),
118
+ get: (id, options = {}) => this.request(
119
+ "GET",
120
+ `/renders/${encodeURIComponent(id)}`,
121
+ void 0,
122
+ options
123
+ ),
99
124
  /** Polls until the render succeeded or failed (async renders). */
100
125
  waitFor: async (id, options = {}) => {
101
126
  const deadline = Date.now() + (options.timeoutMs ?? 12e4);
102
127
  const interval = options.intervalMs ?? 1e3;
103
128
  for (; ; ) {
104
129
  const render = await this.renders.get(id, { signal: options.signal });
105
- if (render.status === "succeeded" || render.status === "failed") return render;
130
+ if (render.status === "succeeded" || render.status === "failed")
131
+ return render;
106
132
  if (Date.now() + interval > deadline)
107
- throw new FormfeedError("timeout", `render ${id} did not finish within the wait time`, 0);
133
+ throw new FormfeedError(
134
+ "timeout",
135
+ `render ${id} did not finish within the wait time`,
136
+ 0
137
+ );
108
138
  await sleep(interval, options.signal);
109
139
  }
110
140
  },
@@ -112,17 +142,34 @@ var Formfeed = class {
112
142
  download: async (render, options = {}) => {
113
143
  const target = typeof render === "string" ? await this.renders.get(render, options) : render;
114
144
  if (!target.download_url)
115
- throw new FormfeedError("not_ready", `render ${target.id} has no output (${target.status})`, 0);
116
- const res = await this.fetchImpl(target.download_url, { signal: options.signal });
117
- if (!res.ok) throw new FormfeedError("download_failed", `download answered HTTP ${res.status}`, res.status);
145
+ throw new FormfeedError(
146
+ "not_ready",
147
+ `render ${target.id} has no output (${target.status})`,
148
+ 0
149
+ );
150
+ const res = await this.fetchImpl(target.download_url, {
151
+ signal: options.signal
152
+ });
153
+ if (!res.ok)
154
+ throw new FormfeedError(
155
+ "download_failed",
156
+ `download answered HTTP ${res.status}`,
157
+ res.status
158
+ );
118
159
  return new Uint8Array(await res.arrayBuffer());
119
160
  },
120
161
  /** Page of renders, newest first. */
121
162
  list: (query = {}, options = {}) => {
122
163
  const params = new URLSearchParams();
123
- for (const [k, v] of Object.entries(query)) if (v !== void 0 && v !== null && v !== "") params.set(k, String(v));
164
+ for (const [k, v] of Object.entries(query))
165
+ if (v !== void 0 && v !== null && v !== "") params.set(k, String(v));
124
166
  const qs = params.toString();
125
- return this.request("GET", `/renders${qs ? `?${qs}` : ""}`, void 0, options);
167
+ return this.request(
168
+ "GET",
169
+ `/renders${qs ? `?${qs}` : ""}`,
170
+ void 0,
171
+ options
172
+ );
126
173
  },
127
174
  /** Every render matching the query, following the cursor. */
128
175
  all: async (query = {}, options = {}) => {
@@ -136,12 +183,22 @@ var Formfeed = class {
136
183
  return out;
137
184
  },
138
185
  /** Removes the stored files of a render before they expire. Needs the `file:delete` scope. */
139
- deleteOutputs: (id, options = {}) => this.request("DELETE", `/renders/${encodeURIComponent(id)}/outputs`, void 0, options),
186
+ deleteOutputs: (id, options = {}) => this.request(
187
+ "DELETE",
188
+ `/renders/${encodeURIComponent(id)}/outputs`,
189
+ void 0,
190
+ options
191
+ ),
140
192
  /**
141
193
  * The stored request of a render, to render it again. Needs `render:input`; throws
142
194
  * `render_input_expired` (410) when the workspace keeps no requests or the period ended.
143
195
  */
144
- input: (id, options = {}) => this.request("GET", `/renders/${encodeURIComponent(id)}/input`, void 0, options),
196
+ input: (id, options = {}) => this.request(
197
+ "GET",
198
+ `/renders/${encodeURIComponent(id)}/input`,
199
+ void 0,
200
+ options
201
+ ),
145
202
  batch: (request, options = {}) => this.request("POST", "/renders/batch", request, {
146
203
  ...options,
147
204
  idempotencyKey: options.idempotencyKey ?? randomKey()
@@ -153,10 +210,30 @@ var Formfeed = class {
153
210
  * 0.5 units each on live keys; info is free.
154
211
  */
155
212
  pdf = {
156
- merge: (sources, output = {}, options = {}) => this.request("POST", "/pdf/merge", { ...output, sources: sources.map(idOf) }, withKey(options)),
157
- protect: (source, protect, options = {}) => this.request("POST", "/pdf/protect", { ...protect, source: idOf(source) }, withKey(options)),
158
- watermark: (source, watermark, options = {}) => this.request("POST", "/pdf/watermark", { ...watermark, source: idOf(source) }, withKey(options)),
159
- info: (source, options = {}) => this.request("POST", "/pdf/info", { source: idOf(source) }, options),
213
+ merge: (sources, output = {}, options = {}) => this.request(
214
+ "POST",
215
+ "/pdf/merge",
216
+ { ...output, sources: sources.map(idOf) },
217
+ withKey(options)
218
+ ),
219
+ protect: (source, protect, options = {}) => this.request(
220
+ "POST",
221
+ "/pdf/protect",
222
+ { ...protect, source: idOf(source) },
223
+ withKey(options)
224
+ ),
225
+ watermark: (source, watermark, options = {}) => this.request(
226
+ "POST",
227
+ "/pdf/watermark",
228
+ { ...watermark, source: idOf(source) },
229
+ withKey(options)
230
+ ),
231
+ info: (source, options = {}) => this.request(
232
+ "POST",
233
+ "/pdf/info",
234
+ { source: idOf(source) },
235
+ options
236
+ ),
160
237
  /**
161
238
  * Converts an office document to a PDF render (Starter plan and above): an uploaded file (Word,
162
239
  * Excel, PowerPoint, OpenDocument, RTF or HTML, up to 20 MB, not kept), or the output of a Word or
@@ -165,8 +242,18 @@ var Formfeed = class {
165
242
  */
166
243
  convert: (source, convert = {}, options = {}) => {
167
244
  if (typeof source === "object" && "file" in source)
168
- return this.request("POST", "/pdf/convert", multipart({ ...convert }, source.file), withKey(options));
169
- return this.request("POST", "/pdf/convert", { ...convert, source: idOf(source) }, withKey(options));
245
+ return this.request(
246
+ "POST",
247
+ "/pdf/convert",
248
+ multipart({ ...convert }, source.file),
249
+ withKey(options)
250
+ );
251
+ return this.request(
252
+ "POST",
253
+ "/pdf/convert",
254
+ { ...convert, source: idOf(source) },
255
+ withKey(options)
256
+ );
170
257
  }
171
258
  };
172
259
  /**
@@ -178,7 +265,10 @@ var Formfeed = class {
178
265
  files = {
179
266
  /** Uploads a file; the same name replaces the existing file in place and keeps its URL. */
180
267
  upload: (file, options = {}) => {
181
- const blob = file.data instanceof Blob ? file.data : new Blob([file.data], file.contentType ? { type: file.contentType } : {});
268
+ const blob = file.data instanceof Blob ? file.data : new Blob(
269
+ [file.data],
270
+ file.contentType ? { type: file.contentType } : {}
271
+ );
182
272
  const form = new FormData();
183
273
  form.set("file", blob, file.name);
184
274
  form.set("name", file.name);
@@ -187,9 +277,15 @@ var Formfeed = class {
187
277
  /** Page of files, newest first. */
188
278
  list: (query = {}, options = {}) => {
189
279
  const params = new URLSearchParams();
190
- for (const [k, v] of Object.entries(query)) if (v !== void 0 && v !== null && v !== "") params.set(k, String(v));
280
+ for (const [k, v] of Object.entries(query))
281
+ if (v !== void 0 && v !== null && v !== "") params.set(k, String(v));
191
282
  const qs = params.toString();
192
- return this.request("GET", `/files${qs ? `?${qs}` : ""}`, void 0, options);
283
+ return this.request(
284
+ "GET",
285
+ `/files${qs ? `?${qs}` : ""}`,
286
+ void 0,
287
+ options
288
+ );
193
289
  },
194
290
  /** Every file matching the query, following the cursor. */
195
291
  all: async (query = {}, options = {}) => {
@@ -202,9 +298,19 @@ var Formfeed = class {
202
298
  } while (cursor);
203
299
  return out;
204
300
  },
205
- get: (id, options = {}) => this.request("GET", `/files/${encodeURIComponent(id)}`, void 0, options),
301
+ get: (id, options = {}) => this.request(
302
+ "GET",
303
+ `/files/${encodeURIComponent(id)}`,
304
+ void 0,
305
+ options
306
+ ),
206
307
  /** Removes the file and its bytes; templates that name it render a missing image afterwards. */
207
- delete: (file, options = {}) => this.request("DELETE", `/files/${encodeURIComponent(idOf(file))}`, void 0, options)
308
+ delete: (file, options = {}) => this.request(
309
+ "DELETE",
310
+ `/files/${encodeURIComponent(idOf(file))}`,
311
+ void 0,
312
+ options
313
+ )
208
314
  };
209
315
  /** The organisation's brand kit, read-only through the API (it is edited in the app). Needs `template:read`. */
210
316
  brand = {
@@ -216,19 +322,44 @@ var Formfeed = class {
216
322
  */
217
323
  partials = {
218
324
  /** Every shared partial, without its source. */
219
- list: async (options = {}) => (await this.request("GET", "/partials", void 0, options)).data,
325
+ list: async (options = {}) => (await this.request(
326
+ "GET",
327
+ "/partials",
328
+ void 0,
329
+ options
330
+ )).data,
220
331
  /** One partial with its source. */
221
- get: (name, options = {}) => this.request("GET", `/partials/${encodeURIComponent(name)}`, void 0, options),
332
+ get: (name, options = {}) => this.request(
333
+ "GET",
334
+ `/partials/${encodeURIComponent(name)}`,
335
+ void 0,
336
+ options
337
+ ),
222
338
  /** Creates or replaces a partial; pass `base_version` to fail with 409 when it changed since you read it. */
223
339
  put: async (name, input, options = {}) => {
224
- const { status, body } = await this.send("PUT", `/partials/${encodeURIComponent(name)}`, input, options);
340
+ const { status, body } = await this.send(
341
+ "PUT",
342
+ `/partials/${encodeURIComponent(name)}`,
343
+ input,
344
+ options
345
+ );
225
346
  return { partial: body, created: status === 201 };
226
347
  },
227
348
  /** Removes a partial; templates that still include it fail to render afterwards. */
228
- delete: (name, options = {}) => this.request("DELETE", `/partials/${encodeURIComponent(name)}`, void 0, options)
349
+ delete: (name, options = {}) => this.request(
350
+ "DELETE",
351
+ `/partials/${encodeURIComponent(name)}`,
352
+ void 0,
353
+ options
354
+ )
229
355
  };
230
356
  jobs = {
231
- get: (id, options = {}) => this.request("GET", `/jobs/${encodeURIComponent(id)}`, void 0, options),
357
+ get: (id, options = {}) => this.request(
358
+ "GET",
359
+ `/jobs/${encodeURIComponent(id)}`,
360
+ void 0,
361
+ options
362
+ ),
232
363
  waitFor: async (id, options = {}) => {
233
364
  const deadline = Date.now() + (options.timeoutMs ?? 6e5);
234
365
  const interval = options.intervalMs ?? 2e3;
@@ -236,37 +367,77 @@ var Formfeed = class {
236
367
  const job = await this.jobs.get(id, { signal: options.signal });
237
368
  if (job.status !== "queued" && job.status !== "processing") return job;
238
369
  if (Date.now() + interval > deadline)
239
- throw new FormfeedError("timeout", `job ${id} did not finish within the wait time`, 0);
370
+ throw new FormfeedError(
371
+ "timeout",
372
+ `job ${id} did not finish within the wait time`,
373
+ 0
374
+ );
240
375
  await sleep(interval, options.signal);
241
376
  }
242
377
  }
243
378
  };
244
379
  webhooks = {
245
- list: async (options = {}) => (await this.request("GET", "/webhooks", void 0, options)).data,
380
+ list: async (options = {}) => (await this.request(
381
+ "GET",
382
+ "/webhooks",
383
+ void 0,
384
+ options
385
+ )).data,
246
386
  create: (input, options = {}) => this.request("POST", "/webhooks", input, options),
247
- update: (id, patch, options = {}) => this.request("PUT", `/webhooks/${encodeURIComponent(id)}`, patch, options),
248
- delete: (id, options = {}) => this.request("DELETE", `/webhooks/${encodeURIComponent(id)}`, void 0, options),
249
- test: (id, options = {}) => this.request("POST", `/webhooks/${encodeURIComponent(id)}/test`, void 0, options),
387
+ update: (id, patch, options = {}) => this.request(
388
+ "PUT",
389
+ `/webhooks/${encodeURIComponent(id)}`,
390
+ patch,
391
+ options
392
+ ),
393
+ delete: (id, options = {}) => this.request(
394
+ "DELETE",
395
+ `/webhooks/${encodeURIComponent(id)}`,
396
+ void 0,
397
+ options
398
+ ),
399
+ test: (id, options = {}) => this.request(
400
+ "POST",
401
+ `/webhooks/${encodeURIComponent(id)}/test`,
402
+ void 0,
403
+ options
404
+ ),
250
405
  /**
251
406
  * Sends a stored event (`evt_…`) again as a new delivery to a registered endpoint (`webhook:manage`)
252
407
  * or a running listen session (`webhook:listen`).
253
408
  */
254
- resend: (eventId, endpointId, options = {}) => this.request("POST", `/webhooks/events/${encodeURIComponent(eventId)}/resend`, { endpoint_id: endpointId }, options),
409
+ resend: (eventId, endpointId, options = {}) => this.request(
410
+ "POST",
411
+ `/webhooks/events/${encodeURIComponent(eventId)}/resend`,
412
+ { endpoint_id: endpointId },
413
+ options
414
+ ),
255
415
  /**
256
416
  * Listen sessions, what `formfeed listen` uses. Needs `webhook:listen`. Starting a session ends the
257
417
  * previous one of the same key; the WebSocket itself is up to the caller.
258
418
  */
259
419
  listen: {
260
420
  start: (input = {}, options = {}) => this.request("POST", "/webhooks/listen", input, options),
261
- end: (id, options = {}) => this.request("DELETE", `/webhooks/listen/${encodeURIComponent(id)}`, void 0, options)
421
+ end: (id, options = {}) => this.request(
422
+ "DELETE",
423
+ `/webhooks/listen/${encodeURIComponent(id)}`,
424
+ void 0,
425
+ options
426
+ )
262
427
  }
263
428
  };
264
429
  templates = {
265
430
  list: (query = {}, options = {}) => {
266
431
  const params = new URLSearchParams();
267
- for (const [k, v] of Object.entries(query)) if (v !== void 0 && v !== null && v !== "") params.set(k, String(v));
432
+ for (const [k, v] of Object.entries(query))
433
+ if (v !== void 0 && v !== null && v !== "") params.set(k, String(v));
268
434
  const qs = params.toString();
269
- return this.request("GET", `/templates${qs ? `?${qs}` : ""}`, void 0, options);
435
+ return this.request(
436
+ "GET",
437
+ `/templates${qs ? `?${qs}` : ""}`,
438
+ void 0,
439
+ options
440
+ );
270
441
  },
271
442
  /** Every template of the workspace, following the cursor. */
272
443
  all: async (query = {}, options = {}) => {
@@ -279,27 +450,63 @@ var Formfeed = class {
279
450
  } while (cursor);
280
451
  return out;
281
452
  },
282
- get: (idOrSlug, options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}`, void 0, options),
453
+ get: (idOrSlug, options = {}) => this.request(
454
+ "GET",
455
+ `/templates/${encodeURIComponent(idOrSlug)}`,
456
+ void 0,
457
+ options
458
+ ),
283
459
  /** A Word or PowerPoint template (`kind` `docx` or `pptx`) is created from its `file`, sent as multipart. */
284
460
  create: (input, options = {}) => {
285
461
  if (isOfficeInput(input) && input.file) {
286
462
  const { file, ...fields } = input;
287
- return this.request("POST", "/templates", multipart(fields, file), options);
463
+ return this.request(
464
+ "POST",
465
+ "/templates",
466
+ multipart(fields, file),
467
+ options
468
+ );
288
469
  }
289
470
  return this.request("POST", "/templates", input, options);
290
471
  },
291
- update: (idOrSlug, patch, options = {}) => this.request("PUT", `/templates/${encodeURIComponent(idOrSlug)}`, patch, options),
292
- archive: (idOrSlug, options = {}) => this.request("DELETE", `/templates/${encodeURIComponent(idOrSlug)}`, void 0, options),
472
+ update: (idOrSlug, patch, options = {}) => this.request(
473
+ "PUT",
474
+ `/templates/${encodeURIComponent(idOrSlug)}`,
475
+ patch,
476
+ options
477
+ ),
478
+ archive: (idOrSlug, options = {}) => this.request(
479
+ "DELETE",
480
+ `/templates/${encodeURIComponent(idOrSlug)}`,
481
+ void 0,
482
+ options
483
+ ),
293
484
  versions: {
294
- list: async (idOrSlug, options = {}) => (await this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/versions`, void 0, options)).data,
485
+ list: async (idOrSlug, options = {}) => (await this.request(
486
+ "GET",
487
+ `/templates/${encodeURIComponent(idOrSlug)}/versions`,
488
+ void 0,
489
+ options
490
+ )).data,
295
491
  /** `which`: 'published', 'latest', a version number or a channel name (its main version). Carries the files. */
296
- get: (idOrSlug, which = "published", options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/versions/${encodeURIComponent(String(which))}`, void 0, options),
492
+ get: (idOrSlug, which = "published", options = {}) => this.request(
493
+ "GET",
494
+ `/templates/${encodeURIComponent(idOrSlug)}/versions/${encodeURIComponent(String(which))}`,
495
+ void 0,
496
+ options
497
+ ),
297
498
  /** For a Word or PowerPoint template, pass `file` to replace the document; without it the latest file is kept. */
298
499
  create: (idOrSlug, input, options = {}) => {
299
500
  const path = `/templates/${encodeURIComponent(idOrSlug)}/versions`;
300
501
  if (isOfficeInput(input)) {
301
502
  const { file, ...fields } = input;
302
- if (file) return this.request("POST", path, multipart(fields, file), options);
503
+ if (file)
504
+ return this.request(
505
+ "POST",
506
+ path,
507
+ multipart(fields, file),
508
+ options
509
+ );
303
510
  return this.request("POST", path, fields, options);
304
511
  }
305
512
  return this.request("POST", path, input, options);
@@ -328,7 +535,12 @@ var Formfeed = class {
328
535
  * uses the version the channel points at. Moves reach renders within a minute.
329
536
  */
330
537
  channels: {
331
- list: (idOrSlug, options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/channels`, void 0, options),
538
+ list: (idOrSlug, options = {}) => this.request(
539
+ "GET",
540
+ `/templates/${encodeURIComponent(idOrSlug)}/channels`,
541
+ void 0,
542
+ options
543
+ ),
332
544
  /** Creates or moves a channel; on `published` this publishes. */
333
545
  set: (idOrSlug, name, move, options = {}) => {
334
546
  const { allowBreaking, ...rest } = options;
@@ -362,19 +574,39 @@ var Formfeed = class {
362
574
  schema: (idOrSlug, options = {}) => {
363
575
  const { version, ...rest } = options;
364
576
  const query = version === void 0 ? "" : `?version=${encodeURIComponent(String(version))}`;
365
- return this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/schema${query}`, void 0, rest);
577
+ return this.request(
578
+ "GET",
579
+ `/templates/${encodeURIComponent(idOrSlug)}/schema${query}`,
580
+ void 0,
581
+ rest
582
+ );
366
583
  },
367
584
  /**
368
585
  * Checks a version with data without rendering: syntax, header and footer, missing partials,
369
586
  * runtime errors and the data against the stored schema. Free. Without `data`, the version's
370
587
  * sample data is checked. `version` takes a channel name too.
371
588
  */
372
- validate: (idOrSlug, input = {}, options = {}) => this.request("POST", `/templates/${encodeURIComponent(idOrSlug)}/validate`, input, options)
589
+ validate: (idOrSlug, input = {}, options = {}) => this.request(
590
+ "POST",
591
+ `/templates/${encodeURIComponent(idOrSlug)}/validate`,
592
+ input,
593
+ options
594
+ )
373
595
  };
374
596
  account = {
375
- get: (options = {}) => this.request("GET", "/account", void 0, options),
597
+ get: (options = {}) => this.request(
598
+ "GET",
599
+ "/account",
600
+ void 0,
601
+ options
602
+ ),
376
603
  /** Units of the current period, or of `YYYY-MM`, with a daily series and a breakdown per template. */
377
- usage: (period = "current", options = {}) => this.request("GET", `/usage?period=${encodeURIComponent(period)}`, void 0, options)
604
+ usage: (period = "current", options = {}) => this.request(
605
+ "GET",
606
+ `/usage?period=${encodeURIComponent(period)}`,
607
+ void 0,
608
+ options
609
+ )
378
610
  };
379
611
  /** Workspaces of the key's organisation. Needs `workspace:delete`, which no key has by default. */
380
612
  workspaces = {
@@ -382,7 +614,12 @@ var Formfeed = class {
382
614
  * Deletes a workspace: it leaves the app and the API at once, its keys are revoked and its content
383
615
  * is removed after 30 days. The organisation's last workspace is refused with `last_workspace`.
384
616
  */
385
- delete: (id, options = {}) => this.request("DELETE", `/workspaces/${encodeURIComponent(id)}`, void 0, options)
617
+ delete: (id, options = {}) => this.request(
618
+ "DELETE",
619
+ `/workspaces/${encodeURIComponent(id)}`,
620
+ void 0,
621
+ options
622
+ )
386
623
  };
387
624
  channelAction(idOrSlug, name, action, options) {
388
625
  const { allowBreaking, ...rest } = options;
@@ -410,13 +647,21 @@ var Formfeed = class {
410
647
  ...this.extraHeaders
411
648
  };
412
649
  const isForm = typeof FormData !== "undefined" && body instanceof FormData;
413
- if (body !== void 0 && !isForm) headers["content-type"] = "application/json";
414
- if (options.idempotencyKey) headers["idempotency-key"] = options.idempotencyKey;
650
+ if (body !== void 0 && !isForm)
651
+ headers["content-type"] = "application/json";
652
+ if (options.idempotencyKey)
653
+ headers["idempotency-key"] = options.idempotencyKey;
415
654
  let attempt = 0;
416
655
  for (; ; ) {
417
656
  const controller = new AbortController();
418
- const timer = setTimeout(() => controller.abort(new Error("request timed out")), this.timeoutMs);
419
- options.signal?.addEventListener("abort", () => controller.abort(options.signal?.reason));
657
+ const timer = setTimeout(
658
+ () => controller.abort(new Error("request timed out")),
659
+ this.timeoutMs
660
+ );
661
+ options.signal?.addEventListener(
662
+ "abort",
663
+ () => controller.abort(options.signal?.reason)
664
+ );
420
665
  let res;
421
666
  try {
422
667
  res = await this.fetchImpl(url, {
@@ -432,22 +677,38 @@ var Formfeed = class {
432
677
  await sleep(this.backoff(attempt, null), options.signal);
433
678
  continue;
434
679
  }
435
- throw new FormfeedError("network_error", e instanceof Error ? e.message : String(e), 0);
680
+ throw new FormfeedError(
681
+ "network_error",
682
+ e instanceof Error ? e.message : String(e),
683
+ 0
684
+ );
436
685
  }
437
686
  clearTimeout(timer);
438
687
  if ((res.status === 429 || res.status === 503) && attempt < this.maxRetries) {
439
688
  attempt++;
440
- await sleep(this.backoff(attempt, res.headers.get("retry-after")), options.signal);
689
+ await sleep(
690
+ this.backoff(attempt, res.headers.get("retry-after")),
691
+ options.signal
692
+ );
441
693
  continue;
442
694
  }
443
695
  if (res.status === 204) return { status: 204, body: void 0 };
444
- if (as === "bytes" && res.ok) return { status: res.status, body: new Uint8Array(await res.arrayBuffer()) };
696
+ if (as === "bytes" && res.ok)
697
+ return {
698
+ status: res.status,
699
+ body: new Uint8Array(await res.arrayBuffer())
700
+ };
445
701
  const text = await res.text();
446
702
  let json = null;
447
703
  try {
448
704
  json = text ? JSON.parse(text) : null;
449
705
  } catch {
450
- if (res.ok) throw new FormfeedError("invalid_response", `the API answered HTTP ${res.status} with a body that is not JSON`, res.status);
706
+ if (res.ok)
707
+ throw new FormfeedError(
708
+ "invalid_response",
709
+ `the API answered HTTP ${res.status} with a body that is not JSON`,
710
+ res.status
711
+ );
451
712
  }
452
713
  if (!res.ok) {
453
714
  const problem = json ?? {};