@inneropen/marvin-sdk 1.0.0
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/QUICKSTART.md +408 -0
- package/README.md +721 -0
- package/dist/index.cjs +859 -0
- package/dist/index.d.cts +430 -0
- package/dist/index.d.ts +430 -0
- package/dist/index.js +846 -0
- package/dist/types/index.cjs +18 -0
- package/dist/types/index.d.cts +120 -0
- package/dist/types/index.d.ts +120 -0
- package/dist/types/index.js +0 -0
- package/examples.md +961 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
# Marvin TypeScript SDK
|
|
2
|
+
|
|
3
|
+
The official TypeScript SDK for Marvin CMS. A modern, workspace-first SDK for building applications that integrate with Marvin.
|
|
4
|
+
|
|
5
|
+
## Why This SDK?
|
|
6
|
+
|
|
7
|
+
Marvin is becoming a platform, not simply a CMS. This SDK is the primary way to integrate with Marvin across:
|
|
8
|
+
|
|
9
|
+
- **Static Site Generators** (Astro, Next.js, Nuxt)
|
|
10
|
+
- **Server Applications** (Express, Fastify, Hono)
|
|
11
|
+
- **CLI Tools** & **Build Scripts**
|
|
12
|
+
- **Automation** (n8n, GitHub Actions)
|
|
13
|
+
- **AI Agents** & **Custom Integrations**
|
|
14
|
+
|
|
15
|
+
The SDK provides:
|
|
16
|
+
|
|
17
|
+
- 🎯 **Workspace-First Architecture** - Work with Marvin concepts (Workspace, Entry, Collection)
|
|
18
|
+
- 🚀 **Performance Optimized** - Built-in caching for build-time usage
|
|
19
|
+
- 📘 **Fully Typed** - Complete TypeScript support
|
|
20
|
+
- 🔄 **Backwards Compatible** - Works with existing publishing APIs
|
|
21
|
+
- 🎨 **Object-Oriented** - Rich objects instead of raw JSON
|
|
22
|
+
- 🔒 **Secure** - Site client tokens, never user tokens
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Copy into your project
|
|
28
|
+
cp -r /path/to/marvin/packages/marvin-sdk src/lib/marvin
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or symlink for development:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
ln -s /path/to/marvin/packages/marvin-sdk src/lib/marvin
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### 1. Configure Environment
|
|
40
|
+
|
|
41
|
+
```env
|
|
42
|
+
MARVIN_API_URL=https://marvin.example.com
|
|
43
|
+
MARVIN_SITE_CLIENT_TOKEN=your-token-here
|
|
44
|
+
MARVIN_WORKSPACE_SLUG=your-workspace
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Create Client
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { createMarvinClient } from './marvin-sdk';
|
|
51
|
+
|
|
52
|
+
const marvin = createMarvinClient();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 3. Fetch Content
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// Get workspace
|
|
59
|
+
const workspace = await marvin.getWorkspace();
|
|
60
|
+
console.log(workspace.site?.title);
|
|
61
|
+
|
|
62
|
+
// Get entries
|
|
63
|
+
const entries = await marvin.entries.list();
|
|
64
|
+
|
|
65
|
+
// Get a specific entry
|
|
66
|
+
const entry = await marvin.entry('about-us');
|
|
67
|
+
console.log(entry.title, entry.contentMarkdown);
|
|
68
|
+
|
|
69
|
+
// Get collection entries
|
|
70
|
+
const projects = await marvin.collection('projects');
|
|
71
|
+
const projectEntries = await projects.entries();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Architecture
|
|
75
|
+
|
|
76
|
+
### Workspace-First Design
|
|
77
|
+
|
|
78
|
+
Everything starts with the **Workspace**:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const workspace = await marvin.getWorkspace();
|
|
82
|
+
|
|
83
|
+
// Access workspace modules
|
|
84
|
+
workspace.entries // Entries module
|
|
85
|
+
workspace.collections // Collections module
|
|
86
|
+
workspace.assets // Assets module
|
|
87
|
+
workspace.site // Site configuration (cached)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Modular Structure
|
|
91
|
+
|
|
92
|
+
The SDK is organized into focused modules:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
@marvin/sdk
|
|
96
|
+
├── client/ → Core client & HTTP
|
|
97
|
+
├── workspaces/ → Workspace object
|
|
98
|
+
├── entries/ → Entries module
|
|
99
|
+
├── collections/ → Collections module
|
|
100
|
+
├── assets/ → Assets module
|
|
101
|
+
├── auth/ → Authentication (future)
|
|
102
|
+
├── types/ → TypeScript types
|
|
103
|
+
└── utils/ → Cache & utilities
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## API Styles
|
|
107
|
+
|
|
108
|
+
The SDK supports **three API styles**:
|
|
109
|
+
|
|
110
|
+
### 1. Workspace API (Recommended)
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const workspace = await marvin.getWorkspace();
|
|
114
|
+
const entries = await workspace.entries.list();
|
|
115
|
+
const collection = await workspace.collections.get('featured');
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 2. Convenience API
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const entry = await marvin.entry('about');
|
|
122
|
+
const projects = await marvin.projects();
|
|
123
|
+
const pages = await marvin.pages();
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 3. Backwards-Compatible API
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// Still works! (Deprecated but supported)
|
|
130
|
+
const site = await marvin.getSite();
|
|
131
|
+
const entries = await marvin.getEntries();
|
|
132
|
+
const entry = await marvin.getEntry('about');
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Core Concepts
|
|
136
|
+
|
|
137
|
+
### Workspace
|
|
138
|
+
|
|
139
|
+
The workspace is the root object representing your Marvin workspace.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const workspace = await marvin.getWorkspace();
|
|
143
|
+
|
|
144
|
+
// Cached site configuration
|
|
145
|
+
workspace.site?.title
|
|
146
|
+
workspace.site?.description
|
|
147
|
+
|
|
148
|
+
// Access modules
|
|
149
|
+
workspace.entries.list()
|
|
150
|
+
workspace.collections.get('slug')
|
|
151
|
+
workspace.assets.images()
|
|
152
|
+
workspace.resources.list()
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Content Primitives
|
|
156
|
+
|
|
157
|
+
Marvin has four first-class content primitives:
|
|
158
|
+
|
|
159
|
+
- **Entries** - Content you create (pages, blog posts, projects)
|
|
160
|
+
- **Collections** - Organize and group entries
|
|
161
|
+
- **Assets** - Binary files and media (images, videos, documents)
|
|
162
|
+
- **Resources** - Reusable structured objects (fabrics, tools, suppliers, APIs)
|
|
163
|
+
|
|
164
|
+
### Entries
|
|
165
|
+
|
|
166
|
+
Rich entry objects with methods and properties:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const entry = await marvin.entry('about-us');
|
|
170
|
+
|
|
171
|
+
// Properties
|
|
172
|
+
entry.title
|
|
173
|
+
entry.slug
|
|
174
|
+
entry.contentMarkdown
|
|
175
|
+
entry.metadata
|
|
176
|
+
entry.publishedAt
|
|
177
|
+
|
|
178
|
+
// Relationships
|
|
179
|
+
entry.assets // MarvinAsset[]
|
|
180
|
+
entry.collections // MarvinCollection[]
|
|
181
|
+
entry.resources // MarvinResource[]
|
|
182
|
+
|
|
183
|
+
// Methods (future)
|
|
184
|
+
await entry.relatedEntries()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Collections
|
|
188
|
+
|
|
189
|
+
Collections as first-class objects:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
const collection = await marvin.collection('projects');
|
|
193
|
+
|
|
194
|
+
// Properties
|
|
195
|
+
collection.name
|
|
196
|
+
collection.slug
|
|
197
|
+
collection.isSmart
|
|
198
|
+
collection.smartRules
|
|
199
|
+
|
|
200
|
+
// Methods
|
|
201
|
+
const entries = await collection.entries();
|
|
202
|
+
|
|
203
|
+
// Future
|
|
204
|
+
await collection.assets()
|
|
205
|
+
await collection.metadata()
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Resources
|
|
209
|
+
|
|
210
|
+
Resources are reusable structured objects referenced by entries:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
const resource = await marvin.resource('kuroki-s022');
|
|
214
|
+
|
|
215
|
+
// Properties
|
|
216
|
+
resource.name
|
|
217
|
+
resource.slug
|
|
218
|
+
resource.resourceType // 'fabric', 'tool', 'supplier', etc.
|
|
219
|
+
resource.description
|
|
220
|
+
resource.externalId
|
|
221
|
+
resource.url
|
|
222
|
+
resource.metadata
|
|
223
|
+
|
|
224
|
+
// Methods
|
|
225
|
+
const entries = await resource.entries(); // Entries that reference this resource
|
|
226
|
+
|
|
227
|
+
// Future
|
|
228
|
+
await resource.assets()
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Examples of Resources:**
|
|
232
|
+
- Fabrics (denim, canvas, etc.)
|
|
233
|
+
- Tools (sewing machine, cutting tool)
|
|
234
|
+
- Suppliers (fabric mills, button manufacturers)
|
|
235
|
+
- Git repositories
|
|
236
|
+
- APIs and services
|
|
237
|
+
- Books and documents
|
|
238
|
+
|
|
239
|
+
**Note:** Resources are not binary files. Use Assets for media files.
|
|
240
|
+
|
|
241
|
+
## Usage Examples
|
|
242
|
+
|
|
243
|
+
### Astro Static Site
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
// src/pages/[slug].astro
|
|
247
|
+
---
|
|
248
|
+
import { marked } from 'marked';
|
|
249
|
+
import { createMarvinClient } from '@/lib/marvin';
|
|
250
|
+
|
|
251
|
+
const marvin = createMarvinClient();
|
|
252
|
+
|
|
253
|
+
export async function getStaticPaths() {
|
|
254
|
+
const pages = await marvin.pages();
|
|
255
|
+
|
|
256
|
+
return pages.map((page) => ({
|
|
257
|
+
params: { slug: page.slug },
|
|
258
|
+
props: { page },
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const { page } = Astro.props;
|
|
263
|
+
const contentHtml = marked.parse(page.contentMarkdown ?? '');
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
<article>
|
|
267
|
+
<h1>{page.title}</h1>
|
|
268
|
+
<div set:html={contentHtml} />
|
|
269
|
+
</article>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Next.js App
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
// app/blog/page.tsx
|
|
276
|
+
import { createMarvinClient } from '@/lib/marvin';
|
|
277
|
+
|
|
278
|
+
const marvin = createMarvinClient();
|
|
279
|
+
|
|
280
|
+
export default async function BlogPage() {
|
|
281
|
+
const posts = await marvin.posts();
|
|
282
|
+
|
|
283
|
+
return (
|
|
284
|
+
<div>
|
|
285
|
+
{posts.map((post) => (
|
|
286
|
+
<article key={post.id}>
|
|
287
|
+
<h2>{post.title}</h2>
|
|
288
|
+
<p>{post.summary}</p>
|
|
289
|
+
</article>
|
|
290
|
+
))}
|
|
291
|
+
</div>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Express Server
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
import express from 'express';
|
|
300
|
+
import { createMarvinClient } from './marvin-sdk';
|
|
301
|
+
|
|
302
|
+
const app = express();
|
|
303
|
+
const marvin = createMarvinClient();
|
|
304
|
+
|
|
305
|
+
app.get('/api/posts', async (req, res) => {
|
|
306
|
+
const posts = await marvin.posts();
|
|
307
|
+
res.json(posts);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
app.get('/api/posts/:slug', async (req, res) => {
|
|
311
|
+
const entry = await marvin.entry(req.params.slug);
|
|
312
|
+
res.json(entry.toJSON());
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### CLI Tool
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
#!/usr/bin/env node
|
|
320
|
+
import { createMarvinClient } from '@marvin/sdk';
|
|
321
|
+
|
|
322
|
+
const marvin = createMarvinClient();
|
|
323
|
+
|
|
324
|
+
async function main() {
|
|
325
|
+
await marvin.initialize();
|
|
326
|
+
|
|
327
|
+
console.log('Site:', marvin.site?.title);
|
|
328
|
+
|
|
329
|
+
const entries = await marvin.entries.list();
|
|
330
|
+
console.log(`Found ${entries.length} entries`);
|
|
331
|
+
|
|
332
|
+
for (const entry of entries) {
|
|
333
|
+
console.log(`- ${entry.title} (${entry.status})`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
main().catch(console.error);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Initialization & Caching
|
|
341
|
+
|
|
342
|
+
### Auto-Initialize
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
const marvin = createMarvinClient({
|
|
346
|
+
autoInitialize: true,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Site is preloaded
|
|
350
|
+
console.log(marvin.site?.title);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Manual Initialize
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
const marvin = createMarvinClient();
|
|
357
|
+
|
|
358
|
+
await marvin.initialize();
|
|
359
|
+
|
|
360
|
+
// Now site is cached
|
|
361
|
+
console.log(marvin.site?.title);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Cache Duration
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
const marvin = createMarvinClient({
|
|
368
|
+
cacheDuration: 10 * 60 * 1000, // 10 minutes
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Convenience Methods
|
|
373
|
+
|
|
374
|
+
Quick access to common content types:
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
// Pages
|
|
378
|
+
const pages = await marvin.pages();
|
|
379
|
+
const about = await marvin.entry('about');
|
|
380
|
+
|
|
381
|
+
// Blog posts
|
|
382
|
+
const posts = await marvin.posts({ limit: 10 });
|
|
383
|
+
|
|
384
|
+
// Projects
|
|
385
|
+
const projects = await marvin.projects();
|
|
386
|
+
|
|
387
|
+
// Collections
|
|
388
|
+
const featured = await marvin.collection('featured');
|
|
389
|
+
const entries = await featured.entries();
|
|
390
|
+
|
|
391
|
+
// Assets
|
|
392
|
+
const images = await marvin.assets.images();
|
|
393
|
+
const videos = await marvin.assets.videos();
|
|
394
|
+
|
|
395
|
+
// Resources
|
|
396
|
+
const resources = await marvin.resources.list();
|
|
397
|
+
const fabric = await marvin.resource('kuroki-s022');
|
|
398
|
+
const entries = await fabric.entries();
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## API Reference
|
|
402
|
+
|
|
403
|
+
### Client Methods
|
|
404
|
+
|
|
405
|
+
#### `createMarvinClient(config?)`
|
|
406
|
+
|
|
407
|
+
Create a new Marvin client instance.
|
|
408
|
+
|
|
409
|
+
**Config Options:**
|
|
410
|
+
- `apiUrl?: string` - API URL (default: `process.env.MARVIN_API_URL`)
|
|
411
|
+
- `siteClientToken?: string` - Site client token (default: `process.env.MARVIN_SITE_CLIENT_TOKEN`)
|
|
412
|
+
- `workspaceSlug?: string` - Workspace slug (default: `process.env.MARVIN_WORKSPACE_SLUG`)
|
|
413
|
+
- `autoInitialize?: boolean` - Auto-initialize on creation (default: `false`)
|
|
414
|
+
- `cacheDuration?: number` - Cache duration in ms (default: `300000` / 5 min)
|
|
415
|
+
|
|
416
|
+
#### `marvin.initialize()`
|
|
417
|
+
|
|
418
|
+
Initialize the SDK and preload workspace data.
|
|
419
|
+
|
|
420
|
+
#### `marvin.getWorkspace()`
|
|
421
|
+
|
|
422
|
+
Get the workspace object.
|
|
423
|
+
|
|
424
|
+
#### `marvin.getWorkspaceInfo()`
|
|
425
|
+
|
|
426
|
+
Get basic workspace information (name and slug only).
|
|
427
|
+
|
|
428
|
+
**Returns:**
|
|
429
|
+
```ts
|
|
430
|
+
{
|
|
431
|
+
slug: string;
|
|
432
|
+
name: string;
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
#### `marvin.entry(slug)`
|
|
437
|
+
|
|
438
|
+
Get a single entry by slug (returns `Entry` object).
|
|
439
|
+
|
|
440
|
+
#### `marvin.collection(slug)`
|
|
441
|
+
|
|
442
|
+
Get a single collection by slug (returns `Collection` object).
|
|
443
|
+
|
|
444
|
+
#### `marvin.pages(options?)`
|
|
445
|
+
|
|
446
|
+
Get all pages.
|
|
447
|
+
|
|
448
|
+
#### `marvin.posts(options?)`
|
|
449
|
+
|
|
450
|
+
Get all blog posts.
|
|
451
|
+
|
|
452
|
+
#### `marvin.projects(options?)`
|
|
453
|
+
|
|
454
|
+
Get all projects.
|
|
455
|
+
|
|
456
|
+
### Workspace
|
|
457
|
+
|
|
458
|
+
#### `workspace.slug`
|
|
459
|
+
|
|
460
|
+
The workspace slug (string).
|
|
461
|
+
|
|
462
|
+
#### `workspace.site`
|
|
463
|
+
|
|
464
|
+
Site configuration (cached after `initialize()`).
|
|
465
|
+
|
|
466
|
+
#### `workspace.getInfo()`
|
|
467
|
+
|
|
468
|
+
Get workspace info (name and slug).
|
|
469
|
+
|
|
470
|
+
#### `workspace.loadSite()`
|
|
471
|
+
|
|
472
|
+
Load/reload site configuration from the API.
|
|
473
|
+
|
|
474
|
+
#### `workspace.entries`
|
|
475
|
+
|
|
476
|
+
Entries module.
|
|
477
|
+
|
|
478
|
+
#### `workspace.collections`
|
|
479
|
+
|
|
480
|
+
Collections module.
|
|
481
|
+
|
|
482
|
+
#### `workspace.assets`
|
|
483
|
+
|
|
484
|
+
Assets module.
|
|
485
|
+
|
|
486
|
+
### Entries Module
|
|
487
|
+
|
|
488
|
+
#### `entries.list(options?)`
|
|
489
|
+
|
|
490
|
+
Get all entries with optional filtering.
|
|
491
|
+
|
|
492
|
+
**Options:**
|
|
493
|
+
- `entryType?: string` - Filter by entry type slug
|
|
494
|
+
- `collection?: string` - Filter by collection slug
|
|
495
|
+
- `status?: string` - Filter by status (default: `'published'`)
|
|
496
|
+
- `limit?: number` - Max results
|
|
497
|
+
- `offset?: number` - Pagination offset
|
|
498
|
+
|
|
499
|
+
#### `entries.get(slug)`
|
|
500
|
+
|
|
501
|
+
Get a single entry (returns `Entry` object).
|
|
502
|
+
|
|
503
|
+
#### `entries.pages(options?)`
|
|
504
|
+
|
|
505
|
+
Get all pages.
|
|
506
|
+
|
|
507
|
+
#### `entries.posts(options?)`
|
|
508
|
+
|
|
509
|
+
Get all blog posts.
|
|
510
|
+
|
|
511
|
+
#### `entries.projects(options?)`
|
|
512
|
+
|
|
513
|
+
Get all projects.
|
|
514
|
+
|
|
515
|
+
### Entry Object
|
|
516
|
+
|
|
517
|
+
Properties:
|
|
518
|
+
- `id`, `title`, `slug`, `summary`, `description`
|
|
519
|
+
- `contentMarkdown` - Raw Markdown content
|
|
520
|
+
- `metadata` - Custom metadata object
|
|
521
|
+
- `status`, `publishedAt`, `createdAt`, `updatedAt`
|
|
522
|
+
- `entryTypeId`, `entryType`
|
|
523
|
+
- `assets` - Array of related assets
|
|
524
|
+
- `collections` - Array of related collections
|
|
525
|
+
|
|
526
|
+
Methods:
|
|
527
|
+
- `entry.toJSON()` - Get raw entry data
|
|
528
|
+
|
|
529
|
+
### Collections Module
|
|
530
|
+
|
|
531
|
+
#### `collections.list()`
|
|
532
|
+
|
|
533
|
+
Get all collections.
|
|
534
|
+
|
|
535
|
+
#### `collections.get(slug)`
|
|
536
|
+
|
|
537
|
+
Get a single collection (returns `Collection` object).
|
|
538
|
+
|
|
539
|
+
#### `collections.entries(slug)`
|
|
540
|
+
|
|
541
|
+
Get entries in a collection.
|
|
542
|
+
|
|
543
|
+
### Collection Object
|
|
544
|
+
|
|
545
|
+
Properties:
|
|
546
|
+
- `id`, `name`, `slug`, `description`
|
|
547
|
+
- `icon`, `color`, `sortOrder`
|
|
548
|
+
- `isSmart`, `smartRules`
|
|
549
|
+
- `createdAt`, `updatedAt`
|
|
550
|
+
|
|
551
|
+
Methods:
|
|
552
|
+
- `collection.entries()` - Get entries in collection
|
|
553
|
+
- `collection.toJSON()` - Get raw collection data
|
|
554
|
+
|
|
555
|
+
### Assets Module
|
|
556
|
+
|
|
557
|
+
#### `assets.list(options?)`
|
|
558
|
+
|
|
559
|
+
Get all assets.
|
|
560
|
+
|
|
561
|
+
**Options:**
|
|
562
|
+
- `type?: string` - Filter by type
|
|
563
|
+
- `limit?: number` - Max results
|
|
564
|
+
- `offset?: number` - Pagination offset
|
|
565
|
+
|
|
566
|
+
#### `assets.images(options?)`
|
|
567
|
+
|
|
568
|
+
Get all images.
|
|
569
|
+
|
|
570
|
+
#### `assets.videos(options?)`
|
|
571
|
+
|
|
572
|
+
Get all videos.
|
|
573
|
+
|
|
574
|
+
#### `assets.documents(options?)`
|
|
575
|
+
|
|
576
|
+
Get all documents.
|
|
577
|
+
|
|
578
|
+
### Resources Module
|
|
579
|
+
|
|
580
|
+
#### `resources.list(options?)`
|
|
581
|
+
|
|
582
|
+
Get all resources.
|
|
583
|
+
|
|
584
|
+
**Options:**
|
|
585
|
+
- `resourceType?: string` - Filter by resource type
|
|
586
|
+
- `limit?: number` - Max results
|
|
587
|
+
- `offset?: number` - Pagination offset
|
|
588
|
+
|
|
589
|
+
#### `resources.get(slug)`
|
|
590
|
+
|
|
591
|
+
Get a single resource (returns `Resource` object).
|
|
592
|
+
|
|
593
|
+
#### `resources.entries(slug)`
|
|
594
|
+
|
|
595
|
+
Get entries that reference a resource.
|
|
596
|
+
|
|
597
|
+
### Resource Object
|
|
598
|
+
|
|
599
|
+
Properties:
|
|
600
|
+
- `id`, `name`, `slug`
|
|
601
|
+
- `resourceType` - Type of resource (fabric, tool, supplier, etc.)
|
|
602
|
+
- `description`, `externalId`, `url`
|
|
603
|
+
- `metadata` - Custom metadata object
|
|
604
|
+
- `createdAt`, `updatedAt`
|
|
605
|
+
|
|
606
|
+
Methods:
|
|
607
|
+
- `resource.entries()` - Get entries that reference this resource
|
|
608
|
+
- `resource.toJSON()` - Get raw resource data
|
|
609
|
+
|
|
610
|
+
## Backend API Endpoints
|
|
611
|
+
|
|
612
|
+
The SDK uses these Marvin publishing API endpoints:
|
|
613
|
+
|
|
614
|
+
| Endpoint | Method | Purpose | Status |
|
|
615
|
+
|----------|--------|---------|--------|
|
|
616
|
+
| `/api/publish/{workspace}` | GET | Workspace info (name, slug) | ✅ Implemented |
|
|
617
|
+
| `/api/publish/{workspace}/site` | GET | Site configuration | ✅ Implemented |
|
|
618
|
+
| `/api/publish/{workspace}/entries` | GET | List entries | ✅ Implemented |
|
|
619
|
+
| `/api/publish/{workspace}/entries/{slug}` | GET | Get entry | ✅ Implemented |
|
|
620
|
+
| `/api/publish/{workspace}/collections` | GET | List collections | ✅ Implemented |
|
|
621
|
+
| `/api/publish/{workspace}/collections/{slug}` | GET | Get collection | ✅ Implemented |
|
|
622
|
+
| `/api/publish/{workspace}/collections/{slug}/entries` | GET | Collection entries | ✅ Implemented |
|
|
623
|
+
| `/api/publish/{workspace}/assets` | GET | List assets | ✅ Implemented |
|
|
624
|
+
| `/api/publish/{workspace}/assets/{slug}` | GET | Get asset metadata | ✅ Implemented |
|
|
625
|
+
| `/api/publish/{workspace}/resources` | GET | List resources | ✅ Implemented |
|
|
626
|
+
| `/api/publish/{workspace}/resources/{slug}` | GET | Get resource | ✅ Implemented |
|
|
627
|
+
| `/api/publish/{workspace}/resources/{slug}/entries` | GET | Resource entries | ✅ Implemented |
|
|
628
|
+
|
|
629
|
+
**Query Parameters:**
|
|
630
|
+
|
|
631
|
+
- **Entries**: `entry_type`, `collection`, `slug`, `updated_since`, `page`, `limit`
|
|
632
|
+
- **Assets**: `type` (MIME type prefix), `limit`, `offset`
|
|
633
|
+
- **Resources**: `resource_type`, `limit`, `offset`
|
|
634
|
+
|
|
635
|
+
## Security
|
|
636
|
+
|
|
637
|
+
### Site Client Tokens
|
|
638
|
+
|
|
639
|
+
Always use **site client tokens**, never user tokens.
|
|
640
|
+
|
|
641
|
+
Get a site client token:
|
|
642
|
+
1. Log into Marvin
|
|
643
|
+
2. Go to **Settings → Publishing → Site Clients**
|
|
644
|
+
3. Create a new client
|
|
645
|
+
4. Copy the token
|
|
646
|
+
|
|
647
|
+
### Server-Side Only
|
|
648
|
+
|
|
649
|
+
**Never expose tokens in browser code:**
|
|
650
|
+
|
|
651
|
+
```ts
|
|
652
|
+
// ✅ Good - Server/build time
|
|
653
|
+
const marvin = createMarvinClient();
|
|
654
|
+
|
|
655
|
+
// ❌ Bad - Browser code with exposed token
|
|
656
|
+
<script>
|
|
657
|
+
const marvin = createMarvinClient({
|
|
658
|
+
siteClientToken: '{TOKEN}' // DON'T DO THIS!
|
|
659
|
+
});
|
|
660
|
+
</script>
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
## Future Modules
|
|
664
|
+
|
|
665
|
+
The SDK architecture supports future expansion:
|
|
666
|
+
|
|
667
|
+
- ✅ **Publishing** - Implemented
|
|
668
|
+
- 🔜 **Authentication** - User authentication
|
|
669
|
+
- 🔜 **Users** - User management
|
|
670
|
+
- 🔜 **Workspaces** - Workspace management
|
|
671
|
+
- 🔜 **Events** - Event streams
|
|
672
|
+
- 🔜 **Webhooks** - Webhook management
|
|
673
|
+
- 🔜 **Search** - Full-text search
|
|
674
|
+
- 🔜 **AI** - AI-powered features
|
|
675
|
+
|
|
676
|
+
## Design Philosophy
|
|
677
|
+
|
|
678
|
+
The SDK is inspired by:
|
|
679
|
+
|
|
680
|
+
- [Supabase JavaScript SDK](https://github.com/supabase/supabase-js)
|
|
681
|
+
- [Stripe SDK](https://github.com/stripe/stripe-node)
|
|
682
|
+
- [GitHub Octokit](https://github.com/octokit/octokit.js)
|
|
683
|
+
|
|
684
|
+
**Simple to start, powerful as you grow.**
|
|
685
|
+
|
|
686
|
+
Consumers think in **Marvin concepts** (Workspace, Entry, Collection), not raw REST endpoints.
|
|
687
|
+
|
|
688
|
+
## Troubleshooting
|
|
689
|
+
|
|
690
|
+
### "MARVIN_API_URL is required"
|
|
691
|
+
|
|
692
|
+
Make sure your `.env` file exists with all required variables.
|
|
693
|
+
|
|
694
|
+
### "401 Unauthorized"
|
|
695
|
+
|
|
696
|
+
- Verify your site client token is valid
|
|
697
|
+
- Check that you're using a site client token, not a user token
|
|
698
|
+
- Ensure the token hasn't expired
|
|
699
|
+
|
|
700
|
+
### TypeScript Errors
|
|
701
|
+
|
|
702
|
+
Make sure your `tsconfig.json` includes:
|
|
703
|
+
|
|
704
|
+
```json
|
|
705
|
+
{
|
|
706
|
+
"compilerOptions": {
|
|
707
|
+
"moduleResolution": "bundler",
|
|
708
|
+
"types": ["node"]
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
## License
|
|
714
|
+
|
|
715
|
+
MIT - Part of the Marvin project.
|
|
716
|
+
|
|
717
|
+
## Support
|
|
718
|
+
|
|
719
|
+
- 📖 [Full Documentation](./examples.md)
|
|
720
|
+
- 🚀 [Quick Start Guide](./QUICKSTART.md)
|
|
721
|
+
- 🐛 [Report Issues](https://github.com/jmashburn/Marvin/issues)
|