@printhive/api 0.2.0 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ - Add typed `printers.search` and `filaments.search`, with bounded integer pagination and catalog `filamentId` values.
6
+ - Reuse the web palette printer matcher and the filament picker ranking through narrow partner routes.
7
+ - Add `acceptedByCreate` using the same catalog lookup and admission predicate as print-job creation. Global, unsynced, and invalid-color rows remain searchable with the flag false.
8
+ - Require Read scope on the existing `/v1/search` and `/v1/materials/command-search` API-key routes. Empty-scope keys receive 403.
9
+
10
+ ## 0.3.2
11
+
12
+ - Add `printJobs.get` for live job polling after creation, including the API's waiting-reason snapshot.
13
+
14
+ ## 0.3.1
15
+
16
+ - Add `cause` to generated filament waiting reasons and export `FilamentWaitingReason`. Reasons include only unmet source slots at creation and claim recheck.
17
+
18
+ ## 0.3.0
19
+
20
+ - Add partner catalog upsert, filtered list and read methods; `preparation_disabled` is not retried as a transient 503.
21
+ - Generate preparation contracts and validators from the current API routes.
22
+ - Add typed preparation creation, reads, total-deadline polling and signed-download metadata.
23
+ - Preserve idempotency keys, immutable retry bodies and typed target refusal reasons.
24
+ - Migrate the Printie simulator from its local preparation wrapper to the SDK.
25
+
3
26
  ## 0.2.0
4
27
 
5
28
  - Add `printJobs.create` for multipart project preparation and existing JSON artifact creation.
package/README.md CHANGED
@@ -4,6 +4,8 @@ A TypeScript client for Print Hive's REST API. Version 0.2.0 has zero runtime de
4
4
 
5
5
  Version 0.2.0 adds public single-plate print creation and creation-request polling alongside customer printer reads, partner source uploads, and model reads.
6
6
 
