tina4-nodejs 3.0.0-rc.2
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/BENCHMARK_REPORT.md +96 -0
- package/CARBONAH.md +140 -0
- package/CLAUDE.md +599 -0
- package/COMPARISON.md +194 -0
- package/README.md +595 -0
- package/package.json +59 -0
- package/packages/cli/src/bin.ts +110 -0
- package/packages/cli/src/commands/init.ts +194 -0
- package/packages/cli/src/commands/migrate.ts +96 -0
- package/packages/cli/src/commands/migrateCreate.ts +59 -0
- package/packages/cli/src/commands/routes.ts +61 -0
- package/packages/cli/src/commands/serve.ts +58 -0
- package/packages/cli/src/commands/test.ts +83 -0
- package/packages/core/gallery/auth/meta.json +1 -0
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/login/post.ts +22 -0
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/verify/get.ts +16 -0
- package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +97 -0
- package/packages/core/gallery/database/meta.json +1 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/get.ts +13 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/post.ts +17 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/tables/get.ts +23 -0
- package/packages/core/gallery/error-overlay/meta.json +1 -0
- package/packages/core/gallery/error-overlay/src/routes/api/gallery/crash/get.ts +17 -0
- package/packages/core/gallery/orm/meta.json +1 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/get.ts +12 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/post.ts +7 -0
- package/packages/core/gallery/queue/meta.json +1 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/produce/post.ts +16 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/status/get.ts +10 -0
- package/packages/core/gallery/rest-api/meta.json +1 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/get.ts +6 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/post.ts +7 -0
- package/packages/core/gallery/templates/meta.json +1 -0
- package/packages/core/gallery/templates/src/routes/gallery/page/get.ts +15 -0
- package/packages/core/gallery/templates/src/templates/gallery_page.twig +257 -0
- package/packages/core/public/css/tina4.css +2463 -0
- package/packages/core/public/css/tina4.min.css +1 -0
- package/packages/core/public/favicon.ico +0 -0
- package/packages/core/public/images/logo.svg +5 -0
- package/packages/core/public/images/tina4-logo-icon.webp +0 -0
- package/packages/core/public/js/frond.min.js +420 -0
- package/packages/core/public/js/tina4-dev-admin.min.js +327 -0
- package/packages/core/public/js/tina4.min.js +93 -0
- package/packages/core/public/swagger/index.html +90 -0
- package/packages/core/public/swagger/oauth2-redirect.html +63 -0
- package/packages/core/src/ai.ts +359 -0
- package/packages/core/src/api.ts +248 -0
- package/packages/core/src/auth.ts +287 -0
- package/packages/core/src/cache.ts +121 -0
- package/packages/core/src/constants.ts +48 -0
- package/packages/core/src/container.ts +90 -0
- package/packages/core/src/devAdmin.ts +2024 -0
- package/packages/core/src/devMailbox.ts +316 -0
- package/packages/core/src/dotenv.ts +172 -0
- package/packages/core/src/errorOverlay.test.ts +122 -0
- package/packages/core/src/errorOverlay.ts +278 -0
- package/packages/core/src/events.ts +112 -0
- package/packages/core/src/fakeData.ts +309 -0
- package/packages/core/src/graphql.ts +812 -0
- package/packages/core/src/health.ts +31 -0
- package/packages/core/src/htmlElement.ts +172 -0
- package/packages/core/src/i18n.ts +136 -0
- package/packages/core/src/index.ts +88 -0
- package/packages/core/src/logger.ts +226 -0
- package/packages/core/src/messenger.ts +822 -0
- package/packages/core/src/middleware.ts +138 -0
- package/packages/core/src/queue.ts +481 -0
- package/packages/core/src/queueBackends/kafkaBackend.ts +348 -0
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +479 -0
- package/packages/core/src/rateLimiter.ts +107 -0
- package/packages/core/src/request.ts +189 -0
- package/packages/core/src/response.ts +146 -0
- package/packages/core/src/routeDiscovery.ts +87 -0
- package/packages/core/src/router.ts +398 -0
- package/packages/core/src/scss.ts +366 -0
- package/packages/core/src/server.ts +610 -0
- package/packages/core/src/service.ts +380 -0
- package/packages/core/src/session.ts +480 -0
- package/packages/core/src/sessionHandlers/mongoHandler.ts +286 -0
- package/packages/core/src/sessionHandlers/valkeyHandler.ts +184 -0
- package/packages/core/src/static.ts +58 -0
- package/packages/core/src/testing.ts +233 -0
- package/packages/core/src/types.ts +98 -0
- package/packages/core/src/watcher.ts +37 -0
- package/packages/core/src/websocket.ts +408 -0
- package/packages/core/src/wsdl.ts +546 -0
- package/packages/core/templates/errors/302.twig +14 -0
- package/packages/core/templates/errors/401.twig +9 -0
- package/packages/core/templates/errors/403.twig +29 -0
- package/packages/core/templates/errors/404.twig +29 -0
- package/packages/core/templates/errors/500.twig +38 -0
- package/packages/core/templates/errors/502.twig +9 -0
- package/packages/core/templates/errors/503.twig +12 -0
- package/packages/core/templates/errors/base.twig +37 -0
- package/packages/frond/src/engine.ts +1475 -0
- package/packages/frond/src/index.ts +2 -0
- package/packages/orm/src/adapters/firebird.ts +455 -0
- package/packages/orm/src/adapters/mssql.ts +440 -0
- package/packages/orm/src/adapters/mysql.ts +355 -0
- package/packages/orm/src/adapters/postgres.ts +362 -0
- package/packages/orm/src/adapters/sqlite.ts +270 -0
- package/packages/orm/src/autoCrud.ts +231 -0
- package/packages/orm/src/baseModel.ts +536 -0
- package/packages/orm/src/database.ts +321 -0
- package/packages/orm/src/fakeData.ts +118 -0
- package/packages/orm/src/index.ts +49 -0
- package/packages/orm/src/migration.ts +392 -0
- package/packages/orm/src/model.ts +56 -0
- package/packages/orm/src/query.ts +113 -0
- package/packages/orm/src/seeder.ts +120 -0
- package/packages/orm/src/sqlTranslation.ts +272 -0
- package/packages/orm/src/types.ts +110 -0
- package/packages/orm/src/validation.ts +93 -0
- package/packages/swagger/src/generator.ts +189 -0
- package/packages/swagger/src/index.ts +2 -0
- package/packages/swagger/src/ui.ts +48 -0
- package/skills/tina4-developer.skill +0 -0
- package/skills/tina4-js.skill +0 -0
- package/skills/tina4-maintainer.skill +0 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tina4 MySQL Adapter — uses the `mysql2` package (optional peer dependency).
|
|
3
|
+
*
|
|
4
|
+
* Install: npm install mysql2
|
|
5
|
+
* URL format: mysql://user:pass@host:port/database
|
|
6
|
+
*/
|
|
7
|
+
import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
|
|
8
|
+
import { SQLTranslator } from "../sqlTranslation.js";
|
|
9
|
+
|
|
10
|
+
let mysql2: any = null;
|
|
11
|
+
|
|
12
|
+
function requireMysql2(): any {
|
|
13
|
+
if (mysql2) return mysql2;
|
|
14
|
+
try {
|
|
15
|
+
const { createRequire } = require("node:module") as typeof import("node:module");
|
|
16
|
+
const req = createRequire(import.meta.url);
|
|
17
|
+
mysql2 = req("mysql2");
|
|
18
|
+
return mysql2;
|
|
19
|
+
} catch {
|
|
20
|
+
throw new Error(
|
|
21
|
+
'MySQL adapter requires the "mysql2" package. Install it with: npm install mysql2',
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface MysqlConfig {
|
|
27
|
+
host?: string;
|
|
28
|
+
port?: number;
|
|
29
|
+
user?: string;
|
|
30
|
+
password?: string;
|
|
31
|
+
database?: string;
|
|
32
|
+
connectionString?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class MysqlAdapter implements DatabaseAdapter {
|
|
36
|
+
private connection: any = null;
|
|
37
|
+
private _lastInsertId: number | bigint | null = null;
|
|
38
|
+
|
|
39
|
+
constructor(private config: MysqlConfig | string) {}
|
|
40
|
+
|
|
41
|
+
/** Connect to MySQL. Must be called before using the adapter. */
|
|
42
|
+
async connect(): Promise<void> {
|
|
43
|
+
const mod = requireMysql2();
|
|
44
|
+
|
|
45
|
+
if (typeof this.config === "string") {
|
|
46
|
+
// Parse URL: mysql://user:pass@host:port/database
|
|
47
|
+
const url = new URL(this.config);
|
|
48
|
+
this.connection = mod.createConnection({
|
|
49
|
+
host: url.hostname || "localhost",
|
|
50
|
+
port: url.port ? parseInt(url.port, 10) : 3306,
|
|
51
|
+
user: decodeURIComponent(url.username),
|
|
52
|
+
password: decodeURIComponent(url.password),
|
|
53
|
+
database: url.pathname.replace(/^\//, ""),
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
this.connection = mod.createConnection({
|
|
57
|
+
host: this.config.host ?? "localhost",
|
|
58
|
+
port: this.config.port ?? 3306,
|
|
59
|
+
user: this.config.user,
|
|
60
|
+
password: this.config.password,
|
|
61
|
+
database: this.config.database,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Promisify the connection
|
|
66
|
+
await new Promise<void>((resolve, reject) => {
|
|
67
|
+
this.connection.connect((err: Error | null) => {
|
|
68
|
+
if (err) reject(err);
|
|
69
|
+
else resolve();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private ensureConnected(): void {
|
|
75
|
+
if (!this.connection) {
|
|
76
|
+
throw new Error("MySQL adapter not connected. Call connect() first.");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private queryPromise(sql: string, params?: unknown[]): Promise<any> {
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
this.connection.query(sql, params ?? [], (err: Error | null, results: any) => {
|
|
83
|
+
if (err) reject(err);
|
|
84
|
+
else resolve(results);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Translate SQL for MySQL dialect. */
|
|
90
|
+
translateSql(sql: string): string {
|
|
91
|
+
// MySQL uses CONCAT() instead of ||
|
|
92
|
+
let translated = SQLTranslator.concatPipesToFunc(sql);
|
|
93
|
+
// MySQL uses LOWER() LIKE instead of ILIKE
|
|
94
|
+
translated = SQLTranslator.ilikeToLike(translated);
|
|
95
|
+
return translated;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
execute(sql: string, params?: unknown[]): unknown {
|
|
99
|
+
throw new Error("Use executeAsync() for MySQL — async adapter requires async methods.");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
executeMany(sql: string, paramsList: unknown[][]): { totalAffected: number; lastInsertId?: number | bigint } {
|
|
103
|
+
throw new Error("Use executeManyAsync() for MySQL — async adapter requires async methods.");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async executeManyAsync(sql: string, paramsList: unknown[][]): Promise<{ totalAffected: number; lastInsertId?: number | bigint }> {
|
|
107
|
+
let totalAffected = 0;
|
|
108
|
+
let lastId: number | bigint | undefined;
|
|
109
|
+
for (const params of paramsList) {
|
|
110
|
+
const result = await this.executeAsync(sql, params) as any;
|
|
111
|
+
totalAffected += result?.affectedRows ?? 1;
|
|
112
|
+
if (result?.insertId) lastId = result.insertId;
|
|
113
|
+
}
|
|
114
|
+
return { totalAffected, lastInsertId: lastId };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async executeAsync(sql: string, params?: unknown[]): Promise<unknown> {
|
|
118
|
+
this.ensureConnected();
|
|
119
|
+
const translated = this.translateSql(sql);
|
|
120
|
+
const result = await this.queryPromise(translated, params);
|
|
121
|
+
if (result?.insertId) {
|
|
122
|
+
this._lastInsertId = result.insertId;
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[] {
|
|
128
|
+
throw new Error("Use queryAsync() for MySQL.");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async queryAsync<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]> {
|
|
132
|
+
this.ensureConnected();
|
|
133
|
+
const translated = this.translateSql(sql);
|
|
134
|
+
const results = await this.queryPromise(translated, params);
|
|
135
|
+
return Array.isArray(results) ? results as T[] : [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fetch<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number): T[] {
|
|
139
|
+
throw new Error("Use fetchAsync() for MySQL.");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async fetchAsync<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number): Promise<T[]> {
|
|
143
|
+
let effectiveSql = sql;
|
|
144
|
+
if (limit !== undefined) {
|
|
145
|
+
effectiveSql += ` LIMIT ${limit}`;
|
|
146
|
+
if (skip !== undefined && skip > 0) {
|
|
147
|
+
effectiveSql += ` OFFSET ${skip}`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return this.queryAsync<T>(effectiveSql, params);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fetchOne<T = Record<string, unknown>>(sql: string, params?: unknown[]): T | null {
|
|
154
|
+
throw new Error("Use fetchOneAsync() for MySQL.");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async fetchOneAsync<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T | null> {
|
|
158
|
+
const rows = await this.queryAsync<T>(sql, params);
|
|
159
|
+
return rows[0] ?? null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
insert(table: string, data: Record<string, unknown>): DatabaseResult {
|
|
163
|
+
throw new Error("Use insertAsync() for MySQL.");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async insertAsync(table: string, data: Record<string, unknown>): Promise<DatabaseResult> {
|
|
167
|
+
this.ensureConnected();
|
|
168
|
+
const keys = Object.keys(data);
|
|
169
|
+
const placeholders = keys.map(() => "?").join(", ");
|
|
170
|
+
const sql = `INSERT INTO \`${table}\` (\`${keys.join("`, `")}\`) VALUES (${placeholders})`;
|
|
171
|
+
const values = Object.values(data);
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const result = await this.queryPromise(sql, values);
|
|
175
|
+
this._lastInsertId = result.insertId ?? null;
|
|
176
|
+
return {
|
|
177
|
+
success: true,
|
|
178
|
+
rowsAffected: result.affectedRows ?? 1,
|
|
179
|
+
lastInsertId: result.insertId,
|
|
180
|
+
};
|
|
181
|
+
} catch (e) {
|
|
182
|
+
return { success: false, rowsAffected: 0, error: (e as Error).message };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
update(table: string, data: Record<string, unknown>, filter: Record<string, unknown>): DatabaseResult {
|
|
187
|
+
throw new Error("Use updateAsync() for MySQL.");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown>): Promise<DatabaseResult> {
|
|
191
|
+
this.ensureConnected();
|
|
192
|
+
const setClauses = Object.keys(data).map((k) => `\`${k}\` = ?`).join(", ");
|
|
193
|
+
const whereClauses = Object.keys(filter).map((k) => `\`${k}\` = ?`).join(" AND ");
|
|
194
|
+
const sql = `UPDATE \`${table}\` SET ${setClauses} WHERE ${whereClauses}`;
|
|
195
|
+
const values = [...Object.values(data), ...Object.values(filter)];
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const result = await this.queryPromise(sql, values);
|
|
199
|
+
return { success: true, rowsAffected: result.affectedRows ?? 0 };
|
|
200
|
+
} catch (e) {
|
|
201
|
+
return { success: false, rowsAffected: 0, error: (e as Error).message };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
delete(table: string, filter: Record<string, unknown>): DatabaseResult {
|
|
206
|
+
throw new Error("Use deleteAsync() for MySQL.");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async deleteAsync(table: string, filter: Record<string, unknown>): Promise<DatabaseResult> {
|
|
210
|
+
this.ensureConnected();
|
|
211
|
+
const whereClauses = Object.keys(filter).map((k) => `\`${k}\` = ?`).join(" AND ");
|
|
212
|
+
const sql = `DELETE FROM \`${table}\` WHERE ${whereClauses}`;
|
|
213
|
+
const values = Object.values(filter);
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const result = await this.queryPromise(sql, values);
|
|
217
|
+
return { success: true, rowsAffected: result.affectedRows ?? 0 };
|
|
218
|
+
} catch (e) {
|
|
219
|
+
return { success: false, rowsAffected: 0, error: (e as Error).message };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
startTransaction(): void {
|
|
224
|
+
throw new Error("Use startTransactionAsync() for MySQL.");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async startTransactionAsync(): Promise<void> {
|
|
228
|
+
await this.executeAsync("START TRANSACTION");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
commit(): void {
|
|
232
|
+
throw new Error("Use commitAsync() for MySQL.");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async commitAsync(): Promise<void> {
|
|
236
|
+
await this.executeAsync("COMMIT");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
rollback(): void {
|
|
240
|
+
throw new Error("Use rollbackAsync() for MySQL.");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async rollbackAsync(): Promise<void> {
|
|
244
|
+
await this.executeAsync("ROLLBACK");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
tables(): string[] {
|
|
248
|
+
throw new Error("Use tablesAsync() for MySQL.");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async tablesAsync(): Promise<string[]> {
|
|
252
|
+
const rows = await this.queryAsync<Record<string, string>>("SHOW TABLES");
|
|
253
|
+
return rows.map((r) => Object.values(r)[0]);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
columns(table: string): ColumnInfo[] {
|
|
257
|
+
throw new Error("Use columnsAsync() for MySQL.");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async columnsAsync(table: string): Promise<ColumnInfo[]> {
|
|
261
|
+
const rows = await this.queryAsync<{
|
|
262
|
+
Field: string;
|
|
263
|
+
Type: string;
|
|
264
|
+
Null: string;
|
|
265
|
+
Default: string | null;
|
|
266
|
+
Key: string;
|
|
267
|
+
}>(`DESCRIBE \`${table}\``);
|
|
268
|
+
return rows.map((r) => ({
|
|
269
|
+
name: r.Field,
|
|
270
|
+
type: r.Type,
|
|
271
|
+
nullable: r.Null === "YES",
|
|
272
|
+
default: r.Default,
|
|
273
|
+
primaryKey: r.Key === "PRI",
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
lastInsertId(): number | bigint | null {
|
|
278
|
+
return this._lastInsertId;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
close(): void {
|
|
282
|
+
if (this.connection) {
|
|
283
|
+
this.connection.end();
|
|
284
|
+
this.connection = null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
tableExists(name: string): boolean {
|
|
289
|
+
throw new Error("Use tableExistsAsync() for MySQL.");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async tableExistsAsync(name: string): Promise<boolean> {
|
|
293
|
+
const rows = await this.queryAsync<Record<string, string>>(
|
|
294
|
+
`SHOW TABLES LIKE ?`,
|
|
295
|
+
[name],
|
|
296
|
+
);
|
|
297
|
+
return rows.length > 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
createTable(name: string, columns: Record<string, FieldDefinition>): void {
|
|
301
|
+
throw new Error("Use createTableAsync() for MySQL.");
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async createTableAsync(name: string, columns: Record<string, FieldDefinition>): Promise<void> {
|
|
305
|
+
const colDefs: string[] = [];
|
|
306
|
+
|
|
307
|
+
for (const [colName, def] of Object.entries(columns)) {
|
|
308
|
+
const sqlType = fieldTypeToMysql(def);
|
|
309
|
+
const parts = [`\`${colName}\` ${sqlType}`];
|
|
310
|
+
|
|
311
|
+
if (def.primaryKey && !def.autoIncrement) parts.push("PRIMARY KEY");
|
|
312
|
+
if (def.required && !def.primaryKey) parts.push("NOT NULL");
|
|
313
|
+
if (def.default !== undefined && def.default !== "now") {
|
|
314
|
+
parts.push(`DEFAULT ${sqlDefault(def.default)}`);
|
|
315
|
+
}
|
|
316
|
+
if (def.default === "now") {
|
|
317
|
+
parts.push("DEFAULT CURRENT_TIMESTAMP");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
colDefs.push(parts.join(" "));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const sql = `CREATE TABLE IF NOT EXISTS \`${name}\` (${colDefs.join(", ")}) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`;
|
|
324
|
+
await this.executeAsync(sql);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function fieldTypeToMysql(def: FieldDefinition): string {
|
|
329
|
+
if (def.primaryKey && def.autoIncrement) {
|
|
330
|
+
return "INT AUTO_INCREMENT PRIMARY KEY";
|
|
331
|
+
}
|
|
332
|
+
switch (def.type) {
|
|
333
|
+
case "integer":
|
|
334
|
+
return "INT";
|
|
335
|
+
case "number":
|
|
336
|
+
case "numeric":
|
|
337
|
+
return "DOUBLE";
|
|
338
|
+
case "boolean":
|
|
339
|
+
return "TINYINT(1)";
|
|
340
|
+
case "datetime":
|
|
341
|
+
return "DATETIME";
|
|
342
|
+
case "text":
|
|
343
|
+
return "TEXT";
|
|
344
|
+
case "string":
|
|
345
|
+
return def.maxLength ? `VARCHAR(${def.maxLength})` : "VARCHAR(255)";
|
|
346
|
+
default:
|
|
347
|
+
return "TEXT";
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function sqlDefault(value: unknown): string {
|
|
352
|
+
if (typeof value === "string") return `'${value}'`;
|
|
353
|
+
if (typeof value === "boolean") return value ? "1" : "0";
|
|
354
|
+
return String(value);
|
|
355
|
+
}
|