@buenojs/bueno 0.8.3 → 0.8.5
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/README.md +136 -16
- package/dist/cli/{index.js → bin.js} +3036 -1421
- package/dist/container/index.js +250 -0
- package/dist/context/index.js +219 -0
- package/dist/database/index.js +493 -0
- package/dist/frontend/index.js +7697 -0
- package/dist/health/index.js +364 -0
- package/dist/i18n/index.js +345 -0
- package/dist/index.js +11043 -6482
- package/dist/jobs/index.js +819 -0
- package/dist/lock/index.js +367 -0
- package/dist/logger/index.js +281 -0
- package/dist/metrics/index.js +289 -0
- package/dist/middleware/index.js +77 -0
- package/dist/migrations/index.js +571 -0
- package/dist/modules/index.js +3346 -0
- package/dist/notification/index.js +484 -0
- package/dist/observability/index.js +331 -0
- package/dist/openapi/index.js +776 -0
- package/dist/orm/index.js +1356 -0
- package/dist/router/index.js +886 -0
- package/dist/rpc/index.js +691 -0
- package/dist/schema/index.js +400 -0
- package/dist/telemetry/index.js +595 -0
- package/dist/template/index.js +640 -0
- package/dist/templates/index.js +640 -0
- package/dist/testing/index.js +1111 -0
- package/dist/types/index.js +60 -0
- package/package.json +121 -27
- package/src/cache/index.ts +2 -1
- package/src/cli/bin.ts +2 -2
- package/src/cli/commands/build.ts +183 -165
- package/src/cli/commands/dev.ts +96 -89
- package/src/cli/commands/generate.ts +142 -111
- package/src/cli/commands/help.ts +20 -16
- package/src/cli/commands/index.ts +3 -6
- package/src/cli/commands/migration.ts +124 -105
- package/src/cli/commands/new.ts +392 -438
- package/src/cli/commands/start.ts +81 -79
- package/src/cli/core/args.ts +68 -50
- package/src/cli/core/console.ts +89 -95
- package/src/cli/core/index.ts +4 -4
- package/src/cli/core/prompt.ts +65 -62
- package/src/cli/core/spinner.ts +23 -20
- package/src/cli/index.ts +46 -38
- package/src/cli/templates/database/index.ts +61 -0
- package/src/cli/templates/database/mysql.ts +14 -0
- package/src/cli/templates/database/none.ts +16 -0
- package/src/cli/templates/database/postgresql.ts +14 -0
- package/src/cli/templates/database/sqlite.ts +14 -0
- package/src/cli/templates/deploy.ts +29 -26
- package/src/cli/templates/docker.ts +41 -30
- package/src/cli/templates/frontend/index.ts +63 -0
- package/src/cli/templates/frontend/none.ts +17 -0
- package/src/cli/templates/frontend/react.ts +140 -0
- package/src/cli/templates/frontend/solid.ts +134 -0
- package/src/cli/templates/frontend/svelte.ts +131 -0
- package/src/cli/templates/frontend/vue.ts +130 -0
- package/src/cli/templates/generators/index.ts +339 -0
- package/src/cli/templates/generators/types.ts +56 -0
- package/src/cli/templates/index.ts +35 -2
- package/src/cli/templates/project/api.ts +81 -0
- package/src/cli/templates/project/default.ts +140 -0
- package/src/cli/templates/project/fullstack.ts +111 -0
- package/src/cli/templates/project/index.ts +95 -0
- package/src/cli/templates/project/minimal.ts +45 -0
- package/src/cli/templates/project/types.ts +94 -0
- package/src/cli/templates/project/website.ts +263 -0
- package/src/cli/utils/fs.ts +55 -41
- package/src/cli/utils/index.ts +3 -2
- package/src/cli/utils/strings.ts +47 -33
- package/src/cli/utils/version.ts +47 -0
- package/src/config/env-validation.ts +100 -0
- package/src/config/env.ts +169 -41
- package/src/config/index.ts +28 -20
- package/src/config/loader.ts +25 -16
- package/src/config/merge.ts +21 -10
- package/src/config/types.ts +545 -25
- package/src/config/validation.ts +215 -7
- package/src/container/forward-ref.ts +22 -22
- package/src/container/index.ts +34 -12
- package/src/context/index.ts +11 -1
- package/src/database/index.ts +7 -190
- package/src/database/orm/builder.ts +457 -0
- package/src/database/orm/casts/index.ts +130 -0
- package/src/database/orm/casts/types.ts +25 -0
- package/src/database/orm/compiler.ts +304 -0
- package/src/database/orm/hooks/index.ts +114 -0
- package/src/database/orm/index.ts +61 -0
- package/src/database/orm/model-registry.ts +59 -0
- package/src/database/orm/model.ts +821 -0
- package/src/database/orm/relationships/base.ts +146 -0
- package/src/database/orm/relationships/belongs-to-many.ts +179 -0
- package/src/database/orm/relationships/belongs-to.ts +56 -0
- package/src/database/orm/relationships/has-many.ts +45 -0
- package/src/database/orm/relationships/has-one.ts +41 -0
- package/src/database/orm/relationships/index.ts +11 -0
- package/src/database/orm/scopes/index.ts +55 -0
- package/src/events/__tests__/event-system.test.ts +235 -0
- package/src/events/config.ts +238 -0
- package/src/events/example-usage.ts +185 -0
- package/src/events/index.ts +278 -0
- package/src/events/manager.ts +385 -0
- package/src/events/registry.ts +182 -0
- package/src/events/types.ts +124 -0
- package/src/frontend/api-routes.ts +65 -23
- package/src/frontend/bundler.ts +76 -34
- package/src/frontend/console-client.ts +2 -2
- package/src/frontend/console-stream.ts +94 -38
- package/src/frontend/dev-server.ts +94 -46
- package/src/frontend/file-router.ts +61 -19
- package/src/frontend/frameworks/index.ts +37 -10
- package/src/frontend/frameworks/react.ts +10 -8
- package/src/frontend/frameworks/solid.ts +11 -9
- package/src/frontend/frameworks/svelte.ts +15 -9
- package/src/frontend/frameworks/vue.ts +13 -11
- package/src/frontend/hmr-client.ts +12 -10
- package/src/frontend/hmr.ts +146 -103
- package/src/frontend/index.ts +14 -5
- package/src/frontend/islands.ts +41 -22
- package/src/frontend/isr.ts +59 -37
- package/src/frontend/layout.ts +36 -21
- package/src/frontend/ssr/react.ts +74 -27
- package/src/frontend/ssr/solid.ts +54 -20
- package/src/frontend/ssr/svelte.ts +48 -14
- package/src/frontend/ssr/vue.ts +50 -18
- package/src/frontend/ssr.ts +83 -39
- package/src/frontend/types.ts +91 -56
- package/src/health/index.ts +21 -9
- package/src/i18n/engine.ts +305 -0
- package/src/i18n/index.ts +38 -0
- package/src/i18n/loader.ts +218 -0
- package/src/i18n/middleware.ts +164 -0
- package/src/i18n/negotiator.ts +162 -0
- package/src/i18n/types.ts +158 -0
- package/src/index.ts +179 -27
- package/src/jobs/drivers/memory.ts +315 -0
- package/src/jobs/drivers/redis.ts +459 -0
- package/src/jobs/index.ts +30 -0
- package/src/jobs/queue.ts +281 -0
- package/src/jobs/types.ts +295 -0
- package/src/jobs/worker.ts +380 -0
- package/src/logger/index.ts +1 -3
- package/src/logger/transports/index.ts +62 -22
- package/src/metrics/index.ts +25 -16
- package/src/migrations/index.ts +9 -0
- package/src/modules/filters.ts +13 -17
- package/src/modules/guards.ts +49 -26
- package/src/modules/index.ts +409 -298
- package/src/modules/interceptors.ts +58 -20
- package/src/modules/lazy.ts +11 -19
- package/src/modules/lifecycle.ts +15 -7
- package/src/modules/metadata.ts +15 -5
- package/src/modules/pipes.ts +94 -72
- package/src/notification/channels/base.ts +68 -0
- package/src/notification/channels/email.ts +105 -0
- package/src/notification/channels/push.ts +104 -0
- package/src/notification/channels/sms.ts +105 -0
- package/src/notification/channels/whatsapp.ts +104 -0
- package/src/notification/index.ts +48 -0
- package/src/notification/service.ts +354 -0
- package/src/notification/types.ts +344 -0
- package/src/observability/__tests__/observability.test.ts +483 -0
- package/src/observability/breadcrumbs.ts +114 -0
- package/src/observability/index.ts +136 -0
- package/src/observability/interceptor.ts +85 -0
- package/src/observability/service.ts +303 -0
- package/src/observability/trace.ts +37 -0
- package/src/observability/types.ts +196 -0
- package/src/openapi/__tests__/decorators.test.ts +335 -0
- package/src/openapi/__tests__/document-builder.test.ts +285 -0
- package/src/openapi/__tests__/route-scanner.test.ts +334 -0
- package/src/openapi/__tests__/schema-generator.test.ts +275 -0
- package/src/openapi/decorators.ts +328 -0
- package/src/openapi/document-builder.ts +274 -0
- package/src/openapi/index.ts +112 -0
- package/src/openapi/metadata.ts +112 -0
- package/src/openapi/route-scanner.ts +289 -0
- package/src/openapi/schema-generator.ts +256 -0
- package/src/openapi/swagger-module.ts +166 -0
- package/src/openapi/types.ts +398 -0
- package/src/orm/index.ts +10 -0
- package/src/rpc/index.ts +3 -1
- package/src/schema/index.ts +9 -0
- package/src/security/index.ts +15 -6
- package/src/ssg/index.ts +9 -8
- package/src/telemetry/index.ts +76 -22
- package/src/template/index.ts +7 -0
- package/src/templates/engine.ts +224 -0
- package/src/templates/index.ts +9 -0
- package/src/templates/loader.ts +331 -0
- package/src/templates/renderers/markdown.ts +212 -0
- package/src/templates/renderers/simple.ts +269 -0
- package/src/templates/types.ts +154 -0
- package/src/testing/index.ts +100 -27
- package/src/types/optional-deps.d.ts +347 -187
- package/src/validation/index.ts +92 -2
- package/src/validation/schemas.ts +536 -0
- package/tests/integration/fullstack.test.ts +4 -4
- package/tests/unit/database.test.ts +2 -72
- package/tests/unit/env-validation.test.ts +166 -0
- package/tests/unit/events.test.ts +910 -0
- package/tests/unit/i18n.test.ts +455 -0
- package/tests/unit/jobs.test.ts +493 -0
- package/tests/unit/notification.test.ts +988 -0
- package/tests/unit/observability.test.ts +453 -0
- package/tests/unit/orm/builder.test.ts +323 -0
- package/tests/unit/orm/casts.test.ts +179 -0
- package/tests/unit/orm/compiler.test.ts +220 -0
- package/tests/unit/orm/eager-loading.test.ts +285 -0
- package/tests/unit/orm/hooks.test.ts +191 -0
- package/tests/unit/orm/model.test.ts +373 -0
- package/tests/unit/orm/relationships.test.ts +303 -0
- package/tests/unit/orm/scopes.test.ts +74 -0
- package/tests/unit/templates-simple.test.ts +53 -0
- package/tests/unit/templates.test.ts +454 -0
- package/tests/unit/validation.test.ts +18 -24
- package/tsconfig.json +11 -3
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
var __legacyDecorateClassTS = function(decorators, target, key, desc) {
|
|
13
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
14
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
15
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
16
|
+
else
|
|
17
|
+
for (var i = decorators.length - 1;i >= 0; i--)
|
|
18
|
+
if (d = decorators[i])
|
|
19
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
20
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
21
|
+
};
|
|
22
|
+
var __legacyMetadataTS = (k, v) => {
|
|
23
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
|
|
24
|
+
return Reflect.metadata(k, v);
|
|
25
|
+
};
|
|
26
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
27
|
+
var __require = import.meta.require;
|
|
28
|
+
|
|
29
|
+
// src/database/schema/index.ts
|
|
30
|
+
class ColumnBuilder {
|
|
31
|
+
column;
|
|
32
|
+
constructor(type) {
|
|
33
|
+
this.column = { type, nullable: false };
|
|
34
|
+
}
|
|
35
|
+
nullable() {
|
|
36
|
+
this.column.nullable = true;
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
notNull() {
|
|
40
|
+
this.column.nullable = false;
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
default(value) {
|
|
44
|
+
this.column.default = value;
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
primaryKey() {
|
|
48
|
+
this.column.primaryKey = true;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
unique() {
|
|
52
|
+
this.column.unique = true;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
length(len) {
|
|
56
|
+
this.column.length = len;
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
precision(p, s) {
|
|
60
|
+
this.column.precision = p;
|
|
61
|
+
if (s !== undefined)
|
|
62
|
+
this.column.scale = s;
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
references(table, column, options) {
|
|
66
|
+
this.column.references = {
|
|
67
|
+
table,
|
|
68
|
+
column,
|
|
69
|
+
...options
|
|
70
|
+
};
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
check(expression) {
|
|
74
|
+
this.column.check = expression;
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
comment(text) {
|
|
78
|
+
this.column.comment = text;
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
build() {
|
|
82
|
+
return { ...this.column };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class SchemaBuilder {
|
|
87
|
+
name;
|
|
88
|
+
columns = {};
|
|
89
|
+
indexes = [];
|
|
90
|
+
constraints = [];
|
|
91
|
+
tableComment;
|
|
92
|
+
constructor(tableName) {
|
|
93
|
+
this.name = tableName;
|
|
94
|
+
}
|
|
95
|
+
serial(name) {
|
|
96
|
+
const builder = new ColumnBuilder("serial");
|
|
97
|
+
builder.primaryKey();
|
|
98
|
+
this.columns[name] = builder.build();
|
|
99
|
+
return builder;
|
|
100
|
+
}
|
|
101
|
+
integer(name) {
|
|
102
|
+
const builder = new ColumnBuilder("integer");
|
|
103
|
+
this.columns[name] = builder.build();
|
|
104
|
+
return builder;
|
|
105
|
+
}
|
|
106
|
+
bigint(name) {
|
|
107
|
+
const builder = new ColumnBuilder("bigint");
|
|
108
|
+
this.columns[name] = builder.build();
|
|
109
|
+
return builder;
|
|
110
|
+
}
|
|
111
|
+
decimal(name) {
|
|
112
|
+
const builder = new ColumnBuilder("decimal");
|
|
113
|
+
this.columns[name] = builder.build();
|
|
114
|
+
return builder;
|
|
115
|
+
}
|
|
116
|
+
varchar(name, length = 255) {
|
|
117
|
+
const builder = new ColumnBuilder("varchar");
|
|
118
|
+
builder.length(length);
|
|
119
|
+
this.columns[name] = builder.build();
|
|
120
|
+
return builder;
|
|
121
|
+
}
|
|
122
|
+
text(name) {
|
|
123
|
+
const builder = new ColumnBuilder("text");
|
|
124
|
+
this.columns[name] = builder.build();
|
|
125
|
+
return builder;
|
|
126
|
+
}
|
|
127
|
+
boolean(name) {
|
|
128
|
+
const builder = new ColumnBuilder("boolean");
|
|
129
|
+
this.columns[name] = builder.build();
|
|
130
|
+
return builder;
|
|
131
|
+
}
|
|
132
|
+
date(name) {
|
|
133
|
+
const builder = new ColumnBuilder("date");
|
|
134
|
+
this.columns[name] = builder.build();
|
|
135
|
+
return builder;
|
|
136
|
+
}
|
|
137
|
+
timestamp(name) {
|
|
138
|
+
const builder = new ColumnBuilder("timestamp");
|
|
139
|
+
this.columns[name] = builder.build();
|
|
140
|
+
return builder;
|
|
141
|
+
}
|
|
142
|
+
timestamptz(name) {
|
|
143
|
+
const builder = new ColumnBuilder("timestamptz");
|
|
144
|
+
this.columns[name] = builder.build();
|
|
145
|
+
return builder;
|
|
146
|
+
}
|
|
147
|
+
json(name) {
|
|
148
|
+
const builder = new ColumnBuilder("json");
|
|
149
|
+
this.columns[name] = builder.build();
|
|
150
|
+
return builder;
|
|
151
|
+
}
|
|
152
|
+
jsonb(name) {
|
|
153
|
+
const builder = new ColumnBuilder("jsonb");
|
|
154
|
+
this.columns[name] = builder.build();
|
|
155
|
+
return builder;
|
|
156
|
+
}
|
|
157
|
+
uuid(name) {
|
|
158
|
+
const builder = new ColumnBuilder("uuid");
|
|
159
|
+
this.columns[name] = builder.build();
|
|
160
|
+
return builder;
|
|
161
|
+
}
|
|
162
|
+
blob(name) {
|
|
163
|
+
const builder = new ColumnBuilder("blob");
|
|
164
|
+
this.columns[name] = builder.build();
|
|
165
|
+
return builder;
|
|
166
|
+
}
|
|
167
|
+
enum(name, values) {
|
|
168
|
+
const builder = new ColumnBuilder("enum");
|
|
169
|
+
builder.column.enum = values;
|
|
170
|
+
this.columns[name] = builder.build();
|
|
171
|
+
return builder;
|
|
172
|
+
}
|
|
173
|
+
column(name, options) {
|
|
174
|
+
this.columns[name] = options;
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
index(columns, options) {
|
|
178
|
+
this.indexes.push({
|
|
179
|
+
columns,
|
|
180
|
+
...options
|
|
181
|
+
});
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
unique(columns, name) {
|
|
185
|
+
this.constraints.push({
|
|
186
|
+
type: "unique",
|
|
187
|
+
columns,
|
|
188
|
+
name
|
|
189
|
+
});
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
foreignKey(columns, reference, name) {
|
|
193
|
+
this.constraints.push({
|
|
194
|
+
type: "foreign",
|
|
195
|
+
columns,
|
|
196
|
+
reference,
|
|
197
|
+
name
|
|
198
|
+
});
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
check(expression, name) {
|
|
202
|
+
this.constraints.push({
|
|
203
|
+
type: "check",
|
|
204
|
+
columns: [],
|
|
205
|
+
check: expression,
|
|
206
|
+
name
|
|
207
|
+
});
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
comment(text) {
|
|
211
|
+
this.tableComment = text;
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
build() {
|
|
215
|
+
return {
|
|
216
|
+
name: this.name,
|
|
217
|
+
columns: { ...this.columns },
|
|
218
|
+
indexes: [...this.indexes],
|
|
219
|
+
constraints: [...this.constraints],
|
|
220
|
+
comment: this.tableComment
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function generateCreateTable(schema, driver = "postgresql") {
|
|
225
|
+
const columnDefs = [];
|
|
226
|
+
const primaryKeys = [];
|
|
227
|
+
for (const [name, col] of Object.entries(schema.columns)) {
|
|
228
|
+
const parts = [name, mapColumnType(col, driver)];
|
|
229
|
+
if (col.primaryKey) {
|
|
230
|
+
primaryKeys.push(name);
|
|
231
|
+
if (driver === "sqlite" && col.type === "serial") {
|
|
232
|
+
parts[1] = "INTEGER PRIMARY KEY AUTOINCREMENT";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (!col.nullable && !col.primaryKey) {
|
|
236
|
+
parts.push("NOT NULL");
|
|
237
|
+
}
|
|
238
|
+
if (col.unique && !col.primaryKey) {
|
|
239
|
+
parts.push("UNIQUE");
|
|
240
|
+
}
|
|
241
|
+
if (col.default !== undefined) {
|
|
242
|
+
parts.push(`DEFAULT ${formatDefault(col.default, driver)}`);
|
|
243
|
+
}
|
|
244
|
+
if (col.references) {
|
|
245
|
+
const ref = col.references;
|
|
246
|
+
parts.push(`REFERENCES ${ref.table}(${ref.column})`);
|
|
247
|
+
if (ref.onDelete)
|
|
248
|
+
parts.push(`ON DELETE ${ref.onDelete}`);
|
|
249
|
+
if (ref.onUpdate)
|
|
250
|
+
parts.push(`ON UPDATE ${ref.onUpdate}`);
|
|
251
|
+
}
|
|
252
|
+
columnDefs.push(parts.join(" "));
|
|
253
|
+
}
|
|
254
|
+
if (primaryKeys.length > 1) {
|
|
255
|
+
columnDefs.push(`PRIMARY KEY (${primaryKeys.join(", ")})`);
|
|
256
|
+
}
|
|
257
|
+
for (const constraint of schema.constraints ?? []) {
|
|
258
|
+
if (constraint.type === "unique") {
|
|
259
|
+
const name = constraint.name || `uq_${schema.name}_${constraint.columns.join("_")}`;
|
|
260
|
+
columnDefs.push(`CONSTRAINT ${name} UNIQUE (${constraint.columns.join(", ")})`);
|
|
261
|
+
} else if (constraint.type === "check" && constraint.check) {
|
|
262
|
+
const name = constraint.name || `chk_${schema.name}`;
|
|
263
|
+
columnDefs.push(`CONSTRAINT ${name} CHECK (${constraint.check})`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
let sql = `CREATE TABLE ${schema.name} (
|
|
267
|
+
${columnDefs.join(`,
|
|
268
|
+
`)}
|
|
269
|
+
)`;
|
|
270
|
+
if (driver === "postgresql" && schema.comment) {
|
|
271
|
+
sql += `;
|
|
272
|
+
COMMENT ON TABLE ${schema.name} IS '${schema.comment}'`;
|
|
273
|
+
}
|
|
274
|
+
return `${sql};`;
|
|
275
|
+
}
|
|
276
|
+
function generateDropTable(tableName, ifExists = true) {
|
|
277
|
+
if (ifExists) {
|
|
278
|
+
return `DROP TABLE IF EXISTS ${tableName};`;
|
|
279
|
+
}
|
|
280
|
+
return `DROP TABLE ${tableName};`;
|
|
281
|
+
}
|
|
282
|
+
function generateCreateIndex(tableName, index, driver = "postgresql") {
|
|
283
|
+
const name = index.name || `idx_${tableName}_${index.columns.join("_")}`;
|
|
284
|
+
const unique = index.unique ? "UNIQUE " : "";
|
|
285
|
+
const type = index.type && driver === "postgresql" ? `USING ${index.type} ` : "";
|
|
286
|
+
return `CREATE ${unique}INDEX ${name} ON ${tableName} ${type}(${index.columns.join(", ")});`;
|
|
287
|
+
}
|
|
288
|
+
function mapColumnType(col, driver) {
|
|
289
|
+
const typeMap = {
|
|
290
|
+
serial: {
|
|
291
|
+
postgresql: "SERIAL",
|
|
292
|
+
mysql: "INT AUTO_INCREMENT",
|
|
293
|
+
sqlite: "INTEGER"
|
|
294
|
+
},
|
|
295
|
+
integer: {
|
|
296
|
+
postgresql: "INTEGER",
|
|
297
|
+
mysql: "INT",
|
|
298
|
+
sqlite: "INTEGER"
|
|
299
|
+
},
|
|
300
|
+
bigint: {
|
|
301
|
+
postgresql: "BIGINT",
|
|
302
|
+
mysql: "BIGINT",
|
|
303
|
+
sqlite: "INTEGER"
|
|
304
|
+
},
|
|
305
|
+
decimal: {
|
|
306
|
+
postgresql: col.precision ? `DECIMAL(${col.precision}, ${col.scale ?? 0})` : "DECIMAL",
|
|
307
|
+
mysql: col.precision ? `DECIMAL(${col.precision}, ${col.scale ?? 0})` : "DECIMAL",
|
|
308
|
+
sqlite: "REAL"
|
|
309
|
+
},
|
|
310
|
+
varchar: {
|
|
311
|
+
postgresql: col.length ? `VARCHAR(${col.length})` : "VARCHAR",
|
|
312
|
+
mysql: col.length ? `VARCHAR(${col.length})` : "VARCHAR(255)",
|
|
313
|
+
sqlite: "TEXT"
|
|
314
|
+
},
|
|
315
|
+
text: {
|
|
316
|
+
postgresql: "TEXT",
|
|
317
|
+
mysql: "TEXT",
|
|
318
|
+
sqlite: "TEXT"
|
|
319
|
+
},
|
|
320
|
+
boolean: {
|
|
321
|
+
postgresql: "BOOLEAN",
|
|
322
|
+
mysql: "TINYINT(1)",
|
|
323
|
+
sqlite: "INTEGER"
|
|
324
|
+
},
|
|
325
|
+
date: {
|
|
326
|
+
postgresql: "DATE",
|
|
327
|
+
mysql: "DATE",
|
|
328
|
+
sqlite: "TEXT"
|
|
329
|
+
},
|
|
330
|
+
timestamp: {
|
|
331
|
+
postgresql: "TIMESTAMP",
|
|
332
|
+
mysql: "DATETIME",
|
|
333
|
+
sqlite: "TEXT"
|
|
334
|
+
},
|
|
335
|
+
timestamptz: {
|
|
336
|
+
postgresql: "TIMESTAMPTZ",
|
|
337
|
+
mysql: "DATETIME",
|
|
338
|
+
sqlite: "TEXT"
|
|
339
|
+
},
|
|
340
|
+
json: {
|
|
341
|
+
postgresql: "JSON",
|
|
342
|
+
mysql: "JSON",
|
|
343
|
+
sqlite: "TEXT"
|
|
344
|
+
},
|
|
345
|
+
jsonb: {
|
|
346
|
+
postgresql: "JSONB",
|
|
347
|
+
mysql: "JSON",
|
|
348
|
+
sqlite: "TEXT"
|
|
349
|
+
},
|
|
350
|
+
uuid: {
|
|
351
|
+
postgresql: "UUID",
|
|
352
|
+
mysql: "CHAR(36)",
|
|
353
|
+
sqlite: "TEXT"
|
|
354
|
+
},
|
|
355
|
+
blob: {
|
|
356
|
+
postgresql: "BYTEA",
|
|
357
|
+
mysql: "BLOB",
|
|
358
|
+
sqlite: "BLOB"
|
|
359
|
+
},
|
|
360
|
+
enum: {
|
|
361
|
+
postgresql: col.enum ? `ENUM(${col.enum.map((e) => `'${e}'`).join(", ")})` : "VARCHAR",
|
|
362
|
+
mysql: col.enum ? `ENUM(${col.enum.map((e) => `'${e}'`).join(", ")})` : "VARCHAR",
|
|
363
|
+
sqlite: "TEXT"
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
return typeMap[col.type][driver];
|
|
367
|
+
}
|
|
368
|
+
function formatDefault(value, driver) {
|
|
369
|
+
if (value === null)
|
|
370
|
+
return "NULL";
|
|
371
|
+
if (value === true)
|
|
372
|
+
return driver === "sqlite" ? "1" : "TRUE";
|
|
373
|
+
if (value === false)
|
|
374
|
+
return driver === "sqlite" ? "0" : "FALSE";
|
|
375
|
+
if (typeof value === "number")
|
|
376
|
+
return String(value);
|
|
377
|
+
if (typeof value === "string") {
|
|
378
|
+
if (value.toUpperCase() === "NOW()" || value.toUpperCase() === "CURRENT_TIMESTAMP") {
|
|
379
|
+
return driver === "mysql" ? "CURRENT_TIMESTAMP" : value.toUpperCase();
|
|
380
|
+
}
|
|
381
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
382
|
+
}
|
|
383
|
+
return String(value);
|
|
384
|
+
}
|
|
385
|
+
function createSchema(tableName) {
|
|
386
|
+
return new SchemaBuilder(tableName);
|
|
387
|
+
}
|
|
388
|
+
function defineTable(name, define) {
|
|
389
|
+
const builder = new SchemaBuilder(name);
|
|
390
|
+
define(builder);
|
|
391
|
+
return builder.build();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/database/migrations/index.ts
|
|
395
|
+
class MigrationRunner {
|
|
396
|
+
db;
|
|
397
|
+
migrationsTable;
|
|
398
|
+
constructor(db, options = {}) {
|
|
399
|
+
this.db = db;
|
|
400
|
+
this.migrationsTable = options.migrationsTable ?? "_migrations";
|
|
401
|
+
}
|
|
402
|
+
async ensureMigrationsTable() {
|
|
403
|
+
await this.db.raw(`
|
|
404
|
+
CREATE TABLE IF NOT EXISTS ${this.migrationsTable} (
|
|
405
|
+
id VARCHAR(255) PRIMARY KEY,
|
|
406
|
+
name VARCHAR(255) NOT NULL,
|
|
407
|
+
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
408
|
+
)
|
|
409
|
+
`);
|
|
410
|
+
}
|
|
411
|
+
async getExecutedMigrations() {
|
|
412
|
+
await this.ensureMigrationsTable();
|
|
413
|
+
const results = await this.db.raw(`SELECT id, name, executed_at FROM ${this.migrationsTable} ORDER BY id ASC`);
|
|
414
|
+
return results.map((r) => ({
|
|
415
|
+
id: r.id,
|
|
416
|
+
name: r.name,
|
|
417
|
+
executedAt: new Date(r.executed_at)
|
|
418
|
+
}));
|
|
419
|
+
}
|
|
420
|
+
async getPendingMigrations(migrations) {
|
|
421
|
+
const executed = await this.getExecutedMigrations();
|
|
422
|
+
const executedIds = new Set(executed.map((m) => m.id));
|
|
423
|
+
return migrations.filter((m) => !executedIds.has(m.id)).sort((a, b) => a.id.localeCompare(b.id));
|
|
424
|
+
}
|
|
425
|
+
async runMigration(migration) {
|
|
426
|
+
await this.db.transaction(async () => {
|
|
427
|
+
await migration.up(this);
|
|
428
|
+
await this.db.raw(`INSERT INTO ${this.migrationsTable} (id, name) VALUES (?, ?)`, [migration.id, migration.name]);
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
async migrate(migrations) {
|
|
432
|
+
const pending = await this.getPendingMigrations(migrations);
|
|
433
|
+
const executed = [];
|
|
434
|
+
for (const migration of pending) {
|
|
435
|
+
await this.runMigration(migration);
|
|
436
|
+
executed.push(migration.id);
|
|
437
|
+
console.log(`Executed migration: ${migration.id} - ${migration.name}`);
|
|
438
|
+
}
|
|
439
|
+
return { executed };
|
|
440
|
+
}
|
|
441
|
+
async rollback(migrations, steps = 1) {
|
|
442
|
+
const executed = await this.getExecutedMigrations();
|
|
443
|
+
const rolledBack = [];
|
|
444
|
+
const migrationsById = new Map(migrations.map((m) => [m.id, m]));
|
|
445
|
+
const toRollback = executed.sort((a, b) => b.id.localeCompare(a.id)).slice(0, steps);
|
|
446
|
+
for (const record of toRollback) {
|
|
447
|
+
const migration = migrationsById.get(record.id);
|
|
448
|
+
if (!migration) {
|
|
449
|
+
console.warn(`Migration not found: ${record.id}`);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
await this.db.transaction(async () => {
|
|
453
|
+
await migration.down(this);
|
|
454
|
+
await this.db.raw(`DELETE FROM ${this.migrationsTable} WHERE id = ?`, [
|
|
455
|
+
record.id
|
|
456
|
+
]);
|
|
457
|
+
});
|
|
458
|
+
rolledBack.push(record.id);
|
|
459
|
+
console.log(`Rolled back migration: ${record.id} - ${record.name}`);
|
|
460
|
+
}
|
|
461
|
+
return { rolledBack };
|
|
462
|
+
}
|
|
463
|
+
async reset(migrations) {
|
|
464
|
+
const executed = await this.getExecutedMigrations();
|
|
465
|
+
return this.rollback(migrations, executed.length);
|
|
466
|
+
}
|
|
467
|
+
async refresh(migrations) {
|
|
468
|
+
const { rolledBack } = await this.reset(migrations);
|
|
469
|
+
const { executed } = await this.migrate(migrations);
|
|
470
|
+
return { rolledBack, executed };
|
|
471
|
+
}
|
|
472
|
+
async createTable(schema) {
|
|
473
|
+
const sql = generateCreateTable(schema, "postgresql");
|
|
474
|
+
await this.db.raw(sql);
|
|
475
|
+
for (const index of schema.indexes ?? []) {
|
|
476
|
+
await this.createIndex(schema.name, index);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
async dropTable(name) {
|
|
480
|
+
const sql = generateDropTable(name);
|
|
481
|
+
await this.db.raw(sql);
|
|
482
|
+
}
|
|
483
|
+
async createIndex(tableName, index) {
|
|
484
|
+
const sql = generateCreateIndex(tableName, index, "postgresql");
|
|
485
|
+
await this.db.raw(sql);
|
|
486
|
+
}
|
|
487
|
+
async dropIndex(name) {
|
|
488
|
+
await this.db.raw(`DROP INDEX IF EXISTS ${name}`);
|
|
489
|
+
}
|
|
490
|
+
async addColumn(table, name, type, options) {
|
|
491
|
+
let sql = `ALTER TABLE ${table} ADD COLUMN ${name} ${type}`;
|
|
492
|
+
if (options?.default !== undefined) {
|
|
493
|
+
sql += ` DEFAULT ${typeof options.default === "string" ? `'${options.default}'` : options.default}`;
|
|
494
|
+
}
|
|
495
|
+
if (!options?.nullable) {
|
|
496
|
+
sql += " NOT NULL";
|
|
497
|
+
}
|
|
498
|
+
await this.db.raw(sql);
|
|
499
|
+
}
|
|
500
|
+
async dropColumn(table, name) {
|
|
501
|
+
await this.db.raw(`ALTER TABLE ${table} DROP COLUMN ${name}`);
|
|
502
|
+
}
|
|
503
|
+
async renameColumn(table, oldName, newName) {
|
|
504
|
+
await this.db.raw(`ALTER TABLE ${table} RENAME COLUMN ${oldName} TO ${newName}`);
|
|
505
|
+
}
|
|
506
|
+
async addForeignKey(table, columns, reference, options) {
|
|
507
|
+
const name = options?.name ?? `fk_${table}_${columns.join("_")}`;
|
|
508
|
+
let sql = `ALTER TABLE ${table} ADD CONSTRAINT ${name} FOREIGN KEY (${columns.join(", ")}) REFERENCES ${reference.table}(${reference.columns.join(", ")})`;
|
|
509
|
+
if (options?.onDelete)
|
|
510
|
+
sql += ` ON DELETE ${options.onDelete}`;
|
|
511
|
+
if (options?.onUpdate)
|
|
512
|
+
sql += ` ON UPDATE ${options.onUpdate}`;
|
|
513
|
+
await this.db.raw(sql);
|
|
514
|
+
}
|
|
515
|
+
async dropForeignKey(table, name) {
|
|
516
|
+
await this.db.raw(`ALTER TABLE ${table} DROP CONSTRAINT ${name}`);
|
|
517
|
+
}
|
|
518
|
+
async raw(sql) {
|
|
519
|
+
await this.db.raw(sql);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
class MigrationBuilder {
|
|
524
|
+
id;
|
|
525
|
+
name;
|
|
526
|
+
upFn = async () => {};
|
|
527
|
+
downFn = async () => {};
|
|
528
|
+
constructor(id, name) {
|
|
529
|
+
this.id = id;
|
|
530
|
+
this.name = name;
|
|
531
|
+
}
|
|
532
|
+
up(fn) {
|
|
533
|
+
this.upFn = fn;
|
|
534
|
+
return this;
|
|
535
|
+
}
|
|
536
|
+
down(fn) {
|
|
537
|
+
this.downFn = fn;
|
|
538
|
+
return this;
|
|
539
|
+
}
|
|
540
|
+
build() {
|
|
541
|
+
return {
|
|
542
|
+
id: this.id,
|
|
543
|
+
name: this.name,
|
|
544
|
+
up: this.upFn,
|
|
545
|
+
down: this.downFn
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function createMigration(id, name) {
|
|
550
|
+
return new MigrationBuilder(id, name);
|
|
551
|
+
}
|
|
552
|
+
function createMigrationRunner(db, options) {
|
|
553
|
+
return new MigrationRunner(db, options);
|
|
554
|
+
}
|
|
555
|
+
function generateMigrationId() {
|
|
556
|
+
const now = new Date;
|
|
557
|
+
const year = now.getFullYear();
|
|
558
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
559
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
560
|
+
const hour = String(now.getHours()).padStart(2, "0");
|
|
561
|
+
const minute = String(now.getMinutes()).padStart(2, "0");
|
|
562
|
+
const second = String(now.getSeconds()).padStart(2, "0");
|
|
563
|
+
return `${year}${month}${day}${hour}${minute}${second}`;
|
|
564
|
+
}
|
|
565
|
+
export {
|
|
566
|
+
generateMigrationId,
|
|
567
|
+
createMigrationRunner,
|
|
568
|
+
createMigration,
|
|
569
|
+
MigrationRunner,
|
|
570
|
+
MigrationBuilder
|
|
571
|
+
};
|