@tamyla/clodo-framework 4.4.1 → 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.
Files changed (33) hide show
  1. package/CHANGELOG.md +2 -1851
  2. package/README.md +44 -18
  3. package/dist/cli/commands/add.js +325 -0
  4. package/dist/config/service-schema-config.js +98 -5
  5. package/dist/index.js +22 -3
  6. package/dist/middleware/Composer.js +2 -1
  7. package/dist/middleware/factories.js +445 -0
  8. package/dist/middleware/index.js +4 -1
  9. package/dist/modules/ModuleManager.js +6 -2
  10. package/dist/routing/EnhancedRouter.js +185 -44
  11. package/dist/routing/RequestContext.js +393 -0
  12. package/dist/schema/SchemaManager.js +6 -2
  13. package/dist/service-management/generators/code/ServiceMiddlewareGenerator.js +79 -223
  14. package/dist/service-management/generators/code/WorkerIndexGenerator.js +241 -98
  15. package/dist/service-management/generators/config/WranglerTomlGenerator.js +130 -89
  16. package/dist/simple-api.js +4 -4
  17. package/dist/utilities/index.js +134 -1
  18. package/dist/validation/environmentGuard.js +172 -0
  19. package/package.json +4 -1
  20. package/scripts/repro-clodo.js +123 -0
  21. package/templates/ai-worker/package.json +19 -0
  22. package/templates/ai-worker/src/index.js +160 -0
  23. package/templates/cron-worker/package.json +19 -0
  24. package/templates/cron-worker/src/index.js +211 -0
  25. package/templates/edge-proxy/package.json +18 -0
  26. package/templates/edge-proxy/src/index.js +150 -0
  27. package/templates/minimal/package.json +17 -0
  28. package/templates/minimal/src/index.js +40 -0
  29. package/templates/queue-processor/package.json +19 -0
  30. package/templates/queue-processor/src/index.js +213 -0
  31. package/templates/rest-api/.dev.vars +2 -0
  32. package/templates/rest-api/package.json +19 -0
  33. package/templates/rest-api/src/index.js +124 -0
@@ -62,4 +62,137 @@ export { ServiceBindingClient, RPCClient, ServiceRouter } from './bindings/index
62
62
  // ============================================================
63
63
 
64
64
  // Analytics Engine
