@formfeed/sdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/index.cjs +172 -12
- package/index.js +172 -12
- package/package.json +1 -1
- package/types/index.d.cts +1 -1
- package/types/index.d.ts +1 -1
- package/types/lib/client.d.cts +380 -23
- package/types/lib/client.d.ts +380 -23
package/README.md
CHANGED
|
@@ -43,6 +43,27 @@ const job = await client.renders.batch({
|
|
|
43
43
|
const finished = await client.jobs.waitFor(job.id);
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
## Word and PowerPoint
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { readFile } from 'node:fs/promises';
|
|
50
|
+
|
|
51
|
+
// a template is a .docx or .pptx with tags such as {{ customer.name }} in its text
|
|
52
|
+
await client.templates.create({
|
|
53
|
+
name: 'Offer',
|
|
54
|
+
slug: 'offer',
|
|
55
|
+
kind: 'docx',
|
|
56
|
+
engine: 'jinja2',
|
|
57
|
+
file: { data: await readFile('offer.docx'), name: 'offer.docx' },
|
|
58
|
+
publish: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const filled = await client.renders.create({ template: 'offer', output: 'docx', data }); // or output: 'pdf'
|
|
62
|
+
|
|
63
|
+
// any office document to PDF (Word, Excel, PowerPoint, OpenDocument, RTF)
|
|
64
|
+
const pdf = await client.pdf.convert({ file: { data: await readFile('report.xlsx'), name: 'report.xlsx' } }, { single_page_sheets: true });
|
|
65
|
+
```
|
|
66
|
+
|
|
46
67
|
## Webhooks
|
|
47
68
|
|
|
48
69
|
```ts
|
package/index.cjs
CHANGED
|
@@ -28,6 +28,22 @@ __export(index_exports, {
|
|
|
28
28
|
module.exports = __toCommonJS(index_exports);
|
|
29
29
|
|
|
30
30
|
// packages/sdk-ts/src/lib/client.ts
|
|
31
|
+
var isOfficeInput = (input) => "file" in input;
|
|
32
|
+
function blobOf(file) {
|
|
33
|
+
const blob = file.data instanceof Blob ? file.data : new Blob([file.data]);
|
|
34
|
+
const own = typeof File !== "undefined" && file.data instanceof File ? file.data.name : void 0;
|
|
35
|
+
return { blob, name: file.name ?? own ?? "document" };
|
|
36
|
+
}
|
|
37
|
+
function multipart(fields, file) {
|
|
38
|
+
const form = new FormData();
|
|
39
|
+
const { blob, name } = blobOf(file);
|
|
40
|
+
form.set("file", blob, name);
|
|
41
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
42
|
+
if (value === void 0) continue;
|
|
43
|
+
form.set(key, typeof value === "string" ? value : typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
44
|
+
}
|
|
45
|
+
return form;
|
|
46
|
+
}
|
|
31
47
|
var FormfeedError = class extends Error {
|
|
32
48
|
constructor(code, message, status, problem = null, requestId = null) {
|
|
33
49
|
super(message);
|
|
@@ -119,6 +135,11 @@ var Formfeed = class {
|
|
|
119
135
|
},
|
|
120
136
|
/** Removes the stored files of a render before they expire. Needs the `file:delete` scope. */
|
|
121
137
|
deleteOutputs: (id, options = {}) => this.request("DELETE", `/renders/${encodeURIComponent(id)}/outputs`, void 0, options),
|
|
138
|
+
/**
|
|
139
|
+
* The stored request of a render, to render it again. Needs `render:input`; throws
|
|
140
|
+
* `render_input_expired` (410) when the workspace keeps no requests or the period ended.
|
|
141
|
+
*/
|
|
142
|
+
input: (id, options = {}) => this.request("GET", `/renders/${encodeURIComponent(id)}/input`, void 0, options),
|
|
122
143
|
batch: (request, options = {}) => this.request("POST", "/renders/batch", request, {
|
|
123
144
|
...options,
|
|
124
145
|
idempotencyKey: options.idempotencyKey ?? randomKey()
|
|
@@ -133,7 +154,18 @@ var Formfeed = class {
|
|
|
133
154
|
merge: (sources, output = {}, options = {}) => this.request("POST", "/pdf/merge", { ...output, sources: sources.map(idOf) }, withKey(options)),
|
|
134
155
|
protect: (source, protect, options = {}) => this.request("POST", "/pdf/protect", { ...protect, source: idOf(source) }, withKey(options)),
|
|
135
156
|
watermark: (source, watermark, options = {}) => this.request("POST", "/pdf/watermark", { ...watermark, source: idOf(source) }, withKey(options)),
|
|
136
|
-
info: (source, options = {}) => this.request("POST", "/pdf/info", { source: idOf(source) }, options)
|
|
157
|
+
info: (source, options = {}) => this.request("POST", "/pdf/info", { source: idOf(source) }, options),
|
|
158
|
+
/**
|
|
159
|
+
* Converts an office document to a PDF render (Starter plan and above): an uploaded file (Word,
|
|
160
|
+
* Excel, PowerPoint, OpenDocument, RTF or HTML, up to 20 MB, not kept), or the output of a Word or
|
|
161
|
+
* PowerPoint template render. Costs follow the PDF rule. A busy converter answers 503, which is
|
|
162
|
+
* retried like any other.
|
|
163
|
+
*/
|
|
164
|
+
convert: (source, convert = {}, options = {}) => {
|
|
165
|
+
if (typeof source === "object" && "file" in source)
|
|
166
|
+
return this.request("POST", "/pdf/convert", multipart({ ...convert }, source.file), withKey(options));
|
|
167
|
+
return this.request("POST", "/pdf/convert", { ...convert, source: idOf(source) }, withKey(options));
|
|
168
|
+
}
|
|
137
169
|
};
|
|
138
170
|
/**
|
|
139
171
|
* The workspace file library (`/files`): images and PDFs a template references by name with
|
|
@@ -172,6 +204,27 @@ var Formfeed = class {
|
|
|
172
204
|
/** Removes the file and its bytes; templates that name it render a missing image afterwards. */
|
|
173
205
|
delete: (file, options = {}) => this.request("DELETE", `/files/${encodeURIComponent(idOf(file))}`, void 0, options)
|
|
174
206
|
};
|
|
207
|
+
/** The organisation's brand kit, read-only through the API (it is edited in the app). Needs `template:read`. */
|
|
208
|
+
brand = {
|
|
209
|
+
get: (options = {}) => this.request("GET", "/brand", void 0, options)
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Shared partials of the organisation. Renders use the current source, so a change reaches every
|
|
213
|
+
* template that includes the partial without a new version. Needs `template:read` or `template:write`.
|
|
214
|
+
*/
|
|
215
|
+
partials = {
|
|
216
|
+
/** Every shared partial, without its source. */
|
|
217
|
+
list: async (options = {}) => (await this.request("GET", "/partials", void 0, options)).data,
|
|
218
|
+
/** One partial with its source. */
|
|
219
|
+
get: (name, options = {}) => this.request("GET", `/partials/${encodeURIComponent(name)}`, void 0, options),
|
|
220
|
+
/** Creates or replaces a partial; pass `base_version` to fail with 409 when it changed since you read it. */
|
|
221
|
+
put: async (name, input, options = {}) => {
|
|
222
|
+
const { status, body } = await this.send("PUT", `/partials/${encodeURIComponent(name)}`, input, options);
|
|
223
|
+
return { partial: body, created: status === 201 };
|
|
224
|
+
},
|
|
225
|
+
/** Removes a partial; templates that still include it fail to render afterwards. */
|
|
226
|
+
delete: (name, options = {}) => this.request("DELETE", `/partials/${encodeURIComponent(name)}`, void 0, options)
|
|
227
|
+
};
|
|
175
228
|
jobs = {
|
|
176
229
|
get: (id, options = {}) => this.request("GET", `/jobs/${encodeURIComponent(id)}`, void 0, options),
|
|
177
230
|
waitFor: async (id, options = {}) => {
|
|
@@ -191,7 +244,20 @@ var Formfeed = class {
|
|
|
191
244
|
create: (input, options = {}) => this.request("POST", "/webhooks", input, options),
|
|
192
245
|
update: (id, patch, options = {}) => this.request("PUT", `/webhooks/${encodeURIComponent(id)}`, patch, options),
|
|
193
246
|
delete: (id, options = {}) => this.request("DELETE", `/webhooks/${encodeURIComponent(id)}`, void 0, options),
|
|
194
|
-
test: (id, options = {}) => this.request("POST", `/webhooks/${encodeURIComponent(id)}/test`, void 0, options)
|
|
247
|
+
test: (id, options = {}) => this.request("POST", `/webhooks/${encodeURIComponent(id)}/test`, void 0, options),
|
|
248
|
+
/**
|
|
249
|
+
* Sends a stored event (`evt_…`) again as a new delivery to a registered endpoint (`webhook:manage`)
|
|
250
|
+
* or a running listen session (`webhook:listen`).
|
|
251
|
+
*/
|
|
252
|
+
resend: (eventId, endpointId, options = {}) => this.request("POST", `/webhooks/events/${encodeURIComponent(eventId)}/resend`, { endpoint_id: endpointId }, options),
|
|
253
|
+
/**
|
|
254
|
+
* Listen sessions, what `formfeed listen` uses. Needs `webhook:listen`. Starting a session ends the
|
|
255
|
+
* previous one of the same key; the WebSocket itself is up to the caller.
|
|
256
|
+
*/
|
|
257
|
+
listen: {
|
|
258
|
+
start: (input = {}, options = {}) => this.request("POST", "/webhooks/listen", input, options),
|
|
259
|
+
end: (id, options = {}) => this.request("DELETE", `/webhooks/listen/${encodeURIComponent(id)}`, void 0, options)
|
|
260
|
+
}
|
|
195
261
|
};
|
|
196
262
|
templates = {
|
|
197
263
|
list: (query = {}, options = {}) => {
|
|
@@ -212,15 +278,79 @@ var Formfeed = class {
|
|
|
212
278
|
return out;
|
|
213
279
|
},
|
|
214
280
|
get: (idOrSlug, options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}`, void 0, options),
|
|
215
|
-
|
|
281
|
+
/** A Word or PowerPoint template (`kind` `docx` or `pptx`) is created from its `file`, sent as multipart. */
|
|
282
|
+
create: (input, options = {}) => {
|
|
283
|
+
if (isOfficeInput(input) && input.file) {
|
|
284
|
+
const { file, ...fields } = input;
|
|
285
|
+
return this.request("POST", "/templates", multipart(fields, file), options);
|
|
286
|
+
}
|
|
287
|
+
return this.request("POST", "/templates", input, options);
|
|
288
|
+
},
|
|
216
289
|
update: (idOrSlug, patch, options = {}) => this.request("PUT", `/templates/${encodeURIComponent(idOrSlug)}`, patch, options),
|
|
217
290
|
archive: (idOrSlug, options = {}) => this.request("DELETE", `/templates/${encodeURIComponent(idOrSlug)}`, void 0, options),
|
|
218
291
|
versions: {
|
|
219
292
|
list: async (idOrSlug, options = {}) => (await this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/versions`, void 0, options)).data,
|
|
220
|
-
/** `which`: 'published', 'latest' or a version
|
|
221
|
-
get: (idOrSlug, which = "published", options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/versions/${which}`, void 0, options),
|
|
222
|
-
|
|
223
|
-
|
|
293
|
+
/** `which`: 'published', 'latest', a version number or a channel name (its main version). Carries the files. */
|
|
294
|
+
get: (idOrSlug, which = "published", options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/versions/${encodeURIComponent(String(which))}`, void 0, options),
|
|
295
|
+
/** For a Word or PowerPoint template, pass `file` to replace the document; without it the latest file is kept. */
|
|
296
|
+
create: (idOrSlug, input, options = {}) => {
|
|
297
|
+
const path = `/templates/${encodeURIComponent(idOrSlug)}/versions`;
|
|
298
|
+
if (isOfficeInput(input)) {
|
|
299
|
+
const { file, ...fields } = input;
|
|
300
|
+
if (file) return this.request("POST", path, multipart(fields, file), options);
|
|
301
|
+
return this.request("POST", path, fields, options);
|
|
302
|
+
}
|
|
303
|
+
return this.request("POST", path, input, options);
|
|
304
|
+
},
|
|
305
|
+
/** The Word or PowerPoint file of a version (`which` as for `get`); needs `template:read`. */
|
|
306
|
+
file: async (idOrSlug, which = "published", options = {}) => {
|
|
307
|
+
const path = `/templates/${encodeURIComponent(idOrSlug)}/versions/${encodeURIComponent(String(which))}/file`;
|
|
308
|
+
return (await this.send("GET", path, void 0, options, "bytes")).body;
|
|
309
|
+
},
|
|
310
|
+
/**
|
|
311
|
+
* Publishes a version. When both versions store a data schema and the new one breaks callers of the
|
|
312
|
+
* published one, the API refuses with `schema_breaking_change` unless `allowBreaking` is set.
|
|
313
|
+
*/
|
|
314
|
+
publish: (idOrSlug, number, options = {}) => {
|
|
315
|
+
const { allowBreaking, ...rest } = options;
|
|
316
|
+
return this.request(
|
|
317
|
+
"POST",
|
|
318
|
+
`/templates/${encodeURIComponent(idOrSlug)}/versions/${number}/publish`,
|
|
319
|
+
allowBreaking ? { allow_breaking: true } : void 0,
|
|
320
|
+
rest
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
/**
|
|
325
|
+
* Release channels (`published` and named ones such as `staging`): a render with `version: 'staging'`
|
|
326
|
+
* uses the version the channel points at. Moves reach renders within a minute.
|
|
327
|
+
*/
|
|
328
|
+
channels: {
|
|
329
|
+
list: (idOrSlug, options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/channels`, void 0, options),
|
|
330
|
+
/** Creates or moves a channel; on `published` this publishes. */
|
|
331
|
+
set: (idOrSlug, name, move, options = {}) => {
|
|
332
|
+
const { allowBreaking, ...rest } = options;
|
|
333
|
+
return this.request(
|
|
334
|
+
"PUT",
|
|
335
|
+
`/templates/${encodeURIComponent(idOrSlug)}/channels/${encodeURIComponent(name)}`,
|
|
336
|
+
{ ...move, ...allowBreaking ? { allow_breaking: true } : {} },
|
|
337
|
+
rest
|
|
338
|
+
);
|
|
339
|
+
},
|
|
340
|
+
/** The canary becomes the main version. */
|
|
341
|
+
promote: (idOrSlug, name, options = {}) => this.channelAction(idOrSlug, name, "promote", options),
|
|
342
|
+
/** Back to the version before the last move. */
|
|
343
|
+
rollback: (idOrSlug, name, options = {}) => this.channelAction(idOrSlug, name, "rollback", options),
|
|
344
|
+
/** Refused with `channel_in_use` while renders of the last hour used the channel, unless `force`. */
|
|
345
|
+
delete: (idOrSlug, name, options = {}) => {
|
|
346
|
+
const { force, ...rest } = options;
|
|
347
|
+
return this.request(
|
|
348
|
+
"DELETE",
|
|
349
|
+
`/templates/${encodeURIComponent(idOrSlug)}/channels/${encodeURIComponent(name)}${force ? "?force=true" : ""}`,
|
|
350
|
+
void 0,
|
|
351
|
+
rest
|
|
352
|
+
);
|
|
353
|
+
}
|
|
224
354
|
},
|
|
225
355
|
schema: (idOrSlug, options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/schema`, void 0, options),
|
|
226
356
|
/**
|
|
@@ -235,13 +365,37 @@ var Formfeed = class {
|
|
|
235
365
|
/** Units of the current period, or of `YYYY-MM`, with a daily series and a breakdown per template. */
|
|
236
366
|
usage: (period = "current", options = {}) => this.request("GET", `/usage?period=${encodeURIComponent(period)}`, void 0, options)
|
|
237
367
|
};
|
|
368
|
+
/** Workspaces of the key's organisation. Needs `workspace:delete`, which no key has by default. */
|
|
369
|
+
workspaces = {
|
|
370
|
+
/**
|
|
371
|
+
* Deletes a workspace: it leaves the app and the API at once, its keys are revoked and its content
|
|
372
|
+
* is removed after 30 days. The organisation's last workspace is refused with `last_workspace`.
|
|
373
|
+
*/
|
|
374
|
+
delete: (id, options = {}) => this.request("DELETE", `/workspaces/${encodeURIComponent(id)}`, void 0, options)
|
|
375
|
+
};
|
|
376
|
+
channelAction(idOrSlug, name, action, options) {
|
|
377
|
+
const { allowBreaking, ...rest } = options;
|
|
378
|
+
return this.request(
|
|
379
|
+
"POST",
|
|
380
|
+
`/templates/${encodeURIComponent(idOrSlug)}/channels/${encodeURIComponent(name)}/${action}`,
|
|
381
|
+
allowBreaking ? { allow_breaking: true } : void 0,
|
|
382
|
+
rest
|
|
383
|
+
);
|
|
384
|
+
}
|
|
238
385
|
/** Raw request with auth, retries and problem mapping; use it for endpoints without a helper. */
|
|
239
386
|
async request(method, path, body, options = {}) {
|
|
387
|
+
return (await this.send(method, path, body, options)).body;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* `request` with the HTTP status, for endpoints whose answer depends on it (201 created, 200 updated).
|
|
391
|
+
* `bytes` returns a successful body as bytes (file downloads); problems are JSON either way.
|
|
392
|
+
*/
|
|
393
|
+
async send(method, path, body, options = {}, as = "json") {
|
|
240
394
|
const url = `${this.baseUrl}${path}`;
|
|
241
395
|
const headers = {
|
|
242
396
|
authorization: `Bearer ${this.apiKey}`,
|
|
243
|
-
accept: "application/json",
|
|
244
|
-
"user-agent": "formfeed-sdk-ts/0.
|
|
397
|
+
accept: as === "bytes" ? "*/*" : "application/json",
|
|
398
|
+
"user-agent": "formfeed-sdk-ts/0.3"
|
|
245
399
|
};
|
|
246
400
|
const isForm = typeof FormData !== "undefined" && body instanceof FormData;
|
|
247
401
|
if (body !== void 0 && !isForm) headers["content-type"] = "application/json";
|
|
@@ -274,9 +428,15 @@ var Formfeed = class {
|
|
|
274
428
|
await sleep(this.backoff(attempt, res.headers.get("retry-after")), options.signal);
|
|
275
429
|
continue;
|
|
276
430
|
}
|
|
277
|
-
if (res.status === 204) return void 0;
|
|
431
|
+
if (res.status === 204) return { status: 204, body: void 0 };
|
|
432
|
+
if (as === "bytes" && res.ok) return { status: res.status, body: new Uint8Array(await res.arrayBuffer()) };
|
|
278
433
|
const text = await res.text();
|
|
279
|
-
|
|
434
|
+
let json = null;
|
|
435
|
+
try {
|
|
436
|
+
json = text ? JSON.parse(text) : null;
|
|
437
|
+
} catch {
|
|
438
|
+
if (res.ok) throw new FormfeedError("invalid_response", `the API answered HTTP ${res.status} with a body that is not JSON`, res.status);
|
|
439
|
+
}
|
|
280
440
|
if (!res.ok) {
|
|
281
441
|
const problem = json ?? {};
|
|
282
442
|
throw new FormfeedError(
|
|
@@ -287,7 +447,7 @@ var Formfeed = class {
|
|
|
287
447
|
res.headers.get("x-request-id")
|
|
288
448
|
);
|
|
289
449
|
}
|
|
290
|
-
return json;
|
|
450
|
+
return { status: res.status, body: json };
|
|
291
451
|
}
|
|
292
452
|
}
|
|
293
453
|
backoff(attempt, retryAfter) {
|
package/index.js
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
// packages/sdk-ts/src/lib/client.ts
|
|
2
|
+
var isOfficeInput = (input) => "file" in input;
|
|
3
|
+
function blobOf(file) {
|
|
4
|
+
const blob = file.data instanceof Blob ? file.data : new Blob([file.data]);
|
|
5
|
+
const own = typeof File !== "undefined" && file.data instanceof File ? file.data.name : void 0;
|
|
6
|
+
return { blob, name: file.name ?? own ?? "document" };
|
|
7
|
+
}
|
|
8
|
+
function multipart(fields, file) {
|
|
9
|
+
const form = new FormData();
|
|
10
|
+
const { blob, name } = blobOf(file);
|
|
11
|
+
form.set("file", blob, name);
|
|
12
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
13
|
+
if (value === void 0) continue;
|
|
14
|
+
form.set(key, typeof value === "string" ? value : typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
15
|
+
}
|
|
16
|
+
return form;
|
|
17
|
+
}
|
|
2
18
|
var FormfeedError = class extends Error {
|
|
3
19
|
constructor(code, message, status, problem = null, requestId = null) {
|
|
4
20
|
super(message);
|
|
@@ -90,6 +106,11 @@ var Formfeed = class {
|
|
|
90
106
|
},
|
|
91
107
|
/** Removes the stored files of a render before they expire. Needs the `file:delete` scope. */
|
|
92
108
|
deleteOutputs: (id, options = {}) => this.request("DELETE", `/renders/${encodeURIComponent(id)}/outputs`, void 0, options),
|
|
109
|
+
/**
|
|
110
|
+
* The stored request of a render, to render it again. Needs `render:input`; throws
|
|
111
|
+
* `render_input_expired` (410) when the workspace keeps no requests or the period ended.
|
|
112
|
+
*/
|
|
113
|
+
input: (id, options = {}) => this.request("GET", `/renders/${encodeURIComponent(id)}/input`, void 0, options),
|
|
93
114
|
batch: (request, options = {}) => this.request("POST", "/renders/batch", request, {
|
|
94
115
|
...options,
|
|
95
116
|
idempotencyKey: options.idempotencyKey ?? randomKey()
|
|
@@ -104,7 +125,18 @@ var Formfeed = class {
|
|
|
104
125
|
merge: (sources, output = {}, options = {}) => this.request("POST", "/pdf/merge", { ...output, sources: sources.map(idOf) }, withKey(options)),
|
|
105
126
|
protect: (source, protect, options = {}) => this.request("POST", "/pdf/protect", { ...protect, source: idOf(source) }, withKey(options)),
|
|
106
127
|
watermark: (source, watermark, options = {}) => this.request("POST", "/pdf/watermark", { ...watermark, source: idOf(source) }, withKey(options)),
|
|
107
|
-
info: (source, options = {}) => this.request("POST", "/pdf/info", { source: idOf(source) }, options)
|
|
128
|
+
info: (source, options = {}) => this.request("POST", "/pdf/info", { source: idOf(source) }, options),
|
|
129
|
+
/**
|
|
130
|
+
* Converts an office document to a PDF render (Starter plan and above): an uploaded file (Word,
|
|
131
|
+
* Excel, PowerPoint, OpenDocument, RTF or HTML, up to 20 MB, not kept), or the output of a Word or
|
|
132
|
+
* PowerPoint template render. Costs follow the PDF rule. A busy converter answers 503, which is
|
|
133
|
+
* retried like any other.
|
|
134
|
+
*/
|
|
135
|
+
convert: (source, convert = {}, options = {}) => {
|
|
136
|
+
if (typeof source === "object" && "file" in source)
|
|
137
|
+
return this.request("POST", "/pdf/convert", multipart({ ...convert }, source.file), withKey(options));
|
|
138
|
+
return this.request("POST", "/pdf/convert", { ...convert, source: idOf(source) }, withKey(options));
|
|
139
|
+
}
|
|
108
140
|
};
|
|
109
141
|
/**
|
|
110
142
|
* The workspace file library (`/files`): images and PDFs a template references by name with
|
|
@@ -143,6 +175,27 @@ var Formfeed = class {
|
|
|
143
175
|
/** Removes the file and its bytes; templates that name it render a missing image afterwards. */
|
|
144
176
|
delete: (file, options = {}) => this.request("DELETE", `/files/${encodeURIComponent(idOf(file))}`, void 0, options)
|
|
145
177
|
};
|
|
178
|
+
/** The organisation's brand kit, read-only through the API (it is edited in the app). Needs `template:read`. */
|
|
179
|
+
brand = {
|
|
180
|
+
get: (options = {}) => this.request("GET", "/brand", void 0, options)
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Shared partials of the organisation. Renders use the current source, so a change reaches every
|
|
184
|
+
* template that includes the partial without a new version. Needs `template:read` or `template:write`.
|
|
185
|
+
*/
|
|
186
|
+
partials = {
|
|
187
|
+
/** Every shared partial, without its source. */
|
|
188
|
+
list: async (options = {}) => (await this.request("GET", "/partials", void 0, options)).data,
|
|
189
|
+
/** One partial with its source. */
|
|
190
|
+
get: (name, options = {}) => this.request("GET", `/partials/${encodeURIComponent(name)}`, void 0, options),
|
|
191
|
+
/** Creates or replaces a partial; pass `base_version` to fail with 409 when it changed since you read it. */
|
|
192
|
+
put: async (name, input, options = {}) => {
|
|
193
|
+
const { status, body } = await this.send("PUT", `/partials/${encodeURIComponent(name)}`, input, options);
|
|
194
|
+
return { partial: body, created: status === 201 };
|
|
195
|
+
},
|
|
196
|
+
/** Removes a partial; templates that still include it fail to render afterwards. */
|
|
197
|
+
delete: (name, options = {}) => this.request("DELETE", `/partials/${encodeURIComponent(name)}`, void 0, options)
|
|
198
|
+
};
|
|
146
199
|
jobs = {
|
|
147
200
|
get: (id, options = {}) => this.request("GET", `/jobs/${encodeURIComponent(id)}`, void 0, options),
|
|
148
201
|
waitFor: async (id, options = {}) => {
|
|
@@ -162,7 +215,20 @@ var Formfeed = class {
|
|
|
162
215
|
create: (input, options = {}) => this.request("POST", "/webhooks", input, options),
|
|
163
216
|
update: (id, patch, options = {}) => this.request("PUT", `/webhooks/${encodeURIComponent(id)}`, patch, options),
|
|
164
217
|
delete: (id, options = {}) => this.request("DELETE", `/webhooks/${encodeURIComponent(id)}`, void 0, options),
|
|
165
|
-
test: (id, options = {}) => this.request("POST", `/webhooks/${encodeURIComponent(id)}/test`, void 0, options)
|
|
218
|
+
test: (id, options = {}) => this.request("POST", `/webhooks/${encodeURIComponent(id)}/test`, void 0, options),
|
|
219
|
+
/**
|
|
220
|
+
* Sends a stored event (`evt_…`) again as a new delivery to a registered endpoint (`webhook:manage`)
|
|
221
|
+
* or a running listen session (`webhook:listen`).
|
|
222
|
+
*/
|
|
223
|
+
resend: (eventId, endpointId, options = {}) => this.request("POST", `/webhooks/events/${encodeURIComponent(eventId)}/resend`, { endpoint_id: endpointId }, options),
|
|
224
|
+
/**
|
|
225
|
+
* Listen sessions, what `formfeed listen` uses. Needs `webhook:listen`. Starting a session ends the
|
|
226
|
+
* previous one of the same key; the WebSocket itself is up to the caller.
|
|
227
|
+
*/
|
|
228
|
+
listen: {
|
|
229
|
+
start: (input = {}, options = {}) => this.request("POST", "/webhooks/listen", input, options),
|
|
230
|
+
end: (id, options = {}) => this.request("DELETE", `/webhooks/listen/${encodeURIComponent(id)}`, void 0, options)
|
|
231
|
+
}
|
|
166
232
|
};
|
|
167
233
|
templates = {
|
|
168
234
|
list: (query = {}, options = {}) => {
|
|
@@ -183,15 +249,79 @@ var Formfeed = class {
|
|
|
183
249
|
return out;
|
|
184
250
|
},
|
|
185
251
|
get: (idOrSlug, options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}`, void 0, options),
|
|
186
|
-
|
|
252
|
+
/** A Word or PowerPoint template (`kind` `docx` or `pptx`) is created from its `file`, sent as multipart. */
|
|
253
|
+
create: (input, options = {}) => {
|
|
254
|
+
if (isOfficeInput(input) && input.file) {
|
|
255
|
+
const { file, ...fields } = input;
|
|
256
|
+
return this.request("POST", "/templates", multipart(fields, file), options);
|
|
257
|
+
}
|
|
258
|
+
return this.request("POST", "/templates", input, options);
|
|
259
|
+
},
|
|
187
260
|
update: (idOrSlug, patch, options = {}) => this.request("PUT", `/templates/${encodeURIComponent(idOrSlug)}`, patch, options),
|
|
188
261
|
archive: (idOrSlug, options = {}) => this.request("DELETE", `/templates/${encodeURIComponent(idOrSlug)}`, void 0, options),
|
|
189
262
|
versions: {
|
|
190
263
|
list: async (idOrSlug, options = {}) => (await this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/versions`, void 0, options)).data,
|
|
191
|
-
/** `which`: 'published', 'latest' or a version
|
|
192
|
-
get: (idOrSlug, which = "published", options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/versions/${which}`, void 0, options),
|
|
193
|
-
|
|
194
|
-
|
|
264
|
+
/** `which`: 'published', 'latest', a version number or a channel name (its main version). Carries the files. */
|
|
265
|
+
get: (idOrSlug, which = "published", options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/versions/${encodeURIComponent(String(which))}`, void 0, options),
|
|
266
|
+
/** For a Word or PowerPoint template, pass `file` to replace the document; without it the latest file is kept. */
|
|
267
|
+
create: (idOrSlug, input, options = {}) => {
|
|
268
|
+
const path = `/templates/${encodeURIComponent(idOrSlug)}/versions`;
|
|
269
|
+
if (isOfficeInput(input)) {
|
|
270
|
+
const { file, ...fields } = input;
|
|
271
|
+
if (file) return this.request("POST", path, multipart(fields, file), options);
|
|
272
|
+
return this.request("POST", path, fields, options);
|
|
273
|
+
}
|
|
274
|
+
return this.request("POST", path, input, options);
|
|
275
|
+
},
|
|
276
|
+
/** The Word or PowerPoint file of a version (`which` as for `get`); needs `template:read`. */
|
|
277
|
+
file: async (idOrSlug, which = "published", options = {}) => {
|
|
278
|
+
const path = `/templates/${encodeURIComponent(idOrSlug)}/versions/${encodeURIComponent(String(which))}/file`;
|
|
279
|
+
return (await this.send("GET", path, void 0, options, "bytes")).body;
|
|
280
|
+
},
|
|
281
|
+
/**
|
|
282
|
+
* Publishes a version. When both versions store a data schema and the new one breaks callers of the
|
|
283
|
+
* published one, the API refuses with `schema_breaking_change` unless `allowBreaking` is set.
|
|
284
|
+
*/
|
|
285
|
+
publish: (idOrSlug, number, options = {}) => {
|
|
286
|
+
const { allowBreaking, ...rest } = options;
|
|
287
|
+
return this.request(
|
|
288
|
+
"POST",
|
|
289
|
+
`/templates/${encodeURIComponent(idOrSlug)}/versions/${number}/publish`,
|
|
290
|
+
allowBreaking ? { allow_breaking: true } : void 0,
|
|
291
|
+
rest
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
/**
|
|
296
|
+
* Release channels (`published` and named ones such as `staging`): a render with `version: 'staging'`
|
|
297
|
+
* uses the version the channel points at. Moves reach renders within a minute.
|
|
298
|
+
*/
|
|
299
|
+
channels: {
|
|
300
|
+
list: (idOrSlug, options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/channels`, void 0, options),
|
|
301
|
+
/** Creates or moves a channel; on `published` this publishes. */
|
|
302
|
+
set: (idOrSlug, name, move, options = {}) => {
|
|
303
|
+
const { allowBreaking, ...rest } = options;
|
|
304
|
+
return this.request(
|
|
305
|
+
"PUT",
|
|
306
|
+
`/templates/${encodeURIComponent(idOrSlug)}/channels/${encodeURIComponent(name)}`,
|
|
307
|
+
{ ...move, ...allowBreaking ? { allow_breaking: true } : {} },
|
|
308
|
+
rest
|
|
309
|
+
);
|
|
310
|
+
},
|
|
311
|
+
/** The canary becomes the main version. */
|
|
312
|
+
promote: (idOrSlug, name, options = {}) => this.channelAction(idOrSlug, name, "promote", options),
|
|
313
|
+
/** Back to the version before the last move. */
|
|
314
|
+
rollback: (idOrSlug, name, options = {}) => this.channelAction(idOrSlug, name, "rollback", options),
|
|
315
|
+
/** Refused with `channel_in_use` while renders of the last hour used the channel, unless `force`. */
|
|
316
|
+
delete: (idOrSlug, name, options = {}) => {
|
|
317
|
+
const { force, ...rest } = options;
|
|
318
|
+
return this.request(
|
|
319
|
+
"DELETE",
|
|
320
|
+
`/templates/${encodeURIComponent(idOrSlug)}/channels/${encodeURIComponent(name)}${force ? "?force=true" : ""}`,
|
|
321
|
+
void 0,
|
|
322
|
+
rest
|
|
323
|
+
);
|
|
324
|
+
}
|
|
195
325
|
},
|
|
196
326
|
schema: (idOrSlug, options = {}) => this.request("GET", `/templates/${encodeURIComponent(idOrSlug)}/schema`, void 0, options),
|
|
197
327
|
/**
|
|
@@ -206,13 +336,37 @@ var Formfeed = class {
|
|
|
206
336
|
/** Units of the current period, or of `YYYY-MM`, with a daily series and a breakdown per template. */
|
|
207
337
|
usage: (period = "current", options = {}) => this.request("GET", `/usage?period=${encodeURIComponent(period)}`, void 0, options)
|
|
208
338
|
};
|
|
339
|
+
/** Workspaces of the key's organisation. Needs `workspace:delete`, which no key has by default. */
|
|
340
|
+
workspaces = {
|
|
341
|
+
/**
|
|
342
|
+
* Deletes a workspace: it leaves the app and the API at once, its keys are revoked and its content
|
|
343
|
+
* is removed after 30 days. The organisation's last workspace is refused with `last_workspace`.
|
|
344
|
+
*/
|
|
345
|
+
delete: (id, options = {}) => this.request("DELETE", `/workspaces/${encodeURIComponent(id)}`, void 0, options)
|
|
346
|
+
};
|
|
347
|
+
channelAction(idOrSlug, name, action, options) {
|
|
348
|
+
const { allowBreaking, ...rest } = options;
|
|
349
|
+
return this.request(
|
|
350
|
+
"POST",
|
|
351
|
+
`/templates/${encodeURIComponent(idOrSlug)}/channels/${encodeURIComponent(name)}/${action}`,
|
|
352
|
+
allowBreaking ? { allow_breaking: true } : void 0,
|
|
353
|
+
rest
|
|
354
|
+
);
|
|
355
|
+
}
|
|
209
356
|
/** Raw request with auth, retries and problem mapping; use it for endpoints without a helper. */
|
|
210
357
|
async request(method, path, body, options = {}) {
|
|
358
|
+
return (await this.send(method, path, body, options)).body;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* `request` with the HTTP status, for endpoints whose answer depends on it (201 created, 200 updated).
|
|
362
|
+
* `bytes` returns a successful body as bytes (file downloads); problems are JSON either way.
|
|
363
|
+
*/
|
|
364
|
+
async send(method, path, body, options = {}, as = "json") {
|
|
211
365
|
const url = `${this.baseUrl}${path}`;
|
|
212
366
|
const headers = {
|
|
213
367
|
authorization: `Bearer ${this.apiKey}`,
|
|
214
|
-
accept: "application/json",
|
|
215
|
-
"user-agent": "formfeed-sdk-ts/0.
|
|
368
|
+
accept: as === "bytes" ? "*/*" : "application/json",
|
|
369
|
+
"user-agent": "formfeed-sdk-ts/0.3"
|
|
216
370
|
};
|
|
217
371
|
const isForm = typeof FormData !== "undefined" && body instanceof FormData;
|
|
218
372
|
if (body !== void 0 && !isForm) headers["content-type"] = "application/json";
|
|
@@ -245,9 +399,15 @@ var Formfeed = class {
|
|
|
245
399
|
await sleep(this.backoff(attempt, res.headers.get("retry-after")), options.signal);
|
|
246
400
|
continue;
|
|
247
401
|
}
|
|
248
|
-
if (res.status === 204) return void 0;
|
|
402
|
+
if (res.status === 204) return { status: 204, body: void 0 };
|
|
403
|
+
if (as === "bytes" && res.ok) return { status: res.status, body: new Uint8Array(await res.arrayBuffer()) };
|
|
249
404
|
const text = await res.text();
|
|
250
|
-
|
|
405
|
+
let json = null;
|
|
406
|
+
try {
|
|
407
|
+
json = text ? JSON.parse(text) : null;
|
|
408
|
+
} catch {
|
|
409
|
+
if (res.ok) throw new FormfeedError("invalid_response", `the API answered HTTP ${res.status} with a body that is not JSON`, res.status);
|
|
410
|
+
}
|
|
251
411
|
if (!res.ok) {
|
|
252
412
|
const problem = json ?? {};
|
|
253
413
|
throw new FormfeedError(
|
|
@@ -258,7 +418,7 @@ var Formfeed = class {
|
|
|
258
418
|
res.headers.get("x-request-id")
|
|
259
419
|
);
|
|
260
420
|
}
|
|
261
|
-
return json;
|
|
421
|
+
return { status: res.status, body: json };
|
|
262
422
|
}
|
|
263
423
|
}
|
|
264
424
|
backoff(attempt, retryAfter) {
|
package/package.json
CHANGED
package/types/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { Formfeed, FormfeedError } from './lib/client.cjs';
|
|
2
|
-
export type { Template, TemplateCreate, TemplateFiles, TemplateKind, TemplateListOptions, TemplateValidation, TemplateVersion, TemplateVersionCreate, VersionStatus, BatchRequest, Diagnostic, Engine, FileListOptions, FileUpload, FormfeedOptions, Job, LibraryFile, OutputFormat, PdfInfo, PdfOutputOptions, PdfPermission, PostProcessing, Problem, ProtectOptions, Region, Render, RenderListOptions, RenderRequest, RenderStatus, RequestOptions, Usage, WaitOptions, WatermarkOptions, WebhookEndpoint, } from './lib/client.cjs';
|
|
2
|
+
export type { Template, TemplateCreate, TemplateFiles, TemplateKind, TemplateListOptions, TemplateValidation, TemplateVersion, TemplateVersionCreate, TemplateFile, OfficeTemplateKind, OfficeTemplateCreate, OfficeVersionCreate, OfficeFileUpload, ConvertOptions, VersionStatus, BatchRequest, Channel, ChannelList, ChannelMove, Brand, SharedPartial, SharedPartialPut, SharedPartialPutResult, Diagnostic, Engine, FileListOptions, FileUpload, FormfeedOptions, FormfeedTemplates, Job, LibraryFile, OutputFormat, PdfInfo, PdfOutputOptions, PdfPermission, PostProcessing, Problem, ProtectOptions, Region, Render, RenderListOptions, RenderRequest, RenderStatus, TemplateData, TypedRenderRequest, RequestOptions, SchemaChange, SchemaCheck, SchemaGuardOptions, Usage, WaitOptions, WatermarkOptions, WebhookEndpoint, WebhookEndpointCreate, WebhookEndpointUpdate, WebhookEnvironment, WebhookResend, ListenSession, ListenSessionOptions, RenderInput, } from './lib/client.cjs';
|
|
3
3
|
export { parseWebhookEvent, verifyWebhookSignature } from './lib/webhooks.cjs';
|
|
4
4
|
export type { VerifyOptions } from './lib/webhooks.cjs';
|
package/types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { Formfeed, FormfeedError } from './lib/client.js';
|
|
2
|
-
export type { Template, TemplateCreate, TemplateFiles, TemplateKind, TemplateListOptions, TemplateValidation, TemplateVersion, TemplateVersionCreate, VersionStatus, BatchRequest, Diagnostic, Engine, FileListOptions, FileUpload, FormfeedOptions, Job, LibraryFile, OutputFormat, PdfInfo, PdfOutputOptions, PdfPermission, PostProcessing, Problem, ProtectOptions, Region, Render, RenderListOptions, RenderRequest, RenderStatus, RequestOptions, Usage, WaitOptions, WatermarkOptions, WebhookEndpoint, } from './lib/client.js';
|
|
2
|
+
export type { Template, TemplateCreate, TemplateFiles, TemplateKind, TemplateListOptions, TemplateValidation, TemplateVersion, TemplateVersionCreate, TemplateFile, OfficeTemplateKind, OfficeTemplateCreate, OfficeVersionCreate, OfficeFileUpload, ConvertOptions, VersionStatus, BatchRequest, Channel, ChannelList, ChannelMove, Brand, SharedPartial, SharedPartialPut, SharedPartialPutResult, Diagnostic, Engine, FileListOptions, FileUpload, FormfeedOptions, FormfeedTemplates, Job, LibraryFile, OutputFormat, PdfInfo, PdfOutputOptions, PdfPermission, PostProcessing, Problem, ProtectOptions, Region, Render, RenderListOptions, RenderRequest, RenderStatus, TemplateData, TypedRenderRequest, RequestOptions, SchemaChange, SchemaCheck, SchemaGuardOptions, Usage, WaitOptions, WatermarkOptions, WebhookEndpoint, WebhookEndpointCreate, WebhookEndpointUpdate, WebhookEnvironment, WebhookResend, ListenSession, ListenSessionOptions, RenderInput, } from './lib/client.js';
|
|
3
3
|
export { parseWebhookEvent, verifyWebhookSignature } from './lib/webhooks.js';
|
|
4
4
|
export type { VerifyOptions } from './lib/webhooks.js';
|