@inneropen/marvin-sdk 2.0.0-next.18 → 2.0.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -27,99 +27,14 @@ __export(config_exports, {
27
27
  validateConfig: () => validateConfig
28
28
  });
29
29
  function validateConfig(config) {
30
- const receivedKeys = Object.keys(config).join(", ");
31
30
  if (!config.apiUrl) {
32
- throw new Error(
33
- `Missing required config: 'apiUrl'
34
-
35
- The API URL is the base URL of your Marvin server.
36
-
37
- Examples:
38
- - Production: https://marvin.example.com
39
- - Development: http://localhost:8080
40
-
41
- Solutions:
42
- 1. Set environment variable: MARVIN_API_URL=https://marvin.example.com
43
- 2. Pass directly: createMarvinClient({ apiUrl: 'https://marvin.example.com' })
44
-
45
- Received config keys: ${receivedKeys || "(none)"}`
46
- );
31
+ throw new Error("MARVIN_API_URL is required");
47
32
  }
48
33
  if (!config.siteClientToken) {
49
- throw new Error(
50
- `Missing required config: 'siteClientToken'
51
-
52
- The site client token provides read-only access to published content.
53
-
54
- How to get it:
55
- 1. Log into Marvin admin panel
56
- 2. Navigate to: Settings \u2192 API Clients
57
- 3. Copy the token for your workspace
58
-
59
- Examples:
60
- - site_client_abc123xyz
61
- - sc_1234567890abcdef
62
-
63
- Solutions:
64
- 1. Set environment variable: MARVIN_SITE_CLIENT_TOKEN=site_client_abc123
65
- 2. Pass directly: createMarvinClient({ siteClientToken: 'site_client_abc123' })
66
-
67
- Received config keys: ${receivedKeys}`
68
- );
34
+ throw new Error("MARVIN_SITE_CLIENT_TOKEN is required");
69
35
  }
70
36
  if (!config.workspaceSlug) {
71
- throw new Error(
72
- `Missing required config: 'workspaceSlug'
73
-
74
- The workspace slug identifies which workspace to access.
75
-
76
- Examples:
77
- - my-blog
78
- - company-website
79
- - acme-corp
80
-
81
- How to find it:
82
- 1. Check your workspace URL: https://marvin.example.com/workspace/[slug]
83
- 2. Or find it in: Settings \u2192 General \u2192 Workspace Slug
84
-
85
- Solutions:
86
- 1. Set environment variable: MARVIN_WORKSPACE_SLUG=my-blog
87
- 2. Pass directly: createMarvinClient({ workspaceSlug: 'my-blog' })
88
-
89
- Received config keys: ${receivedKeys}`
90
- );
91
- }
92
- if (config.apiUrl) {
93
- try {
94
- new URL(config.apiUrl);
95
- } catch {
96
- throw new Error(
97
- `Invalid 'apiUrl' format: "${config.apiUrl}"
98
-
99
- The API URL must be a valid HTTP or HTTPS URL.
100
-
101
- Valid examples:
102
- \u2713 https://marvin.example.com
103
- \u2713 http://localhost:8080
104
- \u2713 https://api.marvin.io
105
-
106
- Invalid examples:
107
- \u2717 marvin.example.com (missing protocol)
108
- \u2717 ftp://marvin.example.com (wrong protocol)
109
- \u2717 /api (not a full URL)`
110
- );
111
- }
112
- const url = new URL(config.apiUrl);
113
- if (!["http:", "https:"].includes(url.protocol)) {
114
- throw new Error(
115
- `Invalid 'apiUrl' protocol: "${url.protocol}"
116
-
117
- Only HTTP and HTTPS protocols are supported.
118
-
119
- Your URL: ${config.apiUrl}
120
- Change to: ${config.apiUrl.replace(url.protocol, "https:")}`
121
- );
122
- }
37
+ throw new Error("MARVIN_WORKSPACE_SLUG is required");
123
38
  }
124
39
  }
125
40
  function createConfigFromEnv(overrides) {
@@ -128,10 +43,8 @@ function createConfigFromEnv(overrides) {
128
43
  siteClientToken: overrides?.siteClientToken || process.env.MARVIN_SITE_CLIENT_TOKEN || "",
129
44
  workspaceSlug: overrides?.workspaceSlug || process.env.MARVIN_WORKSPACE_SLUG || "",
130
45
  autoInitialize: overrides?.autoInitialize ?? false,
131
- cacheDuration: overrides?.cacheDuration ?? 5 * 60 * 1e3,
46
+ cacheDuration: overrides?.cacheDuration ?? 5 * 60 * 1e3
132
47
  // 5 minutes
133
- debug: overrides?.debug ?? process.env.MARVIN_DEBUG === "true",
134
- logger: overrides?.logger ?? console
135
48
  };
136
49
  }
