@printhive/api 0.3.2 → 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,12 @@
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
+
3
10
  ## 0.3.2
4
11
 
5
12
  - Add `printJobs.get` for live job polling after creation, including the API's waiting-reason snapshot.
package/README.md CHANGED
@@ -28,11 +28,27 @@ const created = await hive.printJobs.waitUntilCreated(request.requestId);
28
28
  console.log(created.job);
29
29
  ```
30
30
 
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`, `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.
32
32
 
33
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.
34
34
 
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. 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
36
52
 
37
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.
38
54
 
@@ -143,9 +159,9 @@ Only 429, 503, and network failures retry. Permanent `preparation_disabled` neve
143
159
 
144
160
  ## Coverage and contract maintenance
145
161
 
146
- The current source covers creation and request polling under `/v1/print-jobs/create`, 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.
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.
147
163
 
148
- `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.
149
165
 
150
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.
151
167
 
@@ -222,4 +238,100 @@ All operations enforce organization ownership. Returned rows include `id`, `sku`
222
238
  Errors include 400 `invalid_catalog_input`, 400 `conflicting_color_hex`, 403 `API_KEY_SCOPE_REQUIRED`,
223
239
  404 `catalog_filament_not_found`, 409 `catalog_conflict` for uniqueness conflicts,
224
240
  and 500 `catalog_store_failed`. Invalid bodies return the API validation error.
225
- Print requests can reference synced filaments once #4920 lands.
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.