@vertz/db 0.2.15 → 0.2.17

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.
@@ -1,4 +1,17 @@
1
1
  /**
2
+ * Query executor — wraps raw SQL execution with error mapping.
3
+ *
4
+ * Takes a query function (from the database driver) and wraps it to:
5
+ * 1. Execute parameterized SQL
6
+ * 2. Map PG errors to typed DbError subclasses
7
+ * 3. Return typed QueryResult
8
+ */
9
+ interface ExecutorResult<T> {
10
+ readonly rows: readonly T[];
11
+ readonly rowCount: number;
12
+ }
13
+ type QueryFn = <T>(sql: string, params: readonly unknown[]) => Promise<ExecutorResult<T>>;
14
+ /**
2
15
  * Database driver interface.
3
16
  *
4
17
  * Provides a unified interface for different database backends
@@ -16,6 +29,12 @@ interface DbDriver {
16
29
  rowsAffected: number;
17
30
  }>;
18
31
  /**
32
+ * Execute a callback within a database transaction.
33
+ * The callback receives a transaction-scoped QueryFn.
34
+ * Optional — not all drivers support transactions (e.g., D1).
35
+ */
36
+ beginTransaction?<T>(fn: (txQueryFn: QueryFn) => Promise<T>): Promise<T>;
37
+ /**
19
38
  * Close the database connection.
20
39
  */
21
40
  close(): Promise<void>;
@@ -85,6 +104,20 @@ interface ColumnBuilder<
85
104
  }>;
86
105
  }
87
106
  type InferColumnType<C> = C extends ColumnBuilder<infer T, ColumnMetadata> ? T : never;
107
+ interface ThroughDef<TJoin extends TableDef<ColumnRecord> = TableDef<ColumnRecord>> {
108
+ readonly table: () => TJoin;
109
+ readonly thisKey: string;
110
+ readonly thatKey: string;
111
+ }
112
+ interface RelationDef<
113
+ TTarget extends TableDef<ColumnRecord> = TableDef<ColumnRecord>,
114
+ TType extends "one" | "many" = "one" | "many"
115
+ > {
116
+ readonly _type: TType;
117
+ readonly _target: () => TTarget;
118
+ readonly _foreignKey: string | null;
119
+ readonly _through: ThroughDef | null;
120
+ }
88
121
  type IndexType = "btree" | "hash" | "gin" | "gist" | "brin";