137
50
  var init_config = __esm({
@@ -141,7 +54,7 @@ var init_config = __esm({
141
54
  });
142
55
 
143
56
  // src/core/errors/MarvinError.ts
144
- var MarvinError, MarvinApiError, MarvinAuthError, MarvinConfigError, MarvinValidationError, MarvinNotFoundError, MarvinNetworkError, MarvinServerError;
57
+ var MarvinError, MarvinApiError, MarvinAuthError, MarvinConfigError, MarvinValidationError;
145
58
  var init_MarvinError = __esm({
146
59
  "src/core/errors/MarvinError.ts"() {
147
60
  "use strict";
@@ -197,33 +110,6 @@ ${body}` : ""}`;
197
110
  }
198
111
  field;
199
112
  };
200
- MarvinNotFoundError = class _MarvinNotFoundError extends MarvinError {
201
- constructor(message, endpoint) {
202
- super(message, "NOT_FOUND", 404);
203
- this.endpoint = endpoint;
204
- this.name = "MarvinNotFoundError";
205
- Object.setPrototypeOf(this, _MarvinNotFoundError.prototype);
206
- }
207
- endpoint;
208
- };
209
- MarvinNetworkError = class _MarvinNetworkError extends MarvinError {
210
- constructor(message, originalError) {
211
- super(message, "NETWORK_ERROR");
212
- this.originalError = originalError;
213
- this.name = "MarvinNetworkError";
214
- Object.setPrototypeOf(this, _MarvinNetworkError.prototype);
215
- }
216
- originalError;
217
- };
218
- MarvinServerError = class _MarvinServerError extends MarvinError {
219
- constructor(message, statusCode, endpoint) {
220
- super(message, "SERVER_ERROR", statusCode);
221
- this.endpoint = endpoint;
222
- this.name = "MarvinServerError";
223
- Object.setPrototypeOf(this, _MarvinServerError.prototype);
224
- }
225
- endpoint;
226
- };
227
113
  }
228
114
  });
229
115
 
