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