@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/publish.cjs
DELETED
|
@@ -1,1487 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __esm = (fn, res) => function __init() {
|
|
7
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
-
};
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
-
};
|
|
13
|
-
var __copyProps = (to, from, except, desc) => {
|
|
14
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
-
for (let key of __getOwnPropNames(from))
|
|
16
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
-
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
-
|
|
23
|
-
// src/client/config.ts
|
|
24
|
-
var config_exports = {};
|
|
25
|
-
__export(config_exports, {
|
|
26
|
-
createConfigFromEnv: () => createConfigFromEnv,
|
|
27
|
-
validateConfig: () => validateConfig
|
|
28
|
-
});
|
|
29
|
-
function validateConfig(config) {
|
|
30
|
-
const receivedKeys = Object.keys(config).join(", ");
|
|
31
|
-
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
|
-
);
|
|
47
|
-
}
|
|
48
|
-
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
|
-
);
|
|
69
|
-
}
|
|
70
|
-
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
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
function createConfigFromEnv(overrides) {
|
|
126
|
-
return {
|
|
127
|
-
apiUrl: overrides?.apiUrl || process.env.MARVIN_API_URL || "",
|
|
128
|
-
siteClientToken: overrides?.siteClientToken || process.env.MARVIN_SITE_CLIENT_TOKEN || "",
|
|
129
|
-
workspaceSlug: overrides?.workspaceSlug || process.env.MARVIN_WORKSPACE_SLUG || "",
|
|
130
|
-
autoInitialize: overrides?.autoInitialize ?? false,
|
|
131
|
-
cacheDuration: overrides?.cacheDuration ?? 5 * 60 * 1e3,
|
|
132
|
-
// 5 minutes
|
|
133
|
-
debug: overrides?.debug ?? process.env.MARVIN_DEBUG === "true",
|
|
134
|
-
logger: overrides?.logger ?? console
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
var init_config = __esm({
|
|
138
|
-
"src/client/config.ts"() {
|
|
139
|
-
"use strict";
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// src/publish.ts
|
|
144
|
-
var publish_exports = {};
|
|
145
|
-
__export(publish_exports, {
|
|
146
|
-
BearerTokenAuth: () => BearerTokenAuth,
|
|
147
|
-
Collection: () => Collection,
|
|
148
|
-
Entry: () => Entry,
|
|
149
|
-
HttpClient: () => HttpClient,
|
|
150
|
-
MarvinApiError: () => MarvinApiError,
|
|
151
|
-
MarvinAuthError: () => MarvinAuthError,
|
|
152
|
-
MarvinCache: () => MarvinCache,
|
|
153
|
-
MarvinClient: () => MarvinClient,
|
|
154
|
-
MarvinConfigError: () => MarvinConfigError,
|
|
155
|
-
MarvinError: () => MarvinError,
|
|
156
|
-
MarvinNetworkError: () => MarvinNetworkError,
|
|
157
|
-
MarvinNotFoundError: () => MarvinNotFoundError,
|
|
158
|
-
MarvinServerError: () => MarvinServerError,
|
|
159
|
-
MarvinValidationError: () => MarvinValidationError,
|
|
160
|
-
Resource: () => Resource,
|
|
161
|
-
Workspace: () => Workspace,
|
|
162
|
-
createConfigFromEnv: () => createConfigFromEnv,
|
|
163
|
-
createMarvinClient: () => createMarvinClient,
|
|
164
|
-
createPaginatedResponse: () => createPaginatedResponse,
|
|
165
|
-
extractData: () => extractData,
|
|
166
|
-
isPaginatedResponse: () => isPaginatedResponse,
|
|
167
|
-
offsetToPage: () => offsetToPage,
|
|
168
|
-
pageToOffset: () => pageToOffset
|
|
169
|
-
});
|
|
170
|
-
module.exports = __toCommonJS(publish_exports);
|
|
171
|
-
|
|
172
|
-
// src/client/marvin.ts
|
|
173
|
-
init_config();
|
|
174
|
-
|
|
175
|
-
// src/core/errors/MarvinError.ts
|
|
176
|
-
var MarvinError = class _MarvinError extends Error {
|
|
177
|
-
constructor(message, code, statusCode) {
|
|
178
|
-
super(message);
|
|
179
|
-
this.code = code;
|
|
180
|
-
this.statusCode = statusCode;
|
|
181
|
-
this.name = "MarvinError";
|
|
182
|
-
Object.setPrototypeOf(this, _MarvinError.prototype);
|
|
183
|
-
}
|
|
184
|
-
code;
|
|
185
|
-
statusCode;
|
|
186
|
-
};
|
|
187
|
-
var MarvinApiError = class _MarvinApiError extends MarvinError {
|
|
188
|
-
constructor(message, statusCode, endpoint, responseBody) {
|
|
189
|
-
super(message, "API_ERROR", statusCode);
|
|
190
|
-
this.statusCode = statusCode;
|
|
191
|
-
this.endpoint = endpoint;
|
|
192
|
-
this.responseBody = responseBody;
|
|
193
|
-
this.name = "MarvinApiError";
|
|
194
|
-
Object.setPrototypeOf(this, _MarvinApiError.prototype);
|
|
195
|
-
}
|
|
196
|
-
statusCode;
|
|
197
|
-
endpoint;
|
|
198
|
-
responseBody;
|
|
199
|
-
static fromResponse(status, statusText, endpoint, body) {
|
|
200
|
-
const message = `Marvin API error: ${status} ${statusText} at ${endpoint}${body ? `
|
|
201
|
-
${body}` : ""}`;
|
|
202
|
-
return new _MarvinApiError(message, status, endpoint, body);
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
var MarvinAuthError = class _MarvinAuthError extends MarvinError {
|
|
206
|
-
constructor(message, statusCode) {
|
|
207
|
-
super(message, "AUTH_ERROR", statusCode);
|
|
208
|
-
this.name = "MarvinAuthError";
|
|
209
|
-
Object.setPrototypeOf(this, _MarvinAuthError.prototype);
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
var MarvinConfigError = class _MarvinConfigError extends MarvinError {
|
|
213
|
-
constructor(message) {
|
|
214
|
-
super(message, "CONFIG_ERROR");
|
|
215
|
-
this.name = "MarvinConfigError";
|
|
216
|
-
Object.setPrototypeOf(this, _MarvinConfigError.prototype);
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
var MarvinValidationError = class _MarvinValidationError extends MarvinError {
|
|
220
|
-
constructor(message, field) {
|
|
221
|
-
super(message, "VALIDATION_ERROR");
|
|
222
|
-
this.field = field;
|
|
223
|
-
this.name = "MarvinValidationError";
|
|
224
|
-
Object.setPrototypeOf(this, _MarvinValidationError.prototype);
|
|
225
|
-
}
|
|
226
|
-
field;
|
|
227
|
-
};
|
|
228
|
-
var MarvinNotFoundError = class _MarvinNotFoundError extends MarvinError {
|
|
229
|
-
constructor(message, endpoint) {
|
|
230
|
-
super(message, "NOT_FOUND", 404);
|
|
231
|
-
this.endpoint = endpoint;
|
|
232
|
-
this.name = "MarvinNotFoundError";
|
|
233
|
-
Object.setPrototypeOf(this, _MarvinNotFoundError.prototype);
|
|
234
|
-
}
|
|
235
|
-
endpoint;
|
|
236
|
-
};
|
|
237
|
-
var MarvinNetworkError = class _MarvinNetworkError extends MarvinError {
|
|
238
|
-
constructor(message, originalError) {
|
|
239
|
-
super(message, "NETWORK_ERROR");
|
|
240
|
-
this.originalError = originalError;
|
|
241
|
-
this.name = "MarvinNetworkError";
|
|
242
|
-
Object.setPrototypeOf(this, _MarvinNetworkError.prototype);
|
|
243
|
-
}
|
|
244
|
-
originalError;
|
|
245
|
-
};
|
|
246
|
-
var MarvinServerError = class _MarvinServerError extends MarvinError {
|
|
247
|
-
constructor(message, statusCode, endpoint) {
|
|
248
|
-
super(message, "SERVER_ERROR", statusCode);
|
|
249
|
-
this.endpoint = endpoint;
|
|
250
|
-
this.name = "MarvinServerError";
|
|
251
|
-
Object.setPrototypeOf(this, _MarvinServerError.prototype);
|
|
252
|
-
}
|
|
253
|
-
endpoint;
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
// src/core/http/HttpClient.ts
|
|
257
|
-
var HttpClient = class {
|
|
258
|
-
baseUrl;
|
|
259
|
-
auth;
|
|
260
|
-
defaultHeaders;
|
|
261
|
-
credentials;
|
|
262
|
-
timeout;
|
|
263
|
-
debug;
|
|
264
|
-
retryConfig;
|
|
265
|
-
logger;
|
|
266
|
-
constructor(config) {
|
|
267
|
-
this.baseUrl = config.baseUrl;
|
|
268
|
-
this.auth = config.auth;
|
|
269
|
-
this.defaultHeaders = config.defaultHeaders || {};
|
|
270
|
-
this.credentials = config.credentials || "same-origin";
|
|
271
|
-
const MAX_TIMEOUT = 12e4;
|
|
272
|
-
this.timeout = Math.min(config.timeout || 3e4, MAX_TIMEOUT);
|
|
273
|
-
this.debug = config.debug ?? false;
|
|
274
|
-
this.logger = config.logger ?? console;
|
|
275
|
-
this.retryConfig = {
|
|
276
|
-
maxRetries: config.retry?.maxRetries ?? 3,
|
|
277
|
-
initialDelay: config.retry?.initialDelay ?? 1e3,
|
|
278
|
-
maxDelay: config.retry?.maxDelay ?? 1e4,
|
|
279
|
-
retryableStatuses: config.retry?.retryableStatuses ?? [408, 429, 500, 502, 503, 504]
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Validate path parameter to prevent path traversal attacks
|
|
284
|
-
*/
|
|
285
|
-
validatePathParam(param, paramName = "parameter") {
|
|
286
|
-
if (typeof param === "number") {
|
|
287
|
-
return String(param);
|
|
288
|
-
}
|
|
289
|
-
if (!param || typeof param !== "string") {
|
|
290
|
-
throw new MarvinValidationError(`Invalid ${paramName}: must be a non-empty string or number`);
|
|
291
|
-
}
|
|
292
|
-
if (param.includes("..") || param.includes("/") || param.includes("\\")) {
|
|
293
|
-
throw new MarvinValidationError(
|
|
294
|
-
`Invalid ${paramName}: cannot contain path traversal characters (.. / \\)`
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
if (param.includes("\0")) {
|
|
298
|
-
throw new MarvinValidationError(`Invalid ${paramName}: cannot contain null bytes`);
|
|
299
|
-
}
|
|
300
|
-
if (param.length > 255) {
|
|
301
|
-
throw new MarvinValidationError(`Invalid ${paramName}: exceeds maximum length of 255 characters`);
|
|
302
|
-
}
|
|
303
|
-
return param;
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Build full URL from endpoint
|
|
307
|
-
*/
|
|
308
|
-
buildUrl(endpoint) {
|
|
309
|
-
const base = this.baseUrl.replace(/\/$/, "");
|
|
310
|
-
const path = endpoint.replace(/^\//, "");
|
|
311
|
-
return `${base}/${path}`;
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Build headers for request
|
|
315
|
-
*/
|
|
316
|
-
buildHeaders(additionalHeaders, isFormData = false) {
|
|
317
|
-
const headerInit = {};
|
|
318
|
-
if (!isFormData) {
|
|
319
|
-
headerInit["Content-Type"] = "application/json";
|
|
320
|
-
}
|
|
321
|
-
const headers = new Headers({
|
|
322
|
-
...headerInit,
|
|
323
|
-
...this.defaultHeaders,
|
|
324
|
-
...additionalHeaders
|
|
325
|
-
});
|
|
326
|
-
this.auth.injectAuth(headers);
|
|
327
|
-
return headers;
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Build query string from params
|
|
331
|
-
*/
|
|
332
|
-
buildQueryString(params) {
|
|
333
|
-
const filtered = Object.entries(params).filter(([_, value]) => value !== void 0).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
334
|
-
return filtered.length > 0 ? `?${filtered.join("&")}` : "";
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Sanitize data for logging by redacting sensitive fields
|
|
338
|
-
*/
|
|
339
|
-
sanitizeForLogging(data) {
|
|
340
|
-
if (!data || typeof data !== "object") {
|
|
341
|
-
return data;
|
|
342
|
-
}
|
|
343
|
-
const sensitiveFields = [
|
|
344
|
-
"token",
|
|
345
|
-
"access_token",
|
|
346
|
-
"refresh_token",
|
|
347
|
-
"password",
|
|
348
|
-
"secret",
|
|
349
|
-
"apiKey",
|
|
350
|
-
"api_key",
|
|
351
|
-
"bearer",
|
|
352
|
-
"authorization",
|
|
353
|
-
"csrf_token",
|
|
354
|
-
"session",
|
|
355
|
-
"cookie"
|
|
356
|
-
];
|
|
357
|
-
const sanitize = (obj) => {
|
|
358
|
-
const sanitized = {};
|
|
359
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
360
|
-
const keyLower = key.toLowerCase();
|
|
361
|
-
const isSensitive = sensitiveFields.some((field) => keyLower.includes(field));
|
|
362
|
-
if (isSensitive) {
|
|
363
|
-
sanitized[key] = "[REDACTED]";
|
|
364
|
-
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
365
|
-
sanitized[key] = sanitize(value);
|
|
366
|
-
} else if (Array.isArray(value)) {
|
|
367
|
-
sanitized[key] = value.map(
|
|
368
|
-
(item) => item && typeof item === "object" ? sanitize(item) : item
|
|
369
|
-
);
|
|
370
|
-
} else {
|
|
371
|
-
sanitized[key] = value;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return sanitized;
|
|
375
|
-
};
|
|
376
|
-
if (Array.isArray(data)) {
|
|
377
|
-
return data.map(
|
|
378
|
-
(item) => item && typeof item === "object" ? sanitize(item) : item
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
return sanitize(data);
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Calculate delay for retry with exponential backoff
|
|
385
|
-
*/
|
|
386
|
-
calculateRetryDelay(attempt) {
|
|
387
|
-
const delay = this.retryConfig.initialDelay * Math.pow(2, attempt);
|
|
388
|
-
return Math.min(delay, this.retryConfig.maxDelay);
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Sleep for specified milliseconds
|
|
392
|
-
*/
|
|
393
|
-
sleep(ms) {
|
|
394
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Perform HTTP request with automatic retry on transient failures
|
|
398
|
-
*/
|
|
399
|
-
async request(method, endpoint, options, retryAttempt = 0) {
|
|
400
|
-
const url = this.buildUrl(endpoint) + (options?.params ? this.buildQueryString(options.params) : "");
|
|
401
|
-
const isFormData = options?.body instanceof FormData;
|
|
402
|
-
const headers = this.buildHeaders(options?.headers, isFormData);
|
|
403
|
-
const controller = new AbortController();
|
|
404
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
405
|
-
const startTime = Date.now();
|
|
406
|
-
if (this.debug) {
|
|
407
|
-
this.logger.log(`[Marvin] ${method} ${url}`);
|
|
408
|
-
}
|
|
409
|
-
try {
|
|
410
|
-
const response = await fetch(url, {
|
|
411
|
-
method,
|
|
412
|
-
headers,
|
|
413
|
-
body: isFormData ? options.body : options?.body ? JSON.stringify(options.body) : void 0,
|
|
414
|
-
credentials: this.credentials,
|
|
415
|
-
signal: controller.signal
|
|
416
|
-
});
|
|
417
|
-
clearTimeout(timeoutId);
|
|
418
|
-
const duration = Date.now() - startTime;
|
|
419
|
-
if (this.debug) {
|
|
420
|
-
this.logger.log(
|
|
421
|
-
`[Marvin] ${response.status} ${response.statusText} (${duration}ms)`
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
if (response.status === 401 && this.auth.handleUnauthorized) {
|
|
425
|
-
await this.auth.handleUnauthorized();
|
|
426
|
-
}
|
|
427
|
-
if (!response.ok) {
|
|
428
|
-
const errorText = await response.text().catch(() => "");
|
|
429
|
-
if (this.debug) {
|
|
430
|
-
this.logger.error(
|
|
431
|
-
`[Marvin] Error response: ${response.status} ${response.statusText}`,
|
|
432
|
-
errorText ? `Body: ${errorText}` : ""
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
const isRetryable = this.retryConfig.retryableStatuses.includes(response.status);
|
|
436
|
-
const canRetry = retryAttempt < this.retryConfig.maxRetries;
|
|
437
|
-
if (isRetryable && canRetry) {
|
|
438
|
-
const delay = this.calculateRetryDelay(retryAttempt);
|
|
439
|
-
if (this.debug) {
|
|
440
|
-
this.logger.warn(
|
|
441
|
-
`[Marvin] Retry ${retryAttempt + 1}/${this.retryConfig.maxRetries} after ${delay}ms (status: ${response.status})`
|
|
442
|
-
);
|
|
443
|
-
}
|
|
444
|
-
await this.sleep(delay);
|
|
445
|
-
return this.request(method, endpoint, options, retryAttempt + 1);
|
|
446
|
-
}
|
|
447
|
-
if (response.status === 404) {
|
|
448
|
-
throw new MarvinNotFoundError(
|
|
449
|
-
`Resource not found: ${endpoint}`,
|
|
450
|
-
endpoint
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
if (response.status === 401 || response.status === 403) {
|
|
454
|
-
throw new MarvinAuthError(
|
|
455
|
-
`Authentication failed: ${response.statusText}`,
|
|
456
|
-
response.status
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
if (response.status >= 500) {
|
|
460
|
-
throw new MarvinServerError(
|
|
461
|
-
`Server error: ${response.status} ${response.statusText}`,
|
|
462
|
-
response.status,
|
|
463
|
-
endpoint
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
throw MarvinApiError.fromResponse(
|
|
467
|
-
response.status,
|
|
468
|
-
response.statusText,
|
|
469
|
-
endpoint,
|
|
470
|
-
errorText
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
const contentType = response.headers.get("content-type");
|
|
474
|
-
if (!contentType || !contentType.includes("application/json")) {
|
|
475
|
-
return void 0;
|
|
476
|
-
}
|
|
477
|
-
const data = await response.json();
|
|
478
|
-
if (this.debug) {
|
|
479
|
-
const sanitized = this.sanitizeForLogging(data);
|
|
480
|
-
this.logger.log("[Marvin] Response:", JSON.stringify(sanitized, null, 2));
|
|
481
|
-
}
|
|
482
|
-
return data;
|
|
483
|
-
} catch (error) {
|
|
484
|
-
clearTimeout(timeoutId);
|
|
485
|
-
if (error instanceof MarvinApiError || error instanceof MarvinNotFoundError || error instanceof MarvinAuthError || error instanceof MarvinServerError) {
|
|
486
|
-
throw error;
|
|
487
|
-
}
|
|
488
|
-
if (error instanceof Error) {
|
|
489
|
-
const canRetry = retryAttempt < this.retryConfig.maxRetries;
|
|
490
|
-
if (canRetry && error.name !== "AbortError") {
|
|
491
|
-
const delay = this.calculateRetryDelay(retryAttempt);
|
|
492
|
-
if (this.debug) {
|
|
493
|
-
this.logger.warn(
|
|
494
|
-
`[Marvin] Retry ${retryAttempt + 1}/${this.retryConfig.maxRetries} after ${delay}ms (network error)`
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
await this.sleep(delay);
|
|
498
|
-
return this.request(method, endpoint, options, retryAttempt + 1);
|
|
499
|
-
}
|
|
500
|
-
if (error.name === "AbortError") {
|
|
501
|
-
throw new MarvinNetworkError(
|
|
502
|
-
`Request timeout after ${this.timeout}ms`,
|
|
503
|
-
error
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
|
-
throw new MarvinNetworkError(
|
|
507
|
-
`Network error: ${error.message}`,
|
|
508
|
-
error
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
throw error;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
/**
|
|
515
|
-
* GET request
|
|
516
|
-
*/
|
|
517
|
-
async get(endpoint, params) {
|
|
518
|
-
return this.request("GET", endpoint, { params });
|
|
519
|
-
}
|
|
520
|
-
/**
|
|
521
|
-
* POST request
|
|
522
|
-
*/
|
|
523
|
-
async post(endpoint, body) {
|
|
524
|
-
return this.request("POST", endpoint, { body });
|
|
525
|
-
}
|
|
526
|
-
/**
|
|
527
|
-
* PUT request
|
|
528
|
-
*/
|
|
529
|
-
async put(endpoint, body) {
|
|
530
|
-
return this.request("PUT", endpoint, { body });
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* PATCH request
|
|
534
|
-
*/
|
|
535
|
-
async patch(endpoint, body) {
|
|
536
|
-
return this.request("PATCH", endpoint, { body });
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* DELETE request
|
|
540
|
-
*/
|
|
541
|
-
async delete(endpoint) {
|
|
542
|
-
return this.request("DELETE", endpoint);
|
|
543
|
-
}
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
// src/core/auth/AuthStrategy.ts
|
|
547
|
-
var BearerTokenAuth = class {
|
|
548
|
-
constructor(token) {
|
|
549
|
-
this.token = token;
|
|
550
|
-
}
|
|
551
|
-
token;
|
|
552
|
-
injectAuth(headers) {
|
|
553
|
-
headers.set("Authorization", `Bearer ${this.token}`);
|
|
554
|
-
}
|
|
555
|
-
getDescription() {
|
|
556
|
-
return "Bearer token authentication";
|
|
557
|
-
}
|
|
558
|
-
};
|
|
559
|
-
|
|
560
|
-
// src/core/pagination/PaginatedResponse.ts
|
|
561
|
-
function createPaginatedResponse(data, meta) {
|
|
562
|
-
return { data, meta };
|
|
563
|
-
}
|
|
564
|
-
function extractData(response) {
|
|
565
|
-
if (Array.isArray(response)) {
|
|
566
|
-
return response;
|
|
567
|
-
}
|
|
568
|
-
return response.data;
|
|
569
|
-
}
|
|
570
|
-
function isPaginatedResponse(response) {
|
|
571
|
-
return !Array.isArray(response) && "data" in response;
|
|
572
|
-
}
|
|
573
|
-
function pageToOffset(page, limit) {
|
|
574
|
-
return (page - 1) * limit;
|
|
575
|
-
}
|
|
576
|
-
function offsetToPage(offset, limit) {
|
|
577
|
-
return Math.floor(offset / limit) + 1;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// src/core/cache.ts
|
|
581
|
-
var MarvinCache = class {
|
|
582
|
-
constructor(duration = 5 * 60 * 1e3, maxEntries = 1e3) {
|
|
583
|
-
this.duration = duration;
|
|
584
|
-
this.maxEntries = maxEntries;
|
|
585
|
-
}
|
|
586
|
-
duration;
|
|
587
|
-
maxEntries;
|
|
588
|
-
cache = /* @__PURE__ */ new Map();
|
|
589
|
-
/**
|
|
590
|
-
* Get cached data by key
|
|
591
|
-
* Returns undefined if not found or expired
|
|
592
|
-
*/
|
|
593
|
-
get(key) {
|
|
594
|
-
const entry = this.cache.get(key);
|
|
595
|
-
if (!entry) return void 0;
|
|
596
|
-
const age = Date.now() - entry.timestamp;
|
|
597
|
-
if (age > this.duration) {
|
|
598
|
-
this.cache.delete(key);
|
|
599
|
-
return void 0;
|
|
600
|
-
}
|
|
601
|
-
return entry.data;
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* Set cached data with optional tags for invalidation
|
|
605
|
-
*/
|
|
606
|
-
set(key, data, tags) {
|
|
607
|
-
if (this.cache.size >= this.maxEntries) {
|
|
608
|
-
const oldestKey = this.cache.keys().next().value;
|
|
609
|
-
if (oldestKey) {
|
|
610
|
-
this.cache.delete(oldestKey);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
this.cache.set(key, {
|
|
614
|
-
data,
|
|
615
|
-
timestamp: Date.now(),
|
|
616
|
-
tags
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Check if key exists and is not expired
|
|
621
|
-
*/
|
|
622
|
-
has(key) {
|
|
623
|
-
return this.get(key) !== void 0;
|
|
624
|
-
}
|
|
625
|
-
/**
|
|
626
|
-
* Clear all cached entries
|
|
627
|
-
*/
|
|
628
|
-
clear() {
|
|
629
|
-
this.cache.clear();
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* Delete a specific cache entry
|
|
633
|
-
*/
|
|
634
|
-
delete(key) {
|
|
635
|
-
this.cache.delete(key);
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Invalidate all cache entries with a specific tag
|
|
639
|
-
* Useful for invalidating related data after mutations
|
|
640
|
-
*
|
|
641
|
-
* @example
|
|
642
|
-
* ```typescript
|
|
643
|
-
* // Cache entries with tags
|
|
644
|
-
* cache.set('entry:1', data, ['entries', 'entry:1']);
|
|
645
|
-
* cache.set('entries:list', data, ['entries']);
|
|
646
|
-
*
|
|
647
|
-
* // Invalidate all entries
|
|
648
|
-
* cache.invalidateByTag('entries');
|
|
649
|
-
* ```
|
|
650
|
-
*/
|
|
651
|
-
invalidateByTag(tag) {
|
|
652
|
-
let count = 0;
|
|
653
|
-
for (const [key, entry] of this.cache.entries()) {
|
|
654
|
-
if (entry.tags?.includes(tag)) {
|
|
655
|
-
this.cache.delete(key);
|
|
656
|
-
count++;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
return count;
|
|
660
|
-
}
|
|
661
|
-
/**
|
|
662
|
-
* Get cache statistics
|
|
663
|
-
*/
|
|
664
|
-
getStats() {
|
|
665
|
-
let oldest = null;
|
|
666
|
-
let newest = null;
|
|
667
|
-
for (const entry of this.cache.values()) {
|
|
668
|
-
if (oldest === null || entry.timestamp < oldest) {
|
|
669
|
-
oldest = entry.timestamp;
|
|
670
|
-
}
|
|
671
|
-
if (newest === null || entry.timestamp > newest) {
|
|
672
|
-
newest = entry.timestamp;
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
return {
|
|
676
|
-
size: this.cache.size,
|
|
677
|
-
maxEntries: this.maxEntries,
|
|
678
|
-
oldestEntry: oldest,
|
|
679
|
-
newestEntry: newest
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* Remove expired entries (garbage collection)
|
|
684
|
-
* Returns number of entries removed
|
|
685
|
-
*/
|
|
686
|
-
prune() {
|
|
687
|
-
let count = 0;
|
|
688
|
-
const now = Date.now();
|
|
689
|
-
for (const [key, entry] of this.cache.entries()) {
|
|
690
|
-
const age = now - entry.timestamp;
|
|
691
|
-
if (age > this.duration) {
|
|
692
|
-
this.cache.delete(key);
|
|
693
|
-
count++;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
return count;
|
|
697
|
-
}
|
|
698
|
-
};
|
|
699
|
-
|
|
700
|
-
// src/client/http.ts
|
|
701
|
-
var MarvinHttpClient = class extends HttpClient {
|
|
702
|
-
constructor(config) {
|
|
703
|
-
super({
|
|
704
|
-
baseUrl: config.apiUrl,
|
|
705
|
-
auth: new BearerTokenAuth(config.siteClientToken),
|
|
706
|
-
credentials: "same-origin",
|
|
707
|
-
debug: config.debug,
|
|
708
|
-
logger: config.logger
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* Fetch endpoint (backward compatibility)
|
|
713
|
-
*/
|
|
714
|
-
async fetch(endpoint) {
|
|
715
|
-
return this.get(endpoint);
|
|
716
|
-
}
|
|
717
|
-
};
|
|
718
|
-
|
|
719
|
-
// src/entries/entry.ts
|
|
720
|
-
var Entry = class {
|
|
721
|
-
constructor(data, http, workspaceSlug) {
|
|
722
|
-
this.data = data;
|
|
723
|
-
this.http = http;
|
|
724
|
-
this.workspaceSlug = workspaceSlug;
|
|
725
|
-
}
|
|
726
|
-
data;
|
|
727
|
-
http;
|
|
728
|
-
workspaceSlug;
|
|
729
|
-
// Expose all entry data as properties
|
|
730
|
-
get id() {
|
|
731
|
-
return this.data.id;
|
|
732
|
-
}
|
|
733
|
-
get title() {
|
|
734
|
-
return this.data.title;
|
|
735
|
-
}
|
|
736
|
-
get slug() {
|
|
737
|
-
return this.data.slug;
|
|
738
|
-
}
|
|
739
|
-
get summary() {
|
|
740
|
-
return this.data.summary;
|
|
741
|
-
}
|
|
742
|
-
get description() {
|
|
743
|
-
return this.data.description;
|
|
744
|
-
}
|
|
745
|
-
/**
|
|
746
|
-
* @deprecated Use field('body') or dataJson instead. Will be removed in v3.0.0
|
|
747
|
-
*/
|
|
748
|
-
get contentMarkdown() {
|
|
749
|
-
return this.data.contentMarkdown;
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* Schema-driven content data (replaces contentMarkdown in v2.0.0).
|
|
753
|
-
* Access specific fields using the field() helper method.
|
|
754
|
-
* @since 2.0.0
|
|
755
|
-
*/
|
|
756
|
-
get dataJson() {
|
|
757
|
-
return this.data.dataJson;
|
|
758
|
-
}
|
|
759
|
-
get metadata() {
|
|
760
|
-
return this.data.metadata;
|
|
761
|
-
}
|
|
762
|
-
get status() {
|
|
763
|
-
return this.data.status;
|
|
764
|
-
}
|
|
765
|
-
get publishedAt() {
|
|
766
|
-
return this.data.publishedAt;
|
|
767
|
-
}
|
|
768
|
-
get createdAt() {
|
|
769
|
-
return this.data.createdAt;
|
|
770
|
-
}
|
|
771
|
-
get updatedAt() {
|
|
772
|
-
return this.data.updatedAt;
|
|
773
|
-
}
|
|
774
|
-
get entryTypeId() {
|
|
775
|
-
return this.data.entryTypeId;
|
|
776
|
-
}
|
|
777
|
-
get entryType() {
|
|
778
|
-
return this.data.entryType;
|
|
779
|
-
}
|
|
780
|
-
/**
|
|
781
|
-
* Get entry assets
|
|
782
|
-
*/
|
|
783
|
-
get assets() {
|
|
784
|
-
return this.data.assets || [];
|
|
785
|
-
}
|
|
786
|
-
/**
|
|
787
|
-
* Get entry collections
|
|
788
|
-
*/
|
|
789
|
-
get collections() {
|
|
790
|
-
return this.data.collections || [];
|
|
791
|
-
}
|
|
792
|
-
/**
|
|
793
|
-
* Get a specific field value from the entry's schema-driven content (dataJson).
|
|
794
|
-
*
|
|
795
|
-
* @param key - Field key as defined in the entry type's schema
|
|
796
|
-
* @returns The field value, or undefined if not present
|
|
797
|
-
* @since 2.0.0
|
|
798
|
-
*
|
|
799
|
-
* @example
|
|
800
|
-
* ```typescript
|
|
801
|
-
* const entry = await client.entries.get('my-entry');
|
|
802
|
-
* const body = entry.field('body'); // Get markdown body
|
|
803
|
-
* const difficulty = entry.field('difficulty'); // Get difficulty level
|
|
804
|
-
* const heroImage = entry.field('heroImage'); // Get hero image UUID
|
|
805
|
-
* ```
|
|
806
|
-
*/
|
|
807
|
-
field(key) {
|
|
808
|
-
return this.data.dataJson?.[key];
|
|
809
|
-
}
|
|
810
|
-
/**
|
|
811
|
-
* Get all fields from the entry's schema-driven content.
|
|
812
|
-
* @since 2.0.0
|
|
813
|
-
*/
|
|
814
|
-
get fields() {
|
|
815
|
-
return this.data.dataJson || {};
|
|
816
|
-
}
|
|
817
|
-
/**
|
|
818
|
-
* Get related entries (future)
|
|
819
|
-
*/
|
|
820
|
-
async relatedEntries() {
|
|
821
|
-
throw new Error("Related entries not yet implemented");
|
|
822
|
-
}
|
|
823
|
-
/**
|
|
824
|
-
* Get raw entry data
|
|
825
|
-
*/
|
|
826
|
-
toJSON() {
|
|
827
|
-
return this.data;
|
|
828
|
-
}
|
|
829
|
-
};
|
|
830
|
-
|
|
831
|
-
// src/entries/entries.ts
|
|
832
|
-
var EntriesModule = class {
|
|
833
|
-
constructor(http, workspaceSlug) {
|
|
834
|
-
this.http = http;
|
|
835
|
-
this.workspaceSlug = workspaceSlug;
|
|
836
|
-
}
|
|
837
|
-
http;
|
|
838
|
-
workspaceSlug;
|
|
839
|
-
/**
|
|
840
|
-
* Get all published entries
|
|
841
|
-
*/
|
|
842
|
-
async list(options = {}) {
|
|
843
|
-
const queryString = this.http.buildQueryString({
|
|
844
|
-
entry_type: options.entryType,
|
|
845
|
-
collection: options.collection,
|
|
846
|
-
limit: options.limit,
|
|
847
|
-
offset: options.offset
|
|
848
|
-
// Don't send status - backend always returns published
|
|
849
|
-
});
|
|
850
|
-
const endpoint = `/api/publish/${this.workspaceSlug}/entries${queryString}`;
|
|
851
|
-
const response = await this.http.fetch(endpoint);
|
|
852
|
-
return response.data || [];
|
|
853
|
-
}
|
|
854
|
-
/**
|
|
855
|
-
* Get a single entry by slug
|
|
856
|
-
* Returns null if entry is not found (404)
|
|
857
|
-
*/
|
|
858
|
-
async get(slug) {
|
|
859
|
-
try {
|
|
860
|
-
const endpoint = `/api/publish/${this.workspaceSlug}/entries/${slug}`;
|
|
861
|
-
const data = await this.http.fetch(endpoint);
|
|
862
|
-
return new Entry(data, this.http, this.workspaceSlug);
|
|
863
|
-
} catch (error) {
|
|
864
|
-
if (error instanceof MarvinNotFoundError) {
|
|
865
|
-
return null;
|
|
866
|
-
}
|
|
867
|
-
throw error;
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
/**
|
|
871
|
-
* Convenience: Get all pages
|
|
872
|
-
*/
|
|
873
|
-
async pages(options) {
|
|
874
|
-
return this.list({ ...options, entryType: "page" });
|
|
875
|
-
}
|
|
876
|
-
/**
|
|
877
|
-
* Convenience: Get all blog posts
|
|
878
|
-
*/
|
|
879
|
-
async posts(options) {
|
|
880
|
-
return this.list({ ...options, entryType: "blog" });
|
|
881
|
-
}
|
|
882
|
-
/**
|
|
883
|
-
* Convenience: Get all projects
|
|
884
|
-
*/
|
|
885
|
-
async projects(options) {
|
|
886
|
-
return this.list({ ...options, entryType: "project" });
|
|
887
|
-
}
|
|
888
|
-
};
|
|
889
|
-
|
|
890
|
-
// src/collections/collection.ts
|
|
891
|
-
var Collection = class {
|
|
892
|
-
constructor(data, http, workspaceSlug) {
|
|
893
|
-
this.data = data;
|
|
894
|
-
this.http = http;
|
|
895
|
-
this.workspaceSlug = workspaceSlug;
|
|
896
|
-
}
|
|
897
|
-
data;
|
|
898
|
-
http;
|
|
899
|
-
workspaceSlug;
|
|
900
|
-
// Expose all collection data as properties
|
|
901
|
-
get id() {
|
|
902
|
-
return this.data.id;
|
|
903
|
-
}
|
|
904
|
-
get name() {
|
|
905
|
-
return this.data.name;
|
|
906
|
-
}
|
|
907
|
-
get slug() {
|
|
908
|
-
return this.data.slug;
|
|
909
|
-
}
|
|
910
|
-
get description() {
|
|
911
|
-
return this.data.description;
|
|
912
|
-
}
|
|
913
|
-
get icon() {
|
|
914
|
-
return this.data.icon;
|
|
915
|
-
}
|
|
916
|
-
get color() {
|
|
917
|
-
return this.data.color;
|
|
918
|
-
}
|
|
919
|
-
get sortOrder() {
|
|
920
|
-
return this.data.sortOrder;
|
|
921
|
-
}
|
|
922
|
-
get isSmart() {
|
|
923
|
-
return this.data.isSmart;
|
|
924
|
-
}
|
|
925
|
-
get smartRules() {
|
|
926
|
-
return this.data.smartRules;
|
|
927
|
-
}
|
|
928
|
-
get createdAt() {
|
|
929
|
-
return this.data.createdAt;
|
|
930
|
-
}
|
|
931
|
-
get updatedAt() {
|
|
932
|
-
return this.data.updatedAt;
|
|
933
|
-
}
|
|
934
|
-
/**
|
|
935
|
-
* Get all entries in this collection
|
|
936
|
-
*/
|
|
937
|
-
async entries() {
|
|
938
|
-
return this.data.entries || [];
|
|
939
|
-
}
|
|
940
|
-
/**
|
|
941
|
-
* Get assets in this collection (future)
|
|
942
|
-
*/
|
|
943
|
-
async assets() {
|
|
944
|
-
throw new Error("Collection assets not yet implemented");
|
|
945
|
-
}
|
|
946
|
-
/**
|
|
947
|
-
* Get collection metadata (future)
|
|
948
|
-
*/
|
|
949
|
-
async metadata() {
|
|
950
|
-
throw new Error("Collection metadata not yet implemented");
|
|
951
|
-
}
|
|
952
|
-
/**
|
|
953
|
-
* Get raw collection data
|
|
954
|
-
*/
|
|
955
|
-
toJSON() {
|
|
956
|
-
return this.data;
|
|
957
|
-
}
|
|
958
|
-
};
|
|
959
|
-
|
|
960
|
-
// src/collections/collections.ts
|
|
961
|
-
var CollectionsModule = class {
|
|
962
|
-
constructor(http, workspaceSlug) {
|
|
963
|
-
this.http = http;
|
|
964
|
-
this.workspaceSlug = workspaceSlug;
|
|
965
|
-
}
|
|
966
|
-
http;
|
|
967
|
-
workspaceSlug;
|
|
968
|
-
/**
|
|
969
|
-
* Get all collections
|
|
970
|
-
*/
|
|
971
|
-
async list() {
|
|
972
|
-
const endpoint = `/api/publish/${this.workspaceSlug}/collections`;
|
|
973
|
-
const response = await this.http.fetch(endpoint);
|
|
974
|
-
return response.data || [];
|
|
975
|
-
}
|
|
976
|
-
/**
|
|
977
|
-
* Get a single collection by slug
|
|
978
|
-
* Returns null if collection is not found (404)
|
|
979
|
-
*/
|
|
980
|
-
async get(slug) {
|
|
981
|
-
try {
|
|
982
|
-
const endpoint = `/api/publish/${this.workspaceSlug}/collections/${slug}`;
|
|
983
|
-
const data = await this.http.fetch(endpoint);
|
|
984
|
-
return new Collection(data, this.http, this.workspaceSlug);
|
|
985
|
-
} catch (error) {
|
|
986
|
-
if (error instanceof MarvinNotFoundError) {
|
|
987
|
-
return null;
|
|
988
|
-
}
|
|
989
|
-
throw error;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
/**
|
|
993
|
-
* Get entries in a collection
|
|
994
|
-
* Returns empty array if collection is not found
|
|
995
|
-
*/
|
|
996
|
-
async entries(slug) {
|
|
997
|
-
const collection = await this.get(slug);
|
|
998
|
-
if (!collection) {
|
|
999
|
-
return [];
|
|
1000
|
-
}
|
|
1001
|
-
return collection.entries();
|
|
1002
|
-
}
|
|
1003
|
-
/**
|
|
1004
|
-
* Try multiple collection slugs in fallback order
|
|
1005
|
-
* Returns the first collection that exists and has entries
|
|
1006
|
-
*
|
|
1007
|
-
* @param slugs - Collection slugs to try in order
|
|
1008
|
-
* @param options - Options for fallback behavior
|
|
1009
|
-
* @returns Entries from the first matching collection, or empty array
|
|
1010
|
-
*
|
|
1011
|
-
* @example
|
|
1012
|
-
* ```ts
|
|
1013
|
-
* // Try bench-notes, then journal, then blog
|
|
1014
|
-
* const entries = await collections.fallback(['bench-notes', 'journal', 'blog']);
|
|
1015
|
-
* ```
|
|
1016
|
-
*/
|
|
1017
|
-
async fallback(slugs, options = {}) {
|
|
1018
|
-
const { requireEntries = true } = options;
|
|
1019
|
-
for (const slug of slugs) {
|
|
1020
|
-
try {
|
|
1021
|
-
const collection = await this.get(slug);
|
|
1022
|
-
if (!collection) {
|
|
1023
|
-
continue;
|
|
1024
|
-
}
|
|
1025
|
-
const entries = await collection.entries();
|
|
1026
|
-
if (!requireEntries || entries.length > 0) {
|
|
1027
|
-
return entries;
|
|
1028
|
-
}
|
|
1029
|
-
} catch (error) {
|
|
1030
|
-
continue;
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
return [];
|
|
1034
|
-
}
|
|
1035
|
-
};
|
|
1036
|
-
|
|
1037
|
-
// src/assets/assets.ts
|
|
1038
|
-
var AssetsModule = class {
|
|
1039
|
-
constructor(http, workspaceSlug) {
|
|
1040
|
-
this.http = http;
|
|
1041
|
-
this.workspaceSlug = workspaceSlug;
|
|
1042
|
-
}
|
|
1043
|
-
http;
|
|
1044
|
-
workspaceSlug;
|
|
1045
|
-
/**
|
|
1046
|
-
* Get all published assets
|
|
1047
|
-
*/
|
|
1048
|
-
async list(options = {}) {
|
|
1049
|
-
const queryString = this.http.buildQueryString({
|
|
1050
|
-
type: options.type,
|
|
1051
|
-
limit: options.limit,
|
|
1052
|
-
offset: options.offset
|
|
1053
|
-
});
|
|
1054
|
-
const endpoint = `/api/publish/${this.workspaceSlug}/assets${queryString}`;
|
|
1055
|
-
const response = await this.http.fetch(endpoint);
|
|
1056
|
-
return response.data || [];
|
|
1057
|
-
}
|
|
1058
|
-
/**
|
|
1059
|
-
* Get a single published asset by slug or ID
|
|
1060
|
-
*/
|
|
1061
|
-
async get(slugOrId) {
|
|
1062
|
-
const endpoint = `/api/publish/${this.workspaceSlug}/assets/${slugOrId}`;
|
|
1063
|
-
return this.http.fetch(endpoint);
|
|
1064
|
-
}
|
|
1065
|
-
/**
|
|
1066
|
-
* Convenience: Get all images
|
|
1067
|
-
*/
|
|
1068
|
-
async images(options) {
|
|
1069
|
-
return this.list({ ...options, type: "image" });
|
|
1070
|
-
}
|
|
1071
|
-
/**
|
|
1072
|
-
* Convenience: Get all videos
|
|
1073
|
-
*/
|
|
1074
|
-
async videos(options) {
|
|
1075
|
-
return this.list({ ...options, type: "video" });
|
|
1076
|
-
}
|
|
1077
|
-
/**
|
|
1078
|
-
* Convenience: Get all documents
|
|
1079
|
-
*/
|
|
1080
|
-
async documents(options) {
|
|
1081
|
-
return this.list({ ...options, type: "document" });
|
|
1082
|
-
}
|
|
1083
|
-
};
|
|
1084
|
-
|
|
1085
|
-
// src/resources/resource.ts
|
|
1086
|
-
var Resource = class {
|
|
1087
|
-
constructor(data, http, workspaceSlug) {
|
|
1088
|
-
this.data = data;
|
|
1089
|
-
this.http = http;
|
|
1090
|
-
this.workspaceSlug = workspaceSlug;
|
|
1091
|
-
}
|
|
1092
|
-
data;
|
|
1093
|
-
http;
|
|
1094
|
-
workspaceSlug;
|
|
1095
|
-
// Expose all resource data as properties
|
|
1096
|
-
get id() {
|
|
1097
|
-
return this.data.id;
|
|
1098
|
-
}
|
|
1099
|
-
get name() {
|
|
1100
|
-
return this.data.name;
|
|
1101
|
-
}
|
|
1102
|
-
get slug() {
|
|
1103
|
-
return this.data.slug;
|
|
1104
|
-
}
|
|
1105
|
-
get resourceType() {
|
|
1106
|
-
return this.data.resourceType;
|
|
1107
|
-
}
|
|
1108
|
-
get description() {
|
|
1109
|
-
return this.data.description;
|
|
1110
|
-
}
|
|
1111
|
-
get externalId() {
|
|
1112
|
-
return this.data.externalId;
|
|
1113
|
-
}
|
|
1114
|
-
get url() {
|
|
1115
|
-
return this.data.url;
|
|
1116
|
-
}
|
|
1117
|
-
get metadata() {
|
|
1118
|
-
return this.data.metadata;
|
|
1119
|
-
}
|
|
1120
|
-
get createdAt() {
|
|
1121
|
-
return this.data.createdAt;
|
|
1122
|
-
}
|
|
1123
|
-
get updatedAt() {
|
|
1124
|
-
return this.data.updatedAt;
|
|
1125
|
-
}
|
|
1126
|
-
/**
|
|
1127
|
-
* Get all entries that reference this resource
|
|
1128
|
-
*/
|
|
1129
|
-
async entries() {
|
|
1130
|
-
const endpoint = `/api/publish/${this.workspaceSlug}/resources/${this.data.slug}/entries`;
|
|
1131
|
-
return this.http.fetch(endpoint);
|
|
1132
|
-
}
|
|
1133
|
-
/**
|
|
1134
|
-
* Get assets related to this resource (future)
|
|
1135
|
-
*/
|
|
1136
|
-
async assets() {
|
|
1137
|
-
throw new Error("Resource assets not yet implemented");
|
|
1138
|
-
}
|
|
1139
|
-
/**
|
|
1140
|
-
* Get raw resource data
|
|
1141
|
-
*/
|
|
1142
|
-
toJSON() {
|
|
1143
|
-
return this.data;
|
|
1144
|
-
}
|
|
1145
|
-
};
|
|
1146
|
-
|
|
1147
|
-
// src/resources/resources.ts
|
|
1148
|
-
var ResourcesModule = class {
|
|
1149
|
-
constructor(http, workspaceSlug) {
|
|
1150
|
-
this.http = http;
|
|
1151
|
-
this.workspaceSlug = workspaceSlug;
|
|
1152
|
-
}
|
|
1153
|
-
http;
|
|
1154
|
-
workspaceSlug;
|
|
1155
|
-
/**
|
|
1156
|
-
* Get all published resources
|
|
1157
|
-
*/
|
|
1158
|
-
async list(options = {}) {
|
|
1159
|
-
const queryString = this.http.buildQueryString({
|
|
1160
|
-
resource_type: options.resourceType,
|
|
1161
|
-
limit: options.limit,
|
|
1162
|
-
offset: options.offset
|
|
1163
|
-
});
|
|
1164
|
-
const endpoint = `/api/publish/${this.workspaceSlug}/resources${queryString}`;
|
|
1165
|
-
const response = await this.http.fetch(endpoint);
|
|
1166
|
-
return response.data || [];
|
|
1167
|
-
}
|
|
1168
|
-
/**
|
|
1169
|
-
* Get a single resource by slug
|
|
1170
|
-
*/
|
|
1171
|
-
async get(slug) {
|
|
1172
|
-
const endpoint = `/api/publish/${this.workspaceSlug}/resources/${slug}`;
|
|
1173
|
-
const data = await this.http.fetch(endpoint);
|
|
1174
|
-
return new Resource(data, this.http, this.workspaceSlug);
|
|
1175
|
-
}
|
|
1176
|
-
/**
|
|
1177
|
-
* Get entries that reference this resource
|
|
1178
|
-
*/
|
|
1179
|
-
async entries(slug) {
|
|
1180
|
-
const resource = await this.get(slug);
|
|
1181
|
-
return resource.entries();
|
|
1182
|
-
}
|
|
1183
|
-
};
|
|
1184
|
-
|
|
1185
|
-
// src/workspaces/workspace.ts
|
|
1186
|
-
var Workspace = class {
|
|
1187
|
-
constructor(http, slug) {
|
|
1188
|
-
this.http = http;
|
|
1189
|
-
this._slug = slug;
|
|
1190
|
-
this.entries = new EntriesModule(http, slug);
|
|
1191
|
-
this.collections = new CollectionsModule(http, slug);
|
|
1192
|
-
this.assets = new AssetsModule(http, slug);
|
|
1193
|
-
this.resources = new ResourcesModule(http, slug);
|
|
1194
|
-
}
|
|
1195
|
-
http;
|
|
1196
|
-
_site = null;
|
|
1197
|
-
_slug;
|
|
1198
|
-
entries;
|
|
1199
|
-
collections;
|
|
1200
|
-
assets;
|
|
1201
|
-
resources;
|
|
1202
|
-
/**
|
|
1203
|
-
* Get workspace slug
|
|
1204
|
-
*/
|
|
1205
|
-
get slug() {
|
|
1206
|
-
return this._slug;
|
|
1207
|
-
}
|
|
1208
|
-
/**
|
|
1209
|
-
* Get site configuration (cached after first load)
|
|
1210
|
-
*/
|
|
1211
|
-
get site() {
|
|
1212
|
-
return this._site;
|
|
1213
|
-
}
|
|
1214
|
-
/**
|
|
1215
|
-
* Load site configuration
|
|
1216
|
-
*/
|
|
1217
|
-
async loadSite() {
|
|
1218
|
-
const endpoint = `/api/publish/${this._slug}/site`;
|
|
1219
|
-
this._site = await this.http.fetch(endpoint);
|
|
1220
|
-
return this._site;
|
|
1221
|
-
}
|
|
1222
|
-
/**
|
|
1223
|
-
* Get workspace info (name and slug only)
|
|
1224
|
-
*/
|
|
1225
|
-
async getInfo() {
|
|
1226
|
-
const endpoint = `/api/publish/${this._slug}`;
|
|
1227
|
-
return this.http.fetch(endpoint);
|
|
1228
|
-
}
|
|
1229
|
-
/**
|
|
1230
|
-
* Get workspace settings (future)
|
|
1231
|
-
*/
|
|
1232
|
-
async settings() {
|
|
1233
|
-
throw new Error("Workspace settings not yet implemented");
|
|
1234
|
-
}
|
|
1235
|
-
};
|
|
1236
|
-
|
|
1237
|
-
// src/client/marvin.ts
|
|
1238
|
-
var MarvinClient = class {
|
|
1239
|
-
constructor(config) {
|
|
1240
|
-
this.config = config;
|
|
1241
|
-
validateConfig(config);
|
|
1242
|
-
this.http = new MarvinHttpClient(config);
|
|
1243
|
-
this.cache = new MarvinCache(config.cacheDuration);
|
|
1244
|
-
this.workspace = new Workspace(this.http, config.workspaceSlug);
|
|
1245
|
-
if (config.autoInitialize) {
|
|
1246
|
-
this.initialize();
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
config;
|
|
1250
|
-
http;
|
|
1251
|
-
cache;
|
|
1252
|
-
workspace;
|
|
1253
|
-
initialized = false;
|
|
1254
|
-
/**
|
|
1255
|
-
* Initialize the SDK
|
|
1256
|
-
* Preloads site configuration and metadata
|
|
1257
|
-
*/
|
|
1258
|
-
async initialize() {
|
|
1259
|
-
if (this.initialized) return;
|
|
1260
|
-
try {
|
|
1261
|
-
await this.workspace.loadSite();
|
|
1262
|
-
this.initialized = true;
|
|
1263
|
-
} catch (error) {
|
|
1264
|
-
console.warn("Marvin SDK initialization warning:", error);
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
// ======================
|
|
1268
|
-
// Workspace-First API
|
|
1269
|
-
// ======================
|
|
1270
|
-
/**
|
|
1271
|
-
* Get the workspace object
|
|
1272
|
-
*/
|
|
1273
|
-
async getWorkspace() {
|
|
1274
|
-
if (!this.initialized) {
|
|
1275
|
-
await this.initialize();
|
|
1276
|
-
}
|
|
1277
|
-
return this.workspace;
|
|
1278
|
-
}
|
|
1279
|
-
/**
|
|
1280
|
-
* Get site configuration (cached)
|
|
1281
|
-
*/
|
|
1282
|
-
get site() {
|
|
1283
|
-
return this.workspace.site;
|
|
1284
|
-
}
|
|
1285
|
-
/**
|
|
1286
|
-
* Get workspace info (name and slug)
|
|
1287
|
-
*/
|
|
1288
|
-
async getWorkspaceInfo() {
|
|
1289
|
-
return this.workspace.getInfo();
|
|
1290
|
-
}
|
|
1291
|
-
/**
|
|
1292
|
-
* Get entries module
|
|
1293
|
-
*/
|
|
1294
|
-
get entries() {
|
|
1295
|
-
return this.workspace.entries;
|
|
1296
|
-
}
|
|
1297
|
-
/**
|
|
1298
|
-
* Get collections module
|
|
1299
|
-
*/
|
|
1300
|
-
get collections() {
|
|
1301
|
-
return this.workspace.collections;
|
|
1302
|
-
}
|
|
1303
|
-
/**
|
|
1304
|
-
* Get assets module
|
|
1305
|
-
*/
|
|
1306
|
-
get assets() {
|
|
1307
|
-
return this.workspace.assets;
|
|
1308
|
-
}
|
|
1309
|
-
/**
|
|
1310
|
-
* Get resources module
|
|
1311
|
-
*/
|
|
1312
|
-
get resources() {
|
|
1313
|
-
return this.workspace.resources;
|
|
1314
|
-
}
|
|
1315
|
-
// ======================
|
|
1316
|
-
// Convenience Methods
|
|
1317
|
-
// ======================
|
|
1318
|
-
/**
|
|
1319
|
-
* Get a single entry by slug
|
|
1320
|
-
*/
|
|
1321
|
-
async entry(slug) {
|
|
1322
|
-
return this.workspace.entries.get(slug);
|
|
1323
|
-
}
|
|
1324
|
-
/**
|
|
1325
|
-
* Get a single collection by slug
|
|
1326
|
-
*/
|
|
1327
|
-
async collection(slug) {
|
|
1328
|
-
return this.workspace.collections.get(slug);
|
|
1329
|
-
}
|
|
1330
|
-
/**
|
|
1331
|
-
* Get all pages
|
|
1332
|
-
*/
|
|
1333
|
-
async pages(options) {
|
|
1334
|
-
return this.workspace.entries.pages(options);
|
|
1335
|
-
}
|
|
1336
|
-
/**
|
|
1337
|
-
* Get all blog posts
|
|
1338
|
-
*/
|
|
1339
|
-
async posts(options) {
|
|
1340
|
-
return this.workspace.entries.posts(options);
|
|
1341
|
-
}
|
|
1342
|
-
/**
|
|
1343
|
-
* Get all projects
|
|
1344
|
-
*/
|
|
1345
|
-
async projects(options) {
|
|
1346
|
-
return this.workspace.entries.projects(options);
|
|
1347
|
-
}
|
|
1348
|
-
/**
|
|
1349
|
-
* Get a single resource by slug
|
|
1350
|
-
*/
|
|
1351
|
-
async resource(slug) {
|
|
1352
|
-
return this.workspace.resources.get(slug);
|
|
1353
|
-
}
|
|
1354
|
-
/**
|
|
1355
|
-
* Try multiple collection slugs in fallback order
|
|
1356
|
-
* Returns entries from the first collection that exists and has content
|
|
1357
|
-
*
|
|
1358
|
-
* @param slugs - Collection slugs to try in order
|
|
1359
|
-
* @param options - Options for fallback behavior
|
|
1360
|
-
* @returns Entries from the first matching collection, or empty array
|
|
1361
|
-
*
|
|
1362
|
-
* @example
|
|
1363
|
-
* ```ts
|
|
1364
|
-
* // Try bench-notes, then journal, then blog
|
|
1365
|
-
* const entries = await client.collectionFallback(['bench-notes', 'journal', 'blog']);
|
|
1366
|
-
* ```
|
|
1367
|
-
*/
|
|
1368
|
-
async collectionFallback(slugs, options) {
|
|
1369
|
-
return this.workspace.collections.fallback(slugs, options);
|
|
1370
|
-
}
|
|
1371
|
-
// ======================
|
|
1372
|
-
// Backwards-Compatible Publishing API
|
|
1373
|
-
// ======================
|
|
1374
|
-
/**
|
|
1375
|
-
* @deprecated Use workspace.site or marvin.site instead
|
|
1376
|
-
* Get site configuration
|
|
1377
|
-
*/
|
|
1378
|
-
async getSite() {
|
|
1379
|
-
return this.workspace.loadSite();
|
|
1380
|
-
}
|
|
1381
|
-
/**
|
|
1382
|
-
* @deprecated Use workspace.entries.list() instead
|
|
1383
|
-
* Get all published entries
|
|
1384
|
-
*/
|
|
1385
|
-
async getEntries(options) {
|
|
1386
|
-
return this.workspace.entries.list(options);
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* @deprecated Use workspace.entries.get() instead
|
|
1390
|
-
* Get a single published entry by slug
|
|
1391
|
-
*/
|
|
1392
|
-
async getEntry(slug) {
|
|
1393
|
-
const entry = await this.workspace.entries.get(slug);
|
|
1394
|
-
if (!entry) {
|
|
1395
|
-
return null;
|
|
1396
|
-
}
|
|
1397
|
-
return entry.toJSON();
|
|
1398
|
-
}
|
|
1399
|
-
/**
|
|
1400
|
-
* @deprecated Use workspace.collections.list() instead
|
|
1401
|
-
* Get all collections
|
|
1402
|
-
*/
|
|
1403
|
-
async getCollections() {
|
|
1404
|
-
return this.workspace.collections.list();
|
|
1405
|
-
}
|
|
1406
|
-
/**
|
|
1407
|
-
* @deprecated Use workspace.collections.get() instead
|
|
1408
|
-
* Get a single collection by slug
|
|
1409
|
-
*/
|
|
1410
|
-
async getCollection(slug) {
|
|
1411
|
-
const collection = await this.workspace.collections.get(slug);
|
|
1412
|
-
if (!collection) {
|
|
1413
|
-
return null;
|
|
1414
|
-
}
|
|
1415
|
-
return collection.toJSON();
|
|
1416
|
-
}
|
|
1417
|
-
/**
|
|
1418
|
-
* @deprecated Use workspace.collections.entries() instead
|
|
1419
|
-
* Get all entries in a collection
|
|
1420
|
-
*/
|
|
1421
|
-
async getCollectionEntries(slug) {
|
|
1422
|
-
return this.workspace.collections.entries(slug);
|
|
1423
|
-
}
|
|
1424
|
-
/**
|
|
1425
|
-
* @deprecated Use workspace.assets.list() instead
|
|
1426
|
-
* Get all published assets
|
|
1427
|
-
*/
|
|
1428
|
-
async getAssets(options) {
|
|
1429
|
-
return this.workspace.assets.list(options);
|
|
1430
|
-
}
|
|
1431
|
-
/**
|
|
1432
|
-
* @deprecated Use workspace.resources.list() instead
|
|
1433
|
-
* Get all published resources
|
|
1434
|
-
*/
|
|
1435
|
-
async getResources(options) {
|
|
1436
|
-
return this.workspace.resources.list(options);
|
|
1437
|
-
}
|
|
1438
|
-
/**
|
|
1439
|
-
* @deprecated Use workspace.resources.get() instead
|
|
1440
|
-
* Get a single resource by slug
|
|
1441
|
-
*/
|
|
1442
|
-
async getResource(slug) {
|
|
1443
|
-
const resource = await this.workspace.resources.get(slug);
|
|
1444
|
-
return resource.toJSON();
|
|
1445
|
-
}
|
|
1446
|
-
/**
|
|
1447
|
-
* @deprecated Use workspace.resources.entries() instead
|
|
1448
|
-
* Get entries that reference a resource
|
|
1449
|
-
*/
|
|
1450
|
-
async getResourceEntries(slug) {
|
|
1451
|
-
return this.workspace.resources.entries(slug);
|
|
1452
|
-
}
|
|
1453
|
-
};
|
|
1454
|
-
|
|
1455
|
-
// src/publish.ts
|
|
1456
|
-
init_config();
|
|
1457
|
-
function createMarvinClient(config) {
|
|
1458
|
-
const { createConfigFromEnv: createConfigFromEnv2 } = (init_config(), __toCommonJS(config_exports));
|
|
1459
|
-
const finalConfig = createConfigFromEnv2(config);
|
|
1460
|
-
return new MarvinClient(finalConfig);
|
|
1461
|
-
}
|
|
1462
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1463
|
-
0 && (module.exports = {
|
|
1464
|
-
BearerTokenAuth,
|
|
1465
|
-
Collection,
|
|
1466
|
-
Entry,
|
|
1467
|
-
HttpClient,
|
|
1468
|
-
MarvinApiError,
|
|
1469
|
-
MarvinAuthError,
|
|
1470
|
-
MarvinCache,
|
|
1471
|
-
MarvinClient,
|
|
1472
|
-
MarvinConfigError,
|
|
1473
|
-
MarvinError,
|
|
1474
|
-
MarvinNetworkError,
|
|
1475
|
-
MarvinNotFoundError,
|
|
1476
|
-
MarvinServerError,
|
|
1477
|
-
MarvinValidationError,
|
|
1478
|
-
Resource,
|
|
1479
|
-
Workspace,
|
|
1480
|
-
createConfigFromEnv,
|
|
1481
|
-
createMarvinClient,
|
|
1482
|
-
createPaginatedResponse,
|
|
1483
|
-
extractData,
|
|
1484
|
-
isPaginatedResponse,
|
|
1485
|
-
offsetToPage,
|
|
1486
|
-
pageToOffset
|
|
1487
|
-
});
|