@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/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)