loopwind 0.24.1 → 0.24.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.
@@ -0,0 +1,1001 @@
1
+ # Loopwind API Service Design
2
+
3
+ ## Overview
4
+
5
+ This document outlines the architecture for a cloud-based Loopwind API service that enables:
6
+ - **Publishing templates** via `loopwind publish template-name`
7
+ - **User accounts** at loopwind.dev with authentication
8
+ - **Secure API endpoints** for generating images/videos from published templates
9
+ - **Template marketplace** for sharing and discovering templates
10
+
11
+ ---
12
+
13
+ ## Architecture
14
+
15
+ ### High-Level Architecture
16
+
17
+ ```
18
+ ┌─────────────────────────────────────────────────────────────────────────┐
19
+ │ loopwind CLI │
20
+ │ loopwind publish / loopwind render --remote │
21
+ └─────────────────────────────────────────────────────────────────────────┘
22
+
23
+
24
+ ┌─────────────────────────────────────────────────────────────────────────┐
25
+ │ API Gateway (Kong/AWS) │
26
+ │ Rate Limiting • Auth Validation • Request Routing │
27
+ └─────────────────────────────────────────────────────────────────────────┘
28
+
29
+ ┌───────────────────────┼───────────────────────┐
30
+ ▼ ▼ ▼
31
+ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
32
+ │ Auth Service │ │ Template Service │ │ Render Service │
33
+ │ │ │ │ │ │
34
+ │ • User accounts │ │ • Template CRUD │ │ • Image rendering │
35
+ │ • API keys │ │ • Version control │ │ • Video rendering │
36
+ │ • OAuth │ │ • Validation │ │ • Job queue │
37
+ │ • Sessions │ │ • Search/browse │ │ • Webhooks │
38
+ └───────────────────┘ └───────────────────┘ └───────────────────┘
39
+ │ │ │
40
+ └───────────────────────┼───────────────────────┘
41
+
42
+ ┌─────────────────────────────────────────────────────────────────────────┐
43
+ │ Data Layer │
44
+ │ PostgreSQL (users, templates) • Redis (cache, sessions, queue) │
45
+ │ S3/R2 (template files, renders) • CDN (output delivery) │
46
+ └─────────────────────────────────────────────────────────────────────────┘
47
+ ```
48
+
49
+ ### Design Principles
50
+
51
+ 1. **Microservices** - Independent, deployable services for auth, templates, and rendering
52
+ 2. **WASM-first rendering** - Serverless-compatible using existing h264-mp4-encoder/Resvg
53
+ 3. **Multi-tenancy** - Isolated user data with shared infrastructure
54
+ 4. **Queue-based video rendering** - Long-running jobs handled asynchronously
55
+ 5. **Edge caching** - Fast delivery of rendered assets via CDN
56
+
57
+ ---
58
+
59
+ ## Authentication System
60
+
61
+ ### User Authentication
62
+
63
+ ```
64
+ POST /api/auth/register
65
+ {
66
+ "email": "user@example.com",
67
+ "password": "securepassword",
68
+ "name": "John Doe"
69
+ }
70
+ → 201 { "user": {...}, "token": "jwt..." }
71
+
72
+ POST /api/auth/login
73
+ {
74
+ "email": "user@example.com",
75
+ "password": "securepassword"
76
+ }
77
+ → 200 { "user": {...}, "token": "jwt...", "refreshToken": "..." }
78
+
79
+ POST /api/auth/refresh
80
+ {
81
+ "refreshToken": "..."
82
+ }
83
+ → 200 { "token": "jwt...", "refreshToken": "..." }
84
+
85
+ POST /api/auth/logout
86
+ → 204 No Content
87
+ ```
88
+
89
+ ### API Keys for Programmatic Access
90
+
91
+ ```
92
+ POST /api/keys
93
+ {
94
+ "name": "Production App",
95
+ "scopes": ["render:read", "render:write", "templates:read"]
96
+ }
97
+ → 201 {
98
+ "id": "key_abc123",
99
+ "key": "lw_live_xxxxxxxxxxxxxxxxxxxx", // Only shown once
100
+ "name": "Production App",
101
+ "scopes": [...],
102
+ "createdAt": "2025-01-05T..."
103
+ }
104
+
105
+ GET /api/keys
106
+ → 200 [{ "id": "key_abc123", "name": "...", "lastUsed": "...", ... }]
107
+
108
+ DELETE /api/keys/:id
109
+ → 204 No Content
110
+ ```
111
+
112
+ ### CLI Authentication Flow
113
+
114
+ ```bash
115
+ # Login via browser (OAuth-style device flow)
116
+ $ loopwind login
117
+ Opening browser for authentication...
118
+ Waiting for confirmation...
119
+ ✓ Logged in as user@example.com
120
+
121
+ # Token stored in ~/.loopwind/credentials
122
+ {
123
+ "token": "jwt...",
124
+ "refreshToken": "...",
125
+ "email": "user@example.com",
126
+ "expiresAt": "2025-01-06T..."
127
+ }
128
+
129
+ # Or use API key directly
130
+ $ export LOOPWIND_API_KEY=lw_live_xxxxxxxxxxxxxxxxxxxx
131
+ $ loopwind publish my-template
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Template Publishing
137
+
138
+ ### Publish Command
139
+
140
+ ```bash
141
+ loopwind publish [template-name] [options]
142
+
143
+ Options:
144
+ --version <version> Semantic version (default: auto-increment)
145
+ --description <text> Template description
146
+ --private Make template private (default: public)
147
+ --tags <tags> Comma-separated tags
148
+ --dry-run Validate without publishing
149
+ ```
150
+
151
+ ### Publishing Flow
152
+
153
+ ```
154
+ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ ┌─────────────┐
155
+ │ Validate │ → │ Package │ → │ Upload │ → │ Publish │
156
+ │ Template │ │ Files │ │ to Storage │ │ Metadata │
157
+ └─────────────┘ └──────────────┘ └────────────────┘ └─────────────┘
158
+ │ │ │ │
159
+ ▼ ▼ ▼ ▼
160
+ Check meta.ts Bundle template POST files to S3 Update registry
161
+ Validate props Include assets Get file URLs Set version
162
+ Check deps Compress Store checksums Index for search
163
+ ```
164
+
165
+ ### Publish API Endpoints
166
+
167
+ ```
168
+ POST /api/templates/publish
169
+ Authorization: Bearer <token>
170
+ Content-Type: multipart/form-data
171
+
172
+ {
173
+ "name": "og-card", // Required
174
+ "version": "1.0.0", // Semver
175
+ "description": "Dynamic OG images", // Optional
176
+ "private": false, // Default: false
177
+ "tags": ["og", "social", "image"], // Optional
178
+ "files": [ // Multipart files
179
+ { "path": "template.tsx", "content": "..." },
180
+ { "path": "assets/logo.png", "content": "..." }
181
+ ],
182
+ "meta": { // Extracted from template
183
+ "type": "image",
184
+ "size": { "width": 1200, "height": 630 },
185
+ "props": { "title": "string", "subtitle": "string?" }
186
+ }
187
+ }
188
+
189
+ → 201 {
190
+ "id": "tpl_abc123",
191
+ "name": "og-card",
192
+ "version": "1.0.0",
193
+ "author": "username",
194
+ "url": "https://loopwind.dev/username/og-card",
195
+ "publishedAt": "2025-01-05T..."
196
+ }
197
+ ```
198
+
199
+ ### Template Metadata Schema
200
+
201
+ ```typescript
202
+ interface PublishedTemplate {
203
+ id: string; // tpl_abc123
204
+ name: string; // og-card
205
+ slug: string; // username/og-card
206
+ version: string; // 1.0.0
207
+ description: string;
208
+
209
+ author: {
210
+ id: string;
211
+ username: string;
212
+ avatar?: string;
213
+ };
214
+
215
+ meta: {
216
+ type: "image" | "video";
217
+ size: { width: number; height: number };
218
+ video?: { fps: number; duration: number };
219
+ props: Record<string, string>;
220
+ };
221
+
222
+ files: {
223
+ path: string;
224
+ url: string; // CDN URL
225
+ checksum: string;
226
+ }[];
227
+
228
+ stats: {
229
+ downloads: number;
230
+ renders: number;
231
+ stars: number;
232
+ };
233
+
234
+ private: boolean;
235
+ tags: string[];
236
+
237
+ createdAt: string;
238
+ updatedAt: string;
239
+ publishedAt: string;
240
+ }
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Rendering API
246
+
247
+ ### Synchronous Rendering (Images)
248
+
249
+ For quick image renders (<5 seconds):
250
+
251
+ ```
252
+ POST /api/render
253
+ Authorization: Bearer <token>
254
+ Content-Type: application/json
255
+
256
+ {
257
+ "template": "username/og-card", // Template slug or ID
258
+ "version": "latest", // Optional, default: latest
259
+ "props": {
260
+ "title": "Hello World",
261
+ "subtitle": "My awesome post"
262
+ },
263
+ "format": "png", // png, jpg, webp, svg
264
+ "scale": 2 // Optional, for retina (default: 1)
265
+ }
266
+
267
+ → 200 (binary image data)
268
+ Content-Type: image/png
269
+ X-Render-Duration: 234ms
270
+ X-Render-Id: rnd_xyz789
271
+ ```
272
+
273
+ ### Asynchronous Rendering (Videos)
274
+
275
+ For longer video renders:
276
+
277
+ ```
278
+ POST /api/render/async
279
+ Authorization: Bearer <token>
280
+ Content-Type: application/json
281
+
282
+ {
283
+ "template": "username/promo-video",
284
+ "props": {
285
+ "title": "Product Launch",
286
+ "logoUrl": "https://example.com/logo.png"
287
+ },
288
+ "format": "mp4", // mp4, gif
289
+ "webhook": "https://myapp.com/hooks/render-complete" // Optional
290
+ }
291
+
292
+ → 202 {
293
+ "jobId": "job_abc123",
294
+ "status": "queued",
295
+ "estimatedDuration": 15000, // ms
296
+ "statusUrl": "/api/render/job_abc123",
297
+ "createdAt": "2025-01-05T..."
298
+ }
299
+ ```
300
+
301
+ ### Job Status Polling
302
+
303
+ ```
304
+ GET /api/render/:jobId
305
+ Authorization: Bearer <token>
306
+
307
+ → 200 {
308
+ "jobId": "job_abc123",
309
+ "status": "processing", // queued, processing, completed, failed
310
+ "progress": 0.45, // 0-1 for video
311
+ "currentFrame": 45,
312
+ "totalFrames": 100,
313
+ "createdAt": "2025-01-05T...",
314
+ "startedAt": "2025-01-05T..."
315
+ }
316
+
317
+ // When completed:
318
+ → 200 {
319
+ "jobId": "job_abc123",
320
+ "status": "completed",
321
+ "progress": 1,
322
+ "outputUrl": "https://cdn.loopwind.dev/renders/job_abc123.mp4",
323
+ "expiresAt": "2025-01-06T...", // URL expires in 24h
324
+ "duration": 12500, // Render time in ms
325
+ "completedAt": "2025-01-05T..."
326
+ }
327
+ ```
328
+
329
+ ### Webhook Payload
330
+
331
+ ```
332
+ POST https://myapp.com/hooks/render-complete
333
+ Content-Type: application/json
334
+ X-Loopwind-Signature: sha256=...
335
+
336
+ {
337
+ "event": "render.completed",
338
+ "jobId": "job_abc123",
339
+ "status": "completed",
340
+ "outputUrl": "https://cdn.loopwind.dev/renders/job_abc123.mp4",
341
+ "template": "username/promo-video",
342
+ "format": "mp4",
343
+ "duration": 12500,
344
+ "timestamp": "2025-01-05T..."
345
+ }
346
+ ```
347
+
348
+ ### CLI Remote Rendering
349
+
350
+ ```bash
351
+ # Render using cloud API (fast, no local compute)
352
+ $ loopwind render my-template --remote --props '{"title": "Hello"}'
353
+ ✓ Rendered in 234ms
354
+ Output: https://cdn.loopwind.dev/renders/rnd_xyz789.png
355
+
356
+ # For videos (async)
357
+ $ loopwind render promo-video --remote --format mp4 --props '{"title": "Launch"}'
358
+ ⠋ Rendering video... 45% (45/100 frames)
359
+ ✓ Rendered in 12.5s
360
+ Output: https://cdn.loopwind.dev/renders/job_abc123.mp4
361
+ ```
362
+
363
+ ---
364
+
365
+ ## Template Marketplace
366
+
367
+ ### Discovery Endpoints
368
+
369
+ ```
370
+ GET /api/templates
371
+ ?q=<search> Search query
372
+ &type=image|video Filter by type
373
+ &tags=og,social Filter by tags
374
+ &author=username Filter by author
375
+ &sort=popular|recent|downloads
376
+ &page=1&limit=20
377
+
378
+ → 200 {
379
+ "templates": [...],
380
+ "total": 156,
381
+ "page": 1,
382
+ "pages": 8
383
+ }
384
+
385
+ GET /api/templates/:slug
386
+ → 200 { ...PublishedTemplate }
387
+
388
+ GET /api/templates/:slug/versions
389
+ → 200 [
390
+ { "version": "1.0.0", "publishedAt": "...", "downloads": 50 },
391
+ { "version": "0.9.0", "publishedAt": "...", "downloads": 120 }
392
+ ]
393
+ ```
394
+
395
+ ### User Profile & Templates
396
+
397
+ ```
398
+ GET /api/users/:username
399
+ → 200 {
400
+ "username": "johndoe",
401
+ "name": "John Doe",
402
+ "avatar": "https://...",
403
+ "bio": "Designer & developer",
404
+ "templates": [...],
405
+ "stats": { "templates": 12, "totalDownloads": 5000 }
406
+ }
407
+
408
+ GET /api/me/templates
409
+ Authorization: Bearer <token>
410
+ → 200 [...] // All user's templates including private
411
+ ```
412
+
413
+ ---
414
+
415
+ ## Pricing & Rate Limits
416
+
417
+ ### Free Tier
418
+ - 100 image renders/month
419
+ - 10 video renders/month
420
+ - 5 published templates
421
+ - Public templates only
422
+ - Rate limit: 10 req/min
423
+
424
+ ### Pro Tier ($19/month)
425
+ - 5,000 image renders/month
426
+ - 500 video renders/month
427
+ - Unlimited published templates
428
+ - Private templates
429
+ - Priority rendering
430
+ - Rate limit: 100 req/min
431
+ - Webhook support
432
+
433
+ ### Business Tier ($99/month)
434
+ - 50,000 image renders/month
435
+ - 5,000 video renders/month
436
+ - Team accounts
437
+ - Custom domains
438
+ - SLA guarantee
439
+ - Rate limit: 500 req/min
440
+ - API analytics
441
+
442
+ ### Rate Limit Headers
443
+
444
+ ```
445
+ X-RateLimit-Limit: 100
446
+ X-RateLimit-Remaining: 95
447
+ X-RateLimit-Reset: 1704499200
448
+ ```
449
+
450
+ ---
451
+
452
+ ## Infrastructure
453
+
454
+ ### Recommended Stack (100% Cloudflare)
455
+
456
+ | Component | Technology | Why |
457
+ |-----------|------------|-----|
458
+ | API Gateway | Cloudflare Workers | Rate limiting, auth, routing - all at edge |
459
+ | Auth | Workers + D1 | JWT validation, session management |
460
+ | Database | D1 (SQLite) / Turso | Users, templates, jobs - edge-native |
461
+ | Cache | Workers KV | Sessions, rate limits, template cache |
462
+ | Queue | Durable Objects | Video job orchestration |
463
+ | Storage | R2 | Templates, rendered outputs |
464
+ | CDN | Cloudflare CDN | Automatic, integrated with R2 |
465
+ | Image Rendering | Workers (WASM) | Satori + Resvg, up to 5min CPU |
466
+ | Video Rendering | Containers | FFmpeg, unlimited duration, GPU support |
467
+ | Monitoring | Workers Analytics + Sentry | Built-in metrics + error tracking |
468
+
469
+ ### Deployment Architecture
470
+
471
+ ```
472
+ ┌─────────────────────────┐
473
+ │ Cloudflare CDN │
474
+ │ (R2 public buckets) │
475
+ └───────────┬─────────────┘
476
+
477
+ ┌────────────────────────────────────┼────────────────────────────────────┐
478
+ │ │ │
479
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
480
+ │ │ Cloudflare Workers │ │
481
+ │ │ │ │
482
+ │ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────────────┐ │ │
483
+ │ │ │ API Worker │ │ Template │ │ Image Render Worker │ │ │
484
+ │ │ │ │ │ Worker │ │ │ │ │
485
+ │ │ │ • Auth/JWT │ │ • Publish │ │ • Satori (JSX→SVG) │ │ │
486
+ │ │ │ • Rate limit │ │ • Validate │ │ • Resvg (SVG→PNG) │ │ │
487
+ │ │ │ • Routing │ │ • List/Get │ │ • Up to 5min CPU │ │ │
488
+ │ │ └──────────────┘ └──────────────┘ └───────────────────────┘ │ │
489
+ │ │ │ │
490
+ │ └─────────────────────────────────────────────────────────────────┘ │
491
+ │ │ │
492
+ │ │ getContainer(env.RENDERER, jobId) │
493
+ │ ▼ │
494
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
495
+ │ │ Cloudflare Containers │ │
496
+ │ │ │ │
497
+ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
498
+ │ │ │ job_abc │ │ job_def │ │ job_ghi │ ... │ │
499
+ │ │ │ Container │ │ Container │ │ Container │ │ │
500
+ │ │ │ │ │ │ │ │ │ │
501
+ │ │ │ • FFmpeg │ │ • FFmpeg │ │ • FFmpeg │ │ │
502
+ │ │ │ • Node.js │ │ • Node.js │ │ • Node.js │ │ │
503
+ │ │ │ • Loopwind │ │ • Loopwind │ │ • Loopwind │ │ │
504
+ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
505
+ │ │ │ │ │ │ │
506
+ │ │ └─────────────────┼─────────────────┘ │ │
507
+ │ │ ▼ │ │
508
+ │ │ Durable Object (Job Orchestrator) │ │
509
+ │ │ • Track progress per job │ │
510
+ │ │ • Manage container lifecycle │ │
511
+ │ │ • Handle webhooks │ │
512
+ │ │ │ │
513
+ │ └─────────────────────────────────────────────────────────────────┘ │
514
+ │ │ │
515
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
516
+ │ │ Data Layer │ │
517
+ │ │ │ │
518
+ │ │ D1 (SQLite) KV R2 │ │
519
+ │ │ • Users • Sessions • Templates │ │
520
+ │ │ • Templates • Rate limits • Rendered videos │ │
521
+ │ │ • Jobs • Template cache • Assets │ │
522
+ │ │ • Usage • Font cache │ │
523
+ │ │ │ │
524
+ │ └─────────────────────────────────────────────────────────────────┘ │
525
+ │ │
526
+ └────────────────────────────────────────────────────────────────────────┘
527
+ ```
528
+
529
+ ### Cloudflare Containers for Video Rendering
530
+
531
+ [Cloudflare Containers](https://blog.cloudflare.com/cloudflare-containers-coming-2025/) (launched June 2025) enable **full video rendering on Cloudflare**:
532
+
533
+ #### Key Features
534
+ - **Docker containers** - Run FFmpeg, Node.js, any runtime
535
+ - **No time limits** - Long-running video encodes supported
536
+ - **On-demand scaling** - Containers boot per job, sleep when idle
537
+ - **GPU support** - Hardware-accelerated encoding available
538
+ - **Global deployment** - Containers run near users automatically
539
+ - **Pay per use** - Only charged for active container time
540
+
541
+ #### Container Architecture
542
+
543
+ Each video job gets its own container instance:
544
+
545
+ ```typescript
546
+ // Worker routes video jobs to containers
547
+ import { getContainer } from "cloudflare:container";
548
+
549
+ export default {
550
+ async fetch(request: Request, env: Env) {
551
+ const { jobId, template, props, format } = await request.json();
552
+
553
+ // Each unique jobId = separate container instance
554
+ // Containers auto-scale horizontally
555
+ const container = await getContainer(env.VIDEO_RENDERER, jobId);
556
+
557
+ return container.fetch("/render", {
558
+ method: "POST",
559
+ body: JSON.stringify({ template, props, format })
560
+ });
561
+ }
562
+ }
563
+ ```
564
+
565
+ #### Container Dockerfile
566
+
567
+ ```dockerfile
568
+ FROM node:20-slim
569
+
570
+ # Install FFmpeg for video encoding
571
+ RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*
572
+
573
+ # Install loopwind
574
+ WORKDIR /app
575
+ COPY package*.json ./
576
+ RUN npm ci --production
577
+ COPY . .
578
+
579
+ # Render server listens for jobs
580
+ EXPOSE 8080
581
+ CMD ["node", "render-server.js"]
582
+ ```
583
+
584
+ #### Scaling Patterns
585
+
586
+ **Pattern 1: Job-per-Container (Recommended)**
587
+ ```
588
+ POST /render/video { jobId: "job_abc" }
589
+ → Container "job_abc" boots
590
+ → Renders video
591
+ → Uploads to R2
592
+ → Container sleeps after 60s idle
593
+ ```
594
+
595
+ **Pattern 2: Pooled Workers**
596
+ ```typescript
597
+ // Distribute across N warm containers using consistent hashing
598
+ const POOL_SIZE = 10;
599
+ const workerId = `renderer-${hash(jobId) % POOL_SIZE}`;
600
+ const container = await getContainer(env.VIDEO_RENDERER, workerId);
601
+ ```
602
+
603
+ #### Durable Object Orchestration
604
+
605
+ ```typescript
606
+ // Durable Object manages container lifecycle and job state
607
+ export class VideoJobOrchestrator extends DurableObject {
608
+ async startJob(jobId: string, params: RenderParams) {
609
+ // Store job state
610
+ await this.ctx.storage.put(`job:${jobId}`, {
611
+ status: "processing",
612
+ progress: 0,
613
+ startedAt: Date.now()
614
+ });
615
+
616
+ // Get or create container for this job
617
+ const container = await getContainer(this.env.VIDEO_RENDERER, jobId);
618
+
619
+ // Start render (non-blocking)
620
+ container.fetch("/render", {
621
+ method: "POST",
622
+ body: JSON.stringify(params)
623
+ });
624
+
625
+ return { jobId, status: "processing" };
626
+ }
627
+
628
+ async updateProgress(jobId: string, progress: number) {
629
+ const job = await this.ctx.storage.get(`job:${jobId}`);
630
+ job.progress = progress;
631
+ await this.ctx.storage.put(`job:${jobId}`, job);
632
+
633
+ // Notify via WebSocket if client connected
634
+ this.broadcast({ type: "progress", jobId, progress });
635
+ }
636
+
637
+ async completeJob(jobId: string, outputUrl: string) {
638
+ await this.ctx.storage.put(`job:${jobId}`, {
639
+ status: "completed",
640
+ progress: 1,
641
+ outputUrl,
642
+ completedAt: Date.now()
643
+ });
644
+
645
+ // Fire webhook if configured
646
+ await this.fireWebhook(jobId, outputUrl);
647
+ }
648
+ }
649
+ ```
650
+
651
+ ### Workers for Image Rendering
652
+
653
+ Cloudflare Workers handle all image rendering with [up to 5 minutes CPU time](https://developers.cloudflare.com/changelog/2025-03-25-higher-cpu-limits/):
654
+
655
+ ```toml
656
+ # wrangler.toml
657
+ name = "loopwind-api"
658
+ main = "src/worker.ts"
659
+ compatibility_date = "2025-01-01"
660
+
661
+ [limits]
662
+ cpu_ms = 300000 # 5 minutes - enough for complex images
663
+
664
+ [[d1_databases]]
665
+ binding = "DB"
666
+ database_name = "loopwind"
667
+
668
+ [[r2_buckets]]
669
+ binding = "STORAGE"
670
+ bucket_name = "loopwind-assets"
671
+
672
+ [[kv_namespaces]]
673
+ binding = "CACHE"
674
+ id = "xxx"
675
+ ```
676
+
677
+ ### Rendering Decision Flow
678
+
679
+ ```
680
+ ┌─────────────────┐
681
+ │ Render Request │
682
+ └────────┬────────┘
683
+
684
+
685
+ ┌─────────────────┐
686
+ │ Image or Video?│
687
+ └────────┬────────┘
688
+
689
+ ┌──────────────┴──────────────┐
690
+ │ │
691
+ ▼ ▼
692
+ ┌────────────────┐ ┌────────────────┐
693
+ │ IMAGE │ │ VIDEO │
694
+ └────────┬───────┘ └────────┬───────┘
695
+ │ │
696
+ ▼ ▼
697
+ ┌────────────────┐ ┌────────────────┐
698
+ │ Worker │ │ Container │
699
+ │ │ │ │
700
+ │ • Satori→SVG │ │ • FFmpeg │
701
+ │ • Resvg→PNG │ │ • Full Node.js │
702
+ │ • <5min CPU │ │ • No time limit│
703
+ │ • 128MB RAM │ │ • Multi-core │
704
+ └────────┬───────┘ └────────┬───────┘
705
+ │ │
706
+ ▼ ▼
707
+ ┌────────────────┐ ┌────────────────┐
708
+ │ Sync Response │ │ Async Job ID │
709
+ │ (binary data) │ │ (poll/webhook) │
710
+ └────────────────┘ └────────────────┘
711
+ ```
712
+
713
+ ### Cost Optimization
714
+
715
+ | Workload | Platform | Estimated Cost |
716
+ |----------|----------|----------------|
717
+ | Image render | Worker | ~$0.0001 per render |
718
+ | Short video (<30s) | Worker (WASM) | ~$0.001 per render |
719
+ | Long video (1-5min) | Container | ~$0.01-0.05 per render |
720
+ | GPU video | Container + GPU | ~$0.10-0.50 per render |
721
+
722
+ **Tips:**
723
+ - Use Workers for images (cheapest, fastest)
724
+ - Use WASM encoding in Workers for short videos when possible
725
+ - Containers auto-sleep after idle timeout (default 60s)
726
+ - Cache fonts/assets in KV to reduce cold start time
727
+
728
+ ---
729
+
730
+ ## CLI Implementation
731
+
732
+ ### New Commands
733
+
734
+ ```bash
735
+ # Authentication
736
+ loopwind login # Browser-based OAuth flow
737
+ loopwind logout # Clear credentials
738
+ loopwind whoami # Show current user
739
+
740
+ # Publishing
741
+ loopwind publish [template] # Publish template to registry
742
+ --version <ver> # Semantic version
743
+ --private # Private template
744
+ --dry-run # Validate only
745
+
746
+ # Remote rendering
747
+ loopwind render <template> # Render (local by default)
748
+ --remote # Use cloud API
749
+ --props <json> # Template props
750
+ --format <fmt> # Output format
751
+ --out <path> # Save locally
752
+
753
+ # Account management
754
+ loopwind keys list # List API keys
755
+ loopwind keys create # Create new API key
756
+ loopwind keys revoke <id> # Revoke API key
757
+ ```
758
+
759
+ ### Credential Storage
760
+
761
+ ```
762
+ ~/.loopwind/
763
+ ├── credentials.json # Auth tokens
764
+ ├── config.json # User preferences
765
+ └── keys/ # Cached API keys
766
+ ```
767
+
768
+ ```json
769
+ // ~/.loopwind/credentials.json
770
+ {
771
+ "token": "eyJhbG...",
772
+ "refreshToken": "eyJhbG...",
773
+ "expiresAt": "2025-01-06T12:00:00Z",
774
+ "user": {
775
+ "id": "usr_abc123",
776
+ "email": "user@example.com",
777
+ "username": "johndoe"
778
+ }
779
+ }
780
+ ```
781
+
782
+ ---
783
+
784
+ ## Security Considerations
785
+
786
+ ### API Security
787
+
788
+ 1. **Authentication**
789
+ - JWT tokens with short expiry (15min)
790
+ - Refresh tokens stored securely (HttpOnly cookies on web)
791
+ - API keys hashed with bcrypt before storage
792
+
793
+ 2. **Authorization**
794
+ - Scoped API keys (render:read, templates:write, etc.)
795
+ - Private template access control
796
+ - Rate limiting per API key/user
797
+
798
+ 3. **Input Validation**
799
+ - Template code sandboxed (no network access, no fs access)
800
+ - Props validated against schema
801
+ - File size limits (10MB per template)
802
+
803
+ 4. **Output Security**
804
+ - Signed URLs with expiration
805
+ - CORS configuration
806
+ - Content-Security-Policy headers
807
+
808
+ ### Template Sandboxing
809
+
810
+ Templates run in isolated environment:
811
+
812
+ ```typescript
813
+ // Allowed in templates
814
+ - React JSX rendering
815
+ - Math operations
816
+ - String manipulation
817
+ - Built-in helpers (tw, qr, image, path)
818
+
819
+ // NOT allowed
820
+ - fetch() / network requests
821
+ - fs / file system access
822
+ - eval() / dynamic code
823
+ - process / environment access
824
+ - require() / import() of external modules
825
+ ```
826
+
827
+ ---
828
+
829
+ ## Database Schema
830
+
831
+ ```sql
832
+ -- Users
833
+ CREATE TABLE users (
834
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
835
+ email VARCHAR(255) UNIQUE NOT NULL,
836
+ username VARCHAR(50) UNIQUE NOT NULL,
837
+ password_hash VARCHAR(255),
838
+ name VARCHAR(255),
839
+ avatar_url VARCHAR(500),
840
+ plan VARCHAR(20) DEFAULT 'free',
841
+ created_at TIMESTAMP DEFAULT NOW(),
842
+ updated_at TIMESTAMP DEFAULT NOW()
843
+ );
844
+
845
+ -- API Keys
846
+ CREATE TABLE api_keys (
847
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
848
+ user_id UUID REFERENCES users(id) ON DELETE CASCADE,
849
+ name VARCHAR(100) NOT NULL,
850
+ key_hash VARCHAR(255) NOT NULL,
851
+ key_prefix VARCHAR(12) NOT NULL, -- lw_live_xxxx for display
852
+ scopes TEXT[] DEFAULT '{}',
853
+ last_used_at TIMESTAMP,
854
+ created_at TIMESTAMP DEFAULT NOW()
855
+ );
856
+
857
+ -- Templates
858
+ CREATE TABLE templates (
859
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
860
+ user_id UUID REFERENCES users(id) ON DELETE CASCADE,
861
+ name VARCHAR(100) NOT NULL,
862
+ slug VARCHAR(150) NOT NULL, -- username/template-name
863
+ description TEXT,
864
+ type VARCHAR(20) NOT NULL, -- image, video
865
+ meta JSONB NOT NULL,
866
+ private BOOLEAN DEFAULT FALSE,
867
+ tags TEXT[] DEFAULT '{}',
868
+ downloads INTEGER DEFAULT 0,
869
+ renders INTEGER DEFAULT 0,
870
+ created_at TIMESTAMP DEFAULT NOW(),
871
+ updated_at TIMESTAMP DEFAULT NOW(),
872
+ UNIQUE(user_id, name)
873
+ );
874
+
875
+ -- Template Versions
876
+ CREATE TABLE template_versions (
877
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
878
+ template_id UUID REFERENCES templates(id) ON DELETE CASCADE,
879
+ version VARCHAR(20) NOT NULL,
880
+ files JSONB NOT NULL, -- [{path, url, checksum}]
881
+ published_at TIMESTAMP DEFAULT NOW(),
882
+ UNIQUE(template_id, version)
883
+ );
884
+
885
+ -- Render Jobs
886
+ CREATE TABLE render_jobs (
887
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
888
+ user_id UUID REFERENCES users(id),
889
+ template_id UUID REFERENCES templates(id),
890
+ status VARCHAR(20) DEFAULT 'queued',
891
+ props JSONB,
892
+ format VARCHAR(10),
893
+ output_url VARCHAR(500),
894
+ error TEXT,
895
+ progress FLOAT DEFAULT 0,
896
+ started_at TIMESTAMP,
897
+ completed_at TIMESTAMP,
898
+ created_at TIMESTAMP DEFAULT NOW()
899
+ );
900
+
901
+ -- Usage tracking
902
+ CREATE TABLE usage (
903
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
904
+ user_id UUID REFERENCES users(id),
905
+ type VARCHAR(20) NOT NULL, -- image_render, video_render, publish
906
+ count INTEGER DEFAULT 1,
907
+ month DATE NOT NULL, -- First of month
908
+ UNIQUE(user_id, type, month)
909
+ );
910
+ ```
911
+
912
+ ---
913
+
914
+ ## Implementation Phases
915
+
916
+ ### Phase 1: Authentication & Publishing
917
+ - User registration/login
918
+ - API key management
919
+ - `loopwind login/logout/whoami` commands
920
+ - `loopwind publish` command
921
+ - Template storage in R2
922
+ - Basic registry API
923
+
924
+ ### Phase 2: Image Rendering API
925
+ - Synchronous image rendering endpoint
926
+ - `loopwind render --remote` for images
927
+ - Rate limiting
928
+ - Usage tracking
929
+
930
+ ### Phase 3: Video Rendering & Queue
931
+ - Async video rendering with job queue
932
+ - Webhook notifications
933
+ - Progress tracking
934
+ - `loopwind render --remote` for videos
935
+
936
+ ### Phase 4: Marketplace & Discovery
937
+ - Template search and browse
938
+ - User profiles
939
+ - Download/star tracking
940
+ - loopwind.dev website updates
941
+
942
+ ### Phase 5: Billing & Pro Features
943
+ - Stripe integration
944
+ - Usage-based billing
945
+ - Private templates
946
+ - Team accounts
947
+
948
+ ---
949
+
950
+ ## API Client SDK
951
+
952
+ For easy integration, provide official SDKs:
953
+
954
+ ```typescript
955
+ // JavaScript/TypeScript
956
+ import { Loopwind } from '@loopwind/sdk';
957
+
958
+ const lw = new Loopwind({ apiKey: 'lw_live_xxx' });
959
+
960
+ // Render image
961
+ const image = await lw.render('username/og-card', {
962
+ props: { title: 'Hello World' },
963
+ format: 'png'
964
+ });
965
+
966
+ // Render video (async)
967
+ const job = await lw.renderVideo('username/promo', {
968
+ props: { title: 'Launch' },
969
+ format: 'mp4'
970
+ });
971
+
972
+ // Poll for completion
973
+ const result = await job.wait();
974
+ console.log(result.outputUrl);
975
+
976
+ // Or use webhook
977
+ await lw.renderVideo('username/promo', {
978
+ props: { title: 'Launch' },
979
+ webhook: 'https://myapp.com/hooks/render'
980
+ });
981
+ ```
982
+
983
+ ---
984
+
985
+ ## References
986
+
987
+ ### Cloudflare Platform
988
+ - [Cloudflare Containers Announcement](https://blog.cloudflare.com/cloudflare-containers-coming-2025/) - Containers coming June 2025
989
+ - [Cloudflare Containers Architecture](https://developers.cloudflare.com/containers/architecture/) - How containers work with Workers/DOs
990
+ - [Container Platform with GPUs](https://blog.cloudflare.com/container-platform-preview/) - GPU support preview
991
+ - [Workers 5-Minute CPU Limit](https://developers.cloudflare.com/changelog/2025-03-25-higher-cpu-limits/) - Extended CPU time for Workers
992
+ - [Workers Limits](https://developers.cloudflare.com/workers/platform/limits/) - Full limits documentation
993
+ - [Durable Objects](https://developers.cloudflare.com/durable-objects/) - Stateful coordination
994
+
995
+ ### Architecture & Design
996
+ - [Eden AI - Video Generation APIs](https://www.edenai.co/post/best-ai-video-generation-apis-in-2025)
997
+ - [SaaS Architecture Patterns](https://www.mindinventory.com/blog/software-architecture-patterns/)
998
+ - [API Architecture Best Practices](https://www.catchpoint.com/api-monitoring-tools/api-architecture)
999
+ - [Vercel Marketplace API](https://vercel.com/docs/integrations/create-integration/marketplace-api)
1000
+ - [Sharetribe Marketplace API](https://www.sharetribe.com/docs/concepts/api-sdk/marketplace-api-integration-api/)
1001
+ - [SaaS Architecture Guide 2025](https://www.decipherzone.com/blog-detail/saas-architecture-cto-guide)