@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/platform.cjs CHANGED
@@ -27,24 +27,20 @@ __export(platform_exports, {
27
27
  AssetsModule: () => AssetsModule,
28
28
  AuthModule: () => AuthModule,
29
29
  CollectionsModule: () => CollectionsModule,
30
- EmailTemplatesClient: () => EmailTemplatesClient,
31
30
  EntriesModule: () => EntriesModule,
32
31
  EntryTypesModule: () => EntryTypesModule,
33
32
  EventLogModule: () => EventLogModule,
34
33
  EventsModule: () => EventsModule,
35
- FormsModule: () => FormsModule,
36
34
  InvitesModule: () => InvitesModule,
37
35
  NotificationsModule: () => NotificationsModule,
38
36
  PlatformClient: () => PlatformClient,
39
37
  ResourcesModule: () => ResourcesModule,
40
- ScheduledTasksModule: () => ScheduledTasksModule,
41
38
  ThemeModule: () => ThemeModule,
42
39
  UserModule: () => UserModule,
43
40
  WebhooksModule: () => WebhooksModule,
44
41
  WorkspaceMembersModule: () => WorkspaceMembersModule,
45
42
  WorkspacesModule: () => WorkspacesModule,
46
- createPlatformClient: () => createPlatformClient,
47
- createPlatformConfigFromEnv: () => createPlatformConfigFromEnv
43
+ createPlatformClient: () => createPlatformClient
48
44
  });
49
45
  module.exports = __toCommonJS(platform_exports);
50
46
 
@@ -78,49 +74,6 @@ ${body}` : ""}`;
78
74
  return new _MarvinApiError(message, status, endpoint, body);
79
75
  }
80
76
  };
81
- var MarvinAuthError = class _MarvinAuthError extends MarvinError {
82
- constructor(message, statusCode) {
83
- super(message, "AUTH_ERROR", statusCode);
84
- this.name = "MarvinAuthError";
85
- Object.setPrototypeOf(this, _MarvinAuthError.prototype);
86
- }
87
- };
88
- var MarvinValidationError = class _MarvinValidationError extends MarvinError {
89
- constructor(message, field) {
90
- super(message, "VALIDATION_ERROR");
91
- this.field = field;
92
- this.name = "MarvinValidationError";
93
- Object.setPrototypeOf(this, _MarvinValidationError.prototype);
94
- }
95
- field;
96
- };
97
- var MarvinNotFoundError = class _MarvinNotFoundError extends MarvinError {
98
- constructor(message, endpoint) {
99
- super(message, "NOT_FOUND", 404);
100
- this.endpoint = endpoint;
101
- this.name = "MarvinNotFoundError";
102
- Object.setPrototypeOf(this, _MarvinNotFoundError.prototype);
103
- }
104
- endpoint;
105
- };
106
- var MarvinNetworkError = class _MarvinNetworkError extends MarvinError {
107
- constructor(message, originalError) {
108
- super(message, "NETWORK_ERROR");
109
- this.originalError = originalError;
110
- this.name = "MarvinNetworkError";
111
- Object.setPrototypeOf(this, _MarvinNetworkError.prototype);
112
- }
113
- originalError;
114
- };
115
- var MarvinServerError = class _MarvinServerError extends MarvinError {
116
- constructor(message, statusCode, endpoint) {
117
- super(message, "SERVER_ERROR", statusCode);
118
- this.endpoint = endpoint;
119
- this.name = "MarvinServerError";
120
- Object.setPrototypeOf(this, _MarvinServerError.prototype);
121
- }
122
- endpoint;
123
- };
124
77
 
125
78
  // src/core/http/HttpClient.ts
