@buenojs/bueno 0.8.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/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Schema Definition and Type System
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for defining database schemas and generating
|
|
5
|
+
* TypeScript types from them.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============= Column Types =============
|
|
9
|
+
|
|
10
|
+
export type ColumnType =
|
|
11
|
+
| "serial"
|
|
12
|
+
| "integer"
|
|
13
|
+
| "bigint"
|
|
14
|
+
| "decimal"
|
|
15
|
+
| "varchar"
|
|
16
|
+
| "text"
|
|
17
|
+
| "boolean"
|
|
18
|
+
| "date"
|
|
19
|
+
| "timestamp"
|
|
20
|
+
| "timestamptz"
|
|
21
|
+
| "json"
|
|
22
|
+
| "jsonb"
|
|
23
|
+
| "uuid"
|
|
24
|
+
| "blob"
|
|
25
|
+
| "enum";
|
|
26
|
+
|
|
27
|
+
// ============= Column Options =============
|
|
28
|
+
|
|
29
|
+
export interface ColumnOptions {
|
|
30
|
+
type: ColumnType;
|
|
31
|
+
length?: number;
|
|
32
|
+
precision?: number;
|
|
33
|
+
scale?: number;
|
|
34
|
+
nullable?: boolean;
|
|
35
|
+
default?: unknown;
|
|
36
|
+
primaryKey?: boolean;
|
|
37
|
+
autoIncrement?: boolean;
|
|
38
|
+
unique?: boolean;
|
|
39
|
+
references?: {
|
|
40
|
+
table: string;
|
|
41
|
+
column: string;
|
|
42
|
+
onDelete?: "CASCADE" | "SET NULL" | "RESTRICT" | "NO ACTION";
|
|
43
|
+
onUpdate?: "CASCADE" | "SET NULL" | "RESTRICT" | "NO ACTION";
|
|
44
|
+
};
|
|
45
|
+
check?: string;
|
|
46
|
+
enum?: string[];
|
|
47
|
+
comment?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ============= Table Schema =============
|
|
51
|
+
|
|
52
|
+
export interface TableSchema {
|
|
53
|
+
name: string;
|
|
54
|
+
columns: Record<string, ColumnOptions>;
|
|
55
|
+
indexes?: IndexDefinition[];
|
|
56
|
+
constraints?: ConstraintDefinition[];
|
|
57
|
+
comment?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface IndexDefinition {
|
|
61
|
+
name?: string;
|
|
62
|
+
columns: string[];
|
|
63
|
+
unique?: boolean;
|
|
64
|
+
type?: "btree" | "hash" | "gin" | "gist";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ConstraintDefinition {
|
|
68
|
+
name?: string;
|
|
69
|
+
type: "unique" | "check" | "foreign";
|
|
70
|
+
columns: string[];
|
|
71
|
+
reference?: {
|
|
72
|
+
table: string;
|
|
73
|
+
columns: string[];
|
|
74
|
+
onDelete?: "CASCADE" | "SET NULL" | "RESTRICT" | "NO ACTION";
|
|
75
|
+
onUpdate?: "CASCADE" | "SET NULL" | "RESTRICT" | "NO ACTION";
|
|
76
|
+
};
|
|
77
|
+
check?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============= Column Builder =============
|
|
81
|
+
|
|
82
|
+
class ColumnBuilder {
|
|
83
|
+
column: ColumnOptions;
|
|
84
|
+
|
|
85
|
+
constructor(type: ColumnType) {
|
|
86
|
+
this.column = { type, nullable: false };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
nullable(): this {
|
|
90
|
+
this.column.nullable = true;
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
notNull(): this {
|
|
95
|
+
this.column.nullable = false;
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
default(value: unknown): this {
|
|
100
|
+
this.column.default = value;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
primaryKey(): this {
|
|
105
|
+
this.column.primaryKey = true;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
unique(): this {
|
|
110
|
+
this.column.unique = true;
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
length(len: number): this {
|
|
115
|
+
this.column.length = len;
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
precision(p: number, s?: number): this {
|
|
120
|
+
this.column.precision = p;
|
|
121
|
+
if (s !== undefined) this.column.scale = s;
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
references(
|
|
126
|
+
table: string,
|
|
127
|
+
column: string,
|
|
128
|
+
options?: {
|
|
129
|
+
onDelete?: "CASCADE" | "SET NULL" | "RESTRICT" | "NO ACTION";
|
|
130
|
+
onUpdate?: "CASCADE" | "SET NULL" | "RESTRICT" | "NO ACTION";
|
|
131
|
+
},
|
|
132
|
+
): this {
|
|
133
|
+
this.column.references = {
|
|
134
|
+
table,
|
|
135
|
+
column,
|
|
136
|
+
...options,
|
|
137
|
+
};
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
check(expression: string): this {
|
|
142
|
+
this.column.check = expression;
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
comment(text: string): this {
|
|
147
|
+
this.column.comment = text;
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
build(): ColumnOptions {
|
|
152
|
+
return { ...this.column };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ============= Schema Builder =============
|
|
157
|
+
|
|
158
|
+
export class SchemaBuilder {
|
|
159
|
+
private name: string;
|
|
160
|
+
private columns: Record<string, ColumnOptions> = {};
|
|
161
|
+
private indexes: IndexDefinition[] = [];
|
|
162
|
+
private constraints: ConstraintDefinition[] = [];
|
|
163
|
+
private tableComment?: string;
|
|
164
|
+
|
|
165
|
+
constructor(tableName: string) {
|
|
166
|
+
this.name = tableName;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Column type helpers
|
|
170
|
+
serial(name: string): ColumnBuilder {
|
|
171
|
+
const builder = new ColumnBuilder("serial");
|
|
172
|
+
builder.primaryKey();
|
|
173
|
+
this.columns[name] = builder.build();
|
|
174
|
+
return builder;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
integer(name: string): ColumnBuilder {
|
|
178
|
+
const builder = new ColumnBuilder("integer");
|
|
179
|
+
this.columns[name] = builder.build();
|
|
180
|
+
return builder;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
bigint(name: string): ColumnBuilder {
|
|
184
|
+
const builder = new ColumnBuilder("bigint");
|
|
185
|
+
this.columns[name] = builder.build();
|
|
186
|
+
return builder;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
decimal(name: string): ColumnBuilder {
|
|
190
|
+
const builder = new ColumnBuilder("decimal");
|
|
191
|
+
this.columns[name] = builder.build();
|
|
192
|
+
return builder;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
varchar(name: string, length = 255): ColumnBuilder {
|
|
196
|
+
const builder = new ColumnBuilder("varchar");
|
|
197
|
+
builder.length(length);
|
|
198
|
+
this.columns[name] = builder.build();
|
|
199
|
+
return builder;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
text(name: string): ColumnBuilder {
|
|
203
|
+
const builder = new ColumnBuilder("text");
|
|
204
|
+
this.columns[name] = builder.build();
|
|
205
|
+
return builder;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
boolean(name: string): ColumnBuilder {
|
|
209
|
+
const builder = new ColumnBuilder("boolean");
|
|
210
|
+
this.columns[name] = builder.build();
|
|
211
|
+
return builder;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
date(name: string): ColumnBuilder {
|
|
215
|
+
const builder = new ColumnBuilder("date");
|
|
216
|
+
this.columns[name] = builder.build();
|
|
217
|
+
return builder;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
timestamp(name: string): ColumnBuilder {
|
|
221
|
+
const builder = new ColumnBuilder("timestamp");
|
|
222
|
+
this.columns[name] = builder.build();
|
|
223
|
+
return builder;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
timestamptz(name: string): ColumnBuilder {
|
|
227
|
+
const builder = new ColumnBuilder("timestamptz");
|
|
228
|
+
this.columns[name] = builder.build();
|
|
229
|
+
return builder;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
json(name: string): ColumnBuilder {
|
|
233
|
+
const builder = new ColumnBuilder("json");
|
|
234
|
+
this.columns[name] = builder.build();
|
|
235
|
+
return builder;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
jsonb(name: string): ColumnBuilder {
|
|
239
|
+
const builder = new ColumnBuilder("jsonb");
|
|
240
|
+
this.columns[name] = builder.build();
|
|
241
|
+
return builder;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
uuid(name: string): ColumnBuilder {
|
|
245
|
+
const builder = new ColumnBuilder("uuid");
|
|
246
|
+
this.columns[name] = builder.build();
|
|
247
|
+
return builder;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
blob(name: string): ColumnBuilder {
|
|
251
|
+
const builder = new ColumnBuilder("blob");
|
|
252
|
+
this.columns[name] = builder.build();
|
|
253
|
+
return builder;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
enum(name: string, values: string[]): ColumnBuilder {
|
|
257
|
+
const builder = new ColumnBuilder("enum");
|
|
258
|
+
builder.column.enum = values;
|
|
259
|
+
this.columns[name] = builder.build();
|
|
260
|
+
return builder;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Custom column
|
|
264
|
+
column(name: string, options: ColumnOptions): this {
|
|
265
|
+
this.columns[name] = options;
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Indexes
|
|
270
|
+
index(
|
|
271
|
+
columns: string[],
|
|
272
|
+
options?: {
|
|
273
|
+
name?: string;
|
|
274
|
+
unique?: boolean;
|
|
275
|
+
type?: "btree" | "hash" | "gin" | "gist";
|
|
276
|
+
},
|
|
277
|
+
): this {
|
|
278
|
+
this.indexes.push({
|
|
279
|
+
columns,
|
|
280
|
+
...options,
|
|
281
|
+
});
|
|
282
|
+
return this;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
unique(columns: string[], name?: string): this {
|
|
286
|
+
this.constraints.push({
|
|
287
|
+
type: "unique",
|
|
288
|
+
columns,
|
|
289
|
+
name,
|
|
290
|
+
});
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
foreignKey(
|
|
295
|
+
columns: string[],
|
|
296
|
+
reference: {
|
|
297
|
+
table: string;
|
|
298
|
+
columns: string[];
|
|
299
|
+
onDelete?: "CASCADE" | "SET NULL" | "RESTRICT" | "NO ACTION";
|
|
300
|
+
onUpdate?: "CASCADE" | "SET NULL" | "RESTRICT" | "NO ACTION";
|
|
301
|
+
},
|
|
302
|
+
name?: string,
|
|
303
|
+
): this {
|
|
304
|
+
this.constraints.push({
|
|
305
|
+
type: "foreign",
|
|
306
|
+
columns,
|
|
307
|
+
reference,
|
|
308
|
+
name,
|
|
309
|
+
});
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
check(expression: string, name?: string): this {
|
|
314
|
+
this.constraints.push({
|
|
315
|
+
type: "check",
|
|
316
|
+
columns: [],
|
|
317
|
+
check: expression,
|
|
318
|
+
name,
|
|
319
|
+
});
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
comment(text: string): this {
|
|
324
|
+
this.tableComment = text;
|
|
325
|
+
return this;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
build(): TableSchema {
|
|
329
|
+
return {
|
|
330
|
+
name: this.name,
|
|
331
|
+
columns: { ...this.columns },
|
|
332
|
+
indexes: [...this.indexes],
|
|
333
|
+
constraints: [...this.constraints],
|
|
334
|
+
comment: this.tableComment,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ============= Migration Helpers =============
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Generate CREATE TABLE SQL from schema
|
|
343
|
+
*/
|
|
344
|
+
export function generateCreateTable(
|
|
345
|
+
schema: TableSchema,
|
|
346
|
+
driver: "postgresql" | "mysql" | "sqlite" = "postgresql",
|
|
347
|
+
): string {
|
|
348
|
+
const columnDefs: string[] = [];
|
|
349
|
+
const primaryKeys: string[] = [];
|
|
350
|
+
|
|
351
|
+
for (const [name, col] of Object.entries(schema.columns)) {
|
|
352
|
+
const parts: string[] = [name, mapColumnType(col, driver)];
|
|
353
|
+
|
|
354
|
+
if (col.primaryKey) {
|
|
355
|
+
primaryKeys.push(name);
|
|
356
|
+
if (driver === "sqlite" && col.type === "serial") {
|
|
357
|
+
parts[1] = "INTEGER PRIMARY KEY AUTOINCREMENT";
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (!col.nullable && !col.primaryKey) {
|
|
362
|
+
parts.push("NOT NULL");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (col.unique && !col.primaryKey) {
|
|
366
|
+
parts.push("UNIQUE");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (col.default !== undefined) {
|
|
370
|
+
parts.push(`DEFAULT ${formatDefault(col.default, driver)}`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (col.references) {
|
|
374
|
+
const ref = col.references;
|
|
375
|
+
parts.push(`REFERENCES ${ref.table}(${ref.column})`);
|
|
376
|
+
if (ref.onDelete) parts.push(`ON DELETE ${ref.onDelete}`);
|
|
377
|
+
if (ref.onUpdate) parts.push(`ON UPDATE ${ref.onUpdate}`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
columnDefs.push(parts.join(" "));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Add primary key constraint if multiple
|
|
384
|
+
if (primaryKeys.length > 1) {
|
|
385
|
+
columnDefs.push(`PRIMARY KEY (${primaryKeys.join(", ")})`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Add constraints
|
|
389
|
+
for (const constraint of schema.constraints ?? []) {
|
|
390
|
+
if (constraint.type === "unique") {
|
|
391
|
+
const name =
|
|
392
|
+
constraint.name || `uq_${schema.name}_${constraint.columns.join("_")}`;
|
|
393
|
+
columnDefs.push(
|
|
394
|
+
`CONSTRAINT ${name} UNIQUE (${constraint.columns.join(", ")})`,
|
|
395
|
+
);
|
|
396
|
+
} else if (constraint.type === "check" && constraint.check) {
|
|
397
|
+
const name = constraint.name || `chk_${schema.name}`;
|
|
398
|
+
columnDefs.push(`CONSTRAINT ${name} CHECK (${constraint.check})`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
let sql = `CREATE TABLE ${schema.name} (\n ${columnDefs.join(",\n ")}\n)`;
|
|
403
|
+
|
|
404
|
+
if (driver === "postgresql" && schema.comment) {
|
|
405
|
+
sql += `;\nCOMMENT ON TABLE ${schema.name} IS '${schema.comment}'`;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return `${sql};`;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Generate DROP TABLE SQL
|
|
413
|
+
*/
|
|
414
|
+
export function generateDropTable(tableName: string, ifExists = true): string {
|
|
415
|
+
if (ifExists) {
|
|
416
|
+
return `DROP TABLE IF EXISTS ${tableName};`;
|
|
417
|
+
}
|
|
418
|
+
return `DROP TABLE ${tableName};`;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Generate CREATE INDEX SQL
|
|
423
|
+
*/
|
|
424
|
+
export function generateCreateIndex(
|
|
425
|
+
tableName: string,
|
|
426
|
+
index: IndexDefinition,
|
|
427
|
+
driver: "postgresql" | "mysql" | "sqlite" = "postgresql",
|
|
428
|
+
): string {
|
|
429
|
+
const name = index.name || `idx_${tableName}_${index.columns.join("_")}`;
|
|
430
|
+
const unique = index.unique ? "UNIQUE " : "";
|
|
431
|
+
const type =
|
|
432
|
+
index.type && driver === "postgresql" ? `USING ${index.type} ` : "";
|
|
433
|
+
|
|
434
|
+
return `CREATE ${unique}INDEX ${name} ON ${tableName} ${type}(${index.columns.join(", ")});`;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ============= Type Mappers =============
|
|
438
|
+
|
|
439
|
+
function mapColumnType(
|
|
440
|
+
col: ColumnOptions,
|
|
441
|
+
driver: "postgresql" | "mysql" | "sqlite",
|
|
442
|
+
): string {
|
|
443
|
+
const typeMap: Record<ColumnType, Record<string, string>> = {
|
|
444
|
+
serial: {
|
|
445
|
+
postgresql: "SERIAL",
|
|
446
|
+
mysql: "INT AUTO_INCREMENT",
|
|
447
|
+
sqlite: "INTEGER",
|
|
448
|
+
},
|
|
449
|
+
integer: {
|
|
450
|
+
postgresql: "INTEGER",
|
|
451
|
+
mysql: "INT",
|
|
452
|
+
sqlite: "INTEGER",
|
|
453
|
+
},
|
|
454
|
+
bigint: {
|
|
455
|
+
postgresql: "BIGINT",
|
|
456
|
+
mysql: "BIGINT",
|
|
457
|
+
sqlite: "INTEGER",
|
|
458
|
+
},
|
|
459
|
+
decimal: {
|
|
460
|
+
postgresql: col.precision
|
|
461
|
+
? `DECIMAL(${col.precision}, ${col.scale ?? 0})`
|
|
462
|
+
: "DECIMAL",
|
|
463
|
+
mysql: col.precision
|
|
464
|
+
? `DECIMAL(${col.precision}, ${col.scale ?? 0})`
|
|
465
|
+
: "DECIMAL",
|
|
466
|
+
sqlite: "REAL",
|
|
467
|
+
},
|
|
468
|
+
varchar: {
|
|
469
|
+
postgresql: col.length ? `VARCHAR(${col.length})` : "VARCHAR",
|
|
470
|
+
mysql: col.length ? `VARCHAR(${col.length})` : "VARCHAR(255)",
|
|
471
|
+
sqlite: "TEXT",
|
|
472
|
+
},
|
|
473
|
+
text: {
|
|
474
|
+
postgresql: "TEXT",
|
|
475
|
+
mysql: "TEXT",
|
|
476
|
+
sqlite: "TEXT",
|
|
477
|
+
},
|
|
478
|
+
boolean: {
|
|
479
|
+
postgresql: "BOOLEAN",
|
|
480
|
+
mysql: "TINYINT(1)",
|
|
481
|
+
sqlite: "INTEGER",
|
|
482
|
+
},
|
|
483
|
+
date: {
|
|
484
|
+
postgresql: "DATE",
|
|
485
|
+
mysql: "DATE",
|
|
486
|
+
sqlite: "TEXT",
|
|
487
|
+
},
|
|
488
|
+
timestamp: {
|
|
489
|
+
postgresql: "TIMESTAMP",
|
|
490
|
+
mysql: "DATETIME",
|
|
491
|
+
sqlite: "TEXT",
|
|
492
|
+
},
|
|
493
|
+
timestamptz: {
|
|
494
|
+
postgresql: "TIMESTAMPTZ",
|
|
495
|
+
mysql: "DATETIME",
|
|
496
|
+
sqlite: "TEXT",
|
|
497
|
+
},
|
|
498
|
+
json: {
|
|
499
|
+
postgresql: "JSON",
|
|
500
|
+
mysql: "JSON",
|
|
501
|
+
sqlite: "TEXT",
|
|
502
|
+
},
|
|
503
|
+
jsonb: {
|
|
504
|
+
postgresql: "JSONB",
|
|
505
|
+
mysql: "JSON",
|
|
506
|
+
sqlite: "TEXT",
|
|
507
|
+
},
|
|
508
|
+
uuid: {
|
|
509
|
+
postgresql: "UUID",
|
|
510
|
+
mysql: "CHAR(36)",
|
|
511
|
+
sqlite: "TEXT",
|
|
512
|
+
},
|
|
513
|
+
blob: {
|
|
514
|
+
postgresql: "BYTEA",
|
|
515
|
+
mysql: "BLOB",
|
|
516
|
+
sqlite: "BLOB",
|
|
517
|
+
},
|
|
518
|
+
enum: {
|
|
519
|
+
postgresql: col.enum
|
|
520
|
+
? `ENUM(${col.enum.map((e) => `'${e}'`).join(", ")})`
|
|
521
|
+
: "VARCHAR",
|
|
522
|
+
mysql: col.enum
|
|
523
|
+
? `ENUM(${col.enum.map((e) => `'${e}'`).join(", ")})`
|
|
524
|
+
: "VARCHAR",
|
|
525
|
+
sqlite: "TEXT",
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
return typeMap[col.type][driver];
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function formatDefault(
|
|
533
|
+
value: unknown,
|
|
534
|
+
driver: "postgresql" | "mysql" | "sqlite",
|
|
535
|
+
): string {
|
|
536
|
+
if (value === null) return "NULL";
|
|
537
|
+
if (value === true) return driver === "sqlite" ? "1" : "TRUE";
|
|
538
|
+
if (value === false) return driver === "sqlite" ? "0" : "FALSE";
|
|
539
|
+
if (typeof value === "number") return String(value);
|
|
540
|
+
if (typeof value === "string") {
|
|
541
|
+
// Check for SQL functions
|
|
542
|
+
if (
|
|
543
|
+
value.toUpperCase() === "NOW()" ||
|
|
544
|
+
value.toUpperCase() === "CURRENT_TIMESTAMP"
|
|
545
|
+
) {
|
|
546
|
+
return driver === "mysql" ? "CURRENT_TIMESTAMP" : value.toUpperCase();
|
|
547
|
+
}
|
|
548
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
549
|
+
}
|
|
550
|
+
return String(value);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// ============= Type Inference Helpers =============
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* TypeScript type mapping from column types
|
|
557
|
+
* This is for documentation/type generation purposes
|
|
558
|
+
*/
|
|
559
|
+
export type TypeScriptType<T extends ColumnType> = T extends
|
|
560
|
+
| "serial"
|
|
561
|
+
| "integer"
|
|
562
|
+
? number
|
|
563
|
+
: T extends "bigint"
|
|
564
|
+
? bigint | string
|
|
565
|
+
: T extends "decimal"
|
|
566
|
+
? number | string
|
|
567
|
+
: T extends "varchar" | "text" | "uuid" | "enum"
|
|
568
|
+
? string
|
|
569
|
+
: T extends "boolean"
|
|
570
|
+
? boolean
|
|
571
|
+
: T extends "date" | "timestamp" | "timestamptz"
|
|
572
|
+
? Date | string
|
|
573
|
+
: T extends "json" | "jsonb"
|
|
574
|
+
? Record<string, unknown> | unknown[]
|
|
575
|
+
: T extends "blob"
|
|
576
|
+
? Buffer | ArrayBuffer
|
|
577
|
+
: unknown;
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Infer TypeScript type from schema
|
|
581
|
+
* Usage: type User = InferType<typeof userSchema>
|
|
582
|
+
*/
|
|
583
|
+
export type InferType<S extends TableSchema> = {
|
|
584
|
+
[K in keyof S["columns"]]: S["columns"][K] extends { nullable: true }
|
|
585
|
+
? TypeScriptType<S["columns"][K]["type"]> | null
|
|
586
|
+
: TypeScriptType<S["columns"][K]["type"]>;
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Infer insert type (optional fields with defaults)
|
|
591
|
+
*/
|
|
592
|
+
export type InferInsertType<S extends TableSchema> = {
|
|
593
|
+
[K in keyof S["columns"]]: S["columns"][K] extends { default: unknown }
|
|
594
|
+
? TypeScriptType<S["columns"][K]["type"]> | undefined
|
|
595
|
+
: S["columns"][K] extends { nullable: true }
|
|
596
|
+
? TypeScriptType<S["columns"][K]["type"]> | null | undefined
|
|
597
|
+
: TypeScriptType<S["columns"][K]["type"]>;
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// ============= Schema Factory =============
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Create a new schema builder
|
|
604
|
+
*/
|
|
605
|
+
export function createSchema(tableName: string): SchemaBuilder {
|
|
606
|
+
return new SchemaBuilder(tableName);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Define a table schema
|
|
611
|
+
*/
|
|
612
|
+
export function defineTable(
|
|
613
|
+
name: string,
|
|
614
|
+
define: (builder: SchemaBuilder) => void,
|
|
615
|
+
): TableSchema {
|
|
616
|
+
const builder = new SchemaBuilder(name);
|
|
617
|
+
define(builder);
|
|
618
|
+
return builder.build();
|
|
619
|
+
}
|