@viewrium/sdk 0.1.0 → 2.0.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/dist/index.cjs CHANGED
@@ -1,449 +1,327 @@
1
- 'use strict';
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
2
19
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AuthenticationError: () => AuthenticationError,
24
+ BadRequestError: () => BadRequestError,
25
+ ConflictError: () => ConflictError,
26
+ ConnectionError: () => ConnectionError,
27
+ NotFoundError: () => NotFoundError,
28
+ PaymentRequiredError: () => PaymentRequiredError,
29
+ PermissionError: () => PermissionError,
30
+ RateLimitError: () => RateLimitError,
31
+ ServerError: () => ServerError,
32
+ UnprocessableEntityError: () => UnprocessableEntityError,
33
+ Viewrium: () => Viewrium,
34
+ ViewriumError: () => ViewriumError,
35
+ createApiError: () => createApiError,
36
+ createViewrium: () => createViewrium
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
4
39
 
5
40
  // src/errors.ts
6
41
  var ViewriumError = class extends Error {
42
+ /** HTTP status, or `null` for transport/connection failures. */
43
+ status;
44
+ /** Machine-readable code from the error envelope. */
45
+ code;
46
+ /** Optional structured details from the error envelope. */
47
+ details;
7
48
  constructor(message, options = {}) {
8
- super(message);
9
- this.name = "ViewriumError";
10
- this.statusCode = options.statusCode;
11
- this.responseBody = options.responseBody;
49
+ super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
50
+ this.name = new.target.name;
51
+ this.status = options.status ?? null;
52
+ this.code = options.code ?? "error";
53
+ this.details = options.details;
12
54
  }
13
55
  };
