render-create 0.1.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 +207 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +45 -0
- package/dist/commands/check.d.ts +8 -0
- package/dist/commands/check.js +96 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.js +1201 -0
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.js +126 -0
- package/dist/types.d.ts +246 -0
- package/dist/types.js +4 -0
- package/dist/utils.d.ts +53 -0
- package/dist/utils.js +142 -0
- package/package.json +65 -0
- package/templates/LINTING_SETUP.md +205 -0
- package/templates/README_TEMPLATE.md +68 -0
- package/templates/STYLE_GUIDE.md +241 -0
- package/templates/assets/favicon.png +0 -0
- package/templates/assets/favicon.svg +17 -0
- package/templates/biome.json +43 -0
- package/templates/cursor/rules/drizzle.mdc +165 -0
- package/templates/cursor/rules/fastify.mdc +132 -0
- package/templates/cursor/rules/general.mdc +112 -0
- package/templates/cursor/rules/nextjs.mdc +89 -0
- package/templates/cursor/rules/python.mdc +89 -0
- package/templates/cursor/rules/react.mdc +200 -0
- package/templates/cursor/rules/sqlalchemy.mdc +205 -0
- package/templates/cursor/rules/tailwind.mdc +139 -0
- package/templates/cursor/rules/typescript.mdc +112 -0
- package/templates/cursor/rules/vite.mdc +169 -0
- package/templates/cursor/rules/workflows.mdc +349 -0
- package/templates/docker-compose.example.yml +55 -0
- package/templates/drizzle/db-index.ts +15 -0
- package/templates/drizzle/drizzle.config.ts +10 -0
- package/templates/drizzle/schema.ts +12 -0
- package/templates/env.example +15 -0
- package/templates/fastapi/app/__init__.py +1 -0
- package/templates/fastapi/app/config.py +12 -0
- package/templates/fastapi/app/database.py +16 -0
- package/templates/fastapi/app/models.py +13 -0
- package/templates/fastapi/main.py +22 -0
- package/templates/fastify/index.ts +40 -0
- package/templates/github/CODEOWNERS +10 -0
- package/templates/github/ISSUE_TEMPLATE/bug_report.md +39 -0
- package/templates/github/ISSUE_TEMPLATE/feature_request.md +23 -0
- package/templates/github/PULL_REQUEST_TEMPLATE.md +25 -0
- package/templates/gitignore/node.gitignore +41 -0
- package/templates/gitignore/python.gitignore +49 -0
- package/templates/multi-api/README.md +60 -0
- package/templates/multi-api/gitignore +28 -0
- package/templates/multi-api/node-api/drizzle.config.ts +10 -0
- package/templates/multi-api/node-api/package-simple.json +13 -0
- package/templates/multi-api/node-api/package.json +16 -0
- package/templates/multi-api/node-api/src/db/index.ts +13 -0
- package/templates/multi-api/node-api/src/db/schema.ts +9 -0
- package/templates/multi-api/node-api/src/index-simple.ts +36 -0
- package/templates/multi-api/node-api/src/index.ts +50 -0
- package/templates/multi-api/node-api/tsconfig.json +20 -0
- package/templates/multi-api/python-api/app/__init__.py +1 -0
- package/templates/multi-api/python-api/app/config.py +12 -0
- package/templates/multi-api/python-api/app/database.py +16 -0
- package/templates/multi-api/python-api/app/models.py +13 -0
- package/templates/multi-api/python-api/main-simple.py +25 -0
- package/templates/multi-api/python-api/main.py +44 -0
- package/templates/multi-api/python-api/requirements-simple.txt +3 -0
- package/templates/multi-api/python-api/requirements.txt +8 -0
- package/templates/next/globals.css +126 -0
- package/templates/next/layout.tsx +34 -0
- package/templates/next/next.config.static.ts +10 -0
- package/templates/next/page-fullstack.tsx +120 -0
- package/templates/next/page.tsx +72 -0
- package/templates/presets.json +581 -0
- package/templates/ruff.toml +30 -0
- package/templates/tsconfig.base.json +17 -0
- package/templates/vite/index.css +127 -0
- package/templates/vite/vite.config.ts +7 -0
- package/templates/worker/py/cron.py +53 -0
- package/templates/worker/py/worker.py +95 -0
- package/templates/worker/py/workflow.py +73 -0
- package/templates/worker/ts/cron.ts +49 -0
- package/templates/worker/ts/worker.ts +84 -0
- package/templates/worker/ts/workflow.ts +67 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@plugin "@tailwindcss/typography";
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Brutalist Design Defaults
|
|
6
|
+
* - Black background, white text
|
|
7
|
+
* - No rounded corners
|
|
8
|
+
* - High contrast borders
|
|
9
|
+
* - No gradients or shadows
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
:root {
|
|
13
|
+
--background: #000000;
|
|
14
|
+
--foreground: #ffffff;
|
|
15
|
+
--border: #ffffff;
|
|
16
|
+
--muted: #888888;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
* {
|
|
20
|
+
border-radius: 0 !important;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
html {
|
|
24
|
+
color-scheme: dark;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
body {
|
|
28
|
+
background: var(--background);
|
|
29
|
+
color: var(--foreground);
|
|
30
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
31
|
+
margin: 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Links */
|
|
35
|
+
a {
|
|
36
|
+
color: var(--foreground);
|
|
37
|
+
text-decoration: underline;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
a:hover {
|
|
41
|
+
text-decoration: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Buttons - brutalist style */
|
|
45
|
+
button,
|
|
46
|
+
[role="button"],
|
|
47
|
+
input[type="submit"],
|
|
48
|
+
input[type="button"] {
|
|
49
|
+
background: var(--foreground);
|
|
50
|
+
color: var(--background);
|
|
51
|
+
border: 1px solid var(--foreground);
|
|
52
|
+
padding: 0.75rem 1.5rem;
|
|
53
|
+
font-weight: 600;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
transition: all 0.15s ease;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
button:hover,
|
|
59
|
+
[role="button"]:hover,
|
|
60
|
+
input[type="submit"]:hover,
|
|
61
|
+
input[type="button"]:hover {
|
|
62
|
+
background: transparent;
|
|
63
|
+
color: var(--foreground);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Secondary/outline buttons */
|
|
67
|
+
button.secondary,
|
|
68
|
+
.btn-secondary {
|
|
69
|
+
background: transparent;
|
|
70
|
+
color: var(--foreground);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
button.secondary:hover,
|
|
74
|
+
.btn-secondary:hover {
|
|
75
|
+
background: var(--foreground);
|
|
76
|
+
color: var(--background);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Inputs */
|
|
80
|
+
input,
|
|
81
|
+
textarea,
|
|
82
|
+
select {
|
|
83
|
+
background: transparent;
|
|
84
|
+
border: 1px solid var(--foreground);
|
|
85
|
+
color: var(--foreground);
|
|
86
|
+
padding: 0.75rem;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
input::placeholder,
|
|
90
|
+
textarea::placeholder {
|
|
91
|
+
color: var(--muted);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
input:focus,
|
|
95
|
+
textarea:focus,
|
|
96
|
+
select:focus {
|
|
97
|
+
outline: none;
|
|
98
|
+
border-color: var(--foreground);
|
|
99
|
+
box-shadow: 0 0 0 1px var(--foreground);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Cards/containers */
|
|
103
|
+
.card,
|
|
104
|
+
[class*="card"] {
|
|
105
|
+
background: transparent;
|
|
106
|
+
border: 1px solid var(--foreground);
|
|
107
|
+
padding: 1.5rem;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Typography - dark theme overrides */
|
|
111
|
+
.prose {
|
|
112
|
+
--tw-prose-body: var(--foreground);
|
|
113
|
+
--tw-prose-headings: var(--foreground);
|
|
114
|
+
--tw-prose-links: var(--foreground);
|
|
115
|
+
--tw-prose-bold: var(--foreground);
|
|
116
|
+
--tw-prose-counters: var(--muted);
|
|
117
|
+
--tw-prose-bullets: var(--muted);
|
|
118
|
+
--tw-prose-hr: var(--foreground);
|
|
119
|
+
--tw-prose-quotes: var(--foreground);
|
|
120
|
+
--tw-prose-quote-borders: var(--foreground);
|
|
121
|
+
--tw-prose-captions: var(--muted);
|
|
122
|
+
--tw-prose-code: var(--foreground);
|
|
123
|
+
--tw-prose-pre-code: var(--foreground);
|
|
124
|
+
--tw-prose-pre-bg: rgba(255, 255, 255, 0.05);
|
|
125
|
+
--tw-prose-th-borders: var(--foreground);
|
|
126
|
+
--tw-prose-td-borders: var(--muted);
|
|
127
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cron Job
|
|
3
|
+
|
|
4
|
+
Scheduled task that runs at specified intervals.
|
|
5
|
+
Executes once and exits.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
from dotenv import load_dotenv
|
|
13
|
+
|
|
14
|
+
load_dotenv()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run_cron_job() -> None:
|
|
18
|
+
"""Main cron job logic."""
|
|
19
|
+
start_time = time.time()
|
|
20
|
+
print(f"Cron job started at {datetime.now().isoformat()}")
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
# TODO: Add your scheduled task logic here
|
|
24
|
+
# Examples:
|
|
25
|
+
# - Clean up old records
|
|
26
|
+
# - Send scheduled notifications
|
|
27
|
+
# - Generate reports
|
|
28
|
+
# - Sync data between systems
|
|
29
|
+
|
|
30
|
+
print("Executing scheduled task...")
|
|
31
|
+
|
|
32
|
+
# Placeholder: simulate work
|
|
33
|
+
time.sleep(1)
|
|
34
|
+
|
|
35
|
+
print("Scheduled task completed successfully")
|
|
36
|
+
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"Cron job failed: {e}")
|
|
39
|
+
raise
|
|
40
|
+
|
|
41
|
+
finally:
|
|
42
|
+
duration = time.time() - start_time
|
|
43
|
+
print(f"Cron job finished in {duration:.2f}s")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
try:
|
|
48
|
+
run_cron_job()
|
|
49
|
+
print("Cron job exiting successfully")
|
|
50
|
+
sys.exit(0)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
print(f"Cron job exiting with error: {e}")
|
|
53
|
+
sys.exit(1)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Background Worker
|
|
3
|
+
|
|
4
|
+
Long-running background process for async tasks.
|
|
5
|
+
Runs continuously and processes work items.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import signal
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from dotenv import load_dotenv
|
|
16
|
+
|
|
17
|
+
load_dotenv()
|
|
18
|
+
|
|
19
|
+
POLL_INTERVAL = 5 # seconds
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class WorkItem:
|
|
24
|
+
id: str
|
|
25
|
+
data: Any
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Graceful shutdown flag
|
|
29
|
+
shutdown_requested = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def signal_handler(signum, frame):
|
|
33
|
+
"""Handle shutdown signals gracefully."""
|
|
34
|
+
global shutdown_requested
|
|
35
|
+
print(f"Received signal {signum}, shutting down gracefully...")
|
|
36
|
+
shutdown_requested = True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def process_work_item(item: WorkItem) -> None:
|
|
40
|
+
"""Process a single work item."""
|
|
41
|
+
print(f"Processing work item: {item.id}")
|
|
42
|
+
|
|
43
|
+
# TODO: Add your processing logic here
|
|
44
|
+
# Example: fetch from queue, process data, update database
|
|
45
|
+
|
|
46
|
+
time.sleep(0.1) # Simulate work
|
|
47
|
+
|
|
48
|
+
print(f"Completed work item: {item.id}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def fetch_work_items() -> list[WorkItem]:
|
|
52
|
+
"""Fetch pending work items."""
|
|
53
|
+
# TODO: Implement your work item fetching logic
|
|
54
|
+
# Example: poll a database, queue, or API
|
|
55
|
+
|
|
56
|
+
# Placeholder: return empty list (no work)
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def run_worker() -> None:
|
|
61
|
+
"""Main worker loop."""
|
|
62
|
+
print("Worker started")
|
|
63
|
+
print(f"Poll interval: {POLL_INTERVAL}s")
|
|
64
|
+
|
|
65
|
+
while not shutdown_requested:
|
|
66
|
+
try:
|
|
67
|
+
items = fetch_work_items()
|
|
68
|
+
|
|
69
|
+
for item in items:
|
|
70
|
+
if shutdown_requested:
|
|
71
|
+
break
|
|
72
|
+
process_work_item(item)
|
|
73
|
+
|
|
74
|
+
if not items:
|
|
75
|
+
# No work, wait before polling again
|
|
76
|
+
time.sleep(POLL_INTERVAL)
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f"Worker error: {e}")
|
|
80
|
+
# Wait before retrying on error
|
|
81
|
+
time.sleep(POLL_INTERVAL)
|
|
82
|
+
|
|
83
|
+
print("Worker shutdown complete")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
# Register signal handlers
|
|
88
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
89
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
run_worker()
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print(f"Fatal worker error: {e}")
|
|
95
|
+
sys.exit(1)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Render Workflow
|
|
3
|
+
|
|
4
|
+
Uses the Render SDK to define distributed tasks.
|
|
5
|
+
See: https://pypi.org/project/render-sdk/
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from dotenv import load_dotenv
|
|
12
|
+
from render_sdk import Retry, Workflows
|
|
13
|
+
|
|
14
|
+
load_dotenv()
|
|
15
|
+
|
|
16
|
+
logging.basicConfig(level=logging.INFO)
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# Retry configuration
|
|
20
|
+
retry = Retry(max_retries=3, wait_duration_ms=1000, backoff_scaling=1.5)
|
|
21
|
+
|
|
22
|
+
# Initialize Workflows app
|
|
23
|
+
app = Workflows(
|
|
24
|
+
default_retry=retry,
|
|
25
|
+
default_timeout=300,
|
|
26
|
+
auto_start=True,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.task
|
|
31
|
+
def square(a: int) -> int:
|
|
32
|
+
"""Square a number (subtask)."""
|
|
33
|
+
logger.info(f"Computing square of {a}")
|
|
34
|
+
return a * a
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.task
|
|
38
|
+
async def add_squares(a: int, b: int) -> int:
|
|
39
|
+
"""Add the squares of two numbers using subtasks."""
|
|
40
|
+
logger.info(f"Computing add_squares: {a}, {b}")
|
|
41
|
+
|
|
42
|
+
result1 = await square(a)
|
|
43
|
+
result2 = await square(b)
|
|
44
|
+
|
|
45
|
+
total = result1 + result2
|
|
46
|
+
logger.info(f"Total: {total}")
|
|
47
|
+
return total
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.task
|
|
51
|
+
async def process_data(data: str) -> str:
|
|
52
|
+
"""Process data."""
|
|
53
|
+
logger.info(f"Processing data: {data}")
|
|
54
|
+
|
|
55
|
+
# TODO: Add your data processing logic here
|
|
56
|
+
result = data.upper()
|
|
57
|
+
|
|
58
|
+
logger.info(f"Processed result: {result}")
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@app.task
|
|
63
|
+
async def fan_out(items: list[str]) -> list[str]:
|
|
64
|
+
"""Process multiple items in parallel using subtasks."""
|
|
65
|
+
logger.info(f"Processing {len(items)} items in parallel")
|
|
66
|
+
|
|
67
|
+
tasks = [process_data(item) for item in items]
|
|
68
|
+
results = await asyncio.gather(*tasks)
|
|
69
|
+
|
|
70
|
+
return list(results)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Task server starts automatically with auto_start=True
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron Job
|
|
3
|
+
*
|
|
4
|
+
* Scheduled task that runs at specified intervals.
|
|
5
|
+
* Executes once and exits.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import "dotenv/config";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Main cron job logic
|
|
12
|
+
*/
|
|
13
|
+
async function runCronJob(): Promise<void> {
|
|
14
|
+
const startTime = Date.now();
|
|
15
|
+
console.log(`Cron job started at ${new Date().toISOString()}`);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// TODO: Add your scheduled task logic here
|
|
19
|
+
// Examples:
|
|
20
|
+
// - Clean up old records
|
|
21
|
+
// - Send scheduled notifications
|
|
22
|
+
// - Generate reports
|
|
23
|
+
// - Sync data between systems
|
|
24
|
+
|
|
25
|
+
console.log("Executing scheduled task...");
|
|
26
|
+
|
|
27
|
+
// Placeholder: simulate work
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
29
|
+
|
|
30
|
+
console.log("Scheduled task completed successfully");
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error("Cron job failed:", error);
|
|
33
|
+
throw error;
|
|
34
|
+
} finally {
|
|
35
|
+
const duration = Date.now() - startTime;
|
|
36
|
+
console.log(`Cron job finished in ${duration}ms`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Run the cron job
|
|
41
|
+
runCronJob()
|
|
42
|
+
.then(() => {
|
|
43
|
+
console.log("Cron job exiting successfully");
|
|
44
|
+
process.exit(0);
|
|
45
|
+
})
|
|
46
|
+
.catch((error) => {
|
|
47
|
+
console.error("Cron job exiting with error:", error);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background Worker
|
|
3
|
+
*
|
|
4
|
+
* Long-running background process for async tasks.
|
|
5
|
+
* Runs continuously and processes work items.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import "dotenv/config";
|
|
9
|
+
|
|
10
|
+
const POLL_INTERVAL = 5000; // 5 seconds
|
|
11
|
+
|
|
12
|
+
interface WorkItem {
|
|
13
|
+
id: string;
|
|
14
|
+
data: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Process a single work item
|
|
19
|
+
*/
|
|
20
|
+
async function processWorkItem(item: WorkItem): Promise<void> {
|
|
21
|
+
console.log(`Processing work item: ${item.id}`);
|
|
22
|
+
|
|
23
|
+
// TODO: Add your processing logic here
|
|
24
|
+
// Example: fetch from queue, process data, update database
|
|
25
|
+
|
|
26
|
+
await new Promise((resolve) => setTimeout(resolve, 100)); // Simulate work
|
|
27
|
+
|
|
28
|
+
console.log(`Completed work item: ${item.id}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Fetch pending work items
|
|
33
|
+
*/
|
|
34
|
+
async function fetchWorkItems(): Promise<WorkItem[]> {
|
|
35
|
+
// TODO: Implement your work item fetching logic
|
|
36
|
+
// Example: poll a database, queue, or API
|
|
37
|
+
|
|
38
|
+
// Placeholder: return empty array (no work)
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Main worker loop
|
|
44
|
+
*/
|
|
45
|
+
async function runWorker(): Promise<void> {
|
|
46
|
+
console.log("Worker started");
|
|
47
|
+
console.log(`Poll interval: ${POLL_INTERVAL}ms`);
|
|
48
|
+
|
|
49
|
+
while (true) {
|
|
50
|
+
try {
|
|
51
|
+
const items = await fetchWorkItems();
|
|
52
|
+
|
|
53
|
+
for (const item of items) {
|
|
54
|
+
await processWorkItem(item);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (items.length === 0) {
|
|
58
|
+
// No work, wait before polling again
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error("Worker error:", error);
|
|
63
|
+
// Wait before retrying on error
|
|
64
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Handle graceful shutdown
|
|
70
|
+
process.on("SIGTERM", () => {
|
|
71
|
+
console.log("SIGTERM received, shutting down gracefully...");
|
|
72
|
+
process.exit(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
process.on("SIGINT", () => {
|
|
76
|
+
console.log("SIGINT received, shutting down gracefully...");
|
|
77
|
+
process.exit(0);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Start the worker
|
|
81
|
+
runWorker().catch((error) => {
|
|
82
|
+
console.error("Fatal worker error:", error);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render Workflow
|
|
3
|
+
*
|
|
4
|
+
* Uses the Render SDK to define distributed tasks.
|
|
5
|
+
* See: https://github.com/render-oss/sdk
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import "dotenv/config";
|
|
9
|
+
import { task, type Retry } from "@renderinc/sdk/workflows";
|
|
10
|
+
|
|
11
|
+
// Retry configuration
|
|
12
|
+
const retry: Retry = {
|
|
13
|
+
maxRetries: 3,
|
|
14
|
+
waitDurationMs: 1000,
|
|
15
|
+
factor: 1.5,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Simple task that squares a number (subtask)
|
|
20
|
+
*/
|
|
21
|
+
const square = task({ name: "square" }, function square(a: number): number {
|
|
22
|
+
console.log(`Calculating square of ${a}`);
|
|
23
|
+
return a * a;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Async task that adds two squared numbers
|
|
28
|
+
*/
|
|
29
|
+
task(
|
|
30
|
+
{
|
|
31
|
+
name: "addSquares",
|
|
32
|
+
timeoutSeconds: 300,
|
|
33
|
+
retry,
|
|
34
|
+
},
|
|
35
|
+
async function addSquares(a: number, b: number): Promise<number> {
|
|
36
|
+
console.log(`Adding squares of ${a} and ${b}`);
|
|
37
|
+
|
|
38
|
+
const result1 = await square(a);
|
|
39
|
+
const result2 = await square(b);
|
|
40
|
+
|
|
41
|
+
const sum = result1 + result2;
|
|
42
|
+
console.log(`Result: ${result1} + ${result2} = ${sum}`);
|
|
43
|
+
return sum;
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Task that processes data
|
|
49
|
+
*/
|
|
50
|
+
task(
|
|
51
|
+
{
|
|
52
|
+
name: "processData",
|
|
53
|
+
timeoutSeconds: 300,
|
|
54
|
+
retry,
|
|
55
|
+
},
|
|
56
|
+
async function processData(data: string): Promise<string> {
|
|
57
|
+
console.log(`Processing data: ${data}`);
|
|
58
|
+
|
|
59
|
+
// TODO: Add your data processing logic here
|
|
60
|
+
const result = data.toUpperCase();
|
|
61
|
+
|
|
62
|
+
console.log(`Processed result: ${result}`);
|
|
63
|
+
return result;
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Task server starts automatically when RENDER_SDK_SOCKET_PATH is set
|