@inneropen/marvin-sdk 2.0.0-next.10 → 2.0.0-next.12

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.
@@ -60,6 +60,12 @@ interface HttpClientConfig {
60
60
  defaultHeaders?: Record<string, string>;
61
61
  credentials?: RequestCredentials;
62
62
  timeout?: number;
63
+ debug?: boolean;
64
+ logger?: {
65
+ log: (message: string, ...args: unknown[]) => void;
66
+ warn: (message: string, ...args: unknown[]) => void;
67
+ error: (message: string, ...args: unknown[]) => void;
68
+ };
63
69
  }
64
70
  declare class HttpClient {
65
71
  private baseUrl;
@@ -67,6 +73,8 @@ declare class HttpClient {
67
73
  private defaultHeaders;
68
74
  private credentials;
69
75
  private timeout;
76
+ private debug;
77
+ private logger;
70
78
  constructor(config: HttpClientConfig);
71
79
  /**
72
80
  * Build full URL from endpoint
@@ -60,6 +60,12 @@ interface HttpClientConfig {
60
60
  defaultHeaders?: Record<string, string>;
61
61
  credentials?: RequestCredentials;
62
62
  timeout?: number;
63
+ debug?: boolean;
64
+ logger?: {
65
+ log: (message: string, ...args: unknown[]) => void;
66
+ warn: (message: string, ...args: unknown[]) => void;
67
+ error: (message: string, ...args: unknown[]) => void;
68
+ };
63
69
  }
64
70
  declare class HttpClient {
65
71
  private baseUrl;
@@ -67,6 +73,8 @@ declare class HttpClient {
67
73
  private defaultHeaders;
68
74
  private credentials;
69
75
  private timeout;
76
+ private debug;
77
+ private logger;
70
78
  constructor(config: HttpClientConfig);
71
79
  /**
72
80
  * Build full URL from endpoint
@@ -20,7 +20,7 @@ var __copyProps = (to, from, except, desc) => {
20
20
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
21
 
22
22
  // src/core/errors/MarvinError.ts
23
- var MarvinError, MarvinApiError, MarvinAuthError, MarvinConfigError, MarvinValidationError;
23
+ var MarvinError, MarvinApiError, MarvinAuthError, MarvinConfigError, MarvinValidationError, MarvinNotFoundError, MarvinNetworkError, MarvinServerError;
24
24
  var init_MarvinError = __esm({
25
25
  "src/core/errors/MarvinError.ts"() {
26
26
  "use strict";
@@ -76,6 +76,33 @@ ${body}` : ""}`;
76
76
  }
77
77
  field;
78
78
  };
79
+ MarvinNotFoundError = class _MarvinNotFoundError extends MarvinError {
80
+ constructor(message, endpoint) {
81
+ super(message, "NOT_FOUND", 404);
82
+ this.endpoint = endpoint;
83
+ this.name = "MarvinNotFoundError";
84
+ Object.setPrototypeOf(this, _MarvinNotFoundError.prototype);
85
+ }
86
+ endpoint;
87
+ };
88
+ MarvinNetworkError = class _MarvinNetworkError extends MarvinError {
89
+ constructor(message, originalError) {
90
+ super(message, "NETWORK_ERROR");
91
+ this.originalError = originalError;
92
+ this.name = "MarvinNetworkError";
93
+ Object.setPrototypeOf(this, _MarvinNetworkError.prototype);
94
+ }
95
+ originalError;
96
+ };
97
+ MarvinServerError = class _MarvinServerError extends MarvinError {
98
+ constructor(message, statusCode, endpoint) {
99
+ super(message, "SERVER_ERROR", statusCode);
100
+ this.endpoint = endpoint;
101
+ this.name = "MarvinServerError";
102
+ Object.setPrototypeOf(this, _MarvinServerError.prototype);
103
+ }
104
+ endpoint;
105
+ };
79
106
  }
80
107
  });
81
108
 
@@ -99,12 +126,16 @@ var init_HttpClient = __esm({
99
126
  defaultHeaders;
100
127
  credentials;
101
128
  timeout;
129
+ debug;
130
+ logger;
102
131
  constructor(config) {
103
132
  this.baseUrl = config.baseUrl;
104
133
  this.auth = config.auth;
105
134
  this.defaultHeaders = config.defaultHeaders || {};
106
135
  this.credentials = config.credentials || "same-origin";
107
136
  this.timeout = config.timeout || 3e4;
137
+ this.debug = config.debug ?? false;
138
+ this.logger = config.logger ?? console;
108
139
  }
109
140
  /**
110
141
  * Build full URL from endpoint
@@ -146,6 +177,10 @@ var init_HttpClient = __esm({
146
177
  const headers = this.buildHeaders(options?.headers, isFormData);
147
178
  const controller = new AbortController();
148
179
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
180
+ const startTime = Date.now();
181
+ if (this.debug) {
182
+ this.logger.log(`[Marvin] ${method} ${url}`);
183
+ }
149
184
  try {
150
185
  const response = await fetch(url, {
151
186
  method,
@@ -155,11 +190,42 @@ var init_HttpClient = __esm({
155
190
  signal: controller.signal
156
191
  });
157
192
  clearTimeout(timeoutId);
193
+ const duration = Date.now() - startTime;
194
+ if (this.debug) {
195
+ this.logger.log(
196
+ `[Marvin] ${response.status} ${response.statusText} (${duration}ms)`
197
+ );
198
+ }
158
199
  if (response.status === 401 && this.auth.handleUnauthorized) {
159
200
  await this.auth.handleUnauthorized();
160
201
  }
161
202
  if (!response.ok) {
162
203
  const errorText = await response.text().catch(() => "");
204
+ if (this.debug) {
205
+ this.logger.error(
206
+ `[Marvin] Error response: ${response.status} ${response.statusText}`,
207
+ errorText ? `Body: ${errorText}` : ""
208
+ );
209
+ }
210
+ if (response.status === 404) {
211
+ throw new MarvinNotFoundError(
212
+ `Resource not found: ${endpoint}`,
213
+ endpoint
214
+ );
215
+ }
216
+ if (response.status === 401 || response.status === 403) {
217
+ throw new MarvinAuthError(
218
+ `Authentication failed: ${response.statusText}`,
219
+ response.status
220
+ );
221
+ }
222
+ if (response.status >= 500) {
223
+ throw new MarvinServerError(
224
+ `Server error: ${response.status} ${response.statusText}`,
225
+ response.status,
226
+ endpoint
227
+ );
228
+ }
163
229
  throw MarvinApiError.fromResponse(
164
230
  response.status,
165
231
  response.statusText,
@@ -174,18 +240,20 @@ var init_HttpClient = __esm({
174
240
  return await response.json();
175
241
  } catch (error) {
176
242
  clearTimeout(timeoutId);
177
- if (error instanceof MarvinApiError) {
243
+ if (error instanceof MarvinApiError || error instanceof MarvinNotFoundError || error instanceof MarvinAuthError || error instanceof MarvinServerError) {
178
244
  throw error;
179
245
  }
180
246
  if (error instanceof Error) {
181
247
  if (error.name === "AbortError") {
182
- throw new MarvinApiError(
248
+ throw new MarvinNetworkError(
183
249
  `Request timeout after ${this.timeout}ms`,
184
- 408,
185
- endpoint
250
+ error
186
251
  );
187
252
  }
188
- throw new MarvinApiError(error.message, 0, endpoint);
253
+ throw new MarvinNetworkError(
254
+ `Network error: ${error.message}`,
255
+ error
256
+ );
189
257
  }
190
258
  throw error;
191
259
  }
@@ -382,6 +450,10 @@ export {
382
450
  MarvinAuthError,
383
451
  MarvinConfigError,
384
452
  MarvinValidationError,
453
+ MarvinNotFoundError,
454
+ MarvinNetworkError,
455
+ MarvinServerError,
456
+ init_errors,
385
457
  HttpClient,
386
458
  BearerTokenAuth,
387
459
  SessionAuth,
package/dist/index.cjs CHANGED
@@ -27,14 +27,21 @@ __export(config_exports, {
27
27
  validateConfig: () => validateConfig
28
28
  });
29
29
  function validateConfig(config) {
30
+ const receivedKeys = Object.keys(config).join(", ");
30
31
  if (!config.apiUrl) {
31
- throw new Error("MARVIN_API_URL is required");
32
+ throw new Error(
33
+ `Missing required config parameter 'apiUrl'. Received config keys: ${receivedKeys || "(none)"}. Set this to your MARVIN_API_URL environment variable value.`
34
+ );
32
35
  }
33
36
  if (!config.siteClientToken) {
34
- throw new Error("MARVIN_SITE_CLIENT_TOKEN is required");
37
+ throw new Error(
38
+ `Missing required config parameter 'siteClientToken'. Received config keys: ${receivedKeys}. Set this to your MARVIN_SITE_CLIENT_TOKEN environment variable value.`
39
+ );
35
40
  }
36
41
  if (!config.workspaceSlug) {
37
- throw new Error("MARVIN_WORKSPACE_SLUG is required");
42
+ throw new Error(
43
+ `Missing required config parameter 'workspaceSlug'. Received config keys: ${receivedKeys}. Set this to your MARVIN_WORKSPACE_SLUG environment variable value.`
44
+ );
38
45
  }
39
46
  }
40
47
  function createConfigFromEnv(overrides) {
@@ -43,8 +50,10 @@ function createConfigFromEnv(overrides) {
43
50
  siteClientToken: overrides?.siteClientToken || process.env.MARVIN_SITE_CLIENT_TOKEN || "",
44
51
  workspaceSlug: overrides?.workspaceSlug || process.env.MARVIN_WORKSPACE_SLUG || "",
45
52
  autoInitialize: overrides?.autoInitialize ?? false,
46
- cacheDuration: overrides?.cacheDuration ?? 5 * 60 * 1e3
53
+ cacheDuration: overrides?.cacheDuration ?? 5 * 60 * 1e3,
47
54
  // 5 minutes
55
+ debug: overrides?.debug ?? process.env.MARVIN_DEBUG === "true",
56
+ logger: overrides?.logger ?? console
48
57
  };
49
58
  }
50
59
  var init_config = __esm({
@@ -54,7 +63,7 @@ var init_config = __esm({
54
63
  });
55
64
 
56
65
  // src/core/errors/MarvinError.ts
57
- var MarvinError, MarvinApiError, MarvinAuthError, MarvinConfigError, MarvinValidationError;
66
+ var MarvinError, MarvinApiError, MarvinAuthError, MarvinConfigError, MarvinValidationError, MarvinNotFoundError, MarvinNetworkError, MarvinServerError;
58
67
  var init_MarvinError = __esm({
59
68
  "src/core/errors/MarvinError.ts"() {
60
69
  "use strict";
@@ -110,6 +119,33 @@ ${body}` : ""}`;
110
119
  }
111
120
  field;
112
121
  };
122
+ MarvinNotFoundError = class _MarvinNotFoundError extends MarvinError {
123
+ constructor(message, endpoint) {
124
+ super(message, "NOT_FOUND", 404);
125
+ this.endpoint = endpoint;
126
+ this.name = "MarvinNotFoundError";
127
+ Object.setPrototypeOf(this, _MarvinNotFoundError.prototype);
128
+ }
129
+ endpoint;
130
+ };
131
+ MarvinNetworkError = class _MarvinNetworkError extends MarvinError {
132
+ constructor(message, originalError) {
133
+ super(message, "NETWORK_ERROR");
134
+ this.originalError = originalError;
135
+ this.name = "MarvinNetworkError";
136
+ Object.setPrototypeOf(this, _MarvinNetworkError.prototype);
137
+ }
138
+ originalError;
139
+ };
140
+ MarvinServerError = class _MarvinServerError extends MarvinError {
141
+ constructor(message, statusCode, endpoint) {
142
+ super(message, "SERVER_ERROR", statusCode);
143
+ this.endpoint = endpoint;
144
+ this.name = "MarvinServerError";
145
+ Object.setPrototypeOf(this, _MarvinServerError.prototype);
146
+ }
147
+ endpoint;
148
+ };
113
149
  }
114
150
  });
115
151
 
@@ -133,12 +169,16 @@ var init_HttpClient = __esm({
133
169
  defaultHeaders;
134
170
  credentials;
135
171
  timeout;
172
+ debug;
173
+ logger;
136
174
  constructor(config) {
137
175
  this.baseUrl = config.baseUrl;
138
176
  this.auth = config.auth;
139
177
  this.defaultHeaders = config.defaultHeaders || {};
140
178
  this.credentials = config.credentials || "same-origin";
141
179
  this.timeout = config.timeout || 3e4;
180
+ this.debug = config.debug ?? false;
181
+ this.logger = config.logger ?? console;
142
182
  }
143
183
  /**
144
184
  * Build full URL from endpoint
@@ -180,6 +220,10 @@ var init_HttpClient = __esm({
180
220
  const headers = this.buildHeaders(options?.headers, isFormData);
181
221
  const controller = new AbortController();
182
222
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
223
+ const startTime = Date.now();
224
+ if (this.debug) {
225
+ this.logger.log(`[Marvin] ${method} ${url}`);
226
+ }
183
227
  try {
184
228
  const response = await fetch(url, {
185
229
  method,
@@ -189,11 +233,42 @@ var init_HttpClient = __esm({
189
233
  signal: controller.signal
190
234
  });
191
235
  clearTimeout(timeoutId);
236
+ const duration = Date.now() - startTime;
237
+ if (this.debug) {
238
+ this.logger.log(
239
+ `[Marvin] ${response.status} ${response.statusText} (${duration}ms)`
240
+ );
241
+ }
192
242
  if (response.status === 401 && this.auth.handleUnauthorized) {
193
243
  await this.auth.handleUnauthorized();
194
244
  }
195
245
  if (!response.ok) {
196
246
  const errorText = await response.text().catch(() => "");
247
+ if (this.debug) {
248
+ this.logger.error(
249
+ `[Marvin] Error response: ${response.status} ${response.statusText}`,
250
+ errorText ? `Body: ${errorText}` : ""
251
+ );
252
+ }
253
+ if (response.status === 404) {
254
+ throw new MarvinNotFoundError(
255
+ `Resource not found: ${endpoint}`,
256
+ endpoint
257
+ );
258
+ }
259
+ if (response.status === 401 || response.status === 403) {
260
+ throw new MarvinAuthError(
261
+ `Authentication failed: ${response.statusText}`,
262
+ response.status
263
+ );
264
+ }
265
+ if (response.status >= 500) {
266
+ throw new MarvinServerError(
267
+ `Server error: ${response.status} ${response.statusText}`,
268
+ response.status,
269
+ endpoint
270
+ );
271
+ }
197
272
  throw MarvinApiError.fromResponse(
198
273
  response.status,
199
274
  response.statusText,
@@ -208,18 +283,20 @@ var init_HttpClient = __esm({
208
283
  return await response.json();
209
284
  } catch (error) {
210
285
  clearTimeout(timeoutId);
211
- if (error instanceof MarvinApiError) {
286
+ if (error instanceof MarvinApiError || error instanceof MarvinNotFoundError || error instanceof MarvinAuthError || error instanceof MarvinServerError) {
212
287
  throw error;
213
288
  }
214
289
  if (error instanceof Error) {
215
290
  if (error.name === "AbortError") {
216
- throw new MarvinApiError(
291
+ throw new MarvinNetworkError(
217
292
  `Request timeout after ${this.timeout}ms`,
218
- 408,
219
- endpoint
293
+ error
220
294
  );
221
295
  }
222
- throw new MarvinApiError(error.message, 0, endpoint);
296
+ throw new MarvinNetworkError(
297
+ `Network error: ${error.message}`,
298
+ error
299
+ );
223
300
  }
224
301
  throw error;
225
302
  }
@@ -418,7 +495,9 @@ var init_http2 = __esm({
418
495
  super({
419
496
  baseUrl: config.apiUrl,
420
497
  auth: new BearerTokenAuth(config.siteClientToken),
421
- credentials: "same-origin"
498
+ credentials: "same-origin",
499
+ debug: config.debug,
500
+ logger: config.logger
422
501
  });
423
502
  }
424
503
  /**
@@ -555,6 +634,7 @@ var init_entries = __esm({
555
634
  "src/entries/entries.ts"() {
556
635
  "use strict";
557
636
  init_entry();
637
+ init_errors();
558
638
  EntriesModule = class {
559
639
  constructor(http, workspaceSlug) {
560
640
  this.http = http;
@@ -579,11 +659,19 @@ var init_entries = __esm({
579
659
  }
580
660
  /**
581
661
  * Get a single entry by slug
662
+ * Returns null if entry is not found (404)
582
663
  */
583
664
  async get(slug) {
584
- const endpoint = `/api/publish/${this.workspaceSlug}/entries/${slug}`;
585
- const data = await this.http.fetch(endpoint);
586
- return new Entry(data, this.http, this.workspaceSlug);
665
+ try {
666
+ const endpoint = `/api/publish/${this.workspaceSlug}/entries/${slug}`;
667
+ const data = await this.http.fetch(endpoint);
668
+ return new Entry(data, this.http, this.workspaceSlug);
669
+ } catch (error) {
670
+ if (error instanceof MarvinNotFoundError) {
671
+ return null;
672
+ }
673
+ throw error;
674
+ }
587
675
  }
588
676
  /**
589
677
  * Convenience: Get all pages
@@ -689,6 +777,7 @@ var init_collections = __esm({
689
777
  "src/collections/collections.ts"() {
690
778
  "use strict";
691
779
  init_collection();
780
+ init_errors();
692
781
  CollectionsModule = class {
693
782
  constructor(http, workspaceSlug) {
694
783
  this.http = http;
@@ -706,19 +795,63 @@ var init_collections = __esm({
706
795
  }
707
796
  /**
708
797
  * Get a single collection by slug
798
+ * Returns null if collection is not found (404)
709
799
  */
710
800
  async get(slug) {
711
- const endpoint = `/api/publish/${this.workspaceSlug}/collections/${slug}`;
712
- const data = await this.http.fetch(endpoint);
713
- return new Collection(data, this.http, this.workspaceSlug);
801
+ try {
802
+ const endpoint = `/api/publish/${this.workspaceSlug}/collections/${slug}`;
803
+ const data = await this.http.fetch(endpoint);
804
+ return new Collection(data, this.http, this.workspaceSlug);
805
+ } catch (error) {
806
+ if (error instanceof MarvinNotFoundError) {
807
+ return null;
808
+ }
809
+ throw error;
810
+ }
714
811
  }
715
812
  /**
716
813
  * Get entries in a collection
814
+ * Returns empty array if collection is not found
717
815
  */
718
816
  async entries(slug) {
719
817
  const collection = await this.get(slug);
818
+ if (!collection) {
819
+ return [];
820
+ }
720
821
  return collection.entries();
721
822
  }
823
+ /**
824
+ * Try multiple collection slugs in fallback order
825
+ * Returns the first collection that exists and has entries
826
+ *
827
+ * @param slugs - Collection slugs to try in order
828
+ * @param options - Options for fallback behavior
829
+ * @returns Entries from the first matching collection, or empty array
830
+ *
831
+ * @example
832
+ * ```ts
833
+ * // Try bench-notes, then journal, then blog
834
+ * const entries = await collections.fallback(['bench-notes', 'journal', 'blog']);
835
+ * ```
836
+ */
837
+ async fallback(slugs, options = {}) {
838
+ const { requireEntries = true } = options;
839
+ for (const slug of slugs) {
840
+ try {
841
+ const collection = await this.get(slug);
842
+ if (!collection) {
843
+ continue;
844
+ }
845
+ const entries = await collection.entries();
846
+ if (!requireEntries || entries.length > 0) {
847
+ return entries;
848
+ }
849
+ } catch (error) {
850
+ continue;
851
+ }
852
+ }
853
+ return [];
854
+ }
722
855
  };
723
856
  }
