@inneropen/marvin-sdk 4.1.0 → 4.3.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.
@@ -79,6 +79,11 @@ interface RetryConfig {
79
79
  */
80
80
  retryableStatuses: number[];
81
81
  }
82
+ /** A successful binary read: the body's bytes and the server's content type. */
83
+ interface BinaryResponse {
84
+ data: Uint8Array;
85
+ contentType: string | null;
86
+ }
82
87
  interface HttpClientConfig {
83
88
  baseUrl: string;
84
89
  auth: AuthStrategy;
@@ -158,6 +163,11 @@ declare class HttpClient {
158
163
  * Still clamped to MAX_TIMEOUT.
159
164
  */
160
165
  timeout?: number;
166
+ /**
167
+ * `'binary'` hands back a {@link BinaryResponse} (the raw bytes) instead of parsing JSON. Retries,
168
+ * redirects and errors behave exactly as for a JSON read.
169
+ */
170
+ responseType?: 'json' | 'binary';
161
171
  }, retryAttempt?: number): Promise<T>;
162
172
  /**
163
173
  * A successful response's JSON body, or `undefined` when there is none: 204/205, `content-length: 0`,
@@ -165,10 +175,16 @@ declare class HttpClient {
165
175
  * JSON is a `MarvinApiError` naming the request — never a network error, and never retried.
166
176
  */
167
177
  private readBody;
178
+ /** A successful response's bytes; a 204/205 is an empty array. A failed read is a network error. */
179
+ private readBinary;
168
180
  /**
169
181
  * GET request
170
182
  */
171
183
  get<T>(endpoint: string, params?: Record<string, string | number | boolean | undefined>): Promise<T>;
184
+ /**
185
+ * GET a binary body (a file download). Follows redirects; retried like any GET.
186
+ */
187
+ getBinary(endpoint: string, params?: Record<string, string | number | boolean | undefined>): Promise<BinaryResponse>;
172
188
  /**
173
189
  * POST request
174
190
  */
@@ -189,4 +205,4 @@ declare class HttpClient {
189
205
  delete<T>(endpoint: string): Promise<T>;
190
206
  }
191
207
 
192
- export { type AuthStrategy as A, BearerTokenAuth as B, HttpClient as H, NoAuth as N, SessionAuth as S, type HttpClientConfig as a };
208
+ export { type AuthStrategy as A, BearerTokenAuth as B, HttpClient as H, NoAuth as N, SessionAuth as S, type BinaryResponse as a, type HttpClientConfig as b };
@@ -79,6 +79,11 @@ interface RetryConfig {
79
79
  */
80
80
  retryableStatuses: number[];
81
81
  }
82
+ /** A successful binary read: the body's bytes and the server's content type. */
83
+ interface BinaryResponse {
84
+ data: Uint8Array;
85
+ contentType: string | null;
86
+ }
82
87
  interface HttpClientConfig {
83
88
  baseUrl: string;
84
89
  auth: AuthStrategy;
@@ -158,6 +163,11 @@ declare class HttpClient {
158
163
  * Still clamped to MAX_TIMEOUT.
159
164
  */
160
165
  timeout?: number;
166
+ /**
167
+ * `'binary'` hands back a {@link BinaryResponse} (the raw bytes) instead of parsing JSON. Retries,
168
+ * redirects and errors behave exactly as for a JSON read.
169
+ */
170
+ responseType?: 'json' | 'binary';
161
171
  }, retryAttempt?: number): Promise<T>;
162
172
  /**
163
173
  * A successful response's JSON body, or `undefined` when there is none: 204/205, `content-length: 0`,
@@ -165,10 +175,16 @@ declare class HttpClient {
165
175
  * JSON is a `MarvinApiError` naming the request — never a network error, and never retried.
166
176
  */
167
177
  private readBody;
178
+ /** A successful response's bytes; a 204/205 is an empty array. A failed read is a network error. */
179
+ private readBinary;
168
180
  /**
169
181
  * GET request
170
182
  */
