@inneropen/marvin-sdk 2.0.0-next.11 → 2.0.0-next.13

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,
@@ -171,21 +237,28 @@ var init_HttpClient = __esm({
171
237
  if (!contentType || !contentType.includes("application/json")) {
172
238
  return void 0;
173
239
  }
174
- return await response.json();
240
+ const data = await response.json();
241
+ if (this.debug) {
242
+ const preview = typeof data === "object" && data !== null ? Array.isArray(data) ? `Array(${data.length})` : `Object with keys: ${Object.keys(data).join(", ")}` : String(data);
243
+ this.logger.log(`[Marvin] Response: ${preview}`);
244
+ }
245
+ return data;
175
246
  } catch (error) {
176
247
  clearTimeout(timeoutId);
177
- if (error instanceof MarvinApiError) {
248
+ if (error instanceof MarvinApiError || error instanceof MarvinNotFoundError || error instanceof MarvinAuthError || error instanceof MarvinServerError) {
178
249
  throw error;
179
250
  }
180
251
  if (error instanceof Error) {
181
252
  if (error.name === "AbortError") {
182
- throw new MarvinApiError(
253
+ throw new MarvinNetworkError(
183
254
  `Request timeout after ${this.timeout}ms`,
184
- 408,
185
- endpoint
255
+ error
186
256
  );
187
257
  }
188
- throw new MarvinApiError(error.message, 0, endpoint);
258
+ throw new MarvinNetworkError(
259
+ `Network error: ${error.message}`,
260
+ error
261
+ );
189
262
  }
190
263
  throw error;
191
264
  }
@@ -382,6 +455,10 @@ export {
382
455
  MarvinAuthError,
383
456
  MarvinConfigError,
384
457
  MarvinValidationError,
458
+ MarvinNotFoundError,
459
+ MarvinNetworkError,
460
+ MarvinServerError,
461
+ init_errors,
385
462
  HttpClient,
386
463
  BearerTokenAuth,
387
464
  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,
@@ -205,21 +280,28 @@ var init_HttpClient = __esm({
205
280
  if (!contentType || !contentType.includes("application/json")) {
206
281
  return void 0;
207
282
  }
208
- return await response.json();
283
+ const data = await response.json();
284
+ if (this.debug) {
285
+ const preview = typeof data === "object" && data !== null ? Array.isArray(data) ? `Array(${data.length})` : `Object with keys: ${Object.keys(data).join(", ")}` : String(data);
286
+ this.logger.log(`[Marvin] Response: ${preview}`);
287
+ }
288
+ return data;
209
289
  } catch (error) {
210
290
  clearTimeout(timeoutId);
211
- if (error instanceof MarvinApiError) {
291
+ if (error instanceof MarvinApiError || error instanceof MarvinNotFoundError || error instanceof MarvinAuthError || error instanceof MarvinServerError) {
212
292
  throw error;
213
293
  }
214
294
  if (error instanceof Error) {
215
295
  if (error.name === "AbortError") {
216
- throw new MarvinApiError(
296
+ throw new MarvinNetworkError(
217
297
  `Request timeout after ${this.timeout}ms`,
218
- 408,
219
- endpoint
298
+ error
220
299
  );
221
300
  }
222
- throw new MarvinApiError(error.message, 0, endpoint);
301
+ throw new MarvinNetworkError(
302
+ `Network error: ${error.message}`,
303
+ error
304
+ );
223
305
  }
224
306
  throw error;
225
307
  }
@@ -418,7 +500,9 @@ var init_http2 = __esm({
418
500
  super({
419
501
  baseUrl: config.apiUrl,
420
502
  auth: new BearerTokenAuth(config.siteClientToken),
421
- credentials: "same-origin"
503
+ credentials: "same-origin",
504
+ debug: config.debug,
505
+ logger: config.logger
422
506
  });
423
507
  }
424
508
  /**
@@ -555,6 +639,7 @@ var init_entries = __esm({
555
639
  "src/entries/entries.ts"() {
556
640
  "use strict";
557
641
  init_entry();
642
+ init_errors();
558
643
  EntriesModule = class {
559
644
  constructor(http, workspaceSlug) {
560
645
  this.http = http;
@@ -579,11 +664,19 @@ var init_entries = __esm({
579
664
  }
580
665
  /**
581
666
  * Get a single entry by slug
667
+ * Returns null if entry is not found (404)
582
668
  */
583
669
  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);
670
+ try {
671
+ const endpoint = `/api/publish/${this.workspaceSlug}/entries/${slug}`;
672
+ const data = await this.http.fetch(endpoint);
673
+ return new Entry(data, this.http, this.workspaceSlug);
674
+ } catch (error) {
675
+ if (error instanceof MarvinNotFoundError) {
676
+ return null;
677
+ }
678
+ throw error;
679
+ }
587
680
  }
588
681
  /**
589
682
  * Convenience: Get all pages
@@ -689,6 +782,7 @@ var init_collections = __esm({
689
782
  "src/collections/collections.ts"() {
690
783
  "use strict";
691
784
  init_collection();
785
+ init_errors();
692
786
  CollectionsModule = class {
693
787
  constructor(http, workspaceSlug) {
694
788
  this.http = http;
@@ -706,19 +800,63 @@ var init_collections = __esm({
706
800
  }
707
801
  /**
708
802
  * Get a single collection by slug
803
+ * Returns null if collection is not found (404)
709
804
  */
710
805
  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);
806
+ try {
807
+ const endpoint = `/api/publish/${this.workspaceSlug}/collections/${slug}`;
808
+ const data = await this.http.fetch(endpoint);
809
+ return new Collection(data, this.http, this.workspaceSlug);
810
+ } catch (error) {
811
+ if (error instanceof MarvinNotFoundError) {
812
+ return null;
813
+ }
814
+ throw error;
815
+ }
714
816
  }
715
817
  /**
716
818
  * Get entries in a collection
819
+ * Returns empty array if collection is not found
717
820
  */
718
821
  async entries(slug) {
719
822
  const collection = await this.get(slug);
823
+ if (!collection) {
824
+ return [];
825
+ }
720
826
  return collection.entries();
721
827
  }
828
+ /**
829
+ * Try multiple collection slugs in fallback order
830
+ * Returns the first collection that exists and has entries
831
+ *
832
+ * @param slugs - Collection slugs to try in order
833
+ * @param options - Options for fallback behavior
834
+ * @returns Entries from the first matching collection, or empty array
835
+ *
836
+ * @example
837
+ * ```ts
838
+ * // Try bench-notes, then journal, then blog
839
+ * const entries = await collections.fallback(['bench-notes', 'journal', 'blog']);
840
+ * ```
841
+ */
842
+ async fallback(slugs, options = {}) {
843
+ const { requireEntries = true } = options;
844
+ for (const slug of slugs) {
845
+ try {
846
+ const collection = await this.get(slug);
847
+ if (!collection) {
848
+ continue;
849
+ }
850
+ const entries = await collection.entries();
851
+ if (!requireEntries || entries.length > 0) {
852
+ return entries;
853
+ }
854
+ } catch (error) {
855
+ continue;
856
+ }
857
+ }
858
+ return [];
859
+ }
722
860
  };
723
861
  }
724
862
  });
