{"id":35180907,"url":"https://github.com/spooled-cloud/spooled-example-spriteforge","last_synced_at":"2026-04-06T06:01:32.851Z","repository":{"id":329762815,"uuid":"1120012760","full_name":"Spooled-Cloud/spooled-example-spriteforge","owner":"Spooled-Cloud","description":"Interactive pixel art sprite generator demonstrating Spooled Cloud's distributed job queue. Watch workflows, workers, real-time events, and automatic retries in action. Live demo: example.spooled.cloud","archived":false,"fork":false,"pushed_at":"2025-12-28T23:08:23.000Z","size":1260,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-31T17:36:12.787Z","etag":null,"topics":["background-jobs","demo","example","job-queue","pixel-art","real-time","spooled-cloud","sprite-generator","sse","websocket","workflow"],"latest_commit_sha":null,"homepage":"https://spooled.cloud","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Spooled-Cloud.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-20T09:53:51.000Z","updated_at":"2025-12-28T23:08:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Spooled-Cloud/spooled-example-spriteforge","commit_stats":null,"previous_names":["spooled-cloud/spooled-example-spriteforge"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/Spooled-Cloud/spooled-example-spriteforge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Spooled-Cloud%2Fspooled-example-spriteforge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Spooled-Cloud%2Fspooled-example-spriteforge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Spooled-Cloud%2Fspooled-example-spriteforge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Spooled-Cloud%2Fspooled-example-spriteforge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Spooled-Cloud","download_url":"https://codeload.github.com/Spooled-Cloud/spooled-example-spriteforge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Spooled-Cloud%2Fspooled-example-spriteforge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31461534,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"online","status_checked_at":"2026-04-06T02:00:07.287Z","response_time":112,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["background-jobs","demo","example","job-queue","pixel-art","real-time","spooled-cloud","sprite-generator","sse","websocket","workflow"],"created_at":"2025-12-29T01:48:52.691Z","updated_at":"2026-04-06T06:01:32.839Z","avatar_url":"https://github.com/Spooled-Cloud.png","language":"JavaScript","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://spooled.cloud/logo.webp\" alt=\"Spooled Logo\" width=\"80\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eSpriteForge\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eInteractive pixel art generator powered by Spooled Cloud\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/spooled-cloud/spooled-example-spriteforge/actions/workflows/ci.yml\"\u003e\n    \u003cimg src=\"https://github.com/spooled-cloud/spooled-example-spriteforge/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/spooled-cloud/spooled-example-spriteforge/pkgs/container/spooled-example-spriteforge\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/ghcr.io-spooled--example--spriteforge-blue\" alt=\"Docker\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://example.spooled.cloud\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/demo-example.spooled.cloud-brightgreen\" alt=\"Live Demo\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/license-Apache_2.0-blue.svg\" alt=\"License\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://example.spooled.cloud\"\u003eLive Demo\u003c/a\u003e •\n  \u003ca href=\"#quick-start\"\u003eQuick Start\u003c/a\u003e •\n  \u003ca href=\"#how-it-works\"\u003eHow It Works\u003c/a\u003e •\n  \u003ca href=\"#deployment\"\u003eDeployment\u003c/a\u003e •\n  \u003ca href=\"#api\"\u003eAPI\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## What is SpriteForge?\n\nSpriteForge is a **real-time pixel art sprite generator** that demonstrates the power of [Spooled Cloud](https://spooled.cloud) — a distributed job queue with workflows, real-time events, and automatic retries.\n\n**Try it live:** [example.spooled.cloud](https://example.spooled.cloud)\n\n### Features Demonstrated\n\n| Feature | How It's Used |\n|---------|---------------|\n| **Workflows (DAG)** | Each sprite is a workflow with parallel frame jobs + dependent assemble job |\n| **Workers** | 3 worker types process frames, assemble sprites, and generate public sprites |\n| **Real-time Events** | WebSocket events stream job status to browser via SSE |\n| **Automatic Retries** | \"Chaos mode\" simulates failures — watch jobs retry automatically |\n| **Schedules** | \"Sprite of the Minute\" generated via cron schedule |\n\n---\n\n## Quick Start\n\n### Prerequisites\n\n- Node.js 20+\n- A [Spooled Cloud](https://spooled.cloud) API key (or self-hosted Spooled)\n\n### Local Development\n\n```bash\n# Clone the repository\ngit clone https://github.com/spooled-cloud/spooled-example-spriteforge.git\ncd spooled-example-spriteforge\n\n# Copy environment template\ncp .env.example .env\n\n# Edit .env and add your API key\n# SPOOLED_API_KEY=sp_live_your_key_here\n\n# Install dependencies\nnpm install\n\n# Start the development server\nnpm run dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000)\n\n### Docker\n\n```bash\n# Using Docker Compose (recommended)\ncp .env.example .env\n# Edit .env with your API key\n\ndocker compose up --build\n```\n\nOr run directly:\n\n```bash\ndocker run -p 3000:3000 \\\n  -e SPOOLED_API_KEY=sp_live_your_key \\\n  ghcr.io/spooled-cloud/spooled-example-spriteforge:latest\n```\n\n---\n\n## How It Works\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│                              SpriteForge                                │\n├─────────────────────────────────────────────────────────────────────────┤\n│                                                                         │\n│   Browser                     Server                    Spooled Cloud   │\n│   ───────                     ──────                    ─────────────   │\n│                                                                         │\n│   ┌──────────┐  click    ┌──────────────┐  create    ┌──────────────┐   │\n│   │   UI     │ ────────► │   Express    │  ────────► │   Workflow   │   │\n│   └──────────┘           │   Server     │            │   + Jobs     │   │\n│        ▲                 └──────────────┘            └──────────────┘   │\n│        │                        │                           │           │\n│        │ SSE              ┌─────┴─────┐              Workers│           │\n│        │                  │           │                     ▼           │\n│   ┌────┴─────┐      ┌─────┴───┐ ┌─────┴───┐         ┌──────────────┐    │\n│   │  Events  │ ◄─── │ frame   │ │ assemble│ ◄────── │   Process    │    │\n│   │  Stream  │      │ worker  │ │ worker  │         │   \u0026 Return   │    │\n│   └──────────┘      └─────────┘ └─────────┘         └──────────────┘    │\n│                                                                         │\n└─────────────────────────────────────────────────────────────────────────┘\n```\n\n### Workflow Structure\n\nWhen you click \"Forge Sprite\":\n\n1. **Workflow Created**: A DAG workflow with N+1 jobs\n2. **Frame Jobs** (parallel): Each generates one animation frame\n3. **Assemble Job** (depends on all frames): Combines frames into sprite\n4. **Events Stream**: Job status updates flow to your browser in real-time\n\n```\nSTART ──► [frame-0] [frame-1] [frame-2] ... [frame-N] ──► [assemble] ──► DONE\n            │         │         │              │               │\n            └─────────┴─────────┴──────────────┘               │\n                   all must complete before                    │\n                                                               ▼\n                                                       sprite rendered\n```\n\n---\n\n## Environment Variables\n\n### Required\n\n| Variable | Description |\n|----------|-------------|\n| `SPOOLED_API_KEY` | Your Spooled API key |\n\n### Optional\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `SPOOLED_BASE_URL` | `https://api.spooled.cloud` | Spooled REST API URL |\n| `SPOOLED_WS_URL` | `wss://api.spooled.cloud` | Spooled WebSocket URL |\n| `PORT` | `3000` | Server port |\n| `HOST` | `0.0.0.0` | Server host |\n| `QUEUE_FRAMES` | `spriteforge-frames` | Frame processing queue |\n| `QUEUE_ASSEMBLE` | `spriteforge-assemble` | Sprite assembly queue |\n| `QUEUE_PUBLIC` | `spriteforge-public` | Public sprite queue |\n| `WORKER_CONCURRENCY_FRAMES` | `8` | Concurrent frame jobs |\n| `WORKER_CONCURRENCY_ASSEMBLE` | `2` | Concurrent assemble jobs |\n| `ENABLE_PUBLIC_SCHEDULE` | `true` | Enable \"Sprite of the Minute\" |\n| `PUBLIC_SCHEDULE_CRON` | `0 * * * * *` | Cron for public sprites |\n| `PUBLIC_SCHEDULE_TIMEZONE` | `UTC` | Timezone for schedule |\n| `JOB_RETENTION_HOURS` | `24` | Auto-expire jobs after N hours |\n\nSee [.env.example](.env.example) for a complete template.\n\n---\n\n## Deployment\n\n\u003e 📘 **Full guide**: See [DEPLOY.md](DEPLOY.md) for comprehensive deployment instructions.\n\n### Docker Image\n\nBuilt and pushed to GHCR on every push to `main`:\n\n```\nghcr.io/spooled-cloud/spooled-example-spriteforge:latest\nghcr.io/spooled-cloud/spooled-example-spriteforge:sha-\u003ccommit\u003e\nghcr.io/spooled-cloud/spooled-example-spriteforge:v1.0.0  # tagged releases\n```\n\n### Docker Compose (Production)\n\nThe easiest production deployment with Cloudflare Tunnel:\n\n```bash\ncp .env.example .env\n# Edit .env: add SPOOLED_API_KEY and CLOUDFLARE_TUNNEL_TOKEN\n\ndocker compose -f docker-compose.prod.yml up -d\n```\n\nThis starts:\n- **SpriteForge** container (port 3000 internal)\n- **Cloudflared** tunnel (secure external access)\n\n### Kubernetes\n\n```bash\n# Create secret\nkubectl create secret generic spriteforge-secrets \\\n  --from-literal=SPOOLED_API_KEY=sp_live_your_key \\\n  -n spriteforge\n\n# Deploy\nkubectl apply -k k8s/overlays/production\n```\n\n**Included resources:**\n- Deployment with liveness/readiness probes\n- Service (ClusterIP)\n- Ingress (nginx + cert-manager TLS)\n- HorizontalPodAutoscaler\n- PodDisruptionBudget\n- ServiceAccount + ConfigMap\n\n### Platform Quick Deploy\n\n| Platform | Steps |\n|----------|-------|\n| **Railway** | Fork repo → New project → Connect GitHub → Add `SPOOLED_API_KEY` → Deploy |\n| **Fly.io** | `fly launch` → `fly secrets set SPOOLED_API_KEY=...` → `fly deploy` |\n| **Render** | New Web Service → Docker → Add `SPOOLED_API_KEY` → Deploy |\n| **DigitalOcean** | New App → GitHub → Auto-detect Docker → Deploy |\n\n---\n\n## API\n\n### Health Check\n\n```\nGET /health\n```\n\nReturns `200 OK` when the server is healthy.\n\n### Create Sprite (Forge)\n\n```\nPOST /api/forge\nContent-Type: application/json\n\n{\n  \"sessionId\": \"uuid\",\n  \"seed\": \"my-sprite\",\n  \"paletteName\": \"neon\",\n  \"animation\": \"walk\",\n  \"frameCount\": 8,\n  \"width\": 24,\n  \"height\": 24,\n  \"failChance\": 0.1\n}\n```\n\n### Event Stream (SSE)\n\n```\nGET /api/events?sessionId=\u003cuuid\u003e\n```\n\nStreams real-time events:\n- `hello` - Connection established, includes palettes and session info\n- `spooled` - Job lifecycle events (created, started, completed, failed)\n- `public.sprite` - New public sprite generated\n- `server.realtime` - Server's connection to Spooled status\n\n---\n\n## Project Structure\n\n```\nspooled-example-spriteforge/\n├── .github/\n│   └── workflows/\n│       └── ci.yml              # CI/CD pipeline\n├── k8s/\n│   ├── base/                   # Base Kubernetes manifests\n│   └── overlays/\n│       ├── development/        # Dev overrides\n│       └── production/         # Prod overrides\n├── public/\n│   ├── index.html              # Main HTML\n│   ├── styles.css              # Styles\n│   └── app.js                  # Frontend logic\n├── server/\n│   ├── server.mjs              # Express server + workers\n│   └── spriteforge.mjs         # Pixel art generation\n├── .env.example                # Environment template\n├── Dockerfile                  # Multi-stage Docker build\n├── docker-compose.yml          # Local Docker development\n├── package.json\n└── README.md\n```\n\n---\n\n## Development\n\n### Scripts\n\n```bash\nnpm run dev      # Start development server with hot reload\nnpm start        # Start production server\n```\n\n### Testing Locally with Spooled\n\nYou can run against:\n\n1. **Spooled Cloud** (recommended): Sign up at [spooled.cloud](https://spooled.cloud)\n2. **Self-hosted Spooled**: See [spooled-backend](https://github.com/spooled-cloud/spooled-backend)\n\nFor self-hosted:\n```bash\nSPOOLED_BASE_URL=http://localhost:8080\nSPOOLED_WS_URL=ws://localhost:8080\n```\n\n---\n\n## Architecture Notes\n\n### Why One Replica?\n\nThe demo runs as **1 replica by default** because:\n- SSE connections are stateful\n- Session events route to the correct client\n- No additional infrastructure (Redis for sessions) required\n\nFor multi-replica deployment, you'd need:\n- Sticky sessions (ingress annotation)\n- Or shared session store (Redis)\n\n### Job Cleanup (Handling Many Users)\n\nWhen 100+ users test the demo, jobs and workflows accumulate. SpriteForge handles this automatically:\n\n**1. Job Expiration (Auto-cleanup)**\n- All demo jobs have `expires_at` set to 24 hours (configurable via `JOB_RETENTION_HOURS`)\n- Spooled backend automatically removes expired jobs every 30 seconds\n\n**2. Workflow Cleanup**\n- Completed/failed workflows are cleaned based on plan tier retention\n- Free tier: 3 days, Starter: 14 days, Pro: 30 days, Enterprise: 90 days\n\n**3. In-Memory Cleanup**\n- Session mappings (for routing events) are cleaned every 10 minutes\n- Mappings older than 1 hour are automatically removed\n\n**4. Queue Reuse**\n- Queues (`spriteforge-frames`, `spriteforge-assemble`, `spriteforge-public`) are reused by all users\n- No queue accumulation occurs\n\n\u003e **Tip**: For high-traffic public demos, set `JOB_RETENTION_HOURS=1` in your `.env` file.\n\n**Monitor via health endpoint:**\n```bash\ncurl http://localhost:3000/health\n# Returns stats: { activeSessions, trackedJobs, trackedWorkflows, totalWorkflows, totalJobs }\n```\n\n### Security Considerations\n\nWhen deploying publicly:\n- Use a **dedicated Spooled organization** for the demo\n- Set **plan limits** to prevent abuse\n- The API key is stored server-side; never exposed to browsers\n\n---\n\n## Contributing\n\nContributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) first.\n\n```bash\n# Fork the repo, then:\ngit checkout -b feature/my-feature\n# Make changes\ngit commit -m \"feat: add my feature\"\ngit push origin feature/my-feature\n# Open a PR\n```\n\n---\n\n## License\n\nApache 2.0 © [Spooled Cloud](https://spooled.cloud)\n\n---\n\n\u003cp align=\"center\"\u003e\n  Built with ⚡ by \u003ca href=\"https://spooled.cloud\"\u003eSpooled Cloud\u003c/a\u003e\n\u003c/p\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspooled-cloud%2Fspooled-example-spriteforge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspooled-cloud%2Fspooled-example-spriteforge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspooled-cloud%2Fspooled-example-spriteforge/lists"}