@inneropen/marvin-sdk 2.0.0-next.17 → 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
@@ -985,23 +675,11 @@ var InvitesModule = class {
985
675
  }
986
676
  /**
987
677
  * Generate invitation URL from a token
988
- *
989
- * @param token - Invitation token
990
- * @param baseUrl - Base URL for the frontend (required in browser environments)
991
- * @returns Full invitation URL
992
- *
993
- * @example
994
- * ```typescript
995
- * // Node.js (can use env vars)
996
- * const url = invites.getInvitationUrl(token);
997
- *
998
- * // Browser (must provide baseUrl)
999
- * const url = invites.getInvitationUrl(token, 'https://app.example.com');
1000
- * ```
678
+ * Uses MARVIN_FRONTEND_URL for the base URL, falls back to MARVIN_API_URL with common port mappings
1001
679
  */
1002
680
  getInvitationUrl(token, baseUrl) {
1003
681
  let base = baseUrl;
1004
- if (!base && typeof process !== "undefined" && process.env) {
682
+ if (!base) {
1005
683
  base = process.env.MARVIN_FRONTEND_URL;
1006
684
  if (!base && process.env.MARVIN_API_URL) {
1007
685
  const apiUrl = process.env.MARVIN_API_URL;
@@ -1013,17 +691,11 @@ var InvitesModule = class {
1013
691
  base = apiUrl;
1014
692
  }
1015
693
  }
1016
- }
1017
- if (!base) {
1018
- if (typeof window !== "undefined" && window.location) {
1019
- base = window.location.origin;
1020
- } else {
1021
- throw new Error(
1022
- 'baseUrl is required when not in Node.js environment. Pass the frontend URL as second argument: getInvitationUrl(token, "https://app.example.com")'
1023
- );
694
+ if (!base) {
695
+ base = "http://localhost:4321";
1024
696
  }
1025
697
  }
1026
- return `${base}/register?token=${encodeURIComponent(token)}`;
698
+ return `${base}/register?token=${token}`;
1027
699
  }
1028
700
  };
1029
701
 
@@ -1077,57 +749,6 @@ var WebhooksModule = class {
1077
749
  this.http = http;
1078
750
  }
1079
751
  http;
1080
- /**
1081
- * Validate webhook URL to prevent SSRF attacks
1082
- */
1083
- validateWebhookUrl(url) {
1084
- if (!url || typeof url !== "string") {
1085
- throw new MarvinValidationError("Webhook URL is required and must be a string");
1086
- }
1087
- let parsed;
1088
- try {
1089
- parsed = new URL(url);
1090
- } catch {
1091
- throw new MarvinValidationError("Invalid webhook URL format. Expected: https://example.com/webhook");
1092
- }
1093
- if (!["http:", "https:"].includes(parsed.protocol)) {
1094
- throw new MarvinValidationError(
1095
- `Webhook URL must use HTTP or HTTPS protocol. Got: ${parsed.protocol}`
1096
- );
1097
- }
1098
- const hostname = parsed.hostname.toLowerCase();
1099
- const blockedHosts = [
1100
- "localhost",
1101
- "127.0.0.1",
1102
- "0.0.0.0",
1103
- "::1",
1104
- "169.254.169.254",
1105
- // AWS metadata service
1106
- "[::]"
1107
- ];
1108
- if (blockedHosts.some((blocked) => hostname === blocked || hostname.includes(blocked))) {
1109
- throw new MarvinValidationError(
1110
- "Webhook URL cannot target localhost or internal network addresses"
1111
- );
1112
- }
1113
- const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
1114
- const match = hostname.match(ipv4Regex);
1115
- if (match) {
1116
- const octets = match.slice(1).map(Number);
1117
- if (octets[0] === 10) {
1118
- throw new MarvinValidationError("Webhook URL cannot target private IP range (10.x.x.x)");
1119
- }
1120
- if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
1121
- throw new MarvinValidationError("Webhook URL cannot target private IP range (172.16-31.x.x)");
1122
- }
1123
- if (octets[0] === 192 && octets[1] === 168) {
1124
- throw new MarvinValidationError("Webhook URL cannot target private IP range (192.168.x.x)");
1125
- }
1126
- }
1127
- if (url.length > 2048) {
1128
- throw new MarvinValidationError("Webhook URL exceeds maximum length of 2048 characters");
1129
- }
1130
- }
1131
752
  /**
1132
753
  * List all webhooks for the current workspace
1133
754
  */