@@ -1081,6 +1219,23 @@ var init_marvin = __esm({
1081
1219
  async resource(slug) {
1082
1220
  return this.workspace.resources.get(slug);
1083
1221
  }
1222
+ /**
1223
+ * Try multiple collection slugs in fallback order
1224
+ * Returns entries from the first collection that exists and has content
1225
+ *
1226
+ * @param slugs - Collection slugs to try in order
1227
+ * @param options - Options for fallback behavior
1228
+ * @returns Entries from the first matching collection, or empty array
1229
+ *
1230
+ * @example
1231
+ * ```ts
1232
+ * // Try bench-notes, then journal, then blog
1233
+ * const entries = await client.collectionFallback(['bench-notes', 'journal', 'blog']);
1234
+ * ```
1235
+ */
1236
+ async collectionFallback(slugs, options) {
1237
+ return this.workspace.collections.fallback(slugs, options);
1238
+ }
1084
1239
  // ======================
1085
1240
  // Backwards-Compatible Publishing API
1086
1241
  // ======================
@@ -1104,6 +1259,9 @@ var init_marvin = __esm({
1104
1259
  */
1105
1260
  async getEntry(slug) {
1106
1261
  const entry = await this.workspace.entries.get(slug);
1262
+ if (!entry) {
1263
+ return null;
1264
+ }
1107
1265
  return entry.toJSON();
1108
1266
  }
1109
1267
  /**
@@ -1119,6 +1277,9 @@ var init_marvin = __esm({
1119
1277
  */
1120
1278
  async getCollection(slug) {
1121
1279
  const collection = await this.workspace.collections.get(slug);
1280
+ if (!collection) {
1281
+ return null;
1282
+ }
1122
1283
  return collection.toJSON();
1123
1284
  }
1124
1285
  /**
@@ -1175,6 +1336,9 @@ __export(index_exports, {
1175
1336
  MarvinClient: () => MarvinClient,
1176
1337
  MarvinConfigError: () => MarvinConfigError,
1177
1338
  MarvinError: () => MarvinError,
1339
+ MarvinNetworkError: () => MarvinNetworkError,
1340
+ MarvinNotFoundError: () => MarvinNotFoundError,
1341
+ MarvinServerError: () => MarvinServerError,
1178
1342
  MarvinValidationError: () => MarvinValidationError,
1179
1343
  NoAuth: () => NoAuth,
1180
1344
  Resource: () => Resource,
@@ -1271,6 +1435,9 @@ function createMarvinClient(config) {
1271
1435
  MarvinClient,
1272
1436
  MarvinConfigError,
1273
1437
  MarvinError,
1438
+ MarvinNetworkError,
1439
+ MarvinNotFoundError,
1440
+ MarvinServerError,
1274
1441
  MarvinValidationError,
1275
1442
  NoAuth,
1276
1443
  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-DYC2BEA4.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,
@@ -157,21 +230,28 @@ var HttpClient = class {
157
230
  if (!contentType || !contentType.includes("application/json")) {
158
231
  return void 0;
159
232
  }
160
- return await response.json();
233
+ const data = await response.json();
234
+ if (this.debug) {
235
+ const preview = typeof data === "object" && data !== null ? Array.isArray(data) ? `Array(${data.length})` : `Object with keys: ${Object.keys(data).join(", ")}` : String(data);
236
+ this.logger.log(`[Marvin] Response: ${preview}`);
237
+ }
238
+ return data;
161
239
  } catch (error) {
162
240
  clearTimeout(timeoutId);
163
- if (error instanceof MarvinApiError) {
241
+ if (error instanceof MarvinApiError || error instanceof MarvinNotFoundError || error instanceof MarvinAuthError || error instanceof MarvinServerError) {
164
242
  throw error;
165
243
  }
166
244
  if (error instanceof Error) {
167
245
  if (error.name === "AbortError") {
168
- throw new MarvinApiError(
246
+ throw new MarvinNetworkError(
169
247
  `Request timeout after ${this.timeout}ms`,
170
- 408,
171
- endpoint
248
+ error
172
249
  );
173
250
  }
174
- throw new MarvinApiError(error.message, 0, endpoint);
251
+ throw new MarvinNetworkError(
252
+ `Network error: ${error.message}`,
253
+ error
254
+ );
175
255
  }
176
256
  throw error;
177
257
  }
@@ -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-DYC2BEA4.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.11",
3
+ "version": "2.0.0-next.13",
4
4
  "description": "Official TypeScript SDK for Marvin CMS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",