@formatjet/sdk 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +100 -2
- package/dist/client.d.ts +175 -0
- package/dist/client.js +406 -0
- package/dist/errors.d.ts +47 -0
- package/dist/errors.js +87 -0
- package/dist/generated/openapi.d.ts +2757 -0
- package/dist/generated/openapi.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/webhooks.d.ts +55 -0
- package/dist/webhooks.js +77 -0
- package/package.json +45 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Taprobane Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,101 @@
|
|
|
1
|
-
#
|
|
1
|
+
# FormatJet for TypeScript and JavaScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Convert documents, images, data and PDFs with the [FormatJet API](https://formatjet.com/docs): Word to PDF, PDF to Word, HEIC to JPG, Excel to CSV, Merge PDF and [the rest](https://formatjet.com/api).
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @formatjet/sdk
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Node.js 20 or later, or Bun. ESM only.
|
|
10
|
+
|
|
11
|
+
## Convert a file
|
|
12
|
+
|
|
13
|
+
Make an API key in the [dashboard](https://formatjet.com/app/) and set `FORMATJET_API_KEY`. A test key (`fj_test_…`) converts for free with smaller limits.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
17
|
+
import { FormatJet } from '@formatjet/sdk';
|
|
18
|
+
|
|
19
|
+
const formatjet = new FormatJet(); // reads FORMATJET_API_KEY
|
|
20
|
+
|
|
21
|
+
const result = await formatjet.convert(
|
|
22
|
+
{ data: await readFile('report.docx'), filename: 'report.docx' },
|
|
23
|
+
{ conversion: 'word-to-pdf' },
|
|
24
|
+
);
|
|
25
|
+
await writeFile(result.file.filename ?? 'report.pdf', result.file.data);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`convert()` sends the file in one request (up to 25 MB) and returns the converted files: `result.files` holds several for Split PDF, a PDF's pages as images or a workbook's sheets as CSV. Name the conversion by its slug (`GET /v1/operations` lists them), or say only the format to make: `{ to: 'pdf' }`. Options go in `options`, such as `{ conversion: 'pdf-to-jpg', options: { dpi: 300 } }`.
|
|
29
|
+
|
|
30
|
+
Files come as a `Uint8Array` (Node's `Buffer` is one), an `ArrayBuffer`, a `Blob` or a `File`; pass `{ data, filename }` to name the output after the input.
|
|
31
|
+
|
|
32
|
+
## Larger files
|
|
33
|
+
|
|
34
|
+
`uploadAndConvert()` uploads the file straight to storage first, so it takes files up to your plan's limit, then makes a job, waits for it and downloads the result:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const result = await formatjet.uploadAndConvert(
|
|
38
|
+
{ data: await readFile('scan.pdf'), filename: 'scan.pdf' },
|
|
39
|
+
{ conversion: 'pdf-to-word', metadata: { order: '8812' }, onProgress: (job) => console.log(job.progress) },
|
|
40
|
+
);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Files to join (Merge PDF, Image to PDF) go as a list, in order: `formatjet.convert([first, second], { conversion: 'merge-pdf' })`.
|
|
44
|
+
|
|
45
|
+
## Jobs
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const job = await formatjet.jobs.create({
|
|
49
|
+
input: { url: 'https://example.com/report.docx' },
|
|
50
|
+
steps: [{ op: 'convert', conversion: 'word-to-pdf' }],
|
|
51
|
+
webhook: 'https://example.com/hooks/formatjet',
|
|
52
|
+
});
|
|
53
|
+
const done = await formatjet.jobs.wait(job.id);
|
|
54
|
+
const pdf = await formatjet.download(done.outputs[0]!);
|
|
55
|
+
|
|
56
|
+
for await (const summary of formatjet.jobs.iterate()) console.log(summary.id, summary.status);
|
|
57
|
+
await formatjet.jobs.delete(job.id); // files deleted now; returns the signed receipt
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`jobs.get()`, `jobs.list()`, `jobs.cancel()` and `upload()` cover the rest, and `formatjet.api` calls any endpoint, typed from the OpenAPI document:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const { data } = await formatjet.api.GET('/v1/account');
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Errors and retries
|
|
67
|
+
|
|
68
|
+
Failed calls and failed jobs throw `FormatJetError`. Branch on its `code` ([the list](https://formatjet.com/docs/errors)); `requestId` identifies the request if you contact us.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { FormatJetError } from '@formatjet/sdk';
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
await formatjet.convert(file, { conversion: 'word-to-pdf' });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error instanceof FormatJetError && error.code === 'insufficient_credits') {
|
|
77
|
+
// …
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The client retries 429s, 5xx answers and network errors twice, honouring `Retry-After`. Each POST carries an `Idempotency-Key`, the same on every retry, so a retry never makes a second job. Set `maxRetries` and `timeoutMs` in the constructor.
|
|
83
|
+
|
|
84
|
+
## Webhooks
|
|
85
|
+
|
|
86
|
+
Check each delivery's signature with the raw body, before parsing it:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const event = await formatjet.webhooks.parse({
|
|
90
|
+
body: await request.text(),
|
|
91
|
+
header: request.headers.get('FormatJet-Signature'),
|
|
92
|
+
secret: process.env.FORMATJET_WEBHOOK_SECRET!,
|
|
93
|
+
});
|
|
94
|
+
if (event.type === 'job.completed') console.log(event.data.job.outputs);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`parse()` throws `WebhookSignatureError` for a delivery that doesn't check out; `verify()` returns the reason instead.
|
|
98
|
+
|
|
99
|
+
## Licence
|
|
100
|
+
|
|
101
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { type Client } from 'openapi-fetch';
|
|
2
|
+
import type { components, paths } from './generated/openapi.js';
|
|
3
|
+
import { parseWebhook, verifyWebhook } from './webhooks.js';
|
|
4
|
+
type Schemas = components['schemas'];
|
|
5
|
+
export type Job = Schemas['Job'];
|
|
6
|
+
export type JobSummary = Schemas['JobSummary'];
|
|
7
|
+
export type JobRequest = Schemas['JobRequest'];
|
|
8
|
+
export type JobStep = JobRequest['steps'][number];
|
|
9
|
+
export type JobStatus = Job['status'];
|
|
10
|
+
export type JobOutput = Job['outputs'][number];
|
|
11
|
+
export type Upload = Schemas['Upload'];
|
|
12
|
+
export type DeletionReceipt = Schemas['DeletionReceiptResponse']['receipt'];
|
|
13
|
+
/** A format's ID, such as `pdf` or `docx`. */
|
|
14
|
+
export type FormatId = NonNullable<JobStep['to']>;
|
|
15
|
+
/** The typed client for every endpoint, from the OpenAPI document. */
|
|
16
|
+
export type ApiClient = Client<paths>;
|
|
17
|
+
export declare const DEFAULT_BASE_URL = "https://api.formatjet.com";
|
|
18
|
+
/** A file's bytes: a `Blob` or `File`, a `Uint8Array` (Node's `Buffer` is one) or an `ArrayBuffer`. */
|
|
19
|
+
export type FileData = Blob | ArrayBuffer | ArrayBufferView;
|
|
20
|
+
/** A file, with its name when it isn't a `File`: the output is named after it. */
|
|
21
|
+
export type FileInput = FileData | {
|
|
22
|
+
data: FileData;
|
|
23
|
+
filename?: string;
|
|
24
|
+
};
|
|
25
|
+
export interface FormatJetOptions {
|
|
26
|
+
/** An API key from the dashboard. Defaults to the `FORMATJET_API_KEY` environment variable. */
|
|
27
|
+
apiKey?: string;
|
|
28
|
+
/** Defaults to https://api.formatjet.com. */
|
|
29
|
+
baseUrl?: string;
|
|
30
|
+
/** Retries after a 429, a 5xx answer or a network error (default 2). POSTs carry an Idempotency-Key, so a retry never runs a job twice. */
|
|
31
|
+
maxRetries?: number;
|
|
32
|
+
/** How long one request may take before it's retried, in milliseconds (default 5 minutes). */
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
/** A `fetch` to use instead of the global one. */
|
|
35
|
+
fetch?: (request: Request) => Promise<Response>;
|
|
36
|
+
}
|
|
37
|
+
export interface RequestOptions {
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
/** Sent as `Idempotency-Key` (default: a new UUID for each call, kept across its retries). */
|
|
40
|
+
idempotencyKey?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ConversionOptions {
|
|
43
|
+
/** A conversion's slug from `GET /v1/operations`, such as `word-to-pdf`. */
|
|
44
|
+
conversion?: string;
|
|
45
|
+
/** The format to make, when `conversion` is left out. */
|
|
46
|
+
to?: FormatId;
|
|
47
|
+
/** What the file is; detected from its bytes when left out. */
|
|
48
|
+
from?: FormatId;
|
|
49
|
+
/** The conversion's options (`options_schema` in `GET /v1/operations`). */
|
|
50
|
+
options?: Record<string, unknown>;
|
|
51
|
+
/** The output's name (default: the input's name with the new extension). */
|
|
52
|
+
outputFilename?: string;
|
|
53
|
+
/** Seconds the output is kept after the job ends (default 86400). */
|
|
54
|
+
ttl?: number;
|
|
55
|
+
}
|
|
56
|
+
export interface WaitOptions {
|
|
57
|
+
signal?: AbortSignal;
|
|
58
|
+
/** How long to wait, in milliseconds (default 15 minutes). The job carries on after. */
|
|
59
|
+
timeoutMs?: number;
|
|
60
|
+
/** Called with the job each time it's read while it runs. */
|
|
61
|
+
onProgress?: (job: Job) => void;
|
|
62
|
+
}
|
|
63
|
+
export interface ConvertOptions extends ConversionOptions, RequestOptions {
|
|
64
|
+
/** How long to wait for a job that runs past the request's 30 seconds (default 15 minutes). */
|
|
65
|
+
timeoutMs?: number;
|
|
66
|
+
}
|
|
67
|
+
export interface UploadAndConvertOptions extends ConvertOptions {
|
|
68
|
+
/** Up to 20 string values, returned with the job and in its webhook events. */
|
|
69
|
+
metadata?: Record<string, string>;
|
|
70
|
+
/** A URL that gets this job's events, besides your webhook endpoints. */
|
|
71
|
+
webhook?: string;
|
|
72
|
+
onProgress?: (job: Job) => void;
|
|
73
|
+
}
|
|
74
|
+
export interface OutputFile {
|
|
75
|
+
/** The file's name, from the input's and the conversion's. */
|
|
76
|
+
filename: string | null;
|
|
77
|
+
mime: string;
|
|
78
|
+
data: Uint8Array;
|
|
79
|
+
}
|
|
80
|
+
export interface ConvertResult {
|
|
81
|
+
jobId: string;
|
|
82
|
+
/** The first (usually the only) converted file. */
|
|
83
|
+
file: OutputFile;
|
|
84
|
+
/** Every converted file in order: several for Split PDF, a PDF's pages as images and the like. */
|
|
85
|
+
files: OutputFile[];
|
|
86
|
+
/** Credits charged: none with a test key. */
|
|
87
|
+
credits: number;
|
|
88
|
+
/** The engine and its version, such as `office libreoffice-25.8+runner-7`. */
|
|
89
|
+
engine: string | null;
|
|
90
|
+
/** The job, when the API answered with it (several files, or a wait past 30 seconds). */
|
|
91
|
+
job: Job | null;
|
|
92
|
+
}
|
|
93
|
+
/** The name in a `Content-Disposition` header (`filename*` first, as RFC 6266 says). */
|
|
94
|
+
export declare function filenameFrom(header: string | null): string | null;
|
|
95
|
+
/** Jobs: make, read, list, wait for, cancel and delete them. */
|
|
96
|
+
export declare class Jobs {
|
|
97
|
+
#private;
|
|
98
|
+
constructor(api: ApiClient);
|
|
99
|
+
/** Makes a job (`POST /v1/jobs`). It starts at once; wait for it with `wait()`. */
|
|
100
|
+
create(request: JobRequest, options?: RequestOptions): Promise<Job>;
|
|
101
|
+
get(id: string, options?: {
|
|
102
|
+
signal?: AbortSignal;
|
|
103
|
+
}): Promise<Job>;
|
|
104
|
+
/** One page of jobs, newest first: pass `next_cursor` back as `cursor` for the next. */
|
|
105
|
+
list(params?: {
|
|
106
|
+
limit?: number;
|
|
107
|
+
cursor?: string;
|
|
108
|
+
}, options?: {
|
|
109
|
+
signal?: AbortSignal;
|
|
110
|
+
}): Promise<{
|
|
111
|
+
data: JobSummary[];
|
|
112
|
+
next_cursor: string | null;
|
|
113
|
+
}>;
|
|
114
|
+
/** Every job of the last 30 days, newest first, a page at a time. */
|
|
115
|
+
iterate(params?: {
|
|
116
|
+
limit?: number;
|
|
117
|
+
}, options?: {
|
|
118
|
+
signal?: AbortSignal;
|
|
119
|
+
}): AsyncGenerator<JobSummary>;
|
|
120
|
+
/** Cancels a job that hasn't finished (`POST /v1/jobs/{id}/cancel`). */
|
|
121
|
+
cancel(id: string, options?: RequestOptions): Promise<Job>;
|
|
122
|
+
/** Deletes a job's files now and returns the signed deletion receipt. */
|
|
123
|
+
delete(id: string, options?: {
|
|
124
|
+
signal?: AbortSignal;
|
|
125
|
+
}): Promise<DeletionReceipt>;
|
|
126
|
+
/**
|
|
127
|
+
* Reads the job until it has ended (completed, failed or canceled) and returns it, whatever
|
|
128
|
+
* the outcome. Throws a `FormatJetError` with code `timeout` if it runs past `timeoutMs`.
|
|
129
|
+
*/
|
|
130
|
+
wait(id: string, options?: WaitOptions): Promise<Job>;
|
|
131
|
+
}
|
|
132
|
+
/** The webhook signature helpers. */
|
|
133
|
+
export declare const webhooks: {
|
|
134
|
+
readonly verify: typeof verifyWebhook;
|
|
135
|
+
readonly parse: typeof parseWebhook;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* The FormatJet API. `convert()` sends a file and gets the result in one request (up to
|
|
139
|
+
* 25 MB); `uploadAndConvert()` uploads it first, for files up to the plan's limit. `jobs`
|
|
140
|
+
* manages jobs, `webhooks` checks deliveries and `api` calls any endpoint, typed.
|
|
141
|
+
*/
|
|
142
|
+
export declare class FormatJet {
|
|
143
|
+
#private;
|
|
144
|
+
readonly baseUrl: string;
|
|
145
|
+
/** The typed client for every endpoint (openapi-fetch). */
|
|
146
|
+
readonly api: ApiClient;
|
|
147
|
+
readonly jobs: Jobs;
|
|
148
|
+
readonly webhooks: {
|
|
149
|
+
readonly verify: typeof verifyWebhook;
|
|
150
|
+
readonly parse: typeof parseWebhook;
|
|
151
|
+
};
|
|
152
|
+
constructor(options?: FormatJetOptions);
|
|
153
|
+
/**
|
|
154
|
+
* Converts one file (or the files a joining conversion takes) in one request, up to 25 MB in
|
|
155
|
+
* all, and returns the converted files. A job that runs past 30 seconds is waited for.
|
|
156
|
+
*/
|
|
157
|
+
convert(input: FileInput | FileInput[], options: ConvertOptions): Promise<ConvertResult>;
|
|
158
|
+
/**
|
|
159
|
+
* Uploads the file (or files, for a conversion that joins them), makes a job, waits for it
|
|
160
|
+
* and downloads what it made. For files larger than `convert()` takes.
|
|
161
|
+
*/
|
|
162
|
+
uploadAndConvert(input: FileInput | FileInput[], options: UploadAndConvertOptions): Promise<ConvertResult>;
|
|
163
|
+
/**
|
|
164
|
+
* Makes an upload and PUTs the file to it. Name the upload in a job's `input`
|
|
165
|
+
* (`{ upload_id: upload.id }`) within the hour.
|
|
166
|
+
*/
|
|
167
|
+
upload(input: FileInput, options?: {
|
|
168
|
+
signal?: AbortSignal;
|
|
169
|
+
}): Promise<Upload>;
|
|
170
|
+
/** Downloads one of a job's outputs. Its URL needs no key and works until `expires_at`. */
|
|
171
|
+
download(output: JobOutput, options?: {
|
|
172
|
+
signal?: AbortSignal;
|
|
173
|
+
}): Promise<OutputFile>;
|
|
174
|
+
}
|
|
175
|
+
export {};
|