@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
@@ -56,7 +56,9 @@ export class SchemaManager {
56
56
  });
57
57
  });
58
58
  }
59
- console.log(`✅ Registered model: ${modelName}`);
59
+ if (typeof process !== 'undefined' && process.env?.DEBUG) {
60
+ console.log(`✅ Registered model: ${modelName}`);
61
+ }
60
62
  }
61
63
 
62
64
  /**
@@ -775,4 +777,6 @@ schemaManager.registerModel('logs', {
775
777
  },
776
778
  indexes: ['level', 'user_id', 'timestamp']
777
779
  });
778
- console.log('✅ Schema Manager initialized with existing models');
780
+
781
+ // Schema Manager initialized with 5 default models (users, magic_links, tokens, files, logs)
782
+ // Set DEBUG=true to see registration logs
@@ -3,14 +3,25 @@ import { join } from 'path';
3
3
  import { mkdirSync, writeFileSync } from 'fs';
4
4
 
5
5
  /**
6
- * Service Middleware Generator
7
- * Generates middleware functions for request/response processing
6
+ * Service Middleware Generator (v4.4.1+)
7
+ *
8
+ * IMPORTANT: As of v4.4.1, middleware is imported directly from @tamyla/clodo-framework.
9
+ * This generator now creates a thin middleware config file instead of duplicating
10
+ * framework functionality with MiddlewareRegistry/MiddlewareComposer/Shared.*.
11
+ *
12
+ * The generated worker (WorkerIndexGenerator) imports:
13
+ * createCorsMiddleware, createErrorHandler, createLogger, createRateLimitGuard,
14
+ * composeMiddleware, createBearerAuth, createApiKeyAuth
15
+ * directly from the framework.
16
+ *
17
+ * This generator only creates:
18
+ * src/middleware/config.js — service-specific middleware configuration
8
19
  */
