@africode/core 5.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/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
- package/LICENSE +623 -0
- package/README.md +442 -0
- package/bin/africode.js +73 -0
- package/bin/africode.js.1758507140 +343 -0
- package/bin/cli.ts +83 -0
- package/bin/create-africode.js +158 -0
- package/bin/scaffold.ts +219 -0
- package/components/accordion.js +183 -0
- package/components/alert.js +131 -0
- package/components/auth.js +172 -0
- package/components/avatar.js +117 -0
- package/components/badge.js +104 -0
- package/components/base.d.ts +139 -0
- package/components/base.js +184 -0
- package/components/button.js +164 -0
- package/components/card.js +137 -0
- package/components/cultural-card.js +243 -0
- package/components/divider.js +83 -0
- package/components/dropdown.js +171 -0
- package/components/error-boundary.js +155 -0
- package/components/form.js +131 -0
- package/components/grid.js +273 -0
- package/components/hero.js +138 -0
- package/components/icon.js +36 -0
- package/components/index.js +57 -0
- package/components/input.js +256 -0
- package/components/kanga-card.js +185 -0
- package/components/language-switcher.js +108 -0
- package/components/loader.js +80 -0
- package/components/modal.js +262 -0
- package/components/motion.js +84 -0
- package/components/navbar.js +236 -0
- package/components/pattern-showcase.js +225 -0
- package/components/progress.js +134 -0
- package/components/react.js +111 -0
- package/components/section.js +54 -0
- package/components/select.js +322 -0
- package/components/sidebar.js +180 -0
- package/components/skeleton.js +85 -0
- package/components/table.js +181 -0
- package/components/tabs.js +202 -0
- package/components/theme-toggle.js +82 -0
- package/components/toast.js +139 -0
- package/components/tooltip.js +167 -0
- package/core/a2ui-schema-manager.js +344 -0
- package/core/a2ui.js +431 -0
- package/core/bun-runtime.js +799 -0
- package/core/cli/commands/add.js +23 -0
- package/core/cli/commands/audit.js +58 -0
- package/core/cli/commands/build.js +137 -0
- package/core/cli/commands/create-plugin.js +241 -0
- package/core/cli/commands/dev.js +228 -0
- package/core/cli/commands/lint.js +23 -0
- package/core/cli/commands/test.js +34 -0
- package/core/cli/migrator.js +71 -0
- package/core/cli/ui.js +46 -0
- package/core/compliance.js +628 -0
- package/core/config.js +263 -0
- package/core/db-advanced.js +481 -0
- package/core/db.js +284 -0
- package/core/enhanced-hmr.js +404 -0
- package/core/errors.js +222 -0
- package/core/file-router.js +290 -0
- package/core/heartbeat.js +64 -0
- package/core/hmr-client.js +204 -0
- package/core/hmr.js +196 -0
- package/core/html.d.ts +116 -0
- package/core/html.js +160 -0
- package/core/hydration.js +52 -0
- package/core/lipa-namba-journey.js +572 -0
- package/core/motion.js +106 -0
- package/core/nida-cig-middleware.js +455 -0
- package/core/patterns.d.ts +124 -0
- package/core/patterns.js +833 -0
- package/core/plugins/index.js +312 -0
- package/core/router.js +387 -0
- package/core/sdk-client.js +62 -0
- package/core/sdk.d.ts +133 -0
- package/core/sdk.js +123 -0
- package/core/seo.js +76 -0
- package/core/server/auth-endpoints.js +339 -0
- package/core/server/auth.js +180 -0
- package/core/server/csrf.js +206 -0
- package/core/server/db.js +39 -0
- package/core/server/middleware.js +324 -0
- package/core/server/rate-limit.js +238 -0
- package/core/server/render.js +69 -0
- package/core/server/router.js +120 -0
- package/core/shim.js +28 -0
- package/core/state.d.ts +86 -0
- package/core/state.js +242 -0
- package/core/store.d.ts +122 -0
- package/core/store.js +61 -0
- package/core/validation.d.ts +233 -0
- package/core/validation.js +590 -0
- package/core/websocket.js +639 -0
- package/dist/africode.js +2905 -0
- package/dist/africode.js.map +61 -0
- package/dist/build-info.json +23 -0
- package/dist/components.js +2888 -0
- package/dist/components.js.map +58 -0
- package/dist/styles/africanity.css +322 -0
- package/dist/styles/typography.css +141 -0
- package/docs/IDE-Guide.md +50 -0
- package/package.json +110 -0
- package/src/index.ts +196 -0
- package/styles/africanity.css +322 -0
- package/styles/typography.css +141 -0
- package/templates/starter/.env.example +15 -0
- package/templates/starter/africode.config.js +40 -0
- package/templates/starter/package.json +14 -0
- package/templates/starter/src/pages/index.html +46 -0
- package/templates/starter/src/pages/index.js +32 -0
- package/templates/starter/src/styles/main.css +4 -0
- package/templates/starter-3d/.env.example +7 -0
- package/templates/starter-3d/africode.config.js +29 -0
- package/templates/starter-3d/components/af-model-viewer.js +125 -0
- package/templates/starter-3d/package.json +15 -0
- package/templates/starter-3d/src/pages/index.html +46 -0
- package/templates/starter-3d/src/pages/index.js +50 -0
- package/templates/starter-3d/src/styles/main.css +4 -0
- package/templates/starter-react/.env.example +15 -0
- package/templates/starter-react/africode.config.js +40 -0
- package/templates/starter-react/package.json +16 -0
- package/templates/starter-react/src/pages/index.html +46 -0
- package/templates/starter-react/src/pages/index.js +68 -0
- package/templates/starter-react/src/styles/main.css +4 -0
- package/templates/starter-tailwind/.env.example +15 -0
- package/templates/starter-tailwind/africode.config.js +40 -0
- package/templates/starter-tailwind/package.json +20 -0
- package/templates/starter-tailwind/src/pages/index.html +46 -0
- package/templates/starter-tailwind/src/pages/index.js +37 -0
- package/templates/starter-tailwind/src/styles/main.css +4 -0
- package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
- package/templates/starter-tailwind/src/tailwind-loader.js +30 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Advanced ORM
|
|
3
|
+
* Query builder, relationships, transactions, and migrations
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Fluent query builder (.where, .select, .join, .orderBy, .limit)
|
|
7
|
+
* - Relationship definitions (.hasMany, .belongsTo)
|
|
8
|
+
* - Transaction support for ACID operations
|
|
9
|
+
* - Migration runner for schema versioning
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Database } from 'bun:sqlite';
|
|
13
|
+
import { query as dbQuery, get as dbGet, run as dbRun } from './db.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Query Builder
|
|
17
|
+
* Fluent interface for building SQL queries
|
|
18
|
+
*/
|
|
19
|
+
export class QueryBuilder {
|
|
20
|
+
constructor(table, db = null) {
|
|
21
|
+
this.table = table;
|
|
22
|
+
this.db = db;
|
|
23
|
+
this.selectFields = ['*'];
|
|
24
|
+
this.whereConditions = [];
|
|
25
|
+
this.joinClauses = [];
|
|
26
|
+
this.orderByClause = null;
|
|
27
|
+
this.limitClause = null;
|
|
28
|
+
this.offsetClause = null;
|
|
29
|
+
this.bindings = [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Select specific columns
|
|
34
|
+
*/
|
|
35
|
+
select(...fields) {
|
|
36
|
+
this.selectFields = fields.length > 0 ? fields : ['*'];
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Add WHERE condition
|
|
42
|
+
*/
|
|
43
|
+
where(field, operator, value = undefined) {
|
|
44
|
+
if (value === undefined) {
|
|
45
|
+
// where(field, value) syntax
|
|
46
|
+
value = operator;
|
|
47
|
+
operator = '=';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.whereConditions.push({ field, operator, value });
|
|
51
|
+
this.bindings.push(value);
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Add OR WHERE condition
|
|
57
|
+
*/
|
|
58
|
+
orWhere(field, operator, value = undefined) {
|
|
59
|
+
if (value === undefined) {
|
|
60
|
+
value = operator;
|
|
61
|
+
operator = '=';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.whereConditions.push({ field, operator, value, or: true });
|
|
65
|
+
this.bindings.push(value);
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Add JOIN clause
|
|
71
|
+
*/
|
|
72
|
+
join(table, field1, operator, field2) {
|
|
73
|
+
this.joinClauses.push({ table, field1, operator, field2, type: 'INNER' });
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Add LEFT JOIN clause
|
|
79
|
+
*/
|
|
80
|
+
leftJoin(table, field1, operator, field2) {
|
|
81
|
+
this.joinClauses.push({ table, field1, operator, field2, type: 'LEFT' });
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Order by column
|
|
87
|
+
*/
|
|
88
|
+
orderBy(field, direction = 'ASC') {
|
|
89
|
+
this.orderByClause = `${field} ${direction}`;
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Limit results
|
|
95
|
+
*/
|
|
96
|
+
limit(count) {
|
|
97
|
+
this.limitClause = count;
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Offset results
|
|
103
|
+
*/
|
|
104
|
+
offset(count) {
|
|
105
|
+
this.offsetClause = count;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Build SQL query
|
|
111
|
+
*/
|
|
112
|
+
build() {
|
|
113
|
+
let sql = `SELECT ${this.selectFields.join(', ')} FROM ${this.table}`;
|
|
114
|
+
|
|
115
|
+
// Add JOINs
|
|
116
|
+
for (const join of this.joinClauses) {
|
|
117
|
+
sql += ` ${join.type} JOIN ${join.table} ON ${join.field1} ${join.operator} ${join.field2}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Add WHERE conditions
|
|
121
|
+
if (this.whereConditions.length > 0) {
|
|
122
|
+
const conditions = this.whereConditions.map((cond, idx) => {
|
|
123
|
+
const prefix = idx > 0 && cond.or ? 'OR' : idx > 0 ? 'AND' : '';
|
|
124
|
+
return `${prefix} ${cond.field} ${cond.operator} ?`.trim();
|
|
125
|
+
});
|
|
126
|
+
sql += ` WHERE ${conditions.join(' ')}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Add ORDER BY
|
|
130
|
+
if (this.orderByClause) {
|
|
131
|
+
sql += ` ORDER BY ${this.orderByClause}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Add LIMIT
|
|
135
|
+
if (this.limitClause !== null) {
|
|
136
|
+
sql += ` LIMIT ${this.limitClause}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Add OFFSET
|
|
140
|
+
if (this.offsetClause !== null) {
|
|
141
|
+
sql += ` OFFSET ${this.offsetClause}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { sql, bindings: this.bindings };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Execute query and get all results
|
|
149
|
+
*/
|
|
150
|
+
all() {
|
|
151
|
+
const { sql, bindings } = this.build();
|
|
152
|
+
return dbQuery(sql, bindings);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Execute query and get first result
|
|
157
|
+
*/
|
|
158
|
+
first() {
|
|
159
|
+
const { sql, bindings } = this.build();
|
|
160
|
+
return dbGet(sql, bindings);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Execute query and get count
|
|
165
|
+
*/
|
|
166
|
+
count() {
|
|
167
|
+
const originalSelect = this.selectFields;
|
|
168
|
+
this.selectFields = ['COUNT(*) as count'];
|
|
169
|
+
const result = this.first();
|
|
170
|
+
this.selectFields = originalSelect;
|
|
171
|
+
return result?.count || 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get SQL and bindings without executing
|
|
176
|
+
*/
|
|
177
|
+
toSql() {
|
|
178
|
+
return this.build();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Model Base Class
|
|
184
|
+
* Provides common ORM functionality
|
|
185
|
+
*/
|
|
186
|
+
export class Model {
|
|
187
|
+
constructor(data = {}) {
|
|
188
|
+
this.data = data;
|
|
189
|
+
this.relationships = {};
|
|
190
|
+
this.isDirty = false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Define hasMany relationship
|
|
195
|
+
*/
|
|
196
|
+
static hasMany(relationName, foreignModel, foreignKey, localKey = 'id') {
|
|
197
|
+
return function() {
|
|
198
|
+
const localValue = this.data[localKey];
|
|
199
|
+
if (!localValue) return [];
|
|
200
|
+
const related = new foreignModel();
|
|
201
|
+
return new QueryBuilder(foreignModel.tableName)
|
|
202
|
+
.where(foreignKey, localValue)
|
|
203
|
+
.all();
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Define belongsTo relationship
|
|
209
|
+
*/
|
|
210
|
+
static belongsTo(relationName, parentModel, foreignKey, parentKey = 'id') {
|
|
211
|
+
return function() {
|
|
212
|
+
const foreignValue = this.data[foreignKey];
|
|
213
|
+
if (!foreignValue) return null;
|
|
214
|
+
const parent = new parentModel();
|
|
215
|
+
return new QueryBuilder(parentModel.tableName)
|
|
216
|
+
.where(parentKey, foreignValue)
|
|
217
|
+
.first();
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get a relationship
|
|
223
|
+
*/
|
|
224
|
+
getRelation(name) {
|
|
225
|
+
if (!this.relationships[name]) {
|
|
226
|
+
throw new Error(`Relationship "${name}" not defined on model`);
|
|
227
|
+
}
|
|
228
|
+
return this.relationships[name].call(this);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Query builder for this model
|
|
233
|
+
*/
|
|
234
|
+
static query() {
|
|
235
|
+
return new QueryBuilder(this.tableName);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Find by ID
|
|
240
|
+
*/
|
|
241
|
+
static findById(id) {
|
|
242
|
+
return new QueryBuilder(this.tableName)
|
|
243
|
+
.where('id', id)
|
|
244
|
+
.first();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Find all
|
|
249
|
+
*/
|
|
250
|
+
static all() {
|
|
251
|
+
return new QueryBuilder(this.tableName).all();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Create new record
|
|
256
|
+
*/
|
|
257
|
+
static create(data) {
|
|
258
|
+
const fields = Object.keys(data);
|
|
259
|
+
const values = Object.values(data);
|
|
260
|
+
const placeholders = fields.map(() => '?').join(', ');
|
|
261
|
+
|
|
262
|
+
const sql = `INSERT INTO ${this.tableName} (${fields.join(', ')}) VALUES (${placeholders})`;
|
|
263
|
+
const result = dbRun(sql, values);
|
|
264
|
+
|
|
265
|
+
return { id: result.lastInsertRowid, ...data };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Update record
|
|
270
|
+
*/
|
|
271
|
+
static update(id, data) {
|
|
272
|
+
const fields = Object.keys(data);
|
|
273
|
+
const values = Object.values(data);
|
|
274
|
+
const setClause = fields.map(f => `${f} = ?`).join(', ');
|
|
275
|
+
|
|
276
|
+
const sql = `UPDATE ${this.tableName} SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
|
277
|
+
return dbRun(sql, [...values, id]);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Delete record
|
|
282
|
+
*/
|
|
283
|
+
static delete(id) {
|
|
284
|
+
const sql = `DELETE FROM ${this.tableName} WHERE id = ?`;
|
|
285
|
+
return dbRun(sql, [id]);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Transaction Support
|
|
291
|
+
* Provides ACID-compliant multi-step operations
|
|
292
|
+
* Note: SQLite auto-manages transactions in WAL mode, so we batch operations
|
|
293
|
+
*/
|
|
294
|
+
export class Transaction {
|
|
295
|
+
constructor(db) {
|
|
296
|
+
this.db = db;
|
|
297
|
+
this.isActive = false;
|
|
298
|
+
this.operations = [];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Begin transaction (queues operations)
|
|
303
|
+
*/
|
|
304
|
+
begin() {
|
|
305
|
+
this.isActive = true;
|
|
306
|
+
this.operations = [];
|
|
307
|
+
return this;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Add operation to transaction
|
|
312
|
+
*/
|
|
313
|
+
addOperation(sql, bindings = []) {
|
|
314
|
+
if (!this.isActive) {
|
|
315
|
+
throw new Error('Transaction not active');
|
|
316
|
+
}
|
|
317
|
+
this.operations.push({ sql, bindings });
|
|
318
|
+
return this;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Commit transaction (executes all operations)
|
|
323
|
+
*/
|
|
324
|
+
commit() {
|
|
325
|
+
if (!this.isActive) {
|
|
326
|
+
throw new Error('No active transaction');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
// Execute all operations
|
|
331
|
+
for (const op of this.operations) {
|
|
332
|
+
dbRun(op.sql, op.bindings);
|
|
333
|
+
}
|
|
334
|
+
this.isActive = false;
|
|
335
|
+
this.operations = [];
|
|
336
|
+
return true;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
this.rollback();
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Rollback transaction (clear operations)
|
|
345
|
+
*/
|
|
346
|
+
rollback() {
|
|
347
|
+
if (this.isActive) {
|
|
348
|
+
this.isActive = false;
|
|
349
|
+
this.operations = [];
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Migration Runner
|
|
356
|
+
* Handles schema versioning and migrations
|
|
357
|
+
*/
|
|
358
|
+
export class MigrationRunner {
|
|
359
|
+
constructor(db) {
|
|
360
|
+
this.db = db;
|
|
361
|
+
this.migrationsPath = new URL('../migrations', import.meta.url).pathname;
|
|
362
|
+
this.initMigrationsTable();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Initialize migrations tracking table
|
|
367
|
+
*/
|
|
368
|
+
initMigrationsTable() {
|
|
369
|
+
const sql = `
|
|
370
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
371
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
372
|
+
name TEXT UNIQUE NOT NULL,
|
|
373
|
+
executed_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
374
|
+
)
|
|
375
|
+
`;
|
|
376
|
+
dbRun(sql);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get list of executed migrations
|
|
381
|
+
*/
|
|
382
|
+
getExecutedMigrations() {
|
|
383
|
+
const sql = 'SELECT name FROM migrations ORDER BY executed_at ASC';
|
|
384
|
+
const results = dbQuery(sql);
|
|
385
|
+
return results.map(r => r.name);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Get list of pending migrations
|
|
390
|
+
*/
|
|
391
|
+
async getPendingMigrations() {
|
|
392
|
+
const executed = this.getExecutedMigrations();
|
|
393
|
+
const migrationsDir = Bun.file(this.migrationsPath);
|
|
394
|
+
|
|
395
|
+
if (!migrationsDir.exists) {
|
|
396
|
+
return [];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const allMigrations = [];
|
|
400
|
+
// In a real implementation, this would scan the migrations directory
|
|
401
|
+
return allMigrations.filter(m => !executed.includes(m));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Run a single migration
|
|
406
|
+
*/
|
|
407
|
+
runMigration(name, migrationSQL) {
|
|
408
|
+
try {
|
|
409
|
+
// Execute migration SQL
|
|
410
|
+
if (migrationSQL && migrationSQL.trim()) {
|
|
411
|
+
dbRun(migrationSQL);
|
|
412
|
+
}
|
|
413
|
+
// Record migration
|
|
414
|
+
dbRun(
|
|
415
|
+
'INSERT INTO migrations (name) VALUES (?)',
|
|
416
|
+
[name]
|
|
417
|
+
);
|
|
418
|
+
return true;
|
|
419
|
+
} catch (error) {
|
|
420
|
+
throw new Error(`Migration ${name} failed: ${error.message}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Rollback last migration
|
|
426
|
+
*/
|
|
427
|
+
rollbackMigration(name) {
|
|
428
|
+
const sql = 'DELETE FROM migrations WHERE name = ?';
|
|
429
|
+
dbRun(sql, [name]);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Run all pending migrations
|
|
434
|
+
*/
|
|
435
|
+
async runPending() {
|
|
436
|
+
const pending = await this.getPendingMigrations();
|
|
437
|
+
const results = [];
|
|
438
|
+
|
|
439
|
+
for (const migration of pending) {
|
|
440
|
+
try {
|
|
441
|
+
this.runMigration(migration, ''); // SQL would come from file
|
|
442
|
+
results.push({ name: migration, status: 'success' });
|
|
443
|
+
} catch (error) {
|
|
444
|
+
results.push({ name: migration, status: 'failed', error: error.message });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return results;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Define model relationships dynamically
|
|
454
|
+
*/
|
|
455
|
+
export function defineRelationships(Model, relationships) {
|
|
456
|
+
for (const [name, definition] of Object.entries(relationships)) {
|
|
457
|
+
if (definition.type === 'hasMany') {
|
|
458
|
+
Model.prototype[name] = Model.hasMany(
|
|
459
|
+
name,
|
|
460
|
+
definition.model,
|
|
461
|
+
definition.foreignKey,
|
|
462
|
+
definition.localKey
|
|
463
|
+
);
|
|
464
|
+
} else if (definition.type === 'belongsTo') {
|
|
465
|
+
Model.prototype[name] = Model.belongsTo(
|
|
466
|
+
name,
|
|
467
|
+
definition.model,
|
|
468
|
+
definition.foreignKey,
|
|
469
|
+
definition.parentKey
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export default {
|
|
476
|
+
QueryBuilder,
|
|
477
|
+
Model,
|
|
478
|
+
Transaction,
|
|
479
|
+
MigrationRunner,
|
|
480
|
+
defineRelationships
|
|
481
|
+
};
|