65
- export { AnalyticsWriter, EventTracker, MetricsCollector } from './analytics/index.js';
65
+ export { AnalyticsWriter, EventTracker, MetricsCollector } from './analytics/index.js';
66
+
67
+ // ============================================================
68
+ // STANDALONE UTILITY FUNCTIONS
69
+ // ============================================================
70
+
71
+ // AI Utilities
72
+ /**
73
+ * Run an AI model directly
74
+ * @param {Object} aiBinding - AI binding from env
75
+ * @param {string} model - Model name
76
+ * @param {Object} inputs - Model inputs
77
+ * @returns {Promise<Object>} Model response
78
+ */
79
+ export async function runAIModel(aiBinding, model, inputs) {
80
+ const {
81
+ AIClient
82
+ } = await import('./ai/index.js');
83
+ const ai = new AIClient(aiBinding);
84
+ return ai.run(model, inputs);
85
+ }
86
+
87
+ /**
88
+ * Stream AI response
89
+ * @param {Object} aiBinding - AI binding from env
90
+ * @param {string} model - Model name
91
+ * @param {Object} inputs - Model inputs
92
+ * @returns {Promise<Response>} Streaming response
93
+ */
94
+ export async function streamAIResponse(aiBinding, model, inputs) {
95
+ const {
96
+ AIClient,
97
+ streamResponse
98
+ } = await import('./ai/index.js');
99
+ const ai = new AIClient(aiBinding);
100
+ const stream = await ai.run(model, {
101
+ ...inputs,
102
+ stream: true
103
+ });
104
+ return streamResponse(stream);
105
+ }
106
+
107
+ /**
108
+ * Format AI prompt with context
109
+ * @param {string} prompt - Base prompt
110
+ * @param {Object} context - Context data
111
+ * @returns {string} Formatted prompt
112
+ */
113
+ export function formatAIPrompt(prompt, context = {}) {
114
+ let formatted = prompt;
115
+
116
+ // Replace placeholders like {{key}} with context values
117
+ for (const [key, value] of Object.entries(context)) {
118
+ const placeholder = new RegExp(`{{${key}}}`, 'g');
119
+ formatted = formatted.replace(placeholder, String(value));
120
+ }
121
+ return formatted;
122
+ }
123
+
124
+ // Vectorize Utilities
125
+ /**
126
+ * Query vectors from Vectorize index
127
+ * @param {Object} vectorizeBinding - Vectorize binding from env
128
+ * @param {number[]} vector - Query vector
129
+ * @param {Object} options - Query options
130
+ * @returns {Promise<Object>} Query results
131
+ */
132
+ export async function queryVectors(vectorizeBinding, vector, options = {}) {
133
+ const {
134
+ VectorStore
135
+ } = await import('./vectorize/index.js');
136
+ const store = new VectorStore(vectorizeBinding);
137
+ return store.query(vector, options);
138
+ }
139
+
140
+ /**
141
+ * Upsert vectors to Vectorize index
142
+ * @param {Object} vectorizeBinding - Vectorize binding from env
143
+ * @param {Array} vectors - Vectors to upsert
144
+ * @returns {Promise<Object>} Upsert results
145
+ */
146
+ export async function upsertVectors(vectorizeBinding, vectors) {
147
+ const {
148
+ VectorStore
149
+ } = await import('./vectorize/index.js');
150
+ const store = new VectorStore(vectorizeBinding);
151
+ return store.upsert(vectors);
152
+ }
153
+
154
+ // KV Utilities
155
+ /**
156
+ * Get value from KV storage
157
+ * @param {Object} kvBinding - KV binding from env
158
+ * @param {string} key - Key to retrieve
159
+ * @param {Object} options - Get options
160
+ * @returns {Promise<*>} Retrieved value
161
+ */
162
+ export async function getKV(kvBinding, key, options = {}) {
163
+ const {
164
+ KVStorage
165
+ } = await import('./kv/index.js');
166
+ const kv = new KVStorage(kvBinding);
167
+ return kv.get(key, options);
168
+ }
169
+
170
+ /**
171
+ * Put value in KV storage
172
+ * @param {Object} kvBinding - KV binding from env
173
+ * @param {string} key - Key to set
174
+ * @param {*} value - Value to store
175
+ * @param {Object} options - Put options
176
+ * @returns {Promise<void>}
177
+ */
178
+ export async function putKV(kvBinding, key, value, options = {}) {
179
+ const {
180
+ KVStorage
181
+ } = await import('./kv/index.js');
182
+ const kv = new KVStorage(kvBinding);
183
+ return kv.set(key, value, options);
184
+ }
185
+
186
+ /**
187
+ * List keys from KV storage
188
+ * @param {Object} kvBinding - KV binding from env
189
+ * @param {Object} options - List options
190
+ * @returns {Promise<Object>} List results
191
+ */
192
+ export async function listKV(kvBinding, options = {}) {
193
+ const {
194
+ KVStorage
195
+ } = await import('./kv/index.js');
196
+ const kv = new KVStorage(kvBinding);
197
+ return kv.list(options);
198
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Environment Guard — Validates Cloudflare Worker env bindings at startup
3
+ *
4
+ * Catches missing bindings early with descriptive errors instead of
5
+ * failing deep in handler logic with cryptic "Cannot read property of undefined".
6
+ *
7
+ * @example
8
+ * import { createEnvironmentGuard } from '@tamyla/clodo-framework';
9
+ *
10
+ * const guard = createEnvironmentGuard({
11
+ * required: ['KV_DATA', 'AI', 'SECRET_KEY'],
12
+ * optional: ['DEBUG', 'ANTHROPIC_API_KEY'],
13
+ * validate: {
14
+ * SECRET_KEY: (v) => typeof v === 'string' && v.length >= 32
15
+ * }
16
+ * });
17
+ *
18
+ * // In your Worker fetch handler:
19
+ * export default {
20
+ * async fetch(request, env, ctx) {
21
+ * guard.check(env); // throws if bindings are missing
22
+ * // ... handle request
23
+ * }
24
+ * };
25
+ *
26
+ * @module @tamyla/clodo-framework/validation/environmentGuard
27
+ */
28
+
29
+ /**
30
+ * @typedef {Object} EnvironmentGuardConfig
31
+ * @property {string[]} required - Binding names that MUST be present
32
+ * @property {string[]} [optional=[]] - Binding names that MAY be present
33
+ * @property {Object<string, Function>} [validate={}] - Custom validators per binding
34
+ * @property {boolean} [throwOnMissing=true] - Throw on missing required binding (false = return report)
35
+ */
36
+
37
+ export class EnvironmentGuard {
38
+ /**
39
+ * @param {EnvironmentGuardConfig} config
40
+ */
41
+ constructor(config = {}) {
42
+ this.required = config.required || [];
43
+ this.optional = config.optional || [];
44
+ this.validators = config.validate || {};
45
+ this.throwOnMissing = config.throwOnMissing !== false;
46
+ this._checked = false;
47
+ }
48
+
49
+ /**
50
+ * Validate env bindings. Call once at startup or in the fetch handler.
51
+ * @param {Object} env - Cloudflare Worker environment
52
+ * @returns {{ valid: boolean, missing: string[], invalid: string[], present: string[], warnings: string[] }}
53
+ * @throws {Error} If throwOnMissing is true and required bindings are missing
54
+ */
55
+ check(env) {
56
+ const result = {
57
+ valid: true,
58
+ missing: [],
59
+ invalid: [],
60
+ present: [],
61
+ warnings: []
62
+ };
63
+
64
+ // Check required bindings
65
+ for (const name of this.required) {
66
+ if (env[name] === undefined || env[name] === null) {
67
+ result.missing.push(name);
68
+ result.valid = false;
69
+ } else {
70
+ result.present.push(name);
71
+ }
72
+ }
73
+
74
+ // Check optional bindings (warn if missing, don't fail)
75
+ for (const name of this.optional) {
76
+ if (env[name] === undefined || env[name] === null) {
77
+ result.warnings.push(`Optional binding '${name}' is not configured`);
78
+ } else {
79
+ result.present.push(name);
80
+ }
81
+ }
82
+
83
+ // Run custom validators
84
+ for (const [name, validator] of Object.entries(this.validators)) {
85
+ if (env[name] !== undefined && env[name] !== null) {
86
+ try {
87
+ const isValid = validator(env[name]);
88
+ if (!isValid) {
89
+ result.invalid.push(name);
90
+ result.valid = false;
91
+ }
92
+ } catch (err) {
93
+ result.invalid.push(name);
94
+ result.valid = false;
95
+ }
96
+ }
97
+ }
98
+ this._checked = true;
99
+ if (!result.valid && this.throwOnMissing) {
100
+ const parts = [];
101
+ if (result.missing.length) {
102
+ parts.push(`Missing required bindings: ${result.missing.join(', ')}`);
103
+ }
104
+ if (result.invalid.length) {
105
+ parts.push(`Invalid bindings: ${result.invalid.join(', ')}`);
106
+ }
107
+ throw new Error(`[EnvironmentGuard] Environment validation failed.\n${parts.join('\n')}\n\n` + `Hint: Check your wrangler.toml bindings and .dev.vars for secrets.`);
108
+ }
109
+ return result;
110
+ }
111
+
112
+ /**
113
+ * Create a middleware-compatible object that validates env on each request (with caching).
114
+ * Can be used with router.use() or composeMiddleware().
115
+ * @returns {Object} Middleware object with preprocess method
116
+ */
117
+ asMiddleware() {
118
+ const guard = this;
119
+ return {
120
+ preprocess(request, env) {
121
+ if (!guard._checked) {
122
+ guard.check(env);
123
+ }
124
+ return null; // pass through
125
+ }
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Generate a TypeScript Env interface from the configured bindings.
131
+ * Useful for DX — generates type definitions from guard config.
132
+ * @param {Object} [bindingTypes={}] - Map of binding name → TypeScript type
133
+ * @returns {string} TypeScript interface definition
134
+ */
135
+ generateEnvType(bindingTypes = {}) {
136
+ const lines = ['interface Env {'];
137
+
138
+ // Known Cloudflare binding types
139
+ const defaultTypes = {
140
+ KVNamespace: 'KVNamespace',
141
+ D1Database: 'D1Database',
142
+ R2Bucket: 'R2Bucket',
143
+ Ai: 'Ai',
144
+ VectorizeIndex: 'VectorizeIndex',
145
+ Queue: 'Queue',
146
+ DurableObjectNamespace: 'DurableObjectNamespace',
147
+ Fetcher: 'Fetcher',
148
+ AnalyticsEngineDataset: 'AnalyticsEngineDataset',
149
+ SendEmail: 'SendEmail',
150
+ Hyperdrive: 'Hyperdrive'
151
+ };
152
+ for (const name of this.required) {
153
+ const type = bindingTypes[name] || 'unknown';
154
+ lines.push(` ${name}: ${type};`);
155
+ }
156
+ for (const name of this.optional) {
157
+ const type = bindingTypes[name] || 'string';
158
+ lines.push(` ${name}?: ${type};`);
159
+ }
160
+ lines.push('}');
161
+ return lines.join('\n');
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Create an environment guard — factory function
167
+ * @param {EnvironmentGuardConfig} config
168
+ * @returns {EnvironmentGuard}
169
+ */
170
+ export function createEnvironmentGuard(config = {}) {
171
+ return new EnvironmentGuard(config);
172
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamyla/clodo-framework",
3
- "version": "4.4.1",
3
+ "version": "4.5.0",
4
4
  "description": "Reusable framework for Clodo-style software architecture on Cloudflare Workers + D1",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -17,6 +17,7 @@
17
17
  "./schema": "./dist/schema/SchemaManager.js",
18
18
  "./modules": "./dist/modules/ModuleManager.js",
19
19
  "./routing": "./dist/routing/EnhancedRouter.js",
20
+ "./routing/context": "./dist/routing/RequestContext.js",
20
21
  "./handlers": "./dist/handlers/GenericRouteHandler.js",
21
22
  "./config": "./dist/config/index.js",
22
23
  "./config/customers": "./dist/config/customers.js",
@@ -34,9 +35,11 @@
34
35
  "./programmatic": "./dist/programmatic/index.js",
35
36
  "./api": "./dist/api/index.js",
36
37
  "./validation": "./dist/validation/index.js",
38
+ "./validation/env": "./dist/validation/environmentGuard.js",
37
39
  "./errors": "./dist/errors/index.js",
38
40
  "./testing": "./dist/testing/index.js",
39
41
  "./middleware": "./dist/middleware/index.js",
42
+ "./middleware/factories": "./dist/middleware/factories.js",
40
43
  "./modules/security": "./dist/modules/security.js",
41
44
  "./utilities": "./dist/utilities/index.js",
42
45
  "./utilities/storage": "./dist/utilities/storage/r2.js",
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Reproduction script for Express-like router.get/router.post API
4
+ *
5
+ * This script demonstrates both the original registerRoute() API
6
+ * and the new Express-like convenience methods (router.get(), router.post(), etc.)
7
+ *
8
+ * Run: node scripts/repro-clodo.js
9
+ */
10
+
11
+ import { createEnhancedRouter } from '../src/routing/EnhancedRouter.js';
12
+
13
+ console.log('🧪 CLODO Framework Router API Compatibility Test\n');
14
+ console.log('=' .repeat(60));
15
+
16
+ // Mock D1 Client
17
+ const mockD1Client = {
18
+ prepare: () => ({ bind: () => ({}) }),
19
+ };
20
+
21
+ // Create router instance
22
+ const router = createEnhancedRouter(mockD1Client, {
23
+ requireAuth: false,
24
+ allowPublicRead: true
25
+ });
26
+
27
+ console.log('\n📋 Router Instance Properties:');
28
+ console.log(' Keys:', Object.keys(router).sort());
29
+ console.log(' d1Client present:', !!router.d1Client);
30
+ console.log(' options present:', !!router.options);
31
+ console.log(' routes Map present:', router.routes instanceof Map);
32
+ console.log(' genericHandlers present:', !!router.genericHandlers);
33
+
34
+ console.log('\n📋 Express-like Methods Available:');
35
+ const expressLikeMethods = ['get', 'post', 'put', 'patch', 'delete'];
36
+ for (const method of expressLikeMethods) {
37
+ console.log(` typeof router.${method}:`, typeof router[method]);
38
+ }
39
+
40
+ console.log('\n🔧 Testing Express-like API:');
41
+
42
+ // Test 1: router.get()
43
+ console.log('\n Test 1: router.get()');
44
+ try {
45
+ router.get('/api/users', (req) => new Response('GET /api/users'));
46
+ console.log(' ✅ router.get() registered successfully');
47
+ console.log(' ✓ Route key:', 'GET /api/users');
48
+ console.log(' ✓ Handler registered:', router.routes.has('GET /api/users'));
49
+ } catch (e) {
50
+ console.log(' ❌ router.get() failed:', e.message);
51
+ }
52
+
53
+ // Test 2: router.post()
54
+ console.log('\n Test 2: router.post()');
55
+ try {
56
+ router.post('/api/users', (req) => new Response('POST /api/users', { status: 201 }));
57
+ console.log(' ✅ router.post() registered successfully');
58
+ console.log(' ✓ Route key:', 'POST /api/users');
59
+ console.log(' ✓ Handler registered:', router.routes.has('POST /api/users'));
60
+ } catch (e) {
61
+ console.log(' ❌ router.post() failed:', e.message);
62
+ }
63
+
64
+ // Test 3: router.get() with parameters
65
+ console.log('\n Test 3: router.get() with parameters');
66
+ try {
67
+ router.get('/api/users/:id', (req, id) => new Response(`GET /api/users/${id}`));
68
+ console.log(' ✅ router.get() with parameters registered successfully');
69
+ console.log(' ✓ Route key:', 'GET /api/users/:id');
70
+ console.log(' ✓ Handler registered:', router.routes.has('GET /api/users/:id'));
71
+ } catch (e) {
72
+ console.log(' ❌ router.get() with parameters failed:', e.message);
73
+ }
74
+
75
+ // Test 4: router.patch()
76
+ console.log('\n Test 4: router.patch()');
77
+ try {
78
+ router.patch('/api/users/:id', (req, id) => new Response(`PATCH /api/users/${id}`));
79
+ console.log(' ✅ router.patch() registered successfully');
80
+ console.log(' ✓ Route key:', 'PATCH /api/users/:id');
81
+ console.log(' ✓ Handler registered:', router.routes.has('PATCH /api/users/:id'));
82
+ } catch (e) {
83
+ console.log(' ❌ router.patch() failed:', e.message);
84
+ }
85
+
86
+ // Test 5: router.delete()
87
+ console.log('\n Test 5: router.delete()');
88
+ try {
89
+ router.delete('/api/users/:id', (req, id) => new Response(`DELETE /api/users/${id}`));
90
+ console.log(' ✅ router.delete() registered successfully');
91
+ console.log(' ✓ Route key:', 'DELETE /api/users/:id');
92
+ console.log(' ✓ Handler registered:', router.routes.has('DELETE /api/users/:id'));
93
+ } catch (e) {
94
+ console.log(' ❌ router.delete() failed:', e.message);
95
+ }
96
+
97
+ console.log('\n🔧 Testing Traditional registerRoute() API:');
98
+
99
+ // Test 6: registerRoute()
100
+ console.log('\n Test 6: registerRoute()');
101
+ try {
102
+ router.registerRoute('GET', '/health', (req) => new Response('OK'));
103
+ console.log(' ✅ registerRoute() works correctly');
104
+ console.log(' ✓ Route key:', 'GET /health');
105
+ console.log(' ✓ Handler registered:', router.routes.has('GET /health'));
106
+ } catch (e) {
107
+ console.log(' ❌ registerRoute() failed:', e.message);
108
+ }
109
+
110
+ console.log('\n📊 Route Summary:');
111
+ console.log(` Total routes registered: ${router.routes.size}`);
112
+ console.log(' Registered routes:');
113
+ for (const [key] of router.routes.entries()) {
114
+ console.log(` - ${key}`);
115
+ }
116
+
117
+ console.log('\n' + '='.repeat(60));
118
+ console.log('✨ All tests completed!');
119
+ console.log('\nConclusion:');
120
+ console.log(' ✅ Express-like methods (router.get, router.post, etc.) are now available');
121
+ console.log(' ✅ registerRoute() method still works');
122
+ console.log(' ✅ Both APIs are fully compatible and interchangeable');
123
+ console.log(' ✅ Framework is backward compatible with Express patterns');
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{SERVICE_NAME}}",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "wrangler dev",
8
+ "deploy": "wrangler deploy",
9
+ "test": "vitest"
10
+ },
11
+ "dependencies": {
12
+ "@tamyla/clodo-framework": "^4.4.1"
13
+ },
14
+ "devDependencies": {
15
+ "wrangler": "^3.0.0",
16
+ "vitest": "^2.0.0",
17
+ "@cloudflare/vitest-pool-workers": "^0.5.0"
18
+ }
19
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * AI Worker Template — @tamyla/clodo-framework
3
+ *
4
+ * A fully working Workers AI service with:
5
+ * - Text generation (chat/completion)
6
+ * - Streaming responses (SSE)
7
+ * - Embeddings generation
8
+ * - Prompt formatting
9
+ * - CORS + error handling + rate limiting
10
+ *
11
+ * Deploy with: npx wrangler deploy
12
+ */
13
+
14
+ import {
15
+ createCorsMiddleware,
16
+ createErrorHandler,
17
+ createLogger,
18
+ createRateLimitGuard,
19
+ composeMiddleware,
20
+ createEnvironmentGuard
21
+ } from '@tamyla/clodo-framework';
22
+
23
+ import {
24
+ AIClient,
25
+ Models,
26
+ createSSEStream,
27
+ streamResponse
28
+ } from '@tamyla/clodo-framework/utilities/ai';
29
+
30
+ import { formatAIPrompt } from '@tamyla/clodo-framework/utilities';
31
+
32
+ // ── Environment validation ────────────────────────────────────────────
33
+ const envGuard = createEnvironmentGuard({
34
+ required: ['AI'],
35
+ optional: ['KV_DATA', 'VECTORIZE_INDEX']
36
+ });
37
+
38
+ // ── Middleware stack ──────────────────────────────────────────────────
39
+ const middleware = composeMiddleware(
40
+ createCorsMiddleware({ origins: ['*'] }),
41
+ createLogger({ prefix: 'ai-worker', level: 'info' }),
42
+ createRateLimitGuard({ maxRequests: 50, windowMs: 60000 }),
43
+ createErrorHandler({ includeStack: false })
44
+ );
45
+
46
+ // ── Routes ────────────────────────────────────────────────────────────
47
+ const routes = {
48
+ // Health check
49
+ 'GET /health': async (req, env) => {
50
+ return jsonResponse({ status: 'healthy', model: Models.CHAT });
51
+ },
52
+
53
+ // Chat completion (non-streaming)
54
+ 'POST /api/chat': async (req, env) => {
55
+ const { messages, model, max_tokens } = await req.json();
56
+ const ai = new AIClient(env.AI);
57
+
58
+ const response = await ai.chat(messages || [], {
59
+ model: model || Models.CHAT,
60
+ max_tokens: max_tokens || 1024
61
+ });
62
+
63
+ return jsonResponse({ response: response.response, model: model || Models.CHAT });
64
+ },
65
+
66
+ // Chat completion (streaming SSE)
67
+ 'POST /api/chat/stream': async (req, env) => {
68
+ const { messages, model, max_tokens } = await req.json();
69
+ const ai = new AIClient(env.AI);
70
+
71
+ const stream = await ai.run(model || Models.CHAT, {
72
+ messages: messages || [],
73
+ max_tokens: max_tokens || 1024,
74
+ stream: true
75
+ });
76
+
77
+ return streamResponse(stream);
78
+ },
79
+
80
+ // Text generation from prompt template
81
+ 'POST /api/generate': async (req, env) => {
82
+ const { prompt, context, model, max_tokens } = await req.json();
83
+ const ai = new AIClient(env.AI);
84
+
85
+ // Format the prompt with context variables
86
+ const formattedPrompt = context ? formatAIPrompt(prompt, context) : prompt;
87
+
88
+ const response = await ai.generate(formattedPrompt, {
89
+ model: model || Models.TEXT_GENERATION,
90
+ max_tokens: max_tokens || 2048
91
+ });
92
+
93
+ return jsonResponse({ response: response.response, prompt: formattedPrompt });
94
+ },
95
+
96
+ // Generate embeddings
97
+ 'POST /api/embeddings': async (req, env) => {
98
+ const { text, texts, model } = await req.json();
99
+ const ai = new AIClient(env.AI);
100
+
101
+ const input = texts || [text];
102
+ const embeddings = await ai.embed(input, {
103
+ model: model || Models.EMBEDDINGS
104
+ });
105
+
106
+ return jsonResponse({
107
+ embeddings: embeddings.data,
108
+ model: model || Models.EMBEDDINGS,
109
+ dimensions: embeddings.data?.[0]?.length || 0
110
+ });
111
+ },
112
+
113
+ // Summarize text
114
+ 'POST /api/summarize': async (req, env) => {
115
+ const { text, max_length } = await req.json();
116
+ const ai = new AIClient(env.AI);
117
+
118
+ const result = await ai.summarize(text, {
119
+ max_length: max_length || 256
120
+ });
121
+
122
+ return jsonResponse({ summary: result.summary });
123
+ },
124
+
125
+ // List available models
126
+ 'GET /api/models': async (req, env) => {
127
+ return jsonResponse({
128
+ models: Object.entries(Models).map(([key, value]) => ({
129
+ name: key,
130
+ model: value
131
+ }))
132
+ });
133
+ }
134
+ };
135
+
136
+ // ── Helpers ───────────────────────────────────────────────────────────
137
+ function jsonResponse(data, status = 200) {
138
+ return new Response(JSON.stringify(data), {
139
+ status,
140
+ headers: { 'Content-Type': 'application/json' }
141
+ });
142
+ }
143
+
144
+ // ── Worker entry point ────────────────────────────────────────────────
145
+ export default {
146
+ async fetch(request, env, ctx) {
147
+ envGuard.check(env);
148
+
149
+ const url = new URL(request.url);
150
+ const routeKey = `${request.method} ${url.pathname}`;
151
+ const handler = routes[routeKey];
152
+
153
+ if (!handler) {
154
+ return jsonResponse({ error: 'Not Found', path: url.pathname }, 404);
155
+ }
156
+
157
+ // Execute through middleware chain
158
+ return middleware.execute(request, () => handler(request, env, ctx));
159
+ }
160
+ };
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{SERVICE_NAME}}",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "wrangler dev",
8
+ "deploy": "wrangler deploy",
9
+ "test": "vitest"
10
+ },
11
+ "dependencies": {
12
+ "@tamyla/clodo-framework": "^4.4.1"
13
+ },
14
+ "devDependencies": {
15
+ "wrangler": "^3.0.0",
16
+ "vitest": "^2.0.0",
17
+ "@cloudflare/vitest-pool-workers": "^0.5.0"
18
+ }
19
+ }