@printhive/api 0.1.0 → 0.3.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/CHANGELOG.md +23 -0
- package/README.md +153 -8
- package/dist/{chunk-TGPYH7CV.js → chunk-YZJ5H32X.js} +11137 -2458
- package/dist/{errors-LHFABGGG.js → errors-GNC6X3MK.js} +1 -1
- package/dist/index.cjs +11373 -2462
- package/dist/index.d.cts +2789 -150
- package/dist/index.d.ts +2789 -150
- package/dist/index.js +255 -12
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -3,10 +3,55 @@ import {
|
|
|
3
3
|
PrintHiveNetworkError,
|
|
4
4
|
apiError,
|
|
5
5
|
apiKey,
|
|
6
|
+
createRequest,
|
|
7
|
+
jobSnapshot,
|
|
6
8
|
model,
|
|
7
9
|
page,
|
|
10
|
+
preparation,
|
|
11
|
+
preparationDownload,
|
|
12
|
+
preparationInput,
|
|
13
|
+
printJob,
|
|
14
|
+
printer,
|
|
15
|
+
printers,
|
|
8
16
|
source
|
|
9
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-YZJ5H32X.js";
|
|
18
|
+
|
|
19
|
+
// src/filament-catalog.ts
|
|
20
|
+
var FilamentCatalog = class {
|
|
21
|
+
constructor(transport) {
|
|
22
|
+
this.transport = transport;
|
|
23
|
+
}
|
|
24
|
+
upsert(input) {
|
|
25
|
+
const snapshot = JSON.stringify(input);
|
|
26
|
+
return this.transport.request({
|
|
27
|
+
method: "POST",
|
|
28
|
+
path: "/v1/filament-catalog",
|
|
29
|
+
contentType: "application/json",
|
|
30
|
+
body: async () => snapshot,
|
|
31
|
+
idempotencyKey: encodeURIComponent(input.externalId),
|
|
32
|
+
replayable: true,
|
|
33
|
+
decode: async (response) => response.json()
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
list(input = {}) {
|
|
37
|
+
const query = new URLSearchParams();
|
|
38
|
+
for (const [key, value] of Object.entries(input))
|
|
39
|
+
if (value !== void 0) query.set(key, String(value));
|
|
40
|
+
return this.transport.request({
|
|
41
|
+
method: "GET",
|
|
42
|
+
path: "/v1/filament-catalog",
|
|
43
|
+
query,
|
|
44
|
+
decode: async (response) => response.json()
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
get(id) {
|
|
48
|
+
return this.transport.request({
|
|
49
|
+
method: "GET",
|
|
50
|
+
path: `/v1/filament-catalog/${encodeURIComponent(id)}`,
|
|
51
|
+
decode: async (response) => response.json()
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
};
|
|
10
55
|
|
|
11
56
|
// src/transport.ts
|
|
12
57
|
var MAX_RETRY_AFTER_MS = 6e4;
|
|
@@ -29,6 +74,14 @@ async function pause(ms, signal) {
|
|
|
29
74
|
signal?.addEventListener("abort", abort, { once: true });
|
|
30
75
|
});
|
|
31
76
|
}
|
|
77
|
+
function postHeaders(request) {
|
|
78
|
+
const headers = new Headers({ "idempotency-key": request.idempotencyKey });
|
|
79
|
+
if (request.contentType !== null)
|
|
80
|
+
headers.set("content-type", request.contentType ?? "model/3mf");
|
|
81
|
+
if (request.size !== void 0)
|
|
82
|
+
headers.set("content-length", String(request.size));
|
|
83
|
+
return headers;
|
|
84
|
+
}
|
|
32
85
|
var Transport = class {
|
|
33
86
|
options;
|
|
34
87
|
constructor(options) {
|
|
@@ -76,9 +129,8 @@ var Transport = class {
|
|
|
76
129
|
redirect: "error"
|
|
77
130
|
};
|
|
78
131
|
if (request.method === "POST") {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
headers.set("idempotency-key", request.idempotencyKey);
|
|
132
|
+
for (const [name, value] of postHeaders(request))
|
|
133
|
+
headers.set(name, value);
|
|
82
134
|
init.body = await request.body();
|
|
83
135
|
init.duplex = "half";
|
|
84
136
|
}
|
|
@@ -106,7 +158,7 @@ var Transport = class {
|
|
|
106
158
|
}
|
|
107
159
|
};
|
|
108
160
|
function retryDelay(failure, replayable, attempt, maxRetries) {
|
|
109
|
-
const retryable = failure instanceof PrintHiveApiError ? failure.status === 429 || failure.status === 503 : failure.reason === "network";
|
|
161
|
+
const retryable = failure instanceof PrintHiveApiError ? failure.code !== "preparation_disabled" && (failure.status === 429 || failure.status === 503) : failure.reason === "network";
|
|
110
162
|
if (!retryable || !replayable || attempt >= maxRetries) throw failure;
|
|
111
163
|
const retryAfterMs = failure instanceof PrintHiveApiError ? failure.retryAfterMs ?? 0 : 0;
|
|
112
164
|
if (retryAfterMs > MAX_RETRY_AFTER_MS) throw failure;
|
|
@@ -114,7 +166,14 @@ function retryDelay(failure, replayable, attempt, maxRetries) {
|
|
|
114
166
|
return Math.max(backoff, retryAfterMs);
|
|
115
167
|
}
|
|
116
168
|
|
|
117
|
-
// src/
|
|
169
|
+
// src/print-jobs.ts
|
|
170
|
+
var PrintJobCreateStoppedError = class extends Error {
|
|
171
|
+
constructor(request) {
|
|
172
|
+
super(`Print job creation ${request.status}`);
|
|
173
|
+
this.request = request;
|
|
174
|
+
this.name = "PrintJobCreateStoppedError";
|
|
175
|
+
}
|
|
176
|
+
};
|
|
118
177
|
async function decode(response, validate) {
|
|
119
178
|
const value = await response.json().catch((error) => {
|
|
120
179
|
if (error instanceof SyntaxError)
|
|
@@ -124,6 +183,110 @@ async function decode(response, validate) {
|
|
|
124
183
|
if (!validate(value)) throw new TypeError("Invalid Print Hive API response");
|
|
125
184
|
return value;
|
|
126
185
|
}
|
|
186
|
+
var PrintJobs = class {
|
|
187
|
+
constructor(transport) {
|
|
188
|
+
this.transport = transport;
|
|
189
|
+
}
|
|
190
|
+
async get(id, signal) {
|
|
191
|
+
if (!Number.isSafeInteger(id) || id < 1)
|
|
192
|
+
throw new RangeError("Print job id must be a positive safe integer");
|
|
193
|
+
const response = await this.transport.request({
|
|
194
|
+
method: "GET",
|
|
195
|
+
path: `/v1/print-jobs/${id}`,
|
|
196
|
+
signal,
|
|
197
|
+
decode: (response2) => decode(response2, jobSnapshot)
|
|
198
|
+
});
|
|
199
|
+
if (response.job.id !== id)
|
|
200
|
+
throw new TypeError("Invalid Print Hive API response");
|
|
201
|
+
return response.job;
|
|
202
|
+
}
|
|
203
|
+
async create(input) {
|
|
204
|
+
const { idempotencyKey: suppliedKey, ...bodyInput } = input;
|
|
205
|
+
const idempotencyKey = suppliedKey ?? crypto.randomUUID();
|
|
206
|
+
if ("file" in bodyInput) {
|
|
207
|
+
const { file, filename, ...metadata } = bodyInput;
|
|
208
|
+
const bytes = file instanceof Uint8Array ? new Uint8Array(file) : new Uint8Array(
|
|
209
|
+
await (await import("fs/promises")).readFile(file)
|
|
210
|
+
);
|
|
211
|
+
const response2 = await this.transport.request({
|
|
212
|
+
method: "POST",
|
|
213
|
+
path: "/v1/print-jobs/create",
|
|
214
|
+
idempotencyKey,
|
|
215
|
+
replayable: true,
|
|
216
|
+
contentType: null,
|
|
217
|
+
body: async () => {
|
|
218
|
+
const form = new FormData();
|
|
219
|
+
for (const [field, value] of Object.entries({
|
|
220
|
+
name: metadata.name,
|
|
221
|
+
printer_id: metadata.printer_id,
|
|
222
|
+
on_filament_mismatch: metadata.on_filament_mismatch,
|
|
223
|
+
externalId: metadata.externalId,
|
|
224
|
+
release: metadata.release
|
|
225
|
+
})) {
|
|
226
|
+
if (value !== void 0) form.set(field, value);
|
|
227
|
+
}
|
|
228
|
+
form.set("plate", String(metadata.plate));
|
|
229
|
+
form.set("filaments", JSON.stringify(metadata.filaments));
|
|
230
|
+
if (metadata.print_options !== void 0)
|
|
231
|
+
form.set("print_options", JSON.stringify(metadata.print_options));
|
|
232
|
+
form.set("file", new Blob([bytes]), filename);
|
|
233
|
+
return form;
|
|
234
|
+
},
|
|
235
|
+
decode: (response3) => decode(response3, createRequest)
|
|
236
|
+
});
|
|
237
|
+
return { ...response2, idempotencyKey };
|
|
238
|
+
}
|
|
239
|
+
const response = await this.transport.request({
|
|
240
|
+
method: "POST",
|
|
241
|
+
path: "/v1/print-jobs/create",
|
|
242
|
+
idempotencyKey,
|
|
243
|
+
replayable: true,
|
|
244
|
+
contentType: "application/json",
|
|
245
|
+
body: async () => JSON.stringify(bodyInput),
|
|
246
|
+
decode: (response2) => decode(response2, printJob)
|
|
247
|
+
});
|
|
248
|
+
return { ...response, idempotencyKey };
|
|
249
|
+
}
|
|
250
|
+
getCreateRequest(requestId, signal) {
|
|
251
|
+
return this.transport.request({
|
|
252
|
+
method: "GET",
|
|
253
|
+
path: `/v1/print-jobs/create-requests/${encodeURIComponent(requestId)}`,
|
|
254
|
+
signal,
|
|
255
|
+
decode: (response) => decode(response, createRequest)
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
async waitUntilCreated(requestId, options = {}) {
|
|
259
|
+
const interval = positive(options.intervalMs ?? 1e3, "intervalMs");
|
|
260
|
+
const deadline = AbortSignal.timeout(
|
|
261
|
+
positive(options.timeoutMs ?? 12e4, "timeoutMs")
|
|
262
|
+
);
|
|
263
|
+
const signal = options.signal ? AbortSignal.any([options.signal, deadline]) : deadline;
|
|
264
|
+
try {
|
|
265
|
+
for (; ; ) {
|
|
266
|
+
const request = await this.getCreateRequest(requestId, signal);
|
|
267
|
+
if (request.status === "created") return request;
|
|
268
|
+
if (request.status !== "preparing")
|
|
269
|
+
throw new PrintJobCreateStoppedError(request);
|
|
270
|
+
await pause(interval, signal);
|
|
271
|
+
}
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (deadline.aborted && !options.signal?.aborted)
|
|
274
|
+
throw new PrintHiveNetworkError("timeout");
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/index.ts
|
|
281
|
+
async function decode2(response, validate) {
|
|
282
|
+
const value = await response.json().catch((error) => {
|
|
283
|
+
if (error instanceof SyntaxError)
|
|
284
|
+
throw new TypeError("Invalid Print Hive API response");
|
|
285
|
+
throw error;
|
|
286
|
+
});
|
|
287
|
+
if (!validate(value)) throw new TypeError("Invalid Print Hive API response");
|
|
288
|
+
return value;
|
|
289
|
+
}
|
|
127
290
|
function isPath(file) {
|
|
128
291
|
return !(file instanceof Uint8Array) && !(file instanceof ReadableStream);
|
|
129
292
|
}
|
|
@@ -131,14 +294,93 @@ var prefix = "/v1/partner-production";
|
|
|
131
294
|
var PrintHive = class {
|
|
132
295
|
partnerSources;
|
|
133
296
|
models;
|
|
297
|
+
printers;
|
|
298
|
+
printJobs;
|
|
299
|
+
filamentCatalog;
|
|
300
|
+
preparations;
|
|
134
301
|
constructor(options) {
|
|
135
302
|
const transport = new Transport(options);
|
|
303
|
+
this.printJobs = new PrintJobs(transport);
|
|
304
|
+
this.filamentCatalog = new FilamentCatalog(transport);
|
|
136
305
|
const get = (sourceId, signal) => transport.request({
|
|
137
|
-
decode: (response) =>
|
|
306
|
+
decode: (response) => decode2(response, source),
|
|
138
307
|
method: "GET",
|
|
139
308
|
path: `${prefix}/uploads/${encodeURIComponent(sourceId)}`,
|
|
140
309
|
signal
|
|
141
310
|
});
|
|
311
|
+
const getPreparation = (id, signal) => transport.request({
|
|
312
|
+
decode: (response) => decode2(response, preparation),
|
|
313
|
+
method: "GET",
|
|
314
|
+
path: `${prefix}/preparations/${encodeURIComponent(id)}`,
|
|
315
|
+
signal
|
|
316
|
+
});
|
|
317
|
+
this.preparations = {
|
|
318
|
+
get: getPreparation,
|
|
319
|
+
async create(input) {
|
|
320
|
+
const idempotencyKey = input.idempotencyKey ?? crypto.randomUUID();
|
|
321
|
+
const snapshot = JSON.stringify({ items: input.items });
|
|
322
|
+
if (!preparationInput(JSON.parse(snapshot)))
|
|
323
|
+
throw new TypeError("Invalid preparation input");
|
|
324
|
+
const preparation2 = await transport.request({
|
|
325
|
+
decode: (response) => decode2(response, preparation),
|
|
326
|
+
method: "POST",
|
|
327
|
+
path: `${prefix}/preparations`,
|
|
328
|
+
contentType: "application/json",
|
|
329
|
+
body: async () => snapshot,
|
|
330
|
+
size: new TextEncoder().encode(snapshot).byteLength,
|
|
331
|
+
idempotencyKey,
|
|
332
|
+
replayable: true,
|
|
333
|
+
signal: input.signal
|
|
334
|
+
});
|
|
335
|
+
return { ...preparation2, idempotencyKey };
|
|
336
|
+
},
|
|
337
|
+
async waitUntilSettled(id, options2 = {}) {
|
|
338
|
+
const interval = positive(options2.intervalMs ?? 1e3, "intervalMs");
|
|
339
|
+
const deadline = AbortSignal.timeout(
|
|
340
|
+
positive(options2.timeoutMs ?? 12e4, "timeoutMs")
|
|
341
|
+
);
|
|
342
|
+
const signal = options2.signal ? AbortSignal.any([options2.signal, deadline]) : deadline;
|
|
343
|
+
try {
|
|
344
|
+
for (; ; ) {
|
|
345
|
+
const preparation2 = await getPreparation(id, signal);
|
|
346
|
+
const status = preparation2.status;
|
|
347
|
+
if (status === "ready" || status === "held" || status === "failed")
|
|
348
|
+
return { ...preparation2, status };
|
|
349
|
+
await pause(interval, signal);
|
|
350
|
+
}
|
|
351
|
+
} catch (error) {
|
|
352
|
+
if (deadline.aborted && !options2.signal?.aborted) {
|
|
353
|
+
const { PrintHiveNetworkError: PrintHiveNetworkError2 } = await import("./errors-GNC6X3MK.js");
|
|
354
|
+
throw new PrintHiveNetworkError2("timeout");
|
|
355
|
+
}
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
downloadInfo: (id, plateId, signal) => transport.request({
|
|
360
|
+
decode: (response) => decode2(response, preparationDownload),
|
|
361
|
+
method: "GET",
|
|
362
|
+
path: `${prefix}/preparations/${encodeURIComponent(id)}/plates/${encodeURIComponent(plateId)}/download`,
|
|
363
|
+
signal
|
|
364
|
+
})
|
|
365
|
+
};
|
|
366
|
+
this.printers = {
|
|
367
|
+
list: (input = {}) => {
|
|
368
|
+
const query = new URLSearchParams();
|
|
369
|
+
if (input.limit !== void 0) query.set("limit", String(input.limit));
|
|
370
|
+
if (input.cursor !== void 0) query.set("cursor", input.cursor);
|
|
371
|
+
return transport.request({
|
|
372
|
+
decode: (response) => decode2(response, printers),
|
|
373
|
+
method: "GET",
|
|
374
|
+
path: "/v1/printers",
|
|
375
|
+
query
|
|
376
|
+
});
|
|
377
|
+
},
|
|
378
|
+
get: (id) => transport.request({
|
|
379
|
+
decode: (response) => decode2(response, printer),
|
|
380
|
+
method: "GET",
|
|
381
|
+
path: `/v1/printers/${encodeURIComponent(id)}`
|
|
382
|
+
})
|
|
383
|
+
};
|
|
142
384
|
this.partnerSources = {
|
|
143
385
|
get,
|
|
144
386
|
async upload(input) {
|
|
@@ -172,7 +414,7 @@ var PrintHive = class {
|
|
|
172
414
|
if (!Number.isSafeInteger(size) || size < 0)
|
|
173
415
|
throw new RangeError("size must be a nonnegative safe integer");
|
|
174
416
|
const source2 = await transport.request({
|
|
175
|
-
decode: (response) =>
|
|
417
|
+
decode: (response) => decode2(response, source),
|
|
176
418
|
method: "POST",
|
|
177
419
|
path: `${prefix}/uploads`,
|
|
178
420
|
query,
|
|
@@ -198,7 +440,7 @@ var PrintHive = class {
|
|
|
198
440
|
}
|
|
199
441
|
} catch (error) {
|
|
200
442
|
if (deadline.aborted && !options2.signal?.aborted) {
|
|
201
|
-
const { PrintHiveNetworkError: PrintHiveNetworkError2 } = await import("./errors-
|
|
443
|
+
const { PrintHiveNetworkError: PrintHiveNetworkError2 } = await import("./errors-GNC6X3MK.js");
|
|
202
444
|
throw new PrintHiveNetworkError2("timeout");
|
|
203
445
|
}
|
|
204
446
|
throw error;
|
|
@@ -207,7 +449,7 @@ var PrintHive = class {
|
|
|
207
449
|
};
|
|
208
450
|
this.models = {
|
|
209
451
|
findByExternalId: (externalId) => transport.request({
|
|
210
|
-
decode: (response) =>
|
|
452
|
+
decode: (response) => decode2(response, model),
|
|
211
453
|
method: "GET",
|
|
212
454
|
path: `${prefix}/models`,
|
|
213
455
|
query: new URLSearchParams({ externalId })
|
|
@@ -218,7 +460,7 @@ var PrintHive = class {
|
|
|
218
460
|
const query = new URLSearchParams();
|
|
219
461
|
if (cursor !== null) query.set("cursor", cursor);
|
|
220
462
|
const page2 = await transport.request({
|
|
221
|
-
decode: (response) =>
|
|
463
|
+
decode: (response) => decode2(response, page),
|
|
222
464
|
method: "GET",
|
|
223
465
|
path: `${prefix}/models/${encodeURIComponent(modelId)}/files`,
|
|
224
466
|
query
|
|
@@ -233,5 +475,6 @@ var PrintHive = class {
|
|
|
233
475
|
export {
|
|
234
476
|
PrintHive,
|
|
235
477
|
PrintHiveApiError,
|
|
236
|
-
PrintHiveNetworkError
|
|
478
|
+
PrintHiveNetworkError,
|
|
479
|
+
PrintJobCreateStoppedError
|
|
237
480
|
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@printhive/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "Print Hive
|
|
6
|
+
"description": "Print Hive REST API client",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/PrintHive/hive.git",
|
|
10
|
+
"directory": "packages/api-client"
|
|
11
|
+
},
|
|
7
12
|
"main": "./dist/index.cjs",
|
|
8
13
|
"module": "./dist/index.js",
|
|
9
14
|
"types": "./dist/index.d.ts",
|