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