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

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.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  HttpClient,
3
+ MarvinValidationError,
3
4
  SessionAuth,
4
5
  init_core
5
- } from "./chunk-25F3IF6S.js";
6
+ } from "./chunk-WJC4X7TB.js";
6
7
 
7
8
  // src/platform/client.ts
8
9
  init_core();
@@ -23,7 +24,8 @@ var EntriesModule = class {
23
24
  * Get a single entry by ID
24
25
  */
25
26
  async get(id) {
26
- return this.http.get(`/api/platform/entries/${id}`);
27
+ const validId = this.http.validatePathParam(id, "entry ID");
28
+ return this.http.get(`/api/platform/entries/${validId}`);
27
29
  }
28
30
  /**
29
31
  * Create a new entry
@@ -35,26 +37,31 @@ var EntriesModule = class {
35
37
  * Update an entry
36
38
  */
37
39
  async update(id, data) {
38
- return this.http.patch(`/api/platform/entries/${id}`, data);
40
+ const validId = this.http.validatePathParam(id, "entry ID");
41
+ return this.http.patch(`/api/platform/entries/${validId}`, data);
39
42
  }
40
43
  /**
41
44
  * Delete an entry
42
45
  */
43
46
  async delete(id) {
44
- return this.http.delete(`/api/platform/entries/${id}`);
47
+ const validId = this.http.validatePathParam(id, "entry ID");
48
+ return this.http.delete(`/api/platform/entries/${validId}`);
45
49
  }
46
50
  /**
47
51
  * Get collections that this entry belongs to
48
52
  */
49
53
  async listCollections(entryId) {
50
- return this.http.get(`/api/platform/entries/${entryId}/collections`);
54
+ const validId = this.http.validatePathParam(entryId, "entry ID");
55
+ return this.http.get(`/api/platform/entries/${validId}/collections`);
51
56
  }
52
57
  /**
53
58
  * Add entry to a collection
54
59
  */
55
60
  async addToCollection(entryId, collectionId) {
61
+ const validEntryId = this.http.validatePathParam(entryId, "entry ID");
62
+ const validCollectionId = this.http.validatePathParam(collectionId, "collection ID");
56
63
  return this.http.post(
57
- `/api/platform/entries/${entryId}/collections/${collectionId}`,
64
+ `/api/platform/entries/${validEntryId}/collections/${validCollectionId}`,
58
65
  {}
59
66
  );
60
67
  }
@@ -62,7 +69,9 @@ var EntriesModule = class {
62
69
  * Remove entry from a collection
63
70
  */
64
71
  async removeFromCollection(entryId, collectionId) {
65
- return this.http.delete(`/api/platform/entries/${entryId}/collections/${collectionId}`);
72
+ const validEntryId = this.http.validatePathParam(entryId, "entry ID");
73
+ const validCollectionId = this.http.validatePathParam(collectionId, "collection ID");
74
+ return this.http.delete(`/api/platform/entries/${validEntryId}/collections/${validCollectionId}`);
66
75
  }
67
76
  };
68
77
 
@@ -82,7 +91,8 @@ var CollectionsModule = class {
82
91
  * Get a single collection by ID
83
92
  */
84
93
  async get(id) {
85
- return this.http.get(`/api/platform/collections/${id}`);
94
+ const validId = this.http.validatePathParam(id, "id");
95
+ return this.http.get(`/api/platform/collections/${validId}`);
86
96
  }
87
97
  /**
88
98
  * Create a new collection
@@ -94,25 +104,29 @@ var CollectionsModule = class {
94
104
  * Update a collection
95
105
  */
96
106
  async update(id, data) {
97
- return this.http.patch(`/api/platform/collections/${id}`, data);
107
+ const validId = this.http.validatePathParam(id, "id");
108
+ return this.http.patch(`/api/platform/collections/${validId}`, data);
98
109
  }
99
110
  /**
100
111
  * Delete a collection
101
112
  */
102
113
  async delete(id) {
103
- return this.http.delete(`/api/platform/collections/${id}`);
114
+ const validId = this.http.validatePathParam(id, "collection ID");
115
+ return this.http.delete(`/api/platform/collections/${validId}`);
104
116
  }
105
117
  /**
106
118
  * Get entries in a collection
107
119
  */
108
120
  async getEntries(id) {
109
- return this.http.get(`/api/platform/collections/${id}/entries`);
121
+ const validId = this.http.validatePathParam(id, "collection ID");
122
+ return this.http.get(`/api/platform/collections/${validId}/entries`);
110
123
  }
111
124
  /**
112
125
  * Reorder entries in a collection
113
126
  */
114
127
  async reorderEntries(id, entries) {
115
- return this.http.patch(`/api/platform/collections/${id}/entries/order`, { entries });
128
+ const validId = this.http.validatePathParam(id, "collection ID");
129
+ return this.http.patch(`/api/platform/collections/${validId}/entries/order`, { entries });
116
130
  }
117
131
  };
118
132
 
@@ -132,7 +146,8 @@ var ResourcesModule = class {
132
146
  * Get a single resource by ID
133
147
  */
134
148
  async get(id) {
135
- return this.http.get(`/api/platform/resources/${id}`);
149
+ const validId = this.http.validatePathParam(id, "id");
150
+ return this.http.get(`/api/platform/resources/${validId}`);
136
151
  }
137
152
  /**
138
153
  * Create a new resource
@@ -144,7 +159,8 @@ var ResourcesModule = class {
144
159
  * Update a resource
145
160
  */
146
161
  async update(id, data) {
147
- return this.http.patch(`/api/platform/resources/${id}`, data);
162
+ const validId = this.http.validatePathParam(id, "id");
163
+ return this.http.patch(`/api/platform/resources/${validId}`, data);
148
164
  }
149
165
  /**
150
166
  * Delete a resource
@@ -161,11 +177,53 @@ var ResourcesModule = class {
161
177
  };
162
178
 
163
179
  // src/platform/assets.ts
180
+ init_core();
164
181
  var AssetsModule = class {
165
182
  constructor(http) {
166
183
  this.http = http;
167
184
  }
168
185
  http;
186
+ /**
187
+ * Validate file before upload
188
+ */
189
+ validateFile(file, options) {
190
+ const maxSize = options?.maxFileSize || 100 * 1024 * 1024;
191
+ const allowedTypes = options?.allowedMimeTypes;
192
+ if (file.size === 0) {
193
+ throw new MarvinValidationError("File is empty");
194
+ }
195
+ if (file.size > maxSize) {
196
+ const maxMB = Math.round(maxSize / (1024 * 1024));
197
+ const fileMB = Math.round(file.size / (1024 * 1024));
198
+ throw new MarvinValidationError(
199
+ `File size (${fileMB}MB) exceeds maximum allowed size of ${maxMB}MB`
200
+ );
201
+ }
202
+ if (allowedTypes && allowedTypes.length > 0) {
203
+ if (file instanceof File && file.type) {
204
+ const isAllowed = allowedTypes.some((allowed) => {
205
+ if (allowed.endsWith("/*")) {
206
+ const prefix = allowed.slice(0, -2);
207
+ return file.type.startsWith(prefix);
208
+ }
209
+ return file.type === allowed;
210
+ });
211
+ if (!isAllowed) {
212
+ throw new MarvinValidationError(
213
+ `File type "${file.type}" is not allowed. Allowed types: ${allowedTypes.join(", ")}`
214
+ );
215
+ }
216
+ }
217
+ }
218
+ if (file instanceof File) {
219
+ if (!file.name || file.name.trim().length === 0) {
220
+ throw new MarvinValidationError("File must have a valid name");
221
+ }
222
+ if (file.name.includes("..") || file.name.includes("/") || file.name.includes("\\")) {
223
+ throw new MarvinValidationError("Invalid filename: cannot contain path separators");
224
+ }
225
+ }
226
+ }
169
227
  /**
170
228
  * List all assets
171
229
  */
@@ -176,16 +234,31 @@ var AssetsModule = class {
176
234
  * Get a single asset by ID
177
235
  */
178
236
  async get(id) {
179
- return this.http.get(`/api/platform/assets/${id}`);
237
+ const validId = this.http.validatePathParam(id, "id");
238
+ return this.http.get(`/api/platform/assets/${validId}`);
180
239
  }
181
240
  /**
182
241
  * Upload a new asset file
183
242
  *
184
243
  * @param file - The file to upload (File or Blob)
185
244
  * @param metadata - Asset metadata (slug, name, altText, description, metadata)
245
+ * @param options - Upload options (file size limits, allowed types)
186
246
  * @returns The created asset with extracted metadata
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * // Upload with default limits (100MB, all types)
251
+ * const asset = await assets.upload(file, { slug: 'my-image', name: 'My Image' });
252
+ *
253
+ * // Upload with restrictions
254
+ * const asset = await assets.upload(file,
255
+ * { slug: 'avatar', name: 'Avatar' },
256
+ * { maxFileSize: 5 * 1024 * 1024, allowedMimeTypes: ['image/*'] }
257
+ * );
258
+ * ```
187
259
  */
188
- async upload(file, metadata) {
260
+ async upload(file, metadata, options) {
261
+ this.validateFile(file, options);
189
262
  const formData = new FormData();
190
263
  formData.append("file", file);
191
264
  formData.append("slug", metadata.slug);
@@ -205,13 +278,24 @@ var AssetsModule = class {
205
278
  * Update an asset (editable fields only: slug, name, altText, description, metadata)
206
279
  */
207
280
  async update(id, data) {
208
- return this.http.patch(`/api/platform/assets/${id}`, data);
281
+ const validId = this.http.validatePathParam(id, "id");
282
+ return this.http.patch(`/api/platform/assets/${validId}`, data);
209
283
  }
210
284
  /**
211
285
  * Delete an asset (removes file from storage and database)
212
286
  */
213
287
  async delete(id) {
214
- return this.http.delete(`/api/platform/assets/${id}`);
288
+ const validId = this.http.validatePathParam(id, "asset ID");
289
+ return this.http.delete(`/api/platform/assets/${validId}`);
290
+ }
291
+ /**
292
+ * Get download URL for an asset file
293
+ * Use this to create download links or fetch the file directly
294
+ */
295
+ getDownloadUrl(id, baseUrl) {
296
+ const validId = this.http.validatePathParam(id, "asset ID");
297
+ const base = baseUrl || (typeof window !== "undefined" && window.location ? window.location.origin : "");
298
+ return `${base}/api/platform/assets/${validId}/file`;
215
299
  }
216
300
  };
217
301
 
@@ -231,7 +315,8 @@ var APIClientsModule = class {
231
315
  * Get a single API client by ID
232
316
  */
233
317
  async get(id) {
234
- return this.http.get(`/api/platform/api-clients/${id}`);
318
+ const validId = this.http.validatePathParam(id, "id");
319
+ return this.http.get(`/api/platform/api-clients/${validId}`);
235
320
  }
236
321
  /**
237
322
  * Create a new API client (returns client with token)
@@ -243,7 +328,8 @@ var APIClientsModule = class {
243
328
  * Update an API client
244
329
  */
245
330
  async update(id, data) {
246
- return this.http.patch(`/api/platform/api-clients/${id}`, data);
331
+ const validId = this.http.validatePathParam(id, "id");
332
+ return this.http.patch(`/api/platform/api-clients/${validId}`, data);
247
333
  }
248
334
  /**
249
335
  * Delete an API client
@@ -407,6 +493,15 @@ var WorkspacesModule = class {
407
493
  const url = `/api/admin/groups/${id}${force ? "?force=true" : ""}`;
408
494
  await this.http.delete(url);
409
495
  }
496
+ /**
497
+ * Export workspace data as JSON (collections, entry types, entries, site config)
498
+ */
499
+ async export(options) {
500
+ const pretty = options?.pretty ?? true;
501
+ const endpoint = pretty ? "/api/platform/workspace/export/pretty" : "/api/platform/workspace/export";
502
+ const params = options?.includeSystemTypes ? { include_system_types: true } : void 0;
503
+ return this.http.get(endpoint, params);
504
+ }
410
505
  /**
411
506
  * Get workspace preferences
412
507
  */
@@ -422,6 +517,12 @@ var WorkspacesModule = class {
422
517
  };
423
518
 
424
519
  // src/platform/invites.ts
520
+ function getEnv(key) {
521
+ if (typeof process !== "undefined" && process.env) {
522
+ return process.env[key];
523
+ }
524
+ return void 0;
525
+ }
425
526
  var InvitesModule = class {
426
527
  constructor(http) {
427
528
  this.http = http;
@@ -454,27 +555,47 @@ var InvitesModule = class {
454
555
  }
455
556
  /**
456
557
  * Generate invitation URL from a token
457
- * Uses MARVIN_FRONTEND_URL for the base URL, falls back to MARVIN_API_URL with common port mappings
558
+ *
559
+ * @param token - Invitation token
560
+ * @param baseUrl - Base URL for the frontend (required in browser environments)
561
+ * @returns Full invitation URL
562
+ *
563
+ * @example
564
+ * ```typescript
565
+ * // Node.js (can use env vars)
566
+ * const url = invites.getInvitationUrl(token);
567
+ *
568
+ * // Browser (must provide baseUrl)
569
+ * const url = invites.getInvitationUrl(token, 'https://app.example.com');
570
+ * ```
458
571
  */
459
572
  getInvitationUrl(token, baseUrl) {
460
573
  let base = baseUrl;
461
574
  if (!base) {
462
- base = process.env.MARVIN_FRONTEND_URL;
463
- if (!base && process.env.MARVIN_API_URL) {
464
- const apiUrl = process.env.MARVIN_API_URL;
465
- if (apiUrl.includes(":8080")) {
466
- base = apiUrl.replace(":8080", ":4321");
467
- } else if (apiUrl.includes(":3000")) {
468
- base = apiUrl.replace(":3000", ":4321");
469
- } else {
470
- base = apiUrl;
575
+ base = getEnv("MARVIN_FRONTEND_URL");
576
+ if (!base) {
577
+ const apiUrl = getEnv("MARVIN_API_URL");
578
+ if (apiUrl) {
579
+ if (apiUrl.includes(":8080")) {
580
+ base = apiUrl.replace(":8080", ":4321");
581
+ } else if (apiUrl.includes(":3000")) {
582
+ base = apiUrl.replace(":3000", ":4321");
583
+ } else {
584
+ base = apiUrl;
585
+ }
471
586
  }
472
587
  }
473
- if (!base) {
474
- base = "http://localhost:4321";
588
+ }
589
+ if (!base) {
590
+ if (typeof window !== "undefined" && window.location) {
591
+ base = window.location.origin;
592
+ } else {
593
+ throw new Error(
594
+ 'baseUrl is required when not in Node.js environment. Pass the frontend URL as second argument: getInvitationUrl(token, "https://app.example.com")'
595
+ );
475
596
  }
476
597
  }
477
- return `${base}/register?token=${token}`;
598
+ return `${base}/register?token=${encodeURIComponent(token)}`;
478
599
  }
479
600
  };
480
601
 
@@ -523,11 +644,63 @@ var NotificationsModule = class {
523
644
  };
524
645
 
525
646
  // src/platform/webhooks.ts
647
+ init_core();
526
648
  var WebhooksModule = class {
527
649
  constructor(http) {
528
650
  this.http = http;
529
651
  }
530
652
  http;
653
+ /**
654
+ * Validate webhook URL to prevent SSRF attacks
655
+ */
656
+ validateWebhookUrl(url) {
657
+ if (!url || typeof url !== "string") {
658
+ throw new MarvinValidationError("Webhook URL is required and must be a string");
659
+ }
660
+ let parsed;
661
+ try {
662
+ parsed = new URL(url);
663
+ } catch {
664
+ throw new MarvinValidationError("Invalid webhook URL format. Expected: https://example.com/webhook");
665
+ }
666
+ if (!["http:", "https:"].includes(parsed.protocol)) {
667
+ throw new MarvinValidationError(
668
+ `Webhook URL must use HTTP or HTTPS protocol. Got: ${parsed.protocol}`
669
+ );
670
+ }
671
+ const hostname = parsed.hostname.toLowerCase();
672
+ const blockedHosts = [
673
+ "localhost",
674
+ "127.0.0.1",
675
+ "0.0.0.0",
676
+ "::1",
677
+ "169.254.169.254",
678
+ // AWS metadata service
679
+ "[::]"
680
+ ];
681
+ if (blockedHosts.some((blocked) => hostname === blocked || hostname.includes(blocked))) {
682
+ throw new MarvinValidationError(
683
+ "Webhook URL cannot target localhost or internal network addresses"
684
+ );
685
+ }
686
+ const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
687
+ const match = hostname.match(ipv4Regex);
688
+ if (match) {
689
+ const octets = match.slice(1).map(Number);
690
+ if (octets[0] === 10) {
691
+ throw new MarvinValidationError("Webhook URL cannot target private IP range (10.x.x.x)");
692
+ }
693
+ if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
694
+ throw new MarvinValidationError("Webhook URL cannot target private IP range (172.16-31.x.x)");
695
+ }
696
+ if (octets[0] === 192 && octets[1] === 168) {
697
+ throw new MarvinValidationError("Webhook URL cannot target private IP range (192.168.x.x)");
698
+ }
699
+ }
700
+ if (url.length > 2048) {
701
+ throw new MarvinValidationError("Webhook URL exceeds maximum length of 2048 characters");
702
+ }
703
+ }
531
704
  /**
532
705
  * List all webhooks for the current workspace
533
706
  */
@@ -539,31 +712,43 @@ var WebhooksModule = class {
539
712
  * Get a webhook by ID
540
713
  */
541
714
  async get(id) {
542
- return this.http.get(`/api/groups/webhooks/${id}`);
715
+ const validId = this.http.validatePathParam(id, "webhook ID");
716
+ return this.http.get(`/api/groups/webhooks/${validId}`);
543
717
  }
544
718
  /**
545
719
  * Create a new webhook
720
+ * Validates URL to prevent SSRF attacks
546
721
  */
547
722
  async create(data) {
723
+ if (data.url) {
724
+ this.validateWebhookUrl(data.url);
725
+ }
548
726
  return this.http.post("/api/groups/webhooks", data);
549
727
  }
550
728
  /**
551
729
  * Update a webhook
730
+ * Validates URL if provided to prevent SSRF attacks
552
731
  */
553
732
  async update(id, data) {
554
- return this.http.put(`/api/groups/webhooks/${id}`, data);
733
+ const validId = this.http.validatePathParam(id, "webhook ID");
734
+ if (data.url) {
735
+ this.validateWebhookUrl(data.url);
736
+ }
737
+ return this.http.put(`/api/groups/webhooks/${validId}`, data);
555
738
  }
556
739
  /**
557
740
  * Delete a webhook
558
741
  */
559
742
  async delete(id) {
560
- await this.http.delete(`/api/groups/webhooks/${id}`);
743
+ const validId = this.http.validatePathParam(id, "webhook ID");
744
+ await this.http.delete(`/api/groups/webhooks/${validId}`);
561
745
  }
562
746
  /**
563
747
  * Test a webhook
564
748
  */
565
749
  async test(id) {
566
- return this.http.post(`/api/groups/webhooks/${id}/test`, {});
750
+ const validId = this.http.validatePathParam(id, "webhook ID");
751
+ return this.http.post(`/api/groups/webhooks/${validId}/test`, {});
567
752
  }
568
753
  /**
569
754
  * Rerun failed webhooks
@@ -655,6 +840,7 @@ var AdminSystemModule = class {
655
840
  }
656
841
  /**
657
842
  * Update email settings
843
+ * Note: Password field is write-only and will not be returned in response
658
844
  */
659
845
  async updateEmailSettings(data) {
660
846
  return this.http.put("/api/admin/email", data);
@@ -930,7 +1116,235 @@ var ThemeModule = class {
930
1116
  }
931
1117
  };
932
1118
 
1119
+ // src/platform/emailTemplates.ts
1120
+ var EmailTemplatesClient = class {
1121
+ constructor(http) {
1122
+ this.http = http;
1123
+ }
1124
+ http;
1125
+ /**
1126
+ * List all email templates for the workspace
1127
+ * @param groupId - Workspace ID
1128
+ */
1129
+ async list(groupId) {
1130
+ return this.http.get(`/api/platform/workspaces/${groupId}/email-templates`);
1131
+ }
1132
+ /**
1133
+ * Get a specific email template
1134
+ * @param groupId - Workspace ID
1135
+ * @param templateId - Template ID
1136
+ */
1137
+ async get(groupId, templateId) {
1138
+ return this.http.get(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`);
1139
+ }
1140
+ /**
1141
+ * Create a new email template
1142
+ * @param groupId - Workspace ID
1143
+ * @param data - Template data
1144
+ */
1145
+ async create(groupId, data) {
1146
+ return this.http.post(`/api/platform/workspaces/${groupId}/email-templates`, data);
1147
+ }
1148
+ /**
1149
+ * Update an email template
1150
+ * @param groupId - Workspace ID
1151
+ * @param templateId - Template ID
1152
+ * @param data - Updated template data
1153
+ */
1154
+ async update(groupId, templateId, data) {
1155
+ return this.http.patch(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`, data);
1156
+ }
1157
+ /**
1158
+ * Delete an email template
1159
+ * @param groupId - Workspace ID
1160
+ * @param templateId - Template ID
1161
+ */
1162
+ async delete(groupId, templateId) {
1163
+ await this.http.delete(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`);
1164
+ }
1165
+ /**
1166
+ * Send a test email using a template
1167
+ * @param groupId - Workspace ID
1168
+ * @param templateId - Template ID
1169
+ * @param recipientEmail - Test recipient email address
1170
+ */
1171
+ async sendTest(groupId, templateId, recipientEmail) {
1172
+ return this.http.post(`/api/platform/workspaces/${groupId}/email-templates/${templateId}/test`, {
1173
+ recipient_email: recipientEmail
1174
+ });
1175
+ }
1176
+ };
1177
+
1178
+ // src/platform/scheduledTasks.ts
1179
+ var ScheduledTasksModule = class {
1180
+ constructor(http) {
1181
+ this.http = http;
1182
+ }
1183
+ http;
1184
+ /**
1185
+ * List all scheduled tasks for the current workspace
1186
+ */
1187
+ async list() {
1188
+ return this.http.get("/api/platform/scheduled-tasks");
1189
+ }
1190
+ /**
1191
+ * Get a scheduled task by ID or slug
1192
+ */
1193
+ async get(idOrSlug) {
1194
+ const validIdOrSlug = this.http.validatePathParam(idOrSlug, "idOrSlug");
1195
+ return this.http.get(`/api/platform/scheduled-tasks/${validIdOrSlug}`);
1196
+ }
1197
+ /**
1198
+ * Create a new scheduled task
1199
+ */
1200
+ async create(data) {
1201
+ return this.http.post("/api/platform/scheduled-tasks", data);
1202
+ }
1203
+ /**
1204
+ * Update a scheduled task
1205
+ */
1206
+ async update(idOrSlug, data) {
1207
+ const validIdOrSlug = this.http.validatePathParam(idOrSlug, "idOrSlug");
1208
+ return this.http.patch(`/api/platform/scheduled-tasks/${validIdOrSlug}`, data);
1209
+ }
1210
+ /**
1211
+ * Delete a scheduled task
1212
+ */
1213
+ async delete(idOrSlug) {
1214
+ await this.http.delete(`/api/platform/scheduled-tasks/${idOrSlug}`);
1215
+ }
1216
+ /**
1217
+ * Manually trigger task execution (bypass schedule)
1218
+ */
1219
+ async execute(idOrSlug) {
1220
+ await this.http.post(`/api/platform/scheduled-tasks/${idOrSlug}/execute`, {});
1221
+ }
1222
+ /**
1223
+ * Get task execution history
1224
+ */
1225
+ async history(idOrSlug, options) {
1226
+ const params = new URLSearchParams();
1227
+ if (options?.limit) {
1228
+ params.set("limit", options.limit.toString());
1229
+ }
1230
+ const query = params.toString() ? `?${params.toString()}` : "";
1231
+ return this.http.get(
1232
+ `/api/platform/scheduled-tasks/${idOrSlug}/history${query}`
1233
+ );
1234
+ }
1235
+ /**
1236
+ * Get available task types
1237
+ */
1238
+ async taskTypes(options) {
1239
+ const params = new URLSearchParams();
1240
+ if (options?.detailed) {
1241
+ params.set("detailed", "true");
1242
+ }
1243
+ const query = params.toString() ? `?${params.toString()}` : "";
1244
+ return this.http.get(`/api/platform/scheduled-tasks/task-types${query}`);
1245
+ }
1246
+ };
1247
+
1248
+ // src/platform/forms.ts
1249
+ init_core();
1250
+ var FormsModule = class {
1251
+ constructor(http) {
1252
+ this.http = http;
1253
+ }
1254
+ http;
1255
+ /**
1256
+ * Validate form submission data
1257
+ * - Prevents XSS by checking for script tags
1258
+ * - Validates data is a proper object
1259
+ * - Checks for reasonable size limits
1260
+ */
1261
+ validateFormData(data) {
1262
+ if (!data || typeof data !== "object") {
1263
+ throw new MarvinValidationError("Form data must be a valid object");
1264
+ }
1265
+ const jsonString = JSON.stringify(data);
1266
+ const sizeInBytes = new Blob([jsonString]).size;
1267
+ if (sizeInBytes > 1024 * 1024) {
1268
+ throw new MarvinValidationError("Form data exceeds maximum size of 1MB");
1269
+ }
1270
+ const checkForScripts = (value) => {
1271
+ if (typeof value === "string") {
1272
+ if (/<script[\s\S]*?>[\s\S]*?<\/script>/gi.test(value)) {
1273
+ throw new MarvinValidationError("Form data contains potentially malicious content");
1274
+ }
1275
+ } else if (Array.isArray(value)) {
1276
+ value.forEach(checkForScripts);
1277
+ } else if (value && typeof value === "object") {
1278
+ Object.values(value).forEach(checkForScripts);
1279
+ }
1280
+ };
1281
+ Object.values(data).forEach(checkForScripts);
1282
+ }
1283
+ /**
1284
+ * List all forms
1285
+ */
1286
+ async list() {
1287
+ return this.http.get("/api/platform/forms");
1288
+ }
1289
+ /**
1290
+ * Get a single form by ID
1291
+ */
1292
+ async get(id) {
1293
+ const validId = this.http.validatePathParam(id, "id");
1294
+ return this.http.get(`/api/platform/forms/${validId}`);
1295
+ }
1296
+ /**
1297
+ * Create a new form
1298
+ */
1299
+ async create(data) {
1300
+ return this.http.post("/api/platform/forms", data);
1301
+ }
1302
+ /**
1303
+ * Update a form
1304
+ */
1305
+ async update(id, data) {
1306
+ const validId = this.http.validatePathParam(id, "id");
1307
+ return this.http.patch(`/api/platform/forms/${validId}`, data);
1308
+ }
1309
+ /**
1310
+ * Delete a form
1311
+ */
1312
+ async delete(id) {
1313
+ return this.http.delete(`/api/platform/forms/${id}`);
1314
+ }
1315
+ /**
1316
+ * Get submissions for a form
1317
+ */
1318
+ async getSubmissions(formId) {
1319
+ return this.http.get(`/api/platform/forms/${formId}/submissions`);
1320
+ }
1321
+ /**
1322
+ * Get a published form by slug (Publishing API - public access)
1323
+ */
1324
+ async getPublishedForm(slug) {
1325
+ return this.http.get(`/api/publish/forms/${slug}`);
1326
+ }
1327
+ /**
1328
+ * Submit data to a published form (Publishing API - public access)
1329
+ * Validates data before submission to prevent XSS and malicious content
1330
+ */
1331
+ async submitForm(slug, data) {
1332
+ const validSlug = this.http.validatePathParam(slug, "form slug");
1333
+ this.validateFormData(data);
1334
+ return this.http.post(`/api/publish/forms/${validSlug}/submit`, {
1335
+ dataJson: data
1336
+ });
1337
+ }
1338
+ };
1339
+
933
1340
  // src/platform/client.ts
1341
+ function createPlatformConfigFromEnv(overrides) {
1342
+ return {
1343
+ apiUrl: overrides?.apiUrl || process.env.MARVIN_API_URL || "http://localhost:8080",
1344
+ userToken: overrides?.userToken || process.env.MARVIN_USER_TOKEN,
1345
+ credentials: overrides?.credentials || "include"
1346
+ };
1347
+ }
934
1348
  var PlatformClient = class extends HttpClient {
935
1349
  entries;
936
1350
  collections;
@@ -948,13 +1362,16 @@ var PlatformClient = class extends HttpClient {
948
1362
  eventLog;
949
1363
  session;
950
1364
  theme;
1365
+ emailTemplates;
1366
+ scheduledTasks;
1367
+ forms;
951
1368
  // Admin modules
952
1369
  adminUsers;
953
1370
  adminSystem;
954
1371
  adminMaintenance;
955
1372
  constructor(config) {
956
1373
  const httpConfig = {
957
- baseUrl: config.apiUrl,
1374
+ baseUrl: config.apiUrl || "http://localhost:8080",
958
1375
  auth: new SessionAuth(config.userToken),
959
1376
  credentials: config.credentials || "include"
960
1377
  };
@@ -975,13 +1392,17 @@ var PlatformClient = class extends HttpClient {
975
1392
  this.eventLog = new EventLogModule(this);
976
1393
  this.session = new AuthModule(this);
977
1394
  this.theme = new ThemeModule(this);
1395
+ this.emailTemplates = new EmailTemplatesClient(this);
1396
+ this.scheduledTasks = new ScheduledTasksModule(this);
1397
+ this.forms = new FormsModule(this);
978
1398
  this.adminUsers = new AdminUsersModule(this);
979
1399
  this.adminSystem = new AdminSystemModule(this);
980
1400
  this.adminMaintenance = new AdminMaintenanceModule(this);
981
1401
  }
982
1402
  };
983
1403
  function createPlatformClient(config) {
984
- return new PlatformClient(config);
1404
+ const finalConfig = createPlatformConfigFromEnv(config);
1405
+ return new PlatformClient(finalConfig);
985
1406
  }
986
1407
  export {
987
1408
  APIClientsModule,
@@ -991,18 +1412,22 @@ export {
991
1412
  AssetsModule,
992
1413
  AuthModule,
993
1414
  CollectionsModule,
1415
+ EmailTemplatesClient,
994
1416
  EntriesModule,
995
1417
  EntryTypesModule,
996
1418
  EventLogModule,
997
1419
  EventsModule,
1420
+ FormsModule,
998
1421
  InvitesModule,
999
1422
  NotificationsModule,
1000
1423
  PlatformClient,
1001
1424
  ResourcesModule,
1425
+ ScheduledTasksModule,
1002
1426
  ThemeModule,
1003
1427
  UserModule,
1004
1428
  WebhooksModule,
1005
1429
  WorkspaceMembersModule,
1006
1430
  WorkspacesModule,
1007
- createPlatformClient
1431
+ createPlatformClient,
1432
+ createPlatformConfigFromEnv
1008
1433
  };