@office2pdf/node 0.1.2
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 +129 -0
- package/dist/index.cjs +339 -0
- package/dist/index.d.cts +90 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +301 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# office2pdf (Node.js)
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for the Office2PDF API.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
Convert Word, Excel, and PowerPoint documents to PDF with a simple, production-ready API.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- Node.js **>= 18**
|
|
16
|
+
- An Office2PDF API key
|
|
17
|
+
|
|
18
|
+
Authentication is done via the `x-api-key` request header.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @politehq/office2pdf
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { Office2PDF } from "@politehq/office2pdf";
|
|
34
|
+
|
|
35
|
+
const client = new Office2PDF({
|
|
36
|
+
apiKey: process.env.OFFICE2PDF_API_KEY!,
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Convert and download to file (recommended)
|
|
43
|
+
|
|
44
|
+
For production workloads and large documents, always download directly to disk.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const result = await client.convert({
|
|
48
|
+
filePath: "./input.docx",
|
|
49
|
+
downloadToPath: "./output.pdf",
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
console.log("Saved to:", result.path);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Convert to in-memory buffer
|
|
58
|
+
|
|
59
|
+
Suitable for small files or quick scripts.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import fs from "node:fs";
|
|
63
|
+
|
|
64
|
+
const result = await client.convert({
|
|
65
|
+
filePath: "./input.docx",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (result.kind === "buffer") {
|
|
69
|
+
fs.writeFileSync("./output.pdf", result.buffer);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Streaming output
|
|
76
|
+
|
|
77
|
+
For advanced use-cases, you can receive a Web ReadableStream.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const result = await client.convert({
|
|
81
|
+
filePath: "./input.docx",
|
|
82
|
+
asWebStream: true,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (result.kind === "stream") {
|
|
86
|
+
// pipe or process the stream
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Error handling
|
|
93
|
+
|
|
94
|
+
All errors are thrown as `Office2PDFError` with stable error codes.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { Office2PDFError } from "office2pdf";
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await client.convert({ filePath: "./input.docx" });
|
|
101
|
+
} catch (e) {
|
|
102
|
+
if (e instanceof Office2PDFError) {
|
|
103
|
+
console.error(e.code, e.message, e.requestId);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Common error codes include:
|
|
109
|
+
|
|
110
|
+
- `UNAUTHORIZED`
|
|
111
|
+
- `INVALID_REQUEST`
|
|
112
|
+
- `RATE_LIMITED`
|
|
113
|
+
- `QUOTA_EXCEEDED`
|
|
114
|
+
- `TIMEOUT`
|
|
115
|
+
- `SERVER_ERROR`
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Notes
|
|
120
|
+
|
|
121
|
+
- For large files, prefer `downloadToPath` or `asWebStream`
|
|
122
|
+
- Automatic retries are applied for retryable errors (429, 5xx, network issues)
|
|
123
|
+
- Memory usage is kept stable for production workloads
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
Office2PDF: () => Office2PDF,
|
|
34
|
+
Office2PDFError: () => Office2PDFError
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/client.ts
|
|
39
|
+
var import_undici = require("undici");
|
|
40
|
+
var import_formdata_node = require("formdata-node");
|
|
41
|
+
var import_file_from_path = require("formdata-node/file-from-path");
|
|
42
|
+
var import_promises2 = require("fs/promises");
|
|
43
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
44
|
+
|
|
45
|
+
// src/errors.ts
|
|
46
|
+
var Office2PDFError = class extends Error {
|
|
47
|
+
code;
|
|
48
|
+
status;
|
|
49
|
+
requestId;
|
|
50
|
+
details;
|
|
51
|
+
constructor(args) {
|
|
52
|
+
super(args.message);
|
|
53
|
+
this.name = "Office2PDFError";
|
|
54
|
+
this.code = args.code;
|
|
55
|
+
this.status = args.status;
|
|
56
|
+
this.requestId = args.requestId;
|
|
57
|
+
this.details = args.details;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// src/utils.ts
|
|
62
|
+
var import_node_fs = require("fs");
|
|
63
|
+
var import_promises = require("stream/promises");
|
|
64
|
+
var import_node_stream = require("stream");
|
|
65
|
+
function sleep(ms) {
|
|
66
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
67
|
+
}
|
|
68
|
+
function isRetryAbleStatus(status) {
|
|
69
|
+
return status === 408 || status === 429 || status >= 500 && status <= 599;
|
|
70
|
+
}
|
|
71
|
+
function getBackoffMs(attempt) {
|
|
72
|
+
const base = 300 * Math.pow(2, attempt);
|
|
73
|
+
const jitter = Math.floor(Math.random() * 150);
|
|
74
|
+
return base + jitter;
|
|
75
|
+
}
|
|
76
|
+
async function streamToFile(webStream, outPath) {
|
|
77
|
+
const nodeReadable = import_node_stream.Readable.fromWeb(webStream);
|
|
78
|
+
const ws = (0, import_node_fs.createWriteStream)(outPath);
|
|
79
|
+
await (0, import_promises.pipeline)(nodeReadable, ws);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/client.ts
|
|
83
|
+
var DEFAULT_BASE_URL = "https://api.office2pdf.app";
|
|
84
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
85
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
86
|
+
function normalizeBaseUrl(baseUrl) {
|
|
87
|
+
const url = (baseUrl || DEFAULT_BASE_URL).trim();
|
|
88
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
89
|
+
}
|
|
90
|
+
function mapStatusToCode(status) {
|
|
91
|
+
const map = {
|
|
92
|
+
401: "UNAUTHORIZED",
|
|
93
|
+
403: "FORBIDDEN",
|
|
94
|
+
404: "NOT_FOUND",
|
|
95
|
+
429: "RATE_LIMITED",
|
|
96
|
+
413: "QUOTA_EXCEEDED"
|
|
97
|
+
};
|
|
98
|
+
if (map[status]) return map[status];
|
|
99
|
+
if (status >= 400 && status < 500) return "INVALID_REQUEST";
|
|
100
|
+
if (status >= 500) return "SERVER_ERROR";
|
|
101
|
+
return "UNKNOWN";
|
|
102
|
+
}
|
|
103
|
+
async function safeReadJson(res) {
|
|
104
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
105
|
+
if (!ct.includes("application/json")) return null;
|
|
106
|
+
try {
|
|
107
|
+
return await res.json();
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function getRequestId(res) {
|
|
113
|
+
return res.headers.get("x-request-id") ?? res.headers.get("cf-ray") ?? void 0;
|
|
114
|
+
}
|
|
115
|
+
function mergeAbortSignals(...signals) {
|
|
116
|
+
const active = signals.filter((s) => s != null);
|
|
117
|
+
if (active.length === 0) {
|
|
118
|
+
return { signal: void 0, cleanup: () => {
|
|
119
|
+
} };
|
|
120
|
+
}
|
|
121
|
+
if (active.length === 1) {
|
|
122
|
+
return { signal: active[0], cleanup: () => {
|
|
123
|
+
} };
|
|
124
|
+
}
|
|
125
|
+
const aborted = active.find((s) => s.aborted);
|
|
126
|
+
if (aborted) {
|
|
127
|
+
return { signal: aborted, cleanup: () => {
|
|
128
|
+
} };
|
|
129
|
+
}
|
|
130
|
+
const controller = new AbortController();
|
|
131
|
+
const onAbort = () => controller.abort();
|
|
132
|
+
active.forEach((s) => s.addEventListener("abort", onAbort));
|
|
133
|
+
return {
|
|
134
|
+
signal: controller.signal,
|
|
135
|
+
cleanup: () => {
|
|
136
|
+
active.forEach((s) => s.removeEventListener("abort", onAbort));
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function asBodyInit(body) {
|
|
141
|
+
return body;
|
|
142
|
+
}
|
|
143
|
+
var Office2PDF = class {
|
|
144
|
+
apiKey;
|
|
145
|
+
baseUrl;
|
|
146
|
+
timeoutMs;
|
|
147
|
+
userAgent;
|
|
148
|
+
maxRetries;
|
|
149
|
+
constructor(opts) {
|
|
150
|
+
if (!opts?.apiKey?.trim()) {
|
|
151
|
+
throw new Error("Office2PDF: apiKey is required");
|
|
152
|
+
}
|
|
153
|
+
this.apiKey = opts.apiKey.trim();
|
|
154
|
+
this.baseUrl = normalizeBaseUrl(opts.baseUrl);
|
|
155
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
156
|
+
this.userAgent = opts.userAgent ?? "office2pdf-node";
|
|
157
|
+
this.maxRetries = Math.max(0, opts.maxRetries ?? DEFAULT_MAX_RETRIES);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Convert an Office document to PDF.
|
|
161
|
+
*/
|
|
162
|
+
async convert(params) {
|
|
163
|
+
await this.validateParams(params);
|
|
164
|
+
const url = `${this.baseUrl}/api/pdf/preview`;
|
|
165
|
+
let lastError;
|
|
166
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
167
|
+
try {
|
|
168
|
+
return await this.sendConvertRequest(url, params);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
const normalized = this.normalizeError(err);
|
|
171
|
+
lastError = normalized;
|
|
172
|
+
if (attempt < this.maxRetries && this.isRetryAble(normalized)) {
|
|
173
|
+
await sleep(getBackoffMs(attempt));
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
throw normalized;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
throw lastError ?? new Office2PDFError({
|
|
180
|
+
message: "Unexpected conversion failure",
|
|
181
|
+
code: "UNKNOWN"
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/* ------------------------------ Internals ------------------------------ */
|
|
185
|
+
async validateParams(params) {
|
|
186
|
+
if (!params.filePath?.trim()) {
|
|
187
|
+
throw new Office2PDFError({
|
|
188
|
+
message: "filePath is required",
|
|
189
|
+
code: "INVALID_REQUEST"
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
await (0, import_promises2.access)(params.filePath, import_promises2.constants.R_OK);
|
|
194
|
+
} catch {
|
|
195
|
+
throw new Office2PDFError({
|
|
196
|
+
message: `File not found or not readable: ${params.filePath}`,
|
|
197
|
+
code: "INVALID_REQUEST"
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (params.asWebStream && params.downloadToPath) {
|
|
201
|
+
throw new Office2PDFError({
|
|
202
|
+
message: "Cannot use asWebStream with downloadToPath",
|
|
203
|
+
code: "INVALID_REQUEST"
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (params.downloadToPath) {
|
|
207
|
+
const dir = import_node_path.default.dirname(params.downloadToPath);
|
|
208
|
+
if (dir && dir !== ".") {
|
|
209
|
+
try {
|
|
210
|
+
await (0, import_promises2.access)(dir, import_promises2.constants.W_OK);
|
|
211
|
+
} catch {
|
|
212
|
+
throw new Office2PDFError({
|
|
213
|
+
message: `Download directory not writable: ${dir}`,
|
|
214
|
+
code: "INVALID_REQUEST"
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async sendConvertRequest(url, params) {
|
|
221
|
+
const form = await this.buildFormData(params);
|
|
222
|
+
const timeoutCtrl = new AbortController();
|
|
223
|
+
const timeoutId = setTimeout(() => timeoutCtrl.abort(), this.timeoutMs);
|
|
224
|
+
const merged = mergeAbortSignals(params.signal, timeoutCtrl.signal);
|
|
225
|
+
try {
|
|
226
|
+
const res = await (0, import_undici.fetch)(url, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
body: asBodyInit(form),
|
|
229
|
+
headers: {
|
|
230
|
+
"x-api-key": this.apiKey,
|
|
231
|
+
"User-Agent": this.userAgent
|
|
232
|
+
},
|
|
233
|
+
signal: merged.signal
|
|
234
|
+
});
|
|
235
|
+
return await this.handleResponse(res, params);
|
|
236
|
+
} finally {
|
|
237
|
+
clearTimeout(timeoutId);
|
|
238
|
+
merged.cleanup();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async buildFormData(params) {
|
|
242
|
+
const form = new import_formdata_node.FormData();
|
|
243
|
+
const file = await (0, import_file_from_path.fileFromPath)(params.filePath, params.fileName);
|
|
244
|
+
form.set("file", file);
|
|
245
|
+
form.set("output", params.output ?? "pdf");
|
|
246
|
+
if (params.password) {
|
|
247
|
+
form.set("password", params.password);
|
|
248
|
+
}
|
|
249
|
+
return form;
|
|
250
|
+
}
|
|
251
|
+
async handleResponse(res, params) {
|
|
252
|
+
const requestId = getRequestId(res);
|
|
253
|
+
if (!res.ok) {
|
|
254
|
+
const json = await safeReadJson(res);
|
|
255
|
+
const message = typeof json?.message === "string" ? json.message : typeof json?.error_description === "string" ? json.error_description : `Request failed with status ${res.status}`;
|
|
256
|
+
throw new Office2PDFError({
|
|
257
|
+
message,
|
|
258
|
+
code: mapStatusToCode(res.status),
|
|
259
|
+
status: res.status,
|
|
260
|
+
requestId,
|
|
261
|
+
details: json ?? void 0
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
const contentType = res.headers.get("content-type") ?? "application/pdf";
|
|
265
|
+
if (params.asWebStream) {
|
|
266
|
+
if (!res.body) {
|
|
267
|
+
throw new Office2PDFError({
|
|
268
|
+
message: "Empty response body",
|
|
269
|
+
code: "UNKNOWN",
|
|
270
|
+
requestId
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
kind: "stream",
|
|
275
|
+
stream: res.body,
|
|
276
|
+
contentType,
|
|
277
|
+
requestId
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (params.downloadToPath) {
|
|
281
|
+
if (!res.body) {
|
|
282
|
+
throw new Office2PDFError({
|
|
283
|
+
message: "Empty response body",
|
|
284
|
+
code: "UNKNOWN",
|
|
285
|
+
requestId
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
await streamToFile(res.body, params.downloadToPath);
|
|
289
|
+
return {
|
|
290
|
+
kind: "downloaded",
|
|
291
|
+
path: params.downloadToPath,
|
|
292
|
+
contentType,
|
|
293
|
+
requestId
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const buffer = Buffer.from(new Uint8Array(await res.arrayBuffer()));
|
|
297
|
+
return {
|
|
298
|
+
kind: "buffer",
|
|
299
|
+
buffer,
|
|
300
|
+
contentType,
|
|
301
|
+
requestId
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
normalizeError(err) {
|
|
305
|
+
if (err instanceof Office2PDFError) return err;
|
|
306
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
307
|
+
return new Office2PDFError({
|
|
308
|
+
message: "Request timed out",
|
|
309
|
+
code: "TIMEOUT"
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
if (err instanceof Error) {
|
|
313
|
+
return new Office2PDFError({
|
|
314
|
+
message: err.message,
|
|
315
|
+
code: "NETWORK_ERROR",
|
|
316
|
+
details: { name: err.name }
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return new Office2PDFError({
|
|
320
|
+
message: "Unknown error",
|
|
321
|
+
code: "UNKNOWN",
|
|
322
|
+
details: err
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
isRetryAble(error) {
|
|
326
|
+
if (error.code === "TIMEOUT" || error.code === "NETWORK_ERROR") {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
if (error.status && isRetryAbleStatus(error.status)) {
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
336
|
+
0 && (module.exports = {
|
|
337
|
+
Office2PDF,
|
|
338
|
+
Office2PDFError
|
|
339
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
type Office2PDFClientOptions = {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
userAgent?: string;
|
|
6
|
+
maxRetries?: number;
|
|
7
|
+
};
|
|
8
|
+
type ConvertParams = {
|
|
9
|
+
filePath: string;
|
|
10
|
+
fileName?: string;
|
|
11
|
+
output?: "pdf";
|
|
12
|
+
password?: string;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
downloadToPath?: string;
|
|
15
|
+
asWebStream?: boolean;
|
|
16
|
+
};
|
|
17
|
+
type ConvertResult = {
|
|
18
|
+
kind: "buffer";
|
|
19
|
+
buffer: Buffer;
|
|
20
|
+
contentType: string;
|
|
21
|
+
requestId?: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "downloaded";
|
|
24
|
+
path: string;
|
|
25
|
+
contentType: string;
|
|
26
|
+
requestId?: string;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "stream";
|
|
29
|
+
stream: ReadableStream<Uint8Array>;
|
|
30
|
+
contentType: string;
|
|
31
|
+
requestId?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Official Office2PDF Node.js SDK client.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const client = new Office2PDF({ apiKey: "your-api-key" });
|
|
40
|
+
*
|
|
41
|
+
* // Get as buffer
|
|
42
|
+
* const result = await client.convert({ filePath: "./document.docx" });
|
|
43
|
+
* if (result.kind === "buffer") {
|
|
44
|
+
* fs.writeFileSync("output.pdf", result.buffer);
|
|
45
|
+
* }
|
|
46
|
+
*
|
|
47
|
+
* // Download directly to file
|
|
48
|
+
* await client.convert({
|
|
49
|
+
* filePath: "./document.docx",
|
|
50
|
+
* downloadToPath: "./output.pdf"
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
* Converts Office documents (DOCX, XLSX, PPTX) to PDF.
|
|
54
|
+
* Authentication uses `x-api-key` header.
|
|
55
|
+
*/
|
|
56
|
+
declare class Office2PDF {
|
|
57
|
+
private readonly apiKey;
|
|
58
|
+
private readonly baseUrl;
|
|
59
|
+
private readonly timeoutMs;
|
|
60
|
+
private readonly userAgent;
|
|
61
|
+
private readonly maxRetries;
|
|
62
|
+
constructor(opts: Office2PDFClientOptions);
|
|
63
|
+
/**
|
|
64
|
+
* Convert an Office document to PDF.
|
|
65
|
+
*/
|
|
66
|
+
convert(params: ConvertParams): Promise<ConvertResult>;
|
|
67
|
+
private validateParams;
|
|
68
|
+
private sendConvertRequest;
|
|
69
|
+
private buildFormData;
|
|
70
|
+
private handleResponse;
|
|
71
|
+
private normalizeError;
|
|
72
|
+
private isRetryAble;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type Office2PDFErrorCode = "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "RATE_LIMITED" | "QUOTA_EXCEEDED" | "INVALID_REQUEST" | "SERVER_ERROR" | "NETWORK_ERROR" | "TIMEOUT" | "UNKNOWN";
|
|
76
|
+
declare class Office2PDFError extends Error {
|
|
77
|
+
readonly code: Office2PDFErrorCode;
|
|
78
|
+
readonly status?: number;
|
|
79
|
+
readonly requestId?: string;
|
|
80
|
+
readonly details?: unknown;
|
|
81
|
+
constructor(args: {
|
|
82
|
+
message: string;
|
|
83
|
+
code: Office2PDFErrorCode;
|
|
84
|
+
status?: number;
|
|
85
|
+
requestId?: string;
|
|
86
|
+
details?: unknown;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { type ConvertParams, type ConvertResult, Office2PDF, type Office2PDFClientOptions, Office2PDFError };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
type Office2PDFClientOptions = {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
userAgent?: string;
|
|
6
|
+
maxRetries?: number;
|
|
7
|
+
};
|
|
8
|
+
type ConvertParams = {
|
|
9
|
+
filePath: string;
|
|
10
|
+
fileName?: string;
|
|
11
|
+
output?: "pdf";
|
|
12
|
+
password?: string;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
downloadToPath?: string;
|
|
15
|
+
asWebStream?: boolean;
|
|
16
|
+
};
|
|
17
|
+
type ConvertResult = {
|
|
18
|
+
kind: "buffer";
|
|
19
|
+
buffer: Buffer;
|
|
20
|
+
contentType: string;
|
|
21
|
+
requestId?: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "downloaded";
|
|
24
|
+
path: string;
|
|
25
|
+
contentType: string;
|
|
26
|
+
requestId?: string;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "stream";
|
|
29
|
+
stream: ReadableStream<Uint8Array>;
|
|
30
|
+
contentType: string;
|
|
31
|
+
requestId?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Official Office2PDF Node.js SDK client.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const client = new Office2PDF({ apiKey: "your-api-key" });
|
|
40
|
+
*
|
|
41
|
+
* // Get as buffer
|
|
42
|
+
* const result = await client.convert({ filePath: "./document.docx" });
|
|
43
|
+
* if (result.kind === "buffer") {
|
|
44
|
+
* fs.writeFileSync("output.pdf", result.buffer);
|
|
45
|
+
* }
|
|
46
|
+
*
|
|
47
|
+
* // Download directly to file
|
|
48
|
+
* await client.convert({
|
|
49
|
+
* filePath: "./document.docx",
|
|
50
|
+
* downloadToPath: "./output.pdf"
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
* Converts Office documents (DOCX, XLSX, PPTX) to PDF.
|
|
54
|
+
* Authentication uses `x-api-key` header.
|
|
55
|
+
*/
|
|
56
|
+
declare class Office2PDF {
|
|
57
|
+
private readonly apiKey;
|
|
58
|
+
private readonly baseUrl;
|
|
59
|
+
private readonly timeoutMs;
|
|
60
|
+
private readonly userAgent;
|
|
61
|
+
private readonly maxRetries;
|
|
62
|
+
constructor(opts: Office2PDFClientOptions);
|
|
63
|
+
/**
|
|
64
|
+
* Convert an Office document to PDF.
|
|
65
|
+
*/
|
|
66
|
+
convert(params: ConvertParams): Promise<ConvertResult>;
|
|
67
|
+
private validateParams;
|
|
68
|
+
private sendConvertRequest;
|
|
69
|
+
private buildFormData;
|
|
70
|
+
private handleResponse;
|
|
71
|
+
private normalizeError;
|
|
72
|
+
private isRetryAble;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type Office2PDFErrorCode = "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "RATE_LIMITED" | "QUOTA_EXCEEDED" | "INVALID_REQUEST" | "SERVER_ERROR" | "NETWORK_ERROR" | "TIMEOUT" | "UNKNOWN";
|
|
76
|
+
declare class Office2PDFError extends Error {
|
|
77
|
+
readonly code: Office2PDFErrorCode;
|
|
78
|
+
readonly status?: number;
|
|
79
|
+
readonly requestId?: string;
|
|
80
|
+
readonly details?: unknown;
|
|
81
|
+
constructor(args: {
|
|
82
|
+
message: string;
|
|
83
|
+
code: Office2PDFErrorCode;
|
|
84
|
+
status?: number;
|
|
85
|
+
requestId?: string;
|
|
86
|
+
details?: unknown;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { type ConvertParams, type ConvertResult, Office2PDF, type Office2PDFClientOptions, Office2PDFError };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import { fetch } from "undici";
|
|
3
|
+
import { FormData } from "formdata-node";
|
|
4
|
+
import { fileFromPath } from "formdata-node/file-from-path";
|
|
5
|
+
import { access, constants } from "fs/promises";
|
|
6
|
+
import path from "path";
|
|
7
|
+
|
|
8
|
+
// src/errors.ts
|
|
9
|
+
var Office2PDFError = class extends Error {
|
|
10
|
+
code;
|
|
11
|
+
status;
|
|
12
|
+
requestId;
|
|
13
|
+
details;
|
|
14
|
+
constructor(args) {
|
|
15
|
+
super(args.message);
|
|
16
|
+
this.name = "Office2PDFError";
|
|
17
|
+
this.code = args.code;
|
|
18
|
+
this.status = args.status;
|
|
19
|
+
this.requestId = args.requestId;
|
|
20
|
+
this.details = args.details;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/utils.ts
|
|
25
|
+
import { createWriteStream } from "fs";
|
|
26
|
+
import { pipeline } from "stream/promises";
|
|
27
|
+
import { Readable } from "stream";
|
|
28
|
+
function sleep(ms) {
|
|
29
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
30
|
+
}
|
|
31
|
+
function isRetryAbleStatus(status) {
|
|
32
|
+
return status === 408 || status === 429 || status >= 500 && status <= 599;
|
|
33
|
+
}
|
|
34
|
+
function getBackoffMs(attempt) {
|
|
35
|
+
const base = 300 * Math.pow(2, attempt);
|
|
36
|
+
const jitter = Math.floor(Math.random() * 150);
|
|
37
|
+
return base + jitter;
|
|
38
|
+
}
|
|
39
|
+
async function streamToFile(webStream, outPath) {
|
|
40
|
+
const nodeReadable = Readable.fromWeb(webStream);
|
|
41
|
+
const ws = createWriteStream(outPath);
|
|
42
|
+
await pipeline(nodeReadable, ws);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/client.ts
|
|
46
|
+
var DEFAULT_BASE_URL = "https://api.office2pdf.app";
|
|
47
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
48
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
49
|
+
function normalizeBaseUrl(baseUrl) {
|
|
50
|
+
const url = (baseUrl || DEFAULT_BASE_URL).trim();
|
|
51
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
52
|
+
}
|
|
53
|
+
function mapStatusToCode(status) {
|
|
54
|
+
const map = {
|
|
55
|
+
401: "UNAUTHORIZED",
|
|
56
|
+
403: "FORBIDDEN",
|
|
57
|
+
404: "NOT_FOUND",
|
|
58
|
+
429: "RATE_LIMITED",
|
|
59
|
+
413: "QUOTA_EXCEEDED"
|
|
60
|
+
};
|
|
61
|
+
if (map[status]) return map[status];
|
|
62
|
+
if (status >= 400 && status < 500) return "INVALID_REQUEST";
|
|
63
|
+
if (status >= 500) return "SERVER_ERROR";
|
|
64
|
+
return "UNKNOWN";
|
|
65
|
+
}
|
|
66
|
+
async function safeReadJson(res) {
|
|
67
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
68
|
+
if (!ct.includes("application/json")) return null;
|
|
69
|
+
try {
|
|
70
|
+
return await res.json();
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getRequestId(res) {
|
|
76
|
+
return res.headers.get("x-request-id") ?? res.headers.get("cf-ray") ?? void 0;
|
|
77
|
+
}
|
|
78
|
+
function mergeAbortSignals(...signals) {
|
|
79
|
+
const active = signals.filter((s) => s != null);
|
|
80
|
+
if (active.length === 0) {
|
|
81
|
+
return { signal: void 0, cleanup: () => {
|
|
82
|
+
} };
|
|
83
|
+
}
|
|
84
|
+
if (active.length === 1) {
|
|
85
|
+
return { signal: active[0], cleanup: () => {
|
|
86
|
+
} };
|
|
87
|
+
}
|
|
88
|
+
const aborted = active.find((s) => s.aborted);
|
|
89
|
+
if (aborted) {
|
|
90
|
+
return { signal: aborted, cleanup: () => {
|
|
91
|
+
} };
|
|
92
|
+
}
|
|
93
|
+
const controller = new AbortController();
|
|
94
|
+
const onAbort = () => controller.abort();
|
|
95
|
+
active.forEach((s) => s.addEventListener("abort", onAbort));
|
|
96
|
+
return {
|
|
97
|
+
signal: controller.signal,
|
|
98
|
+
cleanup: () => {
|
|
99
|
+
active.forEach((s) => s.removeEventListener("abort", onAbort));
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function asBodyInit(body) {
|
|
104
|
+
return body;
|
|
105
|
+
}
|
|
106
|
+
var Office2PDF = class {
|
|
107
|
+
apiKey;
|
|
108
|
+
baseUrl;
|
|
109
|
+
timeoutMs;
|
|
110
|
+
userAgent;
|
|
111
|
+
maxRetries;
|
|
112
|
+
constructor(opts) {
|
|
113
|
+
if (!opts?.apiKey?.trim()) {
|
|
114
|
+
throw new Error("Office2PDF: apiKey is required");
|
|
115
|
+
}
|
|
116
|
+
this.apiKey = opts.apiKey.trim();
|
|
117
|
+
this.baseUrl = normalizeBaseUrl(opts.baseUrl);
|
|
118
|
+
this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
119
|
+
this.userAgent = opts.userAgent ?? "office2pdf-node";
|
|
120
|
+
this.maxRetries = Math.max(0, opts.maxRetries ?? DEFAULT_MAX_RETRIES);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Convert an Office document to PDF.
|
|
124
|
+
*/
|
|
125
|
+
async convert(params) {
|
|
126
|
+
await this.validateParams(params);
|
|
127
|
+
const url = `${this.baseUrl}/api/pdf/preview`;
|
|
128
|
+
let lastError;
|
|
129
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
130
|
+
try {
|
|
131
|
+
return await this.sendConvertRequest(url, params);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
const normalized = this.normalizeError(err);
|
|
134
|
+
lastError = normalized;
|
|
135
|
+
if (attempt < this.maxRetries && this.isRetryAble(normalized)) {
|
|
136
|
+
await sleep(getBackoffMs(attempt));
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
throw normalized;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
throw lastError ?? new Office2PDFError({
|
|
143
|
+
message: "Unexpected conversion failure",
|
|
144
|
+
code: "UNKNOWN"
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/* ------------------------------ Internals ------------------------------ */
|
|
148
|
+
async validateParams(params) {
|
|
149
|
+
if (!params.filePath?.trim()) {
|
|
150
|
+
throw new Office2PDFError({
|
|
151
|
+
message: "filePath is required",
|
|
152
|
+
code: "INVALID_REQUEST"
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
await access(params.filePath, constants.R_OK);
|
|
157
|
+
} catch {
|
|
158
|
+
throw new Office2PDFError({
|
|
159
|
+
message: `File not found or not readable: ${params.filePath}`,
|
|
160
|
+
code: "INVALID_REQUEST"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (params.asWebStream && params.downloadToPath) {
|
|
164
|
+
throw new Office2PDFError({
|
|
165
|
+
message: "Cannot use asWebStream with downloadToPath",
|
|
166
|
+
code: "INVALID_REQUEST"
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (params.downloadToPath) {
|
|
170
|
+
const dir = path.dirname(params.downloadToPath);
|
|
171
|
+
if (dir && dir !== ".") {
|
|
172
|
+
try {
|
|
173
|
+
await access(dir, constants.W_OK);
|
|
174
|
+
} catch {
|
|
175
|
+
throw new Office2PDFError({
|
|
176
|
+
message: `Download directory not writable: ${dir}`,
|
|
177
|
+
code: "INVALID_REQUEST"
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async sendConvertRequest(url, params) {
|
|
184
|
+
const form = await this.buildFormData(params);
|
|
185
|
+
const timeoutCtrl = new AbortController();
|
|
186
|
+
const timeoutId = setTimeout(() => timeoutCtrl.abort(), this.timeoutMs);
|
|
187
|
+
const merged = mergeAbortSignals(params.signal, timeoutCtrl.signal);
|
|
188
|
+
try {
|
|
189
|
+
const res = await fetch(url, {
|
|
190
|
+
method: "POST",
|
|
191
|
+
body: asBodyInit(form),
|
|
192
|
+
headers: {
|
|
193
|
+
"x-api-key": this.apiKey,
|
|
194
|
+
"User-Agent": this.userAgent
|
|
195
|
+
},
|
|
196
|
+
signal: merged.signal
|
|
197
|
+
});
|
|
198
|
+
return await this.handleResponse(res, params);
|
|
199
|
+
} finally {
|
|
200
|
+
clearTimeout(timeoutId);
|
|
201
|
+
merged.cleanup();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async buildFormData(params) {
|
|
205
|
+
const form = new FormData();
|
|
206
|
+
const file = await fileFromPath(params.filePath, params.fileName);
|
|
207
|
+
form.set("file", file);
|
|
208
|
+
form.set("output", params.output ?? "pdf");
|
|
209
|
+
if (params.password) {
|
|
210
|
+
form.set("password", params.password);
|
|
211
|
+
}
|
|
212
|
+
return form;
|
|
213
|
+
}
|
|
214
|
+
async handleResponse(res, params) {
|
|
215
|
+
const requestId = getRequestId(res);
|
|
216
|
+
if (!res.ok) {
|
|
217
|
+
const json = await safeReadJson(res);
|
|
218
|
+
const message = typeof json?.message === "string" ? json.message : typeof json?.error_description === "string" ? json.error_description : `Request failed with status ${res.status}`;
|
|
219
|
+
throw new Office2PDFError({
|
|
220
|
+
message,
|
|
221
|
+
code: mapStatusToCode(res.status),
|
|
222
|
+
status: res.status,
|
|
223
|
+
requestId,
|
|
224
|
+
details: json ?? void 0
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const contentType = res.headers.get("content-type") ?? "application/pdf";
|
|
228
|
+
if (params.asWebStream) {
|
|
229
|
+
if (!res.body) {
|
|
230
|
+
throw new Office2PDFError({
|
|
231
|
+
message: "Empty response body",
|
|
232
|
+
code: "UNKNOWN",
|
|
233
|
+
requestId
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
kind: "stream",
|
|
238
|
+
stream: res.body,
|
|
239
|
+
contentType,
|
|
240
|
+
requestId
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (params.downloadToPath) {
|
|
244
|
+
if (!res.body) {
|
|
245
|
+
throw new Office2PDFError({
|
|
246
|
+
message: "Empty response body",
|
|
247
|
+
code: "UNKNOWN",
|
|
248
|
+
requestId
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
await streamToFile(res.body, params.downloadToPath);
|
|
252
|
+
return {
|
|
253
|
+
kind: "downloaded",
|
|
254
|
+
path: params.downloadToPath,
|
|
255
|
+
contentType,
|
|
256
|
+
requestId
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const buffer = Buffer.from(new Uint8Array(await res.arrayBuffer()));
|
|
260
|
+
return {
|
|
261
|
+
kind: "buffer",
|
|
262
|
+
buffer,
|
|
263
|
+
contentType,
|
|
264
|
+
requestId
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
normalizeError(err) {
|
|
268
|
+
if (err instanceof Office2PDFError) return err;
|
|
269
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
270
|
+
return new Office2PDFError({
|
|
271
|
+
message: "Request timed out",
|
|
272
|
+
code: "TIMEOUT"
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (err instanceof Error) {
|
|
276
|
+
return new Office2PDFError({
|
|
277
|
+
message: err.message,
|
|
278
|
+
code: "NETWORK_ERROR",
|
|
279
|
+
details: { name: err.name }
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return new Office2PDFError({
|
|
283
|
+
message: "Unknown error",
|
|
284
|
+
code: "UNKNOWN",
|
|
285
|
+
details: err
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
isRetryAble(error) {
|
|
289
|
+
if (error.code === "TIMEOUT" || error.code === "NETWORK_ERROR") {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
if (error.status && isRetryAbleStatus(error.status)) {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
export {
|
|
299
|
+
Office2PDF,
|
|
300
|
+
Office2PDFError
|
|
301
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@office2pdf/node",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Official Office2PDF Node.js SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/politehq/office2pdf-sdks"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://office2pdf.app",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"office2pdf",
|
|
28
|
+
"pdf",
|
|
29
|
+
"docx",
|
|
30
|
+
"xlsx",
|
|
31
|
+
"pptx",
|
|
32
|
+
"convert",
|
|
33
|
+
"sdk",
|
|
34
|
+
"api"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
},
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
42
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
43
|
+
"test": "vitest run"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"formdata-node": "^6.0.3",
|
|
47
|
+
"undici": "^6.21.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^20.11.0",
|
|
51
|
+
"tsup": "^8.0.1",
|
|
52
|
+
"typescript": "^5.4.0",
|
|
53
|
+
"vitest": "^4.0.16"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"sideEffects": false
|
|
59
|
+
}
|