724
857
  });
@@ -1081,6 +1214,23 @@ var init_marvin = __esm({
1081
1214
  async resource(slug) {
1082
1215
  return this.workspace.resources.get(slug);
1083
1216
  }
1217
+ /**
1218
+ * Try multiple collection slugs in fallback order
1219
+ * Returns entries from the first collection that exists and has content
1220
+ *
1221
+ * @param slugs - Collection slugs to try in order
1222
+ * @param options - Options for fallback behavior
1223
+ * @returns Entries from the first matching collection, or empty array
1224
+ *
1225
+ * @example
1226
+ * ```ts
1227
+ * // Try bench-notes, then journal, then blog
1228
+ * const entries = await client.collectionFallback(['bench-notes', 'journal', 'blog']);
1229
+ * ```
1230
+ */
1231
+ async collectionFallback(slugs, options) {
1232
+ return this.workspace.collections.fallback(slugs, options);
1233
+ }
1084
1234
  // ======================
1085
1235
  // Backwards-Compatible Publishing API
1086
1236
  // ======================
@@ -1104,6 +1254,9 @@ var init_marvin = __esm({
1104
1254
  */
1105
1255
  async getEntry(slug) {
1106
1256
  const entry = await this.workspace.entries.get(slug);
1257
+ if (!entry) {
1258
+ return null;
1259
+ }
1107
1260
  return entry.toJSON();
1108
1261
  }
1109
1262
  /**
@@ -1119,6 +1272,9 @@ var init_marvin = __esm({
1119
1272
  */
1120
1273
  async getCollection(slug) {
1121
1274
  const collection = await this.workspace.collections.get(slug);
1275
+ if (!collection) {
1276
+ return null;
1277
+ }
1122
1278
  return collection.toJSON();
1123
1279
  }
1124
1280
  /**
@@ -1175,6 +1331,9 @@ __export(index_exports, {
1175
1331
  MarvinClient: () => MarvinClient,
1176
1332
  MarvinConfigError: () => MarvinConfigError,
1177
1333
  MarvinError: () => MarvinError,
1334
+ MarvinNetworkError: () => MarvinNetworkError,
1335
+ MarvinNotFoundError: () => MarvinNotFoundError,
1336
+ MarvinServerError: () => MarvinServerError,
1178
1337
  MarvinValidationError: () => MarvinValidationError,
1179
1338
  NoAuth: () => NoAuth,
1180
1339
  Resource: () => Resource,
@@ -1271,6 +1430,9 @@ function createMarvinClient(config) {
1271
1430
  MarvinClient,
1272
1431
  MarvinConfigError,
1273
1432
  MarvinError,
1433
+ MarvinNetworkError,
1434
+ MarvinNotFoundError,
1435
+ MarvinServerError,
1274
1436
  MarvinValidationError,
1275
1437
  NoAuth,
1276
1438
  Resource,
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { MarvinResource, MarvinEntry, MarvinAsset, MarvinCollection, MarvinEntryType, MarvinSite, GetResourcesOptions as GetResourcesOptions$1 } from './types/index.cjs';
2
2
  export { GetAssetsOptions, GetEntriesOptions, MarvinPublishResponse } from './types/index.cjs';
3
- import { H as HttpClient } from './HttpClient-CLtgQGXH.cjs';
4
- export { A as AuthStrategy, B as BearerTokenAuth, a as HttpClientConfig, N as NoAuth, S as SessionAuth } from './HttpClient-CLtgQGXH.cjs';
3
+ import { H as HttpClient } from './HttpClient-BX5Gqd0w.cjs';
4
+ export { A as AuthStrategy, B as BearerTokenAuth, a as HttpClientConfig, N as NoAuth, S as SessionAuth } from './HttpClient-BX5Gqd0w.cjs';
5
5
 
6
6
  /**
7
7
  * Marvin SDK Configuration
@@ -14,6 +14,14 @@ interface MarvinConfig {
14
14
  autoInitialize?: boolean;
15
15
  /** Cache duration in milliseconds (default: 5 minutes) */
16
16
  cacheDuration?: number;
17
+ /** Enable debug logging for HTTP requests and responses */
18
+ debug?: boolean;
19
+ /** Custom logger function (defaults to console) */
20
+ logger?: {
21
+ log: (message: string, ...args: unknown[]) => void;
22
+ warn: (message: string, ...args: unknown[]) => void;
23
+ error: (message: string, ...args: unknown[]) => void;
24
+ };
17
25
  }
18
26
  declare function createConfigFromEnv(overrides?: Partial<MarvinConfig>): MarvinConfig;
19
27
 
@@ -54,6 +62,27 @@ declare class MarvinValidationError extends MarvinError {
54
62
  readonly field?: string | undefined;
55
63
  constructor(message: string, field?: string | undefined);
56
64
  }
65
+ /**
66
+ * Resource not found errors (404)
67
+ */
68
+ declare class MarvinNotFoundError extends MarvinError {
69
+ readonly endpoint: string;
70
+ constructor(message: string, endpoint: string);
71
+ }
72
+ /**
73
+ * Network/connectivity errors
74
+ */
75
+ declare class MarvinNetworkError extends MarvinError {
76
+ readonly originalError?: Error | undefined;
77
+ constructor(message: string, originalError?: Error | undefined);
78
+ }
79
+ /**
80
+ * Server errors (5xx)
81
+ */
82
+ declare class MarvinServerError extends MarvinError {
83
+ readonly endpoint: string;
84
+ constructor(message: string, statusCode: number, endpoint: string);
85
+ }
57
86
 
58
87
  /**
59
88
  * Pagination response types and helpers
@@ -270,12 +299,31 @@ declare class CollectionsModule {
270
299
  list(): Promise<MarvinCollection[]>;
271
300
  /**
272
301
  * Get a single collection by slug
302
+ * Returns null if collection is not found (404)
273
303
  */
274
- get(slug: string): Promise<Collection>;
304
+ get(slug: string): Promise<Collection | null>;
275
305
  /**
276
306
  * Get entries in a collection
307
+ * Returns empty array if collection is not found
277
308
  */
278
309
  entries(slug: string): Promise<MarvinEntry[]>;
310
+ /**
311
+ * Try multiple collection slugs in fallback order
312
+ * Returns the first collection that exists and has entries
313
+ *
314
+ * @param slugs - Collection slugs to try in order
315
+ * @param options - Options for fallback behavior
316
+ * @returns Entries from the first matching collection, or empty array
317
+ *
318
+ * @example
319
+ * ```ts
320
+ * // Try bench-notes, then journal, then blog
321
+ * const entries = await collections.fallback(['bench-notes', 'journal', 'blog']);
322
+ * ```
323
+ */
324
+ fallback(slugs: string[], options?: {
325
+ requireEntries?: boolean;
326
+ }): Promise<MarvinEntry[]>;
279
327
  }
280
328
 
281
329
  declare class Entry {
@@ -365,8 +413,9 @@ declare class EntriesModule {
365
413
  list(options?: GetEntriesOptions): Promise<MarvinEntry[]>;
366
414
  /**
367
415
  * Get a single entry by slug
416
+ * Returns null if entry is not found (404)
368
417
  */
369
- get(slug: string): Promise<Entry>;
418
+ get(slug: string): Promise<Entry | null>;
370
419
  /**
371
420
  * Convenience: Get all pages
372
421
  */
@@ -465,11 +514,11 @@ declare class MarvinClient {
465
514
  /**
466
515
  * Get a single entry by slug
467
516
  */
468
- entry(slug: string): Promise<Entry>;
517
+ entry(slug: string): Promise<Entry | null>;
469
518
  /**
470
519
  * Get a single collection by slug
471
520
  */
472
- collection(slug: string): Promise<Collection>;
521
+ collection(slug: string): Promise<Collection | null>;
473
522
  /**
474
523
  * Get all pages
475
524
  */
@@ -486,6 +535,23 @@ declare class MarvinClient {
486
535
  * Get a single resource by slug
487
536
  */
488
537
  resource(slug: string): Promise<Resource>;
538
+ /**
539
+ * Try multiple collection slugs in fallback order
540
+ * Returns entries from the first collection that exists and has content
541
+ *
542
+ * @param slugs - Collection slugs to try in order
543
+ * @param options - Options for fallback behavior
544
+ * @returns Entries from the first matching collection, or empty array
545
+ *
546
+ * @example
547
+ * ```ts
548
+ * // Try bench-notes, then journal, then blog
549
+ * const entries = await client.collectionFallback(['bench-notes', 'journal', 'blog']);
550
+ * ```
551
+ */
552
+ collectionFallback(slugs: string[], options?: {
553
+ requireEntries?: boolean;
554
+ }): Promise<MarvinEntry[]>;
489
555
  /**
490
556
  * @deprecated Use workspace.site or marvin.site instead
491
557
  * Get site configuration
@@ -500,7 +566,7 @@ declare class MarvinClient {
500
566
  * @deprecated Use workspace.entries.get() instead
501
567
  * Get a single published entry by slug
502
568
  */
503
- getEntry(slug: string): Promise<MarvinEntry>;
569
+ getEntry(slug: string): Promise<MarvinEntry | null>;
504
570
  /**
505
571
  * @deprecated Use workspace.collections.list() instead
506
572
  * Get all collections
@@ -510,7 +576,7 @@ declare class MarvinClient {
510
576
  * @deprecated Use workspace.collections.get() instead
511
577
  * Get a single collection by slug
512
578
  */
513
- getCollection(slug: string): Promise<MarvinCollection>;
579
+ getCollection(slug: string): Promise<MarvinCollection | null>;
514
580
  /**
515
581
  * @deprecated Use workspace.collections.entries() instead
516
582
  * Get all entries in a collection
@@ -621,4 +687,4 @@ declare function createAuthClient(apiUrl: string): AuthClient;
621
687
  */
622
688
  declare function createMarvinClient(config?: Partial<MarvinConfig>): MarvinClient;
623
689
 
624
- export { AuthClient, type AuthToken, Collection, Entry, type ForgotPasswordRequest, GetResourcesOptions$1 as GetResourcesOptions, HttpClient, type LoginRequest, MarvinApiError, MarvinAsset, MarvinAuthError, MarvinCache, MarvinClient, MarvinCollection, type MarvinConfig, MarvinConfigError, MarvinEntry, MarvinEntryType, MarvinError, MarvinResource, MarvinSite, MarvinValidationError, type PaginatedResponse, type PaginationMeta, type PaginationParams, type ResetPasswordRequest, Resource, type UserRegistration, Workspace, createAuthClient, createConfigFromEnv, createMarvinClient, createPaginatedResponse, extractData, isPaginatedResponse, offsetToPage, pageToOffset };
690
+ export { AuthClient, type AuthToken, Collection, Entry, type ForgotPasswordRequest, GetResourcesOptions$1 as GetResourcesOptions, HttpClient, type LoginRequest, MarvinApiError, MarvinAsset, MarvinAuthError, MarvinCache, MarvinClient, MarvinCollection, type MarvinConfig, MarvinConfigError, MarvinEntry, MarvinEntryType, MarvinError, MarvinNetworkError, MarvinNotFoundError, MarvinResource, MarvinServerError, MarvinSite, MarvinValidationError, type PaginatedResponse, type PaginationMeta, type PaginationParams, type ResetPasswordRequest, Resource, type UserRegistration, Workspace, createAuthClient, createConfigFromEnv, createMarvinClient, createPaginatedResponse, extractData, isPaginatedResponse, offsetToPage, pageToOffset };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { MarvinResource, MarvinEntry, MarvinAsset, MarvinCollection, MarvinEntryType, MarvinSite, GetResourcesOptions as GetResourcesOptions$1 } from './types/index.js';
2
2
  export { GetAssetsOptions, GetEntriesOptions, MarvinPublishResponse } from './types/index.js';
3
- import { H as HttpClient } from './HttpClient-CLtgQGXH.js';
4
- export { A as AuthStrategy, B as BearerTokenAuth, a as HttpClientConfig, N as NoAuth, S as SessionAuth } from './HttpClient-CLtgQGXH.js';
3
+ import { H as HttpClient } from './HttpClient-BX5Gqd0w.js';
4
+ export { A as AuthStrategy, B as BearerTokenAuth, a as HttpClientConfig, N as NoAuth, S as SessionAuth } from './HttpClient-BX5Gqd0w.js';
5
5
 
6
6
  /**
7
7
  * Marvin SDK Configuration
@@ -14,6 +14,14 @@ interface MarvinConfig {
14
14
  autoInitialize?: boolean;
15
15
  /** Cache duration in milliseconds (default: 5 minutes) */
16
16
  cacheDuration?: number;
17
+ /** Enable debug logging for HTTP requests and responses */
18
+ debug?: boolean;
19
+ /** Custom logger function (defaults to console) */
20
+ logger?: {
21
+ log: (message: string, ...args: unknown[]) => void;
22
+ warn: (message: string, ...args: unknown[]) => void;
23
+ error: (message: string, ...args: unknown[]) => void;
24
+ };
17
25
  }
18
26
  declare function createConfigFromEnv(overrides?: Partial<MarvinConfig>): MarvinConfig;
19
27
 
@@ -54,6 +62,27 @@ declare class MarvinValidationError extends MarvinError {
54
62
  readonly field?: string | undefined;
55
63
  constructor(message: string, field?: string | undefined);
56
64
  }
65
+ /**
66
+ * Resource not found errors (404)
67
+ */
68
+ declare class MarvinNotFoundError extends MarvinError {
69
+ readonly endpoint: string;
70
+ constructor(message: string, endpoint: string);
71
+ }
72
+ /**
73
+ * Network/connectivity errors
74
+ */
75
+ declare class MarvinNetworkError extends MarvinError {
76
+ readonly originalError?: Error | undefined;
77
+ constructor(message: string, originalError?: Error | undefined);
78
+ }
79
+ /**
80
+ * Server errors (5xx)
81
+ */
82
+ declare class MarvinServerError extends MarvinError {
83
+ readonly endpoint: string;
84
+ constructor(message: string, statusCode: number, endpoint: string);
85
+ }
57
86
 
58
87
  /**
59
88
  * Pagination response types and helpers
@@ -270,12 +299,31 @@ declare class CollectionsModule {
270
299
  list(): Promise<MarvinCollection[]>;
271
300
  /**
272
301
  * Get a single collection by slug
302
+ * Returns null if collection is not found (404)
273
303
  */
274
- get(slug: string): Promise<Collection>;
304
+ get(slug: string): Promise<Collection | null>;
275
305
  /**
276
306
  * Get entries in a collection
307
+ * Returns empty array if collection is not found
277
308
  */
278
309
  entries(slug: string): Promise<MarvinEntry[]>;
310
+ /**
311
+ * Try multiple collection slugs in fallback order
312
+ * Returns the first collection that exists and has entries
313
+ *
314
+ * @param slugs - Collection slugs to try in order
315
+ * @param options - Options for fallback behavior
316
+ * @returns Entries from the first matching collection, or empty array
317
+ *
318
+ * @example
319
+ * ```ts
320
+ * // Try bench-notes, then journal, then blog
321
+ * const entries = await collections.fallback(['bench-notes', 'journal', 'blog']);
322
+ * ```
323
+ */
324
+ fallback(slugs: string[], options?: {
325
+ requireEntries?: boolean;
326
+ }): Promise<MarvinEntry[]>;
279
327
  }
280
328
 
281
329
  declare class Entry {
@@ -365,8 +413,9 @@ declare class EntriesModule {
365
413
  list(options?: GetEntriesOptions): Promise<MarvinEntry[]>;
366
414
  /**
367
415
  * Get a single entry by slug
416
+ * Returns null if entry is not found (404)
368
417
  */
369
- get(slug: string): Promise<Entry>;
418
+ get(slug: string): Promise<Entry | null>;
370
419
  /**
371
420
  * Convenience: Get all pages
372
421
  */
@@ -465,11 +514,11 @@ declare class MarvinClient {
465
514
  /**
466
515
  * Get a single entry by slug
467
516
  */
468
- entry(slug: string): Promise<Entry>;
517
+ entry(slug: string): Promise<Entry | null>;
469
518
  /**
470
519
  * Get a single collection by slug
471
520
  */
472
- collection(slug: string): Promise<Collection>;
521
+ collection(slug: string): Promise<Collection | null>;
473
522
  /**
474
523
  * Get all pages
475
524
  */
@@ -486,6 +535,23 @@ declare class MarvinClient {
486
535
  * Get a single resource by slug
487
536
  */
488
537
  resource(slug: string): Promise<Resource>;
538
+ /**
539
+ * Try multiple collection slugs in fallback order
540
+ * Returns entries from the first collection that exists and has content
541
+ *
542
+ * @param slugs - Collection slugs to try in order
543
+ * @param options - Options for fallback behavior
544
+ * @returns Entries from the first matching collection, or empty array
545
+ *
546
+ * @example
547
+ * ```ts
548
+ * // Try bench-notes, then journal, then blog
549
+ * const entries = await client.collectionFallback(['bench-notes', 'journal', 'blog']);
550
+ * ```
551
+ */
552
+ collectionFallback(slugs: string[], options?: {
553
+ requireEntries?: boolean;
554
+ }): Promise<MarvinEntry[]>;
489
555
  /**
490
556
  * @deprecated Use workspace.site or marvin.site instead
491
557
  * Get site configuration
@@ -500,7 +566,7 @@ declare class MarvinClient {
500
566
  * @deprecated Use workspace.entries.get() instead
501
567
  * Get a single published entry by slug
502
568
  */
503
- getEntry(slug: string): Promise<MarvinEntry>;
569
+ getEntry(slug: string): Promise<MarvinEntry | null>;
504
570
  /**
505
571
  * @deprecated Use workspace.collections.list() instead
506
572
  * Get all collections
@@ -510,7 +576,7 @@ declare class MarvinClient {
510
576
  * @deprecated Use workspace.collections.get() instead
511
577
  * Get a single collection by slug
512
578
  */
513
- getCollection(slug: string): Promise<MarvinCollection>;
579
+ getCollection(slug: string): Promise<MarvinCollection | null>;
514
580
  /**
515
581
  * @deprecated Use workspace.collections.entries() instead
516
582
  * Get all entries in a collection
@@ -621,4 +687,4 @@ declare function createAuthClient(apiUrl: string): AuthClient;
621
687
  */
622
688
  declare function createMarvinClient(config?: Partial<MarvinConfig>): MarvinClient;
623
689
 
624
- export { AuthClient, type AuthToken, Collection, Entry, type ForgotPasswordRequest, GetResourcesOptions$1 as GetResourcesOptions, HttpClient, type LoginRequest, MarvinApiError, MarvinAsset, MarvinAuthError, MarvinCache, MarvinClient, MarvinCollection, type MarvinConfig, MarvinConfigError, MarvinEntry, MarvinEntryType, MarvinError, MarvinResource, MarvinSite, MarvinValidationError, type PaginatedResponse, type PaginationMeta, type PaginationParams, type ResetPasswordRequest, Resource, type UserRegistration, Workspace, createAuthClient, createConfigFromEnv, createMarvinClient, createPaginatedResponse, extractData, isPaginatedResponse, offsetToPage, pageToOffset };
690
+ export { AuthClient, type AuthToken, Collection, Entry, type ForgotPasswordRequest, GetResourcesOptions$1 as GetResourcesOptions, HttpClient, type LoginRequest, MarvinApiError, MarvinAsset, MarvinAuthError, MarvinCache, MarvinClient, MarvinCollection, type MarvinConfig, MarvinConfigError, MarvinEntry, MarvinEntryType, MarvinError, MarvinNetworkError, MarvinNotFoundError, MarvinResource, MarvinServerError, MarvinSite, MarvinValidationError, type PaginatedResponse, type PaginationMeta, type PaginationParams, type ResetPasswordRequest, Resource, type UserRegistration, Workspace, createAuthClient, createConfigFromEnv, createMarvinClient, createPaginatedResponse, extractData, isPaginatedResponse, offsetToPage, pageToOffset };
package/dist/index.js CHANGED
@@ -6,6 +6,9 @@ import {
6
6
  MarvinCache,
7
7
  MarvinConfigError,
8
8
  MarvinError,
9
+ MarvinNetworkError,
10
+ MarvinNotFoundError,
11
+ MarvinServerError,
9
12
  MarvinValidationError,
10
13
  NoAuth,
11
14
  SessionAuth,
@@ -15,10 +18,11 @@ import {
15
18
  createPaginatedResponse,
16
19
  extractData,
17
20
  init_core,
21
+ init_errors,
18
22
  isPaginatedResponse,
19
23
  offsetToPage,
20
24
  pageToOffset
21
- } from "./chunk-25F3IF6S.js";
25
+ } from "./chunk-QBHM2XNX.js";
22
26
 
23
27
  // src/client/config.ts
24
28
  var config_exports = {};
@@ -27,14 +31,21 @@ __export(config_exports, {
27
31
  validateConfig: () => validateConfig
28
32
  });
29
33
  function validateConfig(config) {
34
+ const receivedKeys = Object.keys(config).join(", ");
30
35
  if (!config.apiUrl) {
31
- throw new Error("MARVIN_API_URL is required");
36
+ throw new Error(
37
+ `Missing required config parameter 'apiUrl'. Received config keys: ${receivedKeys || "(none)"}. Set this to your MARVIN_API_URL environment variable value.`
38
+ );
32
39
  }
33
40
  if (!config.siteClientToken) {
34
- throw new Error("MARVIN_SITE_CLIENT_TOKEN is required");
41
+ throw new Error(
42
+ `Missing required config parameter 'siteClientToken'. Received config keys: ${receivedKeys}. Set this to your MARVIN_SITE_CLIENT_TOKEN environment variable value.`
43
+ );
35
44
  }
36
45
  if (!config.workspaceSlug) {
37
- throw new Error("MARVIN_WORKSPACE_SLUG is required");
46
+ throw new Error(
47
+ `Missing required config parameter 'workspaceSlug'. Received config keys: ${receivedKeys}. Set this to your MARVIN_WORKSPACE_SLUG environment variable value.`
48
+ );
38
49
  }
39
50
  }
40
51
  function createConfigFromEnv(overrides) {
@@ -43,8 +54,10 @@ function createConfigFromEnv(overrides) {
43
54
  siteClientToken: overrides?.siteClientToken || process.env.MARVIN_SITE_CLIENT_TOKEN || "",
44
55
  workspaceSlug: overrides?.workspaceSlug || process.env.MARVIN_WORKSPACE_SLUG || "",
45
56
  autoInitialize: overrides?.autoInitialize ?? false,
46
- cacheDuration: overrides?.cacheDuration ?? 5 * 60 * 1e3
57
+ cacheDuration: overrides?.cacheDuration ?? 5 * 60 * 1e3,
47
58
  // 5 minutes
59
+ debug: overrides?.debug ?? process.env.MARVIN_DEBUG === "true",
60
+ logger: overrides?.logger ?? console
48
61
  };
49
62
  }
50
63
  var init_config = __esm({
@@ -64,7 +77,9 @@ var init_http = __esm({
64
77
  super({
65
78
  baseUrl: config.apiUrl,
66
79
  auth: new BearerTokenAuth(config.siteClientToken),
67
- credentials: "same-origin"
80
+ credentials: "same-origin",
81
+ debug: config.debug,
82
+ logger: config.logger
68
83
  });
69
84
  }
70
85
  /**
@@ -201,6 +216,7 @@ var init_entries = __esm({
201
216
  "src/entries/entries.ts"() {
202
217
  "use strict";
203
218
  init_entry();
219
+ init_errors();
204
220
  EntriesModule = class {
205
221
  constructor(http, workspaceSlug) {
206
222
  this.http = http;
@@ -225,11 +241,19 @@ var init_entries = __esm({
225
241
  }
226
242
  /**
227
243
  * Get a single entry by slug
244
+ * Returns null if entry is not found (404)
228
245
  */
229
246
  async get(slug) {
230
- const endpoint = `/api/publish/${this.workspaceSlug}/entries/${slug}`;
231
- const data = await this.http.fetch(endpoint);
232
- return new Entry(data, this.http, this.workspaceSlug);
247
+ try {
248
+ const endpoint = `/api/publish/${this.workspaceSlug}/entries/${slug}`;
249
+ const data = await this.http.fetch(endpoint);
250
+ return new Entry(data, this.http, this.workspaceSlug);
251
+ } catch (error) {
252
+ if (error instanceof MarvinNotFoundError) {
253
+ return null;
254
+ }
255
+ throw error;
256
+ }
233
257
  }
234
258
  /**
235
259
  * Convenience: Get all pages
@@ -335,6 +359,7 @@ var init_collections = __esm({
335
359
  "src/collections/collections.ts"() {
336
360
  "use strict";
337
361
  init_collection();
362
+ init_errors();
338
363
  CollectionsModule = class {
339
364
  constructor(http, workspaceSlug) {
340
365
  this.http = http;
@@ -352,19 +377,63 @@ var init_collections = __esm({
352
377
  }
353
378
  /**
354
379
  * Get a single collection by slug
380
+ * Returns null if collection is not found (404)
355
381
  */
356
382
  async get(slug) {
357
- const endpoint = `/api/publish/${this.workspaceSlug}/collections/${slug}`;
358
- const data = await this.http.fetch(endpoint);
359
- return new Collection(data, this.http, this.workspaceSlug);
383
+ try {
384
+ const endpoint = `/api/publish/${this.workspaceSlug}/collections/${slug}`;
385
+ const data = await this.http.fetch(endpoint);
386
+ return new Collection(data, this.http, this.workspaceSlug);
387
+ } catch (error) {
388
+ if (error instanceof MarvinNotFoundError) {
389
+ return null;
390
+ }
391
+ throw error;
392
+ }
360
393
  }
361
394
  /**
362
395
  * Get entries in a collection
396
+ * Returns empty array if collection is not found
363
397
  */
364
398
  async entries(slug) {
365
399
  const collection = await this.get(slug);
400
+ if (!collection) {
401
+ return [];
402
+ }
366
403
  return collection.entries();
367
404
  }
405
+ /**
406
+ * Try multiple collection slugs in fallback order
407
+ * Returns the first collection that exists and has entries
408
+ *
409
+ * @param slugs - Collection slugs to try in order
410
+ * @param options - Options for fallback behavior
411
+ * @returns Entries from the first matching collection, or empty array
412
+ *
413
+ * @example
414
+ * ```ts
415
+ * // Try bench-notes, then journal, then blog
416
+ * const entries = await collections.fallback(['bench-notes', 'journal', 'blog']);
417
+ * ```
418
+ */
419
+ async fallback(slugs, options = {}) {
420
+ const { requireEntries = true } = options;
421
+ for (const slug of slugs) {
422
+ try {
423
+ const collection = await this.get(slug);
424
+ if (!collection) {
425
+ continue;
426
+ }
427
+ const entries = await collection.entries();
428
+ if (!requireEntries || entries.length > 0) {
429
+ return entries;
430
+ }
431
+ } catch (error) {
432
+ continue;
433
+ }
434
+ }
435
+ return [];
436
+ }
368
437
  };
369
438
  }
370
439
  });
@@ -727,6 +796,23 @@ var init_marvin = __esm({
727
796
  async resource(slug) {
728
797
  return this.workspace.resources.get(slug);
729
798
  }
799
+ /**
800
+ * Try multiple collection slugs in fallback order
801
+ * Returns entries from the first collection that exists and has content
802
+ *
803
+ * @param slugs - Collection slugs to try in order
804
+ * @param options - Options for fallback behavior
805
+ * @returns Entries from the first matching collection, or empty array
806
+ *
807
+ * @example
808
+ * ```ts
809
+ * // Try bench-notes, then journal, then blog
810
+ * const entries = await client.collectionFallback(['bench-notes', 'journal', 'blog']);
811
+ * ```
812
+ */
813
+ async collectionFallback(slugs, options) {
814
+ return this.workspace.collections.fallback(slugs, options);
815
+ }
730
816
  // ======================
731
817
  // Backwards-Compatible Publishing API
732
818
  // ======================
@@ -750,6 +836,9 @@ var init_marvin = __esm({
750
836
  */
751
837
  async getEntry(slug) {
752
838
  const entry = await this.workspace.entries.get(slug);
839
+ if (!entry) {
840
+ return null;
841
+ }
753
842
  return entry.toJSON();
754
843
  }
755
844
  /**
@@ -765,6 +854,9 @@ var init_marvin = __esm({
765
854
  */
766
855
  async getCollection(slug) {
767
856
  const collection = await this.workspace.collections.get(slug);
857
+ if (!collection) {
858
+ return null;
859
+ }
768
860
  return collection.toJSON();
769
861
  }
770
862
  /**
@@ -888,6 +980,9 @@ export {
888
980
  MarvinClient,
889
981
  MarvinConfigError,
890
982
  MarvinError,
983
+ MarvinNetworkError,
984
+ MarvinNotFoundError,
985
+ MarvinServerError,
891
986
  MarvinValidationError,
892
987
  NoAuth,
893
988
  Resource,
package/dist/platform.cjs CHANGED
@@ -77,6 +77,40 @@ ${body}` : ""}`;
77
77
  return new _MarvinApiError(message, status, endpoint, body);
78
78
  }
79
79
  };
80
+ var MarvinAuthError = class _MarvinAuthError extends MarvinError {
81
+ constructor(message, statusCode) {
82
+ super(message, "AUTH_ERROR", statusCode);
83
+ this.name = "MarvinAuthError";
84
+ Object.setPrototypeOf(this, _MarvinAuthError.prototype);
85
+ }
86
+ };
87
+ var MarvinNotFoundError = class _MarvinNotFoundError extends MarvinError {
88
+ constructor(message, endpoint) {
89
+ super(message, "NOT_FOUND", 404);
90
+ this.endpoint = endpoint;
91
+ this.name = "MarvinNotFoundError";
92
+ Object.setPrototypeOf(this, _MarvinNotFoundError.prototype);
93
+ }
94
+ endpoint;
95
+ };
96
+ var MarvinNetworkError = class _MarvinNetworkError extends MarvinError {
97
+ constructor(message, originalError) {
98
+ super(message, "NETWORK_ERROR");
99
+ this.originalError = originalError;
100
+ this.name = "MarvinNetworkError";
101
+ Object.setPrototypeOf(this, _MarvinNetworkError.prototype);
102
+ }
103
+ originalError;
104
+ };
105
+ var MarvinServerError = class _MarvinServerError extends MarvinError {
106
+ constructor(message, statusCode, endpoint) {
107
+ super(message, "SERVER_ERROR", statusCode);
108
+ this.endpoint = endpoint;
109
+ this.name = "MarvinServerError";
110
+ Object.setPrototypeOf(this, _MarvinServerError.prototype);
111
+ }
112
+ endpoint;
113
+ };
80
114
 
81
115
  // src/core/http/HttpClient.ts
82
116
  var HttpClient = class {
@@ -85,12 +119,16 @@ var HttpClient = class {
85
119
  defaultHeaders;
86
120
  credentials;
87
121
  timeout;
122
+ debug;
123
+ logger;
88
124
  constructor(config) {
89
125
  this.baseUrl = config.baseUrl;
90
126
  this.auth = config.auth;
91
127
  this.defaultHeaders = config.defaultHeaders || {};
92
128
  this.credentials = config.credentials || "same-origin";
93
129
  this.timeout = config.timeout || 3e4;
130
+ this.debug = config.debug ?? false;
131
+ this.logger = config.logger ?? console;
94
132
  }
95
133
  /**
96
134
  * Build full URL from endpoint
@@ -132,6 +170,10 @@ var HttpClient = class {
132
170
  const headers = this.buildHeaders(options?.headers, isFormData);
133
171
  const controller = new AbortController();
134
172
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
173
+ const startTime = Date.now();
174
+ if (this.debug) {
175
+ this.logger.log(`[Marvin] ${method} ${url}`);
176
+ }
135
177
  try {
136
178
  const response = await fetch(url, {
137
179
  method,
@@ -141,11 +183,42 @@ var HttpClient = class {
141
183
  signal: controller.signal
142
184
  });
143
185
  clearTimeout(timeoutId);
186
+ const duration = Date.now() - startTime;
187
+ if (this.debug) {
188
+ this.logger.log(
189
+ `[Marvin] ${response.status} ${response.statusText} (${duration}ms)`
190
+ );
191
+ }
144
192
  if (response.status === 401 && this.auth.handleUnauthorized) {
145
193
  await this.auth.handleUnauthorized();
146
194
  }
147
195
  if (!response.ok) {
148
196
  const errorText = await response.text().catch(() => "");
197
+ if (this.debug) {
198
+ this.logger.error(
199
+ `[Marvin] Error response: ${response.status} ${response.statusText}`,
200
+ errorText ? `Body: ${errorText}` : ""
201
+ );
202
+ }
203
+ if (response.status === 404) {
204
+ throw new MarvinNotFoundError(
205
+ `Resource not found: ${endpoint}`,
206
+ endpoint
207
+ );
208
+ }
209
+ if (response.status === 401 || response.status === 403) {
210
+ throw new MarvinAuthError(
211
+ `Authentication failed: ${response.statusText}`,
212
+ response.status
213
+ );
214
+ }
215
+ if (response.status >= 500) {
216
+ throw new MarvinServerError(
217
+ `Server error: ${response.status} ${response.statusText}`,
218
+ response.status,
219
+ endpoint
220
+ );
221
+ }
149
222
  throw MarvinApiError.fromResponse(
150
223
  response.status,
151
224
  response.statusText,
@@ -160,18 +233,20 @@ var HttpClient = class {
160
233
  return await response.json();
161
234
  } catch (error) {
162
235
  clearTimeout(timeoutId);
163
- if (error instanceof MarvinApiError) {
236
+ if (error instanceof MarvinApiError || error instanceof MarvinNotFoundError || error instanceof MarvinAuthError || error instanceof MarvinServerError) {
164
237
  throw error;
165
238
  }
166
239
  if (error instanceof Error) {
167
240
  if (error.name === "AbortError") {
168
- throw new MarvinApiError(
241
+ throw new MarvinNetworkError(
169
242
  `Request timeout after ${this.timeout}ms`,
170
- 408,
171
- endpoint
243
+ error
172
244
  );
173
245
  }
174
- throw new MarvinApiError(error.message, 0, endpoint);
246
+ throw new MarvinNetworkError(
247
+ `Network error: ${error.message}`,
248
+ error
249
+ );
175
250
  }
176
251
  throw error;
177
252
  }
@@ -1,4 +1,4 @@
1
- import { H as HttpClient } from './HttpClient-CLtgQGXH.cjs';
1
+ import { H as HttpClient } from './HttpClient-BX5Gqd0w.cjs';
2
2
 
3
3
  interface components {
4
4
  schemas: {
@@ -1,4 +1,4 @@
1
- import { H as HttpClient } from './HttpClient-CLtgQGXH.js';
1
+ import { H as HttpClient } from './HttpClient-BX5Gqd0w.js';
2
2
 
3
3
  interface components {
4
4
  schemas: {
package/dist/platform.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  HttpClient,
3
3
  SessionAuth,
4
4
  init_core
5
- } from "./chunk-25F3IF6S.js";
5
+ } from "./chunk-QBHM2XNX.js";
6
6
 
7
7
  // src/platform/client.ts
8
8
  init_core();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inneropen/marvin-sdk",
3
- "version": "2.0.0-next.10",
3
+ "version": "2.0.0-next.12",
4
4
  "description": "Official TypeScript SDK for Marvin CMS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",