171
183
  get<T>(endpoint: string, params?: Record<string, string | number | boolean | undefined>): Promise<T>;
184
+ /**
185
+ * GET a binary body (a file download). Follows redirects; retried like any GET.
186
+ */
187
+ getBinary(endpoint: string, params?: Record<string, string | number | boolean | undefined>): Promise<BinaryResponse>;
172
188
  /**
173
189
  * POST request
174
190
  */
@@ -189,4 +205,4 @@ declare class HttpClient {
189
205
  delete<T>(endpoint: string): Promise<T>;
190
206
  }
191
207
 
192
- export { type AuthStrategy as A, BearerTokenAuth as B, HttpClient as H, NoAuth as N, SessionAuth as S, type HttpClientConfig as a };
208
+ export { type AuthStrategy as A, BearerTokenAuth as B, HttpClient as H, NoAuth as N, SessionAuth as S, type BinaryResponse as a, type HttpClientConfig as b };
@@ -6,7 +6,7 @@ import {
6
6
  __export,
7
7
  init_core,
8
8
  init_errors
9
- } from "./chunk-DSVYCVQD.js";
9
+ } from "./chunk-LTX3MAUE.js";
10
10
 
11
11
  // src/client/config.ts
12
12
  var config_exports = {};
@@ -135,6 +135,12 @@ var init_config = __esm({
135
135
  });
136
136
 
137
137
  // src/entries/entry.ts
138
+ function isFullEntryData(raw) {
139
+ if (!raw || typeof raw !== "object") return false;
140
+ const record = raw;
141
+ if ("assetSlugs" in record || "resourceSlugs" in record) return false;
142
+ return Array.isArray(record.assets) || Array.isArray(record.resources);
143
+ }
138
144
  var Entry;