7
+ Preparation support in this source tree is unreleased. See the [changelog](CHANGELOG.md) for released capabilities and the [release procedure](#release) for publishing. Order methods (#4631) remain outside this client.
8
+
7
9
  Partner API keys belong on your server. Never bundle a key into a browser, mobile app, or other distributed client. Printie's browser calls Printie's backend; that backend calls Print Hive.
8
10
 
9
11
  ## Create a print job
@@ -26,11 +28,31 @@ const created = await hive.printJobs.waitUntilCreated(request.requestId);
26
28
  console.log(created.job);
27
29
  ```
28
30
 
29
- `printJobs.create` sends multipart to `/v1/print-jobs/create` for a file path or `Uint8Array` plus `filename`. `plate` and typed `filaments` are required; `targetSlot` is optional. Other inputs include `printer_id`, `name`, `externalId`, `release`, `on_filament_mismatch`, and existing `print_options`. This method reads file paths into memory, copies byte inputs, and can retry the multipart body safely. It returns the server's `202` request fields plus `idempotencyKey`. Persist your own key before calling when retries must survive process restarts. Changed metadata or bytes with the same key conflict.
31
+ `printJobs.create` sends multipart to `/v1/print-jobs/create` for a file path or `Uint8Array` plus `filename`. `plate` and typed `filaments` are required; `targetSlot` is optional. Other inputs include `printer_id`, `name`, `externalId`, `release`, `acceptEngineSettingUpdates`, `on_filament_mismatch`, and existing `print_options`. This method reads file paths into memory, copies byte inputs, and can retry the multipart body safely. It returns the server's `202` request fields plus `idempotencyKey`. Persist your own key before calling when retries must survive process restarts. Changed metadata or bytes with the same key conflict.
30
32
 
31
33
  For an existing artifact, `printJobs.create({ name, model_file_id, ... })` sends JSON and returns the existing `201` response with `job` and eligibility fields plus `idempotencyKey`. The client generates an idempotency key once per call if omitted.
32
34
 
33
- `printJobs.getCreateRequest(requestId)` reads the current receipt. `waitUntilCreated(requestId, { intervalMs?, timeoutMs?, signal? })` polls while `preparing`, defaults to a 120-second total deadline, and returns when `created`, including a retained receipt with `job: null` after the job was deleted. `PrintJobCreateStoppedError.request` exposes a `held`, `failed`, or `cancelled` receipt, including its required actions or failure reasons. It does not authorize a held request automatically. Use the HTTP release/cancel operations described in the [integration reference](../../docs/integrations/printie/planned-production.md) when needed.
35
+ `printJobs.getCreateRequest(requestId)` reads the current receipt. `waitUntilCreated(requestId, { intervalMs?, timeoutMs?, signal? })` polls while `preparing`, defaults to a 120-second total deadline, and returns when `created`, including a retained receipt with `job: null` after the job was deleted. `PrintJobCreateStoppedError.request` exposes a `held`, `failed`, or `cancelled` receipt, including its required actions or failure reasons. It does not authorize a held request automatically.
36
+
37
+ ## Source preparation opt-ins
38
+
39
+ The opt-ins grant separate permissions. `acceptEngineSettingUpdates` permits eligible stock-setting updates; it does not permit an unqualified printer conversion. The separate `allowUnqualifiedConversion` control is being added in [#5083](https://github.com/PrintHive/hive/pull/5083) and is unavailable until that PR merges. Its conversion consent does not approve customised engine-setting changes. When both controls are available, a request can enable both; each still applies its own checks.
40
+
41
+ `acceptEngineSettingUpdates` has OpenAPI `default: false` for JSON and multipart creates. For source preparation, `true` accepts engine changes only to settings absent from the source's `different_settings_to_system` customisation list and backed by nonempty `inherits_group` entries for their presets. Missing diff metadata, a missing diff entry, or missing or empty preset inheritance makes the setting customised and it holds. An empty diff entry counts as stock only with inheritance evidence. Empty source setting values do not exempt customised settings. Accepted changes appear in the prepared plate's `lineage.acceptedEngineSettingChanges`.
42
+
43
+ An engine review hold includes `changes`, `sourceRelease`, and `engineRelease`, alongside the existing `detail`. Each change has `key`, `sourceValue`, `engineValue`, and `slots`. Array values identify changed slots using one-based source slot numbers. Scalar settings have an empty `slots` array. A setting removed by the engine has `engineValue: null`.
44
+
45
+ Call `printJobs.approveReview(requestId, { changes, sourceRelease, engineRelease })` with the reported values to re-slice. This requires `control` scope. It returns `{ approved: true }` with HTTP 202. Repeating the same approval returns 202 while that submitted review still covers the current pending or ready slice, without starting another slice. This includes Cloud Slice retries that change the slice ID while retaining the plate's approval. A stale or mismatched review returns HTTP 409 `review_conflict`. Approval accepts those changes for those releases, including customised settings. The create-time stock opt-in continues to accept eligible stock changes after approval. Other changes hold again. Poll the receipt again after approval.
46
+
47
+ For `release: "manual"` requests held by an engine review or another preparation reason, `/release` now returns HTTP 409 `manual_release_not_available` instead of 202. It keeps the holds and does not authorize manual release. Resolve the preparation holds first, then call `/release` when the only hold is `manual_release_required`.
48
+
49
+ Use the HTTP release/cancel operations described in the [integration reference](../../docs/integrations/printie/planned-production.md) when needed.
50
+
51
+ ## Observe created jobs
52
+
53
+ `printJobs.get(jobId, signal?)` reads `GET /v1/print-jobs/{id}` and returns the inner job snapshot. It requires a positive safe-integer ID. After a create receipt reaches `created` with a non-null job, read that job separately to observe its current status and `print_options.filament_waiting_reasons`. A created job may still wait for materials or printer readiness. Keep polling the live job rather than treating its creation receipt as a current material assessment. A deleted or inaccessible job returns the API's error; the retained create receipt does not recreate it.
54
+
55
+ The sample and simulator upgrade audit is [SDK upgrade from npm 0.2.0](../../tools/printie-simulator/SDK-UPGRADE.md). The local 0.3.2 build includes the live job read and the merged #4945 waiting-reason types. It is not an npm release. Reasons list only unmet filaments; the optional `cause` remains advisory because a material-type mismatch can still report `color_distance`.
34
56
 
35
57
  ## Upload a Printie design
36
58
 
@@ -79,19 +101,67 @@ for await (const source of hive.models.listSourceFiles(model.modelId)) {
79
101
  }
80
102
  ```
81
103
 
104
+ ## Prepare selected plates
105
+
106
+ Preparation requires `production:upload` for creation and `production:read` for reads and downloads, plus the organization's preparation feature flag.
107
+
108
+ ```ts
109
+ const preparation = await hive.preparations.create({
110
+ idempotencyKey: "printie:order-1234",
111
+ items: [
112
+ {
113
+ sourceVersionId: source.sourceVersionId,
114
+ plates: [2],
115
+ quantity: 3,
116
+ materialMappings: [{ sourceSlot: 1, material: "PETG", color: "#1F6FEB" }],
117
+ target: { printerId: "<qualified printer UUID>" },
118
+ },
119
+ ],
120
+ });
121
+ const outcome = await hive.preparations.waitUntilSettled(
122
+ preparation.preparationId,
123
+ {
124
+ timeoutMs: 120_000,
125
+ intervalMs: 1000,
126
+ }
127
+ );
128
+ for (const item of outcome.items) {
129
+ for (const plate of item.preparedPlates) {
130
+ if (plate.status === "ready") {
131
+ const info = await hive.preparations.downloadInfo(
132
+ outcome.preparationId,
133
+ plate.plateId
134
+ );
135
+ // info contains url, expiresAt, filename, bytes and sha256.
136
+ // Fetch the signed URL without sending the Hive API key.
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ Creation accepts at most 20 items. Generated request validation rejects larger arrays locally with `TypeError` before sending HTTP. The API continues to return `422 request_too_large` with limit metadata for oversized raw HTTP requests.
143
+
144
+ Each item names either `sourceVersionId` or `externalId`. Creation returns the preparation plus the supplied or generated `idempotencyKey`, just like upload. Persist a caller-supplied key before sending when recovery must survive process restarts. A replay returns the original preparation identity. Request JSON is snapshotted before sending and stays unchanged across retries.
145
+
146
+ `preparations.get(id, signal?)` returns the full preparation. `waitUntilSettled` resolves on `ready`, `held`, or `failed`. Holds and failures remain in the returned items and plates. Its total deadline aborts active reads, retry delays, and poll delays. A timeout leaves the creation outcome intact; resume reads using the preparation ID instead of creating replacement work. `downloadInfo(id, plateId, signal?)` retains all signed-download metadata without fetching the artifact.
147
+
148
+ `materialMappings[].sourceSlot` is one-based. Ambiguous palettes require explicit source-version or SHA-bound bindings; color order alone cannot establish source slots. Pin `sourceVersionId` after validating a binding. `target.printerId` names the exact qualified target. Preparation creates no print job and never authorizes printer motion.
149
+
150
+ Public preparation types, including `PartnerPreparationItemInput`, `PartnerMaterialMapping`, `PartnerPreparedPlate`, `PartnerPreparationDownload`, and `PartnerPreparationTargetReason`, derive from the current API export.
151
+
82
152
  ## Handle failures and retries
83
153
 
84
- `PrintHiveApiError` exposes HTTP `status`, the response's `error` as `code`, optional `limit` with `name`, `max`, and `observed`, `retryAfterMs`, and `requestId`. Its optional `body` exposes the validated error envelope, preserving additional fields as JSON values for callers to narrow. Invalid envelopes leave `body` undefined. Error messages omit response bodies and credentials. `PrintHiveNetworkError.reason` distinguishes `network`, `timeout`, and `aborted` failures.
154
+ `PrintHiveApiError` exposes HTTP `status`, the response's `error` as `code`, optional `limit` with `name`, `max`, and `observed`, `retryAfterMs`, and `requestId`. For `target_not_qualified`, `reasons` exposes validated, typed printer qualification reasons. Its optional `body` exposes the validated error envelope, preserving additional fields as JSON values for callers to narrow. Invalid envelopes leave `body` undefined. Error messages omit response bodies and credentials. `PrintHiveNetworkError.reason` distinguishes `network`, `timeout`, and `aborted` failures.
85
155
 
86
156
  The API can answer `411 content_length_required`, `413 upload_too_large`, `429 upload_capacity_reached`, `409 external_id_conflict`, or `409 idempotency_conflict`. Inspection failures resolve through the source's `failed` status and include their own error and optional restriction limit.
87
157
 
88
- Only 429, 503, and network failures retry. GET requests are idempotent. Upload retries retain their idempotency key and require replayable bytes or a path. One-shot streams never retry. Timeouts and caller aborts never retry. Retries use exponential backoff with jitter and wait at least `Retry-After`, supporting both seconds and HTTP dates. If `Retry-After` exceeds 60 seconds, the client throws the API error with `retryAfterMs` so the caller can schedule the retry. It never retries before that delay. `maxRetries: 0` disables retries. Request timeouts apply per attempt; polling also has a total deadline.
158
+ Only 429, 503, and network failures retry. Permanent `preparation_disabled` never retries, including HTTP 503. GET requests are idempotent. Preparation JSON and upload retries retain their idempotency key and require replayable bytes or a path. One-shot streams never retry. Timeouts and caller aborts never retry. Retries use exponential backoff with jitter and wait at least `Retry-After`, supporting both seconds and HTTP dates. If `Retry-After` exceeds 60 seconds, the client throws the API error with `retryAfterMs` so the caller can schedule the retry. It never retries before that delay. `maxRetries: 0` disables retries. Request timeouts apply per attempt; polling also has a total deadline.
89
159
 
90
160
  ## Coverage and contract maintenance
91
161
 
92
- Version 0.2.0 covers creation and request polling under `/v1/print-jobs/create`, customer printer list and detail reads at `/v1/printers` and `/v1/printers/{id}`, plus uploads, source reads and polling, external-id model lookup, and source-file listing under `/v1/partner-production/`. It offers no generic request method for other routes.
162
+ The current source covers creation and request polling under `/v1/print-jobs/create`, partner printer and filament search at `/v1/printers/search` and `/v1/filaments/search`, customer printer list and detail reads at `/v1/printers` and `/v1/printers/{id}`, plus preparation creation, reads, polling and download metadata, uploads, source reads and polling, external-id model lookup, and source-file listing under `/v1/partner-production/`. It offers no generic request method for other routes.
93
163
 
94
- `apps/api/scripts/export-openapi.ts` builds a schema-only Fastify app from the real printer, file-compatibility, print-job, and partner route plugins, without listening, worker startup, database calls, or queue startup. It uses stub environment values. This intentionally exports authenticated partner routes directly. The Swagger UI's public-operation filter would remove them, and generic error enrichment would lose partner restriction details.
164
+ `apps/api/scripts/export-openapi.ts` builds a schema-only Fastify app from the real partner search, printer, file-compatibility, print-job, and partner route plugins, without listening, worker startup, database calls, or queue startup. It uses stub environment values. This intentionally exports authenticated partner routes directly. The Swagger UI's public-operation filter would remove them, and generic error enrichment would lose partner restriction details.
95
165
 
96
166
  `bun run generate` filters that export by the prefix list in `scripts/contracts.mjs`, commits `openapi.json`, and generates TypeScript types and standalone response validators. AJV compiles the validators during development; the build bundles their small runtime helpers. `openapi-typescript` is a development dependency because it generates OpenAPI 3 union types without adding runtime dependencies. `bun run check:drift` reexports and compares the snapshot. Types and standalone validators regenerate in ignored `.temp` build output on every check. `bun run test` builds the distribution and runs its HTTP tests against `dist`, including in the release workflow. `api-client-check.yml` also runs when API or client sources change, so drift checks do not depend on Turbo selecting the client as affected.
97
167
 
@@ -132,3 +202,136 @@ never contact a printer.
132
202
 
133
203
  `readiness.ready` reports the stored ready flag. `readiness.bedClear` reports
134
204
  the flag only when its source is `bed_occupancy_verified`; otherwise it is null.
205
+
206
+ ## Sync your filament catalog
207
+
208
+ Use a Control API key to sync each partner record. Repeating an external id
209
+ updates the same organization-owned material, including concurrent retries.
210
+ The external id namespace belongs to the organization, so coordinate ids across
211
+ integrations. Reads require Read scope. Hive trims surrounding whitespace from `externalId`
212
+ and `sku` on writes and list filters; neither field can be blank.
213
+
214
+ ```ts
215
+ const filament = await hive.filamentCatalog.upsert({
216
+ externalId: "printie-pla-blue",
217
+ brand: "Printie",
218
+ material: "PLA",
219
+ colorName: "Blue",
220
+ colorHex: "#0000FF",
221
+ sku: "PLA-BLUE",
222
+ replacementFilamentId: null,
223
+ });
224
+ console.log(filament.id, filament.sku);
225
+ const page = await hive.filamentCatalog.list({ sku: "PLA-BLUE", limit: 100 });
226
+ const saved = await hive.filamentCatalog.get(filament.id);
227
+ ```
228
+
229
+ `POST /v1/filament-catalog` accepts the fields above and returns 200 with the
230
+ material row. `hex` is an alias for `colorHex`; both must agree when supplied.
231
+ `replacementFilamentId` is retained as partner metadata, not resolved as a Hive id.
232
+ `GET /v1/filament-catalog` filters by exact `externalId` and `sku`, with `limit`
233
+ (1–200, default 100) and `offset` (default 0). Results are ordered by Hive id.
234
+ It returns `{ materials, total }`. `GET /v1/filament-catalog/:id` returns one row.
235
+ All operations enforce organization ownership. Returned rows include `id`, `sku`,
236
+ `material_type`, `color`, and partner metadata.
237
+
238
+ Errors include 400 `invalid_catalog_input`, 400 `conflicting_color_hex`, 403 `API_KEY_SCOPE_REQUIRED`,
239
+ 404 `catalog_filament_not_found`, 409 `catalog_conflict` for uniqueness conflicts,
240
+ and 500 `catalog_store_failed`. Invalid bodies return the API validation error.
241
+ Pass the returned `id` as `filaments[].filamentId` in JSON or multipart print-job
242
+ creation. A slot can contain only `{ sourceSlot, filamentId }`, plus an optional
243
+ `targetSlot`. Hive derives material and color from that organization catalog row.
244
+ Explicit `material` and `color` must agree with the row. Material comparisons
245
+ ignore case, and colors accept a leading `#` and either case.
246
+
247
+ A contradiction returns 422 `filament_catalog_conflict` with the `sourceSlot` in
248
+ `filaments[]`. An unknown or foreign catalog ID returns 422
249
+ `catalog_filament_not_found` with the slot. Catalog rows without a material or a
250
+ six-digit hex color return 422 `invalid_catalog_filament`.
251
+
252
+ Stored requirements, waiting reasons, and load-step requirements retain
253
+ `filamentId`, `brand`, `name`, `colorName`, `material`, and `colorHex`. Catalog
254
+ identity is a snapshot. Multipart retries reuse the first saved catalog snapshot,
255
+ even if the catalog changes. Matching still uses material and color proximity.
256
+ Requests and stored jobs without `filamentId` keep their existing shape.
257
+
258
+ Catalog validation errors in JSON or multipart creation do not reserve a new
259
+ idempotency key or `externalId`. For JSON requests that include `filamentId`,
260
+ the same applies to every other validation error that Hive returns
261
+ before it accepts the request. Correct the request, or add the missing catalog
262
+ entry, and retry with the same identity. Existing created or failed receipts
263
+ replay before a new catalog lookup. Once Hive accepts the request, changes to
264
+ its submitted fields or uploaded bytes return 409. Requests without `filamentId`
265
+ keep their existing receipt behavior.
266
+ Filament search returns catalog IDs and `acceptedByCreate`. A result with
267
+ `acceptedByCreate: true` satisfies the current catalog admission checks for
268
+ print-job filament requirements. Other create admission checks still apply.
269
+
270
+ ## Search printers and filaments
271
+
272
+ Both methods require an organization API key with the `read` tier or higher.
273
+ The API derives organization ownership from the authenticated key.
274
+
275
+ The existing `GET /v1/search` and `GET /v1/materials/command-search` routes
276
+ also require the `read` tier or higher for API keys. Empty-scope API keys now
277
+ receive 403 on those routes. JWT authentication keeps its existing membership
278
+ checks.
279
+
280
+ ```ts
281
+ const printerPage = await hive.printers.search({ query: "Boo P1S", limit: 10 });
282
+ const filamentPage = await hive.filaments.search({
283
+ query: "black PETG",
284
+ limit: 20,
285
+ });
286
+ const redFilaments = await hive.filaments.search({ query: "bambu red" });
287
+ ```
288
+
289
+ | Method | Return type | API route |
290
+ | ------------------------------------------------------- | ----------------------------- | -------------------------------- |
291
+ | `printers.search({ query, limit?, offset?, signal? })` | `Promise<PrinterSearchPage>` | `GET /v1/printers/search?q=...` |
292
+ | `filaments.search({ query, limit?, offset?, signal? })` | `Promise<FilamentSearchPage>` | `GET /v1/filaments/search?q=...` |
293
+
294
+ `SearchOptions` contains the string `query`, optional integer `limit` and
295
+ `offset`, and an optional `AbortSignal`. Both methods return one page and use
296
+ the client's normal GET retry policy. Invalid response shapes throw `TypeError`.
297
+ Missing authentication returns 401. Insufficient API-key scope returns 403.
298
+ Fractional or out-of-range pagination returns 400. `limit` accepts 1–50, with
299
+ default 20. `offset` accepts 0–10,000, with default 0.
300
+
301
+ `PrinterSearchPage` contains `printers`, `total`, and `query`. Each
302
+ `PrinterSearchResult` includes `id`, name, model, serial, status, location,
303
+ `score`, and `match`. The API reads organization-scoped stored observations
304
+ and uses the same shared candidate builder and ranking code as the web
305
+ palette. Queries contain 1–256 characters. Ranking happens before pagination.
306
+ Each request reads every organization printer in batches of up to 1,000 before
307
+ ranking and pagination. These reads have no per-organization cache. Large
308
+ fleets and frequent queries therefore increase database read work. The SDK
309
+ does not debounce or cache requests.
310
+
311
+ No printer search index is involved, and no printer is contacted. Printer `id`
312
+ is the identity for explicit printer placement. Search does not guarantee
313
+ readiness; `printers.get(id)` returns stored printer details.
314
+
315
+ `FilamentSearchPage` contains `filaments`, `total`, `hasMore`, `matchMode`, and
316
+ `originalQuery`. Each `FilamentSearchResult` includes `id`, `filamentId`, name,
317
+ brand, material type, color, diameter, category, organization, and optional SKU.
318
+ `filamentId` equals the catalog material row's `id`. It is not an inventory item
319
+ ID. `acceptedByCreate` reports whether the row satisfies the current partner
320
+ catalog identity and material checks. It does not promise that a print job
321
+ passes file, plate, target, or dispatch admission.
322
+
323
+ Filament search reuses the web Material Command Search picker, including its
324
+ ranked database query and the organization's global-filament setting. It can
325
+ return organization filaments and enabled global catalog filaments. Global
326
+ rows have `organization_id: "global"`. Results also include unsynced organization
327
+ rows and rows without a color. These rows do not satisfy the current partner
328
+ catalog contract for `filaments[].filamentId` on `POST /v1/print-jobs/create`.
329
+ These rows have `acceptedByCreate: false`. The contract requires an organization-owned partner catalog row, a material
330
+ family, and a six-digit hex color. Search does not filter out other rows.
331
+
332
+ Queries contain at most 100 characters. An empty query browses the catalog.
333
+ Set the next offset to the previous offset plus the returned filament count
334
+ while `hasMore` is true. When no strict filament match exists, the picker
335
+ returns a broader catalog page with `matchMode: "broadened"` and retains the
336
+ sanitized `originalQuery`. Callers can display that distinction and ask users
337
+ to choose a filament. Neither search method selects a result or creates a job.