@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.
@@ -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(alter[1]) && !alter[2] && !alter[3];
621
- push(isCreateTimeConstraint ? "create-foreign-key-constraints" : `alter-${alter[1]}-${alter[2] || alter[3] || "constraint"}`, stmt);
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
- /** @defaultValue `{ createTable: () => Promise<unknown> }` */
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.112",
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
- "bun-query-builder": "^0.1.56"
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.112",
62
- "@stacksjs/config": "0.70.112",
63
- "@stacksjs/logging": "0.70.112",
64
- "@stacksjs/router": "0.70.112",
65
- "better-dx": "^0.2.16",
66
- "@stacksjs/path": "0.70.112",
67
- "@stacksjs/query-builder": "0.70.112",
68
- "@stacksjs/storage": "0.70.112",
69
- "@stacksjs/strings": "0.70.112",
70
- "@stacksjs/utils": "0.70.112"
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
  }