@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/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-TGPYH7CV.js";
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
- headers.set("content-type", "model/3mf");
80
- headers.set("content-length", String(request.size));
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/index.ts
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) => decode(response, source),
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) => decode(response, source),
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-LHFABGGG.js");
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) => decode(response, model),
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) => decode(response, page),
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.1.0",
3
+ "version": "0.3.2",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "Print Hive partner-production REST API client",
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",