cloudflare-d1 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.
@@ -0,0 +1,128 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo "🚀 Cloudflare D1 + Roda + Containers Setup"
5
+ echo "=========================================="
6
+ echo ""
7
+
8
+ # Check prerequisites
9
+ echo "Checking prerequisites..."
10
+
11
+ if ! command -v wrangler &> /dev/null; then
12
+ echo "❌ Wrangler CLI not found. Install with: npm install -g wrangler"
13
+ exit 1
14
+ fi
15
+ echo "✅ Wrangler CLI found"
16
+
17
+ if ! command -v docker &> /dev/null; then
18
+ echo "❌ Docker not found. Please install Docker Desktop"
19
+ exit 1
20
+ fi
21
+ echo "✅ Docker found"
22
+
23
+ if ! docker info &> /dev/null; then
24
+ echo "❌ Docker daemon not running. Please start Docker Desktop"
25
+ exit 1
26
+ fi
27
+ echo "✅ Docker daemon running"
28
+
29
+ if ! command -v bundle &> /dev/null; then
30
+ echo "❌ Bundler not found. Install with: gem install bundler"
31
+ exit 1
32
+ fi
33
+ echo "✅ Bundler found"
34
+
35
+ echo ""
36
+ echo "Prerequisites check complete!"
37
+ echo ""
38
+
39
+ # Install Ruby dependencies
40
+ echo "📦 Installing Ruby dependencies..."
41
+ bundle install
42
+ echo "✅ Dependencies installed"
43
+ echo ""
44
+
45
+ # Create D1 database
46
+ echo "🗄️ Creating D1 database..."
47
+ echo "Running: wrangler d1 create roda-api-db"
48
+ echo ""
49
+
50
+ DB_OUTPUT=$(wrangler d1 create roda-api-db 2>&1 || true)
51
+ echo "$DB_OUTPUT"
52
+
53
+ if echo "$DB_OUTPUT" | grep -q "already exists"; then
54
+ echo "Database already exists, continuing..."
55
+ DATABASE_ID=$(echo "$DB_OUTPUT" | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | head -1)
56
+ elif echo "$DB_OUTPUT" | grep -q "database_id"; then
57
+ DATABASE_ID=$(echo "$DB_OUTPUT" | grep -oE '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' | head -1)
58
+ else
59
+ echo "⚠️ Could not automatically extract database ID"
60
+ echo "Please create a database manually with: wrangler d1 create roda-api-db"
61
+ exit 1
62
+ fi
63
+
64
+ echo ""
65
+ echo "✅ Database created/found: $DATABASE_ID"
66
+ echo ""
67
+
68
+ # Get account ID
69
+ echo "📋 Getting your Cloudflare account ID..."
70
+ ACCOUNT_ID=$(wrangler whoami 2>&1 | grep -oE 'Account ID: [0-9a-f]+' | cut -d' ' -f3)
71
+
72
+ if [ -z "$ACCOUNT_ID" ]; then
73
+ echo "⚠️ Could not automatically get account ID"
74
+ echo "Please run: wrangler whoami"
75
+ read -p "Enter your Account ID: " ACCOUNT_ID
76
+ fi
77
+
78
+ echo "✅ Account ID: $ACCOUNT_ID"
79
+ echo ""
80
+
81
+ # Set secrets
82
+ echo "🔐 Setting secrets..."
83
+ echo ""
84
+
85
+ echo "Setting CLOUDFLARE_ACCOUNT_ID..."
86
+ echo "$ACCOUNT_ID" | wrangler secret put CLOUDFLARE_ACCOUNT_ID
87
+
88
+ echo ""
89
+ echo "Now you need to set your API token."
90
+ echo "Get it from: https://dash.cloudflare.com/profile/api-tokens"
91
+ echo "Create a token with 'Edit Cloudflare Workers' permissions"
92
+ echo ""
93
+ read -p "Enter your API Token: " API_TOKEN
94
+ echo "$API_TOKEN" | wrangler secret put CLOUDFLARE_API_TOKEN
95
+
96
+ echo ""
97
+ echo "Setting DATABASE_ID..."
98
+ echo "$DATABASE_ID" | wrangler secret put DATABASE_ID
99
+
100
+ echo ""
101
+ echo "✅ Secrets configured"
102
+ echo ""
103
+
104
+ # Deploy
105
+ echo "🚀 Deploying to Cloudflare..."
106
+ echo ""
107
+ echo "This will take a few minutes on first deployment..."
108
+ echo ""
109
+
110
+ wrangler deploy
111
+
112
+ echo ""
113
+ echo "============================================"
114
+ echo "✅ Deployment complete!"
115
+ echo ""
116
+ echo "Your API is available at:"
117
+ echo "https://roda-d1-api.your-subdomain.workers.dev"
118
+ echo ""
119
+ echo "Get your URL with: wrangler deployments list"
120
+ echo ""
121
+ echo "Test your API:"
122
+ echo " curl https://your-url/health"
123
+ echo " curl https://your-url/users"
124
+ echo ""
125
+ echo "View logs:"
126
+ echo " wrangler tail"
127
+ echo ""
128
+ echo "============================================"
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Cloudflare Worker that routes requests to Roda container instances
3
+ */
4
+
5
+ /**
6
+ * Container class configuration
7
+ * Extends the Container base class to configure the Ruby/Roda application
8
+ */
9
+ export class RodaContainer extends Container {
10
+ // Port the container application listens on
11
+ defaultPort = 8080;
12
+
13
+ // Time to keep container alive after inactivity
14
+ sleepAfter = "10m";
15
+
16
+ // Static environment variables (set at class level)
17
+ // For dynamic values, override in fetch() using startAndWaitForPorts()
18
+ envVars = {
19
+ PORT: "8080",
20
+ RACK_ENV: "production",
21
+ };
22
+
23
+ // Lifecycle hooks
24
+ override onStart() {
25
+ console.log("[RodaContainer] Container starting...");
26
+ }
27
+
28
+ override onStop() {
29
+ console.log("[RodaContainer] Container stopping...");
30
+ }
31
+
32
+ override onError(error) {
33
+ console.error("[RodaContainer] Error:", error);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Main Worker export
39
+ * Handles incoming requests and routes them to container instances
40
+ */
41
+ export default {
42
+ async fetch(request, env, ctx) {
43
+ const url = new URL(request.url);
44
+
45
+ // Health check endpoint (runs on Worker, not container)
46
+ if (url.pathname === "/health") {
47
+ return new Response(
48
+ JSON.stringify({
49
+ status: "healthy",
50
+ service: "roda-d1-api",
51
+ timestamp: new Date().toISOString(),
52
+ }),
53
+ {
54
+ headers: { "content-type": "application/json" },
55
+ }
56
+ );
57
+ }
58
+
59
+ // Get container instance
60
+ // Using getByName with a fixed ID creates a single stateful instance
61
+ // For load balancing across multiple instances, use getById with random IDs
62
+ const containerStub = env.RODA_CONTAINER.getByName("main");
63
+
64
+ // Start container with runtime environment variables (secrets)
65
+ await containerStub.startAndWaitForPorts({
66
+ startOptions: {
67
+ envVars: {
68
+ CLOUDFLARE_ACCOUNT_ID: env.CLOUDFLARE_ACCOUNT_ID,
69
+ CLOUDFLARE_API_TOKEN: env.CLOUDFLARE_API_TOKEN,
70
+ DATABASE_ID: env.DATABASE_ID,
71
+ },
72
+ },
73
+ });
74
+
75
+ try {
76
+ // Forward the request to the container
77
+ const response = await containerStub.fetch(request);
78
+
79
+ // Add custom headers
80
+ const headers = new Headers(response.headers);
81
+ headers.set("X-Powered-By", "Cloudflare-Containers");
82
+
83
+ return new Response(response.body, {
84
+ status: response.status,
85
+ statusText: response.statusText,
86
+ headers: headers,
87
+ });
88
+ } catch (error) {
89
+ console.error("Container request failed:", error);
90
+
91
+ return new Response(
92
+ JSON.stringify({
93
+ error: "Container unavailable",
94
+ message: error.message,
95
+ }),
96
+ {
97
+ status: 503,
98
+ headers: { "content-type": "application/json" },
99
+ }
100
+ );
101
+ }
102
+ },
103
+ };
104
+
105
+ /**
106
+ * Alternative routing strategies:
107
+ *
108
+ * 1. Load balanced (stateless):
109
+ * const containerId = Math.floor(Math.random() * 3);
110
+ * const containerStub = env.RODA_CONTAINER.getById(containerId.toString());
111
+ *
112
+ * 2. Session-based (sticky):
113
+ * const sessionId = request.headers.get("X-Session-ID") || "default";
114
+ * const containerStub = env.RODA_CONTAINER.getByName(sessionId);
115
+ *
116
+ * 3. Path-based routing:
117
+ * const pathHash = hashCode(url.pathname);
118
+ * const containerStub = env.RODA_CONTAINER.getById(pathHash.toString());
119
+ */
@@ -0,0 +1,68 @@
1
+ {
2
+ // Application name - must be unique across Cloudflare
3
+ "name": "roda-d1-api",
4
+
5
+ // Compatibility date for Workers runtime
6
+ "compatibility_date": "2024-01-01",
7
+
8
+ // Main Worker entrypoint
9
+ "main": "worker.js",
10
+
11
+ // Container configuration
12
+ "containers": [
13
+ {
14
+ // Unique identifier for this container class
15
+ "class_name": "RodaContainer",
16
+
17
+ // Path to Dockerfile (wrangler will build and push)
18
+ "image": "./Dockerfile",
19
+
20
+ // Maximum number of concurrent container instances
21
+ "max_instances": 10,
22
+
23
+ // Instance type: lite, basic, standard-1, standard-2, standard-3, standard-4
24
+ "instance_type": "lite",
25
+ },
26
+ ],
27
+
28
+ // Durable Objects bindings
29
+ "durable_objects": {
30
+ "bindings": [
31
+ {
32
+ // Binding name used in Worker code
33
+ "name": "RODA_CONTAINER",
34
+
35
+ // Must match container class_name
36
+ "class_name": "RodaContainer",
37
+ },
38
+ ],
39
+ },
40
+
41
+ // Migrations to register Durable Objects
42
+ "migrations": [
43
+ {
44
+ "tag": "v1",
45
+ // Must use new_sqlite_classes for container-backed Durable Objects
46
+ "new_sqlite_classes": ["RodaContainer"],
47
+ },
48
+ ],
49
+
50
+ // Environment variables (non-sensitive)
51
+ "vars": {
52
+ "PORT": "8080",
53
+ },
54
+
55
+ // Secrets (set with: wrangler secret put <NAME>)
56
+ // Run these commands:
57
+ // wrangler secret put CLOUDFLARE_ACCOUNT_ID
58
+ // wrangler secret put CLOUDFLARE_API_TOKEN
59
+ // wrangler secret put DATABASE_ID
60
+
61
+ // Optional: Custom routes
62
+ "routes": [
63
+ {
64
+ "pattern": "roda-d1-api.your-domain.com/*",
65
+ "custom_domain": true,
66
+ },
67
+ ],
68
+ }