89
122
  interface IndexDef {
90
123
  readonly columns: readonly string[];
@@ -173,28 +206,129 @@ interface TableDef<TColumns extends ColumnRecord = ColumnRecord> {
173
206
  /** Mark this table as shared / cross-tenant. */
174
207
  shared(): TableDef<TColumns>;
175
208
  }
209
+ /** Operators available for comparable types (number, string, Date, bigint). */
210
+ interface ComparisonOperators<T> {
211
+ readonly eq?: T;
212
+ readonly ne?: T;
213
+ readonly gt?: T;
214
+ readonly gte?: T;
215
+ readonly lt?: T;
216
+ readonly lte?: T;
217
+ readonly in?: readonly T[];
218
+ readonly notIn?: readonly T[];
219
+ }
220
+ /** Additional operators for string columns. */
221
+ interface StringOperators {
222
+ readonly contains?: string;
223
+ readonly startsWith?: string;
224
+ readonly endsWith?: string;
225
+ }
226
+ /** The `isNull` operator — only available for nullable columns. */
227
+ interface NullOperator {
228
+ readonly isNull?: boolean;
229
+ }
176
230
  /**
177
- * Database Adapter Types for @vertz/db
231
+ * Resolves the filter operators for a single column based on its inferred type
232
+ * and nullable metadata.
178
233
  *
179
- * Generic adapter interface that abstracts database operations.
180
- * Implemented by SQLite, D1, and other database adapters.
234
+ * - All types get comparison + in/notIn
235
+ * - String types additionally get contains, startsWith, endsWith
236
+ * - Nullable columns additionally get isNull
237
+ *
238
+ * Uses [T] extends [string] to prevent union distribution -- ensures that a
239
+ * union like 'admin' | 'editor' keeps the full union in each operator slot.
181
240
  */
182
- interface ListOptions {
241
+ type ColumnFilterOperators<
242
+ TType,
243
+ TNullable extends boolean
244
+ > = ([TType] extends [string] ? ComparisonOperators<TType> & StringOperators : ComparisonOperators<TType>) & (TNullable extends true ? NullOperator : unknown);
245
+ /** Determine whether a column is nullable from its metadata. */
246
+ type IsNullable<C> = C extends ColumnBuilder<unknown, infer M> ? M extends {
247
+ readonly nullable: true;
248
+ } ? true : false : false;
249
+ /**
250
+ * FilterType<TColumns> — typed where clause.
251
+ *
252
+ * Each key maps to either:
253
+ * - A direct value (shorthand for `{ eq: value }`)
254
+ * - An object with typed filter operators
255
+ */
256
+ type FilterType<TColumns extends ColumnRecord> = { [K in keyof TColumns]? : InferColumnType<TColumns[K]> | ColumnFilterOperators<InferColumnType<TColumns[K]>, IsNullable<TColumns[K]>> };
257
+ type OrderByType<TColumns extends ColumnRecord> = { [K in keyof TColumns]? : "asc" | "desc" };
258
+ /** Relations record — maps relation names to RelationDef. */
259
+ type RelationsRecord = Record<string, RelationDef>;
260
+ /**
261
+ * The shape of include options for a given relations record.
262
+ * Each relation can be:
263
+ * - `true` — include with default fields
264
+ * - An object with `select`, `where`, `orderBy`, `limit` constrained to target table columns
265
+ */
266
+ type IncludeOption<TRelations extends RelationsRecord> = { [K in keyof TRelations]? : true | (RelationTarget<TRelations[K]> extends TableDef<infer TCols> ? {
267
+ select?: { [C in keyof TCols]? : true };
268
+ where?: FilterType<TCols>;
269
+ orderBy?: OrderByType<TCols>;
270
+ limit?: number;
271
+ /** Nested includes — untyped until full model registry is threaded through. */
272
+ include?: Record<string, unknown>;
273
+ } : never) };
274
+ /** Extract the target table from a RelationDef. */
275
+ type RelationTarget<R> = R extends RelationDef<infer TTarget, "one" | "many"> ? TTarget : never;
276
+ /** A model entry in the database registry, pairing a table with its relations. */
277
+ interface ModelEntry<
278
+ TTable extends TableDef<ColumnRecord> = TableDef<ColumnRecord>,
279
+ TRelations extends RelationsRecord = RelationsRecord
280
+ > {
281
+ readonly table: TTable;
282
+ readonly relations: TRelations;
283
+ }
284
+ /** A single include entry with optional query constraints. */
285
+ interface AdapterIncludeEntry {
286
+ select?: Record<string, true>;
183
287
  where?: Record<string, unknown>;
184
288
  orderBy?: Record<string, "asc" | "desc">;
185
289
  limit?: number;
290
+ include?: Record<string, unknown>;
291
+ }
292
+ /** Include specification: maps relation names to `true` or structured entries. */
293
+ type AdapterIncludeSpec = Record<string, true | AdapterIncludeEntry>;
294
+ /**
295
+ * Resolves the where clause type for a given entry.
296
+ * When TEntry is the default (unparameterized), falls back to Record<string, unknown>.
297
+ */
298
+ type ResolveWhere<TEntry extends ModelEntry> = TEntry extends ModelEntry<infer TTable> ? TTable extends TableDef<infer TCols> ? [ColumnRecord] extends [TCols] ? Record<string, unknown> : FilterType<TCols> : Record<string, unknown> : Record<string, unknown>;
299
+ /**
300
+ * Resolves the orderBy type for a given entry.
301
+ * When TEntry is the default (unparameterized), falls back to Record<string, 'asc' | 'desc'>.
302
+ */
303
+ type ResolveOrderBy<TEntry extends ModelEntry> = TEntry extends ModelEntry<infer TTable> ? TTable extends TableDef<infer TCols> ? [ColumnRecord] extends [TCols] ? Record<string, "asc" | "desc"> : OrderByType<TCols> : Record<string, "asc" | "desc"> : Record<string, "asc" | "desc">;
304
+ /**
305
+ * Resolves the include type for a given entry.
306
+ * When TEntry is the default (unparameterized), falls back to AdapterIncludeSpec.
307
+ */
308
+ type ResolveInclude<TEntry extends ModelEntry> = TEntry extends ModelEntry<TableDef<ColumnRecord>, infer TRels> ? [Record<string, never>] extends [TRels] ? AdapterIncludeSpec : IncludeOption<TRels> : AdapterIncludeSpec;
309
+ interface ListOptions<TEntry extends ModelEntry = ModelEntry> {
310
+ where?: ResolveWhere<TEntry>;
311
+ orderBy?: ResolveOrderBy<TEntry>;
312
+ limit?: number;
186
313
  /** Cursor-based pagination: fetch records after this ID. */
187
314
  after?: string;
315
+ /** Relation include specification for relation loading. */
316
+ include?: ResolveInclude<TEntry>;
317
+ }
318
+ /** Options for get-by-id operations. */
319
+ interface GetOptions<TEntry extends ModelEntry = ModelEntry> {
320
+ /** Relation include specification for relation loading. */
321
+ include?: ResolveInclude<TEntry>;
188
322
  }
189
- interface EntityDbAdapter {
190
- get(id: string): Promise<Record<string, unknown> | null>;
191
- list(options?: ListOptions): Promise<{
192
- data: Record<string, unknown>[];
323
+ interface EntityDbAdapter<TEntry extends ModelEntry = ModelEntry> {
324
+ get(id: string, options?: GetOptions<TEntry>): Promise<TEntry["table"]["$response"] | null>;
325
+ list(options?: ListOptions<TEntry>): Promise<{
326
+ data: TEntry["table"]["$response"][];
193
327
  total: number;
194
328
  }>;
195
- create(data: Record<string, unknown>): Promise<Record<string, unknown>>;
196
- update(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
197
- delete(id: string): Promise<Record<string, unknown> | null>;
329
+ create(data: TEntry["table"]["$create_input"]): Promise<TEntry["table"]["$response"]>;
330
+ update(id: string, data: TEntry["table"]["$update_input"]): Promise<TEntry["table"]["$response"]>;
331
+ delete(id: string): Promise<TEntry["table"]["$response"] | null>;
198
332
  }
199
333
  interface D1DatabaseBinding {
200
334
  prepare(sql: string): D1PreparedStatement;
package/dist/d1/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createD1Adapter,
3
3
  createD1Driver
4
- } from "../shared/chunk-ndxe1h28.js";
4
+ } from "../shared/chunk-tnaf4hbj.js";
5
5
  export {
6
6
  createD1Driver,
7
7
  createD1Adapter