14
- var AuthError = class extends ViewriumError {
15
- constructor(message, options = {}) {
16
- super(message, options);
17
- this.name = "AuthError";
18
- }
56
+ var BadRequestError = class extends ViewriumError {
57
+ };
58
+ var AuthenticationError = class extends ViewriumError {
59
+ };
60
+ var PaymentRequiredError = class extends ViewriumError {
61
+ };
62
+ var PermissionError = class extends ViewriumError {
19
63
  };
20
64
  var NotFoundError = class extends ViewriumError {
21
- constructor(message, options = {}) {
22
- super(message, options);
23
- this.name = "NotFoundError";
24
- }
25
65
  };
26
- var ValidationError = class extends ViewriumError {
27
- constructor(message, options = {}) {
28
- super(message, options);
29
- this.name = "ValidationError";
30
- }
66
+ var ConflictError = class extends ViewriumError {
67
+ };
68
+ var UnprocessableEntityError = class extends ViewriumError {
31
69
  };
32
70
  var RateLimitError = class extends ViewriumError {
33
- constructor(message, options = {}) {
34
- super(message, options);
35
- this.name = "RateLimitError";
36
- this.retryAfter = options.retryAfter;
37
- }
38
71
  };
39
- var APIError = class extends ViewriumError {
40
- constructor(message, options = {}) {
41
- super(message, options);
42
- this.name = "APIError";
43
- }
72
+ var ServerError = class extends ViewriumError {
44
73
  };
45
-
46
- // src/resources/models.ts
47
- var BASE = "/v1/models";
48
- var ModelsResource = class {
49
- constructor(client) {
50
- this.client = client;
51
- }
52
- async create(params) {
53
- const hasImages = Array.isArray(params.images) && params.images.length > 0;
54
- const hasSource = typeof params.sourceUrl === "string" && params.sourceUrl.length > 0;
55
- if (hasImages === hasSource) {
56
- throw new Error(
57
- "Provide exactly one of `images` (URL list) or `sourceUrl` to models.create()."
58
- );
59
- }
60
- const body = stripUndefined({
61
- custom_id: params.customId,
62
- title: params.title,
63
- description: params.description,
64
- image_urls: params.images,
65
- source_url: params.sourceUrl,
66
- height: params.height,
67
- size_unit: params.sizeUnit,
68
- tags: params.tags,
69
- price: params.price,
70
- currency: params.currency,
71
- metadata: params.metadata,
72
- display_enabled: params.displayEnabled
73
- });
74
- return this.client.request({ method: "POST", path: BASE, json: body });
75
- }
76
- async createFromImages(images, params = {}) {
77
- if (!Array.isArray(images) || images.length < 1) {
78
- throw new Error("At least 1 image is required.");
79
- }
80
- const form = new FormData();
81
- for (const image of images) {
82
- const { blob, filename } = await toBlob(image, "image");
83
- form.append("images", blob, filename);
84
- }
85
- appendMultipartCommon(form, params);
86
- return this.client.request({
87
- method: "POST",
88
- path: `${BASE}/from-images`,
89
- body: form
90
- });
91
- }
92
- async createFromVideo(video, params = {}) {
93
- const { blob, filename } = await toBlob(video, "video");
94
- const form = new FormData();
95
- form.append("video", blob, filename);
96
- appendMultipartCommon(form, params);
97
- return this.client.request({
98
- method: "POST",
99
- path: `${BASE}/from-video`,
100
- body: form
101
- });
102
- }
103
- async retrieve(modelId) {
104
- return this.client.request({ method: "GET", path: `${BASE}/${encodeURIComponent(modelId)}` });
105
- }
106
- async list(params = {}) {
107
- const query = {};
108
- if (params.tag !== void 0) query.tag = params.tag;
109
- if (params.tags !== void 0) {
110
- query.tags = Array.isArray(params.tags) ? params.tags.join(",") : params.tags;
111
- }
112
- if (params.customId !== void 0) query.custom_id = params.customId;
113
- if (params.status !== void 0) query.status = params.status;
114
- if (params.limit !== void 0) query.limit = params.limit;
115
- if (params.offset !== void 0) query.offset = params.offset;
116
- return this.client.request({
117
- method: "GET",
118
- path: BASE,
119
- query
120
- });
121
- }
122
- async modify(modelId, params) {
123
- const body = stripUndefined({
124
- custom_id: params.customId,
125
- title: params.title,
126
- description: params.description,
127
- price: params.price,
128
- currency: params.currency,
129
- height: params.height,
130
- size_unit: params.sizeUnit,
131
- tags: params.tags,
132
- metadata: params.metadata,
133
- display_enabled: params.displayEnabled
134
- });
135
- return this.client.request({
136
- method: "PATCH",
137
- path: `${BASE}/${encodeURIComponent(modelId)}`,
138
- json: body
139
- });
140
- }
141
- async delete(modelId) {
142
- return this.client.request({
143
- method: "DELETE",
144
- path: `${BASE}/${encodeURIComponent(modelId)}`
145
- });
146
- }
147
- async listEvents(modelId, params = {}) {
148
- const query = {};
149
- if (params.limit !== void 0) query.limit = params.limit;
150
- if (params.offset !== void 0) query.offset = params.offset;
151
- return this.client.request({
152
- method: "GET",
153
- path: `${BASE}/${encodeURIComponent(modelId)}/events`,
154
- query
155
- });
156
- }
157
- };
158
- function stripUndefined(obj) {
159
- const out = {};
160
- for (const [key, value] of Object.entries(obj)) {
161
- if (value !== void 0) out[key] = value;
162
- }
163
- return out;
164
- }
165
- function appendMultipartCommon(form, params) {
166
- if (params.title !== void 0) form.append("title", params.title);
167
- if (params.customId !== void 0) form.append("custom_id", params.customId);
168
- if (params.description !== void 0) form.append("description", params.description);
169
- if (params.height !== void 0) form.append("height", String(params.height));
170
- if (params.sizeUnit !== void 0) form.append("size_unit", params.sizeUnit);
171
- if (params.tags !== void 0) form.append("tags", JSON.stringify(params.tags));
172
- if (params.displayEnabled !== void 0) {
173
- form.append("display_enabled", params.displayEnabled ? "true" : "false");
174
- }
175
- }
176
- async function toBlob(input, fallbackKind) {
177
- if (typeof input === "string") {
178
- const { readFile } = await import('fs/promises');
179
- const { basename } = await import('path');
180
- const data = await readFile(input);
181
- const blob = new Blob([data]);
182
- return { blob, filename: basename(input) };
183
- }
184
- if (input instanceof Blob) {
185
- const filename = input.name && typeof input.name === "string" ? input.name : defaultFilename(fallbackKind);
186
- return { blob: input, filename };
187
- }
188
- if (input && typeof input === "object" && "data" in input) {
189
- const { data, filename, contentType } = input;
190
- const part = data instanceof Blob ? data : data instanceof ArrayBuffer ? data : data;
191
- const blob = new Blob([part], contentType ? { type: contentType } : void 0);
192
- return { blob, filename: filename ?? defaultFilename(fallbackKind) };
193
- }
194
- throw new Error("Unsupported file input: expected string path, Blob, File, or {data, filename}.");
195
- }
196
- function defaultFilename(kind) {
197
- return kind === "image" ? "image.bin" : "video.bin";
198
- }
199
-
200
- // src/resources/apiKeys.ts
201
- var BASE2 = "/v1/api_keys";
202
- var ApiKeysResource = class {
203
- constructor(client) {
204
- this.client = client;
205
- }
206
- async create(params = {}) {
207
- const body = {};
208
- if (params.name !== void 0) body.name = params.name;
209
- if (params.keyType !== void 0) body.key_type = params.keyType;
210
- return this.client.request({
211
- method: "POST",
212
- path: BASE2,
213
- json: body
214
- });
215
- }
216
- async list() {
217
- return this.client.request({ method: "GET", path: BASE2 });
218
- }
219
- async delete(keyId) {
220
- return this.client.request({
221
- method: "DELETE",
222
- path: `${BASE2}/${encodeURIComponent(keyId)}`
223
- });
224
- }
74
+ var ConnectionError = class extends ViewriumError {
225
75
  };
226
-
227
- // src/resources/usage.ts
228
- var BASE3 = "/v1/usage";
229
- var UsageResource = class {
230
- constructor(client) {
231
- this.client = client;
232
- }
233
- async retrieve(params = {}) {
234
- const query = {};
235
- if (params.since !== void 0) query.since = params.since;
236
- if (params.until !== void 0) query.until = params.until;
237
- return this.client.request({
238
- method: "GET",
239
- path: BASE3,
240
- query
241
- });
242
- }
76
+ var BY_STATUS = {
77
+ 400: BadRequestError,
78
+ 401: AuthenticationError,
79
+ 402: PaymentRequiredError,
80
+ 403: PermissionError,
81
+ 404: NotFoundError,
82
+ 409: ConflictError,
83
+ 422: UnprocessableEntityError,
84
+ 429: RateLimitError
243
85
  };
244
-
245
- // src/resources/tags.ts
246
- var BASE4 = "/v1/tags";
247
- var TagsResource = class {
248
- constructor(client) {
249
- this.client = client;
86
+ function createApiError(status, payload) {
87
+ const options = { status, code: payload.code, details: payload.details };
88
+ const Specific = BY_STATUS[status];
89
+ if (Specific) {
90
+ return new Specific(payload.message, options);
250
91
  }
251
- async list() {
252
- return this.client.request({ method: "GET", path: BASE4 });
92
+ if (status >= 500) {
93
+ return new ServerError(payload.message, options);
253
94
  }
254
- };
95
+ return new ViewriumError(payload.message, options);
96
+ }
255
97
 
256
- // src/client.ts
257
- var DEFAULT_API_BASE = "https://api.viewrium.com";
258
- var DEFAULT_TIMEOUT_MS = 6e4;
259
- var DEFAULT_MAX_RETRIES = 3;
260
- var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
261
- var VERSION = "0.1.0";
262
- var Viewrium = class {
263
- constructor(apiKey, options = {}) {
264
- const envKey = typeof process !== "undefined" ? process.env?.VIEWRIUM_API_KEY : void 0;
265
- const resolvedKey = options.apiKey ?? apiKey ?? envKey;
266
- if (!resolvedKey) {
267
- throw new AuthError(
268
- "No API key configured. Pass `new Viewrium('sk_live_...')` or set VIEWRIUM_API_KEY."
269
- );
270
- }
271
- this.apiKey = resolvedKey;
272
- const envBase = typeof process !== "undefined" ? process.env?.VIEWRIUM_API_BASE : void 0;
273
- this.apiBase = (options.apiBase ?? envBase ?? DEFAULT_API_BASE).replace(/\/+$/, "");
274
- this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
275
- this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
276
- const providedFetch = options.fetch;
277
- if (providedFetch) {
278
- this.fetchImpl = providedFetch;
279
- } else if (typeof globalThis.fetch === "function") {
280
- this.fetchImpl = globalThis.fetch.bind(globalThis);
281
- } else {
282
- throw new Error(
283
- "No `fetch` implementation available. Use Node 18+ or pass `fetch` in options."
284
- );
285
- }
286
- this.models = new ModelsResource(this);
287
- this.apiKeys = new ApiKeysResource(this);
288
- this.usage = new UsageResource(this);
289
- this.tags = new TagsResource(this);
290
- }
291
- /** Low-level request. Resources delegate here. */
292
- async request(options) {
293
- const url = this.buildUrl(options.path, options.query);
294
- const headers = this.buildHeaders(options);
295
- const body = this.buildBody(options);
296
- let attempt = 0;
297
- let lastResponse;
298
- while (true) {
299
- lastResponse = void 0;
300
- const controller = new AbortController();
301
- const timeoutMs = options.timeoutMs ?? this.timeoutMs;
302
- const timer = setTimeout(() => controller.abort(), timeoutMs);
303
- try {
304
- lastResponse = await this.fetchImpl(url, {
305
- method: options.method,
306
- headers,
307
- body,
308
- signal: controller.signal
309
- });
310
- } catch (err) {
311
- clearTimeout(timer);
312
- if (attempt >= this.maxRetries) throw err;
313
- await sleep(backoffMs(void 0, attempt));
314
- attempt += 1;
315
- continue;
316
- }
317
- clearTimeout(timer);
318
- if (RETRYABLE_STATUSES.has(lastResponse.status) && attempt < this.maxRetries) {
319
- await sleep(backoffMs(lastResponse, attempt));
320
- attempt += 1;
321
- continue;
322
- }
323
- break;
324
- }
325
- if (!lastResponse.ok) {
326
- await raiseForStatus(lastResponse);
327
- }
328
- return await parseResponse(lastResponse);
329
- }
330
- buildUrl(path, query) {
331
- const full = path.startsWith("http://") || path.startsWith("https://") ? path : `${this.apiBase}${path.startsWith("/") ? path : `/${path}`}`;
332
- if (!query) return full;
333
- const params = new URLSearchParams();
98
+ // src/index.ts
99
+ var DEFAULT_BASE_URL = "https://api.viewrium.com";
100
+ function buildUrl(baseUrl, path, query) {
101
+ const url = new URL(baseUrl + path);
102
+ if (query) {
334
103
  for (const [key, value] of Object.entries(query)) {
335
104
  if (value === void 0 || value === null) continue;
336
105
  if (Array.isArray(value)) {
337
- for (const v of value) {
338
- if (v !== void 0 && v !== null) params.append(key, String(v));
339
- }
106
+ for (const item of value) url.searchParams.append(key, String(item));
340
107
  } else {
341
- params.append(key, String(value));
108
+ url.searchParams.append(key, String(value));
342
109
  }
343
110
  }
344
- const qs = params.toString();
345
- return qs ? `${full}?${qs}` : full;
346
111
  }
347
- buildHeaders(options) {
348
- const headers = new Headers(options.headers);
349
- headers.set("Authorization", `Bearer ${this.apiKey}`);
350
- headers.set("Accept", "application/json");
351
- if (!headers.has("User-Agent") && typeof process !== "undefined") {
352
- try {
353
- headers.set("User-Agent", `viewrium-js/${VERSION}`);
354
- } catch {
112
+ return url.toString();
113
+ }
114
+ async function parseError(res) {
115
+ let code = "error";
116
+ let message = res.statusText || `HTTP ${res.status}`;
117
+ let details;
118
+ try {
119
+ const data = await res.json();
120
+ if (data && typeof data === "object" && "error" in data) {
121
+ const envelope = data.error;
122
+ if (envelope && typeof envelope === "object") {
123
+ const e = envelope;
124
+ if (typeof e.code === "string") code = e.code;
125
+ if (typeof e.message === "string") message = e.message;
126
+ details = e.details;
355
127
  }
356
128
  }
357
- if (options.json !== void 0 && !headers.has("Content-Type")) {
358
- headers.set("Content-Type", "application/json");
359
- }
360
- return headers;
361
- }
362
- buildBody(options) {
363
- if (options.body !== void 0) return options.body;
364
- if (options.json !== void 0) return JSON.stringify(options.json);
365
- return void 0;
366
- }
367
- };
368
- async function parseResponse(response) {
369
- if (response.status === 204) return {};
370
- const text = await response.text();
371
- if (!text) return {};
372
- try {
373
- return JSON.parse(text);
374
- } catch (e) {
375
- throw new APIError(`Failed to parse JSON response: ${e.message}`, {
376
- statusCode: response.status,
377
- responseBody: text
378
- });
129
+ } catch {
379
130
  }
131
+ return createApiError(res.status, { code, message, details });
380
132
  }
381
- async function raiseForStatus(response) {
382
- const body = await parseBodyForError(response);
383
- const detail = extractDetail(body, response);
384
- const status = response.status;
385
- const opts = { statusCode: status, responseBody: body };
386
- if (status === 401) throw new AuthError(detail, opts);
387
- if (status === 404) throw new NotFoundError(detail, opts);
388
- if (status === 400 || status === 409 || status === 422) {
389
- throw new ValidationError(detail, opts);
133
+ async function send(transport, method, path, options) {
134
+ const headers = {
135
+ accept: "application/json",
136
+ authorization: `Bearer ${transport.apiKey}`
137
+ };
138
+ const init = { method, headers };
139
+ if (options?.body !== void 0) {
140
+ headers["content-type"] = "application/json";
141
+ init.body = JSON.stringify(options.body);
142
+ }
143
+ let res;
144
+ try {
145
+ res = await transport.fetch(buildUrl(transport.baseUrl, path, options?.query), init);
146
+ } catch (cause) {
147
+ throw new ConnectionError("The request to the Viewrium API could not be sent.", { cause });
390
148
  }
391
- if (status === 429) {
392
- throw new RateLimitError(detail, {
393
- ...opts,
394
- retryAfter: parseRetryAfter(response)
395
- });
149
+ if (!res.ok) {
150
+ throw await parseError(res);
396
151
  }
397
- throw new APIError(detail, opts);
152
+ return res;
398
153
  }
399
- async function parseBodyForError(response) {
400
- const text = await response.text();
401
- if (!text) return void 0;
402
- try {
403
- return JSON.parse(text);
404
- } catch {
405
- return text;
154
+ async function doRequest(transport, method, path, options) {
155
+ const res = await send(transport, method, path, options);
156
+ if (res.status === 204) {
157
+ return void 0;
406
158
  }
159
+ const text = await res.text();
160
+ return text ? JSON.parse(text) : void 0;
407
161
  }
408
- function extractDetail(body, response) {
409
- if (body && typeof body === "object" && !Array.isArray(body)) {
410
- const obj = body;
411
- const candidate = obj.detail ?? obj.error ?? obj.message;
412
- if (typeof candidate === "string" && candidate) return candidate;
413
- if (candidate !== void 0) return String(candidate);
414
- }
415
- if (typeof body === "string" && body) return body;
416
- return `HTTP ${response.status}`;
162
+ async function doBlobRequest(transport, method, path, options) {
163
+ const res = await send(transport, method, path, options);
164
+ if (res.status === 202) return await res.json();
165
+ return res.blob();
417
166
  }
418
- function parseRetryAfter(response) {
419
- const raw = response.headers.get("Retry-After");
420
- if (!raw) return void 0;
421
- const n = Number(raw);
422
- return Number.isFinite(n) ? n : void 0;
167
+ var encode = encodeURIComponent;
168
+ function createProducts(request, requestBlob) {
169
+ return {
170
+ /**
171
+ * POST /v1/products - the single generation entrypoint. Supply exactly one source
172
+ * (`image_paths` | `image_urls` | `video_url` | `source_url` | `model_url` |
173
+ * `resume_token`). A fresh request ALWAYS parks (200, `status === "parked"`, nothing
174
+ * written): the plan, the model verdict and the price come back inside `resume_token`,
175
+ * which you POST back to commit (202, `ProductCreated`). A `model_url` commits at once.
176
+ */
177
+ create: (params) => request("POST", "/v1/products", { body: params }),
178
+ /**
179
+ * GET /v1/products - the catalog: one org (`org_id`, or the caller's only org), or
180
+ * `scope: "all"` for every org an agency member or staff reaches. Filters AND together.
181
+ */
182
+ list: (params) => request("GET", "/v1/products", { query: params }),
183
+ /** GET /v1/products/{id} - the product with its assets (a `pk_` key gets `PublicProduct`). */
184
+ retrieve: (id) => request("GET", `/v1/products/${encode(id)}`),
185
+ /**
186
+ * PATCH /v1/products/{id} - the customer's fields, the user-set status and the publish
187
+ * toggle. 409 on a `custom_id` another product in the org already holds.
188
+ */
189
+ update: (id, params) => request("PATCH", `/v1/products/${encode(id)}`, { body: params }),
190
+ /** DELETE /v1/products/{id} */
191
+ delete: (id) => request("DELETE", `/v1/products/${encode(id)}`),
192
+ /** DELETE /v1/products?ids=... - per-id affected/not_found/forbidden buckets. */
193
+ batchDelete: (ids) => request("DELETE", "/v1/products", { query: { ids } }),
194
+ /** POST /v1/products/publish - the bulk publish toggle; one product toggles through `update`. */
195
+ publish: (params) => request("POST", "/v1/products/publish", { body: params }),
196
+ /** GET /v1/products/tags - distinct tags across the same scope the list takes. */
197
+ tags: (params) => request("GET", "/v1/products/tags", { query: params }),
198
+ /** POST /v1/products/{id}/review - the review flush: image and model decisions plus the
199
+ * product's user-set status, validated together and applied in order. */
200
+ review: (id, params) => request("POST", `/v1/products/${encode(id)}/review`, { body: params }),
201
+ /** PATCH /v1/products/{id}/images/{image_id} - one image's decision and overrides. */
202
+ updateImage: (id, imageId, params) => request("PATCH", `/v1/products/${encode(id)}/images/${encode(imageId)}`, {
203
+ body: params
204
+ }),
205
+ /** PATCH /v1/products/{id}/model - the model's review decision. */
206
+ updateModel: (id, params) => request("PATCH", `/v1/products/${encode(id)}/model`, { body: params }),
207
+ /**
208
+ * POST /v1/products/{id}/model/rescale - re-apply real-world scale to the GLB in place.
209
+ * Every supplied axis is applied to the geometry; `unit` covers them (omitted = cm) and is
210
+ * remembered as the product's `display_unit`. The result carries the ACHIEVED dimensions.
211
+ */
212
+ rescale: (id, params) => request("POST", `/v1/products/${encode(id)}/model/rescale`, { body: params }),
213
+ /** POST /v1/products/{id}/order - park the missing half (images or a model); commit the
214
+ * returned `resume_token` with `create`. */
215
+ order: (id, params) => request("POST", `/v1/products/${encode(id)}/order`, { body: params }),
216
+ /** GET /v1/products/export - a ZIP of finished images (never the 3D model), resolved as a
217
+ * `Blob`; or `ProductExportQueued` (202) when the images are being finished in the
218
+ * background and a download link is emailed - repeat the request later to stream it. */
219
+ export: (params) => requestBlob("GET", "/v1/products/export", { query: params })
220
+ };
423
221
  }
424
- function backoffMs(response, attempt) {
425
- if (response) {
426
- const hinted = parseRetryAfter(response);
427
- if (hinted !== void 0) return hinted * 1e3;
428
- }
429
- const base = Math.min(2 ** attempt, 30);
430
- const jitter = Math.random() * 0.25;
431
- return (base + jitter) * 1e3;
222
+ function createApiKeys(request) {
223
+ return {
224
+ /** GET /v1/api_keys */
225
+ list: () => request("GET", "/v1/api_keys"),
226
+ /** POST /v1/api_keys - the full key is returned exactly once. */
227
+ create: (params) => request("POST", "/v1/api_keys", { body: params }),
228
+ /** DELETE /v1/api_keys/{id} - soft revoke. */
229
+ revoke: (id) => request("DELETE", `/v1/api_keys/${encode(id)}`)
230
+ };
432
231
  }
433
- function sleep(ms) {
434
- return new Promise((resolve) => setTimeout(resolve, ms));
232
+ function createUsage(request) {
233
+ return {
234
+ /** GET /v1/usage - buckets and totals over the window; `group_by: "product"` adds the
235
+ * window's best-performing products in `by_product`. */
236
+ get: (params) => request("GET", "/v1/usage", { query: params })
237
+ };
435
238
  }
436
-
437
- // src/index.ts
438
- var index_default = Viewrium;
439
-
440
- exports.APIError = APIError;
441
- exports.AuthError = AuthError;
442
- exports.NotFoundError = NotFoundError;
443
- exports.RateLimitError = RateLimitError;
444
- exports.ValidationError = ValidationError;
445
- exports.Viewrium = Viewrium;
446
- exports.ViewriumError = ViewriumError;
447
- exports.default = index_default;
448
- //# sourceMappingURL=index.cjs.map
239
+ function createWebhooks(request) {
240
+ return {
241
+ /** GET /v1/webhooks */
242
+ list: () => request("GET", "/v1/webhooks"),
243
+ /** POST /v1/webhooks - includes the signing secret. */
244
+ create: (params) => request("POST", "/v1/webhooks", { body: params }),
245
+ /** GET /v1/webhooks/{id} */
246
+ retrieve: (id) => request("GET", `/v1/webhooks/${encode(id)}`),
247
+ /** PATCH /v1/webhooks/{id} */
248
+ update: (id, params) => request("PATCH", `/v1/webhooks/${encode(id)}`, { body: params }),
249
+ /** DELETE /v1/webhooks/{id} */
250
+ delete: (id) => request("DELETE", `/v1/webhooks/${encode(id)}`),
251
+ /** GET /v1/webhooks/{id}/deliveries */
252
+ listDeliveries: (id, params) => request("GET", `/v1/webhooks/${encode(id)}/deliveries`, {
253
+ query: params
254
+ })
255
+ };
256
+ }
257
+ function createProjects(request) {
258
+ return {
259
+ /** GET /v1/projects */
260
+ list: () => request("GET", "/v1/projects"),
261
+ /** GET /v1/projects/{id} */
262
+ retrieve: (id) => request("GET", `/v1/projects/${encode(id)}`)
263
+ };
264
+ }
265
+ function createBeacon(request) {
266
+ return {
267
+ /** POST /v1/beacon - ingest a view/AR analytics event (204). */
268
+ send: (params) => request("POST", "/v1/beacon", { body: params })
269
+ };
270
+ }
271
+ var Viewrium = class {
272
+ baseUrl;
273
+ products;
274
+ apiKeys;
275
+ usage;
276
+ webhooks;
277
+ projects;
278
+ beacon;
279
+ #request;
280
+ constructor(options) {
281
+ if (!options || !options.apiKey) {
282
+ throw new ViewriumError("A Viewrium client requires an `apiKey` (sk_ or pk_).");
283
+ }
284
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
285
+ const provided = options.fetch;
286
+ const globalFetch = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
287
+ const fetchImpl = provided ?? globalFetch;
288
+ if (!fetchImpl) {
289
+ throw new ViewriumError("No fetch implementation available; pass `options.fetch`.");
290
+ }
291
+ const transport = { baseUrl: this.baseUrl, apiKey: options.apiKey, fetch: fetchImpl };
292
+ const request = (method, path, opts) => doRequest(transport, method, path, opts);
293
+ const requestBlob = (method, path, opts) => doBlobRequest(transport, method, path, opts);
294
+ this.#request = request;
295
+ this.products = createProducts(request, requestBlob);
296
+ this.apiKeys = createApiKeys(request);
297
+ this.usage = createUsage(request);
298
+ this.webhooks = createWebhooks(request);
299
+ this.projects = createProjects(request);
300
+ this.beacon = createBeacon(request);
301
+ }
302
+ /** GET /v1/health */
303
+ health() {
304
+ return this.#request("GET", "/v1/health");
305
+ }
306
+ };
307
+ function createViewrium(options) {
308
+ return new Viewrium(options);
309
+ }
310
+ // Annotate the CommonJS export names for ESM import in node:
311
+ 0 && (module.exports = {
312
+ AuthenticationError,
313
+ BadRequestError,
314
+ ConflictError,
315
+ ConnectionError,
316
+ NotFoundError,
317
+ PaymentRequiredError,
318
+ PermissionError,
319
+ RateLimitError,
320
+ ServerError,
321
+ UnprocessableEntityError,
322
+ Viewrium,
323
+ ViewriumError,
324
+ createApiError,
325
+ createViewrium
326
+ });
449
327
  //# sourceMappingURL=index.cjs.map