@murumets-ee/db 0.1.5 → 0.2.0
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/index.d.mts +725 -7
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +8 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
-
import { PgTableWithColumns } from "drizzle-orm/pg-core";
|
|
2
|
+
import { AnyPgColumn, PgColumnBuilderBase, PgTable, PgTableWithColumns } from "drizzle-orm/pg-core";
|
|
3
3
|
|
|
4
4
|
//#region src/client.d.ts
|
|
5
5
|
interface DbConfig {
|
|
@@ -19,9 +19,35 @@ declare function createDbClient(config: DbConfig): PostgresJsDatabase;
|
|
|
19
19
|
declare function createReadOnlyClient(config: DbConfig): PostgresJsDatabase;
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/migrate.d.ts
|
|
22
|
+
/**
|
|
23
|
+
* Arguments passed to migration `up` / `down` functions.
|
|
24
|
+
*
|
|
25
|
+
* Intentionally tiny: we hand over a read-write Drizzle handle bound to
|
|
26
|
+
* the current transaction. If a migration needs anything beyond raw SQL
|
|
27
|
+
* (e.g. calling an AdminClient for a data backfill), it can import the
|
|
28
|
+
* relevant toolkit helpers inside the migration file — the CLI loader
|
|
29
|
+
* runs the migration in the same process as the config, so all package
|
|
30
|
+
* imports resolve normally.
|
|
31
|
+
*/
|
|
32
|
+
interface MigrateArgs {
|
|
33
|
+
db: PostgresJsDatabase;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Shape of a migration module loaded from a `.ts` file.
|
|
37
|
+
*
|
|
38
|
+
* At minimum a migration must export `up`. `down` is optional — when
|
|
39
|
+
* absent, `lumi migrate:rollback` refuses to roll that file back.
|
|
40
|
+
*/
|
|
41
|
+
interface MigrationModule {
|
|
42
|
+
up: (args: MigrateArgs) => Promise<void>;
|
|
43
|
+
down?: (args: MigrateArgs) => Promise<void>;
|
|
44
|
+
}
|
|
22
45
|
interface MigrationSource {
|
|
46
|
+
/** Absolute path to the migration file on disk. */
|
|
23
47
|
path: string;
|
|
48
|
+
/** `project` for user migrations, `toolkit` for shipped-with-toolkit ones. */
|
|
24
49
|
namespace: 'toolkit' | 'project';
|
|
50
|
+
/** Filename only, used for ordering + tracking. */
|
|
25
51
|
name: string;
|
|
26
52
|
}
|
|
27
53
|
interface MigrationStatus {
|
|
@@ -29,17 +55,48 @@ interface MigrationStatus {
|
|
|
29
55
|
pending: MigrationSource[];
|
|
30
56
|
}
|
|
31
57
|
/**
|
|
32
|
-
*
|
|
58
|
+
* Loader callback — imports a migration `.ts` file and returns its exports.
|
|
59
|
+
*
|
|
60
|
+
* The CLI owns `jiti` (already used to load `toolkit.config.ts`), so
|
|
61
|
+
* `@murumets-ee/db` stays free of TS-runtime loader dependencies.
|
|
62
|
+
* Callers who never apply migrations (read-only servers, tests) never
|
|
63
|
+
* pay the loader cost.
|
|
33
64
|
*/
|
|
34
|
-
|
|
65
|
+
type MigrationLoader = (absolutePath: string) => Promise<MigrationModule>;
|
|
35
66
|
/**
|
|
36
|
-
*
|
|
67
|
+
* Discover all migration files under `projectRoot/migrations`.
|
|
68
|
+
*
|
|
69
|
+
* Layout:
|
|
70
|
+
* migrations/
|
|
71
|
+
* 20260411_180000_add_articles.ts ← user migrations (sort chronologically)
|
|
72
|
+
* 20260411_180000_add_articles.snapshot.json
|
|
73
|
+
* .toolkit/
|
|
74
|
+
* 0001_initial_schema.ts ← toolkit-owned migrations (sort numerically)
|
|
37
75
|
*/
|
|
76
|
+
declare function discoverMigrations(projectRoot: string): Promise<MigrationSource[]>;
|
|
77
|
+
/** Split discovered migrations into applied vs pending by consulting the tracking table. */
|
|
38
78
|
declare function getMigrationStatus(db: PostgresJsDatabase, projectRoot: string): Promise<MigrationStatus>;
|
|
39
79
|
/**
|
|
40
|
-
*
|
|
80
|
+
* Apply every pending migration in order.
|
|
81
|
+
*
|
|
82
|
+
* Each file runs inside its own transaction: the migration's `up()`
|
|
83
|
+
* executes first, then the `_toolkit_migrations` insert commits. If
|
|
84
|
+
* `up()` throws, the transaction rolls back and the whole command
|
|
85
|
+
* aborts — nothing gets half-applied.
|
|
86
|
+
*
|
|
87
|
+
* The `loader` callback is injected so `@murumets-ee/db` itself doesn't
|
|
88
|
+
* depend on a TS-runtime loader. Callers pass a jiti-backed loader from
|
|
89
|
+
* the CLI or their own equivalent.
|
|
90
|
+
*/
|
|
91
|
+
declare function runMigrations(db: PostgresJsDatabase, projectRoot: string, loader: MigrationLoader): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Roll back the most recent applied migration (or N most recent).
|
|
94
|
+
*
|
|
95
|
+
* Each file's `down()` runs in its own transaction. Files without a
|
|
96
|
+
* `down` export cause the rollback to abort before running anything —
|
|
97
|
+
* we never roll back some but not all of a requested batch.
|
|
41
98
|
*/
|
|
42
|
-
declare function
|
|
99
|
+
declare function rollbackMigrations(db: PostgresJsDatabase, projectRoot: string, loader: MigrationLoader, count?: number): Promise<void>;
|
|
43
100
|
//#endregion
|
|
44
101
|
//#region src/schema-registry.d.ts
|
|
45
102
|
/**
|
|
@@ -74,5 +131,666 @@ declare const schemaRegistry: SchemaRegistryImpl;
|
|
|
74
131
|
*/
|
|
75
132
|
declare function createSchemaRegistry(): SchemaRegistry;
|
|
76
133
|
//#endregion
|
|
77
|
-
|
|
134
|
+
//#region src/table/types.d.ts
|
|
135
|
+
/**
|
|
136
|
+
* The discriminating "kind" tag for a column factory.
|
|
137
|
+
*
|
|
138
|
+
* Used at the type level to gate features that only make sense for certain
|
|
139
|
+
* kinds — e.g. only `'jsonb'` columns accept dotted-path keys in the
|
|
140
|
+
* where-builder.
|
|
141
|
+
*/
|
|
142
|
+
type ColumnKind = 'uuid' | 'varchar' | 'text' | 'integer' | 'bigint' | 'double' | 'boolean' | 'timestamp' | 'jsonb' | 'uuidArray';
|
|
143
|
+
/**
|
|
144
|
+
* Internal column factory.
|
|
145
|
+
*
|
|
146
|
+
* A `ColumnFactory` is a callable that, given a column name, produces the
|
|
147
|
+
* corresponding Drizzle column builder. The phantom type parameters
|
|
148
|
+
* (`__type`, `__kind`, `__notNull`) are used purely for compile-time
|
|
149
|
+
* inference of {@link Row} and {@link InsertRow} types — they have no runtime
|
|
150
|
+
* representation beyond a small metadata object.
|
|
151
|
+
*
|
|
152
|
+
* Users do not construct `ColumnFactory` values directly; they use the
|
|
153
|
+
* `column.*` builders from `./columns.ts`.
|
|
154
|
+
*/
|
|
155
|
+
interface ColumnFactory<TType = unknown, TKind extends ColumnKind = ColumnKind, TNotNull extends boolean = boolean, THasDefault extends boolean = boolean> {
|
|
156
|
+
/** Build the underlying Drizzle column for the given column name. */
|
|
157
|
+
(name: string): PgColumnBuilderBase;
|
|
158
|
+
readonly __type: TType;
|
|
159
|
+
readonly __kind: TKind;
|
|
160
|
+
readonly __notNull: TNotNull;
|
|
161
|
+
readonly __hasDefault: THasDefault;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Convenience helper: extract the JS value type for a single column.
|
|
165
|
+
*
|
|
166
|
+
* - `notNull: true` → `T`
|
|
167
|
+
* - `notNull: false` → `T | null`
|
|
168
|
+
*/
|
|
169
|
+
type ColumnValue<C> = C extends ColumnFactory<infer T, ColumnKind, infer N> ? N extends true ? T : T | null : never;
|
|
170
|
+
/**
|
|
171
|
+
* Map a columns record to its row shape (the result of a SELECT).
|
|
172
|
+
*/
|
|
173
|
+
type Row<TCols extends Record<string, ColumnFactory>> = { [K in keyof TCols]: ColumnValue<TCols[K]> };
|
|
174
|
+
/**
|
|
175
|
+
* Map a columns record to its insert shape.
|
|
176
|
+
*
|
|
177
|
+
* - Required columns: must be supplied unless they have a default
|
|
178
|
+
* - Nullable columns: optional, accept `null`
|
|
179
|
+
* - Columns with defaults (`defaultRandom`, `defaultNow`, explicit `default`): optional
|
|
180
|
+
*/
|
|
181
|
+
type InsertRow<TCols extends Record<string, ColumnFactory>> = { [K in keyof TCols as TCols[K] extends ColumnFactory<unknown, ColumnKind, true, false> ? K : never]: ColumnValue<TCols[K]> } & { [K in keyof TCols as TCols[K] extends ColumnFactory<unknown, ColumnKind, true, false> ? never : K]?: ColumnValue<TCols[K]> };
|
|
182
|
+
/**
|
|
183
|
+
* Operator object for a single column.
|
|
184
|
+
*
|
|
185
|
+
* Each property is a comparison operator. Combine with `$and` / `$or` /
|
|
186
|
+
* `$not` at the parent level to build composite predicates.
|
|
187
|
+
*
|
|
188
|
+
* `ilike` and `startsWith` accept user-supplied strings — the table client
|
|
189
|
+
* automatically escapes `%` and `_` in the value before wrapping with
|
|
190
|
+
* pattern wildcards, so callers cannot inject pattern characters by accident.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* { status: 'open' } // shorthand for { eq: 'open' }
|
|
195
|
+
* { status: { in: ['open', 'pending'] } }
|
|
196
|
+
* { createdAt: { gte: new Date('2026-01-01') } }
|
|
197
|
+
* { name: { ilike: 'foo%' } } // % escaped from value, only outer wildcards work
|
|
198
|
+
* { description: { isNull: true } }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
type ColumnOperators<T> = T | null | {
|
|
202
|
+
eq: T;
|
|
203
|
+
} | {
|
|
204
|
+
ne: T;
|
|
205
|
+
} | {
|
|
206
|
+
in: T[];
|
|
207
|
+
} | {
|
|
208
|
+
notIn: T[];
|
|
209
|
+
} | {
|
|
210
|
+
gt: T;
|
|
211
|
+
} | {
|
|
212
|
+
gte: T;
|
|
213
|
+
} | {
|
|
214
|
+
lt: T;
|
|
215
|
+
} | {
|
|
216
|
+
lte: T;
|
|
217
|
+
} | {
|
|
218
|
+
isNull: true;
|
|
219
|
+
} | {
|
|
220
|
+
isNotNull: true;
|
|
221
|
+
} | {
|
|
222
|
+
ilike: string;
|
|
223
|
+
} | {
|
|
224
|
+
startsWith: string;
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* The full WhereClause type.
|
|
228
|
+
*
|
|
229
|
+
* Keys are either column names or dotted-path keys for jsonb columns
|
|
230
|
+
* (e.g. `'metadata.author.name'`). Values are operator objects per
|
|
231
|
+
* {@link ColumnOperators}.
|
|
232
|
+
*
|
|
233
|
+
* Combine with `$and` / `$or` / `$not` for composite predicates. All
|
|
234
|
+
* top-level keys are implicitly AND-ed together.
|
|
235
|
+
*
|
|
236
|
+
* The type intentionally accepts `string` for jsonb-path keys rather than
|
|
237
|
+
* a precisely-narrowed template literal, because the path's nested type is
|
|
238
|
+
* unknown to the column definition. Path segments are validated at runtime
|
|
239
|
+
* against `/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\d+$/` and the parent column must
|
|
240
|
+
* be a `jsonb` kind, otherwise the where-builder throws.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* // Simple equality
|
|
245
|
+
* { userId: 'u_123', status: 'active' }
|
|
246
|
+
*
|
|
247
|
+
* // Composite with $or
|
|
248
|
+
* { $or: [
|
|
249
|
+
* { status: 'open' },
|
|
250
|
+
* { status: 'pending', assignee: { isNull: true } },
|
|
251
|
+
* ] }
|
|
252
|
+
*
|
|
253
|
+
* // jsonb path access (column 'metadata' must be jsonb)
|
|
254
|
+
* { 'metadata.author.name': { ilike: 'alice%' } }
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
type WhereClause<TCols extends Record<string, ColumnFactory>> = { [K in keyof TCols]?: ColumnOperators<ColumnValue<TCols[K]>> } & {
|
|
258
|
+
[key: `${string}.${string}`]: ColumnOperators<unknown>;
|
|
259
|
+
} & {
|
|
260
|
+
$and?: WhereClause<TCols>[];
|
|
261
|
+
$or?: WhereClause<TCols>[];
|
|
262
|
+
$not?: WhereClause<TCols>;
|
|
263
|
+
};
|
|
264
|
+
/**
|
|
265
|
+
* Index definition for a table.
|
|
266
|
+
*
|
|
267
|
+
* Indexes are declared at the table level (not per-column) so composite
|
|
268
|
+
* indexes are first-class. Each index gets an auto-generated name unless
|
|
269
|
+
* `name` is supplied: `idx_<table>_<col1>_<col2>`.
|
|
270
|
+
*/
|
|
271
|
+
interface IndexDefinition<TCols extends Record<string, ColumnFactory>> {
|
|
272
|
+
on: (keyof TCols)[];
|
|
273
|
+
name?: string;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Unique constraint definition.
|
|
277
|
+
*
|
|
278
|
+
* Composite uniques (multi-column) are declared here. Single-column
|
|
279
|
+
* uniqueness is also supported here rather than as a column-level option,
|
|
280
|
+
* to keep the column builder API uniform.
|
|
281
|
+
*/
|
|
282
|
+
interface UniqueDefinition<TCols extends Record<string, ColumnFactory>> {
|
|
283
|
+
on: (keyof TCols)[];
|
|
284
|
+
name?: string;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* The shape passed to `defineTable`.
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```ts
|
|
291
|
+
* defineTable({
|
|
292
|
+
* name: 'ticket_read_state',
|
|
293
|
+
* columns: {
|
|
294
|
+
* ticketId: column.uuid({ notNull: true }),
|
|
295
|
+
* userId: column.varchar({ length: 255, notNull: true }),
|
|
296
|
+
* lastReadAt: column.timestamp({ notNull: true, defaultNow: true, withTimezone: true }),
|
|
297
|
+
* },
|
|
298
|
+
* primaryKey: ['ticketId', 'userId'],
|
|
299
|
+
* indexes: [{ on: ['userId'] }],
|
|
300
|
+
* })
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
interface TableDefinition<TCols extends Record<string, ColumnFactory>> {
|
|
304
|
+
/**
|
|
305
|
+
* Postgres table name. Convention: snake_case, prefix with package name
|
|
306
|
+
* (e.g. `toolkit_jobs`, `ticketing_read_state`).
|
|
307
|
+
*
|
|
308
|
+
* Validated at definition time against `/^[a-z][a-z0-9_]*$/` to prevent
|
|
309
|
+
* accidental quoting issues and to enforce a consistent naming scheme.
|
|
310
|
+
*/
|
|
311
|
+
name: string;
|
|
312
|
+
columns: TCols;
|
|
313
|
+
/**
|
|
314
|
+
* Primary key column(s). May be a single column key or an array for
|
|
315
|
+
* composite keys.
|
|
316
|
+
*
|
|
317
|
+
* If omitted, the table has no primary key — only valid for tables that
|
|
318
|
+
* declare a column with `primaryKey: true` at the column level (e.g. via
|
|
319
|
+
* `column.uuid({ primaryKey: true, defaultRandom: true })`).
|
|
320
|
+
*/
|
|
321
|
+
primaryKey?: keyof TCols | (keyof TCols)[];
|
|
322
|
+
unique?: UniqueDefinition<TCols>[];
|
|
323
|
+
indexes?: IndexDefinition<TCols>[];
|
|
324
|
+
}
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/table/columns.d.ts
|
|
327
|
+
/**
|
|
328
|
+
* Column builder namespace.
|
|
329
|
+
*
|
|
330
|
+
* Each builder produces a {@link ColumnFactory}. Pass the result to a
|
|
331
|
+
* `defineTable` `columns` field. The builders are typed so that the
|
|
332
|
+
* resulting `Row` and `InsertRow` types track each column's value type
|
|
333
|
+
* and nullability.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```ts
|
|
337
|
+
* import { column, defineTable } from '@murumets-ee/db'
|
|
338
|
+
*
|
|
339
|
+
* const jobs = defineTable({
|
|
340
|
+
* name: 'toolkit_jobs',
|
|
341
|
+
* columns: {
|
|
342
|
+
* id: column.uuid({ primaryKey: true, defaultRandom: true }),
|
|
343
|
+
* type: column.varchar({ length: 100, notNull: true }),
|
|
344
|
+
* payload: column.jsonb<{ subject: string; body: string }>({ notNull: true }),
|
|
345
|
+
* status: column.varchar({ length: 20, notNull: true, default: 'pending' }),
|
|
346
|
+
* priority: column.integer({ notNull: true, default: 0 }),
|
|
347
|
+
* runAt: column.timestamp({ notNull: true, defaultNow: true, withTimezone: true }),
|
|
348
|
+
* },
|
|
349
|
+
* })
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
declare const column: {
|
|
353
|
+
/**
|
|
354
|
+
* UUID column.
|
|
355
|
+
*
|
|
356
|
+
* For primary keys, use `{ primaryKey: true, defaultRandom: true }` to
|
|
357
|
+
* get a server-generated UUIDv4.
|
|
358
|
+
*/
|
|
359
|
+
uuid<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {
|
|
360
|
+
primaryKey?: boolean;
|
|
361
|
+
notNull?: TNotNull; /** Server-side `gen_random_uuid()` default. Counts as a default for type inference. */
|
|
362
|
+
defaultRandom?: THasDefault;
|
|
363
|
+
}): ColumnFactory<string, "uuid", TNotNull extends true ? true : false, THasDefault extends true ? true : boolean extends THasDefault ? false : false>;
|
|
364
|
+
/**
|
|
365
|
+
* Variable-length string column. `length` is required and must be a
|
|
366
|
+
* positive integer up to Postgres' actual `varchar` ceiling
|
|
367
|
+
* (1073741823 — i.e. ~1GB / 4 bytes per char). For unbounded text use
|
|
368
|
+
* `column.text()` instead — it stores the same way and skips the
|
|
369
|
+
* length check at write time.
|
|
370
|
+
*
|
|
371
|
+
* Conventional sizes for reference:
|
|
372
|
+
* - 50–100: short identifiers, slugs, status codes
|
|
373
|
+
* - 255: legacy "common max length" — fits a tweet, an email, etc.
|
|
374
|
+
* - 1024: file paths, URLs
|
|
375
|
+
* - 10000+: prefer `text` instead, varchar with very large lengths
|
|
376
|
+
* buys you nothing
|
|
377
|
+
*/
|
|
378
|
+
varchar<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts: {
|
|
379
|
+
length: number;
|
|
380
|
+
notNull?: TNotNull;
|
|
381
|
+
default?: THasDefault extends true ? string : string | undefined;
|
|
382
|
+
}): ColumnFactory<string, "varchar", TNotNull extends true ? true : false, THasDefault extends true ? true : false>;
|
|
383
|
+
/**
|
|
384
|
+
* Unbounded text column.
|
|
385
|
+
*/
|
|
386
|
+
text<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {
|
|
387
|
+
notNull?: TNotNull;
|
|
388
|
+
default?: THasDefault extends true ? string : string | undefined;
|
|
389
|
+
}): ColumnFactory<string, "text", TNotNull extends true ? true : false, THasDefault extends true ? true : false>;
|
|
390
|
+
/**
|
|
391
|
+
* 32-bit integer column.
|
|
392
|
+
*/
|
|
393
|
+
integer<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {
|
|
394
|
+
notNull?: TNotNull;
|
|
395
|
+
default?: THasDefault extends true ? number : number | undefined;
|
|
396
|
+
}): ColumnFactory<number, "integer", TNotNull extends true ? true : false, THasDefault extends true ? true : false>;
|
|
397
|
+
/**
|
|
398
|
+
* 64-bit integer column. `mode: 'number'` returns a JS number (safe for
|
|
399
|
+
* values up to 2^53), `mode: 'bigint'` returns a JS BigInt.
|
|
400
|
+
*/
|
|
401
|
+
bigint<TMode extends "number" | "bigint" = "number", TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {
|
|
402
|
+
mode?: TMode;
|
|
403
|
+
notNull?: TNotNull;
|
|
404
|
+
default?: THasDefault extends true ? (TMode extends "bigint" ? bigint : number) : (TMode extends "bigint" ? bigint : number) | undefined;
|
|
405
|
+
}): ColumnFactory<TMode extends "bigint" ? bigint : number, "bigint", TNotNull extends true ? true : false, THasDefault extends true ? true : false>;
|
|
406
|
+
/**
|
|
407
|
+
* Double-precision floating-point column.
|
|
408
|
+
*/
|
|
409
|
+
double<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {
|
|
410
|
+
notNull?: TNotNull;
|
|
411
|
+
default?: THasDefault extends true ? number : number | undefined;
|
|
412
|
+
}): ColumnFactory<number, "double", TNotNull extends true ? true : false, THasDefault extends true ? true : false>;
|
|
413
|
+
/**
|
|
414
|
+
* Boolean column. Defaults to `false` if no explicit default is supplied
|
|
415
|
+
* AND `notNull` is true — matches the entity package convention.
|
|
416
|
+
*/
|
|
417
|
+
boolean<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {
|
|
418
|
+
notNull?: TNotNull;
|
|
419
|
+
default?: THasDefault extends true ? boolean : boolean | undefined;
|
|
420
|
+
}): ColumnFactory<boolean, "boolean", TNotNull extends true ? true : false, THasDefault extends true ? true : false>;
|
|
421
|
+
/**
|
|
422
|
+
* Timestamp column. `withTimezone: true` is strongly recommended for all
|
|
423
|
+
* timestamps in this codebase — entity audit fields use it consistently.
|
|
424
|
+
*/
|
|
425
|
+
timestamp<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {
|
|
426
|
+
notNull?: TNotNull; /** Server-side `NOW()` default. Counts as a default for type inference. */
|
|
427
|
+
defaultNow?: THasDefault;
|
|
428
|
+
withTimezone?: boolean;
|
|
429
|
+
}): ColumnFactory<Date, "timestamp", TNotNull extends true ? true : false, THasDefault extends true ? true : false>;
|
|
430
|
+
/**
|
|
431
|
+
* JSONB column. The phantom type parameter `T` propagates to row reads
|
|
432
|
+
* and where-clause shorthand for the whole-column case. For dotted-path
|
|
433
|
+
* access via the where-builder, the value type at the path is `unknown`
|
|
434
|
+
* (TypeScript can't follow runtime path navigation).
|
|
435
|
+
*/
|
|
436
|
+
jsonb<T = unknown, TNotNull extends boolean = false>(opts?: {
|
|
437
|
+
notNull?: TNotNull;
|
|
438
|
+
}): ColumnFactory<T, "jsonb", TNotNull extends true ? true : false, false>;
|
|
439
|
+
/**
|
|
440
|
+
* UUID array column (`uuid[]`).
|
|
441
|
+
*/
|
|
442
|
+
uuidArray<TNotNull extends boolean = false>(opts?: {
|
|
443
|
+
notNull?: TNotNull;
|
|
444
|
+
}): ColumnFactory<string[], "uuidArray", TNotNull extends true ? true : false, false>;
|
|
445
|
+
};
|
|
446
|
+
//#endregion
|
|
447
|
+
//#region src/table/client.d.ts
|
|
448
|
+
/**
|
|
449
|
+
* Default page size for `findMany` when no `limit` is supplied.
|
|
450
|
+
*
|
|
451
|
+
* Chosen as a safe upper bound for "show me a page of stuff" — anything
|
|
452
|
+
* above this should be paginated explicitly.
|
|
453
|
+
*/
|
|
454
|
+
declare const DEFAULT_LIMIT = 100;
|
|
455
|
+
/**
|
|
456
|
+
* Hard cap on `findMany.limit`. Caller-supplied values above this are
|
|
457
|
+
* clamped and a warning is emitted via `console.warn`.
|
|
458
|
+
*/
|
|
459
|
+
declare const MAX_LIMIT = 1000;
|
|
460
|
+
/**
|
|
461
|
+
* Hard cap on `insertMany.values.length` per single call.
|
|
462
|
+
*/
|
|
463
|
+
declare const MAX_BATCH = 1000;
|
|
464
|
+
/**
|
|
465
|
+
* A custom error thrown by the table client for any caller-side mistake
|
|
466
|
+
* (empty where on update/delete, batch too large, malformed where, …).
|
|
467
|
+
*
|
|
468
|
+
* Database errors are NOT wrapped — they propagate as-is from postgres-js
|
|
469
|
+
* so callers can match on Postgres error codes.
|
|
470
|
+
*/
|
|
471
|
+
declare class TableClientError extends Error {
|
|
472
|
+
constructor(message: string);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Order-by spec accepted by `findMany`.
|
|
476
|
+
*/
|
|
477
|
+
interface OrderBySpec<TCols extends Record<string, ColumnFactory>> {
|
|
478
|
+
column: keyof TCols & string;
|
|
479
|
+
dir?: 'asc' | 'desc';
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* `findMany` options.
|
|
483
|
+
*/
|
|
484
|
+
interface FindManyOptions<TCols extends Record<string, ColumnFactory>> {
|
|
485
|
+
where?: WhereClause<TCols>;
|
|
486
|
+
orderBy?: OrderBySpec<TCols>[];
|
|
487
|
+
limit?: number;
|
|
488
|
+
offset?: number;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Generic CRUD client. Constructed by `defineTable` — never instantiated
|
|
492
|
+
* directly by callers.
|
|
493
|
+
*
|
|
494
|
+
* The same instance shape is exposed both at the top level (using the
|
|
495
|
+
* package's main `db` connection) and inside a `transaction()` callback
|
|
496
|
+
* (using the transactional `tx` handle), so user code is identical
|
|
497
|
+
* regardless of transactional context.
|
|
498
|
+
*
|
|
499
|
+
* @template TCols The columns record passed to `defineTable`.
|
|
500
|
+
*/
|
|
501
|
+
declare class TableClient<TCols extends Record<string, ColumnFactory>> {
|
|
502
|
+
/** The Drizzle table object. Exposed via `defineTable().table` for typed JOINs. */
|
|
503
|
+
readonly table: PgTable;
|
|
504
|
+
/** Per-column kind metadata. Used by the where-builder for jsonb path validation. */
|
|
505
|
+
private readonly columnKinds;
|
|
506
|
+
/**
|
|
507
|
+
* The active database handle. May be a top-level `PostgresJsDatabase`
|
|
508
|
+
* or a transaction handle of compatible shape — the client doesn't
|
|
509
|
+
* distinguish.
|
|
510
|
+
*/
|
|
511
|
+
private readonly db;
|
|
512
|
+
/**
|
|
513
|
+
* @internal — instances are created by `defineTable()`.
|
|
514
|
+
*/
|
|
515
|
+
constructor(/** The Drizzle table object. Exposed via `defineTable().table` for typed JOINs. */
|
|
516
|
+
|
|
517
|
+
table: PgTable, /** Per-column kind metadata. Used by the where-builder for jsonb path validation. */
|
|
518
|
+
|
|
519
|
+
columnKinds: Readonly<Record<string, ColumnKind>>,
|
|
520
|
+
/**
|
|
521
|
+
* The active database handle. May be a top-level `PostgresJsDatabase`
|
|
522
|
+
* or a transaction handle of compatible shape — the client doesn't
|
|
523
|
+
* distinguish.
|
|
524
|
+
*/
|
|
525
|
+
|
|
526
|
+
db: PostgresJsDatabase);
|
|
527
|
+
/**
|
|
528
|
+
* Find a single row matching the given where clause, or `null` if no row
|
|
529
|
+
* matches. Throws if the where is empty.
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* ```ts
|
|
533
|
+
* await readState.client.findOne({ ticketId: 't_1', userId: 'u_1' })
|
|
534
|
+
* ```
|
|
535
|
+
*/
|
|
536
|
+
findOne(where: WhereClause<TCols>): Promise<Row<TCols> | null>;
|
|
537
|
+
/**
|
|
538
|
+
* Find multiple rows. Pagination, ordering, and filtering are all
|
|
539
|
+
* optional but `limit` is hard-capped at {@link MAX_LIMIT}.
|
|
540
|
+
*
|
|
541
|
+
* @example
|
|
542
|
+
* ```ts
|
|
543
|
+
* await jobs.client.findMany({
|
|
544
|
+
* where: { status: 'pending', runAt: { lte: new Date() } },
|
|
545
|
+
* orderBy: [{ column: 'priority', dir: 'desc' }, { column: 'runAt', dir: 'asc' }],
|
|
546
|
+
* limit: 25,
|
|
547
|
+
* })
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
findMany(options?: FindManyOptions<TCols>): Promise<Row<TCols>[]>;
|
|
551
|
+
/**
|
|
552
|
+
* Count rows matching the where clause. Returns an exact count via
|
|
553
|
+
* `SELECT COUNT(*)`. For estimated/cached counts on large tables, see
|
|
554
|
+
* `countEstimate()` and `countCached()` (added in a follow-up PR).
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* ```ts
|
|
558
|
+
* const open = await jobs.client.count({ status: 'pending' })
|
|
559
|
+
* ```
|
|
560
|
+
*/
|
|
561
|
+
count(where?: WhereClause<TCols>): Promise<number>;
|
|
562
|
+
/**
|
|
563
|
+
* Returns `true` if at least one row matches the where clause. Always
|
|
564
|
+
* uses `LIMIT 1` and short-circuits — cheaper than `count() > 0` on
|
|
565
|
+
* large tables.
|
|
566
|
+
*/
|
|
567
|
+
exists(where: WhereClause<TCols>): Promise<boolean>;
|
|
568
|
+
/**
|
|
569
|
+
* Insert a single row. Returns the inserted row (server-generated
|
|
570
|
+
* defaults populated).
|
|
571
|
+
*/
|
|
572
|
+
insert(values: InsertRow<TCols>): Promise<Row<TCols>>;
|
|
573
|
+
/**
|
|
574
|
+
* Upsert: insert a row, or update an existing row if a unique constraint
|
|
575
|
+
* conflict occurs.
|
|
576
|
+
*
|
|
577
|
+
* `target` names the column(s) whose unique constraint should trigger
|
|
578
|
+
* the conflict path. `set` is the patch applied on conflict — when
|
|
579
|
+
* omitted, the conflict path applies the same `values` (i.e. "last
|
|
580
|
+
* write wins"). The PR 1 version does not support `setWhere`
|
|
581
|
+
* (conditional updates on conflict); see PR 3 for the atomic-locking
|
|
582
|
+
* pattern that uses it.
|
|
583
|
+
*
|
|
584
|
+
* @example Idempotent "mark as read" tracking
|
|
585
|
+
* ```ts
|
|
586
|
+
* await readState.client.upsert(
|
|
587
|
+
* { ticketId, userId, lastReadAt: new Date() },
|
|
588
|
+
* { target: ['ticketId', 'userId'] },
|
|
589
|
+
* )
|
|
590
|
+
* ```
|
|
591
|
+
*
|
|
592
|
+
* @example Counter increment
|
|
593
|
+
* ```ts
|
|
594
|
+
* await counters.client.upsert(
|
|
595
|
+
* { key: 'page_views', value: 1 },
|
|
596
|
+
* { target: 'key', set: { value: sql`${counters.table.value} + 1` } as never },
|
|
597
|
+
* )
|
|
598
|
+
* ```
|
|
599
|
+
*/
|
|
600
|
+
upsert(values: InsertRow<TCols>, opts: {
|
|
601
|
+
target: (keyof TCols & string) | (keyof TCols & string)[];
|
|
602
|
+
set?: Partial<InsertRow<TCols>>;
|
|
603
|
+
}): Promise<Row<TCols>>;
|
|
604
|
+
/**
|
|
605
|
+
* Insert many rows in a single statement. Capped at {@link MAX_BATCH}.
|
|
606
|
+
* Returns all inserted rows in input order.
|
|
607
|
+
*/
|
|
608
|
+
insertMany(values: InsertRow<TCols>[]): Promise<Row<TCols>[]>;
|
|
609
|
+
/**
|
|
610
|
+
* Update **at most one** row matching the where clause. Returns the
|
|
611
|
+
* updated row, or `null` if no row matched.
|
|
612
|
+
*
|
|
613
|
+
* Throws `TableClientError` if:
|
|
614
|
+
* - the where is empty
|
|
615
|
+
* - the where matches more than one row
|
|
616
|
+
*
|
|
617
|
+
* The multi-row guard exists to prevent silent bulk updates from a
|
|
618
|
+
* mistakenly-broad where. If you genuinely want to update multiple
|
|
619
|
+
* rows in one call, use {@link updateMany}, which is explicit about
|
|
620
|
+
* its plurality.
|
|
621
|
+
*
|
|
622
|
+
* Implementation note: this issues a single `UPDATE … RETURNING` and
|
|
623
|
+
* inspects the returned row count *after* the fact. The extra rows
|
|
624
|
+
* over the wire are the cost of the safety check; the alternative
|
|
625
|
+
* (a SELECT round-trip first) would be more rows AND a race window.
|
|
626
|
+
* If the affected count is > 1 the update has *already* happened —
|
|
627
|
+
* the throw is to surface the bug, not to prevent it. Wrap in a
|
|
628
|
+
* transaction if you need atomic rollback.
|
|
629
|
+
*/
|
|
630
|
+
update(where: WhereClause<TCols>, patch: Partial<InsertRow<TCols>>): Promise<Row<TCols> | null>;
|
|
631
|
+
/**
|
|
632
|
+
* Update all rows matching the where clause. Returns every updated row.
|
|
633
|
+
* Throws if the where is empty — there is no "update everything" path.
|
|
634
|
+
*/
|
|
635
|
+
updateMany(where: WhereClause<TCols>, patch: Partial<InsertRow<TCols>>): Promise<Row<TCols>[]>;
|
|
636
|
+
/**
|
|
637
|
+
* Delete **at most one** row matching the where clause. Returns the
|
|
638
|
+
* deleted row, or `null` if no row matched.
|
|
639
|
+
*
|
|
640
|
+
* Throws `TableClientError` if:
|
|
641
|
+
* - the where is empty
|
|
642
|
+
* - the where matches more than one row
|
|
643
|
+
*
|
|
644
|
+
* Same multi-row safety guard as {@link update}. Use {@link deleteMany}
|
|
645
|
+
* for explicit bulk deletes.
|
|
646
|
+
*
|
|
647
|
+
* Implementation note: same as `update` — the delete has already
|
|
648
|
+
* happened by the time the throw fires. Wrap in a transaction if you
|
|
649
|
+
* need atomic rollback.
|
|
650
|
+
*/
|
|
651
|
+
delete(where: WhereClause<TCols>): Promise<Row<TCols> | null>;
|
|
652
|
+
/**
|
|
653
|
+
* Delete all rows matching the where clause. Returns every deleted row.
|
|
654
|
+
* Throws if the where is empty — there is no "delete everything" path.
|
|
655
|
+
*/
|
|
656
|
+
deleteMany(where: WhereClause<TCols>): Promise<Row<TCols>[]>;
|
|
657
|
+
/**
|
|
658
|
+
* Run a callback inside a database transaction. The callback receives a
|
|
659
|
+
* transactional `TableClient` instance with the same shape as the
|
|
660
|
+
* top-level client — write code identically inside or outside.
|
|
661
|
+
*
|
|
662
|
+
* Throwing from the callback rolls back; returning a value commits.
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* ```ts
|
|
666
|
+
* await files.client.transaction(async (tx) => {
|
|
667
|
+
* const row = await tx.insert(values)
|
|
668
|
+
* await adapter.upload(row.key, buffer) // if this throws, the row is rolled back
|
|
669
|
+
* return row
|
|
670
|
+
* })
|
|
671
|
+
* ```
|
|
672
|
+
*/
|
|
673
|
+
transaction<T>(fn: (tx: TableClient<TCols>) => Promise<T>): Promise<T>;
|
|
674
|
+
private buildWhereOrThrow;
|
|
675
|
+
private validateLimit;
|
|
676
|
+
}
|
|
677
|
+
//#endregion
|
|
678
|
+
//#region src/table/define.d.ts
|
|
679
|
+
/**
|
|
680
|
+
* The result of `defineTable()`.
|
|
681
|
+
*
|
|
682
|
+
* @template TCols The columns record passed to `defineTable`.
|
|
683
|
+
*/
|
|
684
|
+
interface DefinedTable<TCols extends Record<string, ColumnFactory>> {
|
|
685
|
+
/**
|
|
686
|
+
* The underlying Drizzle `pgTable`. Exposed so callers can use it in
|
|
687
|
+
* typed JOINs (the one sanctioned escape valve), and so it can be
|
|
688
|
+
* referenced from `drizzle.config.ts` for migration generation.
|
|
689
|
+
*/
|
|
690
|
+
table: PgTable;
|
|
691
|
+
/**
|
|
692
|
+
* The original schema metadata. Useful for introspection / tooling
|
|
693
|
+
* that needs to know about indexes, unique constraints, etc.
|
|
694
|
+
*/
|
|
695
|
+
schema: TableDefinition<TCols>;
|
|
696
|
+
/**
|
|
697
|
+
* Per-column kind metadata (column name → kind tag). Used internally
|
|
698
|
+
* by the where-builder for jsonb path validation.
|
|
699
|
+
*/
|
|
700
|
+
columnKinds: Readonly<Record<string, ColumnKind>>;
|
|
701
|
+
/**
|
|
702
|
+
* Build a typed CRUD client wired to the given database handle.
|
|
703
|
+
*
|
|
704
|
+
* Callers typically construct one client per logical "module" and
|
|
705
|
+
* cache it. The client is stateless aside from the db handle, so it
|
|
706
|
+
* is safe to share.
|
|
707
|
+
*/
|
|
708
|
+
makeClient(db: PostgresJsDatabase): TableClient<TCols>;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Define a typed table.
|
|
712
|
+
*
|
|
713
|
+
* The function signature uses two const-generic parameters to preserve
|
|
714
|
+
* column literal types end-to-end. This is what enables `Row`,
|
|
715
|
+
* `InsertRow`, and `WhereClause` to know exact column shapes at the call
|
|
716
|
+
* site.
|
|
717
|
+
*
|
|
718
|
+
* @throws if the table name doesn't match the snake_case regex, or if
|
|
719
|
+
* the same name is registered twice with different table objects.
|
|
720
|
+
*/
|
|
721
|
+
declare function defineTable<const TCols extends Record<string, ColumnFactory>>(def: TableDefinition<TCols>): DefinedTable<TCols>;
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region src/table/registry.d.ts
|
|
724
|
+
declare class TableRegistryImpl {
|
|
725
|
+
private byName;
|
|
726
|
+
/**
|
|
727
|
+
* Register a defined table. Throws if a table with the same name is
|
|
728
|
+
* already registered (catches accidental double-registration from a
|
|
729
|
+
* file being imported under two paths).
|
|
730
|
+
*/
|
|
731
|
+
register(name: string, table: PgTable, source?: string): void;
|
|
732
|
+
/**
|
|
733
|
+
* Get a defined table by name.
|
|
734
|
+
*/
|
|
735
|
+
get(name: string): PgTable | undefined;
|
|
736
|
+
/**
|
|
737
|
+
* Returns true if a table with the given name has been registered.
|
|
738
|
+
*/
|
|
739
|
+
has(name: string): boolean;
|
|
740
|
+
/**
|
|
741
|
+
* Return every registered table as a `{ [name]: PgTable }` map.
|
|
742
|
+
*
|
|
743
|
+
* Useful for `createTestDb().push(tableRegistry.allTables())` and for
|
|
744
|
+
* exposing a single import target to `drizzle.config.ts`.
|
|
745
|
+
*/
|
|
746
|
+
allTables(): Record<string, PgTable>;
|
|
747
|
+
/**
|
|
748
|
+
* Clear the registry. **Test-only.**
|
|
749
|
+
*
|
|
750
|
+
* Production code should never call this — clearing the registry
|
|
751
|
+
* silently breaks migration discovery and any code path that looks
|
|
752
|
+
* up a table by name. The runtime guard below refuses to run when
|
|
753
|
+
* `NODE_ENV === 'production'` to catch accidental misuse.
|
|
754
|
+
*
|
|
755
|
+
* Test runners typically set `NODE_ENV=test` (Vitest does), but the
|
|
756
|
+
* guard is permissive about other values — it only blocks the one
|
|
757
|
+
* environment where calling it is definitely wrong.
|
|
758
|
+
*/
|
|
759
|
+
clear(): void;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Process-wide table registry singleton.
|
|
763
|
+
*
|
|
764
|
+
* Tables register themselves on import via `defineTable()`. The registry
|
|
765
|
+
* is module-level state, which means a single Node process sees one
|
|
766
|
+
* consistent view of all defined tables across packages.
|
|
767
|
+
*/
|
|
768
|
+
declare const tableRegistry: TableRegistryImpl;
|
|
769
|
+
/**
|
|
770
|
+
* Create an isolated registry — useful for unit tests that want to
|
|
771
|
+
* verify registration behaviour without polluting the global registry.
|
|
772
|
+
*/
|
|
773
|
+
declare function createTableRegistry(): TableRegistryImpl;
|
|
774
|
+
//#endregion
|
|
775
|
+
//#region src/table/where-builder.d.ts
|
|
776
|
+
/**
|
|
777
|
+
* A custom error thrown by the where-builder for any caller-side mistake.
|
|
778
|
+
*
|
|
779
|
+
* Catching `WhereBuilderError` lets the table client surface a 400-style
|
|
780
|
+
* error to its caller without conflating it with database errors.
|
|
781
|
+
*/
|
|
782
|
+
declare class WhereBuilderError extends Error {
|
|
783
|
+
constructor(message: string);
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Returns true if a {@link WhereClause} produces a non-trivial filter (at
|
|
787
|
+
* least one column predicate or sub-clause). Useful for rejecting empty
|
|
788
|
+
* `where` arguments to `update`/`delete`/`updateMany`/`deleteMany`.
|
|
789
|
+
*
|
|
790
|
+
* Note: this does not check whether `buildWhere()` would actually emit SQL
|
|
791
|
+
* for the clause — it just checks that the user supplied something.
|
|
792
|
+
*/
|
|
793
|
+
declare function isNonEmptyWhere(clause: unknown): boolean;
|
|
794
|
+
//#endregion
|
|
795
|
+
export { type AnyPgColumn, type ColumnFactory, type ColumnKind, type ColumnOperators, type ColumnValue, DEFAULT_LIMIT, type DbConfig, type DefinedTable, type FindManyOptions, type IndexDefinition, type InsertRow, MAX_BATCH, MAX_LIMIT, type MigrateArgs, type MigrationLoader, type MigrationModule, type MigrationSource, type MigrationStatus, type OrderBySpec, type Row, type SchemaRegistry, TableClient, TableClientError, type TableDefinition, type UniqueDefinition, WhereBuilderError, type WhereClause, column, createDbClient, createReadOnlyClient, createSchemaRegistry, createTableRegistry, defineTable, discoverMigrations, getMigrationStatus, isNonEmptyWhere, rollbackMigrations, runMigrations, schemaRegistry, tableRegistry };
|
|
78
796
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/client.ts","../src/migrate.ts","../src/schema-registry.ts"],"mappings":";;;;UAGiB,QAAA;EACf,GAAA;EACA,WAAA;EACA,OAAA;EACA,OAAA;AAAA;;;;iBAMc,cAAA,CAAe,MAAA,EAAQ,QAAA,GAAW,kBAAA
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/client.ts","../src/migrate.ts","../src/schema-registry.ts","../src/table/types.ts","../src/table/columns.ts","../src/table/client.ts","../src/table/define.ts","../src/table/registry.ts","../src/table/where-builder.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{drizzle as e}from"drizzle-orm/postgres-js";import t from"postgres";import{existsSync as n}from"node:fs";import{
|
|
1
|
+
import{drizzle as e}from"drizzle-orm/postgres-js";import t from"postgres";import{existsSync as n}from"node:fs";import{readdir as r}from"node:fs/promises";import{join as i}from"node:path";import{and as a,count as o,eq as s,gt as c,gte as l,ilike as u,inArray as d,isNotNull as f,isNull as p,lt as m,lte as h,ne as ee,not as te,notInArray as ne,or as g,sql as _}from"drizzle-orm";import{bigint as v,boolean as y,doublePrecision as b,index as x,integer as S,jsonb as C,pgTable as w,primaryKey as re,text as ie,timestamp as ae,unique as oe,uuid as T,varchar as E}from"drizzle-orm/pg-core";function D(n){return e(t(n.url,{max:n.poolMax||10,idle_timeout:20,max_lifetime:1800}))}function O(n){let r=t(n.readOnlyUrl||n.url,{max:n.poolMax||10,idle_timeout:20,max_lifetime:1800,connection:{application_name:`toolkit_readonly`},onnotice:()=>{}});return r.unsafe(`SET default_transaction_read_only = on`).catch(()=>{}),e(r)}async function k(e){await e.execute(_`
|
|
2
2
|
CREATE TABLE IF NOT EXISTS _toolkit_migrations (
|
|
3
3
|
id SERIAL PRIMARY KEY,
|
|
4
4
|
namespace VARCHAR(50) NOT NULL,
|
|
@@ -6,11 +6,15 @@ import{drizzle as e}from"drizzle-orm/postgres-js";import t from"postgres";import
|
|
|
6
6
|
applied_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
7
7
|
UNIQUE(namespace, name)
|
|
8
8
|
)
|
|
9
|
-
`)}async function
|
|
9
|
+
`)}async function A(e){let t=i(e,`migrations`);if(!n(t))return[];let a=[],o=i(t,`.toolkit`);if(n(o)){let e=await r(o);for(let t of e)t.endsWith(`.ts`)&&a.push({path:i(o,t),namespace:`toolkit`,name:t})}let s=await r(t);for(let e of s)e!==`.toolkit`&&e.endsWith(`.ts`)&&a.push({path:i(t,e),namespace:`project`,name:e});let c=a.filter(e=>e.namespace===`toolkit`).sort((e,t)=>e.name.localeCompare(t.name)),l=a.filter(e=>e.namespace===`project`).sort((e,t)=>e.name.localeCompare(t.name));return[...c,...l]}async function j(e,t){await k(e);let n=await A(t),r=await e.execute(_`
|
|
10
10
|
SELECT namespace, name FROM _toolkit_migrations
|
|
11
11
|
ORDER BY applied_at ASC
|
|
12
|
-
`),i=new Set(r.map(e=>`${e.namespace}:${e.name}`)),a=[],
|
|
12
|
+
`),i=new Set(r.map(e=>`${e.namespace}:${e.name}`)),a=[],o=[];for(let e of n)i.has(`${e.namespace}:${e.name}`)?a.push(e):o.push(e);return{applied:a,pending:o}}async function M(e,t,n){await k(e);let{pending:r}=await j(e,t);if(r.length===0){console.log(`No pending migrations`);return}console.log(`Running ${r.length} pending migration${r.length===1?``:`s`}...`);for(let t of r){console.log(` Applying ${t.namespace}/${t.name}...`);let r=await n(t.path);if(typeof r.up!=`function`)throw Error(`Migration ${t.namespace}/${t.name} has no \`up\` export. Every migration file must export an async \`up({ db })\` function.`);try{await e.transaction(async e=>{await r.up({db:e}),await e.execute(_`
|
|
13
13
|
INSERT INTO _toolkit_migrations (namespace, name)
|
|
14
14
|
VALUES (${t.namespace}, ${t.name})
|
|
15
|
-
`)}),console.log(` ✓ Applied ${t.namespace}/${t.name}`)}catch(e){throw console.error(` ✗ Failed to apply ${t.namespace}/${t.name}:`,e),e}}console.log(`Successfully applied ${
|
|
15
|
+
`)}),console.log(` ✓ Applied ${t.namespace}/${t.name}`)}catch(e){throw console.error(` ✗ Failed to apply ${t.namespace}/${t.name}:`,e),e}}console.log(`Successfully applied ${r.length} migration${r.length===1?``:`s`}`)}async function N(e,t,n,r=1){await k(e);let{applied:i}=await j(e,t);if(i.length===0){console.log(`No applied migrations to roll back`);return}let a=i.slice(-r).reverse(),o=await Promise.all(a.map(async e=>({meta:e,mod:await n(e.path)})));for(let{meta:e,mod:t}of o)if(typeof t.down!=`function`)throw Error(`Migration ${e.namespace}/${e.name} has no \`down\` export — cannot roll back. Add an async \`down({ db })\` function to the migration file.`);for(let{meta:t,mod:n}of o){console.log(` Rolling back ${t.namespace}/${t.name}...`);try{await e.transaction(async e=>{await n.down({db:e}),await e.execute(_`
|
|
16
|
+
DELETE FROM _toolkit_migrations
|
|
17
|
+
WHERE namespace = ${t.namespace} AND name = ${t.name}
|
|
18
|
+
`)}),console.log(` ✓ Rolled back ${t.namespace}/${t.name}`)}catch(e){throw console.error(` ✗ Failed to roll back ${t.namespace}/${t.name}:`,e),e}}console.log(`Successfully rolled back ${o.length} migration${o.length===1?``:`s`}`)}var P=class{schemas=new Map;register(e,t){this.schemas.set(e,t)}get(e){return this.schemas.get(e)}all(){let e={};for(let[t,n]of this.schemas.entries())e[t]=n;return e}has(e){return this.schemas.has(e)}};const F=new P;function I(){return new P}function L(e,t){let n=e;return Object.defineProperty(n,`__kind`,{value:t.kind,enumerable:!1}),Object.defineProperty(n,`__notNull`,{value:t.notNull,enumerable:!1}),Object.defineProperty(n,`__hasDefault`,{value:t.hasDefault,enumerable:!1}),Object.defineProperty(n,`__type`,{value:void 0,enumerable:!1}),n}const R={uuid(e){let t=e?.notNull??!1,n=e?.defaultRandom??!1,r=e?.primaryKey??!1;return L(n=>{let i=T(n);return r&&(i=i.primaryKey()),e?.defaultRandom&&(i=i.defaultRandom()),t&&!r&&(i=i.notNull()),i},{kind:`uuid`,notNull:t||r,hasDefault:n||r})},varchar(e){if(!Number.isInteger(e.length)||e.length<1||e.length>1073741823)throw Error(`column.varchar: length must be an integer between 1 and 1073741823, got ${e.length}`);let t=e.notNull??!1;return L(n=>{let r=E(n,{length:e.length});return t&&(r=r.notNull()),e.default!==void 0&&(r=r.default(e.default)),r},{kind:`varchar`,notNull:t,hasDefault:e.default!==void 0})},text(e){let t=e?.notNull??!1;return L(n=>{let r=ie(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`text`,notNull:t,hasDefault:e?.default!==void 0})},integer(e){let t=e?.notNull??!1;return L(n=>{let r=S(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`integer`,notNull:t,hasDefault:e?.default!==void 0})},bigint(e){let t=e?.mode??`number`,n=e?.notNull??!1;return L(r=>{let i=v(r,{mode:t});return n&&(i=i.notNull()),e?.default!==void 0&&(i=i.default(e.default)),i},{kind:`bigint`,notNull:n,hasDefault:e?.default!==void 0})},double(e){let t=e?.notNull??!1;return L(n=>{let r=b(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`double`,notNull:t,hasDefault:e?.default!==void 0})},boolean(e){let t=e?.notNull??!1;return L(n=>{let r=y(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`boolean`,notNull:t,hasDefault:e?.default!==void 0})},timestamp(e){let t=e?.notNull??!1,n=e?.defaultNow??!1,r=e?.withTimezone??!0;return L(n=>{let i=ae(n,{withTimezone:r});return t&&(i=i.notNull()),e?.defaultNow&&(i=i.defaultNow()),i},{kind:`timestamp`,notNull:t,hasDefault:n})},jsonb(e){let t=e?.notNull??!1;return L(e=>{let n=C(e);return t&&(n=n.notNull()),n},{kind:`jsonb`,notNull:t,hasDefault:!1})},uuidArray(e){let t=e?.notNull??!1;return L(e=>{let n=T(e).array();return t&&(n=n.notNull()),n},{kind:`uuidArray`,notNull:t,hasDefault:!1})}},z=/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\d+$/,B=new Set([`$and`,`$or`,`$not`]),se=new Set([`eq`,`ne`,`in`,`notIn`,`gt`,`gte`,`lt`,`lte`,`isNull`,`isNotNull`,`ilike`,`startsWith`]);var V=class extends Error{constructor(e){super(`WhereBuilder: ${e}`),this.name=`WhereBuilderError`}};function H(e){return e.replace(/[\\%_]/g,`\\$&`)}function U(e,t,n){if(!n.includes(`.`)){let t=e[n];if(!t)throw new V(`unknown column "${n}"`);return t}let[r,...i]=n.split(`.`);if(!r||i.length===0)throw new V(`malformed dotted key "${n}"`);let a=t[r];if(!a)throw new V(`unknown column "${r}" in dotted key "${n}"`);if(a!==`jsonb`)throw new V(`dotted-path access is only supported on jsonb columns, but "${r}" is ${a}`);for(let e of i)if(!z.test(e))throw new V(`invalid jsonb path segment "${e}" — must match [a-zA-Z_][a-zA-Z0-9_-]* or be a non-negative integer`);let o=e[r];if(!o)throw new V(`unknown column "${r}"`);let s=_`${o}`;for(let e=0;e<i.length;e++){let t=i[e],n=e===i.length-1;if(/^\d+$/.test(t)){if(t.length>10)throw new V(`jsonb path index "${t}" is too large`);let e=_.raw(t);s=n?_`${s}->>${e}`:_`${s}->${e}`}else s=n?_`${s}->>${t}`:_`${s}->${t}`}return s}function W(e,t){if(t===null)return p(e);if(typeof t==`string`||typeof t==`number`||typeof t==`boolean`||typeof t==`bigint`||t instanceof Date)return s(e,t);if(Array.isArray(t))throw new V(`arrays must be wrapped in an operator (e.g. { in: [...] }) — bare arrays are not allowed`);if(typeof t!=`object`)throw new V(`unsupported value type: ${typeof t}`);let n=t,r=Object.keys(n);if(r.length===0)throw new V(`empty operator object`);let i=[];for(let t of r){if(!se.has(t))throw new V(`unknown operator "${t}"`);let r=n[t];switch(t){case`eq`:i.push(s(e,r));break;case`ne`:i.push(ee(e,r));break;case`in`:if(!Array.isArray(r))throw new V(`"in" operator requires an array`);r.length===0?i.push(_`false`):i.push(d(e,r));break;case`notIn`:if(!Array.isArray(r))throw new V(`"notIn" operator requires an array`);r.length===0?i.push(_`true`):i.push(ne(e,r));break;case`gt`:i.push(c(e,r));break;case`gte`:i.push(l(e,r));break;case`lt`:i.push(m(e,r));break;case`lte`:i.push(h(e,r));break;case`isNull`:if(r!==!0)throw new V('"isNull" operator must be `true`');i.push(p(e));break;case`isNotNull`:if(r!==!0)throw new V('"isNotNull" operator must be `true`');i.push(f(e));break;case`ilike`:if(typeof r!=`string`)throw new V(`"ilike" operator requires a string`);i.push(u(e,`%${H(r)}%`));break;case`startsWith`:if(typeof r!=`string`)throw new V(`"startsWith" operator requires a string`);i.push(u(e,`${H(r)}%`));break}}if(i.length!==0)return i.length===1?i[0]:a(...i)}function G(e,t,n,r=0){if(!n)return;if(typeof n!=`object`||Array.isArray(n))throw new V(`where clause must be a plain object`);if(r>16)throw new V(`where clause exceeds maximum nesting depth of 16 — real queries should not need this much nesting; if yours does, reconsider whether it should be expressed differently`);let i=[];for(let o of Object.keys(n)){let s=n[o];if(o===`$and`){if(!Array.isArray(s))throw new V(`$and must be an array`);if(s.length===0)throw new V(`$and must contain at least one clause`);let n=s.map(n=>G(e,t,n,r+1)).filter(e=>e!==void 0);n.length>0&&i.push(a(...n));continue}if(o===`$or`){if(!Array.isArray(s))throw new V(`$or must be an array`);if(s.length===0)throw new V(`$or must contain at least one clause`);let n=s.map(n=>G(e,t,n,r+1)).filter(e=>e!==void 0);n.length>0&&i.push(g(...n));continue}if(o===`$not`){if(!K(s))throw new V(`$not must contain a non-empty clause`);let n=G(e,t,s,r+1);n&&i.push(te(n));continue}if(B.has(o))continue;let c=W(U(e,t,o),s);c&&i.push(c)}if(i.length!==0)return i.length===1?i[0]:a(...i)}function K(e){return typeof e!=`object`||!e||Array.isArray(e)?!1:Object.keys(e).length>0}const ce=100,q=1e3,J=1e3;var Y=class extends Error{constructor(e){super(`TableClient: ${e}`),this.name=`TableClientError`}},X=class e{constructor(e,t,n){this.table=e,this.columnKinds=t,this.db=n}async findOne(e){if(!K(e))throw new Y(`findOne requires a non-empty where clause`);let t=this.buildWhereOrThrow(e);return(await this.db.select().from(this.table).where(t).limit(1))[0]??null}async findMany(e={}){let t=this.validateLimit(e.limit);if(e.offset!==void 0&&(!Number.isInteger(e.offset)||e.offset<0))throw new Y(`offset must be a non-negative integer, got ${e.offset}`);let n=e.orderBy&&e.orderBy.length>0?e.orderBy.map(e=>{let t=this.table[e.column];if(!t)throw new Y(`unknown orderBy column "${e.column}"`);return e.dir===`desc`?_`${t} DESC`:_`${t} ASC`}):void 0,r=e.where?this.buildWhereOrThrow(e.where):void 0,i=this.db.select().from(this.table).$dynamic();return r&&(i=i.where(r)),n&&(i=i.orderBy(...n)),i=i.limit(t),e.offset!==void 0&&(i=i.offset(e.offset)),await i}async count(e){let t=e?this.buildWhereOrThrow(e):void 0,n=this.db.select({value:o()}).from(this.table).$dynamic();t&&(n=n.where(t));let r=await n;return Number(r[0]?.value??0)}async exists(e){if(!K(e))throw new Y(`exists requires a non-empty where clause`);let t=this.buildWhereOrThrow(e);return(await this.db.select({one:_`1`}).from(this.table).where(t).limit(1)).length>0}async insert(e){return(await this.db.insert(this.table).values(e).returning())[0]}async upsert(e,t){let n=Array.isArray(t.target)?t.target:[t.target];if(n.length===0)throw new Y(`upsert requires at least one target column`);let r=n.map(e=>{let t=this.table[e];if(!t)throw new Y(`upsert target references unknown column "${e}"`);return t}),i=t.set??e;return(await this.db.insert(this.table).values(e).onConflictDoUpdate({target:r,set:i}).returning())[0]}async insertMany(e){if(!Array.isArray(e))throw new Y(`insertMany requires an array of rows`);if(e.length===0)return[];if(e.length>1e3)throw new Y(`insertMany batch too large: ${e.length} > ${J}`);return await this.db.insert(this.table).values(e).returning()}async update(e,t){if(!K(e))throw new Y(`update requires a non-empty where clause`);let n=this.buildWhereOrThrow(e),r=await this.db.update(this.table).set(t).where(n).returning();if(r.length>1)throw new Y(`update matched ${r.length} rows but is expected to match at most one — use updateMany for bulk updates. WARNING: the update has already been applied; wrap in a transaction if you need rollback.`);return r[0]??null}async updateMany(e,t){if(!K(e))throw new Y(`updateMany requires a non-empty where clause`);let n=this.buildWhereOrThrow(e);return await this.db.update(this.table).set(t).where(n).returning()}async delete(e){if(!K(e))throw new Y(`delete requires a non-empty where clause`);let t=this.buildWhereOrThrow(e),n=await this.db.delete(this.table).where(t).returning();if(n.length>1)throw new Y(`delete matched ${n.length} rows but is expected to match at most one — use deleteMany for bulk deletes. WARNING: the delete has already been applied; wrap in a transaction if you need rollback.`);return n[0]??null}async deleteMany(e){if(!K(e))throw new Y(`deleteMany requires a non-empty where clause`);let t=this.buildWhereOrThrow(e);return await this.db.delete(this.table).where(t).returning()}async transaction(t){return await this.db.transaction(async n=>await t(new e(this.table,this.columnKinds,n)))}buildWhereOrThrow(e){try{let t=G(this.table,this.columnKinds,e);if(!t)throw new Y(`where clause produced no SQL predicate`);return t}catch(e){throw e instanceof V?new Y(e.message.replace(/^WhereBuilder: /,``)):e}}validateLimit(e){if(e===void 0)return 100;if(!Number.isInteger(e)||e<1)throw new Y(`limit must be a positive integer, got ${e}`);if(e>1e3)throw new Y(`limit ${e} exceeds MAX_LIMIT ${q} — paginate or use claim() for batch processing`);return e}},Z=class{byName=new Map;register(e,t,n){let r=this.byName.get(e);if(r){if(r.table===t)return;throw Error(`tableRegistry: a different table is already registered under name "${e}". Did you call defineTable twice? First registered from ${r.source??`<unknown>`}, now from ${n??`<unknown>`}.`)}this.byName.set(e,{name:e,table:t,source:n})}get(e){return this.byName.get(e)?.table}has(e){return this.byName.has(e)}allTables(){let e={};for(let t of this.byName.values())e[t.name]=t.table;return e}clear(){if(process.env.NODE_ENV===`production`)throw Error(`tableRegistry.clear() is not allowed when NODE_ENV=production — this is a test-only utility. If you reached this from production code, you have a bug.`);this.byName.clear()}};const Q=new Z;function le(){return new Z}const ue=/^[a-z][a-z0-9_]*$/;function $(e,t,n){if(e.length>63)throw Error(`defineTable: ${t} "${e}" is ${e.length} characters — Postgres truncates identifiers at 63 chars silently, which can cause name collisions${n?`. ${n}`:``}`)}function de(e){if(!ue.test(e.name))throw Error(`defineTable: invalid table name "${e.name}" — must match /^[a-z][a-z0-9_]*$/`);$(e.name,`table name`);let t={},n={};for(let[r,i]of Object.entries(e.columns)){if(typeof i!=`function`)throw Error(`defineTable: column "${r}" is not a column factory — did you call e.g. column.uuid() with parens?`);$(r,`column name in table "${e.name}"`),t[r]=i(r),n[r]=i.__kind}let r=new Set(Object.keys(t));if(e.primaryKey!==void 0){let t=Array.isArray(e.primaryKey)?e.primaryKey:[e.primaryKey];for(let e of t)if(!r.has(String(e)))throw Error(`defineTable: primaryKey references unknown column "${String(e)}"`)}if(e.unique)for(let t of e.unique){for(let e of t.on)if(!r.has(String(e)))throw Error(`defineTable: unique constraint references unknown column "${String(e)}"`);$(t.name??`uq_${e.name}_${t.on.map(String).join(`_`)}`,t.name?`unique constraint name`:`auto-generated unique constraint name`,t.name?void 0:"Supply a shorter explicit `name` to the unique definition.")}if(e.indexes)for(let t of e.indexes){for(let e of t.on)if(!r.has(String(e)))throw Error(`defineTable: index references unknown column "${String(e)}"`);$(t.name??`idx_${e.name}_${t.on.map(String).join(`_`)}`,t.name?`index name`:`auto-generated index name`,t.name?void 0:"Supply a shorter explicit `name` to the index definition.")}let i=e.primaryKey!==void 0||e.unique&&e.unique.length>0||e.indexes&&e.indexes.length>0?w(e.name,t,t=>{let n={};if(e.primaryKey!==void 0&&(n.pk=re({columns:(Array.isArray(e.primaryKey)?e.primaryKey:[e.primaryKey]).map(e=>{let n=t[e];if(!n)throw Error(`defineTable: primaryKey references unknown column "${String(e)}"`);return n})})),e.unique)for(let r of e.unique){let i=r.on.map(e=>{let n=t[e];if(!n)throw Error(`defineTable: unique constraint references unknown column "${String(e)}"`);return n}),a=r.name??`uq_${e.name}_${r.on.map(String).join(`_`)}`;n[a]=oe(a).on(...i)}if(e.indexes)for(let r of e.indexes){let i=r.on.map(e=>{let n=t[e];if(!n)throw Error(`defineTable: index references unknown column "${String(e)}"`);return n}),a=r.name??`idx_${e.name}_${r.on.map(String).join(`_`)}`;n[a]=x(a).on(...i)}return n}):w(e.name,t),a=fe();Q.register(e.name,i,a);let o=Object.freeze({...n});return{table:i,schema:e,columnKinds:o,makeClient:e=>new X(i,o,e)}}function fe(){let e=Error().stack;if(!e)return;let t=e.split(`
|
|
19
|
+
`).slice(1);for(let e of t){if(e.includes(`/table/define.`)||e.includes(`/table/registry.`))continue;let t=e.match(/\(([^)]+)\)/)??e.match(/at (.+)$/);if(t)return t[1].trim()}}export{ce as DEFAULT_LIMIT,J as MAX_BATCH,q as MAX_LIMIT,X as TableClient,Y as TableClientError,V as WhereBuilderError,R as column,D as createDbClient,O as createReadOnlyClient,I as createSchemaRegistry,le as createTableRegistry,de as defineTable,A as discoverMigrations,j as getMigrationStatus,K as isNonEmptyWhere,N as rollbackMigrations,M as runMigrations,F as schemaRegistry,Q as tableRegistry};
|
|
16
20
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/client.ts","../src/migrate.ts","../src/schema-registry.ts"],"sourcesContent":["import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport postgres from 'postgres'\n\nexport interface DbConfig {\n url: string\n readOnlyUrl?: string\n poolMin?: number\n poolMax?: number\n}\n\n/**\n * Create a full read-write database client\n */\nexport function createDbClient(config: DbConfig): PostgresJsDatabase {\n const sql = postgres(config.url, {\n max: config.poolMax || 10,\n idle_timeout: 20,\n max_lifetime: 60 * 30,\n })\n\n return drizzle(sql)\n}\n\n/**\n * Create a read-only database client\n * Sets default_transaction_read_only=on to prevent writes even if SQL injection occurs\n */\nexport function createReadOnlyClient(config: DbConfig): PostgresJsDatabase {\n const url = config.readOnlyUrl || config.url\n\n const sql = postgres(url, {\n max: config.poolMax || 10,\n idle_timeout: 20,\n max_lifetime: 60 * 30,\n connection: {\n application_name: 'toolkit_readonly',\n },\n onnotice: () => {}, // Suppress NOTICE messages\n })\n\n // Set session to read-only\n // This enforces read-only at the PostgreSQL level, not just application level\n sql.unsafe('SET default_transaction_read_only = on').catch(() => {\n // Silently fail if already set\n })\n\n return drizzle(sql)\n}\n","import { existsSync } from 'node:fs'\nimport { readdir, readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { sql } from 'drizzle-orm'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\n\nexport interface MigrationSource {\n path: string\n namespace: 'toolkit' | 'project'\n name: string\n}\n\nexport interface MigrationStatus {\n applied: MigrationSource[]\n pending: MigrationSource[]\n}\n\n/**\n * Ensure the _toolkit_migrations table exists\n */\nasync function ensureMigrationsTable(db: PostgresJsDatabase): Promise<void> {\n await db.execute(sql`\n CREATE TABLE IF NOT EXISTS _toolkit_migrations (\n id SERIAL PRIMARY KEY,\n namespace VARCHAR(50) NOT NULL,\n name VARCHAR(255) NOT NULL,\n applied_at TIMESTAMP NOT NULL DEFAULT NOW(),\n UNIQUE(namespace, name)\n )\n `)\n}\n\n/**\n * Discover migrations from both toolkit (.toolkit/) and project (migrations/) directories\n */\nexport async function discoverMigrations(projectRoot: string): Promise<MigrationSource[]> {\n const migrations: MigrationSource[] = []\n\n // Discover toolkit migrations (.toolkit/ directory)\n const toolkitDir = join(projectRoot, 'migrations', '.toolkit')\n if (existsSync(toolkitDir)) {\n const files = await readdir(toolkitDir)\n for (const file of files) {\n if (file.endsWith('.sql') || file.endsWith('.ts')) {\n migrations.push({\n path: join(toolkitDir, file),\n namespace: 'toolkit',\n name: file,\n })\n }\n }\n }\n\n // Discover project migrations (migrations/ directory, excluding .toolkit/)\n const migrationsDir = join(projectRoot, 'migrations')\n if (existsSync(migrationsDir)) {\n const files = await readdir(migrationsDir)\n for (const file of files) {\n if (file !== '.toolkit' && (file.endsWith('.sql') || file.endsWith('.ts'))) {\n migrations.push({\n path: join(migrationsDir, file),\n namespace: 'project',\n name: file,\n })\n }\n }\n }\n\n // Sort migrations:\n // 1. Toolkit migrations by name (they should have numeric prefixes)\n // 2. Project migrations by name (timestamp prefixes)\n const toolkitMigrations = migrations\n .filter((m) => m.namespace === 'toolkit')\n .sort((a, b) => a.name.localeCompare(b.name))\n\n const projectMigrations = migrations\n .filter((m) => m.namespace === 'project')\n .sort((a, b) => a.name.localeCompare(b.name))\n\n return [...toolkitMigrations, ...projectMigrations]\n}\n\n/**\n * Get the status of migrations (applied vs pending)\n */\nexport async function getMigrationStatus(\n db: PostgresJsDatabase,\n projectRoot: string,\n): Promise<MigrationStatus> {\n await ensureMigrationsTable(db)\n\n const allMigrations = await discoverMigrations(projectRoot)\n\n // Get applied migrations from database\n const appliedRows = await db.execute<{ namespace: string; name: string }>(sql`\n SELECT namespace, name FROM _toolkit_migrations\n ORDER BY applied_at ASC\n `)\n\n const appliedSet = new Set(\n appliedRows.map((row: { namespace: string; name: string }) => `${row.namespace}:${row.name}`),\n )\n\n const applied: MigrationSource[] = []\n const pending: MigrationSource[] = []\n\n for (const migration of allMigrations) {\n const key = `${migration.namespace}:${migration.name}`\n if (appliedSet.has(key)) {\n applied.push(migration)\n } else {\n pending.push(migration)\n }\n }\n\n return { applied, pending }\n}\n\n/**\n * Run pending migrations\n */\nexport async function runMigrations(db: PostgresJsDatabase, projectRoot: string): Promise<void> {\n await ensureMigrationsTable(db)\n\n const { pending } = await getMigrationStatus(db, projectRoot)\n\n if (pending.length === 0) {\n console.log('No pending migrations')\n return\n }\n\n console.log(`Running ${pending.length} pending migrations...`)\n\n for (const migration of pending) {\n console.log(` Applying ${migration.namespace}/${migration.name}...`)\n\n try {\n // Read migration file\n const content = await readFile(migration.path, 'utf-8')\n\n // Execute migration in a transaction\n await db.transaction(async (tx) => {\n // Execute the migration SQL\n await tx.execute(sql.raw(content))\n\n // Record the migration as applied\n await tx.execute(sql`\n INSERT INTO _toolkit_migrations (namespace, name)\n VALUES (${migration.namespace}, ${migration.name})\n `)\n })\n\n console.log(` ✓ Applied ${migration.namespace}/${migration.name}`)\n } catch (error) {\n console.error(` ✗ Failed to apply ${migration.namespace}/${migration.name}:`, error)\n throw error\n }\n }\n\n console.log(`Successfully applied ${pending.length} migrations`)\n}\n","import type { PgTableWithColumns } from 'drizzle-orm/pg-core'\n\n/**\n * Schema registry for storing Drizzle schemas\n * This will be populated by @org/entity when schemas are generated from entity definitions\n *\n * We use PgTableWithColumns<any> because entity table schemas are dynamic (columns\n * not known at compile time) and we need runtime property access (.id, .status, etc.).\n * AnyPgTable won't work here — it doesn't expose column accessors.\n */\n\n// biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any> for property access\ntype AnyTable = PgTableWithColumns<any>\n\nexport interface SchemaRegistry {\n register(name: string, schema: AnyTable): void\n get(name: string): AnyTable | undefined\n all(): Record<string, AnyTable>\n has(name: string): boolean\n}\n\nclass SchemaRegistryImpl implements SchemaRegistry {\n private schemas: Map<string, AnyTable> = new Map()\n\n register(name: string, schema: AnyTable): void {\n this.schemas.set(name, schema)\n }\n\n get(name: string): AnyTable | undefined {\n return this.schemas.get(name)\n }\n\n all(): Record<string, AnyTable> {\n const result: Record<string, AnyTable> = {}\n for (const [name, schema] of this.schemas.entries()) {\n result[name] = schema\n }\n return result\n }\n\n has(name: string): boolean {\n return this.schemas.has(name)\n }\n}\n\n/**\n * Global schema registry instance\n * Entities will register their schemas here when they are defined\n */\nexport const schemaRegistry = new SchemaRegistryImpl()\n\n/**\n * Create a new isolated schema registry (useful for testing)\n */\nexport function createSchemaRegistry(): SchemaRegistry {\n return new SchemaRegistryImpl()\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["countFn","drizzlePrimaryKey","drizzleUnique","drizzleIndex"],"sources":["../src/client.ts","../src/migrate.ts","../src/schema-registry.ts","../src/table/columns.ts","../src/table/where-builder.ts","../src/table/client.ts","../src/table/registry.ts","../src/table/define.ts"],"sourcesContent":["import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport postgres from 'postgres'\n\nexport interface DbConfig {\n url: string\n readOnlyUrl?: string\n poolMin?: number\n poolMax?: number\n}\n\n/**\n * Create a full read-write database client\n */\nexport function createDbClient(config: DbConfig): PostgresJsDatabase {\n const sql = postgres(config.url, {\n max: config.poolMax || 10,\n idle_timeout: 20,\n max_lifetime: 60 * 30,\n })\n\n return drizzle(sql)\n}\n\n/**\n * Create a read-only database client\n * Sets default_transaction_read_only=on to prevent writes even if SQL injection occurs\n */\nexport function createReadOnlyClient(config: DbConfig): PostgresJsDatabase {\n const url = config.readOnlyUrl || config.url\n\n const sql = postgres(url, {\n max: config.poolMax || 10,\n idle_timeout: 20,\n max_lifetime: 60 * 30,\n connection: {\n application_name: 'toolkit_readonly',\n },\n onnotice: () => {}, // Suppress NOTICE messages\n })\n\n // Set session to read-only\n // This enforces read-only at the PostgreSQL level, not just application level\n sql.unsafe('SET default_transaction_read_only = on').catch(() => {\n // Silently fail if already set\n })\n\n return drizzle(sql)\n}\n","import { existsSync } from 'node:fs'\nimport { readdir } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { sql } from 'drizzle-orm'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\n\n/**\n * Arguments passed to migration `up` / `down` functions.\n *\n * Intentionally tiny: we hand over a read-write Drizzle handle bound to\n * the current transaction. If a migration needs anything beyond raw SQL\n * (e.g. calling an AdminClient for a data backfill), it can import the\n * relevant toolkit helpers inside the migration file — the CLI loader\n * runs the migration in the same process as the config, so all package\n * imports resolve normally.\n */\nexport interface MigrateArgs {\n db: PostgresJsDatabase\n}\n\n/**\n * Shape of a migration module loaded from a `.ts` file.\n *\n * At minimum a migration must export `up`. `down` is optional — when\n * absent, `lumi migrate:rollback` refuses to roll that file back.\n */\nexport interface MigrationModule {\n up: (args: MigrateArgs) => Promise<void>\n down?: (args: MigrateArgs) => Promise<void>\n}\n\nexport interface MigrationSource {\n /** Absolute path to the migration file on disk. */\n path: string\n /** `project` for user migrations, `toolkit` for shipped-with-toolkit ones. */\n namespace: 'toolkit' | 'project'\n /** Filename only, used for ordering + tracking. */\n name: string\n}\n\nexport interface MigrationStatus {\n applied: MigrationSource[]\n pending: MigrationSource[]\n}\n\n/**\n * Loader callback — imports a migration `.ts` file and returns its exports.\n *\n * The CLI owns `jiti` (already used to load `toolkit.config.ts`), so\n * `@murumets-ee/db` stays free of TS-runtime loader dependencies.\n * Callers who never apply migrations (read-only servers, tests) never\n * pay the loader cost.\n */\nexport type MigrationLoader = (absolutePath: string) => Promise<MigrationModule>\n\n/** Ensure the `_toolkit_migrations` tracking table exists. */\nasync function ensureMigrationsTable(db: PostgresJsDatabase): Promise<void> {\n await db.execute(sql`\n CREATE TABLE IF NOT EXISTS _toolkit_migrations (\n id SERIAL PRIMARY KEY,\n namespace VARCHAR(50) NOT NULL,\n name VARCHAR(255) NOT NULL,\n applied_at TIMESTAMP NOT NULL DEFAULT NOW(),\n UNIQUE(namespace, name)\n )\n `)\n}\n\n/**\n * Discover all migration files under `projectRoot/migrations`.\n *\n * Layout:\n * migrations/\n * 20260411_180000_add_articles.ts ← user migrations (sort chronologically)\n * 20260411_180000_add_articles.snapshot.json\n * .toolkit/\n * 0001_initial_schema.ts ← toolkit-owned migrations (sort numerically)\n */\nexport async function discoverMigrations(projectRoot: string): Promise<MigrationSource[]> {\n const migrationsDir = join(projectRoot, 'migrations')\n if (!existsSync(migrationsDir)) return []\n\n const migrations: MigrationSource[] = []\n\n // Toolkit migrations live under .toolkit/ — numeric prefix ordering.\n const toolkitDir = join(migrationsDir, '.toolkit')\n if (existsSync(toolkitDir)) {\n const files = await readdir(toolkitDir)\n for (const file of files) {\n if (!file.endsWith('.ts')) continue\n migrations.push({\n path: join(toolkitDir, file),\n namespace: 'toolkit',\n name: file,\n })\n }\n }\n\n // Project migrations live directly under migrations/ — timestamp prefix.\n const files = await readdir(migrationsDir)\n for (const file of files) {\n if (file === '.toolkit') continue\n if (!file.endsWith('.ts')) continue\n migrations.push({\n path: join(migrationsDir, file),\n namespace: 'project',\n name: file,\n })\n }\n\n // Stable ordering: toolkit first (numeric prefix), then project (timestamp prefix).\n const toolkitMigrations = migrations\n .filter((m) => m.namespace === 'toolkit')\n .sort((a, b) => a.name.localeCompare(b.name))\n const projectMigrations = migrations\n .filter((m) => m.namespace === 'project')\n .sort((a, b) => a.name.localeCompare(b.name))\n\n return [...toolkitMigrations, ...projectMigrations]\n}\n\n/** Split discovered migrations into applied vs pending by consulting the tracking table. */\nexport async function getMigrationStatus(\n db: PostgresJsDatabase,\n projectRoot: string,\n): Promise<MigrationStatus> {\n await ensureMigrationsTable(db)\n\n const all = await discoverMigrations(projectRoot)\n\n const appliedRows = await db.execute<{ namespace: string; name: string }>(sql`\n SELECT namespace, name FROM _toolkit_migrations\n ORDER BY applied_at ASC\n `)\n const appliedSet = new Set(appliedRows.map((row) => `${row.namespace}:${row.name}`))\n\n const applied: MigrationSource[] = []\n const pending: MigrationSource[] = []\n for (const m of all) {\n if (appliedSet.has(`${m.namespace}:${m.name}`)) applied.push(m)\n else pending.push(m)\n }\n return { applied, pending }\n}\n\n/**\n * Apply every pending migration in order.\n *\n * Each file runs inside its own transaction: the migration's `up()`\n * executes first, then the `_toolkit_migrations` insert commits. If\n * `up()` throws, the transaction rolls back and the whole command\n * aborts — nothing gets half-applied.\n *\n * The `loader` callback is injected so `@murumets-ee/db` itself doesn't\n * depend on a TS-runtime loader. Callers pass a jiti-backed loader from\n * the CLI or their own equivalent.\n */\nexport async function runMigrations(\n db: PostgresJsDatabase,\n projectRoot: string,\n loader: MigrationLoader,\n): Promise<void> {\n await ensureMigrationsTable(db)\n\n const { pending } = await getMigrationStatus(db, projectRoot)\n\n if (pending.length === 0) {\n console.log('No pending migrations')\n return\n }\n\n console.log(`Running ${pending.length} pending migration${pending.length === 1 ? '' : 's'}...`)\n\n for (const migration of pending) {\n console.log(` Applying ${migration.namespace}/${migration.name}...`)\n\n const mod = await loader(migration.path)\n if (typeof mod.up !== 'function') {\n throw new Error(\n `Migration ${migration.namespace}/${migration.name} has no \\`up\\` export. ` +\n 'Every migration file must export an async `up({ db })` function.',\n )\n }\n\n try {\n await db.transaction(async (tx) => {\n await mod.up({ db: tx as PostgresJsDatabase })\n await tx.execute(sql`\n INSERT INTO _toolkit_migrations (namespace, name)\n VALUES (${migration.namespace}, ${migration.name})\n `)\n })\n console.log(` ✓ Applied ${migration.namespace}/${migration.name}`)\n } catch (error) {\n console.error(` ✗ Failed to apply ${migration.namespace}/${migration.name}:`, error)\n throw error\n }\n }\n\n console.log(`Successfully applied ${pending.length} migration${pending.length === 1 ? '' : 's'}`)\n}\n\n/**\n * Roll back the most recent applied migration (or N most recent).\n *\n * Each file's `down()` runs in its own transaction. Files without a\n * `down` export cause the rollback to abort before running anything —\n * we never roll back some but not all of a requested batch.\n */\nexport async function rollbackMigrations(\n db: PostgresJsDatabase,\n projectRoot: string,\n loader: MigrationLoader,\n count = 1,\n): Promise<void> {\n await ensureMigrationsTable(db)\n const { applied } = await getMigrationStatus(db, projectRoot)\n\n if (applied.length === 0) {\n console.log('No applied migrations to roll back')\n return\n }\n\n // Take the last `count` applied, in reverse application order.\n const toRollback = applied.slice(-count).reverse()\n\n // Load all requested modules first and verify every one has `down`.\n const modules = await Promise.all(\n toRollback.map(async (m) => ({ meta: m, mod: await loader(m.path) })),\n )\n for (const { meta, mod } of modules) {\n if (typeof mod.down !== 'function') {\n throw new Error(\n `Migration ${meta.namespace}/${meta.name} has no \\`down\\` export — cannot roll back. ` +\n 'Add an async `down({ db })` function to the migration file.',\n )\n }\n }\n\n for (const { meta, mod } of modules) {\n console.log(` Rolling back ${meta.namespace}/${meta.name}...`)\n try {\n await db.transaction(async (tx) => {\n // biome-ignore lint/style/noNonNullAssertion: guarded above\n await mod.down!({ db: tx as PostgresJsDatabase })\n await tx.execute(sql`\n DELETE FROM _toolkit_migrations\n WHERE namespace = ${meta.namespace} AND name = ${meta.name}\n `)\n })\n console.log(` ✓ Rolled back ${meta.namespace}/${meta.name}`)\n } catch (error) {\n console.error(` ✗ Failed to roll back ${meta.namespace}/${meta.name}:`, error)\n throw error\n }\n }\n\n console.log(`Successfully rolled back ${modules.length} migration${modules.length === 1 ? '' : 's'}`)\n}\n","import type { PgTableWithColumns } from 'drizzle-orm/pg-core'\n\n/**\n * Schema registry for storing Drizzle schemas\n * This will be populated by @org/entity when schemas are generated from entity definitions\n *\n * We use PgTableWithColumns<any> because entity table schemas are dynamic (columns\n * not known at compile time) and we need runtime property access (.id, .status, etc.).\n * AnyPgTable won't work here — it doesn't expose column accessors.\n */\n\n// biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any> for property access\ntype AnyTable = PgTableWithColumns<any>\n\nexport interface SchemaRegistry {\n register(name: string, schema: AnyTable): void\n get(name: string): AnyTable | undefined\n all(): Record<string, AnyTable>\n has(name: string): boolean\n}\n\nclass SchemaRegistryImpl implements SchemaRegistry {\n private schemas: Map<string, AnyTable> = new Map()\n\n register(name: string, schema: AnyTable): void {\n this.schemas.set(name, schema)\n }\n\n get(name: string): AnyTable | undefined {\n return this.schemas.get(name)\n }\n\n all(): Record<string, AnyTable> {\n const result: Record<string, AnyTable> = {}\n for (const [name, schema] of this.schemas.entries()) {\n result[name] = schema\n }\n return result\n }\n\n has(name: string): boolean {\n return this.schemas.has(name)\n }\n}\n\n/**\n * Global schema registry instance\n * Entities will register their schemas here when they are defined\n */\nexport const schemaRegistry = new SchemaRegistryImpl()\n\n/**\n * Create a new isolated schema registry (useful for testing)\n */\nexport function createSchemaRegistry(): SchemaRegistry {\n return new SchemaRegistryImpl()\n}\n","/**\n * Column builders for `defineTable`.\n *\n * Each builder returns a {@link ColumnFactory} — a callable that, given a\n * column name, produces the corresponding Drizzle column builder. Phantom\n * type parameters carry the JS type, \"kind\" tag, nullability, and\n * has-default flag through to the row/insert/where types.\n *\n * Conventions vs. `@murumets-ee/entity` field builders:\n * - `notNull` here is the equivalent of `required` in field builders.\n * Renamed because tables don't have a UI distinction between\n * \"required at the schema level\" and \"required to display\".\n * - No `translatable`, `indexed`, `unique`, `access` — those are entity-only\n * concepts. `unique` and indexes are declared at the table level instead.\n * - `column.jsonb<T>()` accepts a phantom type parameter for typed reads.\n *\n * @example\n * ```ts\n * column.uuid({ primaryKey: true, defaultRandom: true })\n * column.varchar({ length: 255, notNull: true })\n * column.timestamp({ notNull: true, defaultNow: true, withTimezone: true })\n * column.jsonb<{ description: string; tags: string[] }>({ notNull: true })\n * ```\n */\n\nimport {\n type AnyPgColumn,\n bigint,\n boolean,\n doublePrecision,\n integer,\n jsonb,\n type PgColumnBuilderBase,\n text,\n timestamp,\n uuid,\n varchar,\n} from 'drizzle-orm/pg-core'\nimport type { ColumnFactory, ColumnKind } from './types.js'\n\n// ---------------------------------------------------------------------------\n// Internal: factory constructor\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a Drizzle column-builder factory function with the phantom metadata\n * needed for type inference. The phantom fields are non-enumerable so they\n * don't pollute logging or JSON serialization.\n */\nfunction makeFactory<TType, TKind extends ColumnKind, TNotNull extends boolean, THasDefault extends boolean>(\n build: (name: string) => PgColumnBuilderBase,\n meta: { kind: TKind; notNull: TNotNull; hasDefault: THasDefault },\n): ColumnFactory<TType, TKind, TNotNull, THasDefault> {\n const factory = build as ColumnFactory<TType, TKind, TNotNull, THasDefault>\n Object.defineProperty(factory, '__kind', { value: meta.kind, enumerable: false })\n Object.defineProperty(factory, '__notNull', { value: meta.notNull, enumerable: false })\n Object.defineProperty(factory, '__hasDefault', { value: meta.hasDefault, enumerable: false })\n // __type is purely a phantom — never read at runtime\n Object.defineProperty(factory, '__type', { value: undefined, enumerable: false })\n return factory\n}\n\n// ---------------------------------------------------------------------------\n// Public column builders\n// ---------------------------------------------------------------------------\n\n/**\n * Column builder namespace.\n *\n * Each builder produces a {@link ColumnFactory}. Pass the result to a\n * `defineTable` `columns` field. The builders are typed so that the\n * resulting `Row` and `InsertRow` types track each column's value type\n * and nullability.\n *\n * @example\n * ```ts\n * import { column, defineTable } from '@murumets-ee/db'\n *\n * const jobs = defineTable({\n * name: 'toolkit_jobs',\n * columns: {\n * id: column.uuid({ primaryKey: true, defaultRandom: true }),\n * type: column.varchar({ length: 100, notNull: true }),\n * payload: column.jsonb<{ subject: string; body: string }>({ notNull: true }),\n * status: column.varchar({ length: 20, notNull: true, default: 'pending' }),\n * priority: column.integer({ notNull: true, default: 0 }),\n * runAt: column.timestamp({ notNull: true, defaultNow: true, withTimezone: true }),\n * },\n * })\n * ```\n */\nexport const column = {\n /**\n * UUID column.\n *\n * For primary keys, use `{ primaryKey: true, defaultRandom: true }` to\n * get a server-generated UUIDv4.\n */\n uuid<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {\n primaryKey?: boolean\n notNull?: TNotNull\n /** Server-side `gen_random_uuid()` default. Counts as a default for type inference. */\n defaultRandom?: THasDefault\n }): ColumnFactory<\n string,\n 'uuid',\n TNotNull extends true ? true : false,\n // Primary key columns are always optional in inserts (auto-generated)\n THasDefault extends true ? true : boolean extends THasDefault ? false : false\n > {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = (opts?.defaultRandom ?? false) as boolean\n const isPk = opts?.primaryKey ?? false\n return makeFactory(\n (name) => {\n let col = uuid(name)\n if (isPk) col = col.primaryKey()\n if (opts?.defaultRandom) col = col.defaultRandom()\n // Primary keys are implicitly NOT NULL in Postgres; only call notNull for non-PK\n if (notNull && !isPk) col = col.notNull()\n return col\n },\n {\n kind: 'uuid',\n // Primary keys are always non-null\n notNull: (notNull || isPk) as TNotNull extends true ? true : false,\n hasDefault: (hasDefault || isPk) as THasDefault extends true ? true : false,\n },\n )\n },\n\n /**\n * Variable-length string column. `length` is required and must be a\n * positive integer up to Postgres' actual `varchar` ceiling\n * (1073741823 — i.e. ~1GB / 4 bytes per char). For unbounded text use\n * `column.text()` instead — it stores the same way and skips the\n * length check at write time.\n *\n * Conventional sizes for reference:\n * - 50–100: short identifiers, slugs, status codes\n * - 255: legacy \"common max length\" — fits a tweet, an email, etc.\n * - 1024: file paths, URLs\n * - 10000+: prefer `text` instead, varchar with very large lengths\n * buys you nothing\n */\n varchar<\n TNotNull extends boolean = false,\n THasDefault extends boolean = false,\n >(opts: {\n length: number\n notNull?: TNotNull\n default?: THasDefault extends true ? string : string | undefined\n }): ColumnFactory<\n string,\n 'varchar',\n TNotNull extends true ? true : false,\n THasDefault extends true ? true : false\n > {\n // Postgres' real varchar limit is 1073741823 (varchar without length\n // mod uses TEXT internally). We enforce the same upper bound so the\n // error fires at definition time instead of at INSERT time.\n if (!Number.isInteger(opts.length) || opts.length < 1 || opts.length > 1073741823) {\n throw new Error(\n `column.varchar: length must be an integer between 1 and 1073741823, got ${opts.length}`,\n )\n }\n const notNull = (opts.notNull ?? false) as boolean\n const hasDefault = opts.default !== undefined\n return makeFactory(\n (name) => {\n let col = varchar(name, { length: opts.length })\n if (notNull) col = col.notNull()\n if (opts.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'varchar',\n notNull: notNull as TNotNull extends true ? true : false,\n hasDefault: hasDefault as THasDefault extends true ? true : false,\n },\n )\n },\n\n /**\n * Unbounded text column.\n */\n text<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {\n notNull?: TNotNull\n default?: THasDefault extends true ? string : string | undefined\n }): ColumnFactory<\n string,\n 'text',\n TNotNull extends true ? true : false,\n THasDefault extends true ? true : false\n > {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = text(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'text',\n notNull: notNull as TNotNull extends true ? true : false,\n hasDefault: hasDefault as THasDefault extends true ? true : false,\n },\n )\n },\n\n /**\n * 32-bit integer column.\n */\n integer<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {\n notNull?: TNotNull\n default?: THasDefault extends true ? number : number | undefined\n }): ColumnFactory<\n number,\n 'integer',\n TNotNull extends true ? true : false,\n THasDefault extends true ? true : false\n > {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = integer(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'integer',\n notNull: notNull as TNotNull extends true ? true : false,\n hasDefault: hasDefault as THasDefault extends true ? true : false,\n },\n )\n },\n\n /**\n * 64-bit integer column. `mode: 'number'` returns a JS number (safe for\n * values up to 2^53), `mode: 'bigint'` returns a JS BigInt.\n */\n bigint<\n TMode extends 'number' | 'bigint' = 'number',\n TNotNull extends boolean = false,\n THasDefault extends boolean = false,\n >(opts?: {\n mode?: TMode\n notNull?: TNotNull\n default?: THasDefault extends true ? (TMode extends 'bigint' ? bigint : number) : (TMode extends 'bigint' ? bigint : number) | undefined\n }): ColumnFactory<\n TMode extends 'bigint' ? bigint : number,\n 'bigint',\n TNotNull extends true ? true : false,\n THasDefault extends true ? true : false\n > {\n const mode = (opts?.mode ?? 'number') as 'number' | 'bigint'\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = bigint(name, { mode })\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default as never)\n return col\n },\n {\n kind: 'bigint',\n notNull: notNull as TNotNull extends true ? true : false,\n hasDefault: hasDefault as THasDefault extends true ? true : false,\n },\n )\n },\n\n /**\n * Double-precision floating-point column.\n */\n double<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {\n notNull?: TNotNull\n default?: THasDefault extends true ? number : number | undefined\n }): ColumnFactory<\n number,\n 'double',\n TNotNull extends true ? true : false,\n THasDefault extends true ? true : false\n > {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = doublePrecision(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'double',\n notNull: notNull as TNotNull extends true ? true : false,\n hasDefault: hasDefault as THasDefault extends true ? true : false,\n },\n )\n },\n\n /**\n * Boolean column. Defaults to `false` if no explicit default is supplied\n * AND `notNull` is true — matches the entity package convention.\n */\n boolean<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {\n notNull?: TNotNull\n default?: THasDefault extends true ? boolean : boolean | undefined\n }): ColumnFactory<\n boolean,\n 'boolean',\n TNotNull extends true ? true : false,\n THasDefault extends true ? true : false\n > {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = boolean(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'boolean',\n notNull: notNull as TNotNull extends true ? true : false,\n hasDefault: hasDefault as THasDefault extends true ? true : false,\n },\n )\n },\n\n /**\n * Timestamp column. `withTimezone: true` is strongly recommended for all\n * timestamps in this codebase — entity audit fields use it consistently.\n */\n timestamp<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {\n notNull?: TNotNull\n /** Server-side `NOW()` default. Counts as a default for type inference. */\n defaultNow?: THasDefault\n withTimezone?: boolean\n }): ColumnFactory<\n Date,\n 'timestamp',\n TNotNull extends true ? true : false,\n THasDefault extends true ? true : false\n > {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = (opts?.defaultNow ?? false) as boolean\n const withTimezone = opts?.withTimezone ?? true\n return makeFactory(\n (name) => {\n let col = timestamp(name, { withTimezone })\n if (notNull) col = col.notNull()\n if (opts?.defaultNow) col = col.defaultNow()\n return col\n },\n {\n kind: 'timestamp',\n notNull: notNull as TNotNull extends true ? true : false,\n hasDefault: hasDefault as THasDefault extends true ? true : false,\n },\n )\n },\n\n /**\n * JSONB column. The phantom type parameter `T` propagates to row reads\n * and where-clause shorthand for the whole-column case. For dotted-path\n * access via the where-builder, the value type at the path is `unknown`\n * (TypeScript can't follow runtime path navigation).\n */\n jsonb<T = unknown, TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n }): ColumnFactory<T, 'jsonb', TNotNull extends true ? true : false, false> {\n const notNull = (opts?.notNull ?? false) as boolean\n return makeFactory(\n (name) => {\n let col = jsonb(name)\n if (notNull) col = col.notNull()\n return col\n },\n {\n kind: 'jsonb',\n notNull: notNull as TNotNull extends true ? true : false,\n hasDefault: false as false,\n },\n )\n },\n\n /**\n * UUID array column (`uuid[]`).\n */\n uuidArray<TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n }): ColumnFactory<string[], 'uuidArray', TNotNull extends true ? true : false, false> {\n const notNull = (opts?.notNull ?? false) as boolean\n return makeFactory(\n (name) => {\n let col = uuid(name).array() as unknown as PgColumnBuilderBase\n // .notNull is on the array builder; cast through unknown to satisfy types\n if (notNull) {\n col = (col as unknown as { notNull(): PgColumnBuilderBase }).notNull()\n }\n return col\n },\n {\n kind: 'uuidArray',\n notNull: notNull as TNotNull extends true ? true : false,\n hasDefault: false as false,\n },\n )\n },\n}\n\n// Re-export the Drizzle column type for advanced consumers that need to\n// reach into the underlying pgTable for typed JOINs.\nexport type { AnyPgColumn }\n","/**\n * WhereClause → Drizzle SQL translator.\n *\n * This module is the *only* place in the table layer that touches Drizzle's\n * query operators. All `findOne`/`findMany`/`update`/`delete`/`count` calls\n * funnel their where-objects through `buildWhere()` here. That makes this\n * file the single security-review surface for query construction.\n *\n * Safety properties:\n *\n * 1. **Values are always parameterized.** Every operator passes its value\n * through Drizzle's typed operators (`eq`, `gt`, etc.) which use\n * parameter binding. Raw `sql` template literals are NEVER constructed\n * from caller-supplied values.\n * 2. **Column identifiers are looked up by key.** The `where` object's keys\n * are matched against the table's known column set; unknown keys throw.\n * No dynamic identifier interpolation.\n * 3. **jsonb path segments are whitelist-validated.** Each segment must\n * match `/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\\d+$/`. Anything else throws.\n * 4. **`ilike`/`startsWith` values are escaped.** `%` and `_` in caller-\n * supplied strings are backslash-escaped before wrapping with wildcards\n * so callers cannot accidentally inject pattern characters.\n *\n * @internal\n */\n\nimport {\n and,\n eq,\n gt,\n gte,\n ilike,\n inArray,\n isNotNull,\n isNull,\n lt,\n lte,\n ne,\n not,\n notInArray,\n or,\n type SQL,\n type SQLWrapper,\n sql,\n} from 'drizzle-orm'\nimport type { AnyPgColumn, PgTable } from 'drizzle-orm/pg-core'\nimport type { ColumnFactory, ColumnKind, WhereClause } from './types.js'\n\n/** Identifier shape allowed for jsonb path segments. */\nconst JSONB_SEGMENT_RE = /^[a-zA-Z_][a-zA-Z0-9_-]*$|^\\d+$/\n\n/** Reserved meta-keys in a where-clause object. */\nconst META_KEYS = new Set(['$and', '$or', '$not'])\n\n/**\n * Maximum nesting depth for `$and` / `$or` / `$not` recursion.\n *\n * Real production where-clauses are 2-3 levels deep at most. The cap\n * exists to prevent stack-overflow DoS attacks if anyone ever passes\n * untrusted user input through the where-builder (which the design\n * docs forbid, but warnings are not code).\n *\n * Bumping this is fine if a real consumer needs it — but think hard\n * about why a query needs more than 16 levels of nesting first.\n */\nconst MAX_WHERE_DEPTH = 16\n\n/** Operator keys recognised in a column-operator object. */\nconst OP_KEYS = new Set([\n 'eq',\n 'ne',\n 'in',\n 'notIn',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'isNull',\n 'isNotNull',\n 'ilike',\n 'startsWith',\n])\n\n/**\n * A custom error thrown by the where-builder for any caller-side mistake.\n *\n * Catching `WhereBuilderError` lets the table client surface a 400-style\n * error to its caller without conflating it with database errors.\n */\nexport class WhereBuilderError extends Error {\n constructor(message: string) {\n super(`WhereBuilder: ${message}`)\n this.name = 'WhereBuilderError'\n }\n}\n\n/**\n * Escape `%` and `_` (and the escape char `\\`) in a caller-supplied LIKE\n * pattern value. The result is safe to wrap with our own wildcards without\n * leaking pattern syntax to the caller.\n */\nfunction escapeLikeValue(value: string): string {\n return value.replace(/[\\\\%_]/g, '\\\\$&')\n}\n\n/**\n * Resolve a where-clause key to a Drizzle column reference or a jsonb path\n * SQL expression.\n *\n * - Plain column key: `'userId'` → `table.userId` (typed Drizzle column)\n * - jsonb dotted path: `'metadata.author.name'` → `metadata->'author'->>'name'`\n *\n * The parent column for any dotted path MUST be of kind `jsonb`. The\n * column kind is read from the column factory metadata stored at table\n * definition time.\n */\nfunction resolveTarget(\n table: PgTable,\n columnKinds: Readonly<Record<string, ColumnKind>>,\n key: string,\n): SQLWrapper {\n if (!key.includes('.')) {\n const col = (table as unknown as Record<string, AnyPgColumn | undefined>)[key]\n if (!col) {\n throw new WhereBuilderError(`unknown column \"${key}\"`)\n }\n return col\n }\n\n const segments = key.split('.')\n const [columnName, ...path] = segments\n if (!columnName || path.length === 0) {\n throw new WhereBuilderError(`malformed dotted key \"${key}\"`)\n }\n const kind = columnKinds[columnName]\n if (!kind) {\n throw new WhereBuilderError(`unknown column \"${columnName}\" in dotted key \"${key}\"`)\n }\n if (kind !== 'jsonb') {\n throw new WhereBuilderError(\n `dotted-path access is only supported on jsonb columns, but \"${columnName}\" is ${kind}`,\n )\n }\n for (const seg of path) {\n if (!JSONB_SEGMENT_RE.test(seg)) {\n throw new WhereBuilderError(\n `invalid jsonb path segment \"${seg}\" — must match [a-zA-Z_][a-zA-Z0-9_-]* or be a non-negative integer`,\n )\n }\n }\n\n const col = (table as unknown as Record<string, AnyPgColumn | undefined>)[columnName]\n if (!col) {\n throw new WhereBuilderError(`unknown column \"${columnName}\"`)\n }\n\n // Build: col->'a'->'b'->>'c' (intermediate -> for nested object access,\n // final ->> for text extraction so comparison/ilike operators work).\n //\n // String segments are parameterized normally — Postgres binds them as\n // text and `->'key'` does object access.\n //\n // Numeric segments are inlined as raw SQL because parameter binding\n // doesn't reliably dispatch to the array-index variant of `->`/`->>`\n // (Postgres often binds bigint, but the array-index operator overload\n // requires a literal int). The segment is already whitelist-validated\n // as `^\\d+$`, so inlining is safe by construction. Re-validate here as\n // belt-and-braces.\n let target: SQL = sql`${col}`\n for (let i = 0; i < path.length; i++) {\n const seg = path[i]\n const isLast = i === path.length - 1\n if (/^\\d+$/.test(seg)) {\n // Defence-in-depth re-check: must be pure digits, no leading zeros\n // beyond a single 0, length capped to avoid runaway integers.\n if (seg.length > 10) {\n throw new WhereBuilderError(`jsonb path index \"${seg}\" is too large`)\n }\n const literal = sql.raw(seg)\n target = isLast ? sql`${target}->>${literal}` : sql`${target}->${literal}`\n } else {\n target = isLast ? sql`${target}->>${seg}` : sql`${target}->${seg}`\n }\n }\n return target\n}\n\n/**\n * Apply a single column-operator object (or shorthand value) to a target\n * (column or jsonb-path expression). Returns the corresponding Drizzle SQL\n * predicate.\n */\nfunction applyOperator(target: SQLWrapper, value: unknown): SQL | undefined {\n // Shorthand: bare value → eq, bare null → isNull\n if (value === null) {\n return isNull(target as AnyPgColumn)\n }\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean' ||\n typeof value === 'bigint' ||\n value instanceof Date\n ) {\n return eq(target as AnyPgColumn, value as never)\n }\n if (Array.isArray(value)) {\n throw new WhereBuilderError(\n 'arrays must be wrapped in an operator (e.g. { in: [...] }) — bare arrays are not allowed',\n )\n }\n if (typeof value !== 'object') {\n throw new WhereBuilderError(`unsupported value type: ${typeof value}`)\n }\n\n const obj = value as Record<string, unknown>\n const opKeys = Object.keys(obj)\n if (opKeys.length === 0) {\n throw new WhereBuilderError('empty operator object')\n }\n\n // Multiple operators in one object are AND-ed together\n const parts: SQL[] = []\n for (const op of opKeys) {\n if (!OP_KEYS.has(op)) {\n throw new WhereBuilderError(`unknown operator \"${op}\"`)\n }\n const v = obj[op]\n switch (op) {\n case 'eq':\n parts.push(eq(target as AnyPgColumn, v as never))\n break\n case 'ne':\n parts.push(ne(target as AnyPgColumn, v as never))\n break\n case 'in':\n if (!Array.isArray(v)) {\n throw new WhereBuilderError('\"in\" operator requires an array')\n }\n if (v.length === 0) {\n // IN () is invalid SQL; an empty IN should match nothing\n parts.push(sql`false`)\n } else {\n parts.push(inArray(target as AnyPgColumn, v as never[]))\n }\n break\n case 'notIn':\n if (!Array.isArray(v)) {\n throw new WhereBuilderError('\"notIn\" operator requires an array')\n }\n if (v.length === 0) {\n // NOT IN () matches everything\n parts.push(sql`true`)\n } else {\n parts.push(notInArray(target as AnyPgColumn, v as never[]))\n }\n break\n case 'gt':\n parts.push(gt(target as AnyPgColumn, v as never))\n break\n case 'gte':\n parts.push(gte(target as AnyPgColumn, v as never))\n break\n case 'lt':\n parts.push(lt(target as AnyPgColumn, v as never))\n break\n case 'lte':\n parts.push(lte(target as AnyPgColumn, v as never))\n break\n case 'isNull':\n if (v !== true) {\n throw new WhereBuilderError('\"isNull\" operator must be `true`')\n }\n parts.push(isNull(target as AnyPgColumn))\n break\n case 'isNotNull':\n if (v !== true) {\n throw new WhereBuilderError('\"isNotNull\" operator must be `true`')\n }\n parts.push(isNotNull(target as AnyPgColumn))\n break\n case 'ilike': {\n if (typeof v !== 'string') {\n throw new WhereBuilderError('\"ilike\" operator requires a string')\n }\n // Caller's value is treated as a literal string — outer wildcards\n // come from the operator semantics, not from caller-supplied %.\n // We escape any wildcard chars in the value to prevent accidental\n // pattern injection.\n parts.push(ilike(target as AnyPgColumn, `%${escapeLikeValue(v)}%`))\n break\n }\n case 'startsWith': {\n if (typeof v !== 'string') {\n throw new WhereBuilderError('\"startsWith\" operator requires a string')\n }\n parts.push(ilike(target as AnyPgColumn, `${escapeLikeValue(v)}%`))\n break\n }\n }\n }\n\n if (parts.length === 0) return undefined\n if (parts.length === 1) return parts[0]\n return and(...parts)\n}\n\n/**\n * Translate a {@link WhereClause} object into a single Drizzle SQL\n * expression suitable for `db.select(...).where(here)`.\n *\n * Returns `undefined` for an empty clause (no filtering). Callers that\n * require a non-empty where (update, delete) should reject `undefined`\n * before invoking the SQL.\n *\n * @param depth - internal recursion depth, used to enforce\n * {@link MAX_WHERE_DEPTH}. External callers should not pass this.\n * @throws {@link WhereBuilderError} for any malformed clause, unknown\n * column, invalid operator, invalid jsonb path segment, or recursion\n * deeper than {@link MAX_WHERE_DEPTH}.\n */\nexport function buildWhere<TCols extends Record<string, ColumnFactory>>(\n table: PgTable,\n columnKinds: Readonly<Record<string, ColumnKind>>,\n clause: WhereClause<TCols> | undefined,\n depth = 0,\n): SQL | undefined {\n if (!clause) return undefined\n if (typeof clause !== 'object' || Array.isArray(clause)) {\n throw new WhereBuilderError('where clause must be a plain object')\n }\n if (depth > MAX_WHERE_DEPTH) {\n throw new WhereBuilderError(\n `where clause exceeds maximum nesting depth of ${MAX_WHERE_DEPTH} — ` +\n 'real queries should not need this much nesting; if yours does, ' +\n 'reconsider whether it should be expressed differently',\n )\n }\n\n const parts: SQL[] = []\n\n for (const key of Object.keys(clause)) {\n const value = (clause as Record<string, unknown>)[key]\n\n if (key === '$and') {\n if (!Array.isArray(value)) {\n throw new WhereBuilderError('$and must be an array')\n }\n if (value.length === 0) {\n // $and: [] is almost certainly a bug — surface it loudly,\n // matching the $or: [] behaviour for consistency.\n throw new WhereBuilderError('$and must contain at least one clause')\n }\n const subParts = (value as WhereClause<TCols>[])\n .map((c) => buildWhere(table, columnKinds, c, depth + 1))\n .filter((p): p is SQL => p !== undefined)\n if (subParts.length > 0) parts.push(and(...subParts) as SQL)\n continue\n }\n\n if (key === '$or') {\n if (!Array.isArray(value)) {\n throw new WhereBuilderError('$or must be an array')\n }\n if (value.length === 0) {\n // $or: [] would match nothing; reject explicitly to surface the bug\n throw new WhereBuilderError('$or must contain at least one clause')\n }\n const subParts = (value as WhereClause<TCols>[])\n .map((c) => buildWhere(table, columnKinds, c, depth + 1))\n .filter((p): p is SQL => p !== undefined)\n if (subParts.length > 0) parts.push(or(...subParts) as SQL)\n continue\n }\n\n if (key === '$not') {\n if (!isNonEmptyWhere(value)) {\n // $not: {} or $not: undefined is almost certainly a bug — the\n // negation of \"match everything\" or \"no filter\" produces\n // surprising semantics.\n throw new WhereBuilderError('$not must contain a non-empty clause')\n }\n const sub = buildWhere(table, columnKinds, value as WhereClause<TCols>, depth + 1)\n if (sub) parts.push(not(sub))\n continue\n }\n\n if (META_KEYS.has(key)) {\n // unreachable — guarded above\n continue\n }\n\n const target = resolveTarget(table, columnKinds, key)\n const predicate = applyOperator(target, value)\n if (predicate) parts.push(predicate)\n }\n\n if (parts.length === 0) return undefined\n if (parts.length === 1) return parts[0]\n return and(...parts) as SQL\n}\n\n/**\n * Returns true if a {@link WhereClause} produces a non-trivial filter (at\n * least one column predicate or sub-clause). Useful for rejecting empty\n * `where` arguments to `update`/`delete`/`updateMany`/`deleteMany`.\n *\n * Note: this does not check whether `buildWhere()` would actually emit SQL\n * for the clause — it just checks that the user supplied something.\n */\nexport function isNonEmptyWhere(clause: unknown): boolean {\n if (clause === undefined || clause === null) return false\n if (typeof clause !== 'object') return false\n if (Array.isArray(clause)) return false\n return Object.keys(clause as object).length > 0\n}\n","/**\n * Generic typed CRUD client for `defineTable` tables.\n *\n * Single-table operations only — no joins, no aggregates beyond `count`,\n * no hooks. For typed multi-table queries, callers reach for the\n * underlying `table` object exposed by `defineTable` and write plain\n * Drizzle. That's the one sanctioned escape valve.\n *\n * Hard rules baked into the client (not just at the SQL level):\n *\n * - `update`, `updateMany`, `delete`, `deleteMany` REQUIRE a non-empty\n * `where` clause. Empty wheres throw `TableClientError` at the client,\n * not just \"0 rows updated\".\n * - `findMany.limit` defaults to {@link DEFAULT_LIMIT} (100) and is hard-\n * capped at {@link MAX_LIMIT} (1000). Larger values get clamped and a\n * warning is logged.\n * - `insertMany` is capped at {@link MAX_BATCH} (1000) per call to keep a\n * single round trip from monopolising the connection.\n * - All write methods return the affected row(s). No silent updates.\n *\n * For more advanced operations (`upsert` with `setWhere`, `claim()`,\n * `aggregate()`, `countEstimate()`) see follow-up PRs in the\n * `feat/define-table` series.\n */\n\nimport { count as countFn, sql } from 'drizzle-orm'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport type { PgTable } from 'drizzle-orm/pg-core'\nimport type { ColumnFactory, ColumnKind, InsertRow, Row, WhereClause } from './types.js'\nimport { buildWhere, isNonEmptyWhere, WhereBuilderError } from './where-builder.js'\n\n/**\n * Default page size for `findMany` when no `limit` is supplied.\n *\n * Chosen as a safe upper bound for \"show me a page of stuff\" — anything\n * above this should be paginated explicitly.\n */\nexport const DEFAULT_LIMIT = 100\n\n/**\n * Hard cap on `findMany.limit`. Caller-supplied values above this are\n * clamped and a warning is emitted via `console.warn`.\n */\nexport const MAX_LIMIT = 1000\n\n/**\n * Hard cap on `insertMany.values.length` per single call.\n */\nexport const MAX_BATCH = 1000\n\n/**\n * A custom error thrown by the table client for any caller-side mistake\n * (empty where on update/delete, batch too large, malformed where, …).\n *\n * Database errors are NOT wrapped — they propagate as-is from postgres-js\n * so callers can match on Postgres error codes.\n */\nexport class TableClientError extends Error {\n constructor(message: string) {\n super(`TableClient: ${message}`)\n this.name = 'TableClientError'\n }\n}\n\n/**\n * Order-by spec accepted by `findMany`.\n */\nexport interface OrderBySpec<TCols extends Record<string, ColumnFactory>> {\n column: keyof TCols & string\n dir?: 'asc' | 'desc'\n}\n\n/**\n * `findMany` options.\n */\nexport interface FindManyOptions<TCols extends Record<string, ColumnFactory>> {\n where?: WhereClause<TCols>\n orderBy?: OrderBySpec<TCols>[]\n limit?: number\n offset?: number\n}\n\n/**\n * Generic CRUD client. Constructed by `defineTable` — never instantiated\n * directly by callers.\n *\n * The same instance shape is exposed both at the top level (using the\n * package's main `db` connection) and inside a `transaction()` callback\n * (using the transactional `tx` handle), so user code is identical\n * regardless of transactional context.\n *\n * @template TCols The columns record passed to `defineTable`.\n */\nexport class TableClient<TCols extends Record<string, ColumnFactory>> {\n /**\n * @internal — instances are created by `defineTable()`.\n */\n constructor(\n /** The Drizzle table object. Exposed via `defineTable().table` for typed JOINs. */\n public readonly table: PgTable,\n /** Per-column kind metadata. Used by the where-builder for jsonb path validation. */\n private readonly columnKinds: Readonly<Record<string, ColumnKind>>,\n /**\n * The active database handle. May be a top-level `PostgresJsDatabase`\n * or a transaction handle of compatible shape — the client doesn't\n * distinguish.\n */\n private readonly db: PostgresJsDatabase,\n ) {}\n\n // -------------------------------------------------------------------------\n // Reads\n // -------------------------------------------------------------------------\n\n /**\n * Find a single row matching the given where clause, or `null` if no row\n * matches. Throws if the where is empty.\n *\n * @example\n * ```ts\n * await readState.client.findOne({ ticketId: 't_1', userId: 'u_1' })\n * ```\n */\n async findOne(where: WhereClause<TCols>): Promise<Row<TCols> | null> {\n if (!isNonEmptyWhere(where)) {\n throw new TableClientError('findOne requires a non-empty where clause')\n }\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db\n .select()\n .from(this.table)\n .where(condition)\n .limit(1)\n return (rows[0] as Row<TCols> | undefined) ?? null\n }\n\n /**\n * Find multiple rows. Pagination, ordering, and filtering are all\n * optional but `limit` is hard-capped at {@link MAX_LIMIT}.\n *\n * @example\n * ```ts\n * await jobs.client.findMany({\n * where: { status: 'pending', runAt: { lte: new Date() } },\n * orderBy: [{ column: 'priority', dir: 'desc' }, { column: 'runAt', dir: 'asc' }],\n * limit: 25,\n * })\n * ```\n */\n async findMany(options: FindManyOptions<TCols> = {}): Promise<Row<TCols>[]> {\n // Validate all caller-supplied options up front, before touching the\n // database. This guarantees TableClientError is thrown for input\n // mistakes regardless of db state — important for unit testability\n // and for failing fast on misuse.\n const limit = this.validateLimit(options.limit)\n if (options.offset !== undefined) {\n if (!Number.isInteger(options.offset) || options.offset < 0) {\n throw new TableClientError(`offset must be a non-negative integer, got ${options.offset}`)\n }\n }\n const orderExprs =\n options.orderBy && options.orderBy.length > 0\n ? options.orderBy.map((spec) => {\n const col = (this.table as unknown as Record<string, unknown>)[spec.column]\n if (!col) {\n throw new TableClientError(`unknown orderBy column \"${spec.column}\"`)\n }\n return spec.dir === 'desc' ? sql`${col} DESC` : sql`${col} ASC`\n })\n : undefined\n const condition = options.where ? this.buildWhereOrThrow(options.where) : undefined\n\n let query = this.db.select().from(this.table).$dynamic()\n if (condition) query = query.where(condition)\n if (orderExprs) query = query.orderBy(...orderExprs)\n query = query.limit(limit)\n if (options.offset !== undefined) query = query.offset(options.offset)\n\n const rows = await query\n return rows as Row<TCols>[]\n }\n\n /**\n * Count rows matching the where clause. Returns an exact count via\n * `SELECT COUNT(*)`. For estimated/cached counts on large tables, see\n * `countEstimate()` and `countCached()` (added in a follow-up PR).\n *\n * @example\n * ```ts\n * const open = await jobs.client.count({ status: 'pending' })\n * ```\n */\n async count(where?: WhereClause<TCols>): Promise<number> {\n const condition = where ? this.buildWhereOrThrow(where) : undefined\n let query = this.db\n .select({ value: countFn() })\n .from(this.table)\n .$dynamic()\n if (condition) query = query.where(condition)\n const rows = await query\n return Number(rows[0]?.value ?? 0)\n }\n\n /**\n * Returns `true` if at least one row matches the where clause. Always\n * uses `LIMIT 1` and short-circuits — cheaper than `count() > 0` on\n * large tables.\n */\n async exists(where: WhereClause<TCols>): Promise<boolean> {\n if (!isNonEmptyWhere(where)) {\n throw new TableClientError('exists requires a non-empty where clause')\n }\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db\n .select({ one: sql`1` })\n .from(this.table)\n .where(condition)\n .limit(1)\n return rows.length > 0\n }\n\n // -------------------------------------------------------------------------\n // Writes\n // -------------------------------------------------------------------------\n\n /**\n * Insert a single row. Returns the inserted row (server-generated\n * defaults populated).\n */\n async insert(values: InsertRow<TCols>): Promise<Row<TCols>> {\n const rows = await this.db\n .insert(this.table)\n .values(values as never)\n .returning()\n return rows[0] as Row<TCols>\n }\n\n /**\n * Upsert: insert a row, or update an existing row if a unique constraint\n * conflict occurs.\n *\n * `target` names the column(s) whose unique constraint should trigger\n * the conflict path. `set` is the patch applied on conflict — when\n * omitted, the conflict path applies the same `values` (i.e. \"last\n * write wins\"). The PR 1 version does not support `setWhere`\n * (conditional updates on conflict); see PR 3 for the atomic-locking\n * pattern that uses it.\n *\n * @example Idempotent \"mark as read\" tracking\n * ```ts\n * await readState.client.upsert(\n * { ticketId, userId, lastReadAt: new Date() },\n * { target: ['ticketId', 'userId'] },\n * )\n * ```\n *\n * @example Counter increment\n * ```ts\n * await counters.client.upsert(\n * { key: 'page_views', value: 1 },\n * { target: 'key', set: { value: sql`${counters.table.value} + 1` } as never },\n * )\n * ```\n */\n async upsert(\n values: InsertRow<TCols>,\n opts: {\n target: (keyof TCols & string) | (keyof TCols & string)[]\n set?: Partial<InsertRow<TCols>>\n },\n ): Promise<Row<TCols>> {\n const targetKeys = Array.isArray(opts.target) ? opts.target : [opts.target]\n if (targetKeys.length === 0) {\n throw new TableClientError('upsert requires at least one target column')\n }\n const targetCols = targetKeys.map((k) => {\n const col = (this.table as unknown as Record<string, unknown>)[k]\n if (!col) {\n throw new TableClientError(`upsert target references unknown column \"${k}\"`)\n }\n return col\n })\n // When `set` is omitted, fall back to applying the same values on\n // conflict — this is the natural \"last write wins\" semantics that\n // covers the common upsert case.\n const setPatch = (opts.set ?? values) as Partial<InsertRow<TCols>>\n\n const rows = await this.db\n .insert(this.table)\n .values(values as never)\n .onConflictDoUpdate({\n // biome-ignore lint/suspicious/noExplicitAny: composite target columns are heterogeneous\n target: targetCols as any,\n set: setPatch as never,\n })\n .returning()\n return rows[0] as Row<TCols>\n }\n\n /**\n * Insert many rows in a single statement. Capped at {@link MAX_BATCH}.\n * Returns all inserted rows in input order.\n */\n async insertMany(values: InsertRow<TCols>[]): Promise<Row<TCols>[]> {\n if (!Array.isArray(values)) {\n throw new TableClientError('insertMany requires an array of rows')\n }\n if (values.length === 0) return []\n if (values.length > MAX_BATCH) {\n throw new TableClientError(\n `insertMany batch too large: ${values.length} > ${MAX_BATCH}`,\n )\n }\n const rows = await this.db\n .insert(this.table)\n .values(values as never[])\n .returning()\n return rows as Row<TCols>[]\n }\n\n /**\n * Update **at most one** row matching the where clause. Returns the\n * updated row, or `null` if no row matched.\n *\n * Throws `TableClientError` if:\n * - the where is empty\n * - the where matches more than one row\n *\n * The multi-row guard exists to prevent silent bulk updates from a\n * mistakenly-broad where. If you genuinely want to update multiple\n * rows in one call, use {@link updateMany}, which is explicit about\n * its plurality.\n *\n * Implementation note: this issues a single `UPDATE … RETURNING` and\n * inspects the returned row count *after* the fact. The extra rows\n * over the wire are the cost of the safety check; the alternative\n * (a SELECT round-trip first) would be more rows AND a race window.\n * If the affected count is > 1 the update has *already* happened —\n * the throw is to surface the bug, not to prevent it. Wrap in a\n * transaction if you need atomic rollback.\n */\n async update(\n where: WhereClause<TCols>,\n patch: Partial<InsertRow<TCols>>,\n ): Promise<Row<TCols> | null> {\n if (!isNonEmptyWhere(where)) {\n throw new TableClientError('update requires a non-empty where clause')\n }\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db\n .update(this.table)\n .set(patch as never)\n .where(condition)\n .returning()\n if (rows.length > 1) {\n throw new TableClientError(\n `update matched ${rows.length} rows but is expected to match at most one — use updateMany for bulk updates. ` +\n `WARNING: the update has already been applied; wrap in a transaction if you need rollback.`,\n )\n }\n return (rows[0] as Row<TCols> | undefined) ?? null\n }\n\n /**\n * Update all rows matching the where clause. Returns every updated row.\n * Throws if the where is empty — there is no \"update everything\" path.\n */\n async updateMany(\n where: WhereClause<TCols>,\n patch: Partial<InsertRow<TCols>>,\n ): Promise<Row<TCols>[]> {\n if (!isNonEmptyWhere(where)) {\n throw new TableClientError('updateMany requires a non-empty where clause')\n }\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db\n .update(this.table)\n .set(patch as never)\n .where(condition)\n .returning()\n return rows as Row<TCols>[]\n }\n\n /**\n * Delete **at most one** row matching the where clause. Returns the\n * deleted row, or `null` if no row matched.\n *\n * Throws `TableClientError` if:\n * - the where is empty\n * - the where matches more than one row\n *\n * Same multi-row safety guard as {@link update}. Use {@link deleteMany}\n * for explicit bulk deletes.\n *\n * Implementation note: same as `update` — the delete has already\n * happened by the time the throw fires. Wrap in a transaction if you\n * need atomic rollback.\n */\n async delete(where: WhereClause<TCols>): Promise<Row<TCols> | null> {\n if (!isNonEmptyWhere(where)) {\n throw new TableClientError('delete requires a non-empty where clause')\n }\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db.delete(this.table).where(condition).returning()\n if (rows.length > 1) {\n throw new TableClientError(\n `delete matched ${rows.length} rows but is expected to match at most one — use deleteMany for bulk deletes. ` +\n `WARNING: the delete has already been applied; wrap in a transaction if you need rollback.`,\n )\n }\n return (rows[0] as Row<TCols> | undefined) ?? null\n }\n\n /**\n * Delete all rows matching the where clause. Returns every deleted row.\n * Throws if the where is empty — there is no \"delete everything\" path.\n */\n async deleteMany(where: WhereClause<TCols>): Promise<Row<TCols>[]> {\n if (!isNonEmptyWhere(where)) {\n throw new TableClientError('deleteMany requires a non-empty where clause')\n }\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db.delete(this.table).where(condition).returning()\n return rows as Row<TCols>[]\n }\n\n // -------------------------------------------------------------------------\n // Transactions\n // -------------------------------------------------------------------------\n\n /**\n * Run a callback inside a database transaction. The callback receives a\n * transactional `TableClient` instance with the same shape as the\n * top-level client — write code identically inside or outside.\n *\n * Throwing from the callback rolls back; returning a value commits.\n *\n * @example\n * ```ts\n * await files.client.transaction(async (tx) => {\n * const row = await tx.insert(values)\n * await adapter.upload(row.key, buffer) // if this throws, the row is rolled back\n * return row\n * })\n * ```\n */\n async transaction<T>(fn: (tx: TableClient<TCols>) => Promise<T>): Promise<T> {\n return await this.db.transaction(async (innerDb) => {\n const txClient = new TableClient<TCols>(\n this.table,\n this.columnKinds,\n innerDb as unknown as PostgresJsDatabase,\n )\n return await fn(txClient)\n })\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n private buildWhereOrThrow(where: WhereClause<TCols>) {\n try {\n const condition = buildWhere(this.table, this.columnKinds, where)\n if (!condition) {\n throw new TableClientError('where clause produced no SQL predicate')\n }\n return condition\n } catch (err) {\n if (err instanceof WhereBuilderError) {\n throw new TableClientError(err.message.replace(/^WhereBuilder: /, ''))\n }\n throw err\n }\n }\n\n private validateLimit(limit: number | undefined): number {\n if (limit === undefined) return DEFAULT_LIMIT\n if (!Number.isInteger(limit) || limit < 1) {\n throw new TableClientError(`limit must be a positive integer, got ${limit}`)\n }\n // Strict throw — *not* silent clamping. The previous behaviour\n // (clamp + console.warn) was the worst of both worlds: callers got\n // a quietly different result than they asked for, AND the warn\n // didn't go through the project logger. If a caller passes a limit\n // above MAX_LIMIT it's almost certainly a bug. Make them be\n // explicit: either pass a value within the cap, or refactor to\n // paginate / use claim() (PR 4) for batch processing.\n if (limit > MAX_LIMIT) {\n throw new TableClientError(\n `limit ${limit} exceeds MAX_LIMIT ${MAX_LIMIT} — paginate or use claim() for batch processing`,\n )\n }\n return limit\n }\n}\n","/**\n * Process-wide registry of all `defineTable()` calls.\n *\n * Used by:\n * - **Migration generation** — drizzle-kit reads schema files; the registry\n * lets the project's `drizzle.config.ts` discover all defined tables\n * without having to maintain a hand-edited list. Re-export\n * `tableRegistry.allTables()` from a barrel and reference that file in\n * `drizzle.config.ts → schema`.\n * - **Test setup** — `createTestDb().push()` accepts a record of Drizzle\n * tables; pass `tableRegistry.allTables()` to push every defined table\n * into a fresh test schema in one call.\n * - **Audits / introspection** — list all known infra tables for tooling.\n *\n * The registry is intentionally separate from `schemaRegistry` (which is\n * for entity-generated schemas) so the two layers stay decoupled. They\n * may be merged in a follow-up if it simplifies migration discovery.\n */\n\nimport type { PgTable } from 'drizzle-orm/pg-core'\n\ninterface RegistryEntry {\n name: string\n table: PgTable\n /** Source file (best-effort, captured at definition time for debugging). */\n source?: string\n}\n\nclass TableRegistryImpl {\n private byName: Map<string, RegistryEntry> = new Map()\n\n /**\n * Register a defined table. Throws if a table with the same name is\n * already registered (catches accidental double-registration from a\n * file being imported under two paths).\n */\n register(name: string, table: PgTable, source?: string): void {\n const existing = this.byName.get(name)\n if (existing) {\n // Same table object → idempotent re-import; allow silently.\n if (existing.table === table) return\n throw new Error(\n `tableRegistry: a different table is already registered under name \"${name}\". ` +\n `Did you call defineTable twice? First registered from ${existing.source ?? '<unknown>'}, ` +\n `now from ${source ?? '<unknown>'}.`,\n )\n }\n this.byName.set(name, { name, table, source })\n }\n\n /**\n * Get a defined table by name.\n */\n get(name: string): PgTable | undefined {\n return this.byName.get(name)?.table\n }\n\n /**\n * Returns true if a table with the given name has been registered.\n */\n has(name: string): boolean {\n return this.byName.has(name)\n }\n\n /**\n * Return every registered table as a `{ [name]: PgTable }` map.\n *\n * Useful for `createTestDb().push(tableRegistry.allTables())` and for\n * exposing a single import target to `drizzle.config.ts`.\n */\n allTables(): Record<string, PgTable> {\n const out: Record<string, PgTable> = {}\n for (const entry of this.byName.values()) {\n out[entry.name] = entry.table\n }\n return out\n }\n\n /**\n * Clear the registry. **Test-only.**\n *\n * Production code should never call this — clearing the registry\n * silently breaks migration discovery and any code path that looks\n * up a table by name. The runtime guard below refuses to run when\n * `NODE_ENV === 'production'` to catch accidental misuse.\n *\n * Test runners typically set `NODE_ENV=test` (Vitest does), but the\n * guard is permissive about other values — it only blocks the one\n * environment where calling it is definitely wrong.\n */\n clear(): void {\n if (process.env.NODE_ENV === 'production') {\n throw new Error(\n 'tableRegistry.clear() is not allowed when NODE_ENV=production — ' +\n 'this is a test-only utility. If you reached this from production ' +\n 'code, you have a bug.',\n )\n }\n this.byName.clear()\n }\n}\n\n/**\n * Process-wide table registry singleton.\n *\n * Tables register themselves on import via `defineTable()`. The registry\n * is module-level state, which means a single Node process sees one\n * consistent view of all defined tables across packages.\n */\nexport const tableRegistry = new TableRegistryImpl()\n\n/**\n * Create an isolated registry — useful for unit tests that want to\n * verify registration behaviour without polluting the global registry.\n */\nexport function createTableRegistry() {\n return new TableRegistryImpl()\n}\n","/**\n * `defineTable()` — the entry point for the table layer.\n *\n * Given a {@link TableDefinition}, builds:\n *\n * - The underlying Drizzle `pgTable` (exposed for typed JOINs)\n * - A typed {@link TableClient} factory (callers wire in their `db` handle)\n * - The original schema metadata (for introspection / migrations)\n *\n * The table is also registered in the process-wide {@link tableRegistry}\n * so migration tooling and test setup can discover it.\n *\n * @example\n * ```ts\n * import { column, defineTable } from '@murumets-ee/db'\n *\n * export const ticketReadState = defineTable({\n * name: 'ticketing_read_state',\n * columns: {\n * ticketId: column.uuid({ notNull: true }),\n * userId: column.varchar({ length: 255, notNull: true }),\n * lastReadAt: column.timestamp({ notNull: true, defaultNow: true, withTimezone: true }),\n * },\n * primaryKey: ['ticketId', 'userId'],\n * indexes: [{ on: ['userId'] }],\n * })\n *\n * // Use the typed Drizzle table for migrations / typed JOINs:\n * export const ticketReadStateTable = ticketReadState.table\n *\n * // Build a client by wiring in a db handle:\n * const client = ticketReadState.makeClient(db)\n * await client.upsert(...) // (upsert lands in PR 3)\n * ```\n */\n\nimport {\n index as drizzleIndex,\n type PgColumnBuilderBase,\n pgTable,\n primaryKey as drizzlePrimaryKey,\n type PgTable,\n unique as drizzleUnique,\n} from 'drizzle-orm/pg-core'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport { TableClient } from './client.js'\nimport type { ColumnFactory, ColumnKind, TableDefinition } from './types.js'\nimport { tableRegistry } from './registry.js'\n\n/** Allowed shape for table names. Snake_case, leading lowercase letter. */\nconst TABLE_NAME_RE = /^[a-z][a-z0-9_]*$/\n\n/**\n * Postgres' default identifier length limit (NAMEDATALEN - 1).\n *\n * Postgres silently truncates identifiers longer than this — which can\n * cause auto-generated constraint/index names to collide. We enforce\n * the same limit at definition time so the failure is loud and\n * caller-controllable.\n *\n * Bumping this requires a custom Postgres build (NAMEDATALEN), which\n * no production setup we ship has.\n */\nconst PG_IDENTIFIER_MAX = 63\n\n/**\n * Check that a Postgres identifier (table/column/constraint/index name)\n * fits in the 63-char limit, throwing with a useful error otherwise.\n *\n * The `kind` argument is for error messages only — e.g. \"table name\",\n * \"auto-generated index name\". The `suggestion` is the auto-generated\n * name's caller-facing alternative (\"supply an explicit `name`\").\n */\nfunction assertIdentifierLength(\n identifier: string,\n kind: string,\n suggestion?: string,\n): void {\n if (identifier.length > PG_IDENTIFIER_MAX) {\n throw new Error(\n `defineTable: ${kind} \"${identifier}\" is ${identifier.length} characters — ` +\n `Postgres truncates identifiers at ${PG_IDENTIFIER_MAX} chars silently, ` +\n `which can cause name collisions${suggestion ? `. ${suggestion}` : ''}`,\n )\n }\n}\n\n/**\n * The result of `defineTable()`.\n *\n * @template TCols The columns record passed to `defineTable`.\n */\nexport interface DefinedTable<TCols extends Record<string, ColumnFactory>> {\n /**\n * The underlying Drizzle `pgTable`. Exposed so callers can use it in\n * typed JOINs (the one sanctioned escape valve), and so it can be\n * referenced from `drizzle.config.ts` for migration generation.\n */\n table: PgTable\n /**\n * The original schema metadata. Useful for introspection / tooling\n * that needs to know about indexes, unique constraints, etc.\n */\n schema: TableDefinition<TCols>\n /**\n * Per-column kind metadata (column name → kind tag). Used internally\n * by the where-builder for jsonb path validation.\n */\n columnKinds: Readonly<Record<string, ColumnKind>>\n /**\n * Build a typed CRUD client wired to the given database handle.\n *\n * Callers typically construct one client per logical \"module\" and\n * cache it. The client is stateless aside from the db handle, so it\n * is safe to share.\n */\n makeClient(db: PostgresJsDatabase): TableClient<TCols>\n}\n\n/**\n * Define a typed table.\n *\n * The function signature uses two const-generic parameters to preserve\n * column literal types end-to-end. This is what enables `Row`,\n * `InsertRow`, and `WhereClause` to know exact column shapes at the call\n * site.\n *\n * @throws if the table name doesn't match the snake_case regex, or if\n * the same name is registered twice with different table objects.\n */\nexport function defineTable<const TCols extends Record<string, ColumnFactory>>(\n def: TableDefinition<TCols>,\n): DefinedTable<TCols> {\n // Validate the table name shape — fail loudly at definition time rather\n // than letting Postgres catch it at migration time.\n if (!TABLE_NAME_RE.test(def.name)) {\n throw new Error(\n `defineTable: invalid table name \"${def.name}\" — must match /^[a-z][a-z0-9_]*$/`,\n )\n }\n assertIdentifierLength(def.name, 'table name')\n\n // Build the per-column drizzle column builders by invoking each factory\n // with its property name. The factory contract requires the produced\n // column to be a `PgColumnBuilderBase` ready for `pgTable(...)`.\n const drizzleCols: Record<string, PgColumnBuilderBase> = {}\n const columnKinds: Record<string, ColumnKind> = {}\n for (const [name, factory] of Object.entries(def.columns)) {\n if (typeof factory !== 'function') {\n throw new Error(\n `defineTable: column \"${name}\" is not a column factory — did you call e.g. column.uuid() with parens?`,\n )\n }\n assertIdentifierLength(name, `column name in table \"${def.name}\"`)\n drizzleCols[name] = factory(name)\n columnKinds[name] = factory.__kind\n }\n\n // Validate that all constraint references point at known columns BEFORE\n // building the pgTable. The pgTable constraint callback may not surface\n // errors at construction time, so we check eagerly here for fast failure\n // and consistent error messages.\n const knownColumns = new Set(Object.keys(drizzleCols))\n if (def.primaryKey !== undefined) {\n const pkCols = Array.isArray(def.primaryKey) ? def.primaryKey : [def.primaryKey]\n for (const k of pkCols) {\n if (!knownColumns.has(String(k))) {\n throw new Error(`defineTable: primaryKey references unknown column \"${String(k)}\"`)\n }\n }\n }\n if (def.unique) {\n for (const uq of def.unique) {\n for (const k of uq.on) {\n if (!knownColumns.has(String(k))) {\n throw new Error(\n `defineTable: unique constraint references unknown column \"${String(k)}\"`,\n )\n }\n }\n const name = uq.name ?? `uq_${def.name}_${uq.on.map(String).join('_')}`\n assertIdentifierLength(\n name,\n uq.name ? 'unique constraint name' : 'auto-generated unique constraint name',\n uq.name\n ? undefined\n : 'Supply a shorter explicit `name` to the unique definition.',\n )\n }\n }\n if (def.indexes) {\n for (const ix of def.indexes) {\n for (const k of ix.on) {\n if (!knownColumns.has(String(k))) {\n throw new Error(`defineTable: index references unknown column \"${String(k)}\"`)\n }\n }\n const name = ix.name ?? `idx_${def.name}_${ix.on.map(String).join('_')}`\n assertIdentifierLength(\n name,\n ix.name ? 'index name' : 'auto-generated index name',\n ix.name ? undefined : 'Supply a shorter explicit `name` to the index definition.',\n )\n }\n }\n\n // Build the constraint callback for indexes / unique / primary key.\n // pgTable accepts an optional callback that returns a constraint object.\n const hasConstraints =\n def.primaryKey !== undefined ||\n (def.unique && def.unique.length > 0) ||\n (def.indexes && def.indexes.length > 0)\n\n const table: PgTable = hasConstraints\n ? pgTable(def.name, drizzleCols, (t) => {\n // biome-ignore lint/suspicious/noExplicitAny: drizzle pgTable callback returns dynamic constraint records\n const constraints: Record<string, any> = {}\n\n // Composite primary key\n if (def.primaryKey !== undefined) {\n const pkCols = Array.isArray(def.primaryKey) ? def.primaryKey : [def.primaryKey]\n const cols = pkCols.map((k) => {\n const col = (t as unknown as Record<string, unknown>)[k as string]\n if (!col) {\n throw new Error(`defineTable: primaryKey references unknown column \"${String(k)}\"`)\n }\n return col\n })\n // Single-column PKs are usually declared at the column level via\n // column.uuid({ primaryKey: true }), but we allow them here too\n // for symmetry with composite PKs.\n // biome-ignore lint/suspicious/noExplicitAny: composite PK columns are heterogeneously typed\n constraints.pk = drizzlePrimaryKey({ columns: cols as any })\n }\n\n // Unique constraints\n if (def.unique) {\n for (const uq of def.unique) {\n const cols = uq.on.map((k: keyof TCols) => {\n const col = (t as unknown as Record<string, unknown>)[k as string]\n if (!col) {\n throw new Error(\n `defineTable: unique constraint references unknown column \"${String(k)}\"`,\n )\n }\n return col\n })\n const name =\n uq.name ?? `uq_${def.name}_${uq.on.map(String).join('_')}`\n // biome-ignore lint/suspicious/noExplicitAny: composite unique columns are heterogeneously typed\n constraints[name] = drizzleUnique(name).on(...(cols as [any, ...any[]]))\n }\n }\n\n // Indexes\n if (def.indexes) {\n for (const ix of def.indexes) {\n const cols = ix.on.map((k) => {\n const col = (t as unknown as Record<string, unknown>)[k as string]\n if (!col) {\n throw new Error(\n `defineTable: index references unknown column \"${String(k)}\"`,\n )\n }\n return col\n })\n const name = ix.name ?? `idx_${def.name}_${ix.on.map(String).join('_')}`\n // biome-ignore lint/suspicious/noExplicitAny: composite index columns are heterogeneously typed\n constraints[name] = drizzleIndex(name).on(...(cols as [any, ...any[]]))\n }\n }\n\n return constraints\n })\n : pgTable(def.name, drizzleCols)\n\n // Capture caller location for nicer double-registration errors. Best-\n // effort — falls back to undefined if stack parsing fails.\n const source = captureCallerLocation()\n tableRegistry.register(def.name, table, source)\n\n const frozenKinds: Readonly<Record<string, ColumnKind>> = Object.freeze({ ...columnKinds })\n\n return {\n table,\n schema: def,\n columnKinds: frozenKinds,\n makeClient: (db: PostgresJsDatabase) => new TableClient<TCols>(table, frozenKinds, db),\n }\n}\n\n/**\n * Best-effort: walk the stack to find the first non-internal frame so\n * double-registration errors point at the user's call site instead of\n * deep into `defineTable`.\n */\nfunction captureCallerLocation(): string | undefined {\n const stack = new Error().stack\n if (!stack) return undefined\n const lines = stack.split('\\n').slice(1)\n for (const line of lines) {\n if (line.includes('/table/define.') || line.includes('/table/registry.')) continue\n const match = line.match(/\\(([^)]+)\\)/) ?? line.match(/at (.+)$/)\n if (match) return match[1].trim()\n }\n return undefined\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@murumets-ee/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsdown",
|
|
31
31
|
"dev": "tsdown --watch",
|
|
32
|
-
"test": "vitest"
|
|
32
|
+
"test": "vitest",
|
|
33
|
+
"test:integration": "vitest run --config vitest.integration.config.ts"
|
|
33
34
|
}
|
|
34
35
|
}
|