@tamyla/clodo-framework 1.0.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 +564 -0
- package/LICENSE +21 -0
- package/README.md +1393 -0
- package/bin/README.md +71 -0
- package/bin/clodo-service.js +416 -0
- package/bin/security/security-cli.js +96 -0
- package/bin/service-management/README.md +74 -0
- package/bin/service-management/create-service.js +129 -0
- package/bin/service-management/init-service.js +102 -0
- package/bin/service-management/init-service.js.backup +889 -0
- package/bin/shared/config/customer-cli.js +293 -0
- package/dist/config/ConfigurationManager.js +159 -0
- package/dist/config/CustomerConfigCLI.js +220 -0
- package/dist/config/FeatureManager.js +426 -0
- package/dist/config/customers.js +441 -0
- package/dist/config/domains.js +180 -0
- package/dist/config/features.js +225 -0
- package/dist/config/index.js +6 -0
- package/dist/database/database-orchestrator.js +730 -0
- package/dist/database/index.js +4 -0
- package/dist/deployment/auditor.js +971 -0
- package/dist/deployment/index.js +10 -0
- package/dist/deployment/rollback-manager.js +523 -0
- package/dist/deployment/testers/api-tester.js +80 -0
- package/dist/deployment/testers/auth-tester.js +129 -0
- package/dist/deployment/testers/core.js +217 -0
- package/dist/deployment/testers/database-tester.js +105 -0
- package/dist/deployment/testers/index.js +74 -0
- package/dist/deployment/testers/load-tester.js +120 -0
- package/dist/deployment/testers/performance-tester.js +105 -0
- package/dist/deployment/validator.js +558 -0
- package/dist/deployment/wrangler-deployer.js +574 -0
- package/dist/handlers/GenericRouteHandler.js +532 -0
- package/dist/index.js +39 -0
- package/dist/migration/MigrationAdapters.js +562 -0
- package/dist/modules/ModuleManager.js +668 -0
- package/dist/modules/security.js +98 -0
- package/dist/orchestration/cross-domain-coordinator.js +1083 -0
- package/dist/orchestration/index.js +5 -0
- package/dist/orchestration/modules/DeploymentCoordinator.js +258 -0
- package/dist/orchestration/modules/DomainResolver.js +196 -0
- package/dist/orchestration/modules/StateManager.js +332 -0
- package/dist/orchestration/multi-domain-orchestrator.js +255 -0
- package/dist/routing/EnhancedRouter.js +158 -0
- package/dist/schema/SchemaManager.js +778 -0
- package/dist/security/ConfigurationValidator.js +490 -0
- package/dist/security/DeploymentManager.js +208 -0
- package/dist/security/SecretGenerator.js +142 -0
- package/dist/security/SecurityCLI.js +228 -0
- package/dist/security/index.js +51 -0
- package/dist/security/patterns/environment-rules.js +66 -0
- package/dist/security/patterns/insecure-patterns.js +21 -0
- package/dist/service-management/ConfirmationEngine.js +411 -0
- package/dist/service-management/ErrorTracker.js +294 -0
- package/dist/service-management/GenerationEngine.js +3109 -0
- package/dist/service-management/InputCollector.js +237 -0
- package/dist/service-management/ServiceCreator.js +229 -0
- package/dist/service-management/ServiceInitializer.js +448 -0
- package/dist/service-management/ServiceOrchestrator.js +638 -0
- package/dist/service-management/handlers/ConfigMutator.js +130 -0
- package/dist/service-management/handlers/ConfirmationHandler.js +71 -0
- package/dist/service-management/handlers/GenerationHandler.js +80 -0
- package/dist/service-management/handlers/InputHandler.js +59 -0
- package/dist/service-management/handlers/ValidationHandler.js +203 -0
- package/dist/service-management/index.js +7 -0
- package/dist/services/GenericDataService.js +488 -0
- package/dist/shared/cloudflare/domain-discovery.js +562 -0
- package/dist/shared/cloudflare/domain-manager.js +912 -0
- package/dist/shared/cloudflare/index.js +8 -0
- package/dist/shared/cloudflare/ops.js +387 -0
- package/dist/shared/config/cache.js +1167 -0
- package/dist/shared/config/command-config-manager.js +174 -0
- package/dist/shared/config/customer-cli.js +258 -0
- package/dist/shared/config/index.js +9 -0
- package/dist/shared/config/manager.js +289 -0
- package/dist/shared/database/connection-manager.js +338 -0
- package/dist/shared/database/index.js +7 -0
- package/dist/shared/database/orchestrator.js +632 -0
- package/dist/shared/deployment/auditor.js +971 -0
- package/dist/shared/deployment/index.js +10 -0
- package/dist/shared/deployment/rollback-manager.js +523 -0
- package/dist/shared/deployment/validator.js +558 -0
- package/dist/shared/index.js +32 -0
- package/dist/shared/monitoring/health-checker.js +250 -0
- package/dist/shared/monitoring/index.js +8 -0
- package/dist/shared/monitoring/memory-manager.js +382 -0
- package/dist/shared/monitoring/production-monitor.js +390 -0
- package/dist/shared/production-tester/api-tester.js +80 -0
- package/dist/shared/production-tester/auth-tester.js +129 -0
- package/dist/shared/production-tester/core.js +217 -0
- package/dist/shared/production-tester/database-tester.js +105 -0
- package/dist/shared/production-tester/index.js +74 -0
- package/dist/shared/production-tester/load-tester.js +120 -0
- package/dist/shared/production-tester/performance-tester.js +105 -0
- package/dist/shared/security/api-token-manager.js +296 -0
- package/dist/shared/security/index.js +8 -0
- package/dist/shared/security/secret-generator.js +918 -0
- package/dist/shared/security/secure-token-manager.js +379 -0
- package/dist/shared/utils/error-recovery.js +240 -0
- package/dist/shared/utils/graceful-shutdown-manager.js +380 -0
- package/dist/shared/utils/index.js +9 -0
- package/dist/shared/utils/interactive-prompts.js +134 -0
- package/dist/shared/utils/rate-limiter.js +249 -0
- package/dist/utils/ErrorHandler.js +173 -0
- package/dist/utils/deployment/config-cache.js +1160 -0
- package/dist/utils/deployment/index.js +6 -0
- package/dist/utils/deployment/interactive-prompts.js +97 -0
- package/dist/utils/deployment/secret-generator.js +896 -0
- package/dist/utils/dirname-helper.js +35 -0
- package/dist/utils/domain-config.js +159 -0
- package/dist/utils/error-recovery.js +240 -0
- package/dist/utils/esm-helper.js +52 -0
- package/dist/utils/framework-config.js +481 -0
- package/dist/utils/graceful-shutdown-manager.js +379 -0
- package/dist/utils/health-checker.js +114 -0
- package/dist/utils/index.js +36 -0
- package/dist/utils/prompt-handler.js +98 -0
- package/dist/utils/usage-tracker.js +252 -0
- package/dist/utils/validation.js +112 -0
- package/dist/version/VersionDetector.js +723 -0
- package/dist/worker/index.js +4 -0
- package/dist/worker/integration.js +332 -0
- package/docs/FRAMEWORK-ARCHITECTURE-OVERVIEW.md +206 -0
- package/docs/INTEGRATION_GUIDE.md +2045 -0
- package/docs/README.md +82 -0
- package/docs/SECURITY.md +242 -0
- package/docs/deployment/deployment-guide.md +540 -0
- package/docs/overview.md +280 -0
- package/package.json +176 -0
- package/types/index.d.ts +575 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { schemaManager } from '../schema/SchemaManager.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generic Data Service
|
|
5
|
+
* Provides CRUD operations for any configured data model
|
|
6
|
+
*/
|
|
7
|
+
export class GenericDataService {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new GenericDataService instance
|
|
10
|
+
* @param {Object} d1Client - D1 database client
|
|
11
|
+
* @param {string} modelName - Name of the model to work with
|
|
12
|
+
* @param {Object} options - Configuration options
|
|
13
|
+
*/
|
|
14
|
+
constructor(d1Client, modelName, options = {}) {
|
|
15
|
+
this.d1Client = d1Client;
|
|
16
|
+
this.modelName = modelName;
|
|
17
|
+
this.schema = schemaManager.getModel(modelName);
|
|
18
|
+
if (!this.schema) {
|
|
19
|
+
throw new Error(`Model '${modelName}' not registered. Use schemaManager.registerModel() first.`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Query result caching
|
|
23
|
+
this.queryCache = new Map();
|
|
24
|
+
this.cacheEnabled = options.cacheEnabled !== false;
|
|
25
|
+
this.defaultCacheTTL = options.defaultCacheTTL || 300; // 5 minutes default
|
|
26
|
+
|
|
27
|
+
// Security configuration
|
|
28
|
+
this.securityConfig = {
|
|
29
|
+
maxQueryLimit: options.maxQueryLimit || 1000,
|
|
30
|
+
// Maximum records per query
|
|
31
|
+
defaultQueryLimit: options.defaultQueryLimit || 100,
|
|
32
|
+
// Default records per query
|
|
33
|
+
maxBulkOperationSize: options.maxBulkOperationSize || 100,
|
|
34
|
+
// Maximum records in bulk operations
|
|
35
|
+
enablePagination: options.enablePagination !== false,
|
|
36
|
+
// Enable pagination by default
|
|
37
|
+
...options.securityConfig
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate cache key for query
|
|
43
|
+
* @param {string} operation - Operation type
|
|
44
|
+
* @param {Object} params - Query parameters
|
|
45
|
+
* @returns {string} Cache key
|
|
46
|
+
*/
|
|
47
|
+
generateCacheKey(operation, params = {}) {
|
|
48
|
+
return `${this.modelName}:${operation}:${JSON.stringify(params)}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get cached result if valid
|
|
53
|
+
* @param {string} cacheKey - Cache key
|
|
54
|
+
* @returns {any|null} Cached result or null if expired/not found
|
|
55
|
+
*/
|
|
56
|
+
getCachedResult(cacheKey) {
|
|
57
|
+
if (!this.cacheEnabled) return null;
|
|
58
|
+
const cached = this.queryCache.get(cacheKey);
|
|
59
|
+
if (!cached) return null;
|
|
60
|
+
|
|
61
|
+
// Check if expired
|
|
62
|
+
if (Date.now() > cached.expiresAt) {
|
|
63
|
+
this.queryCache.delete(cacheKey);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return cached.data;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Cache query result
|
|
71
|
+
* @param {string} cacheKey - Cache key
|
|
72
|
+
* @param {any} data - Data to cache
|
|
73
|
+
* @param {number} ttl - Time to live in seconds
|
|
74
|
+
*/
|
|
75
|
+
setCachedResult(cacheKey, data, ttl = null) {
|
|
76
|
+
if (!this.cacheEnabled) return;
|
|
77
|
+
const expiresAt = Date.now() + (ttl || this.defaultCacheTTL) * 1000;
|
|
78
|
+
this.queryCache.set(cacheKey, {
|
|
79
|
+
data,
|
|
80
|
+
expiresAt,
|
|
81
|
+
createdAt: Date.now()
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Clear cache for specific operations or all cache
|
|
87
|
+
* @param {string} operation - Optional: specific operation to clear
|
|
88
|
+
* @param {Object} params - Optional: specific parameters to clear
|
|
89
|
+
*/
|
|
90
|
+
clearCache(operation = null, params = null) {
|
|
91
|
+
if (operation && params) {
|
|
92
|
+
// Clear specific cache entry
|
|
93
|
+
const cacheKey = this.generateCacheKey(operation, params);
|
|
94
|
+
this.queryCache.delete(cacheKey);
|
|
95
|
+
} else if (operation) {
|
|
96
|
+
// Clear all cache entries for an operation
|
|
97
|
+
for (const [key] of this.queryCache) {
|
|
98
|
+
if (key.startsWith(`${this.modelName}:${operation}:`)) {
|
|
99
|
+
this.queryCache.delete(key);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
// Clear all cache
|
|
104
|
+
this.queryCache.clear();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Format validation errors into a human-readable message
|
|
110
|
+
* @param {Array} errors - Validation errors array
|
|
111
|
+
* @returns {string} Formatted error message
|
|
112
|
+
*/
|
|
113
|
+
formatValidationErrors(errors = []) {
|
|
114
|
+
if (!Array.isArray(errors) || errors.length === 0) {
|
|
115
|
+
return 'Unknown validation error';
|
|
116
|
+
}
|
|
117
|
+
const details = errors.map(error => {
|
|
118
|
+
if (!error) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
if (typeof error === 'string') {
|
|
122
|
+
return error;
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(error)) {
|
|
125
|
+
return error.map(item => this.formatValidationErrors([item])).join('; ');
|
|
126
|
+
}
|
|
127
|
+
if (typeof error === 'object') {
|
|
128
|
+
const field = error.field || error.path || error.name || 'field';
|
|
129
|
+
if (error.message) {
|
|
130
|
+
return `${field}: ${error.message}`;
|
|
131
|
+
}
|
|
132
|
+
if (error.reason) {
|
|
133
|
+
return `${field}: ${error.reason}`;
|
|
134
|
+
}
|
|
135
|
+
if (error.code) {
|
|
136
|
+
return `${field}: ${error.code}`;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
return `${field}: ${JSON.stringify(error)}`;
|
|
140
|
+
} catch {
|
|
141
|
+
return `${field}: ${String(error)}`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return String(error);
|
|
145
|
+
}).filter(Boolean);
|
|
146
|
+
return details.length > 0 ? details.join('; ') : 'Unknown validation error';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create a new record
|
|
151
|
+
* @param {Object} data - Record data
|
|
152
|
+
* @returns {Promise<Object>} Created record
|
|
153
|
+
*/
|
|
154
|
+
async create(data) {
|
|
155
|
+
// Validate data
|
|
156
|
+
const validation = schemaManager.validateData(this.modelName, data);
|
|
157
|
+
if (!validation.valid) {
|
|
158
|
+
const errorMessage = this.formatValidationErrors(validation.errors);
|
|
159
|
+
throw new Error(`Validation failed: ${errorMessage}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Generate ID if not provided
|
|
163
|
+
const recordData = {
|
|
164
|
+
...validation.data
|
|
165
|
+
};
|
|
166
|
+
if (!recordData.id) {
|
|
167
|
+
recordData.id = this.d1Client.generateId();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Set timestamps
|
|
171
|
+
const now = this.d1Client.getCurrentTimestamp();
|
|
172
|
+
if (this.schema.columns.created_at && !recordData.created_at) {
|
|
173
|
+
recordData.created_at = now;
|
|
174
|
+
}
|
|
175
|
+
if (this.schema.columns.updated_at && !recordData.updated_at) {
|
|
176
|
+
recordData.updated_at = now;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Generate SQL
|
|
180
|
+
const {
|
|
181
|
+
sql,
|
|
182
|
+
params
|
|
183
|
+
} = schemaManager.generateSQL(this.modelName, 'create', recordData);
|
|
184
|
+
|
|
185
|
+
// Execute
|
|
186
|
+
const result = await this.d1Client.run(sql, params);
|
|
187
|
+
if (result.success) {
|
|
188
|
+
// Clear relevant caches after successful creation
|
|
189
|
+
this.clearCache('findAll');
|
|
190
|
+
this.clearCache('find');
|
|
191
|
+
return {
|
|
192
|
+
...recordData,
|
|
193
|
+
id: recordData.id
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
throw new Error('Failed to create record');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Find records by criteria with advanced options
|
|
201
|
+
* @param {Object} criteria - Search criteria
|
|
202
|
+
* @param {Array} include - Relationships to include
|
|
203
|
+
* @param {Array} fields - Fields to select
|
|
204
|
+
* @returns {Promise<Array>} Found records
|
|
205
|
+
*/
|
|
206
|
+
async find(criteria = {}, include = [], fields = null) {
|
|
207
|
+
const cacheKey = this.generateCacheKey('find', {
|
|
208
|
+
criteria,
|
|
209
|
+
include,
|
|
210
|
+
fields
|
|
211
|
+
});
|
|
212
|
+
const cached = this.getCachedResult(cacheKey);
|
|
213
|
+
if (cached !== null) {
|
|
214
|
+
return cached;
|
|
215
|
+
}
|
|
216
|
+
let result;
|
|
217
|
+
if (include.length > 0) {
|
|
218
|
+
result = await this.findWithRelations(criteria, include, fields);
|
|
219
|
+
} else {
|
|
220
|
+
const {
|
|
221
|
+
sql,
|
|
222
|
+
params
|
|
223
|
+
} = schemaManager.generateSQL(this.modelName, 'read', {
|
|
224
|
+
where: criteria,
|
|
225
|
+
fields
|
|
226
|
+
});
|
|
227
|
+
result = await this.d1Client.all(sql, params);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Cache the result
|
|
231
|
+
this.setCachedResult(cacheKey, result);
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Find all records with pagination and security limits
|
|
237
|
+
* @param {Object} options - Query options
|
|
238
|
+
* @param {number} options.limit - Maximum records to return (default: configured default, max: configured max)
|
|
239
|
+
* @param {number} options.offset - Number of records to skip (default: 0)
|
|
240
|
+
* @param {Object} options.orderBy - Sort options {field: 'asc'|'desc'}
|
|
241
|
+
* @param {Object} options.where - Filter criteria
|
|
242
|
+
* @returns {Promise<Object>} Paginated result with data, total, limit, offset
|
|
243
|
+
*/
|
|
244
|
+
async findAll(options = {}) {
|
|
245
|
+
// Apply security limits
|
|
246
|
+
const requestedLimit = options.limit !== undefined ? options.limit : this.securityConfig.defaultQueryLimit;
|
|
247
|
+
|
|
248
|
+
// Validate limit before applying security constraints
|
|
249
|
+
if (requestedLimit <= 0) {
|
|
250
|
+
throw new Error(`Invalid limit: ${requestedLimit}. Must be positive.`);
|
|
251
|
+
}
|
|
252
|
+
const limit = Math.min(requestedLimit, this.securityConfig.maxQueryLimit);
|
|
253
|
+
const offset = Math.max(0, options.offset || 0);
|
|
254
|
+
|
|
255
|
+
// Prevent excessive offset (basic protection against very large offsets)
|
|
256
|
+
if (offset > 1000000) {
|
|
257
|
+
throw new Error(`Offset too large: ${offset}. Maximum allowed offset is 1,000,000.`);
|
|
258
|
+
}
|
|
259
|
+
const queryOptions = {
|
|
260
|
+
limit,
|
|
261
|
+
offset,
|
|
262
|
+
orderBy: options.orderBy,
|
|
263
|
+
where: options.where || {}
|
|
264
|
+
};
|
|
265
|
+
const cacheKey = this.generateCacheKey('findAll', queryOptions);
|
|
266
|
+
const cached = this.getCachedResult(cacheKey);
|
|
267
|
+
if (cached !== null) {
|
|
268
|
+
return cached;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Build query with pagination
|
|
272
|
+
let sql = `SELECT * FROM ${this.schema.tableName}`;
|
|
273
|
+
let params = [];
|
|
274
|
+
|
|
275
|
+
// Add WHERE clause if provided
|
|
276
|
+
if (options.where && Object.keys(options.where).length > 0) {
|
|
277
|
+
const conditions = [];
|
|
278
|
+
Object.entries(options.where).forEach(([key, value]) => {
|
|
279
|
+
conditions.push(`${key} = ?`);
|
|
280
|
+
params.push(value);
|
|
281
|
+
});
|
|
282
|
+
sql += ` WHERE ${conditions.join(' AND ')}`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Add ORDER BY if provided
|
|
286
|
+
if (options.orderBy && Object.keys(options.orderBy).length > 0) {
|
|
287
|
+
const orderClauses = [];
|
|
288
|
+
Object.entries(options.orderBy).forEach(([field, direction]) => {
|
|
289
|
+
orderClauses.push(`${field} ${direction.toUpperCase()}`);
|
|
290
|
+
});
|
|
291
|
+
sql += ` ORDER BY ${orderClauses.join(', ')}`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Add LIMIT and OFFSET
|
|
295
|
+
sql += ` LIMIT ? OFFSET ?`;
|
|
296
|
+
params.push(limit, offset);
|
|
297
|
+
|
|
298
|
+
// Execute the query
|
|
299
|
+
const data = await this.d1Client.all(sql, params);
|
|
300
|
+
|
|
301
|
+
// Get total count for pagination (without LIMIT/OFFSET)
|
|
302
|
+
let countSql = `SELECT COUNT(*) as total FROM ${this.schema.tableName}`;
|
|
303
|
+
let countParams = [];
|
|
304
|
+
if (options.where && Object.keys(options.where).length > 0) {
|
|
305
|
+
const conditions = [];
|
|
306
|
+
Object.entries(options.where).forEach(([key, value]) => {
|
|
307
|
+
conditions.push(`${key} = ?`);
|
|
308
|
+
countParams.push(value);
|
|
309
|
+
});
|
|
310
|
+
countSql += ` WHERE ${conditions.join(' AND ')}`;
|
|
311
|
+
}
|
|
312
|
+
const countResult = await this.d1Client.first(countSql, countParams);
|
|
313
|
+
const total = countResult ? countResult.total : 0;
|
|
314
|
+
const result = {
|
|
315
|
+
data,
|
|
316
|
+
pagination: {
|
|
317
|
+
total,
|
|
318
|
+
limit,
|
|
319
|
+
offset,
|
|
320
|
+
hasNext: offset + limit < total,
|
|
321
|
+
hasPrev: offset > 0,
|
|
322
|
+
totalPages: Math.ceil(total / limit),
|
|
323
|
+
currentPage: Math.floor(offset / limit) + 1
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Cache the result
|
|
328
|
+
this.setCachedResult(cacheKey, result);
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Update a record
|
|
334
|
+
* @param {string} id - Record ID
|
|
335
|
+
* @param {Object} updates - Fields to update
|
|
336
|
+
* @returns {Promise<Object>} Updated record
|
|
337
|
+
*/
|
|
338
|
+
async update(id, updates) {
|
|
339
|
+
// Validate updates
|
|
340
|
+
const validation = schemaManager.validateData(this.modelName, updates);
|
|
341
|
+
if (!validation.valid) {
|
|
342
|
+
const errorMessage = this.formatValidationErrors(validation.errors);
|
|
343
|
+
throw new Error(`Validation failed: ${errorMessage}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Set updated timestamp
|
|
347
|
+
const updateData = {
|
|
348
|
+
...validation.data,
|
|
349
|
+
id
|
|
350
|
+
};
|
|
351
|
+
if (this.schema.columns.updated_at) {
|
|
352
|
+
updateData.updated_at = this.d1Client.getCurrentTimestamp();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Generate SQL
|
|
356
|
+
const {
|
|
357
|
+
sql,
|
|
358
|
+
params
|
|
359
|
+
} = schemaManager.generateSQL(this.modelName, 'update', updateData);
|
|
360
|
+
|
|
361
|
+
// Execute
|
|
362
|
+
const result = await this.d1Client.run(sql, params);
|
|
363
|
+
if (result.success) {
|
|
364
|
+
// Return updated record
|
|
365
|
+
return await this.findById(id);
|
|
366
|
+
} else {
|
|
367
|
+
throw new Error('Failed to update record');
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Delete a record
|
|
373
|
+
* @param {string} id - Record ID
|
|
374
|
+
* @returns {Promise<boolean>} Success status
|
|
375
|
+
*/
|
|
376
|
+
async delete(id) {
|
|
377
|
+
const {
|
|
378
|
+
sql,
|
|
379
|
+
params
|
|
380
|
+
} = schemaManager.generateSQL(this.modelName, 'delete', {
|
|
381
|
+
id
|
|
382
|
+
});
|
|
383
|
+
const result = await this.d1Client.run(sql, params);
|
|
384
|
+
return result.success;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Count records matching criteria
|
|
389
|
+
* @param {Object} criteria - Count criteria
|
|
390
|
+
* @returns {Promise<number>} Record count
|
|
391
|
+
*/
|
|
392
|
+
async count(criteria = {}) {
|
|
393
|
+
let sql = `SELECT COUNT(*) as count FROM ${this.schema.tableName}`;
|
|
394
|
+
let params = [];
|
|
395
|
+
if (Object.keys(criteria).length > 0) {
|
|
396
|
+
const conditions = [];
|
|
397
|
+
Object.entries(criteria).forEach(([key, value]) => {
|
|
398
|
+
conditions.push(`${key} = ?`);
|
|
399
|
+
params.push(value);
|
|
400
|
+
});
|
|
401
|
+
sql += ` WHERE ${conditions.join(' AND ')}`;
|
|
402
|
+
}
|
|
403
|
+
const result = await this.d1Client.first(sql, params);
|
|
404
|
+
return result?.count || 0;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Check if record exists
|
|
409
|
+
* @param {string} id - Record ID
|
|
410
|
+
* @returns {Promise<boolean>} Existence status
|
|
411
|
+
*/
|
|
412
|
+
async exists(id) {
|
|
413
|
+
const record = await this.findById(id);
|
|
414
|
+
return !!record;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Get paginated results
|
|
419
|
+
* @param {Object} criteria - Search criteria
|
|
420
|
+
* @param {Object} pagination - Pagination options
|
|
421
|
+
* @returns {Promise<Object>} Paginated results
|
|
422
|
+
*/
|
|
423
|
+
async paginate(criteria = {}, pagination = {}) {
|
|
424
|
+
const {
|
|
425
|
+
page = 1,
|
|
426
|
+
limit = 10
|
|
427
|
+
} = pagination;
|
|
428
|
+
const offset = (page - 1) * limit;
|
|
429
|
+
|
|
430
|
+
// Get total count
|
|
431
|
+
const total = await this.count(criteria);
|
|
432
|
+
|
|
433
|
+
// Get paginated results
|
|
434
|
+
let sql = `SELECT * FROM ${this.schema.tableName}`;
|
|
435
|
+
let params = [];
|
|
436
|
+
if (Object.keys(criteria).length > 0) {
|
|
437
|
+
const conditions = [];
|
|
438
|
+
Object.entries(criteria).forEach(([key, value]) => {
|
|
439
|
+
conditions.push(`${key} = ?`);
|
|
440
|
+
params.push(value);
|
|
441
|
+
});
|
|
442
|
+
sql += ` WHERE ${conditions.join(' AND ')}`;
|
|
443
|
+
}
|
|
444
|
+
sql += ` LIMIT ? OFFSET ?`;
|
|
445
|
+
params.push(limit, offset);
|
|
446
|
+
const records = await this.d1Client.all(sql, params);
|
|
447
|
+
return {
|
|
448
|
+
data: records,
|
|
449
|
+
pagination: {
|
|
450
|
+
page,
|
|
451
|
+
limit,
|
|
452
|
+
total,
|
|
453
|
+
totalPages: Math.ceil(total / limit),
|
|
454
|
+
hasNext: page * limit < total,
|
|
455
|
+
hasPrev: page > 1
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Factory function to create a data service for a model
|
|
463
|
+
* @param {Object} d1Client - D1 database client
|
|
464
|
+
* @param {string} modelName - Name of the model
|
|
465
|
+
* @returns {GenericDataService} Data service instance
|
|
466
|
+
*/
|
|
467
|
+
/**
|
|
468
|
+
* Factory function to create a data service for a model
|
|
469
|
+
* @param {Object} d1Client - D1 database client
|
|
470
|
+
* @param {string} modelName - Name of the model
|
|
471
|
+
* @returns {GenericDataService} Data service instance
|
|
472
|
+
*/
|
|
473
|
+
export function createDataService(d1Client, modelName) {
|
|
474
|
+
return new GenericDataService(d1Client, modelName);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Get all available data services
|
|
479
|
+
* @returns {Object} Map of model names to service instances
|
|
480
|
+
*/
|
|
481
|
+
export function getAllDataServices() {
|
|
482
|
+
const services = {};
|
|
483
|
+
// Note: This would require schemaManager to have getAllModels method
|
|
484
|
+
// for (const [modelName] of schemaManager.getAllModels()) {
|
|
485
|
+
// services[modelName] = new GenericDataService(d1Client, modelName);
|
|
486
|
+
// }
|
|
487
|
+
return services;
|
|
488
|
+
}
|