@@ -247,47 +133,12 @@ var init_HttpClient = __esm({
247
133
  defaultHeaders;
248
134
  credentials;
249
135
  timeout;
250
- debug;
251
- retryConfig;
252
- logger;
253
136
  constructor(config) {
254
137
  this.baseUrl = config.baseUrl;
255
138
  this.auth = config.auth;
256
139
  this.defaultHeaders = config.defaultHeaders || {};
257
140
  this.credentials = config.credentials || "same-origin";
258
- const MAX_TIMEOUT = 12e4;
259
- this.timeout = Math.min(config.timeout || 3e4, MAX_TIMEOUT);
260
- this.debug = config.debug ?? false;
261
- this.logger = config.logger ?? console;
262
- this.retryConfig = {
263
- maxRetries: config.retry?.maxRetries ?? 3,
264
- initialDelay: config.retry?.initialDelay ?? 1e3,
265
- maxDelay: config.retry?.maxDelay ?? 1e4,
266
- retryableStatuses: config.retry?.retryableStatuses ?? [408, 429, 500, 502, 503, 504]
267
- };
268
- }
269
- /**
270
- * Validate path parameter to prevent path traversal attacks
271
- */
272
- validatePathParam(param, paramName = "parameter") {
273
- if (typeof param === "number") {
274
- return String(param);
275
- }
276
- if (!param || typeof param !== "string") {
277
- throw new MarvinValidationError(`Invalid ${paramName}: must be a non-empty string or number`);
278
- }
279
- if (param.includes("..") || param.includes("/") || param.includes("\\")) {
280
- throw new MarvinValidationError(
281
- `Invalid ${paramName}: cannot contain path traversal characters (.. / \\)`
282
- );
283
- }
284
- if (param.includes("\0")) {
285
- throw new MarvinValidationError(`Invalid ${paramName}: cannot contain null bytes`);
286
- }
287
- if (param.length > 255) {
288
- throw new MarvinValidationError(`Invalid ${paramName}: exceeds maximum length of 255 characters`);
289
- }
290
- return param;
141
+ this.timeout = config.timeout || 3e4;
291
142
  }
292
143
  /**
293
144
  * Build full URL from endpoint
@@ -321,78 +172,14 @@ var init_HttpClient = __esm({
321
172
  return filtered.length > 0 ? `?${filtered.join("&")}` : "";
322
173
  }
323
174
  /**
324
- * Sanitize data for logging by redacting sensitive fields
175
+ * Perform HTTP request
325
176
  */
326
- sanitizeForLogging(data) {
327
- if (!data || typeof data !== "object") {
328
- return data;
329
- }
330
- const sensitiveFields = [
331
- "token",
332
- "access_token",
333
- "refresh_token",
334
- "password",
335
- "secret",
336
- "apiKey",
337
- "api_key",
338
- "bearer",
339
- "authorization",
340
- "csrf_token",
341
- "session",
342
- "cookie"
343
- ];
344
- const sanitize = (obj) => {
345
- const sanitized = {};
346
- for (const [key, value] of Object.entries(obj)) {
347
- const keyLower = key.toLowerCase();
348
- const isSensitive = sensitiveFields.some((field) => keyLower.includes(field));
349
- if (isSensitive) {
350
- sanitized[key] = "[REDACTED]";
351
- } else if (value && typeof value === "object" && !Array.isArray(value)) {
352
- sanitized[key] = sanitize(value);
353
- } else if (Array.isArray(value)) {
354
- sanitized[key] = value.map(
355
- (item) => item && typeof item === "object" ? sanitize(item) : item
356
- );
357
- } else {
358
- sanitized[key] = value;
359
- }
360
- }
361
- return sanitized;
362
- };
363
- if (Array.isArray(data)) {
364
- return data.map(
365
- (item) => item && typeof item === "object" ? sanitize(item) : item
366
- );
367
- }
368
- return sanitize(data);
369
- }
370
- /**
371
- * Calculate delay for retry with exponential backoff
372
- */
373
- calculateRetryDelay(attempt) {
374
- const delay = this.retryConfig.initialDelay * Math.pow(2, attempt);
375
- return Math.min(delay, this.retryConfig.maxDelay);
376
- }
377
- /**
378
- * Sleep for specified milliseconds
379
- */
380
- sleep(ms) {
381
- return new Promise((resolve) => setTimeout(resolve, ms));
382
- }
383
- /**
384
- * Perform HTTP request with automatic retry on transient failures
385
- */
386
- async request(method, endpoint, options, retryAttempt = 0) {
177
+ async request(method, endpoint, options) {
387
178
  const url = this.buildUrl(endpoint) + (options?.params ? this.buildQueryString(options.params) : "");
388
179
  const isFormData = options?.body instanceof FormData;
389
180
  const headers = this.buildHeaders(options?.headers, isFormData);
390
181
  const controller = new AbortController();
391
182
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
392
- const startTime = Date.now();
393
- if (this.debug) {
394
- this.logger.log(`[Marvin] ${method} ${url}`);
395
- }
396
183
  try {
397
184
  const response = await fetch(url, {
398
185
  method,
@@ -402,54 +189,11 @@ var init_HttpClient = __esm({
402
189
  signal: controller.signal
403
190
  });
404
191
  clearTimeout(timeoutId);
405
- const duration = Date.now() - startTime;
406
- if (this.debug) {
407
- this.logger.log(
408
- `[Marvin] ${response.status} ${response.statusText} (${duration}ms)`
409
- );
410
- }
411
192
  if (response.status === 401 && this.auth.handleUnauthorized) {
412
193
  await this.auth.handleUnauthorized();
413
194
  }
414
195
  if (!response.ok) {
415
196
  const errorText = await response.text().catch(() => "");
416
- if (this.debug) {
417
- this.logger.error(
418
- `[Marvin] Error response: ${response.status} ${response.statusText}`,
419
- errorText ? `Body: ${errorText}` : ""
420
- );
421
- }
422
- const isRetryable = this.retryConfig.retryableStatuses.includes(response.status);
423
- const canRetry = retryAttempt < this.retryConfig.maxRetries;
424
- if (isRetryable && canRetry) {
425
- const delay = this.calculateRetryDelay(retryAttempt);
426
- if (this.debug) {
427
- this.logger.warn(
428
- `[Marvin] Retry ${retryAttempt + 1}/${this.retryConfig.maxRetries} after ${delay}ms (status: ${response.status})`
429
- );
430
- }
431
- await this.sleep(delay);
432
- return this.request(method, endpoint, options, retryAttempt + 1);
433
- }
434
- if (response.status === 404) {
435
- throw new MarvinNotFoundError(
436
- `Resource not found: ${endpoint}`,
437
- endpoint
438
- );
439
- }
440
- if (response.status === 401 || response.status === 403) {
441
- throw new MarvinAuthError(
442
- `Authentication failed: ${response.statusText}`,
443
- response.status
444
- );
445
- }
446
- if (response.status >= 500) {
447
- throw new MarvinServerError(
448
- `Server error: ${response.status} ${response.statusText}`,
449
- response.status,
450
- endpoint
451
- );
452
- }
453
197
  throw MarvinApiError.fromResponse(
454
198
  response.status,
455
199
  response.statusText,
@@ -461,39 +205,21 @@ var init_HttpClient = __esm({
461
205
  if (!contentType || !contentType.includes("application/json")) {
462
206
  return void 0;
463
207
  }
464
- const data = await response.json();
465
- if (this.debug) {
466
- const sanitized = this.sanitizeForLogging(data);
467
- this.logger.log("[Marvin] Response:", JSON.stringify(sanitized, null, 2));
468
- }
469
- return data;
208
+ return await response.json();
470
209
  } catch (error) {
471
210
  clearTimeout(timeoutId);
472
- if (error instanceof MarvinApiError || error instanceof MarvinNotFoundError || error instanceof MarvinAuthError || error instanceof MarvinServerError) {
211
+ if (error instanceof MarvinApiError) {
473
212
  throw error;
474
213
  }
475
214
  if (error instanceof Error) {
476
- const canRetry = retryAttempt < this.retryConfig.maxRetries;
477
- if (canRetry && error.name !== "AbortError") {
478
- const delay = this.calculateRetryDelay(retryAttempt);
479
- if (this.debug) {
480
- this.logger.warn(
481
- `[Marvin] Retry ${retryAttempt + 1}/${this.retryConfig.maxRetries} after ${delay}ms (network error)`
482
- );
483
- }
484
- await this.sleep(delay);
485
- return this.request(method, endpoint, options, retryAttempt + 1);
486
- }
487
215
  if (error.name === "AbortError") {
488
- throw new MarvinNetworkError(
216
+ throw new MarvinApiError(
489
217
  `Request timeout after ${this.timeout}ms`,
490
- error
218
+ 408,
219
+ endpoint
491
220
  );
492
221
  }
493
- throw new MarvinNetworkError(
494
- `Network error: ${error.message}`,
495
- error
496
- );
222
+ throw new MarvinApiError(error.message, 0, endpoint);
497
223
  }
498
224
  throw error;
499
225
  }
@@ -564,27 +290,10 @@ var init_AuthStrategy = __esm({
564
290
  }
565
291
  sessionToken;
566
292
  onUnauthorized;
567
- csrfToken;
568
- /**
569
- * Set CSRF token for cross-site request forgery protection
570
- * The token should be obtained from the server after login
571
- */
572
- setCsrfToken(token) {
573
- this.csrfToken = token;
574
- }
575
- /**
576
- * Get current CSRF token
577
- */
578
- getCsrfToken() {
579
- return this.csrfToken;
580
- }
581
293
  injectAuth(headers) {
582
294
  if (this.sessionToken) {
583
295
  headers.set("Authorization", `Bearer ${this.sessionToken}`);
584
296
  }
585
- if (this.csrfToken) {
586
- headers.set("X-CSRF-Token", this.csrfToken);
587
- }
588
297
  }
589
298
  async handleUnauthorized() {
590
299
  if (this.onUnauthorized) {
@@ -592,9 +301,7 @@ var init_AuthStrategy = __esm({
592
301
  }
593
302
  }
594
303
  getDescription() {
595
- const auth = this.sessionToken ? "Session authentication (explicit token)" : "Session authentication (cookies)";
596
- const csrf = this.csrfToken ? " with CSRF protection" : "";
597
- return auth + csrf;
304
+ return this.sessionToken ? "Session authentication (explicit token)" : "Session authentication (cookies)";
598
305
  }
599
306
  };
600
307
  NoAuth = class {
@@ -654,17 +361,11 @@ var init_cache = __esm({
654
361
  "src/core/cache.ts"() {
655
362
  "use strict";
656
363
  MarvinCache = class {
657
- constructor(duration = 5 * 60 * 1e3, maxEntries = 1e3) {
364
+ constructor(duration = 5 * 60 * 1e3) {
658
365
  this.duration = duration;
659
- this.maxEntries = maxEntries;
660
366
  }
661
367
  duration;
662
- maxEntries;
663
368
  cache = /* @__PURE__ */ new Map();
664
- /**
665
- * Get cached data by key
666
- * Returns undefined if not found or expired
667
- */
668
369
  get(key) {
669
370
  const entry = this.cache.get(key);
670
371
  if (!entry) return void 0;
@@ -675,149 +376,25 @@ var init_cache = __esm({
675
376
  }
676
377
  return entry.data;
677
378
  }
678
- /**
679
- * Set cached data with optional tags for invalidation
680
- */
681
- set(key, data, tags) {
682
- if (this.cache.size >= this.maxEntries) {
683
- const oldestKey = this.cache.keys().next().value;
684
- if (oldestKey) {
685
- this.cache.delete(oldestKey);
686
- }
687
- }
379
+ set(key, data) {
688
380
  this.cache.set(key, {
689
381
  data,
690
- timestamp: Date.now(),
691
- tags
382
+ timestamp: Date.now()
692
383
  });
693
384
  }
694
- /**
695
- * Check if key exists and is not expired
696
- */
697
385
  has(key) {
698
386
  return this.get(key) !== void 0;
699
387
  }
700
- /**
701
- * Clear all cached entries
702
- */
703
388
  clear() {
704
389
  this.cache.clear();
705
390
  }
706
- /**
707
- * Delete a specific cache entry
708
- */
709
391
  delete(key) {
710
392
  this.cache.delete(key);
711
393
  }
712
- /**
713
- * Invalidate all cache entries with a specific tag
714
- * Useful for invalidating related data after mutations
715
- *
716
- * @example
717
- * ```typescript
718
- * // Cache entries with tags
719
- * cache.set('entry:1', data, ['entries', 'entry:1']);
720
- * cache.set('entries:list', data, ['entries']);
721
- *
722
- * // Invalidate all entries
723
- * cache.invalidateByTag('entries');
724
- * ```
725
- */
726
- invalidateByTag(tag) {
727
- let count = 0;
728
- for (const [key, entry] of this.cache.entries()) {
729
- if (entry.tags?.includes(tag)) {
730
- this.cache.delete(key);
731
- count++;
732
- }
733
- }
734
- return count;
735
- }
736
- /**
737
- * Get cache statistics
738
- */
739
- getStats() {
740
- let oldest = null;
741
- let newest = null;
742
- for (const entry of this.cache.values()) {
743
- if (oldest === null || entry.timestamp < oldest) {
744
- oldest = entry.timestamp;
745
- }
746
- if (newest === null || entry.timestamp > newest) {
747
- newest = entry.timestamp;
748
- }
749
- }
750
- return {
751
- size: this.cache.size,
752
- maxEntries: this.maxEntries,
753
- oldestEntry: oldest,
754
- newestEntry: newest
755
- };
756
- }
757
- /**
758
- * Remove expired entries (garbage collection)
759
- * Returns number of entries removed
760
- */
761
- prune() {
762
- let count = 0;
763
- const now = Date.now();
764
- for (const [key, entry] of this.cache.entries()) {
765
- const age = now - entry.timestamp;
766
- if (age > this.duration) {
767
- this.cache.delete(key);
768
- count++;
769
- }
770
- }
771
- return count;
772
- }
773
394
  };
774
395
  }
775
396
  });
776
397
 
777
- // src/core/validation.ts
778
- function validateEmail(email, fieldName = "Email") {
779
- if (!email || typeof email !== "string") {
780
- throw new MarvinValidationError(`${fieldName} is required and must be a string`);
781
- }
782
- const trimmed = email.trim();
783
- if (trimmed.length === 0) {
784
- throw new MarvinValidationError(`${fieldName} cannot be empty`);
785
- }
786
- if (trimmed.length < 5) {
787
- throw new MarvinValidationError(`${fieldName} is too short`);
788
- }
789
- if (trimmed.length > 254) {
790
- throw new MarvinValidationError(`${fieldName} exceeds maximum length of 254 characters`);
791
- }
792
- if (!EMAIL_REGEX.test(trimmed)) {
793
- throw new MarvinValidationError(
794
- `${fieldName} format is invalid. Expected format: user@example.com`
795
- );
796
- }
797
- const [localPart, domain] = trimmed.split("@");
798
- if (localPart.length > 64) {
799
- throw new MarvinValidationError(`${fieldName} local part exceeds 64 characters`);
800
- }
801
- if (domain.length > 253) {
802
- throw new MarvinValidationError(`${fieldName} domain exceeds 253 characters`);
803
- }
804
- if (trimmed.includes("..")) {
805
- throw new MarvinValidationError(`${fieldName} cannot contain consecutive dots`);
806
- }
807
- if (localPart.startsWith(".") || localPart.endsWith(".")) {
808
- throw new MarvinValidationError(`${fieldName} local part cannot start or end with a dot`);
809
- }
810
- return trimmed;
811
- }
812
- var EMAIL_REGEX;
813
- var init_validation = __esm({
814
- "src/core/validation.ts"() {
815
- "use strict";
816
- init_errors();
817
- EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
818
- }
819
- });
820
-
821
398
  // src/core/index.ts
822
399
  var init_core = __esm({
823
400
  "src/core/index.ts"() {
@@ -827,7 +404,6 @@ var init_core = __esm({
827
404
  init_errors();
828
405
  init_pagination();
829
406
  init_cache();
830
- init_validation();
831
407
  }
832
408
  });
833
409
 
@@ -842,9 +418,7 @@ var init_http2 = __esm({
842
418
  super({
843
419
  baseUrl: config.apiUrl,
844
420
  auth: new BearerTokenAuth(config.siteClientToken),
845
- credentials: "same-origin",
846
- debug: config.debug,
847
- logger: config.logger
421
+ credentials: "same-origin"
848
422
  });
849
423
  }
850
424
  /**
@@ -981,7 +555,6 @@ var init_entries = __esm({
981
555
  "src/entries/entries.ts"() {
982
556
  "use strict";
983
557
  init_entry();
984
- init_errors();
985
558
  EntriesModule = class {
986
559
  constructor(http, workspaceSlug) {
987
560
  this.http = http;
@@ -1006,19 +579,11 @@ var init_entries = __esm({
1006
579
  }
1007
580
  /**
1008
581
  * Get a single entry by slug
1009
- * Returns null if entry is not found (404)
1010
582
  */
1011
583
  async get(slug) {
1012
- try {
1013
- const endpoint = `/api/publish/${this.workspaceSlug}/entries/${slug}`;
1014
- const data = await this.http.fetch(endpoint);
1015
- return new Entry(data, this.http, this.workspaceSlug);
1016
- } catch (error) {
1017
- if (error instanceof MarvinNotFoundError) {
1018
- return null;
1019
- }
1020
- throw error;
1021
- }
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);
1022
587
  }
1023
588
  /**
1024
589
  * Convenience: Get all pages
@@ -1124,7 +689,6 @@ var init_collections = __esm({
1124
689
  "src/collections/collections.ts"() {
1125
690
  "use strict";
1126
691
  init_collection();
1127
- init_errors();
1128
692
  CollectionsModule = class {
1129
693
  constructor(http, workspaceSlug) {
1130
694
  this.http = http;
@@ -1142,63 +706,19 @@ var init_collections = __esm({
1142
706
  }
1143
707
  /**
1144
708
  * Get a single collection by slug
1145
- * Returns null if collection is not found (404)
1146
709
  */
1147
710
  async get(slug) {
1148
- try {
1149
- const endpoint = `/api/publish/${this.workspaceSlug}/collections/${slug}`;
1150
- const data = await this.http.fetch(endpoint);
1151
- return new Collection(data, this.http, this.workspaceSlug);
1152
- } catch (error) {
1153
- if (error instanceof MarvinNotFoundError) {
1154
- return null;
1155
- }
1156
- throw error;
1157
- }
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);
1158
714
  }
1159
715
  /**
1160
716
  * Get entries in a collection
1161
- * Returns empty array if collection is not found
1162
717
  */
1163
718
  async entries(slug) {
1164
719
  const collection = await this.get(slug);
1165
- if (!collection) {
1166
- return [];
1167
- }
1168
720
  return collection.entries();
1169
721
  }
1170
- /**
1171
- * Try multiple collection slugs in fallback order
1172
- * Returns the first collection that exists and has entries
1173
- *
1174
- * @param slugs - Collection slugs to try in order
1175
- * @param options - Options for fallback behavior
1176
- * @returns Entries from the first matching collection, or empty array
1177
- *
1178
- * @example
1179
- * ```ts
1180
- * // Try bench-notes, then journal, then blog
1181
- * const entries = await collections.fallback(['bench-notes', 'journal', 'blog']);
1182
- * ```
1183
- */
1184
- async fallback(slugs, options = {}) {
1185
- const { requireEntries = true } = options;
1186
- for (const slug of slugs) {
1187
- try {
1188
- const collection = await this.get(slug);
1189
- if (!collection) {
1190
- continue;
1191
- }
1192
- const entries = await collection.entries();
1193
- if (!requireEntries || entries.length > 0) {
1194
- return entries;
1195
- }
1196
- } catch (error) {
1197
- continue;
1198
- }
1199
- }
1200
- return [];
1201
- }
1202
722
  };
1203
723
  }
1204
724
  });
@@ -1561,23 +1081,6 @@ var init_marvin = __esm({
1561
1081
  async resource(slug) {
1562
1082
  return this.workspace.resources.get(slug);
1563
1083
  }
1564
- /**
1565
- * Try multiple collection slugs in fallback order
1566
- * Returns entries from the first collection that exists and has content
1567
- *
1568
- * @param slugs - Collection slugs to try in order
1569
- * @param options - Options for fallback behavior
1570
- * @returns Entries from the first matching collection, or empty array
1571
- *
1572
- * @example
1573
- * ```ts
1574
- * // Try bench-notes, then journal, then blog
1575
- * const entries = await client.collectionFallback(['bench-notes', 'journal', 'blog']);
1576
- * ```
1577
- */
1578
- async collectionFallback(slugs, options) {
1579
- return this.workspace.collections.fallback(slugs, options);
1580
- }
1581
1084
  // ======================
1582
1085
  // Backwards-Compatible Publishing API
1583
1086
  // ======================
@@ -1601,9 +1104,6 @@ var init_marvin = __esm({
1601
1104
  */
1602
1105
  async getEntry(slug) {
1603
1106
  const entry = await this.workspace.entries.get(slug);
1604
- if (!entry) {
1605
- return null;
1606
- }
1607
1107
  return entry.toJSON();
1608
1108
  }
1609
1109
  /**
@@ -1619,9 +1119,6 @@ var init_marvin = __esm({
1619
1119
  */
1620
1120
  async getCollection(slug) {
1621
1121
  const collection = await this.workspace.collections.get(slug);
1622
- if (!collection) {
1623
- return null;
1624
- }
1625
1122
  return collection.toJSON();
1626
1123
  }
1627
1124
  /**
@@ -1678,9 +1175,6 @@ __export(index_exports, {
1678
1175
  MarvinClient: () => MarvinClient,
1679
1176
  MarvinConfigError: () => MarvinConfigError,
1680
1177
  MarvinError: () => MarvinError,
1681
- MarvinNetworkError: () => MarvinNetworkError,
1682
- MarvinNotFoundError: () => MarvinNotFoundError,
1683
- MarvinServerError: () => MarvinServerError,
1684
1178
  MarvinValidationError: () => MarvinValidationError,
1685
1179
  NoAuth: () => NoAuth,
1686
1180
  Resource: () => Resource,
@@ -1715,14 +1209,12 @@ var AuthClient = class extends HttpClient {
1715
1209
  * Returns the created user object
1716
1210
  */
1717
1211
  async register(data) {
1718
- validateEmail(data.email, "Email");
1719
1212
  return this.post("/api/users/register", data);
1720
1213
  }
1721
1214
  /**
1722
1215
  * Request password reset (sends email with token)
1723
1216
  */
1724
1217
  async forgotPassword(data) {
1725
- validateEmail(data.email, "Email");
1726
1218
  return this.post("/api/users/forgot-password", data);
1727
1219
  }
1728
1220
  /**
@@ -1779,9 +1271,6 @@ function createMarvinClient(config) {
1779
1271
  MarvinClient,
1780
1272
  MarvinConfigError,
1781
1273
  MarvinError,
1782
- MarvinNetworkError,
1783
- MarvinNotFoundError,
1784
- MarvinServerError,
1785
1274
  MarvinValidationError,
1786
1275
  NoAuth,
1787
1276
  Resource,