@vertz/db 0.2.0 → 0.2.1

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.
@@ -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";
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Database driver interface.
3
+ *
4
+ * Provides a unified interface for different database backends
5
+ * (PostgreSQL, SQLite/D1, etc.) with query and execute methods.
6
+ */
7
+ interface DbDriver {
8
+ /**
9
+ * Execute a read query and return results.
10
+ */
11
+ query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
12
+ /**
13
+ * Execute a write query and return affected row count.
14
+ */
15
+ execute(sql: string, params?: unknown[]): Promise<{
16
+ rowsAffected: number;
17
+ }>;
18
+ /**
19
+ * Close the database connection.
20
+ */
21
+ close(): Promise<void>;
22
+ }
23
+ interface JsonbValidator<T> {
24
+ parse(value: unknown): T;
25
+ }
26
+ interface ColumnMetadata {
27
+ readonly sqlType: string;
28
+ readonly primary: boolean;
29
+ readonly unique: boolean;
30
+ readonly nullable: boolean;
31
+ readonly hasDefault: boolean;
32
+ readonly _annotations: Record<string, true>;
33
+ readonly isReadOnly: boolean;
34
+ readonly isAutoUpdate: boolean;
35
+ readonly isTenant: boolean;
36
+ readonly references: {
37
+ readonly table: string;
38
+ readonly column: string;
39
+ } | null;
40
+ readonly check: string | null;
41
+ readonly defaultValue?: unknown;
42
+ readonly format?: string;
43
+ readonly length?: number;
44
+ readonly precision?: number;
45
+ readonly scale?: number;
46
+ readonly enumName?: string;
47
+ readonly enumValues?: readonly string[];
48
+ readonly validator?: JsonbValidator<unknown>;
49
+ readonly generate?: "cuid" | "uuid" | "nanoid";
50
+ }
51
+ /** Phantom symbol to carry the TypeScript type without a runtime value. */
52
+ declare const PhantomType: unique symbol;
53
+ interface ColumnBuilder<
54
+ TType,
55
+ TMeta extends ColumnMetadata = ColumnMetadata
56
+ > {
57
+ /** Phantom field -- only exists at the type level for inference. Do not access at runtime. */
58
+ readonly [PhantomType]: TType;
59
+ readonly _meta: TMeta;
60
+ primary(options?: {
61
+ generate?: "cuid" | "uuid" | "nanoid";
62
+ }): ColumnBuilder<TType, Omit<TMeta, "primary" | "hasDefault" | "generate"> & {
63
+ readonly primary: true;
64
+ readonly hasDefault: true;
65
+ readonly generate?: "cuid" | "uuid" | "nanoid";
66
+ }>;
67
+ unique(): ColumnBuilder<TType, Omit<TMeta, "unique"> & {
68
+ readonly unique: true;
69
+ }>;
70
+ nullable(): ColumnBuilder<TType | null, Omit<TMeta, "nullable"> & {
71
+ readonly nullable: true;
72
+ }>;
73
+ default(value: TType | "now"): ColumnBuilder<TType, Omit<TMeta, "hasDefault"> & {
74
+ readonly hasDefault: true;
75
+ readonly defaultValue: TType | "now";
76
+ }>;
77
+ is<TFlag extends string>(flag: TFlag): ColumnBuilder<TType, Omit<TMeta, "_annotations"> & {
78
+ readonly _annotations: TMeta["_annotations"] & { readonly [K in TFlag] : true };
79
+ }>;
80
+ readOnly(): ColumnBuilder<TType, Omit<TMeta, "isReadOnly"> & {
81
+ readonly isReadOnly: true;
82
+ }>;
83
+ autoUpdate(): ColumnBuilder<TType, Omit<TMeta, "isAutoUpdate" | "isReadOnly"> & {
84
+ readonly isAutoUpdate: true;
85
+ readonly isReadOnly: true;
86
+ }>;
87
+ check(sql: string): ColumnBuilder<TType, Omit<TMeta, "check"> & {
88
+ readonly check: string;
89
+ }>;
90
+ references(table: string, column?: string): ColumnBuilder<TType, Omit<TMeta, "references"> & {
91
+ readonly references: {
92
+ readonly table: string;
93
+ readonly column: string;
94
+ };
95
+ }>;
96
+ }
97
+ type InferColumnType<C> = C extends ColumnBuilder<infer T, ColumnMetadata> ? T : never;
98
+ interface IndexDef {
99
+ readonly columns: readonly string[];
100
+ readonly name?: string;
101
+ readonly unique?: boolean;
102
+ }
103
+ /** A record of column builders -- the shape passed to d.table(). */
104
+ type ColumnRecord = Record<string, ColumnBuilder<unknown, ColumnMetadata>>;
105
+ /** Extract the TypeScript type from every column in a record. */
106
+ type InferColumns<T extends ColumnRecord> = { [K in keyof T] : InferColumnType<T[K]> };
107
+ /** Keys of columns where a given metadata property is `true`. */
108
+ type ColumnKeysWhere<
109
+ T extends ColumnRecord,
110
+ Flag extends keyof ColumnMetadata
111
+ > = { [K in keyof T] : T[K] extends ColumnBuilder<unknown, infer M> ? M extends Record<Flag, true> ? K : never : never }[keyof T];
112
+ /** Keys of columns where a given metadata property is NOT `true` (i.e., false). */
113
+ type ColumnKeysWhereNot<
114
+ T extends ColumnRecord,
115
+ Flag extends keyof ColumnMetadata
116
+ > = { [K in keyof T] : T[K] extends ColumnBuilder<unknown, infer M> ? M extends Record<Flag, true> ? never : K : never }[keyof T];
117
+ /** Keys of columns that do NOT have ANY of the specified annotations in `_annotations`. */
118
+ type ColumnKeysWithoutAnyAnnotation<
119
+ T extends ColumnRecord,
120
+ Annotations extends string
121
+ > = { [K in keyof T] : T[K] extends ColumnBuilder<unknown, infer M> ? M["_annotations"] extends Record<Annotations, true> ? never : K : never }[keyof T];
122
+ /**
123
+ * $infer -- default SELECT type.
124
+ * Excludes columns annotated 'hidden'. Includes everything else.
125
+ */
126
+ type Infer<T extends ColumnRecord> = { [K in ColumnKeysWithoutAnyAnnotation<T, "hidden">] : InferColumnType<T[K]> };
127
+ /**
128
+ * $infer_all -- all columns including hidden.
129
+ */
130
+ type InferAll<T extends ColumnRecord> = InferColumns<T>;
131
+ /**
132
+ * $insert -- write type. ALL columns included (visibility is read-side only).
133
+ * Columns with hasDefault: true become optional.
134
+ */
135
+ type Insert<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "hasDefault">] : InferColumnType<T[K]> } & { [K in ColumnKeysWhere<T, "hasDefault">]? : InferColumnType<T[K]> };
136
+ /**
137
+ * $update -- write type. ALL non-primary-key columns, all optional.
138
+ * Primary key excluded (you don't update a PK).
139
+ */
140
+ type Update<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "primary">]? : InferColumnType<T[K]> };
141
+ /**
142
+ * $response -- API response shape. Excludes columns annotated 'hidden'.
143
+ */
144
+ type Response<T extends ColumnRecord> = { [K in ColumnKeysWithoutAnyAnnotation<T, "hidden">] : InferColumnType<T[K]> };
145
+ /**
146
+ * $create_input -- API create input shape.
147
+ * Excludes readOnly and primary key columns.
148
+ * Columns with defaults are optional.
149
+ */
150
+ type ApiCreateInput<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "isReadOnly"> & ColumnKeysWhereNot<T, "primary"> & ColumnKeysWhereNot<T, "hasDefault"> & string] : InferColumnType<T[K]> } & { [K in ColumnKeysWhereNot<T, "isReadOnly"> & ColumnKeysWhereNot<T, "primary"> & ColumnKeysWhere<T, "hasDefault"> & string]? : InferColumnType<T[K]> };
151
+ /**
152
+ * $update_input -- API update input shape.
153
+ * Excludes readOnly and primary key columns. All fields optional (partial update).
154
+ */
155
+ type ApiUpdateInput<T extends ColumnRecord> = { [K in ColumnKeysWhereNot<T, "isReadOnly"> & ColumnKeysWhereNot<T, "primary"> & string]? : InferColumnType<T[K]> };
156
+ interface TableDef<TColumns extends ColumnRecord = ColumnRecord> {
157
+ readonly _name: string;
158
+ readonly _columns: TColumns;
159
+ readonly _indexes: readonly IndexDef[];
160
+ readonly _shared: boolean;
161
+ /** Default SELECT type -- excludes columns annotated 'hidden'. */
162
+ readonly $infer: Infer<TColumns>;
163
+ /** All columns including hidden. */
164
+ readonly $infer_all: InferAll<TColumns>;
165
+ /** Insert type -- defaulted columns optional. ALL columns included. */
166
+ readonly $insert: Insert<TColumns>;
167
+ /** Update type -- all non-PK columns optional. ALL columns included. */
168
+ readonly $update: Update<TColumns>;
169
+ /** API response shape — excludes columns annotated 'hidden'. */
170
+ readonly $response: Response<TColumns>;
171
+ /** API create input — excludes readOnly + PK; defaulted columns optional. */
172
+ readonly $create_input: ApiCreateInput<TColumns>;
173
+ /** API update input — excludes readOnly + PK; all fields optional. */
174
+ readonly $update_input: ApiUpdateInput<TColumns>;
175
+ /** Mark this table as shared / cross-tenant. */
176
+ shared(): TableDef<TColumns>;
177
+ }
178
+ /**
179
+ * Database Adapter Types for @vertz/db
180
+ *
181
+ * Generic adapter interface that abstracts database operations.
182
+ * Implemented by SQLite, D1, and other database adapters.
183
+ */
184
+ interface ListOptions {
185
+ where?: Record<string, unknown>;
186
+ orderBy?: Record<string, "asc" | "desc">;
187
+ limit?: number;
188
+ /** Cursor-based pagination: fetch records after this ID. */
189
+ after?: string;
190
+ }
191
+ interface EntityDbAdapter {
192
+ get(id: string): Promise<Record<string, unknown> | null>;
193
+ list(options?: ListOptions): Promise<{
194
+ data: Record<string, unknown>[];
195
+ total: number;
196
+ }>;
197
+ create(data: Record<string, unknown>): Promise<Record<string, unknown>>;
198
+ update(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
199
+ delete(id: string): Promise<Record<string, unknown> | null>;
200
+ }
201
+ interface SqliteAdapterOptions<T extends ColumnRecord> {
202
+ /** The table schema definition */
203
+ schema: TableDef<T>;
204
+ /** Path to the SQLite database file */
205
+ dbPath?: string;
206
+ /** Directory to store the database file (alternative to dbPath) */
207
+ dataDir?: string;
208
+ /** Auto-apply migrations on startup */
209
+ migrations?: {
210
+ autoApply?: boolean;
211
+ };
212
+ }
213
+ /**
214
+ * Create a SQLite driver using bun:sqlite or better-sqlite3.
215
+ */
216
+ declare function createSqliteDriver(dbPath: string): DbDriver;
217
+ /**
218
+ * Create a SQLite EntityDbAdapter from a schema.
219
+ */
220
+ declare function createSqliteAdapter<T extends ColumnRecord>(options: SqliteAdapterOptions<T>): Promise<EntityDbAdapter>;
221
+ export { createSqliteDriver, createSqliteAdapter, SqliteAdapterOptions };