@inneropen/marvin-sdk 2.0.1 → 3.0.1
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 +179 -14
- package/dist/{HttpClient-CLtgQGXH.d.cts → HttpClient-CdF3IHM1.d.cts} +49 -2
- package/dist/{HttpClient-CLtgQGXH.d.ts → HttpClient-CdF3IHM1.d.ts} +49 -2
- package/dist/chunk-R3EZI43S.js +855 -0
- package/dist/chunk-WJC4X7TB.js +738 -0
- package/dist/index-D5zkKa4N.d.cts +3394 -0
- package/dist/index-D5zkKa4N.d.ts +3394 -0
- package/dist/index.cjs +599 -213
- package/dist/index.d.cts +20 -544
- package/dist/index.d.ts +20 -544
- package/dist/index.js +27 -791
- package/dist/marvin-B_gJRxHK.d.cts +611 -0
- package/dist/marvin-C5tRQ2nD.d.ts +611 -0
- package/dist/platform.cjs +512 -69
- package/dist/platform.d.cts +141 -3381
- package/dist/platform.d.ts +141 -3381
- package/dist/platform.js +283 -59
- package/dist/publish.cjs +1355 -0
- package/dist/publish.d.cts +49 -0
- package/dist/publish.d.ts +49 -0
- package/dist/publish.js +75 -0
- package/dist/types/index.d.cts +1 -168
- package/dist/types/index.d.ts +1 -168
- package/package.json +11 -6
- package/dist/chunk-25F3IF6S.js +0 -396
package/dist/index.cjs
CHANGED
|
@@ -27,24 +27,117 @@ __export(config_exports, {
|
|
|
27
27
|
validateConfig: () => validateConfig
|
|
28
28
|
});
|
|
29
29
|
function validateConfig(config) {
|
|
30
|
+
const receivedKeys = Object.keys(config).join(", ");
|
|
30
31
|
if (!config.apiUrl) {
|
|
31
|
-
throw new Error(
|
|
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
|
+
);
|
|
32
47
|
}
|
|
33
48
|
if (!config.siteClientToken) {
|
|
34
|
-
throw new Error(
|
|
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
|
+
);
|
|
35
69
|
}
|
|
36
70
|
if (!config.workspaceSlug) {
|
|
37
|
-
throw new Error(
|
|
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
|
+
);
|
|
38
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
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function getEnv(key) {
|
|
126
|
+
if (typeof process !== "undefined" && process.env) {
|
|
127
|
+
return process.env[key];
|
|
128
|
+
}
|
|
129
|
+
return void 0;
|
|
39
130
|
}
|
|
40
131
|
function createConfigFromEnv(overrides) {
|
|
41
132
|
return {
|
|
42
|
-
apiUrl: overrides?.apiUrl ||
|
|
43
|
-
siteClientToken: overrides?.siteClientToken ||
|
|
44
|
-
workspaceSlug: overrides?.workspaceSlug ||
|
|
133
|
+
apiUrl: overrides?.apiUrl || getEnv("MARVIN_API_URL") || "",
|
|
134
|
+
siteClientToken: overrides?.siteClientToken || getEnv("MARVIN_SITE_CLIENT_TOKEN") || "",
|
|
135
|
+
workspaceSlug: overrides?.workspaceSlug || getEnv("MARVIN_WORKSPACE_SLUG") || "",
|
|
45
136
|
autoInitialize: overrides?.autoInitialize ?? false,
|
|
46
|
-
cacheDuration: overrides?.cacheDuration ?? 5 * 60 * 1e3
|
|
137
|
+
cacheDuration: overrides?.cacheDuration ?? 5 * 60 * 1e3,
|
|
47
138
|
// 5 minutes
|
|
139
|
+
debug: overrides?.debug ?? getEnv("MARVIN_DEBUG") === "true",
|
|
140
|
+
logger: overrides?.logger ?? console
|
|
48
141
|
};
|
|
49
142
|
}
|
|
50
143
|
var init_config = __esm({
|
|
@@ -54,7 +147,7 @@ var init_config = __esm({
|
|
|
54
147
|
});
|
|
55
148
|
|
|
56
149
|
// src/core/errors/MarvinError.ts
|
|
57
|
-
var MarvinError, MarvinApiError, MarvinAuthError, MarvinConfigError, MarvinValidationError;
|
|
150
|
+
var MarvinError, MarvinApiError, MarvinAuthError, MarvinConfigError, MarvinValidationError, MarvinNotFoundError, MarvinNetworkError, MarvinServerError;
|
|
58
151
|
var init_MarvinError = __esm({
|
|
59
152
|
"src/core/errors/MarvinError.ts"() {
|
|
60
153
|
"use strict";
|
|
@@ -110,6 +203,33 @@ ${body}` : ""}`;
|
|
|
110
203
|
}
|
|
111
204
|
field;
|
|
112
205
|
};
|
|
206
|
+
MarvinNotFoundError = class _MarvinNotFoundError extends MarvinError {
|
|
207
|
+
constructor(message, endpoint) {
|
|
208
|
+
super(message, "NOT_FOUND", 404);
|
|
209
|
+
this.endpoint = endpoint;
|
|
210
|
+
this.name = "MarvinNotFoundError";
|
|
211
|
+
Object.setPrototypeOf(this, _MarvinNotFoundError.prototype);
|
|
212
|
+
}
|
|
213
|
+
endpoint;
|
|
214
|
+
};
|
|
215
|
+
MarvinNetworkError = class _MarvinNetworkError extends MarvinError {
|
|
216
|
+
constructor(message, originalError) {
|
|
217
|
+
super(message, "NETWORK_ERROR");
|
|
218
|
+
this.originalError = originalError;
|
|
219
|
+
this.name = "MarvinNetworkError";
|
|
220
|
+
Object.setPrototypeOf(this, _MarvinNetworkError.prototype);
|
|
221
|
+
}
|
|
222
|
+
originalError;
|
|
223
|
+
};
|
|
224
|
+
MarvinServerError = class _MarvinServerError extends MarvinError {
|
|
225
|
+
constructor(message, statusCode, endpoint) {
|
|
226
|
+
super(message, "SERVER_ERROR", statusCode);
|
|
227
|
+
this.endpoint = endpoint;
|
|
228
|
+
this.name = "MarvinServerError";
|
|
229
|
+
Object.setPrototypeOf(this, _MarvinServerError.prototype);
|
|
230
|
+
}
|
|
231
|
+
endpoint;
|
|
232
|
+
};
|
|
113
233
|
}
|
|
114
234
|
});
|
|
115
235
|
|
|
@@ -133,12 +253,47 @@ var init_HttpClient = __esm({
|
|
|
133
253
|
defaultHeaders;
|
|
134
254
|
credentials;
|
|
135
255
|
timeout;
|
|
256
|
+
debug;
|
|
257
|
+
retryConfig;
|
|
258
|
+
logger;
|
|
136
259
|
constructor(config) {
|
|
137
260
|
this.baseUrl = config.baseUrl;
|
|
138
261
|
this.auth = config.auth;
|
|
139
262
|
this.defaultHeaders = config.defaultHeaders || {};
|
|
140
263
|
this.credentials = config.credentials || "same-origin";
|
|
141
|
-
|
|
264
|
+
const MAX_TIMEOUT = 12e4;
|
|
265
|
+
this.timeout = Math.min(config.timeout || 3e4, MAX_TIMEOUT);
|
|
266
|
+
this.debug = config.debug ?? false;
|
|
267
|
+
this.logger = config.logger ?? console;
|
|
268
|
+
this.retryConfig = {
|
|
269
|
+
maxRetries: config.retry?.maxRetries ?? 3,
|
|
270
|
+
initialDelay: config.retry?.initialDelay ?? 1e3,
|
|
271
|
+
maxDelay: config.retry?.maxDelay ?? 1e4,
|
|
272
|
+
retryableStatuses: config.retry?.retryableStatuses ?? [408, 429, 500, 502, 503, 504]
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Validate path parameter to prevent path traversal attacks
|
|
277
|
+
*/
|
|
278
|
+
validatePathParam(param, paramName = "parameter") {
|
|
279
|
+
if (typeof param === "number") {
|
|
280
|
+
return String(param);
|
|
281
|
+
}
|
|
282
|
+
if (!param || typeof param !== "string") {
|
|
283
|
+
throw new MarvinValidationError(`Invalid ${paramName}: must be a non-empty string or number`);
|
|
284
|
+
}
|
|
285
|
+
if (param.includes("..") || param.includes("/") || param.includes("\\")) {
|
|
286
|
+
throw new MarvinValidationError(
|
|
287
|
+
`Invalid ${paramName}: cannot contain path traversal characters (.. / \\)`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (param.includes("\0")) {
|
|
291
|
+
throw new MarvinValidationError(`Invalid ${paramName}: cannot contain null bytes`);
|
|
292
|
+
}
|
|
293
|
+
if (param.length > 255) {
|
|
294
|
+
throw new MarvinValidationError(`Invalid ${paramName}: exceeds maximum length of 255 characters`);
|
|
295
|
+
}
|
|
296
|
+
return param;
|
|
142
297
|
}
|
|
143
298
|
/**
|
|
144
299
|
* Build full URL from endpoint
|
|
@@ -172,14 +327,78 @@ var init_HttpClient = __esm({
|
|
|
172
327
|
return filtered.length > 0 ? `?${filtered.join("&")}` : "";
|
|
173
328
|
}
|
|
174
329
|
/**
|
|
175
|
-
*
|
|
330
|
+
* Sanitize data for logging by redacting sensitive fields
|
|
331
|
+
*/
|
|
332
|
+
sanitizeForLogging(data) {
|
|
333
|
+
if (!data || typeof data !== "object") {
|
|
334
|
+
return data;
|
|
335
|
+
}
|
|
336
|
+
const sensitiveFields = [
|
|
337
|
+
"token",
|
|
338
|
+
"access_token",
|
|
339
|
+
"refresh_token",
|
|
340
|
+
"password",
|
|
341
|
+
"secret",
|
|
342
|
+
"apiKey",
|
|
343
|
+
"api_key",
|
|
344
|
+
"bearer",
|
|
345
|
+
"authorization",
|
|
346
|
+
"csrf_token",
|
|
347
|
+
"session",
|
|
348
|
+
"cookie"
|
|
349
|
+
];
|
|
350
|
+
const sanitize = (obj) => {
|
|
351
|
+
const sanitized = {};
|
|
352
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
353
|
+
const keyLower = key.toLowerCase();
|
|
354
|
+
const isSensitive = sensitiveFields.some((field) => keyLower.includes(field));
|
|
355
|
+
if (isSensitive) {
|
|
356
|
+
sanitized[key] = "[REDACTED]";
|
|
357
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
358
|
+
sanitized[key] = sanitize(value);
|
|
359
|
+
} else if (Array.isArray(value)) {
|
|
360
|
+
sanitized[key] = value.map(
|
|
361
|
+
(item) => item && typeof item === "object" ? sanitize(item) : item
|
|
362
|
+
);
|
|
363
|
+
} else {
|
|
364
|
+
sanitized[key] = value;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return sanitized;
|
|
368
|
+
};
|
|
369
|
+
if (Array.isArray(data)) {
|
|
370
|
+
return data.map(
|
|
371
|
+
(item) => item && typeof item === "object" ? sanitize(item) : item
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
return sanitize(data);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Calculate delay for retry with exponential backoff
|
|
378
|
+
*/
|
|
379
|
+
calculateRetryDelay(attempt) {
|
|
380
|
+
const delay = this.retryConfig.initialDelay * Math.pow(2, attempt);
|
|
381
|
+
return Math.min(delay, this.retryConfig.maxDelay);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Sleep for specified milliseconds
|
|
176
385
|
*/
|
|
177
|
-
|
|
386
|
+
sleep(ms) {
|
|
387
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Perform HTTP request with automatic retry on transient failures
|
|
391
|
+
*/
|
|
392
|
+
async request(method, endpoint, options, retryAttempt = 0) {
|
|
178
393
|
const url = this.buildUrl(endpoint) + (options?.params ? this.buildQueryString(options.params) : "");
|
|
179
394
|
const isFormData = options?.body instanceof FormData;
|
|
180
395
|
const headers = this.buildHeaders(options?.headers, isFormData);
|
|
181
396
|
const controller = new AbortController();
|
|
182
397
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
398
|
+
const startTime = Date.now();
|
|
399
|
+
if (this.debug) {
|
|
400
|
+
this.logger.log(`[Marvin] ${method} ${url}`);
|
|
401
|
+
}
|
|
183
402
|
try {
|
|
184
403
|
const response = await fetch(url, {
|
|
185
404
|
method,
|
|
@@ -189,11 +408,54 @@ var init_HttpClient = __esm({
|
|
|
189
408
|
signal: controller.signal
|
|
190
409
|
});
|
|
191
410
|
clearTimeout(timeoutId);
|
|
411
|
+
const duration = Date.now() - startTime;
|
|
412
|
+
if (this.debug) {
|
|
413
|
+
this.logger.log(
|
|
414
|
+
`[Marvin] ${response.status} ${response.statusText} (${duration}ms)`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
192
417
|
if (response.status === 401 && this.auth.handleUnauthorized) {
|
|
193
418
|
await this.auth.handleUnauthorized();
|
|
194
419
|
}
|
|
195
420
|
if (!response.ok) {
|
|
196
421
|
const errorText = await response.text().catch(() => "");
|
|
422
|
+
if (this.debug) {
|
|
423
|
+
this.logger.error(
|
|
424
|
+
`[Marvin] Error response: ${response.status} ${response.statusText}`,
|
|
425
|
+
errorText ? `Body: ${errorText}` : ""
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
const isRetryable = this.retryConfig.retryableStatuses.includes(response.status);
|
|
429
|
+
const canRetry = retryAttempt < this.retryConfig.maxRetries;
|
|
430
|
+
if (isRetryable && canRetry) {
|
|
431
|
+
const delay = this.calculateRetryDelay(retryAttempt);
|
|
432
|
+
if (this.debug) {
|
|
433
|
+
this.logger.warn(
|
|
434
|
+
`[Marvin] Retry ${retryAttempt + 1}/${this.retryConfig.maxRetries} after ${delay}ms (status: ${response.status})`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
await this.sleep(delay);
|
|
438
|
+
return this.request(method, endpoint, options, retryAttempt + 1);
|
|
439
|
+
}
|
|
440
|
+
if (response.status === 404) {
|
|
441
|
+
throw new MarvinNotFoundError(
|
|
442
|
+
`Resource not found: ${endpoint}`,
|
|
443
|
+
endpoint
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
if (response.status === 401 || response.status === 403) {
|
|
447
|
+
throw new MarvinAuthError(
|
|
448
|
+
`Authentication failed: ${response.statusText}`,
|
|
449
|
+
response.status
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
if (response.status >= 500) {
|
|
453
|
+
throw new MarvinServerError(
|
|
454
|
+
`Server error: ${response.status} ${response.statusText}`,
|
|
455
|
+
response.status,
|
|
456
|
+
endpoint
|
|
457
|
+
);
|
|
458
|
+
}
|
|
197
459
|
throw MarvinApiError.fromResponse(
|
|
198
460
|
response.status,
|
|
199
461
|
response.statusText,
|
|
@@ -205,21 +467,39 @@ var init_HttpClient = __esm({
|
|
|
205
467
|
if (!contentType || !contentType.includes("application/json")) {
|
|
206
468
|
return void 0;
|
|
207
469
|
}
|
|
208
|
-
|
|
470
|
+
const data = await response.json();
|
|
471
|
+
if (this.debug) {
|
|
472
|
+
const sanitized = this.sanitizeForLogging(data);
|
|
473
|
+
this.logger.log("[Marvin] Response:", JSON.stringify(sanitized, null, 2));
|
|
474
|
+
}
|
|
475
|
+
return data;
|
|
209
476
|
} catch (error) {
|
|
210
477
|
clearTimeout(timeoutId);
|
|
211
|
-
if (error instanceof MarvinApiError) {
|
|
478
|
+
if (error instanceof MarvinApiError || error instanceof MarvinNotFoundError || error instanceof MarvinAuthError || error instanceof MarvinServerError) {
|
|
212
479
|
throw error;
|
|
213
480
|
}
|
|
214
481
|
if (error instanceof Error) {
|
|
482
|
+
const canRetry = retryAttempt < this.retryConfig.maxRetries;
|
|
483
|
+
if (canRetry && error.name !== "AbortError") {
|
|
484
|
+
const delay = this.calculateRetryDelay(retryAttempt);
|
|
485
|
+
if (this.debug) {
|
|
486
|
+
this.logger.warn(
|
|
487
|
+
`[Marvin] Retry ${retryAttempt + 1}/${this.retryConfig.maxRetries} after ${delay}ms (network error)`
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
await this.sleep(delay);
|
|
491
|
+
return this.request(method, endpoint, options, retryAttempt + 1);
|
|
492
|
+
}
|
|
215
493
|
if (error.name === "AbortError") {
|
|
216
|
-
throw new
|
|
494
|
+
throw new MarvinNetworkError(
|
|
217
495
|
`Request timeout after ${this.timeout}ms`,
|
|
218
|
-
|
|
219
|
-
endpoint
|
|
496
|
+
error
|
|
220
497
|
);
|
|
221
498
|
}
|
|
222
|
-
throw new
|
|
499
|
+
throw new MarvinNetworkError(
|
|
500
|
+
`Network error: ${error.message}`,
|
|
501
|
+
error
|
|
502
|
+
);
|
|
223
503
|
}
|
|
224
504
|
throw error;
|
|
225
505
|
}
|
|
@@ -290,10 +570,27 @@ var init_AuthStrategy = __esm({
|
|
|
290
570
|
}
|
|
291
571
|
sessionToken;
|
|
292
572
|
onUnauthorized;
|
|
573
|
+
csrfToken;
|
|
574
|
+
/**
|
|
575
|
+
* Set CSRF token for cross-site request forgery protection
|
|
576
|
+
* The token should be obtained from the server after login
|
|
577
|
+
*/
|
|
578
|
+
setCsrfToken(token) {
|
|
579
|
+
this.csrfToken = token;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Get current CSRF token
|
|
583
|
+
*/
|
|
584
|
+
getCsrfToken() {
|
|
585
|
+
return this.csrfToken;
|
|
586
|
+
}
|
|
293
587
|
injectAuth(headers) {
|
|
294
588
|
if (this.sessionToken) {
|
|
295
589
|
headers.set("Authorization", `Bearer ${this.sessionToken}`);
|
|
296
590
|
}
|
|
591
|
+
if (this.csrfToken) {
|
|
592
|
+
headers.set("X-CSRF-Token", this.csrfToken);
|
|
593
|
+
}
|
|
297
594
|
}
|
|
298
595
|
async handleUnauthorized() {
|
|
299
596
|
if (this.onUnauthorized) {
|
|
@@ -301,7 +598,9 @@ var init_AuthStrategy = __esm({
|
|
|
301
598
|
}
|
|
302
599
|
}
|
|
303
600
|
getDescription() {
|
|
304
|
-
|
|
601
|
+
const auth = this.sessionToken ? "Session authentication (explicit token)" : "Session authentication (cookies)";
|
|
602
|
+
const csrf = this.csrfToken ? " with CSRF protection" : "";
|
|
603
|
+
return auth + csrf;
|
|
305
604
|
}
|
|
306
605
|
};
|
|
307
606
|
NoAuth = class {
|
|
@@ -361,11 +660,17 @@ var init_cache = __esm({
|
|
|
361
660
|
"src/core/cache.ts"() {
|
|
362
661
|
"use strict";
|
|
363
662
|
MarvinCache = class {
|
|
364
|
-
constructor(duration = 5 * 60 * 1e3) {
|
|
663
|
+
constructor(duration = 5 * 60 * 1e3, maxEntries = 1e3) {
|
|
365
664
|
this.duration = duration;
|
|
665
|
+
this.maxEntries = maxEntries;
|
|
366
666
|
}
|
|
367
667
|
duration;
|
|
668
|
+
maxEntries;
|
|
368
669
|
cache = /* @__PURE__ */ new Map();
|
|
670
|
+
/**
|
|
671
|
+
* Get cached data by key
|
|
672
|
+
* Returns undefined if not found or expired
|
|
673
|
+
*/
|
|
369
674
|
get(key) {
|
|
370
675
|
const entry = this.cache.get(key);
|
|
371
676
|
if (!entry) return void 0;
|
|
@@ -376,25 +681,149 @@ var init_cache = __esm({
|
|
|
376
681
|
}
|
|
377
682
|
return entry.data;
|
|
378
683
|
}
|
|
379
|
-
|
|
684
|
+
/**
|
|
685
|
+
* Set cached data with optional tags for invalidation
|
|
686
|
+
*/
|
|
687
|
+
set(key, data, tags) {
|
|
688
|
+
if (this.cache.size >= this.maxEntries) {
|
|
689
|
+
const oldestKey = this.cache.keys().next().value;
|
|
690
|
+
if (oldestKey) {
|
|
691
|
+
this.cache.delete(oldestKey);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
380
694
|
this.cache.set(key, {
|
|
381
695
|
data,
|
|
382
|
-
timestamp: Date.now()
|
|
696
|
+
timestamp: Date.now(),
|
|
697
|
+
tags
|
|
383
698
|
});
|
|
384
699
|
}
|
|
700
|
+
/**
|
|
701
|
+
* Check if key exists and is not expired
|
|
702
|
+
*/
|
|
385
703
|
has(key) {
|
|
386
704
|
return this.get(key) !== void 0;
|
|
387
705
|
}
|
|
706
|
+
/**
|
|
707
|
+
* Clear all cached entries
|
|
708
|
+
*/
|
|
388
709
|
clear() {
|
|
389
710
|
this.cache.clear();
|
|
390
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* Delete a specific cache entry
|
|
714
|
+
*/
|
|
391
715
|
delete(key) {
|
|
392
716
|
this.cache.delete(key);
|
|
393
717
|
}
|
|
718
|
+
/**
|
|
719
|
+
* Invalidate all cache entries with a specific tag
|
|
720
|
+
* Useful for invalidating related data after mutations
|
|
721
|
+
*
|
|
722
|
+
* @example
|
|
723
|
+
* ```typescript
|
|
724
|
+
* // Cache entries with tags
|
|
725
|
+
* cache.set('entry:1', data, ['entries', 'entry:1']);
|
|
726
|
+
* cache.set('entries:list', data, ['entries']);
|
|
727
|
+
*
|
|
728
|
+
* // Invalidate all entries
|
|
729
|
+
* cache.invalidateByTag('entries');
|
|
730
|
+
* ```
|
|
731
|
+
*/
|
|
732
|
+
invalidateByTag(tag) {
|
|
733
|
+
let count = 0;
|
|
734
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
735
|
+
if (entry.tags?.includes(tag)) {
|
|
736
|
+
this.cache.delete(key);
|
|
737
|
+
count++;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return count;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Get cache statistics
|
|
744
|
+
*/
|
|
745
|
+
getStats() {
|
|
746
|
+
let oldest = null;
|
|
747
|
+
let newest = null;
|
|
748
|
+
for (const entry of this.cache.values()) {
|
|
749
|
+
if (oldest === null || entry.timestamp < oldest) {
|
|
750
|
+
oldest = entry.timestamp;
|
|
751
|
+
}
|
|
752
|
+
if (newest === null || entry.timestamp > newest) {
|
|
753
|
+
newest = entry.timestamp;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return {
|
|
757
|
+
size: this.cache.size,
|
|
758
|
+
maxEntries: this.maxEntries,
|
|
759
|
+
oldestEntry: oldest,
|
|
760
|
+
newestEntry: newest
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Remove expired entries (garbage collection)
|
|
765
|
+
* Returns number of entries removed
|
|
766
|
+
*/
|
|
767
|
+
prune() {
|
|
768
|
+
let count = 0;
|
|
769
|
+
const now = Date.now();
|
|
770
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
771
|
+
const age = now - entry.timestamp;
|
|
772
|
+
if (age > this.duration) {
|
|
773
|
+
this.cache.delete(key);
|
|
774
|
+
count++;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return count;
|
|
778
|
+
}
|
|
394
779
|
};
|
|
395
780
|
}
|
|
396
781
|
});
|
|
397
782
|
|
|
783
|
+
// src/core/validation.ts
|
|
784
|
+
function validateEmail(email, fieldName = "Email") {
|
|
785
|
+
if (!email || typeof email !== "string") {
|
|
786
|
+
throw new MarvinValidationError(`${fieldName} is required and must be a string`);
|
|
787
|
+
}
|
|
788
|
+
const trimmed = email.trim();
|
|
789
|
+
if (trimmed.length === 0) {
|
|
790
|
+
throw new MarvinValidationError(`${fieldName} cannot be empty`);
|
|
791
|
+
}
|
|
792
|
+
if (trimmed.length < 5) {
|
|
793
|
+
throw new MarvinValidationError(`${fieldName} is too short`);
|
|
794
|
+
}
|
|
795
|
+
if (trimmed.length > 254) {
|
|
796
|
+
throw new MarvinValidationError(`${fieldName} exceeds maximum length of 254 characters`);
|
|
797
|
+
}
|
|
798
|
+
if (!EMAIL_REGEX.test(trimmed)) {
|
|
799
|
+
throw new MarvinValidationError(
|
|
800
|
+
`${fieldName} format is invalid. Expected format: user@example.com`
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
const [localPart, domain] = trimmed.split("@");
|
|
804
|
+
if (localPart.length > 64) {
|
|
805
|
+
throw new MarvinValidationError(`${fieldName} local part exceeds 64 characters`);
|
|
806
|
+
}
|
|
807
|
+
if (domain.length > 253) {
|
|
808
|
+
throw new MarvinValidationError(`${fieldName} domain exceeds 253 characters`);
|
|
809
|
+
}
|
|
810
|
+
if (trimmed.includes("..")) {
|
|
811
|
+
throw new MarvinValidationError(`${fieldName} cannot contain consecutive dots`);
|
|
812
|
+
}
|
|
813
|
+
if (localPart.startsWith(".") || localPart.endsWith(".")) {
|
|
814
|
+
throw new MarvinValidationError(`${fieldName} local part cannot start or end with a dot`);
|
|
815
|
+
}
|
|
816
|
+
return trimmed;
|
|
817
|
+
}
|
|
818
|
+
var EMAIL_REGEX;
|
|
819
|
+
var init_validation = __esm({
|
|
820
|
+
"src/core/validation.ts"() {
|
|
821
|
+
"use strict";
|
|
822
|
+
init_errors();
|
|
823
|
+
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])?)*$/;
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
|
|
398
827
|
// src/core/index.ts
|
|
399
828
|
var init_core = __esm({
|
|
400
829
|
"src/core/index.ts"() {
|
|
@@ -404,6 +833,7 @@ var init_core = __esm({
|
|
|
404
833
|
init_errors();
|
|
405
834
|
init_pagination();
|
|
406
835
|
init_cache();
|
|
836
|
+
init_validation();
|
|
407
837
|
}
|
|
408
838
|
});
|
|
409
839
|
|
|
@@ -418,7 +848,9 @@ var init_http2 = __esm({
|
|
|
418
848
|
super({
|
|
419
849
|
baseUrl: config.apiUrl,
|
|
420
850
|
auth: new BearerTokenAuth(config.siteClientToken),
|
|
421
|
-
credentials: "same-origin"
|
|
851
|
+
credentials: "same-origin",
|
|
852
|
+
debug: config.debug,
|
|
853
|
+
logger: config.logger
|
|
422
854
|
});
|
|
423
855
|
}
|
|
424
856
|
/**
|
|
@@ -437,113 +869,54 @@ var init_entry = __esm({
|
|
|
437
869
|
"src/entries/entry.ts"() {
|
|
438
870
|
"use strict";
|
|
439
871
|
Entry = class {
|
|
440
|
-
constructor(
|
|
441
|
-
this.
|
|
442
|
-
this.http = http;
|
|
443
|
-
this.workspaceSlug = workspaceSlug;
|
|
444
|
-
}
|
|
445
|
-
data;
|
|
446
|
-
http;
|
|
447
|
-
workspaceSlug;
|
|
448
|
-
// Expose all entry data as properties
|
|
449
|
-
get id() {
|
|
450
|
-
return this.data.id;
|
|
872
|
+
constructor(raw) {
|
|
873
|
+
this.raw = raw;
|
|
451
874
|
}
|
|
875
|
+
raw;
|
|
452
876
|
get title() {
|
|
453
|
-
return this.
|
|
877
|
+
return this.raw.title;
|
|
454
878
|
}
|
|
455
879
|
get slug() {
|
|
456
|
-
return this.
|
|
880
|
+
return this.raw.slug;
|
|
457
881
|
}
|
|
458
882
|
get summary() {
|
|
459
|
-
return this.
|
|
883
|
+
return this.raw.summary;
|
|
460
884
|
}
|
|
461
|
-
get
|
|
462
|
-
return this.
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* @deprecated Use field('body') or dataJson instead. Will be removed in v3.0.0
|
|
466
|
-
*/
|
|
467
|
-
get contentMarkdown() {
|
|
468
|
-
return this.data.contentMarkdown;
|
|
885
|
+
get entryType() {
|
|
886
|
+
return this.raw.entryType;
|
|
469
887
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
* Access specific fields using the field() helper method.
|
|
473
|
-
* @since 2.0.0
|
|
474
|
-
*/
|
|
475
|
-
get dataJson() {
|
|
476
|
-
return this.data.dataJson;
|
|
888
|
+
get entryTypeInfo() {
|
|
889
|
+
return this.raw.entryTypeInfo;
|
|
477
890
|
}
|
|
478
|
-
get
|
|
479
|
-
return this.data
|
|
891
|
+
get data() {
|
|
892
|
+
return this.raw.data;
|
|
480
893
|
}
|
|
481
|
-
get
|
|
482
|
-
return this.
|
|
894
|
+
get metadataJson() {
|
|
895
|
+
return this.raw.metadataJson;
|
|
483
896
|
}
|
|
484
897
|
get publishedAt() {
|
|
485
|
-
return this.
|
|
898
|
+
return this.raw.publishedAt;
|
|
486
899
|
}
|
|
487
|
-
get
|
|
488
|
-
return this.
|
|
900
|
+
get order() {
|
|
901
|
+
return this.raw.order;
|
|
489
902
|
}
|
|
490
|
-
get updatedAt() {
|
|
491
|
-
return this.data.updatedAt;
|
|
492
|
-
}
|
|
493
|
-
get entryTypeId() {
|
|
494
|
-
return this.data.entryTypeId;
|
|
495
|
-
}
|
|
496
|
-
get entryType() {
|
|
497
|
-
return this.data.entryType;
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* Get entry assets
|
|
501
|
-
*/
|
|
502
903
|
get assets() {
|
|
503
|
-
return this.
|
|
904
|
+
return this.raw.assets;
|
|
504
905
|
}
|
|
505
|
-
/**
|
|
506
|
-
* Get entry collections
|
|
507
|
-
*/
|
|
508
906
|
get collections() {
|
|
509
|
-
return this.
|
|
907
|
+
return this.raw.collections;
|
|
908
|
+
}
|
|
909
|
+
get resources() {
|
|
910
|
+
return this.raw.resources;
|
|
510
911
|
}
|
|
511
|
-
/**
|
|
512
|
-
* Get a specific field value from the entry's schema-driven content (dataJson).
|
|
513
|
-
*
|
|
514
|
-
* @param key - Field key as defined in the entry type's schema
|
|
515
|
-
* @returns The field value, or undefined if not present
|
|
516
|
-
* @since 2.0.0
|
|
517
|
-
*
|
|
518
|
-
* @example
|
|
519
|
-
* ```typescript
|
|
520
|
-
* const entry = await client.entries.get('my-entry');
|
|
521
|
-
* const body = entry.field('body'); // Get markdown body
|
|
522
|
-
* const difficulty = entry.field('difficulty'); // Get difficulty level
|
|
523
|
-
* const heroImage = entry.field('heroImage'); // Get hero image UUID
|
|
524
|
-
* ```
|
|
525
|
-
*/
|
|
526
912
|
field(key) {
|
|
527
|
-
return this.data
|
|
913
|
+
return this.raw.data[key];
|
|
528
914
|
}
|
|
529
|
-
/**
|
|
530
|
-
* Get all fields from the entry's schema-driven content.
|
|
531
|
-
* @since 2.0.0
|
|
532
|
-
*/
|
|
533
915
|
get fields() {
|
|
534
|
-
return this.data
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Get related entries (future)
|
|
538
|
-
*/
|
|
539
|
-
async relatedEntries() {
|
|
540
|
-
throw new Error("Related entries not yet implemented");
|
|
916
|
+
return this.raw.data;
|
|
541
917
|
}
|
|
542
|
-
/**
|
|
543
|
-
* Get raw entry data
|
|
544
|
-
*/
|
|
545
918
|
toJSON() {
|
|
546
|
-
return this.
|
|
919
|
+
return this.raw;
|
|
547
920
|
}
|
|
548
921
|
};
|
|
549
922
|
}
|
|
@@ -555,6 +928,7 @@ var init_entries = __esm({
|
|
|
555
928
|
"src/entries/entries.ts"() {
|
|
556
929
|
"use strict";
|
|
557
930
|
init_entry();
|
|
931
|
+
init_errors();
|
|
558
932
|
EntriesModule = class {
|
|
559
933
|
constructor(http, workspaceSlug) {
|
|
560
934
|
this.http = http;
|
|
@@ -571,7 +945,6 @@ var init_entries = __esm({
|
|
|
571
945
|
collection: options.collection,
|
|
572
946
|
limit: options.limit,
|
|
573
947
|
offset: options.offset
|
|
574
|
-
// Don't send status - backend always returns published
|
|
575
948
|
});
|
|
576
949
|
const endpoint = `/api/publish/${this.workspaceSlug}/entries${queryString}`;
|
|
577
950
|
const response = await this.http.fetch(endpoint);
|
|
@@ -579,11 +952,19 @@ var init_entries = __esm({
|
|
|
579
952
|
}
|
|
580
953
|
/**
|
|
581
954
|
* Get a single entry by slug
|
|
955
|
+
* Returns null if entry is not found (404)
|
|
582
956
|
*/
|
|
583
957
|
async get(slug) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
958
|
+
try {
|
|
959
|
+
const endpoint = `/api/publish/${this.workspaceSlug}/entries/${slug}`;
|
|
960
|
+
const data = await this.http.fetch(endpoint);
|
|
961
|
+
return new Entry(data);
|
|
962
|
+
} catch (error) {
|
|
963
|
+
if (error instanceof MarvinNotFoundError) {
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
throw error;
|
|
967
|
+
}
|
|
587
968
|
}
|
|
588
969
|
/**
|
|
589
970
|
* Convenience: Get all pages
|
|
@@ -613,18 +994,10 @@ var init_collection = __esm({
|
|
|
613
994
|
"src/collections/collection.ts"() {
|
|
614
995
|
"use strict";
|
|
615
996
|
Collection = class {
|
|
616
|
-
constructor(data
|
|
997
|
+
constructor(data) {
|
|
617
998
|
this.data = data;
|
|
618
|
-
this.http = http;
|
|
619
|
-
this.workspaceSlug = workspaceSlug;
|
|
620
999
|
}
|
|
621
1000
|
data;
|
|
622
|
-
http;
|
|
623
|
-
workspaceSlug;
|
|
624
|
-
// Expose all collection data as properties
|
|
625
|
-
get id() {
|
|
626
|
-
return this.data.id;
|
|
627
|
-
}
|
|
628
1001
|
get name() {
|
|
629
1002
|
return this.data.name;
|
|
630
1003
|
}
|
|
@@ -634,48 +1007,21 @@ var init_collection = __esm({
|
|
|
634
1007
|
get description() {
|
|
635
1008
|
return this.data.description;
|
|
636
1009
|
}
|
|
637
|
-
get icon() {
|
|
638
|
-
return this.data.icon;
|
|
639
|
-
}
|
|
640
|
-
get color() {
|
|
641
|
-
return this.data.color;
|
|
642
|
-
}
|
|
643
|
-
get sortOrder() {
|
|
644
|
-
return this.data.sortOrder;
|
|
645
|
-
}
|
|
646
1010
|
get isSmart() {
|
|
647
1011
|
return this.data.isSmart;
|
|
648
1012
|
}
|
|
649
1013
|
get smartRules() {
|
|
650
1014
|
return this.data.smartRules;
|
|
651
1015
|
}
|
|
652
|
-
get
|
|
653
|
-
return this.data.
|
|
1016
|
+
get metadataJson() {
|
|
1017
|
+
return this.data.metadataJson;
|
|
654
1018
|
}
|
|
655
|
-
get
|
|
656
|
-
return this.data.
|
|
1019
|
+
get entryCount() {
|
|
1020
|
+
return this.data.entryCount;
|
|
657
1021
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
*/
|
|
661
|
-
async entries() {
|
|
662
|
-
return this.data.entries || [];
|
|
663
|
-
}
|
|
664
|
-
/**
|
|
665
|
-
* Get assets in this collection (future)
|
|
666
|
-
*/
|
|
667
|
-
async assets() {
|
|
668
|
-
throw new Error("Collection assets not yet implemented");
|
|
669
|
-
}
|
|
670
|
-
/**
|
|
671
|
-
* Get collection metadata (future)
|
|
672
|
-
*/
|
|
673
|
-
async metadata() {
|
|
674
|
-
throw new Error("Collection metadata not yet implemented");
|
|
1022
|
+
get entries() {
|
|
1023
|
+
return this.data.entries;
|
|
675
1024
|
}
|
|
676
|
-
/**
|
|
677
|
-
* Get raw collection data
|
|
678
|
-
*/
|
|
679
1025
|
toJSON() {
|
|
680
1026
|
return this.data;
|
|
681
1027
|
}
|
|
@@ -689,6 +1035,7 @@ var init_collections = __esm({
|
|
|
689
1035
|
"src/collections/collections.ts"() {
|
|
690
1036
|
"use strict";
|
|
691
1037
|
init_collection();
|
|
1038
|
+
init_errors();
|
|
692
1039
|
CollectionsModule = class {
|
|
693
1040
|
constructor(http, workspaceSlug) {
|
|
694
1041
|
this.http = http;
|
|
@@ -706,18 +1053,52 @@ var init_collections = __esm({
|
|
|
706
1053
|
}
|
|
707
1054
|
/**
|
|
708
1055
|
* Get a single collection by slug
|
|
1056
|
+
* Returns null if collection is not found (404)
|
|
709
1057
|
*/
|
|
710
1058
|
async get(slug) {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1059
|
+
try {
|
|
1060
|
+
const endpoint = `/api/publish/${this.workspaceSlug}/collections/${slug}`;
|
|
1061
|
+
const data = await this.http.fetch(endpoint);
|
|
1062
|
+
return new Collection(data);
|
|
1063
|
+
} catch (error) {
|
|
1064
|
+
if (error instanceof MarvinNotFoundError) {
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
throw error;
|
|
1068
|
+
}
|
|
714
1069
|
}
|
|
715
1070
|
/**
|
|
716
1071
|
* Get entries in a collection
|
|
1072
|
+
* Returns empty array if collection is not found
|
|
717
1073
|
*/
|
|
718
1074
|
async entries(slug) {
|
|
719
1075
|
const collection = await this.get(slug);
|
|
720
|
-
|
|
1076
|
+
if (!collection) {
|
|
1077
|
+
return [];
|
|
1078
|
+
}
|
|
1079
|
+
return collection.entries;
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Try multiple collection slugs in fallback order
|
|
1083
|
+
* Returns the first collection that exists and has entries
|
|
1084
|
+
*/
|
|
1085
|
+
async fallback(slugs, options = {}) {
|
|
1086
|
+
const { requireEntries = true } = options;
|
|
1087
|
+
for (const slug of slugs) {
|
|
1088
|
+
try {
|
|
1089
|
+
const collection = await this.get(slug);
|
|
1090
|
+
if (!collection) {
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
const entries = collection.entries;
|
|
1094
|
+
if (!requireEntries || entries.length > 0) {
|
|
1095
|
+
return entries;
|
|
1096
|
+
}
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
return [];
|
|
721
1102
|
}
|
|
722
1103
|
};
|
|
723
1104
|
}
|
|
@@ -783,18 +1164,10 @@ var init_resource = __esm({
|
|
|
783
1164
|
"src/resources/resource.ts"() {
|
|
784
1165
|
"use strict";
|
|
785
1166
|
Resource = class {
|
|
786
|
-
constructor(data
|
|
1167
|
+
constructor(data) {
|
|
787
1168
|
this.data = data;
|
|
788
|
-
this.http = http;
|
|
789
|
-
this.workspaceSlug = workspaceSlug;
|
|
790
1169
|
}
|
|
791
1170
|
data;
|
|
792
|
-
http;
|
|
793
|
-
workspaceSlug;
|
|
794
|
-
// Expose all resource data as properties
|
|
795
|
-
get id() {
|
|
796
|
-
return this.data.id;
|
|
797
|
-
}
|
|
798
1171
|
get name() {
|
|
799
1172
|
return this.data.name;
|
|
800
1173
|
}
|
|
@@ -813,31 +1186,12 @@ var init_resource = __esm({
|
|
|
813
1186
|
get url() {
|
|
814
1187
|
return this.data.url;
|
|
815
1188
|
}
|
|
816
|
-
get
|
|
817
|
-
return this.data.
|
|
818
|
-
}
|
|
819
|
-
get createdAt() {
|
|
820
|
-
return this.data.createdAt;
|
|
821
|
-
}
|
|
822
|
-
get updatedAt() {
|
|
823
|
-
return this.data.updatedAt;
|
|
1189
|
+
get metadataJson() {
|
|
1190
|
+
return this.data.metadataJson;
|
|
824
1191
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
*/
|
|
828
|
-
async entries() {
|
|
829
|
-
const endpoint = `/api/publish/${this.workspaceSlug}/resources/${this.data.slug}/entries`;
|
|
830
|
-
return this.http.fetch(endpoint);
|
|
831
|
-
}
|
|
832
|
-
/**
|
|
833
|
-
* Get assets related to this resource (future)
|
|
834
|
-
*/
|
|
835
|
-
async assets() {
|
|
836
|
-
throw new Error("Resource assets not yet implemented");
|
|
1192
|
+
get entries() {
|
|
1193
|
+
return this.data.entries;
|
|
837
1194
|
}
|
|
838
|
-
/**
|
|
839
|
-
* Get raw resource data
|
|
840
|
-
*/
|
|
841
1195
|
toJSON() {
|
|
842
1196
|
return this.data;
|
|
843
1197
|
}
|
|
@@ -877,14 +1231,34 @@ var init_resources = __esm({
|
|
|
877
1231
|
async get(slug) {
|
|
878
1232
|
const endpoint = `/api/publish/${this.workspaceSlug}/resources/${slug}`;
|
|
879
1233
|
const data = await this.http.fetch(endpoint);
|
|
880
|
-
return new Resource(data
|
|
1234
|
+
return new Resource(data);
|
|
881
1235
|
}
|
|
882
1236
|
/**
|
|
883
|
-
* Get
|
|
1237
|
+
* Get entry slugs that reference this resource
|
|
884
1238
|
*/
|
|
885
1239
|
async entries(slug) {
|
|
886
1240
|
const resource = await this.get(slug);
|
|
887
|
-
return resource.entries
|
|
1241
|
+
return resource.entries;
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
// src/renderers/renderers.ts
|
|
1248
|
+
var RenderersModule;
|
|
1249
|
+
var init_renderers = __esm({
|
|
1250
|
+
"src/renderers/renderers.ts"() {
|
|
1251
|
+
"use strict";
|
|
1252
|
+
RenderersModule = class {
|
|
1253
|
+
constructor(http, workspaceSlug) {
|
|
1254
|
+
this.http = http;
|
|
1255
|
+
this.workspaceSlug = workspaceSlug;
|
|
1256
|
+
}
|
|
1257
|
+
http;
|
|
1258
|
+
workspaceSlug;
|
|
1259
|
+
async list() {
|
|
1260
|
+
const endpoint = `/api/publish/${this.workspaceSlug}/entry-types`;
|
|
1261
|
+
return this.http.fetch(endpoint);
|
|
888
1262
|
}
|
|
889
1263
|
};
|
|
890
1264
|
}
|
|
@@ -899,6 +1273,7 @@ var init_workspace = __esm({
|
|
|
899
1273
|
init_collections();
|
|
900
1274
|
init_assets();
|
|
901
1275
|
init_resources();
|
|
1276
|
+
init_renderers();
|
|
902
1277
|
Workspace = class {
|
|
903
1278
|
constructor(http, slug) {
|
|
904
1279
|
this.http = http;
|
|
@@ -907,6 +1282,7 @@ var init_workspace = __esm({
|
|
|
907
1282
|
this.collections = new CollectionsModule(http, slug);
|
|
908
1283
|
this.assets = new AssetsModule(http, slug);
|
|
909
1284
|
this.resources = new ResourcesModule(http, slug);
|
|
1285
|
+
this.renderers = new RenderersModule(http, slug);
|
|
910
1286
|
}
|
|
911
1287
|
http;
|
|
912
1288
|
_site = null;
|
|
@@ -915,6 +1291,7 @@ var init_workspace = __esm({
|
|
|
915
1291
|
collections;
|
|
916
1292
|
assets;
|
|
917
1293
|
resources;
|
|
1294
|
+
renderers;
|
|
918
1295
|
/**
|
|
919
1296
|
* Get workspace slug
|
|
920
1297
|
*/
|
|
@@ -942,12 +1319,6 @@ var init_workspace = __esm({
|
|
|
942
1319
|
const endpoint = `/api/publish/${this._slug}`;
|
|
943
1320
|
return this.http.fetch(endpoint);
|
|
944
1321
|
}
|
|
945
|
-
/**
|
|
946
|
-
* Get workspace settings (future)
|
|
947
|
-
*/
|
|
948
|
-
async settings() {
|
|
949
|
-
throw new Error("Workspace settings not yet implemented");
|
|
950
|
-
}
|
|
951
1322
|
};
|
|
952
1323
|
}
|
|
953
1324
|
});
|
|
@@ -966,8 +1337,11 @@ var init_marvin = __esm({
|
|
|
966
1337
|
init_core();
|
|
967
1338
|
init_workspace();
|
|
968
1339
|
MarvinClient = class {
|
|
1340
|
+
http;
|
|
1341
|
+
cache;
|
|
1342
|
+
workspace;
|
|
1343
|
+
initialized = false;
|
|
969
1344
|
constructor(config) {
|
|
970
|
-
this.config = config;
|
|
971
1345
|
validateConfig(config);
|
|
972
1346
|
this.http = new MarvinHttpClient(config);
|
|
973
1347
|
this.cache = new MarvinCache(config.cacheDuration);
|
|
@@ -976,11 +1350,6 @@ var init_marvin = __esm({
|
|
|
976
1350
|
this.initialize();
|
|
977
1351
|
}
|
|
978
1352
|
}
|
|
979
|
-
config;
|
|
980
|
-
http;
|
|
981
|
-
cache;
|
|
982
|
-
workspace;
|
|
983
|
-
initialized = false;
|
|
984
1353
|
/**
|
|
985
1354
|
* Initialize the SDK
|
|
986
1355
|
* Preloads site configuration and metadata
|
|
@@ -1042,6 +1411,12 @@ var init_marvin = __esm({
|
|
|
1042
1411
|
get resources() {
|
|
1043
1412
|
return this.workspace.resources;
|
|
1044
1413
|
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Get renderers module
|
|
1416
|
+
*/
|
|
1417
|
+
get renderers() {
|
|
1418
|
+
return this.workspace.renderers;
|
|
1419
|
+
}
|
|
1045
1420
|
// ======================
|
|
1046
1421
|
// Convenience Methods
|
|
1047
1422
|
// ======================
|
|
@@ -1081,70 +1456,74 @@ var init_marvin = __esm({
|
|
|
1081
1456
|
async resource(slug) {
|
|
1082
1457
|
return this.workspace.resources.get(slug);
|
|
1083
1458
|
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Try multiple collection slugs in fallback order
|
|
1461
|
+
* Returns entries from the first collection that exists and has content
|
|
1462
|
+
*/
|
|
1463
|
+
async collectionFallback(slugs, options) {
|
|
1464
|
+
return this.workspace.collections.fallback(slugs, options);
|
|
1465
|
+
}
|
|
1084
1466
|
// ======================
|
|
1085
1467
|
// Backwards-Compatible Publishing API
|
|
1086
1468
|
// ======================
|
|
1087
1469
|
/**
|
|
1088
1470
|
* @deprecated Use workspace.site or marvin.site instead
|
|
1089
|
-
* Get site configuration
|
|
1090
1471
|
*/
|
|
1091
1472
|
async getSite() {
|
|
1092
1473
|
return this.workspace.loadSite();
|
|
1093
1474
|
}
|
|
1094
1475
|
/**
|
|
1095
1476
|
* @deprecated Use workspace.entries.list() instead
|
|
1096
|
-
* Get all published entries
|
|
1097
1477
|
*/
|
|
1098
1478
|
async getEntries(options) {
|
|
1099
1479
|
return this.workspace.entries.list(options);
|
|
1100
1480
|
}
|
|
1101
1481
|
/**
|
|
1102
1482
|
* @deprecated Use workspace.entries.get() instead
|
|
1103
|
-
* Get a single published entry by slug
|
|
1104
1483
|
*/
|
|
1105
1484
|
async getEntry(slug) {
|
|
1106
1485
|
const entry = await this.workspace.entries.get(slug);
|
|
1486
|
+
if (!entry) {
|
|
1487
|
+
return null;
|
|
1488
|
+
}
|
|
1107
1489
|
return entry.toJSON();
|
|
1108
1490
|
}
|
|
1109
1491
|
/**
|
|
1110
1492
|
* @deprecated Use workspace.collections.list() instead
|
|
1111
|
-
* Get all collections
|
|
1112
1493
|
*/
|
|
1113
1494
|
async getCollections() {
|
|
1114
1495
|
return this.workspace.collections.list();
|
|
1115
1496
|
}
|
|
1116
1497
|
/**
|
|
1117
1498
|
* @deprecated Use workspace.collections.get() instead
|
|
1118
|
-
* Get a single collection by slug
|
|
1119
1499
|
*/
|
|
1120
1500
|
async getCollection(slug) {
|
|
1121
1501
|
const collection = await this.workspace.collections.get(slug);
|
|
1502
|
+
if (!collection) {
|
|
1503
|
+
return null;
|
|
1504
|
+
}
|
|
1122
1505
|
return collection.toJSON();
|
|
1123
1506
|
}
|
|
1124
1507
|
/**
|
|
1125
1508
|
* @deprecated Use workspace.collections.entries() instead
|
|
1126
|
-
* Get all entries in a collection
|
|
1127
1509
|
*/
|
|
1128
1510
|
async getCollectionEntries(slug) {
|
|
1129
1511
|
return this.workspace.collections.entries(slug);
|
|
1130
1512
|
}
|
|
1131
1513
|
/**
|
|
1132
1514
|
* @deprecated Use workspace.assets.list() instead
|
|
1133
|
-
* Get all published assets
|
|
1134
1515
|
*/
|
|
1135
1516
|
async getAssets(options) {
|
|
1136
1517
|
return this.workspace.assets.list(options);
|
|
1137
1518
|
}
|
|
1138
1519
|
/**
|
|
1139
1520
|
* @deprecated Use workspace.resources.list() instead
|
|
1140
|
-
* Get all published resources
|
|
1141
1521
|
*/
|
|
1142
1522
|
async getResources(options) {
|
|
1143
1523
|
return this.workspace.resources.list(options);
|
|
1144
1524
|
}
|
|
1145
1525
|
/**
|
|
1146
1526
|
* @deprecated Use workspace.resources.get() instead
|
|
1147
|
-
* Get a single resource by slug
|
|
1148
1527
|
*/
|
|
1149
1528
|
async getResource(slug) {
|
|
1150
1529
|
const resource = await this.workspace.resources.get(slug);
|
|
@@ -1152,7 +1531,6 @@ var init_marvin = __esm({
|
|
|
1152
1531
|
}
|
|
1153
1532
|
/**
|
|
1154
1533
|
* @deprecated Use workspace.resources.entries() instead
|
|
1155
|
-
* Get entries that reference a resource
|
|
1156
1534
|
*/
|
|
1157
1535
|
async getResourceEntries(slug) {
|
|
1158
1536
|
return this.workspace.resources.entries(slug);
|
|
@@ -1175,6 +1553,9 @@ __export(index_exports, {
|
|
|
1175
1553
|
MarvinClient: () => MarvinClient,
|
|
1176
1554
|
MarvinConfigError: () => MarvinConfigError,
|
|
1177
1555
|
MarvinError: () => MarvinError,
|
|
1556
|
+
MarvinNetworkError: () => MarvinNetworkError,
|
|
1557
|
+
MarvinNotFoundError: () => MarvinNotFoundError,
|
|
1558
|
+
MarvinServerError: () => MarvinServerError,
|
|
1178
1559
|
MarvinValidationError: () => MarvinValidationError,
|
|
1179
1560
|
NoAuth: () => NoAuth,
|
|
1180
1561
|
Resource: () => Resource,
|
|
@@ -1209,12 +1590,14 @@ var AuthClient = class extends HttpClient {
|
|
|
1209
1590
|
* Returns the created user object
|
|
1210
1591
|
*/
|
|
1211
1592
|
async register(data) {
|
|
1593
|
+
validateEmail(data.email, "Email");
|
|
1212
1594
|
return this.post("/api/users/register", data);
|
|
1213
1595
|
}
|
|
1214
1596
|
/**
|
|
1215
1597
|
* Request password reset (sends email with token)
|
|
1216
1598
|
*/
|
|
1217
1599
|
async forgotPassword(data) {
|
|
1600
|
+
validateEmail(data.email, "Email");
|
|
1218
1601
|
return this.post("/api/users/forgot-password", data);
|
|
1219
1602
|
}
|
|
1220
1603
|
/**
|
|
@@ -1271,6 +1654,9 @@ function createMarvinClient(config) {
|
|
|
1271
1654
|
MarvinClient,
|
|
1272
1655
|
MarvinConfigError,
|
|
1273
1656
|
MarvinError,
|
|
1657
|
+
MarvinNetworkError,
|
|
1658
|
+
MarvinNotFoundError,
|
|
1659
|
+
MarvinServerError,
|
|
1274
1660
|
MarvinValidationError,
|
|
1275
1661
|
NoAuth,
|
|
1276
1662
|
Resource,
|