@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 CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2
4
+
5
+ - Add `printJobs.get` for live job polling after creation, including the API's waiting-reason snapshot.
6
+
7
+ ## 0.3.1
8
+
9
+ - Add `cause` to generated filament waiting reasons and export `FilamentWaitingReason`. Reasons include only unmet source slots at creation and claim recheck.
10
+
11
+ ## 0.3.0
12
+
13
+ - Add partner catalog upsert, filtered list and read methods; `preparation_disabled` is not retried as a transient 503.
14
+ - Generate preparation contracts and validators from the current API routes.
15
+ - Add typed preparation creation, reads, total-deadline polling and signed-download metadata.
16
+ - Preserve idempotency keys, immutable retry bodies and typed target refusal reasons.
17
+ - Migrate the Printie simulator from its local preparation wrapper to the SDK.
18
+
19
+ ## 0.2.0
20
+
21
+ - Add `printJobs.create` for multipart project preparation and existing JSON artifact creation.
22
+ - Add creation receipt reads and `waitUntilCreated`, including typed stopped-request errors.
23
+ - Return created receipts even when their referenced job has been deleted.
24
+ - Preserve idempotency keys across retries and include public creation routes in the generated OpenAPI contract.
25
+
3
26
  ## 0.1.0
4
27
 
5
28
  Initial partner-production client for server-side integrations such as Printie. Version 0.1.0 covers uploads and model reads only, including source inspection polling and source-file listing. Preparation methods (#4764) and order methods (#4631) will come in later versions.
package/README.md CHANGED
@@ -1,11 +1,43 @@
1
1
  # @printhive/api
2
2
 
3
- A TypeScript client for Print Hive's partner-production REST API. Version 0.1.0 has zero runtime dependencies and uses global `fetch`. It supports Node 20+, Bun, and Deno with npm compatibility. File paths require the runtime's Node filesystem compatibility. ESM, CommonJS, and TypeScript declarations ship together.
3
+ A TypeScript client for Print Hive's REST API. Version 0.2.0 has zero runtime dependencies and uses global `fetch`. It supports Node 20+, Bun, and Deno with npm compatibility. File paths require the runtime's Node filesystem compatibility. ESM, CommonJS, and TypeScript declarations ship together.
4
4
 
5
- Version 0.1.0 covers uploads and model reads only, including source inspection polling and source-file listing. Preparation methods (#4764) and order methods (#4631) will come in later versions.
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
+
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.
6
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
 
11
+ ## Create a print job
12
+
13
+ Use an API key with `control` to create and `read` to poll. See [ADR 0071](../../docs/adr/0071-public-print-job-requests.md) for target selection and filament semantics.
14
+
15
+ ```ts
16
+ import { PrintHive } from "@printhive/api";
17
+
18
+ const hive = new PrintHive({ apiKey: process.env.PRINTHIVE_API_KEY! });
19
+ const request = await hive.printJobs.create({
20
+ file: "/srv/designs/project.3mf",
21
+ filename: "project.3mf",
22
+ plate: 2,
23
+ filaments: [{ sourceSlot: 0, material: "PETG", color: "#FF0000" }],
24
+ printer_id: "00000000-0000-4000-8000-000000000001",
25
+ idempotencyKey: "storefront:print-1234",
26
+ });
27
+ const created = await hive.printJobs.waitUntilCreated(request.requestId);
28
+ console.log(created.job);
29
+ ```
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.
32
+
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
+
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.
36
+
37
+ `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
+
39
+ 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`.
40
+
9
41
  ## Upload a Printie design
10
42
 
11
43
  Install `@printhive/api` after its first npm release. Before release, build and pack this workspace for local installation.
@@ -53,19 +85,67 @@ for await (const source of hive.models.listSourceFiles(model.modelId)) {
53
85
  }
54
86
  ```
55
87
 
88
+ ## Prepare selected plates
89
+
90
+ Preparation requires `production:upload` for creation and `production:read` for reads and downloads, plus the organization's preparation feature flag.
91
+
92
+ ```ts
93
+ const preparation = await hive.preparations.create({
94
+ idempotencyKey: "printie:order-1234",
95
+ items: [
96
+ {
97
+ sourceVersionId: source.sourceVersionId,
98
+ plates: [2],
99
+ quantity: 3,
100
+ materialMappings: [{ sourceSlot: 1, material: "PETG", color: "#1F6FEB" }],
101
+ target: { printerId: "<qualified printer UUID>" },
102
+ },
103
+ ],
104
+ });
105
+ const outcome = await hive.preparations.waitUntilSettled(
106
+ preparation.preparationId,
107
+ {
108
+ timeoutMs: 120_000,
109
+ intervalMs: 1000,
110
+ }
111
+ );
112
+ for (const item of outcome.items) {
113
+ for (const plate of item.preparedPlates) {
114
+ if (plate.status === "ready") {
115
+ const info = await hive.preparations.downloadInfo(
116
+ outcome.preparationId,
117
+ plate.plateId
118
+ );
119
+ // info contains url, expiresAt, filename, bytes and sha256.
120
+ // Fetch the signed URL without sending the Hive API key.
121
+ }
122
+ }
123
+ }
124
+ ```
125
+
126
+ 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.
127
+
128
+ 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.
129
+
130
+ `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.
131
+
132
+ `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.
133
+
134
+ Public preparation types, including `PartnerPreparationItemInput`, `PartnerMaterialMapping`, `PartnerPreparedPlate`, `PartnerPreparationDownload`, and `PartnerPreparationTargetReason`, derive from the current API export.
135
+
56
136
  ## Handle failures and retries
57
137
 
58
- `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.
138
+ `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.
59
139
 
60
140
  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.
61
141
 
62
- 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.
142
+ 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.
63
143
 
64
144
  ## Coverage and contract maintenance
65
145
 
66
- Version 0.1.0 covers only 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.
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.
67
147
 
68
- `apps/api/scripts/export-openapi.ts` builds a schema-only Fastify app from the real partner route plugin, 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.
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.
69
149
 
70
150
  `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.
71
151
 
@@ -75,6 +155,71 @@ Run `bun run build`, `bun run check-types`, and `bun run test` in this package.
75
155
 
76
156
  ## Release
77
157
 
78
- The manual `api-client-publish.yml` workflow builds, verifies, checks npm version availability, and publishes with `npm publish --access public` using `NPM_TOKEN`. It never runs on push. The `@printhive` npm organization must exist before the first release. Creating it and dispatching a release are maintainer actions.
158
+ The manual `api-client-publish.yml` workflow builds, verifies, checks npm version availability, and publishes with `npm publish --access public`. It never runs on push. npm trusted publishing authorizes the release: npm trusts that workflow file in `PrintHive/hive`, so the workflow needs no stored npm token. Package maintainers can still publish by hand with their own npm credentials. To bump the version, change `version` in `package.json`, merge, and dispatch the workflow. Dispatching a release is a maintainer action.
159
+
160
+ Provenance is disabled because the source repository is private. It can be enabled later through a matching public source mirror and its publishing workflow. The package's `repository` field names `PrintHive/hive`, because trusted publishing from GitHub requires `repository.url` to match the publishing repository. Outside readers see only the URL.
161
+
162
+ ### Read organization printers
163
+
164
+ Use a customer API key with the `read` tier or higher. Keys with any
165
+ `production:*` scope cannot call these routes, including mixed-scope keys.
166
+
167
+ ```ts
168
+ const customer = new PrintHive({
169
+ apiKey: process.env.PRINT_HIVE_CUSTOMER_API_KEY!,
170
+ });
171
+ const page = await customer.printers.list({ limit: 50 });
172
+ if (page.printers[0]) {
173
+ const printer = await customer.printers.get(page.printers[0].id);
174
+ }
175
+ ```
176
+
177
+ List returns `{ printers, nextCursor }`. Pass `nextCursor` as `cursor` until
178
+ it is null. Detail returns 404 for missing or foreign-organization printers.
179
+ Each printer includes model, connection, status, and `readyMarked`, the stored
180
+ readiness flag. It does not guarantee an empty bed or authorize dispatch.
181
+ `loadedFilament` uses Smart Queue's material source. Each slot includes
182
+ `address: { ams_id, slot_id }`, material, color, and a `state` of `loaded`,
183
+ `empty`, or `unknown`. AMS IDs and tray IDs are zero based; external spools
184
+ use AMS ID 255 and slot ID 254 or 255. Reads return stored observations and
185
+ never contact a printer.
186
+
187
+ `readiness.ready` reports the stored ready flag. `readiness.bedClear` reports
188
+ the flag only when its source is `bed_occupancy_verified`; otherwise it is null.
189
+
190
+ ## Sync your filament catalog
191
+
192
+ Use a Control API key to sync each partner record. Repeating an external id
193
+ updates the same organization-owned material, including concurrent retries.
194
+ The external id namespace belongs to the organization, so coordinate ids across
195
+ integrations. Reads require Read scope. Hive trims surrounding whitespace from `externalId`
196
+ and `sku` on writes and list filters; neither field can be blank.
197
+
198
+ ```ts
199
+ const filament = await hive.filamentCatalog.upsert({
200
+ externalId: "printie-pla-blue",
201
+ brand: "Printie",
202
+ material: "PLA",
203
+ colorName: "Blue",
204
+ colorHex: "#0000FF",
205
+ sku: "PLA-BLUE",
206
+ replacementFilamentId: null,
207
+ });
208
+ console.log(filament.id, filament.sku);
209
+ const page = await hive.filamentCatalog.list({ sku: "PLA-BLUE", limit: 100 });
210
+ const saved = await hive.filamentCatalog.get(filament.id);
211
+ ```
79
212
 
80
- Provenance is intentionally disabled because the source repository is private. It can be enabled later through a matching public source mirror and its publishing workflow. The package omits a repository field pointing at the private monorepo.
213
+ `POST /v1/filament-catalog` accepts the fields above and returns 200 with the
214
+ material row. `hex` is an alias for `colorHex`; both must agree when supplied.
215
+ `replacementFilamentId` is retained as partner metadata, not resolved as a Hive id.
216
+ `GET /v1/filament-catalog` filters by exact `externalId` and `sku`, with `limit`
217
+ (1–200, default 100) and `offset` (default 0). Results are ordered by Hive id.
218
+ It returns `{ materials, total }`. `GET /v1/filament-catalog/:id` returns one row.
219
+ All operations enforce organization ownership. Returned rows include `id`, `sku`,
220
+ `material_type`, `color`, and partner metadata.
221
+
222
+ Errors include 400 `invalid_catalog_input`, 400 `conflicting_color_hex`, 403 `API_KEY_SCOPE_REQUIRED`,
223
+ 404 `catalog_filament_not_found`, 409 `catalog_conflict` for uniqueness conflicts,
224
+ and 500 `catalog_store_failed`. Invalid bodies return the API validation error.
225
+ Print requests can reference synced filaments once #4920 lands.