@@ -1139,43 +760,31 @@ var WebhooksModule = class {
1139
760
  * Get a webhook by ID
1140
761
  */
1141
762
  async get(id) {
1142
- const validId = this.http.validatePathParam(id, "webhook ID");
1143
- return this.http.get(`/api/groups/webhooks/${validId}`);
763
+ return this.http.get(`/api/groups/webhooks/${id}`);
1144
764
  }
1145
765
  /**
1146
766
  * Create a new webhook
1147
- * Validates URL to prevent SSRF attacks
1148
767
  */
1149
768
  async create(data) {
1150
- if (data.url) {
1151
- this.validateWebhookUrl(data.url);
1152
- }
1153
769
  return this.http.post("/api/groups/webhooks", data);
1154
770
  }
1155
771
  /**
1156
772
  * Update a webhook
1157
- * Validates URL if provided to prevent SSRF attacks
1158
773
  */
1159
774
  async update(id, data) {
1160
- const validId = this.http.validatePathParam(id, "webhook ID");
1161
- if (data.url) {
1162
- this.validateWebhookUrl(data.url);
1163
- }
1164
- return this.http.put(`/api/groups/webhooks/${validId}`, data);
775
+ return this.http.put(`/api/groups/webhooks/${id}`, data);
1165
776
  }
1166
777
  /**
1167
778
  * Delete a webhook
1168
779
  */
1169
780
  async delete(id) {
1170
- const validId = this.http.validatePathParam(id, "webhook ID");
1171
- await this.http.delete(`/api/groups/webhooks/${validId}`);
781
+ await this.http.delete(`/api/groups/webhooks/${id}`);
1172
782
  }
1173
783
  /**
1174
784
  * Test a webhook
1175
785
  */
1176
786
  async test(id) {
1177
- const validId = this.http.validatePathParam(id, "webhook ID");
1178
- return this.http.post(`/api/groups/webhooks/${validId}/test`, {});
787
+ return this.http.post(`/api/groups/webhooks/${id}/test`, {});
1179
788
  }
1180
789
  /**
1181
790
  * Rerun failed webhooks
@@ -1267,7 +876,6 @@ var AdminSystemModule = class {
1267
876
  }
1268
877
  /**
1269
878
  * Update email settings
1270
- * Note: Password field is write-only and will not be returned in response
1271
879
  */
1272
880
  async updateEmailSettings(data) {
1273
881
  return this.http.put("/api/admin/email", data);
@@ -1543,234 +1151,7 @@ var ThemeModule = class {
1543
1151
  }
1544
1152
  };
1545
1153
 
1546
- // src/platform/emailTemplates.ts
1547
- var EmailTemplatesClient = class {
1548
- constructor(http) {
1549
- this.http = http;
1550
- }
1551
- http;
1552
- /**
1553
- * List all email templates for the workspace
1554
- * @param groupId - Workspace ID
1555
- */
1556
- async list(groupId) {
1557
- return this.http.get(`/api/platform/workspaces/${groupId}/email-templates`);
1558
- }
1559
- /**
1560
- * Get a specific email template
1561
- * @param groupId - Workspace ID
1562
- * @param templateId - Template ID
1563
- */
1564
- async get(groupId, templateId) {
1565
- return this.http.get(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`);
1566
- }
1567
- /**
1568
- * Create a new email template
1569
- * @param groupId - Workspace ID
1570
- * @param data - Template data
1571
- */
1572
- async create(groupId, data) {
1573
- return this.http.post(`/api/platform/workspaces/${groupId}/email-templates`, data);
1574
- }
1575
- /**
1576
- * Update an email template
1577
- * @param groupId - Workspace ID
1578
- * @param templateId - Template ID
1579
- * @param data - Updated template data
1580
- */
1581
- async update(groupId, templateId, data) {
1582
- return this.http.patch(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`, data);
1583
- }
1584
- /**
1585
- * Delete an email template
1586
- * @param groupId - Workspace ID
1587
- * @param templateId - Template ID
1588
- */
1589
- async delete(groupId, templateId) {
1590
- await this.http.delete(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`);
1591
- }
1592
- /**
1593
- * Send a test email using a template
1594
- * @param groupId - Workspace ID
1595
- * @param templateId - Template ID
1596
- * @param recipientEmail - Test recipient email address
1597
- */
1598
- async sendTest(groupId, templateId, recipientEmail) {
1599
- return this.http.post(`/api/platform/workspaces/${groupId}/email-templates/${templateId}/test`, {
1600
- recipient_email: recipientEmail
1601
- });
1602
- }
1603
- };
1604
-
1605
- // src/platform/scheduledTasks.ts
1606
- var ScheduledTasksModule = class {
1607
- constructor(http) {
1608
- this.http = http;
1609
- }
1610
- http;
1611
- /**
1612
- * List all scheduled tasks for the current workspace
1613
- */
1614
- async list() {
1615
- return this.http.get("/api/platform/scheduled-tasks");
1616
- }
1617
- /**
1618
- * Get a scheduled task by ID or slug
1619
- */
1620
- async get(idOrSlug) {
1621
- const validIdOrSlug = this.http.validatePathParam(idOrSlug, "idOrSlug");
1622
- return this.http.get(`/api/platform/scheduled-tasks/${validIdOrSlug}`);
1623
- }
1624
- /**
1625
- * Create a new scheduled task
1626
- */
1627
- async create(data) {
1628
- return this.http.post("/api/platform/scheduled-tasks", data);
1629
- }
1630
- /**
1631
- * Update a scheduled task
1632
- */
1633
- async update(idOrSlug, data) {
1634
- const validIdOrSlug = this.http.validatePathParam(idOrSlug, "idOrSlug");
1635
- return this.http.patch(`/api/platform/scheduled-tasks/${validIdOrSlug}`, data);
1636
- }
1637
- /**
1638
- * Delete a scheduled task
1639
- */
1640
- async delete(idOrSlug) {
1641
- await this.http.delete(`/api/platform/scheduled-tasks/${idOrSlug}`);
1642
- }
1643
- /**
1644
- * Manually trigger task execution (bypass schedule)
1645
- */
1646
- async execute(idOrSlug) {
1647
- await this.http.post(`/api/platform/scheduled-tasks/${idOrSlug}/execute`, {});
1648
- }
1649
- /**
1650
- * Get task execution history
1651
- */
1652
- async history(idOrSlug, options) {
1653
- const params = new URLSearchParams();
1654
- if (options?.limit) {
1655
- params.set("limit", options.limit.toString());
1656
- }
1657
- const query = params.toString() ? `?${params.toString()}` : "";
1658
- return this.http.get(
1659
- `/api/platform/scheduled-tasks/${idOrSlug}/history${query}`
1660
- );
1661
- }
1662
- /**
1663
- * Get available task types
1664
- */
1665
- async taskTypes(options) {
1666
- const params = new URLSearchParams();
1667
- if (options?.detailed) {
1668
- params.set("detailed", "true");
1669
- }
1670
- const query = params.toString() ? `?${params.toString()}` : "";
1671
- return this.http.get(`/api/platform/scheduled-tasks/task-types${query}`);
1672
- }
1673
- };
1674
-
1675
- // src/platform/forms.ts
1676
- var FormsModule = class {
1677
- constructor(http) {
1678
- this.http = http;
1679
- }
1680
- http;
1681
- /**
1682
- * Validate form submission data
1683
- * - Prevents XSS by checking for script tags
1684
- * - Validates data is a proper object
1685
- * - Checks for reasonable size limits
1686
- */
1687
- validateFormData(data) {
1688
- if (!data || typeof data !== "object") {
1689
- throw new MarvinValidationError("Form data must be a valid object");
1690
- }
1691
- const jsonString = JSON.stringify(data);
1692
- const sizeInBytes = new Blob([jsonString]).size;
1693
- if (sizeInBytes > 1024 * 1024) {
1694
- throw new MarvinValidationError("Form data exceeds maximum size of 1MB");
1695
- }
1696
- const checkForScripts = (value) => {
1697
- if (typeof value === "string") {
1698
- if (/<script[\s\S]*?>[\s\S]*?<\/script>/gi.test(value)) {
1699
- throw new MarvinValidationError("Form data contains potentially malicious content");
1700
- }
1701
- } else if (Array.isArray(value)) {
1702
- value.forEach(checkForScripts);
1703
- } else if (value && typeof value === "object") {
1704
- Object.values(value).forEach(checkForScripts);
1705
- }
1706
- };
1707
- Object.values(data).forEach(checkForScripts);
1708
- }
1709
- /**
1710
- * List all forms
1711
- */
1712
- async list() {
1713
- return this.http.get("/api/platform/forms");
1714
- }
1715
- /**
1716
- * Get a single form by ID
1717
- */
1718
- async get(id) {
1719
- const validId = this.http.validatePathParam(id, "id");
1720
- return this.http.get(`/api/platform/forms/${validId}`);
1721
- }
1722
- /**
1723
- * Create a new form
1724
- */
1725
- async create(data) {
1726
- return this.http.post("/api/platform/forms", data);
1727
- }
1728
- /**
1729
- * Update a form
1730
- */
1731
- async update(id, data) {
1732
- const validId = this.http.validatePathParam(id, "id");
1733
- return this.http.patch(`/api/platform/forms/${validId}`, data);
1734
- }
1735
- /**
1736
- * Delete a form
1737
- */
1738
- async delete(id) {
1739
- return this.http.delete(`/api/platform/forms/${id}`);
1740
- }
1741
- /**
1742
- * Get submissions for a form
1743
- */
1744
- async getSubmissions(formId) {
1745
- return this.http.get(`/api/platform/forms/${formId}/submissions`);
1746
- }
1747
- /**
1748
- * Get a published form by slug (Publishing API - public access)
1749
- */
1750
- async getPublishedForm(slug) {
1751
- return this.http.get(`/api/publish/forms/${slug}`);
1752
- }
1753
- /**
1754
- * Submit data to a published form (Publishing API - public access)
1755
- * Validates data before submission to prevent XSS and malicious content
1756
- */
1757
- async submitForm(slug, data) {
1758
- const validSlug = this.http.validatePathParam(slug, "form slug");
1759
- this.validateFormData(data);
1760
- return this.http.post(`/api/publish/forms/${validSlug}/submit`, {
1761
- dataJson: data
1762
- });
1763
- }
1764
- };
1765
-
1766
1154
  // src/platform/client.ts
1767
- function createPlatformConfigFromEnv(overrides) {
1768
- return {
1769
- apiUrl: overrides?.apiUrl || process.env.MARVIN_API_URL || "http://localhost:8080",
1770
- userToken: overrides?.userToken || process.env.MARVIN_USER_TOKEN,
1771
- credentials: overrides?.credentials || "include"
1772
- };
1773
- }
1774
1155
  var PlatformClient = class extends HttpClient {
1775
1156
  entries;
1776
1157
  collections;
@@ -1788,16 +1169,13 @@ var PlatformClient = class extends HttpClient {
1788
1169
  eventLog;
1789
1170
  session;
1790
1171
  theme;
1791
- emailTemplates;
1792
- scheduledTasks;
1793
- forms;
1794
1172
  // Admin modules
1795
1173
  adminUsers;
1796
1174
  adminSystem;
1797
1175
  adminMaintenance;
1798
1176
  constructor(config) {
1799
1177
  const httpConfig = {
1800
- baseUrl: config.apiUrl || "http://localhost:8080",
1178
+ baseUrl: config.apiUrl,
1801
1179
  auth: new SessionAuth(config.userToken),
1802
1180
  credentials: config.credentials || "include"
1803
1181
  };
@@ -1818,17 +1196,13 @@ var PlatformClient = class extends HttpClient {
1818
1196
  this.eventLog = new EventLogModule(this);
1819
1197
  this.session = new AuthModule(this);
1820
1198
  this.theme = new ThemeModule(this);
1821
- this.emailTemplates = new EmailTemplatesClient(this);
1822
- this.scheduledTasks = new ScheduledTasksModule(this);
1823
- this.forms = new FormsModule(this);
1824
1199
  this.adminUsers = new AdminUsersModule(this);
1825
1200
  this.adminSystem = new AdminSystemModule(this);
1826
1201
  this.adminMaintenance = new AdminMaintenanceModule(this);
1827
1202
  }
1828
1203
  };
1829
1204
  function createPlatformClient(config) {
1830
- const finalConfig = createPlatformConfigFromEnv(config);
1831
- return new PlatformClient(finalConfig);
1205
+ return new PlatformClient(config);
1832
1206
  }
1833
1207
  // Annotate the CommonJS export names for ESM import in node:
1834
1208
  0 && (module.exports = {
@@ -1839,22 +1213,18 @@ function createPlatformClient(config) {
1839
1213
  AssetsModule,
1840
1214
  AuthModule,
1841
1215
  CollectionsModule,
1842
- EmailTemplatesClient,
1843
1216
  EntriesModule,
1844
1217
  EntryTypesModule,
1845
1218
  EventLogModule,
1846
1219
  EventsModule,
1847
- FormsModule,
1848
1220
  InvitesModule,
1849
1221
  NotificationsModule,
1850
1222
  PlatformClient,
1851
1223
  ResourcesModule,
1852
- ScheduledTasksModule,
1853
1224
  ThemeModule,
1854
1225
  UserModule,
1855
1226
  WebhooksModule,
1856
1227
  WorkspaceMembersModule,
1857
1228
  WorkspacesModule,
1858
- createPlatformClient,
1859
- createPlatformConfigFromEnv
1229
+ createPlatformClient
1860
1230
  });