tempest-db-js 0.2.0 → 0.3.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/README.md +2 -2
- package/dist/bin.cjs +89 -30
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +2 -2
- package/dist/{chunk-QMW4NKMH.js → chunk-OP7FRDI5.js} +199 -34
- package/dist/chunk-OP7FRDI5.js.map +1 -0
- package/dist/{chunk-AGDD7K3F.js → chunk-Q32CBI2A.js} +133 -16
- package/dist/chunk-Q32CBI2A.js.map +1 -0
- package/dist/index.cjs +131 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -6
- package/dist/index.d.ts +28 -6
- package/dist/index.js +1 -1
- package/dist/migrations/index.cjs +196 -30
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +42 -5
- package/dist/migrations/index.d.ts +42 -5
- package/dist/migrations/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-AGDD7K3F.js.map +0 -1
- package/dist/chunk-QMW4NKMH.js.map +0 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
📖 **Documentation:** [Português (BR)](https://mauriciobenjamin700.github.io/tempest-db-js/) · [English (US)](https://mauriciobenjamin700.github.io/tempest-db-js/en/)
|
|
7
7
|
|
|
8
|
-
> ✅ **Status: alpha (v0.
|
|
8
|
+
> ✅ **Status: alpha (v0.3.0), published on [npm](https://www.npmjs.com/package/tempest-db-js).** The full path works end-to-end — declarative models, typed query builder (aggregations, `DISTINCT`, upsert), **real SQLite + PostgreSQL execution**, a **MySQL** dialect, joins, relations, Alembic-style migrations (sync + **async** runner) with a `tempest-db` CLI, a typed `BaseRepository`, and an opt-in active-record layer. The public API may still shift before v1.0.
|
|
9
9
|
|
|
10
10
|
## Why tempest-db-js
|
|
11
11
|
|
|
@@ -105,7 +105,7 @@ HTTP integration recipes (Hono, Express, Fastify) live in the [docs](https://mau
|
|
|
105
105
|
|
|
106
106
|
## Roadmap
|
|
107
107
|
|
|
108
|
-
See [ROADMAP.md](./ROADMAP.md). Shipped: SQLite + PostgreSQL execution, joins, relations,
|
|
108
|
+
See [ROADMAP.md](./ROADMAP.md). Shipped (v0.3.0): SQLite + PostgreSQL execution (both tested in CI, Postgres against a live database), a MySQL dialect, joins, relations, sync + async migration runners with a `tempest-db` CLI, repository, aggregations/upsert, opt-in active-record. Next: MySQL execution in CI + `RETURNING` round-trip, async CLI wiring, then `tempest-ts-sdk`.
|
|
109
109
|
|
|
110
110
|
## Development
|
|
111
111
|
|
package/dist/bin.cjs
CHANGED
|
@@ -97,8 +97,8 @@ function makeRevisionId(label, parents) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
// src/migrations/ddl.ts
|
|
100
|
-
function quoteId(name) {
|
|
101
|
-
return `"${name.replace(/"/g, '""')}"`;
|
|
100
|
+
function quoteId(name, dialect) {
|
|
101
|
+
return dialect === "mysql" ? `\`${name.replace(/`/g, "``")}\`` : `"${name.replace(/"/g, '""')}"`;
|
|
102
102
|
}
|
|
103
103
|
function quoteLiteral(value) {
|
|
104
104
|
return `'${value.replace(/'/g, "''")}'`;
|
|
@@ -123,6 +123,45 @@ function renderColumnType(type, dialect) {
|
|
|
123
123
|
return "TEXT";
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
+
if (dialect === "mysql") {
|
|
127
|
+
switch (kind) {
|
|
128
|
+
case "smallint":
|
|
129
|
+
return "SMALLINT";
|
|
130
|
+
case "integer":
|
|
131
|
+
return "INT";
|
|
132
|
+
case "bigint":
|
|
133
|
+
return "BIGINT";
|
|
134
|
+
case "numeric":
|
|
135
|
+
return meta.precision !== void 0 ? `DECIMAL(${meta.precision}${meta.scale !== void 0 ? `, ${meta.scale}` : ""})` : "DECIMAL";
|
|
136
|
+
case "real":
|
|
137
|
+
return "FLOAT";
|
|
138
|
+
case "double":
|
|
139
|
+
return "DOUBLE";
|
|
140
|
+
case "varchar":
|
|
141
|
+
return `VARCHAR(${meta.length ?? 255})`;
|
|
142
|
+
case "char":
|
|
143
|
+
return `CHAR(${meta.length ?? 255})`;
|
|
144
|
+
case "text":
|
|
145
|
+
return "TEXT";
|
|
146
|
+
case "boolean":
|
|
147
|
+
return "TINYINT(1)";
|
|
148
|
+
case "date":
|
|
149
|
+
return "DATE";
|
|
150
|
+
case "time":
|
|
151
|
+
return "TIME";
|
|
152
|
+
case "datetime":
|
|
153
|
+
case "timestamp":
|
|
154
|
+
return "DATETIME";
|
|
155
|
+
case "blob":
|
|
156
|
+
return "BLOB";
|
|
157
|
+
case "json":
|
|
158
|
+
return "JSON";
|
|
159
|
+
case "uuid":
|
|
160
|
+
return "CHAR(36)";
|
|
161
|
+
case "enum":
|
|
162
|
+
return `ENUM(${(meta.values ?? []).map(quoteLiteral).join(", ")})`;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
126
165
|
switch (kind) {
|
|
127
166
|
case "smallint":
|
|
128
167
|
return "SMALLINT";
|
|
@@ -167,19 +206,21 @@ function renderDefault(def, dialect) {
|
|
|
167
206
|
if (typeof expr === "object") return expr.raw;
|
|
168
207
|
switch (expr) {
|
|
169
208
|
case "now":
|
|
170
|
-
return dialect === "
|
|
209
|
+
return dialect === "postgresql" ? "now()" : "CURRENT_TIMESTAMP";
|
|
171
210
|
case "current_date":
|
|
172
211
|
return "CURRENT_DATE";
|
|
173
212
|
case "current_time":
|
|
174
213
|
return "CURRENT_TIME";
|
|
175
214
|
case "uuidv4":
|
|
176
|
-
|
|
215
|
+
if (dialect === "postgresql") return "gen_random_uuid()";
|
|
216
|
+
if (dialect === "mysql") return "(UUID())";
|
|
217
|
+
return "(lower(hex(randomblob(16))))";
|
|
177
218
|
}
|
|
178
219
|
}
|
|
179
220
|
const value = def.value;
|
|
180
221
|
if (value === null) return "NULL";
|
|
181
222
|
if (typeof value === "boolean") {
|
|
182
|
-
return dialect === "
|
|
223
|
+
return dialect === "postgresql" ? value ? "TRUE" : "FALSE" : value ? "1" : "0";
|
|
183
224
|
}
|
|
184
225
|
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
185
226
|
if (value instanceof Date) return quoteLiteral(value.toISOString());
|
|
@@ -187,7 +228,7 @@ function renderDefault(def, dialect) {
|
|
|
187
228
|
return quoteLiteral(String(value));
|
|
188
229
|
}
|
|
189
230
|
function renderColumnDef(col, dialect) {
|
|
190
|
-
let sql = `${quoteId(col.name)} ${renderColumnType(col.type, dialect)}`;
|
|
231
|
+
let sql = `${quoteId(col.name, dialect)} ${renderColumnType(col.type, dialect)}`;
|
|
191
232
|
if (col.notNull) sql += " NOT NULL";
|
|
192
233
|
if (col.default !== null) sql += ` DEFAULT ${renderDefault(col.default, dialect)}`;
|
|
193
234
|
return sql;
|
|
@@ -209,23 +250,28 @@ function renderCreateTable(table, dialect) {
|
|
|
209
250
|
if (dialect === "postgresql" && c.type.kind === "enum") {
|
|
210
251
|
const typeName = enumTypeName(table.name, c.name);
|
|
211
252
|
const values = (c.type.meta.values ?? []).map(quoteLiteral).join(", ");
|
|
212
|
-
typeStmts.push(`CREATE TYPE ${quoteId(typeName)} AS ENUM (${values})`);
|
|
213
|
-
let def = `${quoteId(c.name)} ${quoteId(typeName)}`;
|
|
253
|
+
typeStmts.push(`CREATE TYPE ${quoteId(typeName, dialect)} AS ENUM (${values})`);
|
|
254
|
+
let def = `${quoteId(c.name, dialect)} ${quoteId(typeName, dialect)}`;
|
|
214
255
|
if (c.notNull) def += " NOT NULL";
|
|
215
256
|
if (c.default !== null) def += ` DEFAULT ${renderDefault(c.default, dialect)}`;
|
|
216
257
|
return def;
|
|
217
258
|
}
|
|
218
259
|
if (dialect === "postgresql" && isAutoIncrementPk(table, c)) {
|
|
219
|
-
return `${quoteId(c.name)} ${postgresSerialType(c.type.kind)}`;
|
|
260
|
+
return `${quoteId(c.name, dialect)} ${postgresSerialType(c.type.kind)}`;
|
|
261
|
+
}
|
|
262
|
+
if (dialect === "mysql" && isAutoIncrementPk(table, c)) {
|
|
263
|
+
return `${quoteId(c.name, dialect)} ${renderColumnType(c.type, dialect)} NOT NULL AUTO_INCREMENT`;
|
|
220
264
|
}
|
|
221
265
|
return renderColumnDef(c, dialect);
|
|
222
266
|
});
|
|
223
267
|
if (table.primaryKey.length > 0) {
|
|
224
|
-
cols.push(
|
|
268
|
+
cols.push(
|
|
269
|
+
`PRIMARY KEY (${table.primaryKey.map((c) => quoteId(c, dialect)).join(", ")})`
|
|
270
|
+
);
|
|
225
271
|
}
|
|
226
272
|
return [
|
|
227
273
|
...typeStmts,
|
|
228
|
-
`CREATE TABLE ${quoteId(table.name)} (
|
|
274
|
+
`CREATE TABLE ${quoteId(table.name, dialect)} (
|
|
229
275
|
${cols.join(",\n ")}
|
|
230
276
|
)`
|
|
231
277
|
];
|
|
@@ -235,23 +281,27 @@ function renderOperation(op, dialect) {
|
|
|
235
281
|
case "create_table":
|
|
236
282
|
return renderCreateTable(op.table, dialect);
|
|
237
283
|
case "drop_table":
|
|
238
|
-
return [`DROP TABLE ${quoteId(op.table.name)}`];
|
|
284
|
+
return [`DROP TABLE ${quoteId(op.table.name, dialect)}`];
|
|
239
285
|
case "rename_table":
|
|
240
|
-
return [`
|
|
286
|
+
return dialect === "mysql" ? [`RENAME TABLE ${quoteId(op.from, dialect)} TO ${quoteId(op.to, dialect)}`] : [
|
|
287
|
+
`ALTER TABLE ${quoteId(op.from, dialect)} RENAME TO ${quoteId(op.to, dialect)}`
|
|
288
|
+
];
|
|
241
289
|
case "add_column":
|
|
242
290
|
return [
|
|
243
|
-
`ALTER TABLE ${quoteId(op.table)} ADD COLUMN ${renderColumnDef(op.column, dialect)}`
|
|
291
|
+
`ALTER TABLE ${quoteId(op.table, dialect)} ADD COLUMN ${renderColumnDef(op.column, dialect)}`
|
|
244
292
|
];
|
|
245
293
|
case "drop_column":
|
|
246
|
-
return [
|
|
294
|
+
return [
|
|
295
|
+
`ALTER TABLE ${quoteId(op.table, dialect)} DROP COLUMN ${quoteId(op.column.name, dialect)}`
|
|
296
|
+
];
|
|
247
297
|
case "rename_column":
|
|
248
298
|
return [
|
|
249
|
-
`ALTER TABLE ${quoteId(op.table)} RENAME COLUMN ${quoteId(op.from)} TO ${quoteId(op.to)}`
|
|
299
|
+
`ALTER TABLE ${quoteId(op.table, dialect)} RENAME COLUMN ${quoteId(op.from, dialect)} TO ${quoteId(op.to, dialect)}`
|
|
250
300
|
];
|
|
251
301
|
case "alter_column":
|
|
252
302
|
return renderAlterColumn(op.table, op.to, dialect);
|
|
253
303
|
case "recreate_table":
|
|
254
|
-
return dialect === "sqlite" ? renderSqliteRebuild(op.from, op.to) :
|
|
304
|
+
return dialect === "sqlite" ? renderSqliteRebuild(op.from, op.to) : renderTableDiff(op.from, op.to, dialect);
|
|
255
305
|
case "execute":
|
|
256
306
|
return [op.up];
|
|
257
307
|
}
|
|
@@ -261,34 +311,38 @@ function renderSqliteRebuild(from, to) {
|
|
|
261
311
|
const common = Object.keys(to.columns).filter((c) => c in from.columns);
|
|
262
312
|
const cols = Object.values(to.columns).map((c) => renderColumnDef(c, "sqlite"));
|
|
263
313
|
if (to.primaryKey.length > 0) {
|
|
264
|
-
cols.push(
|
|
314
|
+
cols.push(
|
|
315
|
+
`PRIMARY KEY (${to.primaryKey.map((c) => quoteId(c, "sqlite")).join(", ")})`
|
|
316
|
+
);
|
|
265
317
|
}
|
|
266
|
-
const commonSql = common.map(quoteId).join(", ");
|
|
318
|
+
const commonSql = common.map((c) => quoteId(c, "sqlite")).join(", ");
|
|
267
319
|
return [
|
|
268
320
|
"PRAGMA foreign_keys=off",
|
|
269
|
-
`CREATE TABLE ${quoteId(tmp)} (
|
|
321
|
+
`CREATE TABLE ${quoteId(tmp, "sqlite")} (
|
|
270
322
|
${cols.join(",\n ")}
|
|
271
323
|
)`,
|
|
272
|
-
common.length > 0 ? `INSERT INTO ${quoteId(tmp)} (${commonSql}) SELECT ${commonSql} FROM ${quoteId(from.name)}` : `-- no common columns to copy from ${from.name}`,
|
|
273
|
-
`DROP TABLE ${quoteId(from.name)}`,
|
|
274
|
-
`ALTER TABLE ${quoteId(tmp)} RENAME TO ${quoteId(to.name)}`,
|
|
324
|
+
common.length > 0 ? `INSERT INTO ${quoteId(tmp, "sqlite")} (${commonSql}) SELECT ${commonSql} FROM ${quoteId(from.name, "sqlite")}` : `-- no common columns to copy from ${from.name}`,
|
|
325
|
+
`DROP TABLE ${quoteId(from.name, "sqlite")}`,
|
|
326
|
+
`ALTER TABLE ${quoteId(tmp, "sqlite")} RENAME TO ${quoteId(to.name, "sqlite")}`,
|
|
275
327
|
"PRAGMA foreign_keys=on"
|
|
276
328
|
];
|
|
277
329
|
}
|
|
278
|
-
function
|
|
330
|
+
function renderTableDiff(from, to, dialect) {
|
|
279
331
|
const stmts = [];
|
|
280
332
|
for (const [name, col] of Object.entries(to.columns)) {
|
|
281
333
|
if (!(name in from.columns)) {
|
|
282
334
|
stmts.push(
|
|
283
|
-
`ALTER TABLE ${quoteId(to.name)} ADD COLUMN ${renderColumnDef(col,
|
|
335
|
+
`ALTER TABLE ${quoteId(to.name, dialect)} ADD COLUMN ${renderColumnDef(col, dialect)}`
|
|
284
336
|
);
|
|
285
337
|
} else {
|
|
286
|
-
stmts.push(...renderAlterColumn(to.name, col,
|
|
338
|
+
stmts.push(...renderAlterColumn(to.name, col, dialect));
|
|
287
339
|
}
|
|
288
340
|
}
|
|
289
341
|
for (const name of Object.keys(from.columns)) {
|
|
290
342
|
if (!(name in to.columns)) {
|
|
291
|
-
stmts.push(
|
|
343
|
+
stmts.push(
|
|
344
|
+
`ALTER TABLE ${quoteId(to.name, dialect)} DROP COLUMN ${quoteId(name, dialect)}`
|
|
345
|
+
);
|
|
292
346
|
}
|
|
293
347
|
}
|
|
294
348
|
return stmts;
|
|
@@ -296,11 +350,16 @@ function renderPostgresTableDiff(from, to) {
|
|
|
296
350
|
function renderAlterColumn(table, to, dialect) {
|
|
297
351
|
if (dialect === "sqlite") {
|
|
298
352
|
throw new Error(
|
|
299
|
-
`alter_column on SQLite needs
|
|
353
|
+
`alter_column on SQLite needs a table-rebuild (recreate_table); column ${table}.${to.name}`
|
|
300
354
|
);
|
|
301
355
|
}
|
|
302
|
-
|
|
303
|
-
|
|
356
|
+
if (dialect === "mysql") {
|
|
357
|
+
return [
|
|
358
|
+
`ALTER TABLE ${quoteId(table, dialect)} MODIFY COLUMN ${renderColumnDef(to, dialect)}`
|
|
359
|
+
];
|
|
360
|
+
}
|
|
361
|
+
const t = quoteId(table, dialect);
|
|
362
|
+
const c = quoteId(to.name, dialect);
|
|
304
363
|
const stmts = [
|
|
305
364
|
`ALTER TABLE ${t} ALTER COLUMN ${c} TYPE ${renderColumnType(to.type, dialect)}`
|
|
306
365
|
];
|