@vertz/db 0.2.0 → 0.2.3

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.
@@ -0,0 +1,100 @@
1
+ import {
2
+ __require
3
+ } from "./chunk-j4kwq1gh.js";
4
+
5
+ // src/client/postgres-driver.ts
6
+ function loadPostgres() {
7
+ try {
8
+ const mod = __require("postgres");
9
+ return typeof mod === "function" ? mod : mod.default;
10
+ } catch {
11
+ throw new Error('The "postgres" package is required for PostgreSQL. Install: bun add postgres');
12
+ }
13
+ }
14
+ function isPostgresError(error) {
15
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && "message" in error && typeof error.message === "string";
16
+ }
17
+ function adaptPostgresError(error) {
18
+ if (isPostgresError(error)) {
19
+ const adapted = Object.assign(new Error(error.message), {
20
+ code: error.code,
21
+ message: error.message,
22
+ table: error.table_name,
23
+ column: error.column_name,
24
+ constraint: error.constraint_name,
25
+ detail: error.detail
26
+ });
27
+ throw adapted;
28
+ }
29
+ throw error;
30
+ }
31
+ function createPostgresDriver(url, pool) {
32
+ const sql = loadPostgres()(url, {
33
+ max: pool?.max ?? 10,
34
+ idle_timeout: pool?.idleTimeout !== undefined ? pool.idleTimeout / 1000 : 30,
35
+ connect_timeout: pool?.connectionTimeout !== undefined ? pool.connectionTimeout / 1000 : 10,
36
+ fetch_types: false
37
+ });
38
+ const queryFn = async (sqlStr, params) => {
39
+ try {
40
+ const result = await sql.unsafe(sqlStr, params);
41
+ const rows = result.map((row) => {
42
+ const mapped = {};
43
+ for (const [key, value] of Object.entries(row)) {
44
+ mapped[key] = coerceValue(value);
45
+ }
46
+ return mapped;
47
+ });
48
+ return {
49
+ rows,
50
+ rowCount: result.count ?? rows.length
51
+ };
52
+ } catch (error) {
53
+ adaptPostgresError(error);
54
+ }
55
+ };
56
+ return {
57
+ queryFn,
58
+ query: async (sql2, params) => {
59
+ const result = await queryFn(sql2, params ?? []);
60
+ return result.rows;
61
+ },
62
+ execute: async (sql2, params) => {
63
+ const result = await queryFn(sql2, params ?? []);
64
+ return { rowsAffected: result.rowCount };
65
+ },
66
+ async close() {
67
+ await sql.end();
68
+ },
69
+ async isHealthy() {
70
+ let timer;
71
+ try {
72
+ const healthCheckTimeout = pool?.healthCheckTimeout ?? 5000;
73
+ const timeout = new Promise((_, reject) => {
74
+ timer = setTimeout(() => reject(new Error("Health check timed out")), healthCheckTimeout);
75
+ });
76
+ await Promise.race([sql`SELECT 1`, timeout]);
77
+ return true;
78
+ } catch {
79
+ return false;
80
+ } finally {
81
+ if (timer !== undefined)
82
+ clearTimeout(timer);
83
+ }
84
+ }
85
+ };
86
+ }
87
+ function coerceValue(value) {
88
+ if (typeof value === "string" && isTimestampString(value)) {
89
+ const date = new Date(value);
90
+ if (!Number.isNaN(date.getTime())) {
91
+ return date;
92
+ }
93
+ }
94
+ return value;
95
+ }
96
+ function isTimestampString(value) {
97
+ return /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/.test(value);
98
+ }
99
+
100
+ export { createPostgresDriver };
@@ -0,0 +1,306 @@
1
+ // src/id/generators.ts
2
+ import { createId } from "@paralleldrive/cuid2";
3
+ import { v7 as uuidv7 } from "uuid";
4
+ import { nanoid } from "nanoid";
5
+ function generateId(strategy) {
6
+ switch (strategy) {
7
+ case "cuid":
8
+ return createId();
9
+ case "uuid":
10
+ return uuidv7();
11
+ case "nanoid":
12
+ return nanoid();
13
+ default:
14
+ throw new Error(`Unknown ID generation strategy: ${strategy}`);
15
+ }
16
+ }
17
+
18
+ // src/adapters/sql-utils.ts
19
+ function getSqlType(meta) {
20
+ switch (meta.sqlType) {
21
+ case "serial":
22
+ return "INTEGER";
23
+ case "varchar":
24
+ return meta.length ? `VARCHAR(${meta.length})` : "TEXT";
25
+ case "text":
26
+ return "TEXT";
27
+ case "integer":
28
+ return "INTEGER";
29
+ case "bigint":
30
+ return "BIGINT";
31
+ case "decimal":
32
+ return meta.precision && meta.scale ? `DECIMAL(${meta.precision},${meta.scale})` : "REAL";
33
+ case "boolean":
34
+ return "INTEGER";
35
+ case "timestamp":
36
+ case "timestamptz":
37
+ return "TEXT";
38
+ case "date":
39
+ return "TEXT";
40
+ case "json":
41
+ case "jsonb":
42
+ return "TEXT";
43
+ case "uuid":
44
+ return "TEXT";
45
+ case "enum":
46
+ return "TEXT";
47
+ default:
48
+ return "TEXT";
49
+ }
50
+ }
51
+ function generateCreateTableSql(schema) {
52
+ const columns = [];
53
+ const tableName = schema._name;
54
+ for (const [colName, colBuilder] of Object.entries(schema._columns)) {
55
+ const meta = colBuilder._meta;
56
+ let colDef = `${colName} ${getSqlType(meta)}`;
57
+ if (meta.primary) {
58
+ colDef += " PRIMARY KEY";
59
+ if (meta.generate === "uuid") {
60
+ colDef += " DEFAULT (uuid())";
61
+ } else if (meta.generate === "cuid") {
62
+ colDef += " DEFAULT (cuid())";
63
+ }
64
+ }
65
+ if (meta.unique && !meta.primary) {
66
+ colDef += " UNIQUE";
67
+ }
68
+ if (!meta.nullable && !meta.primary) {
69
+ colDef += " NOT NULL";
70
+ }
71
+ if (meta.hasDefault && meta.defaultValue !== undefined) {
72
+ if (meta.defaultValue === "now") {
73
+ colDef += " DEFAULT (datetime('now'))";
74
+ } else if (typeof meta.defaultValue === "string") {
75
+ colDef += ` DEFAULT '${meta.defaultValue.replace(/'/g, "''")}'`;
76
+ } else if (typeof meta.defaultValue === "number") {
77
+ colDef += ` DEFAULT ${meta.defaultValue}`;
78
+ } else if (typeof meta.defaultValue === "boolean") {
79
+ colDef += ` DEFAULT ${meta.defaultValue ? 1 : 0}`;
80
+ }
81
+ }
82
+ if (meta.check) {
83
+ const DANGEROUS_PATTERN = /;|--|\b(DROP|DELETE|INSERT|UPDATE|ALTER|CREATE|EXEC)\b/i;
84
+ if (DANGEROUS_PATTERN.test(meta.check)) {
85
+ throw new Error(`Unsafe CHECK constraint expression: "${meta.check}"`);
86
+ }
87
+ colDef += ` CHECK (${meta.check})`;
88
+ }
89
+ columns.push(colDef);
90
+ }
91
+ return `CREATE TABLE IF NOT EXISTS ${tableName} (
92
+ ${columns.join(`,
93
+ `)}
94
+ )`;
95
+ }
96
+ function generateIndexSql(schema) {
97
+ const sqls = [];
98
+ const tableName = schema._name;
99
+ for (const index of schema._indexes) {
100
+ const indexName = index.name || `idx_${tableName}_${index.columns.join("_")}`;
101
+ const unique = index.unique ? "UNIQUE " : "";
102
+ sqls.push(`CREATE ${unique}INDEX IF NOT EXISTS ${indexName} ON ${tableName} (${index.columns.join(", ")})`);
103
+ }
104
+ for (const [colName, colBuilder] of Object.entries(schema._columns)) {
105
+ const meta = colBuilder._meta;
106
+ if (meta.primary || meta.unique)
107
+ continue;
108
+ if (meta.sqlType === "boolean") {
109
+ sqls.push(`CREATE INDEX IF NOT EXISTS idx_${tableName}_${colName} ON ${tableName}(${colName})`);
110
+ }
111
+ }
112
+ return sqls;
113
+ }
114
+ function convertValueForSql(value, sqlType) {
115
+ if (sqlType === "boolean") {
116
+ return value ? 1 : 0;
117
+ }
118
+ return value;
119
+ }
120
+ function buildWhereClause(where, columns) {
121
+ const clauses = [];
122
+ const params = [];
123
+ for (const [key, value] of Object.entries(where)) {
124
+ clauses.push(`${key} = ?`);
125
+ const colMeta = columns[key]?._meta;
126
+ const convertedValue = convertValueForSql(value, colMeta?.sqlType);
127
+ params.push(convertedValue);
128
+ }
129
+ return { clauses, params };
130
+ }
131
+
132
+ class BaseSqlAdapter {
133
+ driver;
134
+ schema;
135
+ tableName;
136
+ constructor(driver, schema) {
137
+ this.driver = driver;
138
+ this.schema = schema;
139
+ this.tableName = schema._name;
140
+ }
141
+ getAllowedWhereColumns() {
142
+ const columns = new Set;
143
+ for (const colName of Object.keys(this.schema._columns)) {
144
+ columns.add(colName);
145
+ }
146
+ return columns;
147
+ }
148
+ convertRow(row) {
149
+ const converted = { ...row };
150
+ for (const [colName, colBuilder] of Object.entries(this.schema._columns)) {
151
+ const meta = colBuilder._meta;
152
+ if (meta.sqlType === "boolean" && row[colName] !== undefined) {
153
+ converted[colName] = Boolean(row[colName]);
154
+ }
155
+ }
156
+ return converted;
157
+ }
158
+ convertValueForColumn(value, colName) {
159
+ const colBuilder = this.schema._columns[colName];
160
+ if (!colBuilder)
161
+ return value;
162
+ const meta = colBuilder._meta;
163
+ return convertValueForSql(value, meta?.sqlType);
164
+ }
165
+ async get(id) {
166
+ try {
167
+ const rows = await this.driver.query(`SELECT * FROM ${this.tableName} WHERE id = ?`, [id]);
168
+ if (!rows[0])
169
+ return null;
170
+ return this.convertRow(rows[0]);
171
+ } catch {
172
+ throw new Error(`Failed to retrieve record: resource may be unavailable`);
173
+ }
174
+ }
175
+ async list(options) {
176
+ const allowedColumns = this.getAllowedWhereColumns();
177
+ try {
178
+ if (options?.where) {
179
+ for (const key of Object.keys(options.where)) {
180
+ if (!allowedColumns.has(key)) {
181
+ throw new Error(`Invalid filter column: ${key}`);
182
+ }
183
+ }
184
+ }
185
+ let countSql = `SELECT COUNT(*) as count FROM ${this.tableName}`;
186
+ const countParams = [];
187
+ if (options?.where && Object.keys(options.where).length > 0) {
188
+ const { clauses, params: params2 } = buildWhereClause(options.where, this.schema._columns);
189
+ countSql += ` WHERE ${clauses.join(" AND ")}`;
190
+ countParams.push(...params2);
191
+ }
192
+ const countResult = await this.driver.query(countSql, countParams);
193
+ const total = Number(countResult[0]?.count ?? 0);
194
+ let sql = `SELECT * FROM ${this.tableName}`;
195
+ const params = [];
196
+ if (options?.where && Object.keys(options.where).length > 0) {
197
+ const { clauses, params: whereParams } = buildWhereClause(options.where, this.schema._columns);
198
+ sql += ` WHERE ${clauses.join(" AND ")}`;
199
+ params.push(...whereParams);
200
+ }
201
+ if (options?.after) {
202
+ sql += params.length > 0 ? " AND" : " WHERE";
203
+ sql += " id > ?";
204
+ params.push(options.after);
205
+ }
206
+ sql += " ORDER BY id ASC";
207
+ const limit = options?.limit ?? 20;
208
+ sql += " LIMIT ?";
209
+ params.push(limit);
210
+ const data = await this.driver.query(sql, params);
211
+ const convertedData = data.map((row) => this.convertRow(row));
212
+ return { data: convertedData, total };
213
+ } catch (error) {
214
+ if (error instanceof Error && error.message.startsWith("Invalid filter column:")) {
215
+ throw error;
216
+ }
217
+ throw new Error(`Failed to list records: please try again later`);
218
+ }
219
+ }
220
+ async create(data) {
221
+ try {
222
+ const columns = [];
223
+ const placeholders = [];
224
+ const params = [];
225
+ for (const [colName, colBuilder] of Object.entries(this.schema._columns)) {
226
+ const meta = colBuilder._meta;
227
+ if (meta.isReadOnly && !meta.isAutoUpdate)
228
+ continue;
229
+ if (meta.primary && !data[colName] && meta.generate) {
230
+ data[colName] = generateId(meta.generate);
231
+ }
232
+ if (meta.isAutoUpdate) {
233
+ data[colName] = new Date().toISOString();
234
+ }
235
+ if (data[colName] !== undefined || meta.hasDefault) {
236
+ columns.push(colName);
237
+ placeholders.push("?");
238
+ let value = data[colName];
239
+ if (value === undefined && meta.hasDefault) {
240
+ if (meta.defaultValue === "now") {
241
+ value = new Date().toISOString();
242
+ } else {
243
+ value = meta.defaultValue;
244
+ }
245
+ }
246
+ value = this.convertValueForColumn(value, colName);
247
+ params.push(value);
248
+ }
249
+ }
250
+ await this.driver.execute(`INSERT INTO ${this.tableName} (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`, params);
251
+ const id = data.id;
252
+ return this.get(id);
253
+ } catch {
254
+ throw new Error(`Failed to create record: please check your input`);
255
+ }
256
+ }
257
+ async update(id, data) {
258
+ try {
259
+ const updates = [];
260
+ const params = [];
261
+ for (const [colName, colBuilder] of Object.entries(this.schema._columns)) {
262
+ const meta = colBuilder._meta;
263
+ if (meta.isReadOnly && !meta.isAutoUpdate || meta.primary)
264
+ continue;
265
+ if (meta.isAutoUpdate) {
266
+ updates.push(`${colName} = ?`);
267
+ params.push(new Date().toISOString());
268
+ }
269
+ if (data[colName] !== undefined) {
270
+ updates.push(`${colName} = ?`);
271
+ const value = this.convertValueForColumn(data[colName], colName);
272
+ params.push(value);
273
+ }
274
+ }
275
+ if (updates.length === 0) {
276
+ return this.get(id);
277
+ }
278
+ params.push(id);
279
+ await this.driver.execute(`UPDATE ${this.tableName} SET ${updates.join(", ")} WHERE id = ?`, params);
280
+ const result = await this.get(id);
281
+ if (!result) {
282
+ throw new Error("Record not found");
283
+ }
284
+ return result;
285
+ } catch (error) {
286
+ if (error instanceof Error && error.message === "Record not found") {
287
+ throw error;
288
+ }
289
+ throw new Error(`Failed to update record: please try again later`);
290
+ }
291
+ }
292
+ async delete(id) {
293
+ try {
294
+ const existing = await this.get(id);
295
+ if (!existing) {
296
+ return null;
297
+ }
298
+ await this.driver.execute(`DELETE FROM ${this.tableName} WHERE id = ?`, [id]);
299
+ return existing;
300
+ } catch {
301
+ throw new Error(`Failed to delete record: please try again later`);
302
+ }
303
+ }
304
+ }
305
+
306
+ export { generateId, generateCreateTableSql, generateIndexSql, BaseSqlAdapter };
@@ -0,0 +1,9 @@
1
+ // src/util/hash.ts
2
+ async function sha256Hex(input) {
3
+ const data = new TextEncoder().encode(input);
4
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
5
+ const hashArray = new Uint8Array(hashBuffer);
6
+ return Array.from(hashArray).map((b) => b.toString(16).padStart(2, "0")).join("");
7
+ }
8
+
9
+ export { sha256Hex };
@@ -1,12 +1,22 @@
1
1
  // src/sql/casing.ts
