@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.
- package/CHANGELOG.md +2 -1851
- 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 +185 -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/validation/environmentGuard.js +172 -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
|
@@ -56,7 +56,9 @@ export class SchemaManager {
|
|
|
56
56
|
});
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
//
|
|
26
|
-
const
|
|
27
|
-
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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;
|
|
122
|
+
return true;
|
|
267
123
|
}
|
|
268
124
|
}
|