126
79
  var HttpClient = class {
@@ -129,47 +82,12 @@ var HttpClient = class {
129
82
  defaultHeaders;
130
83
  credentials;
131
84
  timeout;
132
- debug;
133
- retryConfig;
134
- logger;
135
85
  constructor(config) {
136
86
  this.baseUrl = config.baseUrl;
137
87
  this.auth = config.auth;
138
88
  this.defaultHeaders = config.defaultHeaders || {};
139
89
  this.credentials = config.credentials || "same-origin";
140
- const MAX_TIMEOUT = 12e4;
141
- this.timeout = Math.min(config.timeout || 3e4, MAX_TIMEOUT);
142
- this.debug = config.debug ?? false;
143
- this.logger = config.logger ?? console;
144
- this.retryConfig = {
145
- maxRetries: config.retry?.maxRetries ?? 3,
146
- initialDelay: config.retry?.initialDelay ?? 1e3,
147
- maxDelay: config.retry?.maxDelay ?? 1e4,
148
- retryableStatuses: config.retry?.retryableStatuses ?? [408, 429, 500, 502, 503, 504]
149
- };
150
- }
151
- /**
152
- * Validate path parameter to prevent path traversal attacks
153
- */
154
- validatePathParam(param, paramName = "parameter") {
155
- if (typeof param === "number") {
156
- return String(param);
157
- }
158
- if (!param || typeof param !== "string") {
159
- throw new MarvinValidationError(`Invalid ${paramName}: must be a non-empty string or number`);
160
- }
161
- if (param.includes("..") || param.includes("/") || param.includes("\\")) {
162
- throw new MarvinValidationError(
163
- `Invalid ${paramName}: cannot contain path traversal characters (.. / \\)`
164
- );
165
- }
166
- if (param.includes("\0")) {
167
- throw new MarvinValidationError(`Invalid ${paramName}: cannot contain null bytes`);
168
- }
169
- if (param.length > 255) {
170
- throw new MarvinValidationError(`Invalid ${paramName}: exceeds maximum length of 255 characters`);
171
- }
172
- return param;
90
+ this.timeout = config.timeout || 3e4;
173
91
  }
174
92
  /**
175
93
  * Build full URL from endpoint
@@ -203,78 +121,14 @@ var HttpClient = class {
203
121
  return filtered.length > 0 ? `?${filtered.join("&")}` : "";
204
122
  }
205
123
  /**
206
- * Sanitize data for logging by redacting sensitive fields
207
- */
208
- sanitizeForLogging(data) {
209
- if (!data || typeof data !== "object") {
210
- return data;
211
- }
212
- const sensitiveFields = [
213
- "token",
214
- "access_token",
215
- "refresh_token",
216
- "password",
217
- "secret",
218
- "apiKey",
219
- "api_key",
220
- "bearer",
221
- "authorization",
222
- "csrf_token",
223
- "session",
224
- "cookie"
225
- ];
226
- const sanitize = (obj) => {
227
- const sanitized = {};
228
- for (const [key, value] of Object.entries(obj)) {
229
- const keyLower = key.toLowerCase();
230
- const isSensitive = sensitiveFields.some((field) => keyLower.includes(field));
231
- if (isSensitive) {
232
- sanitized[key] = "[REDACTED]";
233
- } else if (value && typeof value === "object" && !Array.isArray(value)) {
234
- sanitized[key] = sanitize(value);
235
- } else if (Array.isArray(value)) {
236
- sanitized[key] = value.map(
237
- (item) => item && typeof item === "object" ? sanitize(item) : item
238
- );
239
- } else {
240
- sanitized[key] = value;
241
- }
242
- }
243
- return sanitized;
244
- };
245
- if (Array.isArray(data)) {
246
- return data.map(
247
- (item) => item && typeof item === "object" ? sanitize(item) : item
248
- );
249
- }
250
- return sanitize(data);
251
- }
252
- /**
253
- * Calculate delay for retry with exponential backoff
254
- */
255
- calculateRetryDelay(attempt) {
256
- const delay = this.retryConfig.initialDelay * Math.pow(2, attempt);
257
- return Math.min(delay, this.retryConfig.maxDelay);
258
- }
259
- /**
260
- * Sleep for specified milliseconds
124
+ * Perform HTTP request
261
125
  */
262
- sleep(ms) {
263
- return new Promise((resolve) => setTimeout(resolve, ms));
264
- }
265
- /**
266
- * Perform HTTP request with automatic retry on transient failures
267
- */
268
- async request(method, endpoint, options, retryAttempt = 0) {
126
+ async request(method, endpoint, options) {
269
127
  const url = this.buildUrl(endpoint) + (options?.params ? this.buildQueryString(options.params) : "");
270
128
  const isFormData = options?.body instanceof FormData;
271
129
  const headers = this.buildHeaders(options?.headers, isFormData);
272
130
  const controller = new AbortController();
273
131
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
274
- const startTime = Date.now();
275
- if (this.debug) {
276
- this.logger.log(`[Marvin] ${method} ${url}`);
277
- }
278
132
  try {
279
133
  const response = await fetch(url, {
280
134
  method,
@@ -284,54 +138,11 @@ var HttpClient = class {
284
138
  signal: controller.signal
285
139
  });
286
140
  clearTimeout(timeoutId);
287
- const duration = Date.now() - startTime;
288
- if (this.debug) {
289
- this.logger.log(
290
- `[Marvin] ${response.status} ${response.statusText} (${duration}ms)`
291
- );
292
- }
293
141
  if (response.status === 401 && this.auth.handleUnauthorized) {
294
142
  await this.auth.handleUnauthorized();
295
143
  }
296
144
  if (!response.ok) {
297
145
  const errorText = await response.text().catch(() => "");
298
- if (this.debug) {
299
- this.logger.error(
300
- `[Marvin] Error response: ${response.status} ${response.statusText}`,
301
- errorText ? `Body: ${errorText}` : ""
302
- );
303
- }
304
- const isRetryable = this.retryConfig.retryableStatuses.includes(response.status);
305
- const canRetry = retryAttempt < this.retryConfig.maxRetries;
306
- if (isRetryable && canRetry) {
307
- const delay = this.calculateRetryDelay(retryAttempt);
308
- if (this.debug) {
309
- this.logger.warn(
310
- `[Marvin] Retry ${retryAttempt + 1}/${this.retryConfig.maxRetries} after ${delay}ms (status: ${response.status})`
311
- );
312
- }
313
- await this.sleep(delay);
314
- return this.request(method, endpoint, options, retryAttempt + 1);
315
- }
316
- if (response.status === 404) {
317
- throw new MarvinNotFoundError(
318
- `Resource not found: ${endpoint}`,
319
- endpoint
320
- );
321
- }
322
- if (response.status === 401 || response.status === 403) {
323
- throw new MarvinAuthError(
324
- `Authentication failed: ${response.statusText}`,
325
- response.status
326
- );
327
- }
328
- if (response.status >= 500) {
329
- throw new MarvinServerError(
330
- `Server error: ${response.status} ${response.statusText}`,
331
- response.status,
332
- endpoint
333
- );
334
- }
335
146
  throw MarvinApiError.fromResponse(
336
147
  response.status,
337
148
  response.statusText,
@@ -343,39 +154,21 @@ var HttpClient = class {
343
154
  if (!contentType || !contentType.includes("application/json")) {
344
155
  return void 0;
345
156
  }
346
- const data = await response.json();
347
- if (this.debug) {
348
- const sanitized = this.sanitizeForLogging(data);
349
- this.logger.log("[Marvin] Response:", JSON.stringify(sanitized, null, 2));
350
- }
351
- return data;
157
+ return await response.json();
352
158
  } catch (error) {
353
159
  clearTimeout(timeoutId);
354
- if (error instanceof MarvinApiError || error instanceof MarvinNotFoundError || error instanceof MarvinAuthError || error instanceof MarvinServerError) {
160
+ if (error instanceof MarvinApiError) {
355
161
  throw error;
356
162
  }
357
163
  if (error instanceof Error) {
358
- const canRetry = retryAttempt < this.retryConfig.maxRetries;
359
- if (canRetry && error.name !== "AbortError") {
360
- const delay = this.calculateRetryDelay(retryAttempt);
361
- if (this.debug) {
362
- this.logger.warn(
363
- `[Marvin] Retry ${retryAttempt + 1}/${this.retryConfig.maxRetries} after ${delay}ms (network error)`
364
- );
365
- }
366
- await this.sleep(delay);
367
- return this.request(method, endpoint, options, retryAttempt + 1);
368
- }
369
164
  if (error.name === "AbortError") {
370
- throw new MarvinNetworkError(
165
+ throw new MarvinApiError(
371
166
  `Request timeout after ${this.timeout}ms`,
372
- error
167
+ 408,
168
+ endpoint
373
169
  );
374
170
  }
375
- throw new MarvinNetworkError(
376
- `Network error: ${error.message}`,
377
- error
378
- );
171
+ throw new MarvinApiError(error.message, 0, endpoint);
379
172
  }
380
173
  throw error;
381
174
  }
@@ -420,27 +213,10 @@ var SessionAuth = class {
420
213
  }
421
214
  sessionToken;
422
215
  onUnauthorized;
423
- csrfToken;
424
- /**
425
- * Set CSRF token for cross-site request forgery protection
426
- * The token should be obtained from the server after login
427
- */
428
- setCsrfToken(token) {
429
- this.csrfToken = token;
430
- }
431
- /**
432
- * Get current CSRF token
433
- */
434
- getCsrfToken() {
435
- return this.csrfToken;
436
- }
437
216
  injectAuth(headers) {
438
217
  if (this.sessionToken) {
439
218
  headers.set("Authorization", `Bearer ${this.sessionToken}`);
440
219
  }
441
- if (this.csrfToken) {
442
- headers.set("X-CSRF-Token", this.csrfToken);
443
- }
444
220
  }
445
221
  async handleUnauthorized() {
446
222
  if (this.onUnauthorized) {
@@ -448,9 +224,7 @@ var SessionAuth = class {
448
224
  }
449
225
  }
450
226
  getDescription() {
451
- const auth = this.sessionToken ? "Session authentication (explicit token)" : "Session authentication (cookies)";
452
- const csrf = this.csrfToken ? " with CSRF protection" : "";
453
- return auth + csrf;
227
+ return this.sessionToken ? "Session authentication (explicit token)" : "Session authentication (cookies)";
454
228
  }
455
229
  };
456
230
 
@@ -470,8 +244,7 @@ var EntriesModule = class {
470
244
  * Get a single entry by ID
471
245
  */
472
246
  async get(id) {
473
- const validId = this.http.validatePathParam(id, "entry ID");
474
- return this.http.get(`/api/platform/entries/${validId}`);
247
+ return this.http.get(`/api/platform/entries/${id}`);
475
248
  }
476
249
  /**
477
250
  * Create a new entry
@@ -483,31 +256,26 @@ var EntriesModule = class {
483
256
  * Update an entry
484
257
  */
485
258
  async update(id, data) {
486
- const validId = this.http.validatePathParam(id, "entry ID");
487
- return this.http.patch(`/api/platform/entries/${validId}`, data);
259
+ return this.http.patch(`/api/platform/entries/${id}`, data);
488
260
  }
489
261
  /**
490
262
  * Delete an entry
491
263
  */
492
264
  async delete(id) {
493
- const validId = this.http.validatePathParam(id, "entry ID");
494
- return this.http.delete(`/api/platform/entries/${validId}`);
265
+ return this.http.delete(`/api/platform/entries/${id}`);
495
266
  }
496
267
  /**
497
268
  * Get collections that this entry belongs to
498
269
  */
499
270
  async listCollections(entryId) {
500
- const validId = this.http.validatePathParam(entryId, "entry ID");
501
- return this.http.get(`/api/platform/entries/${validId}/collections`);
271
+ return this.http.get(`/api/platform/entries/${entryId}/collections`);
502
272
  }
503
273
  /**
504
274
  * Add entry to a collection
505
275
  */
506
276
  async addToCollection(entryId, collectionId) {
507
- const validEntryId = this.http.validatePathParam(entryId, "entry ID");
508
- const validCollectionId = this.http.validatePathParam(collectionId, "collection ID");
509
277
  return this.http.post(
510
- `/api/platform/entries/${validEntryId}/collections/${validCollectionId}`,
278
+ `/api/platform/entries/${entryId}/collections/${collectionId}`,
511
279
  {}
512
280
  );
513
281
  }
@@ -515,9 +283,7 @@ var EntriesModule = class {
515
283
  * Remove entry from a collection
516
284
  */
517
285
  async removeFromCollection(entryId, collectionId) {
518
- const validEntryId = this.http.validatePathParam(entryId, "entry ID");
519
- const validCollectionId = this.http.validatePathParam(collectionId, "collection ID");
520
- return this.http.delete(`/api/platform/entries/${validEntryId}/collections/${validCollectionId}`);
286
+ return this.http.delete(`/api/platform/entries/${entryId}/collections/${collectionId}`);
521
287
  }
522
288
  };
523
289
 
@@ -537,8 +303,7 @@ var CollectionsModule = class {
537
303
  * Get a single collection by ID
538
304
  */
539
305
  async get(id) {
540
- const validId = this.http.validatePathParam(id, "id");
541
- return this.http.get(`/api/platform/collections/${validId}`);
306
+ return this.http.get(`/api/platform/collections/${id}`);
542
307
  }
543
308
  /**
544
309
  * Create a new collection
@@ -550,29 +315,25 @@ var CollectionsModule = class {
550
315
  * Update a collection
551
316
  */
552
317
  async update(id, data) {
553
- const validId = this.http.validatePathParam(id, "id");
554
- return this.http.patch(`/api/platform/collections/${validId}`, data);
318
+ return this.http.patch(`/api/platform/collections/${id}`, data);
555
319
  }
556
320
  /**
557
321
  * Delete a collection
558
322
  */
559
323
  async delete(id) {
560
- const validId = this.http.validatePathParam(id, "collection ID");
561
- return this.http.delete(`/api/platform/collections/${validId}`);
324
+ return this.http.delete(`/api/platform/collections/${id}`);
562
325
  }
563
326
  /**
564
327
  * Get entries in a collection
565
328
  */
566
329
  async getEntries(id) {
567
- const validId = this.http.validatePathParam(id, "collection ID");
568
- return this.http.get(`/api/platform/collections/${validId}/entries`);
330
+ return this.http.get(`/api/platform/collections/${id}/entries`);
569
331
  }
570
332
  /**
571
333
  * Reorder entries in a collection
572
334
  */
573
335
  async reorderEntries(id, entries) {
574
- const validId = this.http.validatePathParam(id, "collection ID");
575
- return this.http.patch(`/api/platform/collections/${validId}/entries/order`, { entries });
336
+ return this.http.patch(`/api/platform/collections/${id}/entries/order`, { entries });
576
337
  }
577
338
  };
578
339
 
@@ -592,8 +353,7 @@ var ResourcesModule = class {
592
353
  * Get a single resource by ID
593
354
  */
594
355
  async get(id) {
595
- const validId = this.http.validatePathParam(id, "id");
596
- return this.http.get(`/api/platform/resources/${validId}`);
356
+ return this.http.get(`/api/platform/resources/${id}`);
597
357
  }
598
358
  /**
599
359
  * Create a new resource
@@ -605,8 +365,7 @@ var ResourcesModule = class {
605
365
  * Update a resource
606
366
  */
607
367
  async update(id, data) {
608
- const validId = this.http.validatePathParam(id, "id");
609
- return this.http.patch(`/api/platform/resources/${validId}`, data);
368
+ return this.http.patch(`/api/platform/resources/${id}`, data);
610
369
  }
611
370
  /**
612
371
  * Delete a resource
@@ -628,47 +387,6 @@ var AssetsModule = class {
628
387
  this.http = http;
629
388
  }
630
389
  http;
631
- /**
632
- * Validate file before upload
633
- */
634
- validateFile(file, options) {
635
- const maxSize = options?.maxFileSize || 100 * 1024 * 1024;
636
- const allowedTypes = options?.allowedMimeTypes;
637
- if (file.size === 0) {
638
- throw new MarvinValidationError("File is empty");
639
- }
640
- if (file.size > maxSize) {
641
- const maxMB = Math.round(maxSize / (1024 * 1024));
642
- const fileMB = Math.round(file.size / (1024 * 1024));
643
- throw new MarvinValidationError(
644
- `File size (${fileMB}MB) exceeds maximum allowed size of ${maxMB}MB`
645
- );
646
- }
647
- if (allowedTypes && allowedTypes.length > 0) {
648
- if (file instanceof File && file.type) {
649
- const isAllowed = allowedTypes.some((allowed) => {
650
- if (allowed.endsWith("/*")) {
651
- const prefix = allowed.slice(0, -2);
652
- return file.type.startsWith(prefix);
653
- }
654
- return file.type === allowed;
655
- });
656
- if (!isAllowed) {
657
- throw new MarvinValidationError(
658
- `File type "${file.type}" is not allowed. Allowed types: ${allowedTypes.join(", ")}`
659
- );
660
- }
661
- }
662
- }
663
- if (file instanceof File) {
664
- if (!file.name || file.name.trim().length === 0) {
665
- throw new MarvinValidationError("File must have a valid name");
666
- }
667
- if (file.name.includes("..") || file.name.includes("/") || file.name.includes("\\")) {
668
- throw new MarvinValidationError("Invalid filename: cannot contain path separators");
669
- }
670
- }
671
- }
672
390
  /**
673
391
  * List all assets
674
392
  */
@@ -679,31 +397,16 @@ var AssetsModule = class {
679
397
  * Get a single asset by ID
680
398
  */
681
399
  async get(id) {
682
- const validId = this.http.validatePathParam(id, "id");
683
- return this.http.get(`/api/platform/assets/${validId}`);
400
+ return this.http.get(`/api/platform/assets/${id}`);
684
401
  }
685
402
  /**
686
403
  * Upload a new asset file
687
404
  *
688
405
  * @param file - The file to upload (File or Blob)
689
406
  * @param metadata - Asset metadata (slug, name, altText, description, metadata)
690
- * @param options - Upload options (file size limits, allowed types)
691
407
  * @returns The created asset with extracted metadata
692
- *
693
- * @example
694
- * ```typescript
695
- * // Upload with default limits (100MB, all types)
696
- * const asset = await assets.upload(file, { slug: 'my-image', name: 'My Image' });
697
- *
698
- * // Upload with restrictions
699
- * const asset = await assets.upload(file,
700
- * { slug: 'avatar', name: 'Avatar' },
701
- * { maxFileSize: 5 * 1024 * 1024, allowedMimeTypes: ['image/*'] }
702
- * );
703
- * ```
704
408
  */
705
- async upload(file, metadata, options) {
706
- this.validateFile(file, options);
409
+ async upload(file, metadata) {
707
410
  const formData = new FormData();
708
411
  formData.append("file", file);
709
412
  formData.append("slug", metadata.slug);
@@ -723,24 +426,13 @@ var AssetsModule = class {
723
426
  * Update an asset (editable fields only: slug, name, altText, description, metadata)
724
427
  */
725
428
  async update(id, data) {
726
- const validId = this.http.validatePathParam(id, "id");
727
- return this.http.patch(`/api/platform/assets/${validId}`, data);
429
+ return this.http.patch(`/api/platform/assets/${id}`, data);
728
430
  }
729
431
  /**
730
432
  * Delete an asset (removes file from storage and database)
731
433
  */
732
434
  async delete(id) {
733
- const validId = this.http.validatePathParam(id, "asset ID");
734
- return this.http.delete(`/api/platform/assets/${validId}`);
735
- }
736
- /**
737
- * Get download URL for an asset file
738
- * Use this to create download links or fetch the file directly
739
- */
740
- getDownloadUrl(id, baseUrl) {
741
- const validId = this.http.validatePathParam(id, "asset ID");
742
- const base = baseUrl || (typeof window !== "undefined" && window.location ? window.location.origin : "");
743
- return `${base}/api/platform/assets/${validId}/file`;
435
+ return this.http.delete(`/api/platform/assets/${id}`);
744
436
  }
745
437
  };
746
438
 
@@ -760,8 +452,7 @@ var APIClientsModule = class {
760
452
  * Get a single API client by ID
761
453
  */
762
454
  async get(id) {
763
- const validId = this.http.validatePathParam(id, "id");
764
- return this.http.get(`/api/platform/api-clients/${validId}`);
455
+ return this.http.get(`/api/platform/api-clients/${id}`);
765
456
  }
766
457
  /**
767
458
  * Create a new API client (returns client with token)
@@ -773,8 +464,7 @@ var APIClientsModule = class {
773
464
  * Update an API client
774
465
  */
775
466
  async update(id, data) {
776
- const validId = this.http.validatePathParam(id, "id");
777
- return this.http.patch(`/api/platform/api-clients/${validId}`, data);
467
+ return this.http.patch(`/api/platform/api-clients/${id}`, data);
778
468
  }
779
469
  /**
780
470
  * Delete an API client
@@ -938,15 +628,6 @@ var WorkspacesModule = class {
938
628
  const url = `/api/admin/groups/${id}${force ? "?force=true" : ""}`;
939
629
  await this.http.delete(url);
940
630
  }
941
- /**
942
- * Export workspace data as JSON (collections, entry types, entries, site config)
943
- */
944
- async export(options) {
945
- const pretty = options?.pretty ?? true;
946
- const endpoint = pretty ? "/api/platform/workspace/export/pretty" : "/api/platform/workspace/export";
947
- const params = options?.includeSystemTypes ? { include_system_types: true } : void 0;
948
- return this.http.get(endpoint, params);
949
- }
950
631
  /**
951
632
  * Get workspace preferences
952
633
  */
@@ -994,23 +675,11 @@ var InvitesModule = class {
994
675
  }
995
676
  /**
996
677
  * Generate invitation URL from a token
997
- *
998
- * @param token - Invitation token
999
- * @param baseUrl - Base URL for the frontend (required in browser environments)
1000
- * @returns Full invitation URL
1001
- *
1002
- * @example
1003
- * ```typescript
1004
- * // Node.js (can use env vars)
1005
- * const url = invites.getInvitationUrl(token);
1006
- *
1007
- * // Browser (must provide baseUrl)
1008
- * const url = invites.getInvitationUrl(token, 'https://app.example.com');
1009
- * ```
678
+ * Uses MARVIN_FRONTEND_URL for the base URL, falls back to MARVIN_API_URL with common port mappings
1010
679
  */
1011
680
  getInvitationUrl(token, baseUrl) {
1012
681
  let base = baseUrl;
1013
- if (!base && typeof process !== "undefined" && process.env) {
682
+ if (!base) {
1014
683
  base = process.env.MARVIN_FRONTEND_URL;
1015
684
  if (!base && process.env.MARVIN_API_URL) {
1016
685
  const apiUrl = process.env.MARVIN_API_URL;
@@ -1022,17 +691,11 @@ var InvitesModule = class {
1022
691
  base = apiUrl;
1023
692
  }
1024
693
  }
1025
- }
1026
- if (!base) {
1027
- if (typeof window !== "undefined" && window.location) {
1028
- base = window.location.origin;
1029
- } else {
1030
- throw new Error(
1031
- 'baseUrl is required when not in Node.js environment. Pass the frontend URL as second argument: getInvitationUrl(token, "https://app.example.com")'
1032
- );
694
+ if (!base) {
695
+ base = "http://localhost:4321";
1033
696
  }
1034
697
  }
1035
- return `${base}/register?token=${encodeURIComponent(token)}`;
698
+ return `${base}/register?token=${token}`;
1036
699
  }
1037
700
  };
1038
701
 
@@ -1086,57 +749,6 @@ var WebhooksModule = class {
1086
749
  this.http = http;
1087
750
  }
1088
751
  http;
1089
- /**
1090
- * Validate webhook URL to prevent SSRF attacks
1091
- */
1092
- validateWebhookUrl(url) {
1093
- if (!url || typeof url !== "string") {
1094
- throw new MarvinValidationError("Webhook URL is required and must be a string");
1095
- }
1096
- let parsed;
1097
- try {
1098
- parsed = new URL(url);
1099
- } catch {
1100
- throw new MarvinValidationError("Invalid webhook URL format. Expected: https://example.com/webhook");
1101
- }
1102
- if (!["http:", "https:"].includes(parsed.protocol)) {
1103
- throw new MarvinValidationError(
1104
- `Webhook URL must use HTTP or HTTPS protocol. Got: ${parsed.protocol}`
1105
- );
1106
- }
1107
- const hostname = parsed.hostname.toLowerCase();
1108
- const blockedHosts = [
1109
- "localhost",
1110
- "127.0.0.1",
1111
- "0.0.0.0",
1112
- "::1",
1113
- "169.254.169.254",
1114
- // AWS metadata service
1115
- "[::]"
1116
- ];
1117
- if (blockedHosts.some((blocked) => hostname === blocked || hostname.includes(blocked))) {
1118
- throw new MarvinValidationError(
1119
- "Webhook URL cannot target localhost or internal network addresses"
1120
- );
1121
- }
1122
- const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
1123
- const match = hostname.match(ipv4Regex);
1124
- if (match) {
1125
- const octets = match.slice(1).map(Number);
1126
- if (octets[0] === 10) {
1127
- throw new MarvinValidationError("Webhook URL cannot target private IP range (10.x.x.x)");
1128
- }
1129
- if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
1130
- throw new MarvinValidationError("Webhook URL cannot target private IP range (172.16-31.x.x)");
1131
- }
1132
- if (octets[0] === 192 && octets[1] === 168) {
1133
- throw new MarvinValidationError("Webhook URL cannot target private IP range (192.168.x.x)");
1134
- }
1135
- }
1136
- if (url.length > 2048) {
1137
- throw new MarvinValidationError("Webhook URL exceeds maximum length of 2048 characters");
1138
- }
1139
- }
1140
752
  /**
1141
753
  * List all webhooks for the current workspace
1142
754
  */
@@ -1148,43 +760,31 @@ var WebhooksModule = class {
1148
760
  * Get a webhook by ID
1149
761
  */
1150
762
  async get(id) {
1151
- const validId = this.http.validatePathParam(id, "webhook ID");
1152
- return this.http.get(`/api/groups/webhooks/${validId}`);
763
+ return this.http.get(`/api/groups/webhooks/${id}`);
1153
764
  }
1154
765
  /**
1155
766
  * Create a new webhook
1156
- * Validates URL to prevent SSRF attacks
1157
767
  */
1158
768
  async create(data) {
1159
- if (data.url) {
1160
- this.validateWebhookUrl(data.url);
1161
- }
1162
769
  return this.http.post("/api/groups/webhooks", data);
1163
770
  }
1164
771
  /**
1165
772
  * Update a webhook
1166
- * Validates URL if provided to prevent SSRF attacks
1167
773
  */
1168
774
  async update(id, data) {
1169
- const validId = this.http.validatePathParam(id, "webhook ID");
1170
- if (data.url) {
1171
- this.validateWebhookUrl(data.url);
1172
- }
1173
- return this.http.put(`/api/groups/webhooks/${validId}`, data);
775
+ return this.http.put(`/api/groups/webhooks/${id}`, data);
1174
776
  }
1175
777
  /**
1176
778
  * Delete a webhook
1177
779
  */
1178
780
  async delete(id) {
1179
- const validId = this.http.validatePathParam(id, "webhook ID");
1180
- await this.http.delete(`/api/groups/webhooks/${validId}`);
781
+ await this.http.delete(`/api/groups/webhooks/${id}`);
1181
782
  }
1182
783
  /**
1183
784
  * Test a webhook
1184
785
  */
1185
786
  async test(id) {
1186
- const validId = this.http.validatePathParam(id, "webhook ID");
1187
- return this.http.post(`/api/groups/webhooks/${validId}/test`, {});
787
+ return this.http.post(`/api/groups/webhooks/${id}/test`, {});
1188
788
  }
1189
789
  /**
1190
790
  * Rerun failed webhooks
@@ -1276,7 +876,6 @@ var AdminSystemModule = class {
1276
876
  }
1277
877
  /**
1278
878
  * Update email settings
1279
- * Note: Password field is write-only and will not be returned in response
1280
879
  */
1281
880
  async updateEmailSettings(data) {
1282
881
  return this.http.put("/api/admin/email", data);
@@ -1552,234 +1151,7 @@ var ThemeModule = class {
1552
1151
  }
1553
1152
  };
1554
1153
 
1555
- // src/platform/emailTemplates.ts
1556
- var EmailTemplatesClient = class {
1557
- constructor(http) {
1558
- this.http = http;
1559
- }
1560
- http;
1561
- /**
1562
- * List all email templates for the workspace
1563
- * @param groupId - Workspace ID
1564
- */
1565
- async list(groupId) {
1566
- return this.http.get(`/api/platform/workspaces/${groupId}/email-templates`);
1567
- }
1568
- /**
1569
- * Get a specific email template
1570
- * @param groupId - Workspace ID
1571
- * @param templateId - Template ID
1572
- */
1573
- async get(groupId, templateId) {
1574
- return this.http.get(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`);
1575
- }
1576
- /**
1577
- * Create a new email template
1578
- * @param groupId - Workspace ID
1579
- * @param data - Template data
1580
- */
1581
- async create(groupId, data) {
1582
- return this.http.post(`/api/platform/workspaces/${groupId}/email-templates`, data);
1583
- }
1584
- /**
1585
- * Update an email template
1586
- * @param groupId - Workspace ID
1587
- * @param templateId - Template ID
1588
- * @param data - Updated template data
1589
- */
1590
- async update(groupId, templateId, data) {
1591
- return this.http.patch(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`, data);
1592
- }
1593
- /**
1594
- * Delete an email template
1595
- * @param groupId - Workspace ID
1596
- * @param templateId - Template ID
1597
- */
1598
- async delete(groupId, templateId) {
1599
- await this.http.delete(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`);
1600
- }
1601
- /**
1602
- * Send a test email using a template
1603
- * @param groupId - Workspace ID
1604
- * @param templateId - Template ID
1605
- * @param recipientEmail - Test recipient email address
1606
- */
1607
- async sendTest(groupId, templateId, recipientEmail) {
1608
- return this.http.post(`/api/platform/workspaces/${groupId}/email-templates/${templateId}/test`, {
1609
- recipient_email: recipientEmail
1610
- });
1611
- }
1612
- };
1613
-
1614
- // src/platform/scheduledTasks.ts
1615
- var ScheduledTasksModule = class {
1616
- constructor(http) {
1617
- this.http = http;
1618
- }
1619
- http;
1620
- /**
1621
- * List all scheduled tasks for the current workspace
1622
- */
1623
- async list() {
1624
- return this.http.get("/api/platform/scheduled-tasks");
1625
- }
1626
- /**
1627
- * Get a scheduled task by ID or slug
1628
- */
1629
- async get(idOrSlug) {
1630
- const validIdOrSlug = this.http.validatePathParam(idOrSlug, "idOrSlug");
1631
- return this.http.get(`/api/platform/scheduled-tasks/${validIdOrSlug}`);
1632
- }
1633
- /**
1634
- * Create a new scheduled task
1635
- */
1636
- async create(data) {
1637
- return this.http.post("/api/platform/scheduled-tasks", data);
1638
- }
1639
- /**
1640
- * Update a scheduled task
1641
- */
1642
- async update(idOrSlug, data) {
1643
- const validIdOrSlug = this.http.validatePathParam(idOrSlug, "idOrSlug");
1644
- return this.http.patch(`/api/platform/scheduled-tasks/${validIdOrSlug}`, data);
1645
- }
1646
- /**
1647
- * Delete a scheduled task
1648
- */
1649
- async delete(idOrSlug) {
1650
- await this.http.delete(`/api/platform/scheduled-tasks/${idOrSlug}`);
1651
- }
1652
- /**
1653
- * Manually trigger task execution (bypass schedule)
1654
- */
1655
- async execute(idOrSlug) {
1656
- await this.http.post(`/api/platform/scheduled-tasks/${idOrSlug}/execute`, {});
1657
- }
1658
- /**
1659
- * Get task execution history
1660
- */
1661
- async history(idOrSlug, options) {
1662
- const params = new URLSearchParams();
1663
- if (options?.limit) {
1664
- params.set("limit", options.limit.toString());
1665
- }
1666
- const query = params.toString() ? `?${params.toString()}` : "";
1667
- return this.http.get(
1668
- `/api/platform/scheduled-tasks/${idOrSlug}/history${query}`
1669
- );
1670
- }
1671
- /**
1672
- * Get available task types
1673
- */
1674
- async taskTypes(options) {
1675
- const params = new URLSearchParams();
1676
- if (options?.detailed) {
1677
- params.set("detailed", "true");
1678
- }
1679
- const query = params.toString() ? `?${params.toString()}` : "";
1680
- return this.http.get(`/api/platform/scheduled-tasks/task-types${query}`);
1681
- }
1682
- };
1683
-
1684
- // src/platform/forms.ts
1685
- var FormsModule = class {
1686
- constructor(http) {
1687
- this.http = http;
1688
- }
1689
- http;
1690
- /**
1691
- * Validate form submission data
1692
- * - Prevents XSS by checking for script tags
1693
- * - Validates data is a proper object
1694
- * - Checks for reasonable size limits
1695
- */
1696
- validateFormData(data) {
1697
- if (!data || typeof data !== "object") {
1698
- throw new MarvinValidationError("Form data must be a valid object");
1699
- }
1700
- const jsonString = JSON.stringify(data);
1701
- const sizeInBytes = new Blob([jsonString]).size;
1702
- if (sizeInBytes > 1024 * 1024) {
1703
- throw new MarvinValidationError("Form data exceeds maximum size of 1MB");
1704
- }
1705
- const checkForScripts = (value) => {
1706
- if (typeof value === "string") {
1707
- if (/<script[\s\S]*?>[\s\S]*?<\/script>/gi.test(value)) {
1708
- throw new MarvinValidationError("Form data contains potentially malicious content");
1709
- }
1710
- } else if (Array.isArray(value)) {
1711
- value.forEach(checkForScripts);
1712
- } else if (value && typeof value === "object") {
1713
- Object.values(value).forEach(checkForScripts);
1714
- }
1715
- };
1716
- Object.values(data).forEach(checkForScripts);
1717
- }
1718
- /**
1719
- * List all forms
1720
- */
1721
- async list() {
1722
- return this.http.get("/api/platform/forms");
1723
- }
1724
- /**
1725
- * Get a single form by ID
1726
- */
1727
- async get(id) {
1728
- const validId = this.http.validatePathParam(id, "id");
1729
- return this.http.get(`/api/platform/forms/${validId}`);
1730
- }
1731
- /**
1732
- * Create a new form
1733
- */
1734
- async create(data) {
1735
- return this.http.post("/api/platform/forms", data);
1736
- }
1737
- /**
1738
- * Update a form
1739
- */
1740
- async update(id, data) {
1741
- const validId = this.http.validatePathParam(id, "id");
1742
- return this.http.patch(`/api/platform/forms/${validId}`, data);
1743
- }
1744
- /**
1745
- * Delete a form
1746
- */
1747
- async delete(id) {
1748
- return this.http.delete(`/api/platform/forms/${id}`);
1749
- }
1750
- /**
1751
- * Get submissions for a form
1752
- */
1753
- async getSubmissions(formId) {
1754
- return this.http.get(`/api/platform/forms/${formId}/submissions`);
1755
- }
1756
- /**
1757
- * Get a published form by slug (Publishing API - public access)
1758
- */
1759
- async getPublishedForm(slug) {
1760
- return this.http.get(`/api/publish/forms/${slug}`);
1761
- }
1762
- /**
1763
- * Submit data to a published form (Publishing API - public access)
1764
- * Validates data before submission to prevent XSS and malicious content
1765
- */
1766
- async submitForm(slug, data) {
1767
- const validSlug = this.http.validatePathParam(slug, "form slug");
1768
- this.validateFormData(data);
1769
- return this.http.post(`/api/publish/forms/${validSlug}/submit`, {
1770
- dataJson: data
1771
- });
1772
- }
1773
- };
1774
-
1775
1154
  // src/platform/client.ts
1776
- function createPlatformConfigFromEnv(overrides) {
1777
- return {
1778
- apiUrl: overrides?.apiUrl || process.env.MARVIN_API_URL || "http://localhost:8080",
1779
- userToken: overrides?.userToken || process.env.MARVIN_USER_TOKEN,
1780
- credentials: overrides?.credentials || "include"
1781
- };
1782
- }
1783
1155
  var PlatformClient = class extends HttpClient {
1784
1156
  entries;
1785
1157
  collections;
@@ -1797,16 +1169,13 @@ var PlatformClient = class extends HttpClient {
1797
1169
  eventLog;
1798
1170
  session;
1799
1171
  theme;
1800
- emailTemplates;
1801
- scheduledTasks;
1802
- forms;
1803
1172
  // Admin modules
1804
1173
  adminUsers;
1805
1174
  adminSystem;
1806
1175
  adminMaintenance;
1807
1176
  constructor(config) {
1808
1177
  const httpConfig = {
1809
- baseUrl: config.apiUrl || "http://localhost:8080",
1178
+ baseUrl: config.apiUrl,
1810
1179
  auth: new SessionAuth(config.userToken),
1811
1180
  credentials: config.credentials || "include"
1812
1181
  };
@@ -1827,17 +1196,13 @@ var PlatformClient = class extends HttpClient {
1827
1196
  this.eventLog = new EventLogModule(this);
1828
1197
  this.session = new AuthModule(this);
1829
1198
  this.theme = new ThemeModule(this);
1830
- this.emailTemplates = new EmailTemplatesClient(this);
1831
- this.scheduledTasks = new ScheduledTasksModule(this);
1832
- this.forms = new FormsModule(this);
1833
1199
  this.adminUsers = new AdminUsersModule(this);
1834
1200
  this.adminSystem = new AdminSystemModule(this);
1835
1201
  this.adminMaintenance = new AdminMaintenanceModule(this);
1836
1202
  }
1837
1203
  };
1838
1204
  function createPlatformClient(config) {
1839
- const finalConfig = createPlatformConfigFromEnv(config);
1840
- return new PlatformClient(finalConfig);
1205
+ return new PlatformClient(config);
1841
1206
  }
1842
1207
  // Annotate the CommonJS export names for ESM import in node:
1843
1208
  0 && (module.exports = {
@@ -1848,22 +1213,18 @@ function createPlatformClient(config) {
1848
1213
  AssetsModule,
1849
1214
  AuthModule,
1850
1215
  CollectionsModule,
1851
- EmailTemplatesClient,
1852
1216
  EntriesModule,
1853
1217
  EntryTypesModule,
1854
1218
  EventLogModule,
1855
1219
  EventsModule,
1856
- FormsModule,
1857
1220
  InvitesModule,
1858
1221
  NotificationsModule,
1859
1222
  PlatformClient,
1860
1223
  ResourcesModule,
1861
- ScheduledTasksModule,
1862
1224
  ThemeModule,
1863
1225
  UserModule,
1864
1226
  WebhooksModule,
1865
1227
  WorkspaceMembersModule,
1866
1228
  WorkspacesModule,
1867
- createPlatformClient,
1868
- createPlatformConfigFromEnv
1229
+ createPlatformClient
1869
1230
  });