139
145
  var init_entry = __esm({
140
146
  "src/entries/entry.ts"() {
@@ -314,6 +320,11 @@ var init_resource = __esm({
314
320
  });
315
321
 
316
322
  // src/entries/entries.ts
323
+ function csv(value) {
324
+ if (value === void 0) return void 0;
325
+ const joined = (Array.isArray(value) ? value : [value]).map((v) => v.trim()).filter(Boolean).join(",");
326
+ return joined || void 0;
327
+ }
317
328
  function toListEntry(raw) {
318
329
  const { collections, ...rest } = raw;
319
330
  return {
@@ -337,19 +348,24 @@ var init_entries = __esm({
337
348
  }
338
349
  http;
339
350
  workspaceSlug;
340
- /**
341
- * Get all published entries
342
- */
343
351
  async list(options = {}) {
344
352
  const queryString = this.http.buildQueryString({
345
353
  entry_type: options.entryType,
346
354
  collection: options.collection,
355
+ tag: csv(options.tag),
356
+ slug: csv(options.slug),
357
+ updated_since: options.updatedSince instanceof Date ? options.updatedSince.toISOString() : options.updatedSince,
347
358
  limit: options.limit,
348
- offset: options.offset
359
+ offset: options.offset,
360
+ expand: options.expand
349
361
  });
350
362
  const endpoint = `/api/publish/${this.workspaceSlug}/entries${queryString}`;
351
363
  const response = await this.http.fetch(endpoint);
352
- return (response.data || []).map(toListEntry);
364
+ const data = response.data || [];
365
+ if (options.expand === "full" && data.every(isFullEntryData)) {
366
+ return data.map((entry) => new Entry(entry));
367
+ }
368
+ return data.map(toListEntry);
353
369
  }
354
370
  /**
355
371
  * Get a single entry by slug
@@ -395,6 +411,7 @@ var init_collections = __esm({
395
411
  "src/collections/collections.ts"() {
396
412
  "use strict";
397
413
  init_collection();
414
+ init_entry();
398
415
  init_errors();
399
416
  CollectionsModule = class {
400
417
  constructor(http, workspaceSlug) {
@@ -427,18 +444,25 @@ var init_collections = __esm({
427
444
  throw error;
428
445
  }
429
446
  }
430
- /**
431
- * Get entries in a collection with junction context surfaced directly.
432
- * Each entry has role/position/junctionMetadata from this collection's junction,
433
- * and collections reduced to slugs of other memberships.
434
- * Returns empty array if collection is not found.
435
- */
436
- async entries(slug) {
437
- const collection = await this.get(slug);
438
- if (Array.isArray(collection)) {
439
- return collection;
447
+ async entries(slug, options = {}) {
448
+ if (options.expand !== "full") {
449
+ const collection = await this.get(slug);
450
+ return Array.isArray(collection) ? collection : collection.entries;
451
+ }
452
+ let data;
453
+ try {
454
+ data = await this.http.fetch(
455
+ `/api/publish/${this.workspaceSlug}/collections/${slug}?expand=full`
456
+ );
457
+ } catch (error) {
458
+ if (error instanceof MarvinNotFoundError) return [];
459
+ throw error;
440
460
  }
441
- return collection.entries;
461
+ const entries = data.entries ?? [];
462
+ if (entries.every(isFullEntryData)) {
463
+ return data.entries.map((entry) => new Entry(entry));
464
+ }
465
+ return new Collection(data).entries;
442
466
  }
443
467
  /**
444
468
  * Try multiple collection slugs in fallback order.
@@ -498,6 +522,14 @@ var init_assets = __esm({
498
522
  const endpoint = `/api/publish/${this.workspaceSlug}/assets/${slugOrId}`;
499
523
  return this.http.fetch(endpoint);
500
524
  }
525
+ /**
526
+ * Download a published asset's file by slug: its bytes and content type. The file route
527
+ * redirects to storage; the redirect is followed. Needs the `read:assets` permission.
528
+ */
529
+ async download(slug) {
530
+ const validSlug = this.http.validatePathParam(slug, "asset slug");
531
+ return this.http.getBinary(`/api/publish/${this.workspaceSlug}/assets/${validSlug}/file`);
532
+ }
501
533
  /**
502
534
  * Convenience: Get all images
503
535
  */
@@ -888,6 +920,7 @@ export {
888
920
  createConfigFromEnv,
889
921
  config_exports,
890
922
  init_config,
923
+ isFullEntryData,
891
924
  Entry,
892
925
  init_entry,
893
926
  Collection,
@@ -383,6 +383,9 @@ var init_HttpClient = __esm({
383
383
  errorText
384
384
  );
385
385
  }
386
+ if (options?.responseType === "binary") {
387
+ return await this.readBinary(response, method, endpoint);
388
+ }
386
389
  const data = await this.readBody(response, method, endpoint);
387
390
  if (this.debug && data !== void 0) {
388
391
  const sanitized = this.sanitizeForLogging(data);
@@ -428,12 +431,34 @@ var init_HttpClient = __esm({
428
431
  );
429
432
  }
430
433
  }
434
+ /** A successful response's bytes; a 204/205 is an empty array. A failed read is a network error. */
435
+ async readBinary(response, method, endpoint) {
436
+ const contentType = response.headers.get("content-type");
437
+ if (response.status === 204 || response.status === 205) {
438
+ return { data: new Uint8Array(0), contentType };
439
+ }
440
+ try {
441
+ return { data: new Uint8Array(await response.arrayBuffer()), contentType };
442
+ } catch (error) {
443
+ const cause = error instanceof Error ? error : void 0;
444
+ throw new MarvinNetworkError(
445
+ `Reading the ${response.status} response to ${method} ${endpoint} failed: ${cause?.message ?? String(error)}`,
446
+ cause
447
+ );
448
+ }
449
+ }
431
450
  /**
432
451
  * GET request
433
452
  */
434
453
  async get(endpoint, params) {
435
454
  return this.request("GET", endpoint, { params });
436
455
  }
456
+ /**
457
+ * GET a binary body (a file download). Follows redirects; retried like any GET.
458
+ */
459
+ async getBinary(endpoint, params) {
460
+ return this.request("GET", endpoint, { params, responseType: "binary" });
461
+ }
437
462
  /**
438
463
  * POST request
439
464
  */