@tamyla/clodo-framework 4.4.0 → 4.5.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/CHANGELOG.md +2 -1844
- package/README.md +44 -18
- package/dist/cli/commands/add.js +325 -0
- package/dist/config/service-schema-config.js +98 -5
- package/dist/index.js +22 -3
- package/dist/middleware/Composer.js +2 -1
- package/dist/middleware/factories.js +445 -0
- package/dist/middleware/index.js +4 -1
- package/dist/modules/ModuleManager.js +6 -2
- package/dist/routing/EnhancedRouter.js +248 -44
- package/dist/routing/RequestContext.js +393 -0
- package/dist/schema/SchemaManager.js +6 -2
- package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +79 -223
- package/dist/service-management/generators/code/WorkerIndexGenerator.js +241 -98
- package/dist/service-management/generators/config/WranglerTomlGenerator.js +130 -89
- package/dist/simple-api.js +4 -4
- package/dist/utilities/index.js +134 -1
- package/dist/utils/config/environment-var-normalizer.js +233 -0
- package/dist/validation/environmentGuard.js +172 -0
- package/docs/CHANGELOG.md +1877 -0
- package/docs/api-reference.md +153 -0
- package/package.json +4 -1
- package/scripts/repro-clodo.js +123 -0
- package/templates/ai-worker/package.json +19 -0
- package/templates/ai-worker/src/index.js +160 -0
- package/templates/cron-worker/package.json +19 -0
- package/templates/cron-worker/src/index.js +211 -0
- package/templates/edge-proxy/package.json +18 -0
- package/templates/edge-proxy/src/index.js +150 -0
- package/templates/minimal/package.json +17 -0
- package/templates/minimal/src/index.js +40 -0
- package/templates/queue-processor/package.json +19 -0
- package/templates/queue-processor/src/index.js +213 -0
- package/templates/rest-api/.dev.vars +2 -0
- package/templates/rest-api/package.json +19 -0
- package/templates/rest-api/src/index.js +124 -0
|
@@ -59,121 +59,264 @@ export class WorkerIndexGenerator extends BaseGenerator {
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Build the complete worker index.js content
|
|
62
|
+
*
|
|
63
|
+
* v4.4.1+: Generates code that imports directly from @tamyla/clodo-framework
|
|
64
|
+
* using EnhancedRouter, composeMiddleware, createEnvironmentGuard, and
|
|
65
|
+
* middleware factories — NOT the old MiddlewareRegistry/Composer pattern.
|
|
62
66
|
*/
|
|
63
67
|
_buildWorkerIndex(coreInputs, confirmedValues) {
|
|
68
|
+
const features = confirmedValues.features || {};
|
|
69
|
+
const serviceName = coreInputs.serviceName;
|
|
70
|
+
const serviceType = coreInputs.serviceType;
|
|
71
|
+
|
|
72
|
+
// Determine which env bindings are required based on features
|
|
73
|
+
const requiredBindings = [];
|
|
74
|
+
const optionalBindings = ['DEBUG'];
|
|
75
|
+
if (features.d1 || features.database) requiredBindings.push('DB');
|
|
76
|
+
if (features.kv || features.upstash) requiredBindings.push('KV');
|
|
77
|
+
if (features.r2) requiredBindings.push('R2_STORAGE');
|
|
78
|
+
if (features.ai) requiredBindings.push('AI');
|
|
79
|
+
if (features.vectorize) requiredBindings.push('VECTORIZE_INDEX');
|
|
80
|
+
if (features.queues) requiredBindings.push('QUEUE');
|
|
81
|
+
if (features.email) requiredBindings.push('EMAIL');
|
|
82
|
+
if (features.hyperdrive) requiredBindings.push('HYPERDRIVE');
|
|
83
|
+
if (features.durableObject || features.durableObjects) requiredBindings.push('DURABLE_OBJECT');
|
|
84
|
+
|
|
85
|
+
// Build framework imports
|
|
86
|
+
const frameworkImports = ['createEnhancedRouter', 'createCorsMiddleware', 'createErrorHandler', 'createLogger', 'composeMiddleware', 'createEnvironmentGuard'];
|
|
87
|
+
if (features.rateLimiting) frameworkImports.push('createRateLimitGuard');
|
|
88
|
+
|
|
89
|
+
// Build utility imports based on features
|
|
90
|
+
const utilityImports = [];
|
|
91
|
+
if (features.kv || features.upstash) utilityImports.push('getKV', 'putKV', 'listKV', 'deleteKV');
|
|
92
|
+
if (features.r2) utilityImports.push('putR2Object', 'getR2Object');
|
|
93
|
+
if (features.ai) utilityImports.push('runAIModel', 'streamAIResponse');
|
|
94
|
+
|
|
95
|
+
// Build route stubs based on service type
|
|
96
|
+
const routeStubs = this._buildRouteStubs(coreInputs, confirmedValues, features);
|
|
97
|
+
|
|
98
|
+
// Build queue handler if queues feature is enabled
|
|
99
|
+
const queueHandler = features.queues ? this._buildQueueHandler(serviceName) : '';
|
|
100
|
+
|
|
101
|
+
// Build cron handler if cron feature is enabled
|
|
102
|
+
const cronHandler = features.cron ? this._buildCronHandler(serviceName) : '';
|
|
64
103
|
return `/**
|
|
65
104
|
* ${confirmedValues.displayName} - Cloudflare Worker
|
|
66
105
|
*
|
|
67
|
-
* Generated by Clodo Framework
|
|
68
|
-
* Service Type: ${
|
|
106
|
+
* Generated by Clodo Framework v4.4.1
|
|
107
|
+
* Service Type: ${serviceType}
|
|
69
108
|
*/
|
|
70
109
|
|
|
71
|
-
import {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
110
|
+
import {
|
|
111
|
+
${frameworkImports.join(',\n ')}
|
|
112
|
+
} from '@tamyla/clodo-framework';
|
|
113
|
+
${utilityImports.length > 0 ? `\nimport { ${utilityImports.join(', ')} } from '@tamyla/clodo-framework/utilities';\n` : ''}
|
|
114
|
+
// ── Environment validation ────────────────────────────────────────────
|
|
115
|
+
const envGuard = createEnvironmentGuard({
|
|
116
|
+
required: [${requiredBindings.map(b => `'${b}'`).join(', ')}],
|
|
117
|
+
optional: [${optionalBindings.map(b => `'${b}'`).join(', ')}]
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ── Middleware stack ──────────────────────────────────────────────────
|
|
121
|
+
const middleware = composeMiddleware(
|
|
122
|
+
createCorsMiddleware({ origins: ['*'] }),
|
|
123
|
+
createLogger({ prefix: '${serviceName}', level: 'info' }),${features.rateLimiting ? `\n createRateLimitGuard({ maxRequests: 100, windowMs: 60000 }),` : ''}
|
|
124
|
+
createErrorHandler({ includeStack: false })
|
|
125
|
+
);
|
|
75
126
|
|
|
127
|
+
// ── Router ────────────────────────────────────────────────────────────
|
|
128
|
+
const router = createEnhancedRouter(${features.d1 || features.database ? 'null /* D1 injected per-request via env.DB */' : 'null'}, {
|
|
129
|
+
autoRegisterGenericRoutes: false
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Health check
|
|
133
|
+
router.get('${confirmedValues.healthCheckPath || '/health'}', (c) => {
|
|
134
|
+
return c.json({
|
|
135
|
+
status: 'healthy',
|
|
136
|
+
service: '${serviceName}',
|
|
137
|
+
version: '${confirmedValues.version}',
|
|
138
|
+
timestamp: new Date().toISOString()
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
${routeStubs}
|
|
143
|
+
|
|
144
|
+
// ── Worker entry point ────────────────────────────────────────────────
|
|
76
145
|
export default {
|
|
77
146
|
async fetch(request, env, ctx) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const serviceConfig = domains['${coreInputs.serviceName}'];
|
|
147
|
+
envGuard.check(env);
|
|
148
|
+
const url = new URL(request.url);
|
|
81
149
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
150
|
+
return middleware.execute(request, () =>
|
|
151
|
+
router.handleRequest(request.method, url.pathname, request, env, ctx)
|
|
152
|
+
);
|
|
153
|
+
}${queueHandler}${cronHandler}
|
|
154
|
+
};
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
87
157
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Generate route stubs based on service type and features
|
|
160
|
+
*/
|
|
161
|
+
_buildRouteStubs(coreInputs, confirmedValues, features) {
|
|
162
|
+
const apiBase = confirmedValues.apiBasePath || '/api/v1';
|
|
163
|
+
const serviceType = coreInputs.serviceType;
|
|
91
164
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (mod?.registerMiddleware) {
|
|
96
|
-
// New-style registration helper
|
|
97
|
-
mod.registerMiddleware(MiddlewareRegistry, serviceConfig.name);
|
|
98
|
-
serviceMiddlewareInstance = MiddlewareRegistry.get(serviceConfig.name);
|
|
99
|
-
} else if (mod?.default) {
|
|
100
|
-
const def = mod.default;
|
|
101
|
-
// If the default is a class (constructor), instantiate and register
|
|
102
|
-
if (typeof def === 'function' && def.prototype) {
|
|
103
|
-
try {
|
|
104
|
-
const instance = new def();
|
|
105
|
-
MiddlewareRegistry.register(serviceConfig.name, instance);
|
|
106
|
-
serviceMiddlewareInstance = instance;
|
|
107
|
-
} catch (e) {
|
|
108
|
-
// ignore instantiation errors
|
|
109
|
-
}
|
|
110
|
-
} else if (typeof def === 'function') {
|
|
111
|
-
// Legacy factory exported as default
|
|
112
|
-
legacyFactory = def;
|
|
113
|
-
}
|
|
114
|
-
} else if (mod?.createServiceMiddleware) {
|
|
115
|
-
legacyFactory = mod.createServiceMiddleware;
|
|
116
|
-
}
|
|
117
|
-
} catch (e) {
|
|
118
|
-
// No service-specific middleware found - continue with shared only
|
|
119
|
-
}
|
|
165
|
+
// REST API routes with KV
|
|
166
|
+
if ((features.kv || features.upstash) && !features.d1) {
|
|
167
|
+
return `// ── API Routes (KV-backed) ─────────────────────────────────────────────
|
|
120
168
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
postprocess: async (res) => {
|
|
136
|
-
if (legacyInstance && typeof legacyInstance.processResponse === 'function') {
|
|
137
|
-
const r = await legacyInstance.processResponse(res);
|
|
138
|
-
return r instanceof Response ? r : res;
|
|
139
|
-
}
|
|
140
|
-
return res;
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
chain = MiddlewareComposer.compose(...sharedMiddlewares, adapter);
|
|
145
|
-
} else {
|
|
146
|
-
const svcMw = serviceMiddlewareInstance || MiddlewareRegistry.get(serviceConfig.name);
|
|
147
|
-
chain = MiddlewareComposer.compose(...sharedMiddlewares, svcMw);
|
|
148
|
-
}
|
|
169
|
+
router.get('${apiBase}/items', async (c) => {
|
|
170
|
+
const list = await listKV(c.env.KV, { prefix: 'item:' });
|
|
171
|
+
const items = await Promise.all(
|
|
172
|
+
(list.keys || []).map(async (k) => getKV(c.env.KV, k.name, { type: 'json' }))
|
|
173
|
+
);
|
|
174
|
+
return c.json({ items: items.filter(Boolean), count: items.length });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
router.get('${apiBase}/items/:id', async (c) => {
|
|
178
|
+
const id = c.req.param('id');
|
|
179
|
+
const item = await getKV(c.env.KV, \`item:\${id}\`, { type: 'json' });
|
|
180
|
+
if (!item) return c.json({ error: 'Not found' }, 404);
|
|
181
|
+
return c.json(item);
|
|
182
|
+
});
|
|
149
183
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
184
|
+
router.post('${apiBase}/items', async (c) => {
|
|
185
|
+
const body = await c.req.json();
|
|
186
|
+
const id = crypto.randomUUID();
|
|
187
|
+
const item = { id, ...body, createdAt: new Date().toISOString() };
|
|
188
|
+
await putKV(c.env.KV, \`item:\${id}\`, JSON.stringify(item));
|
|
189
|
+
return c.json(item, 201);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
router.put('${apiBase}/items/:id', async (c) => {
|
|
193
|
+
const id = c.req.param('id');
|
|
194
|
+
const existing = await getKV(c.env.KV, \`item:\${id}\`, { type: 'json' });
|
|
195
|
+
if (!existing) return c.json({ error: 'Not found' }, 404);
|
|
196
|
+
const body = await c.req.json();
|
|
197
|
+
const updated = { ...existing, ...body, id, updatedAt: new Date().toISOString() };
|
|
198
|
+
await putKV(c.env.KV, \`item:\${id}\`, JSON.stringify(updated));
|
|
199
|
+
return c.json(updated);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
router.delete('${apiBase}/items/:id', async (c) => {
|
|
203
|
+
const id = c.req.param('id');
|
|
204
|
+
await deleteKV(c.env.KV, \`item:\${id}\`);
|
|
205
|
+
return c.json({ deleted: true, id });
|
|
206
|
+
});`;
|
|
173
207
|
}
|
|
208
|
+
|
|
209
|
+
// D1-backed routes
|
|
210
|
+
if (features.d1 || features.database) {
|
|
211
|
+
return `// ── API Routes (D1-backed) ─────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
router.get('${apiBase}/items', async (c) => {
|
|
214
|
+
const { results } = await c.env.DB.prepare('SELECT * FROM items ORDER BY created_at DESC LIMIT 50').all();
|
|
215
|
+
return c.json({ items: results, count: results.length });
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
router.get('${apiBase}/items/:id', async (c) => {
|
|
219
|
+
const id = c.req.param('id');
|
|
220
|
+
const item = await c.env.DB.prepare('SELECT * FROM items WHERE id = ?').bind(id).first();
|
|
221
|
+
if (!item) return c.json({ error: 'Not found' }, 404);
|
|
222
|
+
return c.json(item);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
router.post('${apiBase}/items', async (c) => {
|
|
226
|
+
const body = await c.req.json();
|
|
227
|
+
const id = crypto.randomUUID();
|
|
228
|
+
await c.env.DB.prepare('INSERT INTO items (id, name, data, created_at) VALUES (?, ?, ?, ?)')
|
|
229
|
+
.bind(id, body.name, JSON.stringify(body.data || {}), new Date().toISOString())
|
|
230
|
+
.run();
|
|
231
|
+
return c.json({ id, ...body, createdAt: new Date().toISOString() }, 201);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
router.delete('${apiBase}/items/:id', async (c) => {
|
|
235
|
+
const id = c.req.param('id');
|
|
236
|
+
await c.env.DB.prepare('DELETE FROM items WHERE id = ?').bind(id).run();
|
|
237
|
+
return c.json({ deleted: true, id });
|
|
238
|
+
});`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// AI worker routes
|
|
242
|
+
if (features.ai) {
|
|
243
|
+
return `// ── AI Routes ─────────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
router.post('${apiBase}/chat', async (c) => {
|
|
246
|
+
const { messages, model } = await c.req.json();
|
|
247
|
+
const result = await runAIModel(c.env.AI, model || '@cf/meta/llama-3.1-8b-instruct', {
|
|
248
|
+
messages: messages || []
|
|
249
|
+
});
|
|
250
|
+
return c.json({ response: result.response });
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
router.post('${apiBase}/chat/stream', async (c) => {
|
|
254
|
+
const { messages, model } = await c.req.json();
|
|
255
|
+
const stream = await runAIModel(c.env.AI, model || '@cf/meta/llama-3.1-8b-instruct', {
|
|
256
|
+
messages: messages || [], stream: true
|
|
257
|
+
});
|
|
258
|
+
return streamAIResponse(stream);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
router.get('${apiBase}/models', (c) => {
|
|
262
|
+
return c.json({
|
|
263
|
+
models: [
|
|
264
|
+
{ name: 'llama-3.1-8b-instruct', type: 'chat' },
|
|
265
|
+
{ name: 'bge-base-en-v1.5', type: 'embeddings' }
|
|
266
|
+
]
|
|
267
|
+
});
|
|
268
|
+
});`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Default minimal routes
|
|
272
|
+
return `// ── API Routes ─────────────────────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
router.get('${apiBase}/status', (c) => {
|
|
275
|
+
return c.json({
|
|
276
|
+
service: '${coreInputs.serviceName}',
|
|
277
|
+
status: 'running',
|
|
278
|
+
timestamp: new Date().toISOString()
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Add your routes here:
|
|
283
|
+
// router.get('${apiBase}/items', async (c) => { ... });
|
|
284
|
+
// router.post('${apiBase}/items', async (c) => { ... });`;
|
|
174
285
|
}
|
|
175
|
-
|
|
176
|
-
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Build queue consumer handler
|
|
289
|
+
*/
|
|
290
|
+
_buildQueueHandler(serviceName) {
|
|
291
|
+
return `,
|
|
292
|
+
|
|
293
|
+
// Queue consumer handler
|
|
294
|
+
async queue(batch, env, ctx) {
|
|
295
|
+
console.log(\`[${serviceName}] Processing batch of \${batch.messages.length} messages\`);
|
|
296
|
+
for (const message of batch.messages) {
|
|
297
|
+
try {
|
|
298
|
+
console.log('Processing:', message.body);
|
|
299
|
+
// Add your message processing logic here
|
|
300
|
+
message.ack();
|
|
301
|
+
} catch (error) {
|
|
302
|
+
console.error('Message processing failed:', error.message);
|
|
303
|
+
message.retry();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Build cron/scheduled handler
|
|
311
|
+
*/
|
|
312
|
+
_buildCronHandler(serviceName) {
|
|
313
|
+
return `,
|
|
314
|
+
|
|
315
|
+
// Scheduled (cron) handler
|
|
316
|
+
async scheduled(event, env, ctx) {
|
|
317
|
+
console.log(\`[${serviceName}] Cron triggered: \${event.cron} at \${new Date(event.scheduledTime).toISOString()}\`);
|
|
318
|
+
// Add your scheduled job logic here
|
|
319
|
+
}`;
|
|
177
320
|
}
|
|
178
321
|
|
|
179
322
|
/**
|
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
import { BaseGenerator } from '../BaseGenerator.js';
|
|
2
|
-
import { WranglerCompatibilityDetector } from '../../../lib/shared/utils/wrangler-compatibility.js';
|
|
3
2
|
import { join } from 'path';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
* WranglerTomlGenerator
|
|
5
|
+
* WranglerTomlGenerator (v4.4.1+)
|
|
7
6
|
*
|
|
8
7
|
* Generates wrangler.toml configuration files for Cloudflare Workers.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
18
|
-
* Dependencies:
|
|
19
|
-
* - RouteGenerator (for routes configuration)
|
|
20
|
-
* - SiteConfigGenerator (for Workers Sites configuration)
|
|
9
|
+
* v4.4.1 changes:
|
|
10
|
+
* - Supports ALL modern Cloudflare bindings: D1, KV, R2, AI, Vectorize,
|
|
11
|
+
* Queues, Email, Hyperdrive, Browser, Durable Objects, Analytics Engine
|
|
12
|
+
* - Uses RECOMMENDED_COMPATIBILITY_DATE from framework
|
|
13
|
+
* - Generates empty IDs for auto-provisioning in local dev (Wrangler 3.91+)
|
|
14
|
+
* - Includes [observability] defaults
|
|
15
|
+
* - Clean comments explaining each binding
|
|
21
16
|
*/
|
|
22
17
|
export class WranglerTomlGenerator extends BaseGenerator {
|
|
23
18
|
constructor(options = {}) {
|
|
@@ -114,109 +109,155 @@ export class WranglerTomlGenerator extends BaseGenerator {
|
|
|
114
109
|
}
|
|
115
110
|
|
|
116
111
|
/**
|
|
117
|
-
* Build the complete wrangler.toml content
|
|
112
|
+
* Build the complete wrangler.toml content (v4.4.1+)
|
|
113
|
+
* Supports all modern Cloudflare bindings.
|
|
118
114
|
*/
|
|
119
115
|
async _buildWranglerToml(coreInputs, confirmedValues, routesConfig, siteConfig) {
|
|
120
|
-
const compatDate =
|
|
116
|
+
const compatDate = '2024-12-01'; // Framework recommended compatibility date
|
|
121
117
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
let buildString = '\n\n[build]\ncommand = "npm run build"\n\n[build.upload]\nformat = "modules"\n\n[build.upload.external]\ninclude = ["node:*"]';
|
|
125
|
-
try {
|
|
126
|
-
const compatibilityDetector = new WranglerCompatibilityDetector();
|
|
127
|
-
const wranglerVersion = await Promise.race([compatibilityDetector.detectVersion(), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 2000)) // 2 second timeout
|
|
128
|
-
]);
|
|
129
|
-
const compatibilityConfig = compatibilityDetector.getOptimalConfig(wranglerVersion);
|
|
130
|
-
const buildConfig = compatibilityDetector.getBuildConfig(wranglerVersion);
|
|
118
|
+
const features = confirmedValues.features || {};
|
|
119
|
+
const workerName = confirmedValues.workerName || coreInputs.serviceName;
|
|
131
120
|
|
|
132
|
-
|
|
133
|
-
if (compatibilityConfig.nodejs_compat !== undefined) {
|
|
134
|
-
compatibilityString = `nodejs_compat = ${compatibilityConfig.nodejs_compat}`;
|
|
135
|
-
} else if (compatibilityConfig.compatibility_flags) {
|
|
136
|
-
compatibilityString = `compatibility_flags = ${JSON.stringify(compatibilityConfig.compatibility_flags)}`;
|
|
137
|
-
}
|
|
121
|
+
// ── Binding blocks based on features ──────────────────────────────
|
|
138
122
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
} catch (error) {
|
|
147
|
-
// Fall back to default configuration if detection fails
|
|
148
|
-
console.warn(`⚠️ Wrangler compatibility detection failed, using defaults: ${error.message}`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Only include D1/kv/r2 bindings when the confirmed values indicate features are enabled
|
|
152
|
-
const d1Block = confirmedValues.features && confirmedValues.features.d1 ? `
|
|
153
|
-
|
|
154
|
-
# Database bindings
|
|
123
|
+
const bindingBlocks = [];
|
|
124
|
+
if (features.d1 || features.database) {
|
|
125
|
+
bindingBlocks.push(`
|
|
126
|
+
# ═══ D1 Database ═══════════════════════════════════════════════════
|
|
127
|
+
# Local: Leave database_id empty for auto-provisioned SQLite
|
|
128
|
+
# Production: Run \`wrangler d1 create ${confirmedValues.databaseName || workerName + '-db'}\` and paste ID
|
|
155
129
|
[[d1_databases]]
|
|
156
130
|
binding = "DB"
|
|
157
|
-
database_name = "${confirmedValues.databaseName}"
|
|
158
|
-
database_id = ""
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
# KV namespaces
|
|
131
|
+
database_name = "${confirmedValues.databaseName || workerName + '-db'}"
|
|
132
|
+
database_id = ""`);
|
|
133
|
+
}
|
|
134
|
+
if (features.kv || features.upstash) {
|
|
135
|
+
bindingBlocks.push(`
|
|
136
|
+
# ═══ KV Namespace ══════════════════════════════════════════════════
|
|
137
|
+
# Local: Leave id empty for auto-provisioned local KV
|
|
138
|
+
# Production: Run \`wrangler kv namespace create KV\` and paste ID
|
|
166
139
|
[[kv_namespaces]]
|
|
167
140
|
binding = "KV"
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
# R2
|
|
141
|
+
id = ""`);
|
|
142
|
+
}
|
|
143
|
+
if (features.r2) {
|
|
144
|
+
bindingBlocks.push(`
|
|
145
|
+
# ═══ R2 Object Storage ═════════════════════════════════════════════
|
|
146
|
+
# Production: Run \`wrangler r2 bucket create ${workerName}-storage\`
|
|
173
147
|
[[r2_buckets]]
|
|
174
148
|
binding = "R2_STORAGE"
|
|
175
|
-
bucket_name = "${confirmedValues.bucketName ||
|
|
176
|
-
|
|
149
|
+
bucket_name = "${confirmedValues.bucketName || workerName + '-storage'}"`);
|
|
150
|
+
}
|
|
151
|
+
if (features.ai) {
|
|
152
|
+
bindingBlocks.push(`
|
|
153
|
+
# ═══ Workers AI ════════════════════════════════════════════════════
|
|
154
|
+
[ai]
|
|
155
|
+
binding = "AI"`);
|
|
156
|
+
}
|
|
157
|
+
if (features.vectorize) {
|
|
158
|
+
bindingBlocks.push(`
|
|
159
|
+
# ═══ Vectorize (Vector Database) ═══════════════════════════════════
|
|
160
|
+
# Production: Run \`wrangler vectorize create ${workerName}-index --dimensions 768 --metric cosine\`
|
|
161
|
+
[[vectorize]]
|
|
162
|
+
binding = "VECTORIZE_INDEX"
|
|
163
|
+
index_name = "${workerName}-index"`);
|
|
164
|
+
}
|
|
165
|
+
if (features.queues) {
|
|
166
|
+
bindingBlocks.push(`
|
|
167
|
+
# ═══ Queues ════════════════════════════════════════════════════════
|
|
168
|
+
[[queues.producers]]
|
|
169
|
+
binding = "QUEUE"
|
|
170
|
+
queue = "${workerName}-queue"
|
|
171
|
+
|
|
172
|
+
[[queues.consumers]]
|
|
173
|
+
queue = "${workerName}-queue"
|
|
174
|
+
max_batch_size = 10
|
|
175
|
+
max_batch_timeout = 30
|
|
176
|
+
max_retries = 3
|
|
177
|
+
dead_letter_queue = "${workerName}-queue-dlq"`);
|
|
178
|
+
}
|
|
179
|
+
if (features.email) {
|
|
180
|
+
bindingBlocks.push(`
|
|
181
|
+
# ═══ Email Workers ═════════════════════════════════════════════════
|
|
182
|
+
[send_email]
|
|
183
|
+
binding = "EMAIL"`);
|
|
184
|
+
}
|
|
185
|
+
if (features.hyperdrive) {
|
|
186
|
+
bindingBlocks.push(`
|
|
187
|
+
# ═══ Hyperdrive (Postgres Connection Pool) ═════════════════════════
|
|
188
|
+
# Production: Run \`wrangler hyperdrive create ${workerName}-db --connection-string "postgres://..."\`
|
|
189
|
+
[[hyperdrive]]
|
|
190
|
+
binding = "HYPERDRIVE"
|
|
191
|
+
id = ""`);
|
|
192
|
+
}
|
|
193
|
+
if (features.browser) {
|
|
194
|
+
bindingBlocks.push(`
|
|
195
|
+
# ═══ Browser Rendering ═════════════════════════════════════════════
|
|
196
|
+
[browser]
|
|
197
|
+
binding = "BROWSER"`);
|
|
198
|
+
}
|
|
199
|
+
if (features.durableObject || features.durableObjects) {
|
|
200
|
+
bindingBlocks.push(`
|
|
201
|
+
# ═══ Durable Objects ══════════════════════════════════════════════
|
|
202
|
+
[[durable_objects.bindings]]
|
|
203
|
+
name = "DURABLE_OBJECT"
|
|
204
|
+
class_name = "MyDurableObject"
|
|
205
|
+
|
|
206
|
+
[[migrations]]
|
|
207
|
+
tag = "v1"
|
|
208
|
+
new_classes = ["MyDurableObject"]`);
|
|
209
|
+
}
|
|
210
|
+
if (features.analytics) {
|
|
211
|
+
bindingBlocks.push(`
|
|
212
|
+
# ═══ Analytics Engine ══════════════════════════════════════════════
|
|
213
|
+
[[analytics_engine_datasets]]
|
|
214
|
+
binding = "ANALYTICS"
|
|
215
|
+
dataset = "${workerName}-analytics"`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── Cron triggers ─────────────────────────────────────────────────
|
|
219
|
+
const cronBlock = features.cron ? `
|
|
220
|
+
# ═══ Cron Triggers ═════════════════════════════════════════════════
|
|
221
|
+
[triggers]
|
|
222
|
+
crons = ["*/5 * * * *"] # Every 5 minutes — customize as needed
|
|
177
223
|
` : '';
|
|
178
|
-
return `#
|
|
179
|
-
|
|
224
|
+
return `# ═══════════════════════════════════════════════════════════════════
|
|
225
|
+
# ${confirmedValues.displayName} — Cloudflare Worker Configuration
|
|
226
|
+
# Generated by Clodo Framework v4.4.1
|
|
227
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
228
|
+
name = "${workerName}"
|
|
180
229
|
main = "src/worker/index.js"
|
|
181
230
|
compatibility_date = "${compatDate}"
|
|
182
|
-
|
|
231
|
+
compatibility_flags = ["nodejs_compat"]
|
|
183
232
|
|
|
184
233
|
# Account configuration
|
|
185
|
-
account_id = "${coreInputs.cloudflareAccountId}"
|
|
234
|
+
account_id = "${coreInputs.cloudflareAccountId || ''}"
|
|
186
235
|
|
|
187
|
-
${routesConfig}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
# Environment configurations
|
|
236
|
+
${routesConfig}${siteConfig ? '\n' + siteConfig : ''}
|
|
237
|
+
# ═══ Environment configurations ════════════════════════════════════
|
|
191
238
|
[env.development]
|
|
192
|
-
name = "${
|
|
239
|
+
name = "${workerName}-dev"
|
|
193
240
|
|
|
194
241
|
[env.staging]
|
|
195
|
-
name = "${
|
|
242
|
+
name = "${workerName}-staging"
|
|
196
243
|
|
|
197
244
|
[env.production]
|
|
198
|
-
name = "${
|
|
199
|
-
|
|
200
|
-
${
|
|
201
|
-
# Environment variables
|
|
245
|
+
name = "${workerName}"
|
|
246
|
+
${bindingBlocks.join('\n')}
|
|
247
|
+
${cronBlock}
|
|
248
|
+
# ═══ Environment variables ═════════════════════════════════════════
|
|
202
249
|
[vars]
|
|
203
250
|
SERVICE_NAME = "${coreInputs.serviceName}"
|
|
204
251
|
SERVICE_TYPE = "${coreInputs.serviceType}"
|
|
205
|
-
DOMAIN_NAME = "${coreInputs.domainName}"
|
|
206
|
-
ENVIRONMENT = "${coreInputs.environment}"
|
|
207
|
-
API_BASE_PATH = "${confirmedValues.apiBasePath}"
|
|
208
|
-
HEALTH_CHECK_PATH = "${confirmedValues.healthCheckPath}"
|
|
209
|
-
|
|
210
|
-
# Domain-specific variables
|
|
211
|
-
PRODUCTION_URL = "${confirmedValues.productionUrl}"
|
|
212
|
-
STAGING_URL = "${confirmedValues.stagingUrl}"
|
|
213
|
-
DEVELOPMENT_URL = "${confirmedValues.developmentUrl}"
|
|
214
252
|
|
|
215
|
-
#
|
|
216
|
-
|
|
253
|
+
# ═══ Observability ═════════════════════════════════════════════════
|
|
254
|
+
[observability]
|
|
255
|
+
enabled = true
|
|
217
256
|
|
|
218
|
-
|
|
219
|
-
|
|
257
|
+
[observability.logs]
|
|
258
|
+
enabled = true
|
|
259
|
+
invocation_logs = true
|
|
260
|
+
# head_sampling_rate = 0.01 # Uncomment in production
|
|
220
261
|
`;
|
|
221
262
|
}
|
|
222
263
|
|
package/dist/simple-api.js
CHANGED
|
@@ -108,10 +108,10 @@ export class Clodo {
|
|
|
108
108
|
*/
|
|
109
109
|
static getInfo() {
|
|
110
110
|
return {
|
|
111
|
-
name: '
|
|
112
|
-
version: '
|
|
113
|
-
description: '
|
|
114
|
-
features: ['Service
|
|
111
|
+
name: '@tamyla/clodo-framework',
|
|
112
|
+
version: '4.4.1',
|
|
113
|
+
description: 'Batteries-included framework for Cloudflare Workers — routing, middleware, AI, KV, R2, Vectorize, Queues & more',
|
|
114
|
+
features: ['Enhanced Router (Express/Hono-style)', 'Middleware Composition', 'Workers AI Integration', 'KV / R2 / D1 Storage', 'Vectorize (Vector DB)', 'Queues (Producer/Consumer)', 'Cron / Scheduled Handlers', 'Durable Objects', 'Email Workers', 'Service Bindings', 'Analytics Engine', 'Health Checks & Monitoring', 'Feature Flags', 'Rate Limiting', 'Environment Validation', 'Service Creation & Deployment', 'Multi-Domain Orchestration']
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
}
|