@stacksjs/database 0.70.112 → 0.70.114
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/dist/migrations.js +4 -4
- package/dist/schema.d.ts +8 -1
- package/dist/types.d.ts +22 -0
- package/dist/types.js +79 -4
- package/dist/utils.js +3 -0
- package/package.json +14 -12
package/dist/migrations.js
CHANGED
|
@@ -615,10 +615,10 @@ export function groupGeneratedStatements(sqlStatements) {
|
|
|
615
615
|
push(`create-${create[1]}-table`, stmt);
|
|
616
616
|
continue;
|
|
617
617
|
}
|
|
618
|
-
const alter = stmt.match(/^\s*ALTER\s+TABLE\s+["`]?(\w+)["`]?\s+(?:ADD\s+COLUMN\s+["`]?(\w+)["`]?|DROP\s+COLUMN\s+["`]?(\w+)["`]?|ADD\s+CONSTRAINT)/i);
|
|
619
|
-
if (alter) {
|
|
620
|
-
const isCreateTimeConstraint = createdTables.has(
|
|
621
|
-
push(isCreateTimeConstraint ? "create-foreign-key-constraints" : `alter-${
|
|
618
|
+
const alter = stmt.match(/^\s*ALTER\s+TABLE\s+["`]?(\w+)["`]?\s+(?:ADD\s+COLUMN\s+["`]?(\w+)["`]?|DROP\s+COLUMN\s+["`]?(\w+)["`]?|ADD\s+CONSTRAINT)/i), alterTable = alter?.[1];
|
|
619
|
+
if (alter && alterTable) {
|
|
620
|
+
const isCreateTimeConstraint = createdTables.has(alterTable) && !alter[2] && !alter[3];
|
|
621
|
+
push(isCreateTimeConstraint ? "create-foreign-key-constraints" : `alter-${alterTable}-${alter[2] || alter[3] || "constraint"}`, stmt);
|
|
622
622
|
continue;
|
|
623
623
|
}
|
|
624
624
|
const idx = stmt.match(/^\s*CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?["`]?(\w+)["`]?\s+ON\s+["`]?(\w+)["`]?/i), idxName = idx?.[1], idxTable = idx?.[2];
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* @defaultValue
|
|
3
|
+
* ```ts
|
|
4
|
+
* {
|
|
5
|
+
* createTable: (tableName: string, callback: (table: Table) => void) => Promise<void>
|
|
6
|
+
* }
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
2
9
|
export declare const Schema: {
|
|
3
10
|
createTable: (tableName: string, callback: (table: Table) => void) => Promise<void>
|
|
4
11
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -8,6 +8,21 @@
|
|
|
8
8
|
* ```
|
|
9
9
|
*/
|
|
10
10
|
export declare function sql(strings: TemplateStringsArray, ...values: unknown[]): Sql;
|
|
11
|
+
/**
|
|
12
|
+
* Runtime implementation behind `db.fn`. bun-query-builder has no
|
|
13
|
+
* top-level `fn` accessor (its select pipeline consumes plain SQL
|
|
14
|
+
* fragments), so the `db` proxy serves this object directly. Every
|
|
15
|
+
* aggregate renders to a fragment whose `.sql` text the select /
|
|
16
|
+
* groupBy machinery picks up verbatim, e.g.:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* db.selectFrom('query_logs')
|
|
20
|
+
* .select(db.fn.count('id').as('count'))
|
|
21
|
+
* .execute()
|
|
22
|
+
* // SELECT COUNT(id) AS count FROM query_logs
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare const aggregateFunctions: ExpressionFunctions;
|
|
11
26
|
/**
|
|
12
27
|
* Type for raw SQL expressions.
|
|
13
28
|
* Used when building dynamic SQL queries.
|
|
@@ -24,6 +39,8 @@ export declare interface RawBuilder<T = unknown> {
|
|
|
24
39
|
export declare interface Sql {
|
|
25
40
|
readonly sql: string
|
|
26
41
|
readonly parameters: unknown[]
|
|
42
|
+
as: (alias: string) => Sql
|
|
43
|
+
toString: () => string
|
|
27
44
|
}
|
|
28
45
|
/**
|
|
29
46
|
* Reference to a column for use inside an expression. Returned by
|
|
@@ -43,8 +60,13 @@ export declare interface ColumnRef {
|
|
|
43
60
|
* names the resulting column in the projection; `.filterWhere(...)`
|
|
44
61
|
* scopes the aggregate to a sub-population (`COUNT(*) FILTER (WHERE
|
|
45
62
|
* status = 'success')` style).
|
|
63
|
+
*
|
|
64
|
+
* The `sql` text is part of the contract: bun-query-builder's select
|
|
65
|
+
* pipeline renders fragments from their `.sql` property, so every
|
|
66
|
+
* chain step returns a fresh expression with the fully rendered text.
|
|
46
67
|
*/
|
|
47
68
|
export declare interface AggregateExpression {
|
|
69
|
+
readonly sql: string
|
|
48
70
|
as: (alias: string) => AggregateExpression
|
|
49
71
|
filterWhere: (column: string, op: ExpressionOperator | string, value: unknown) => AggregateExpression
|
|
50
72
|
}
|
package/dist/types.js
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
const SAFE_ALIAS = /^[A-Z_][A-Z0-9_]*$/i, SAFE_COLUMN = /^[A-Z_][A-Z0-9_]*(\.[A-Z_][A-Z0-9_]*)?$/i;
|
|
2
|
+
function assertSqlTextIdentifier(value, kind) {
|
|
3
|
+
if (!(kind === "column" ? SAFE_COLUMN : SAFE_ALIAS).test(value))
|
|
4
|
+
throw TypeError(`[database] refusing to interpolate unsafe ${kind} ${JSON.stringify(value)} into SQL text - expected a plain identifier (letters, digits, underscores)`);
|
|
5
|
+
}
|
|
6
|
+
function createSqlFragment(text, parameters) {
|
|
7
|
+
return {
|
|
8
|
+
sql: text,
|
|
9
|
+
parameters,
|
|
10
|
+
as(alias) {
|
|
11
|
+
assertSqlTextIdentifier(alias, "alias");
|
|
12
|
+
return createSqlFragment(`${text} AS ${alias}`, parameters);
|
|
13
|
+
},
|
|
14
|
+
toString() {
|
|
15
|
+
return text;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
1
19
|
export function sql(strings, ...values) {
|
|
2
20
|
const sqlParts = [], parameters = [];
|
|
3
21
|
for (let i = 0;i < strings.length; i++) {
|
|
@@ -10,10 +28,7 @@ export function sql(strings, ...values) {
|
|
|
10
28
|
parameters.push(values[i]);
|
|
11
29
|
}
|
|
12
30
|
}
|
|
13
|
-
return
|
|
14
|
-
sql: sqlParts.join(""),
|
|
15
|
-
parameters
|
|
16
|
-
};
|
|
31
|
+
return createSqlFragment(sqlParts.join(""), parameters);
|
|
17
32
|
}
|
|
18
33
|
sql.raw = function raw(value) {
|
|
19
34
|
return { raw: value };
|
|
@@ -21,3 +36,63 @@ sql.raw = function raw(value) {
|
|
|
21
36
|
sql.ref = function ref(column) {
|
|
22
37
|
return { raw: column };
|
|
23
38
|
};
|
|
39
|
+
sql.literal = function literal(value) {
|
|
40
|
+
return { raw: inlineSqlLiteral(value) };
|
|
41
|
+
};
|
|
42
|
+
const SAFE_FILTER_OPERATORS = new Set([
|
|
43
|
+
"=",
|
|
44
|
+
"!=",
|
|
45
|
+
"<>",
|
|
46
|
+
"<",
|
|
47
|
+
"<=",
|
|
48
|
+
">",
|
|
49
|
+
">=",
|
|
50
|
+
"like",
|
|
51
|
+
"not like",
|
|
52
|
+
"ilike",
|
|
53
|
+
"not ilike",
|
|
54
|
+
"is",
|
|
55
|
+
"is not"
|
|
56
|
+
]);
|
|
57
|
+
function inlineSqlLiteral(value) {
|
|
58
|
+
if (value === null || value === void 0)
|
|
59
|
+
return "NULL";
|
|
60
|
+
if (typeof value === "number") {
|
|
61
|
+
if (!Number.isFinite(value))
|
|
62
|
+
throw TypeError(`[database] refusing to inline non-finite number into SQL: ${value}`);
|
|
63
|
+
return String(value);
|
|
64
|
+
}
|
|
65
|
+
if (typeof value === "boolean")
|
|
66
|
+
return value ? "1" : "0";
|
|
67
|
+
if (typeof value === "bigint")
|
|
68
|
+
return value.toString();
|
|
69
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
70
|
+
}
|
|
71
|
+
function createAggregateExpression(text) {
|
|
72
|
+
return {
|
|
73
|
+
sql: text,
|
|
74
|
+
as(alias) {
|
|
75
|
+
assertSqlTextIdentifier(alias, "alias");
|
|
76
|
+
return createAggregateExpression(`${text} AS ${alias}`);
|
|
77
|
+
},
|
|
78
|
+
filterWhere(column, op, value) {
|
|
79
|
+
assertSqlTextIdentifier(column, "column");
|
|
80
|
+
if (!SAFE_FILTER_OPERATORS.has(op.toLowerCase()))
|
|
81
|
+
throw TypeError(`[database] refusing unsafe aggregate filter operator ${JSON.stringify(op)} - allowed: ${[...SAFE_FILTER_OPERATORS].join(", ")}`);
|
|
82
|
+
return createAggregateExpression(`${text} FILTER (WHERE ${column} ${op} ${inlineSqlLiteral(value)})`);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function aggregate(name, column) {
|
|
87
|
+
if (column !== void 0)
|
|
88
|
+
assertSqlTextIdentifier(column, "column");
|
|
89
|
+
return createAggregateExpression(column === void 0 ? `${name}(*)` : `${name}(${column})`);
|
|
90
|
+
}
|
|
91
|
+
export const aggregateFunctions = {
|
|
92
|
+
countAll: () => aggregate("COUNT"),
|
|
93
|
+
count: (column) => aggregate("COUNT", column),
|
|
94
|
+
sum: (column) => aggregate("SUM", column),
|
|
95
|
+
avg: (column) => aggregate("AVG", column),
|
|
96
|
+
min: (column) => aggregate("MIN", column),
|
|
97
|
+
max: (column) => aggregate("MAX", column)
|
|
98
|
+
};
|
package/dist/utils.js
CHANGED
|
@@ -2,6 +2,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
2
2
|
import { createQueryBuilder, setConfig } from "@stacksjs/query-builder";
|
|
3
3
|
import { env as envVars } from "@stacksjs/env";
|
|
4
4
|
import { getConnectionDefaults } from "./defaults";
|
|
5
|
+
import { aggregateFunctions } from "./types";
|
|
5
6
|
const sqliteDefaults = getConnectionDefaults("sqlite", envVars), mysqlDefaults = getConnectionDefaults("mysql", envVars), postgresDefaults = getConnectionDefaults("postgres", envVars);
|
|
6
7
|
let appEnv = envVars.APP_ENV || "local", dbDriver = envVars.DB_CONNECTION || "sqlite", dbConfig = {
|
|
7
8
|
connections: {
|
|
@@ -153,6 +154,8 @@ function getDb() {
|
|
|
153
154
|
ensureConfigLoaded();
|
|
154
155
|
export const db = new Proxy({}, {
|
|
155
156
|
get(_target, prop) {
|
|
157
|
+
if (prop === "fn")
|
|
158
|
+
return aggregateFunctions;
|
|
156
159
|
const instance = getDb(), value = instance[prop];
|
|
157
160
|
if (typeof value === "function")
|
|
158
161
|
return value.bind(instance);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/database",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.114",
|
|
6
6
|
"description": "The Stacks database integration.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -55,18 +55,20 @@
|
|
|
55
55
|
"prepublishOnly": "bun run build"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"
|
|
58
|
+
"@stacksjs/ts-validation": "^0.5.0",
|
|
59
|
+
"bun-query-builder": "catalog:",
|
|
60
|
+
"dynamodb-tooling": "^0.3.2"
|
|
59
61
|
},
|
|
60
62
|
"devDependencies": {
|
|
61
|
-
"@stacksjs/cli": "0.70.
|
|
62
|
-
"@stacksjs/config": "0.70.
|
|
63
|
-
"@stacksjs/logging": "0.70.
|
|
64
|
-
"@stacksjs/router": "0.70.
|
|
65
|
-
"better-dx": "
|
|
66
|
-
"@stacksjs/path": "0.70.
|
|
67
|
-
"@stacksjs/query-builder": "0.70.
|
|
68
|
-
"@stacksjs/storage": "0.70.
|
|
69
|
-
"@stacksjs/strings": "0.70.
|
|
70
|
-
"@stacksjs/utils": "0.70.
|
|
63
|
+
"@stacksjs/cli": "0.70.114",
|
|
64
|
+
"@stacksjs/config": "0.70.114",
|
|
65
|
+
"@stacksjs/logging": "0.70.114",
|
|
66
|
+
"@stacksjs/router": "0.70.114",
|
|
67
|
+
"better-dx": "catalog:",
|
|
68
|
+
"@stacksjs/path": "0.70.114",
|
|
69
|
+
"@stacksjs/query-builder": "0.70.114",
|
|
70
|
+
"@stacksjs/storage": "0.70.114",
|
|
71
|
+
"@stacksjs/strings": "0.70.114",
|
|
72
|
+
"@stacksjs/utils": "0.70.114"
|
|
71
73
|
}
|
|
72
74
|
}
|