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,321 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from "./types.js";
|
|
2
|
+
|
|
3
|
+
let activeAdapter: DatabaseAdapter | null = null;
|
|
4
|
+
const namedAdapters: Map<string, DatabaseAdapter> = new Map();
|
|
5
|
+
|
|
6
|
+
export function setAdapter(adapter: DatabaseAdapter): void {
|
|
7
|
+
activeAdapter = adapter;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getAdapter(): DatabaseAdapter {
|
|
11
|
+
if (!activeAdapter) {
|
|
12
|
+
throw new Error("No database adapter configured. Call setAdapter() first.");
|
|
13
|
+
}
|
|
14
|
+
return activeAdapter;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register a named adapter for multi-database support.
|
|
19
|
+
* Models reference it via `static _db = 'name'`.
|
|
20
|
+
*/
|
|
21
|
+
export function setNamedAdapter(name: string, adapter: DatabaseAdapter): void {
|
|
22
|
+
namedAdapters.set(name, adapter);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get a named adapter. Falls back to the default adapter if name not found.
|
|
27
|
+
*/
|
|
28
|
+
export function getNamedAdapter(name: string): DatabaseAdapter {
|
|
29
|
+
const adapter = namedAdapters.get(name);
|
|
30
|
+
if (adapter) return adapter;
|
|
31
|
+
// Fall back to default
|
|
32
|
+
return getAdapter();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function closeDatabase(): void {
|
|
36
|
+
if (activeAdapter) {
|
|
37
|
+
activeAdapter.close();
|
|
38
|
+
activeAdapter = null;
|
|
39
|
+
}
|
|
40
|
+
for (const [, adapter] of namedAdapters) {
|
|
41
|
+
adapter.close();
|
|
42
|
+
}
|
|
43
|
+
namedAdapters.clear();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DatabaseConfig {
|
|
47
|
+
type?: "sqlite" | "postgres" | "mysql" | "mssql" | "sqlserver" | "firebird";
|
|
48
|
+
path?: string;
|
|
49
|
+
url?: string;
|
|
50
|
+
host?: string;
|
|
51
|
+
port?: number;
|
|
52
|
+
user?: string;
|
|
53
|
+
username?: string;
|
|
54
|
+
password?: string;
|
|
55
|
+
database?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Parsed result from a DATABASE_URL connection string.
|
|
60
|
+
*/
|
|
61
|
+
export interface ParsedDatabaseUrl {
|
|
62
|
+
type: "sqlite" | "postgres" | "mysql" | "mssql" | "firebird";
|
|
63
|
+
path?: string;
|
|
64
|
+
host?: string;
|
|
65
|
+
port?: number;
|
|
66
|
+
user?: string;
|
|
67
|
+
password?: string;
|
|
68
|
+
database?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parse a DATABASE_URL connection string into its components.
|
|
73
|
+
*
|
|
74
|
+
* Supported formats:
|
|
75
|
+
* sqlite:///path/to/db.sqlite
|
|
76
|
+
* sqlite://./relative/path.db
|
|
77
|
+
* postgresql://user:pass@host:port/dbname
|
|
78
|
+
* postgres://user:pass@host:port/dbname
|
|
79
|
+
* mysql://user:pass@host:port/dbname
|
|
80
|
+
*
|
|
81
|
+
* @param url - The connection URL string.
|
|
82
|
+
* @param username - Optional username to merge when the URL has no credentials.
|
|
83
|
+
* @param password - Optional password to merge when the URL has no credentials.
|
|
84
|
+
* @returns Parsed database configuration.
|
|
85
|
+
* @throws Error if the URL scheme is not supported.
|
|
86
|
+
*/
|
|
87
|
+
export function parseDatabaseUrl(url: string, username?: string, password?: string): ParsedDatabaseUrl {
|
|
88
|
+
let result: ParsedDatabaseUrl;
|
|
89
|
+
|
|
90
|
+
// Handle sqlite:// separately because URL class mangles the path
|
|
91
|
+
if (url.startsWith("sqlite:///")) {
|
|
92
|
+
// sqlite:///absolute/path — three slashes means absolute
|
|
93
|
+
const path = url.slice("sqlite://".length);
|
|
94
|
+
result = { type: "sqlite", path };
|
|
95
|
+
} else if (url.startsWith("sqlite://")) {
|
|
96
|
+
// sqlite://./relative or sqlite://relative
|
|
97
|
+
const path = url.slice("sqlite://".length);
|
|
98
|
+
result = { type: "sqlite", path };
|
|
99
|
+
} else if (url.startsWith("mssql://") || url.startsWith("sqlserver://")) {
|
|
100
|
+
// Handle mssql:// and sqlserver:// with custom parsing (URL class doesn't know these schemes)
|
|
101
|
+
const match = url.match(/(?:mssql|sqlserver):\/\/(?:([^:]+):([^@]+)@)?([^:/]+)(?::(\d+))?\/(.*)/);
|
|
102
|
+
if (!match) throw new Error(`Invalid MSSQL URL: ${url}`);
|
|
103
|
+
result = {
|
|
104
|
+
type: "mssql",
|
|
105
|
+
user: match[1] ? decodeURIComponent(match[1]) : undefined,
|
|
106
|
+
password: match[2] ? decodeURIComponent(match[2]) : undefined,
|
|
107
|
+
host: match[3],
|
|
108
|
+
port: match[4] ? parseInt(match[4], 10) : undefined,
|
|
109
|
+
database: match[5],
|
|
110
|
+
};
|
|
111
|
+
} else if (url.startsWith("firebird://")) {
|
|
112
|
+
const match = url.match(/firebird:\/\/(?:([^:]+):([^@]+)@)?([^:/]+)(?::(\d+))?\/(.*)/);
|
|
113
|
+
if (!match) throw new Error(`Invalid Firebird URL: ${url}`);
|
|
114
|
+
result = {
|
|
115
|
+
type: "firebird",
|
|
116
|
+
user: match[1] ? decodeURIComponent(match[1]) : undefined,
|
|
117
|
+
password: match[2] ? decodeURIComponent(match[2]) : undefined,
|
|
118
|
+
host: match[3],
|
|
119
|
+
port: match[4] ? parseInt(match[4], 10) : undefined,
|
|
120
|
+
database: "/" + match[5],
|
|
121
|
+
};
|
|
122
|
+
} else {
|
|
123
|
+
// Normalize postgres:// to postgresql:// for URL parsing
|
|
124
|
+
const normalizedUrl = url.startsWith("postgres://")
|
|
125
|
+
? url.replace(/^postgres:\/\//, "postgresql://")
|
|
126
|
+
: url;
|
|
127
|
+
|
|
128
|
+
let parsed: URL;
|
|
129
|
+
try {
|
|
130
|
+
parsed = new URL(normalizedUrl);
|
|
131
|
+
} catch {
|
|
132
|
+
throw new Error(`Invalid database URL: ${url}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const scheme = parsed.protocol.replace(/:$/, "");
|
|
136
|
+
let type: "sqlite" | "postgres" | "mysql" | "mssql" | "firebird";
|
|
137
|
+
|
|
138
|
+
switch (scheme) {
|
|
139
|
+
case "postgresql":
|
|
140
|
+
type = "postgres";
|
|
141
|
+
break;
|
|
142
|
+
case "mysql":
|
|
143
|
+
type = "mysql";
|
|
144
|
+
break;
|
|
145
|
+
default:
|
|
146
|
+
throw new Error(`Unsupported database URL scheme: "${scheme}". Supported: sqlite, postgres/postgresql, mysql, mssql/sqlserver, firebird.`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const database = parsed.pathname.startsWith("/")
|
|
150
|
+
? parsed.pathname.slice(1)
|
|
151
|
+
: parsed.pathname;
|
|
152
|
+
|
|
153
|
+
result = {
|
|
154
|
+
type,
|
|
155
|
+
host: parsed.hostname || undefined,
|
|
156
|
+
port: parsed.port ? parseInt(parsed.port, 10) : undefined,
|
|
157
|
+
user: parsed.username ? decodeURIComponent(parsed.username) : undefined,
|
|
158
|
+
password: parsed.password ? decodeURIComponent(parsed.password) : undefined,
|
|
159
|
+
database: database || undefined,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Merge separate username/password when the URL contained no credentials
|
|
164
|
+
if (!result.user && username) {
|
|
165
|
+
result.user = username;
|
|
166
|
+
}
|
|
167
|
+
if (!result.password && password) {
|
|
168
|
+
result.password = password;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Initialize the database from a config object or DATABASE_URL env var.
|
|
176
|
+
*
|
|
177
|
+
* Priority:
|
|
178
|
+
* 1. config.url (explicit URL)
|
|
179
|
+
* 2. process.env.DATABASE_URL
|
|
180
|
+
* 3. config.type + config.path (legacy)
|
|
181
|
+
*/
|
|
182
|
+
export async function initDatabase(config?: DatabaseConfig): Promise<DatabaseAdapter> {
|
|
183
|
+
// Resolve credentials: config.user > config.username > env DATABASE_USERNAME
|
|
184
|
+
const resolvedUser = config?.user ?? config?.username ?? process.env.DATABASE_USERNAME;
|
|
185
|
+
const resolvedPassword = config?.password ?? process.env.DATABASE_PASSWORD;
|
|
186
|
+
|
|
187
|
+
// Resolve from URL if provided
|
|
188
|
+
const url = config?.url ?? process.env.DATABASE_URL;
|
|
189
|
+
|
|
190
|
+
if (url) {
|
|
191
|
+
const parsed = parseDatabaseUrl(url, resolvedUser, resolvedPassword);
|
|
192
|
+
|
|
193
|
+
switch (parsed.type) {
|
|
194
|
+
case "sqlite": {
|
|
195
|
+
const { SQLiteAdapter } = await import("./adapters/sqlite.js");
|
|
196
|
+
const adapter = new SQLiteAdapter(parsed.path ?? "./data/tina4.db");
|
|
197
|
+
setAdapter(adapter);
|
|
198
|
+
return adapter;
|
|
199
|
+
}
|
|
200
|
+
case "postgres": {
|
|
201
|
+
const { PostgresAdapter } = await import("./adapters/postgres.js");
|
|
202
|
+
const adapter = new PostgresAdapter({
|
|
203
|
+
host: parsed.host,
|
|
204
|
+
port: parsed.port,
|
|
205
|
+
user: parsed.user,
|
|
206
|
+
password: parsed.password,
|
|
207
|
+
database: parsed.database,
|
|
208
|
+
});
|
|
209
|
+
await adapter.connect();
|
|
210
|
+
setAdapter(adapter);
|
|
211
|
+
return adapter;
|
|
212
|
+
}
|
|
213
|
+
case "mysql": {
|
|
214
|
+
const { MysqlAdapter } = await import("./adapters/mysql.js");
|
|
215
|
+
const adapter = new MysqlAdapter({
|
|
216
|
+
host: parsed.host,
|
|
217
|
+
port: parsed.port,
|
|
218
|
+
user: parsed.user,
|
|
219
|
+
password: parsed.password,
|
|
220
|
+
database: parsed.database,
|
|
221
|
+
});
|
|
222
|
+
await adapter.connect();
|
|
223
|
+
setAdapter(adapter);
|
|
224
|
+
return adapter;
|
|
225
|
+
}
|
|
226
|
+
case "mssql": {
|
|
227
|
+
const { MssqlAdapter } = await import("./adapters/mssql.js");
|
|
228
|
+
const adapter = new MssqlAdapter({
|
|
229
|
+
host: parsed.host,
|
|
230
|
+
port: parsed.port,
|
|
231
|
+
user: parsed.user,
|
|
232
|
+
password: parsed.password,
|
|
233
|
+
database: parsed.database,
|
|
234
|
+
});
|
|
235
|
+
await adapter.connect();
|
|
236
|
+
setAdapter(adapter);
|
|
237
|
+
return adapter;
|
|
238
|
+
}
|
|
239
|
+
case "firebird": {
|
|
240
|
+
const { FirebirdAdapter } = await import("./adapters/firebird.js");
|
|
241
|
+
const adapter = new FirebirdAdapter({
|
|
242
|
+
host: parsed.host,
|
|
243
|
+
port: parsed.port,
|
|
244
|
+
user: parsed.user,
|
|
245
|
+
password: parsed.password,
|
|
246
|
+
database: parsed.database,
|
|
247
|
+
});
|
|
248
|
+
await adapter.connect();
|
|
249
|
+
setAdapter(adapter);
|
|
250
|
+
return adapter;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Legacy config path — normalize "sqlserver" to "mssql"
|
|
256
|
+
const rawType = config?.type ?? "sqlite";
|
|
257
|
+
const type = rawType === "sqlserver" ? "mssql" : rawType;
|
|
258
|
+
|
|
259
|
+
switch (type) {
|
|
260
|
+
case "sqlite": {
|
|
261
|
+
const { SQLiteAdapter } = await import("./adapters/sqlite.js");
|
|
262
|
+
const adapter = new SQLiteAdapter(config?.path ?? "./data/tina4.db");
|
|
263
|
+
setAdapter(adapter);
|
|
264
|
+
return adapter;
|
|
265
|
+
}
|
|
266
|
+
case "postgres": {
|
|
267
|
+
const { PostgresAdapter } = await import("./adapters/postgres.js");
|
|
268
|
+
const adapter = new PostgresAdapter({
|
|
269
|
+
host: config?.host,
|
|
270
|
+
port: config?.port,
|
|
271
|
+
user: resolvedUser,
|
|
272
|
+
password: resolvedPassword,
|
|
273
|
+
database: config?.database,
|
|
274
|
+
});
|
|
275
|
+
await adapter.connect();
|
|
276
|
+
setAdapter(adapter);
|
|
277
|
+
return adapter;
|
|
278
|
+
}
|
|
279
|
+
case "mysql": {
|
|
280
|
+
const { MysqlAdapter } = await import("./adapters/mysql.js");
|
|
281
|
+
const adapter = new MysqlAdapter({
|
|
282
|
+
host: config?.host,
|
|
283
|
+
port: config?.port,
|
|
284
|
+
user: resolvedUser,
|
|
285
|
+
password: resolvedPassword,
|
|
286
|
+
database: config?.database,
|
|
287
|
+
});
|
|
288
|
+
await adapter.connect();
|
|
289
|
+
setAdapter(adapter);
|
|
290
|
+
return adapter;
|
|
291
|
+
}
|
|
292
|
+
case "mssql": {
|
|
293
|
+
const { MssqlAdapter } = await import("./adapters/mssql.js");
|
|
294
|
+
const adapter = new MssqlAdapter({
|
|
295
|
+
host: config?.host,
|
|
296
|
+
port: config?.port,
|
|
297
|
+
user: resolvedUser,
|
|
298
|
+
password: resolvedPassword,
|
|
299
|
+
database: config?.database,
|
|
300
|
+
});
|
|
301
|
+
await adapter.connect();
|
|
302
|
+
setAdapter(adapter);
|
|
303
|
+
return adapter;
|
|
304
|
+
}
|
|
305
|
+
case "firebird": {
|
|
306
|
+
const { FirebirdAdapter } = await import("./adapters/firebird.js");
|
|
307
|
+
const adapter = new FirebirdAdapter({
|
|
308
|
+
host: config?.host,
|
|
309
|
+
port: config?.port,
|
|
310
|
+
user: resolvedUser,
|
|
311
|
+
password: resolvedPassword,
|
|
312
|
+
database: config?.database,
|
|
313
|
+
});
|
|
314
|
+
await adapter.connect();
|
|
315
|
+
setAdapter(adapter);
|
|
316
|
+
return adapter;
|
|
317
|
+
}
|
|
318
|
+
default:
|
|
319
|
+
throw new Error(`Unknown database type: ${type}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Tina4 ORM FakeData — extends core FakeData with ORM-aware data generation.
|
|
2
|
+
// Re-exports core FakeData with additional forField() for auto-generating
|
|
3
|
+
// data based on ORM field definitions.
|
|
4
|
+
|
|
5
|
+
import { FakeData as CoreFakeData } from "../../core/src/fakeData.js";
|
|
6
|
+
import type { FieldDefinition } from "./types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ORM-aware FakeData — wraps the core FakeData and adds forField()
|
|
10
|
+
* which generates appropriate fake data based on an ORM FieldDefinition.
|
|
11
|
+
*/
|
|
12
|
+
export class FakeData extends CoreFakeData {
|
|
13
|
+
constructor(seed?: number) {
|
|
14
|
+
super(seed);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Alias for fullName() to match the Python API. */
|
|
18
|
+
name(): string {
|
|
19
|
+
return this.fullName();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Alias for float() to match the Python API. */
|
|
23
|
+
numeric(min = 0, max = 1000, decimals = 2): number {
|
|
24
|
+
return this.float(min, max, decimals);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate a Date object within a year range.
|
|
29
|
+
* Matches the Python API's datetime() method.
|
|
30
|
+
*/
|
|
31
|
+
datetime(startYear = 2020, endYear = 2025): Date {
|
|
32
|
+
const startMs = new Date(startYear, 0, 1).getTime();
|
|
33
|
+
const endMs = new Date(endYear, 11, 31, 23, 59, 59).getTime();
|
|
34
|
+
const diffDays = Math.floor((endMs - startMs) / 86400000);
|
|
35
|
+
const offset = this.integer(0, diffDays);
|
|
36
|
+
return new Date(startMs + offset * 86400000);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generate a fake value appropriate for an ORM field definition.
|
|
41
|
+
* Respects min/max, minLength/maxLength, and type constraints.
|
|
42
|
+
*
|
|
43
|
+
* @param fieldDef - An ORM FieldDefinition object
|
|
44
|
+
* @param columnName - Optional column name for heuristic matching (e.g. "email", "phone")
|
|
45
|
+
*/
|
|
46
|
+
forField(fieldDef: FieldDefinition, columnName?: string): unknown {
|
|
47
|
+
// Auto-increment primary keys should not be generated
|
|
48
|
+
if (fieldDef.primaryKey && fieldDef.autoIncrement) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// If there's a default, use it sometimes (but not always for variety)
|
|
53
|
+
if (fieldDef.default !== undefined) {
|
|
54
|
+
return fieldDef.default;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Heuristic: use column name to pick a smarter generator
|
|
58
|
+
const col = (columnName ?? "").toLowerCase();
|
|
59
|
+
|
|
60
|
+
if (col.includes("email")) return this.email();
|
|
61
|
+
if (col.includes("phone") || col.includes("mobile") || col.includes("tel")) return this.phone();
|
|
62
|
+
if (col === "name" || col === "full_name" || col === "fullname") return this.name();
|
|
63
|
+
if (col === "first_name" || col === "firstname") return this.firstName();
|
|
64
|
+
if (col === "last_name" || col === "lastname" || col === "surname") return this.lastName();
|
|
65
|
+
if (col.includes("address")) return this.address();
|
|
66
|
+
if (col.includes("city")) return this.city();
|
|
67
|
+
if (col.includes("country")) return this.country();
|
|
68
|
+
if (col.includes("zip") || col.includes("postal")) return this.zipCode();
|
|
69
|
+
if (col.includes("company") || col.includes("org")) return this.company();
|
|
70
|
+
if (col.includes("job") || col.includes("title") || col.includes("position")) return this.jobTitle();
|
|
71
|
+
if (col.includes("url") || col.includes("website") || col.includes("link")) return this.url();
|
|
72
|
+
if (col.includes("color") || col.includes("colour")) return this.hexColor();
|
|
73
|
+
if (col.includes("uuid") || col === "guid") return this.uuid();
|
|
74
|
+
if (col === "ip" || col === "ip_address" || col === "ipaddress") return this.ipAddress();
|
|
75
|
+
if (col.includes("currency")) return this.currency();
|
|
76
|
+
|
|
77
|
+
// Fall back to type-based generation
|
|
78
|
+
switch (fieldDef.type) {
|
|
79
|
+
case "string": {
|
|
80
|
+
const maxLen = fieldDef.maxLength ?? 50;
|
|
81
|
+
const minLen = fieldDef.minLength ?? 3;
|
|
82
|
+
// Generate a sentence and trim to fit
|
|
83
|
+
let value = this.sentence(Math.max(2, Math.ceil(maxLen / 6)));
|
|
84
|
+
if (value.length > maxLen) value = value.slice(0, maxLen);
|
|
85
|
+
if (value.length < minLen) {
|
|
86
|
+
while (value.length < minLen) value += " " + this.word();
|
|
87
|
+
value = value.slice(0, maxLen);
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case "text":
|
|
93
|
+
return this.paragraph(3);
|
|
94
|
+
|
|
95
|
+
case "integer": {
|
|
96
|
+
const min = (fieldDef.min as number) ?? 0;
|
|
97
|
+
const max = (fieldDef.max as number) ?? 10000;
|
|
98
|
+
return this.integer(min, max);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
case "number":
|
|
102
|
+
case "numeric": {
|
|
103
|
+
const min = (fieldDef.min as number) ?? 0;
|
|
104
|
+
const max = (fieldDef.max as number) ?? 10000;
|
|
105
|
+
return this.numeric(min, max, 2);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
case "boolean":
|
|
109
|
+
return this.boolean();
|
|
110
|
+
|
|
111
|
+
case "datetime":
|
|
112
|
+
return this.datetime().toISOString();
|
|
113
|
+
|
|
114
|
+
default:
|
|
115
|
+
return this.sentence(4);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
FieldType,
|
|
3
|
+
FieldDefinition,
|
|
4
|
+
ModelDefinition,
|
|
5
|
+
DatabaseAdapter,
|
|
6
|
+
DatabaseResult,
|
|
7
|
+
ColumnInfo,
|
|
8
|
+
QueryOptions,
|
|
9
|
+
RelationshipDefinition,
|
|
10
|
+
} from "./types.js";
|
|
11
|
+
|
|
12
|
+
export { initDatabase, getAdapter, setAdapter, closeDatabase, parseDatabaseUrl, setNamedAdapter, getNamedAdapter } from "./database.js";
|
|
13
|
+
export type { DatabaseConfig, ParsedDatabaseUrl } from "./database.js";
|
|
14
|
+
export { discoverModels } from "./model.js";
|
|
15
|
+
export type { DiscoveredModel } from "./model.js";
|
|
16
|
+
export {
|
|
17
|
+
syncModels,
|
|
18
|
+
ensureMigrationTable,
|
|
19
|
+
getNextBatch,
|
|
20
|
+
isMigrationApplied,
|
|
21
|
+
recordMigration,
|
|
22
|
+
applyMigration,
|
|
23
|
+
rollback,
|
|
24
|
+
getAppliedMigrations,
|
|
25
|
+
getLastBatchMigrations,
|
|
26
|
+
removeMigrationRecord,
|
|
27
|
+
migrate,
|
|
28
|
+
createMigration,
|
|
29
|
+
} from "./migration.js";
|
|
30
|
+
export type { MigrationResult } from "./migration.js";
|
|
31
|
+
export { generateCrudRoutes } from "./autoCrud.js";
|
|
32
|
+
export { buildQuery, parseQueryString } from "./query.js";
|
|
33
|
+
export { validate } from "./validation.js";
|
|
34
|
+
export type { ValidationError } from "./validation.js";
|
|
35
|
+
export { BaseModel } from "./baseModel.js";
|
|
36
|
+
export { SQLTranslator, QueryCache } from "./sqlTranslation.js";
|
|
37
|
+
export { FakeData } from "./fakeData.js";
|
|
38
|
+
export { seedTable, seedOrm } from "./seeder.js";
|
|
39
|
+
|
|
40
|
+
// Database adapters
|
|
41
|
+
export { SQLiteAdapter } from "./adapters/sqlite.js";
|
|
42
|
+
export { PostgresAdapter } from "./adapters/postgres.js";
|
|
43
|
+
export type { PostgresConfig } from "./adapters/postgres.js";
|
|
44
|
+
export { MysqlAdapter } from "./adapters/mysql.js";
|
|
45
|
+
export type { MysqlConfig } from "./adapters/mysql.js";
|
|
46
|
+
export { MssqlAdapter } from "./adapters/mssql.js";
|
|
47
|
+
export type { MssqlConfig } from "./adapters/mssql.js";
|
|
48
|
+
export { FirebirdAdapter } from "./adapters/firebird.js";
|
|
49
|
+
export type { FirebirdConfig } from "./adapters/firebird.js";
|