2
- function camelToSnake(str) {
2
+ function camelToSnake(str, overrides) {
3
3
  if (str.length === 0)
4
4
  return str;
5
+ if (overrides && str in overrides) {
6
+ return overrides[str];
7
+ }
5
8
  return str.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase();
6
9
  }
7
- function snakeToCamel(str) {
10
+ function snakeToCamel(str, overrides) {
8
11
  if (str.length === 0)
9
12
  return str;
13
+ if (overrides) {
14
+ for (const [camelKey, snakeVal] of Object.entries(overrides)) {
15
+ if (snakeVal === str) {
16
+ return camelKey;
17
+ }
18
+ }
19
+ }
10
20
  return str.replace(/([a-zA-Z\d])_([a-zA-Z])/g, (_, prev, char) => prev + char.toUpperCase());
11
21
  }
12
22
 
@@ -5,33 +5,69 @@
5
5
  * to PostgreSQL column names (snake_case) and vice versa.
6
6
  */
7
7
  /**
8
+ * Override map for custom casing conversions.
9
+ * Keys are camelCase, values are snake_case.
10
+ * Example: { 'oAuth': 'oauth', 'userID': 'user_id' }
11
+ */
12
+ type CasingOverrides = Record<string, string>;
13
+ /**
8
14
  * Convert a camelCase string to snake_case.
9
15
  *
10
16
  * Handles acronyms correctly:
11
17
  * - "parseJSON" -> "parse_json"
12
18
  * - "getHTTPSUrl" -> "get_https_url"
13
19
  * - "htmlParser" -> "html_parser"
20
+ *
21
+ * @param str - The camelCase string to convert
22
+ * @param overrides - Optional map of camelCase -> snake_case overrides that take precedence
14
23
  */
15
- declare function camelToSnake(str: string): string;
24
+ declare function camelToSnake(str: string, overrides?: CasingOverrides): string;
16
25
  /**
17
26
  * Convert a snake_case string to camelCase.
18
27
  *
19
28
  * - "first_name" -> "firstName"
20
29
  * - "created_at_timestamp" -> "createdAtTimestamp"
21
- */
22
- declare function snakeToCamel(str: string): string;
23
- /**
24
- * DELETE statement builder.
25
30
  *
26
- * Generates parameterized DELETE queries with support for:
27
- * - WHERE clause via the where builder
28
- * - RETURNING clause with column aliasing
29
- * - camelCase -> snake_case column conversion
30
- */
31
+ * @param str - The snake_case string to convert
32
+ * @param overrides - Optional map of camelCase -> snake_case overrides (reverse lookup)
33
+ */
34
+ declare function snakeToCamel(str: string, overrides?: CasingOverrides): string;
35
+ interface Dialect {
36
+ /** Dialect name. */
37
+ readonly name: "postgres" | "sqlite";
38
+ /**
39
+ * Parameter placeholder: $1, $2 (postgres) or ? (sqlite).
40
+ * @param index - 1-based parameter index
41
+ */
42
+ param(index: number): string;
43
+ /** SQL function for current timestamp. */
44
+ now(): string;
45
+ /**
46
+ * Map a vertz column sqlType to the dialect's SQL type.
47
+ * @param sqlType - The generic sqlType from column metadata
48
+ * @param meta - Additional metadata (enum values, length, precision)
49
+ */
50
+ mapColumnType(sqlType: string, meta?: ColumnTypeMeta): string;
51
+ /** Whether the dialect supports RETURNING clause. */
52
+ readonly supportsReturning: boolean;
53
+ /** Whether the dialect supports array operators (@>, <@, &&). */
54
+ readonly supportsArrayOps: boolean;
55
+ /** Whether the dialect supports JSONB path operators (->>, ->). */
56
+ readonly supportsJsonbPath: boolean;
57
+ }
58
+ interface ColumnTypeMeta {
59
+ readonly enumName?: string;
60
+ readonly enumValues?: readonly string[];
61
+ readonly length?: number;
62
+ readonly precision?: number;
63
+ readonly scale?: number;
64
+ }
31
65
  interface DeleteOptions {
32
66
  readonly table: string;
33
67
  readonly where?: Record<string, unknown>;
34
68
  readonly returning?: "*" | readonly string[];
69
+ /** SQL dialect to use. Defaults to postgres. */
70
+ readonly dialect?: Dialect;
35
71
  }
36
72
  interface DeleteResult {
37
73
  readonly sql: string;
@@ -40,17 +76,7 @@ interface DeleteResult {
40
76
  /**
41
77
  * Build a DELETE statement from the given options.
42
78
  */
43
- declare function buildDelete(options: DeleteOptions): DeleteResult;
44
- /**
45
- * INSERT statement builder.
46
- *
47
- * Generates parameterized INSERT queries with support for:
48
- * - Single row and batch (multi-row VALUES) inserts
49
- * - RETURNING clause with column aliasing
50
- * - ON CONFLICT (upsert) — DO NOTHING or DO UPDATE SET
51
- * - camelCase -> snake_case column conversion
52
- * - "now" sentinel handling for timestamp defaults
53
- */
79
+ declare function buildDelete(options: DeleteOptions, dialect?: Dialect): DeleteResult;
54
80
  interface OnConflictOptions {
55
81
  readonly columns: readonly string[];
56
82
  readonly action: "nothing" | "update";
@@ -65,6 +91,8 @@ interface InsertOptions {
65
91
  readonly onConflict?: OnConflictOptions;
66
92
  /** Column names (camelCase) that should use NOW() instead of a parameterized value when the value is "now". */
67
93
  readonly nowColumns?: readonly string[];
94
+ /** SQL dialect to use. Defaults to postgres. */
95
+ readonly dialect?: Dialect;
68
96
  }
69
97
  interface InsertResult {
70
98
  readonly sql: string;
@@ -73,18 +101,7 @@ interface InsertResult {
73
101
  /**
74
102
  * Build an INSERT statement from the given options.
75
103
  */
76
- declare function buildInsert(options: InsertOptions): InsertResult;
77
- /**
78
- * SELECT statement builder.
79
- *
80
- * Generates parameterized SELECT queries with support for:
81
- * - Column selection with camelCase -> snake_case conversion and aliasing
82
- * - WHERE clause via the where builder
83
- * - ORDER BY with direction
84
- * - LIMIT / OFFSET pagination (parameterized)
85
- * - Cursor-based pagination (cursor + take)
86
- * - COUNT(*) OVER() for listAndCount
87
- */
104
+ declare function buildInsert(options: InsertOptions, dialect?: Dialect): InsertResult;
88
105
  interface SelectOptions {
89
106
  readonly table: string;
90
107
  readonly columns?: readonly string[];
@@ -97,6 +114,10 @@ interface SelectOptions {
97
114
  readonly cursor?: Record<string, unknown>;
98
115
  /** Number of rows to take (used with cursor). Aliases `limit` when cursor is present. */
99
116
  readonly take?: number;
117
+ /** Custom casing overrides for camelCase -> snake_case conversion. */
118
+ readonly casingOverrides?: CasingOverrides;
119
+ /** SQL dialect to use. Defaults to postgres. */
120
+ readonly dialect?: Dialect;
100
121
  }
101
122
  interface SelectResult {
102
123
  readonly sql: string;
@@ -105,7 +126,7 @@ interface SelectResult {
105
126
  /**
106
127
  * Build a SELECT statement from the given options.
107
128
  */
108
- declare function buildSelect(options: SelectOptions): SelectResult;
129
+ declare function buildSelect(options: SelectOptions, dialect?: Dialect): SelectResult;
109
130
  /**
110
131
  * SQL tagged template literal and escape hatch.
111
132
  *
@@ -151,16 +172,6 @@ declare const sql: {
151
172
  (strings: TemplateStringsArray, ...values: unknown[]): SqlFragment;
152
173
  raw(value: string): SqlFragment;
153
174
  };
154
- /**
155
- * UPDATE statement builder.
156
- *
157
- * Generates parameterized UPDATE queries with support for:
158
- * - SET clause from a data object
159
- * - WHERE clause via the where builder
160
- * - RETURNING clause with column aliasing
161
- * - camelCase -> snake_case column conversion
162
- * - "now" sentinel handling for timestamp defaults
163
- */
164
175
  interface UpdateOptions {
165
176
  readonly table: string;
166
177
  readonly data: Record<string, unknown>;
@@ -168,6 +179,8 @@ interface UpdateOptions {
168
179
  readonly returning?: "*" | readonly string[];
169
180
  /** Column names (camelCase) that should use NOW() instead of a parameterized value when the value is "now". */
170
181
  readonly nowColumns?: readonly string[];
182
+ /** SQL dialect to use. Defaults to postgres. */
183
+ readonly dialect?: Dialect;
171
184
  }
172
185
  interface UpdateResult {
173
186
  readonly sql: string;
@@ -176,22 +189,7 @@ interface UpdateResult {
176
189
  /**
177
190
  * Build an UPDATE statement from the given options.
178
191
  */
179
- declare function buildUpdate(options: UpdateOptions): UpdateResult;
180
- /**
181
- * WHERE clause builder with parameterized queries.
182
- *
183
- * Supports all filter operators from the schema layer:
184
- * - Comparison: eq, ne, gt, gte, lt, lte
185
- * - String: contains, startsWith, endsWith
186
- * - Set: in, notIn
187
- * - Null: isNull (true/false)
188
- * - Logical: AND, OR, NOT
189
- * - PostgreSQL array: arrayContains (@>), arrayContainedBy (<@), arrayOverlaps (&&)
190
- * - JSONB path: metadata->key syntax
191
- *
192
- * All values are parameterized ($1, $2, ...) to prevent SQL injection.
193
- * Column names are converted from camelCase to snake_case.
194
- */
192
+ declare function buildUpdate(options: UpdateOptions, dialect?: Dialect): UpdateResult;
195
193
  interface WhereResult {
196
194
  readonly sql: string;
197
195
  readonly params: readonly unknown[];
@@ -207,7 +205,9 @@ interface WhereFilter {
207
205
  *
208
206
  * @param filter - The filter object with column conditions
209
207
  * @param paramOffset - Starting parameter offset (0-based, params start at $offset+1)
208
+ * @param overrides - Optional casing overrides for camelCase -> snake_case conversion
209
+ * @param dialect - SQL dialect for parameter placeholders and feature checks
210
210
  * @returns WhereResult with the SQL string (without WHERE keyword) and parameter values
211
211
  */
212
- declare function buildWhere(filter: WhereFilter | undefined, paramOffset?: number): WhereResult;
212
+ declare function buildWhere(filter: WhereFilter | undefined, paramOffset?: number, overrides?: CasingOverrides, dialect?: Dialect): WhereResult;
213
213
  export { sql, snakeToCamel, camelToSnake, buildWhere, buildUpdate, buildSelect, buildInsert, buildDelete, WhereResult, UpdateResult, UpdateOptions, SqlFragment, SelectResult, SelectOptions, OnConflictOptions, InsertResult, InsertOptions, DeleteResult, DeleteOptions };
package/dist/sql/index.js CHANGED
@@ -4,11 +4,11 @@ import {
4
4
  buildSelect,
5
5
  buildUpdate,
6
6
  buildWhere
7
- } from "../shared/chunk-3f2grpak.js";
7
+ } from "../shared/chunk-0e1vy9qd.js";
8
8
  import {
9
9
  camelToSnake,
10
10
  snakeToCamel
11
- } from "../shared/chunk-hrfdj0rr.js";
11
+ } from "../shared/chunk-v2qm94qp.js";
12
12
  // src/sql/tagged.ts
13
13
  function isSqlFragment(value) {
14
14
  return typeof value === "object" && value !== null && "_tag" in value && value._tag === "SqlFragment";