9
20
  export class ServiceMiddlewareGenerator extends BaseGenerator {
10
21
  /**
11
- * Generate service middleware
22
+ * Generate service middleware configuration
12
23
  * @param {Object} context - Generation context
13
- * @returns {Promise<string>} Path to generated middleware file
24
+ * @returns {Promise<string>} Path to generated middleware config file
14
25
  */
15
26
  async generate(context) {
16
27
  const {
@@ -21,241 +32,86 @@ export class ServiceMiddlewareGenerator extends BaseGenerator {
21
32
  if (!this.shouldGenerate(context)) {
22
33
  return null;
23
34
  }
35
+ const rawFeatures = confirmedValues.features || {};
36
+ const features = Array.isArray(rawFeatures) ? Object.fromEntries(rawFeatures.map(f => [f, true])) : rawFeatures;
24
37
 
25
- // Allow legacy strategy if requested by context
26
- const strategy = context && context.middlewareStrategy ? context.middlewareStrategy : 'contract';
27
- let middlewareContent;
28
- if (strategy === 'legacy') {
29
- middlewareContent = `/**
30
- * ${confirmedValues.displayName} - Service Middleware (LEGACY)
38
+ // Generate a clean middleware config that re-uses framework APIs
39
+ const configContent = `/**
40
+ * ${confirmedValues.displayName} - Middleware Configuration
31
41
  *
32
- * Generated by Clodo Framework GenerationEngine
42
+ * Generated by Clodo Framework v4.4.1
33
43
  * Service Type: ${coreInputs.serviceType}
44
+ *
45
+ * This file configures the middleware stack for your service.
46
+ * All middleware is imported from @tamyla/clodo-framework — no local
47
+ * duplicates. Edit the options below to customize behavior.
34
48
  */
35
49
 
36
- export function createServiceMiddleware(serviceConfig, env) {
37
- return {
38
- async processRequest(request) {
39
- let processedRequest = request;
40
-
41
- // Add service context headers
42
- const headers = new Headers(request.headers);
43
- headers.set('X-Service', serviceConfig.name);
44
- headers.set('X-Version', '${confirmedValues.version}');
45
- headers.set('X-Environment', '${coreInputs.environment}');
46
- headers.set('X-Request-ID', crypto.randomUUID());
47
-
48
- // CORS headers for API requests
49
- if (request.url.includes('${confirmedValues.apiBasePath}')) {
50
- headers.set('Access-Control-Allow-Origin', '*');
51
- headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
52
- headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
53
- }
54
-
55
- ${confirmedValues.features && confirmedValues.features.logging ? `
56
- // Request logging
57
- console.log('[' + new Date().toISOString() + '] ' + request.method + ' ' + request.url);` : ''}
58
-
59
- ${confirmedValues.features && confirmedValues.features.rateLimiting ? `
60
- // Rate limiting (placeholder - implement based on requirements)
61
- // This would typically check request frequency and block if over limit` : ''}
62
-
63
- ${confirmedValues.features && confirmedValues.features.authentication ? `
64
- // Authentication middleware (placeholder)
65
- // This would validate JWT tokens, API keys, etc.` : ''}
66
-
67
- ${confirmedValues.features && confirmedValues.features.authorization ? `
68
- // Authorization middleware (placeholder)
69
- // This would check user permissions and roles` : ''}
70
-
71
- return new Request(request.url, {
72
- ...request,
73
- headers
74
- });
75
- },
76
-
77
- async processResponse(response) {
78
- const headers = new Headers(response.headers);
79
-
80
- // Add standard response headers
81
- headers.set('X-Service', serviceConfig.name);
82
- headers.set('X-Version', '${confirmedValues.version}');
83
- headers.set('X-Response-Time', Date.now().toString());
84
-
85
- ${confirmedValues.features && confirmedValues.features.monitoring ? `
86
- // Response monitoring
87
- console.log('Response: ' + response.status + ' (' + Date.now() + 'ms)');` : ''}
88
-
89
- ${confirmedValues.features && confirmedValues.features.caching ? `
90
- // Cache headers (placeholder - implement based on content type)
91
- if (response.status === 200) {
92
- headers.set('Cache-Control', 'public, max-age=300'); // 5 minutes
93
- }` : ''}
50
+ import {
51
+ createCorsMiddleware,
52
+ createErrorHandler,
53
+ createLogger,${features.rateLimiting ? '\n createRateLimitGuard,' : ''}${features.authentication ? '\n createBearerAuth,' : ''}
54
+ composeMiddleware
55
+ } from '@tamyla/clodo-framework';
94
56
 
95
- return new Response(response.body, {
96
- ...response,
97
- headers
98
- });
99
- }
100
- };
101
- }
102
- `;
103
- } else {
104
- middlewareContent = `/**
105
- * ${confirmedValues.displayName} - Service Middleware
106
- *
107
- * Generated by Clodo Framework GenerationEngine
108
- * Service Type: ${coreInputs.serviceType}
57
+ /**
58
+ * Middleware configuration for ${coreInputs.serviceName}
59
+ * Modify these options to customize your middleware stack.
109
60
  */
61
+ export const middlewareConfig = {
62
+ cors: {
63
+ origins: ['*'],
64
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
65
+ headers: ['Content-Type', 'Authorization'],
66
+ credentials: false
67
+ },
68
+ logger: {
69
+ prefix: '${coreInputs.serviceName}',
70
+ level: 'info',
71
+ includeHeaders: false
72
+ },
73
+ errorHandler: {
74
+ includeStack: false,
75
+ logErrors: true
76
+ }${features.rateLimiting ? `,
77
+ rateLimit: {
78
+ maxRequests: 100,
79
+ windowMs: 60000
80
+ }` : ''}${features.authentication ? `,
81
+ auth: {
82
+ // Configure your auth strategy:
83
+ // token: env.SECRET_KEY (for bearer auth)
84
+ // keys: [env.API_KEY] (for API key auth)
85
+ realm: '${coreInputs.serviceName}'
86
+ }` : ''}${features.durableObject || features.durableObjects ? `,
87
+ // Durable Objects / DurableObject placeholder - update with your binding name
88
+ // Example: DurableObject binding name -> DURABLE_OBJECT
89
+ durable: {
90
+ namespace: 'DURABLE_OBJECT'
91
+ }` : ''}
92
+ };
110
93
 
111
- // Service middleware contract skeleton - minimal and opt-in
112
- export default class ${confirmedValues.packageName || confirmedValues.serviceName ? confirmedValues.serviceName : 'Service'}Middleware {
113
- // Implement only the hooks you need
114
- async preprocess(request) { return null; }
115
- async authenticate(request) { return null; }
116
- async validate(request) { return null; }
117
- async postprocess(response) { return response; }
118
- }
119
-
120
- // Optional registration helper - Worker generator will call this to register
121
- export function registerMiddleware(registry, serviceName) {
122
- if (!registry || typeof registry.register !== 'function') return;
123
- try {
124
- registry.register(serviceName || '${coreInputs.serviceName}', new (exports.default || ${confirmedValues.packageName || confirmedValues.serviceName ? confirmedValues.serviceName : 'Service'}Middleware)());
125
- } catch (e) {
126
- // Non-fatal - allow services to manually register if needed
127
- }
94
+ /**
95
+ * Create the composed middleware stack from configuration.
96
+ * Called from the worker entry point.
97
+ */
98
+ export function createMiddlewareStack(config = middlewareConfig) {
99
+ return composeMiddleware(
100
+ createCorsMiddleware(config.cors),
101
+ createLogger(config.logger),${features.rateLimiting ? '\n createRateLimitGuard(config.rateLimit),' : ''}
102
+ createErrorHandler(config.errorHandler)
103
+ );
128
104
  }
129
-
130
105
  `;
131
- }
132
- const filePath = join(servicePath, 'src', 'middleware', 'service-middleware.js');
133
106
 
134
107
  // Ensure directory exists
135
108
  const dir = join(servicePath, 'src', 'middleware');
136
109
  mkdirSync(dir, {
137
110
  recursive: true
138
111
  });
139
- writeFileSync(filePath, middlewareContent, 'utf8');
112
+ const filePath = join(servicePath, 'src', 'middleware', 'config.js');
113
+ writeFileSync(filePath, configContent, 'utf8');
140
114
  this.logger.info(`Generated: ${filePath}`);
141
-
142
- // Also generate a lightweight runtime helper for middleware composition and registry
143
- let runtimeContent = '';
144
-
145
- // If Durable Objects are enabled (support both singular/plural feature names), inject DO imports/bindings
146
- if (confirmedValues.features && (confirmedValues.features.durableObjects || confirmedValues.features.durableObject)) {
147
- runtimeContent += `// Durable Object imports and bindings\nimport { DurableObject } from '@cloudflare/workers-types';\nexport const DURABLE_OBJECT = { type: 'durable_object', className: 'MyDurableObject' };\n\n`;
148
- }
149
- runtimeContent += `// Lightweight middleware runtime for generated services
150
- export const MiddlewareRegistry = (() => {
151
- const map = new Map();
152
- return {
153
- register(serviceName, instance) { map.set(serviceName, instance); },
154
- get(serviceName) { return map.get(serviceName) || null; },
155
- clear() { map.clear(); }
156
- };
157
- })();
158
-
159
- export const MiddlewareComposer = {
160
- compose(...middlewares) {
161
- const chain = middlewares.filter(Boolean);
162
- return {
163
- async execute(request, handler) {
164
- let req = request;
165
- for (const m of chain) {
166
- if (typeof m.preprocess === 'function') {
167
- const res = await m.preprocess(req);
168
- if (res) return res;
169
- }
170
- if (typeof m.authenticate === 'function') {
171
- const res = await m.authenticate(req);
172
- if (res) return res;
173
- }
174
- if (typeof m.validate === 'function') {
175
- const res = await m.validate(req);
176
- if (res) return res;
177
- }
178
- }
179
- let response = await handler(req);
180
- for (const m of chain.slice().reverse()) {
181
- if (typeof m.postprocess === 'function') {
182
- const updated = await m.postprocess(response);
183
- if (updated instanceof Response) response = updated;
184
- }
185
- }
186
- return response;
187
- }
188
- };
189
- }
190
- };
191
- `;
192
- const runtimePath = join(servicePath, 'src', 'middleware', 'runtime.js');
193
- writeFileSync(runtimePath, runtimeContent, 'utf8');
194
- this.logger.info(`Generated: ${runtimePath}`);
195
-
196
- // Minimal shared implementations (copied into generated service for self-containment)
197
- const sharedDir = join(servicePath, 'src', 'middleware', 'shared');
198
- mkdirSync(sharedDir, {
199
- recursive: true
200
- });
201
- const corsContent = `export function cors(options = {}) {
202
- const origin = options.origin || '*';
203
- const allowMethods = options.methods || 'GET, POST, PUT, DELETE, OPTIONS';
204
- const allowHeaders = options.headers || 'Content-Type, Authorization';
205
-
206
- return {
207
- preprocess(request) {
208
- if (request.method === 'OPTIONS') {
209
- const headers = new Headers();
210
- headers.set('Access-Control-Allow-Origin', origin);
211
- headers.set('Access-Control-Allow-Methods', allowMethods);
212
- headers.set('Access-Control-Allow-Headers', allowHeaders);
213
- return new Response(null, { status: 204, headers });
214
- }
215
- return null;
216
- },
217
- postprocess(response) {
218
- const headers = new Headers(response.headers);
219
- headers.set('Access-Control-Allow-Origin', origin);
220
- return new Response(response.body, { ...response, headers });
221
- }
222
- };
223
- }
224
- `;
225
- const loggingContent = `export function logging(options = {}) {
226
- const level = options.level || 'info';
227
- return {
228
- preprocess(request) {
229
- try {
230
- const path = new URL(request.url).pathname;
231
- console.log('[' + new Date().toISOString() + '] ' + level.toUpperCase() + ' ' + request.method + ' ' + path);
232
- } catch (e) {}
233
- return null;
234
- }
235
- };
236
- }
237
- `;
238
- const basicAuthContent = `export function basicAuth({ realm = 'Restricted' } = {}) {
239
- return {
240
- authenticate(request) {
241
- const auth = request.headers.get('Authorization');
242
- if (!auth) {
243
- return new Response(JSON.stringify({ error: 'Unauthorized' }), {
244
- status: 401,
245
- headers: { 'Content-Type': 'application/json', 'WWW-Authenticate': 'Basic realm="' + realm + '"' }
246
- });
247
- }
248
- return null;
249
- }
250
- };
251
- }
252
- `;
253
- writeFileSync(join(sharedDir, 'cors.js'), corsContent, 'utf8');
254
- writeFileSync(join(sharedDir, 'logging.js'), loggingContent, 'utf8');
255
- writeFileSync(join(sharedDir, 'basicAuth.js'), basicAuthContent, 'utf8');
256
- const indexContent = `export { cors } from './cors.js';\nexport { logging } from './logging.js';\nexport { basicAuth } from './basicAuth.js';\n`;
257
- writeFileSync(join(sharedDir, 'index.js'), indexContent, 'utf8');
258
- this.logger.info(`Generated shared middleware in: ${sharedDir}`);
259
115
  return filePath;
260
116
  }
261
117
 
@@ -263,6 +119,6 @@ export const MiddlewareComposer = {
263
119
  * Determine if generator should run
264
120
  */
265
121
  shouldGenerate(context) {
266
- return true; // Always generate middleware
122
+ return true;
267
123
  }
268
124
  }