@murumets-ee/db 0.1.5 → 0.3.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 +876 -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,6 @@
|
|
|
1
1
|
import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
-
import {
|
|
2
|
+
import { SQL } from "drizzle-orm";
|
|
3
|
+
import { AnyPgColumn, PgColumnBuilderBase, PgTable, PgTableWithColumns, TableConfig } from "drizzle-orm/pg-core";
|
|
3
4
|
|
|
4
5
|
//#region src/client.d.ts
|
|
5
6
|
interface DbConfig {
|
|
@@ -19,9 +20,35 @@ declare function createDbClient(config: DbConfig): PostgresJsDatabase;
|
|
|
19
20
|
declare function createReadOnlyClient(config: DbConfig): PostgresJsDatabase;
|
|
20
21
|
//#endregion
|
|
21
22
|
//#region src/migrate.d.ts
|
|
23
|
+
/**
|
|
24
|
+
* Arguments passed to migration `up` / `down` functions.
|
|
25
|
+
*
|
|
26
|
+
* Intentionally tiny: we hand over a read-write Drizzle handle bound to
|
|
27
|
+
* the current transaction. If a migration needs anything beyond raw SQL
|
|
28
|
+
* (e.g. calling an AdminClient for a data backfill), it can import the
|
|
29
|
+
* relevant toolkit helpers inside the migration file — the CLI loader
|
|
30
|
+
* runs the migration in the same process as the config, so all package
|
|
31
|
+
* imports resolve normally.
|
|
32
|
+
*/
|
|
33
|
+
interface MigrateArgs {
|
|
34
|
+
db: PostgresJsDatabase;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Shape of a migration module loaded from a `.ts` file.
|
|
38
|
+
*
|
|
39
|
+
* At minimum a migration must export `up`. `down` is optional — when
|
|
40
|
+
* absent, `lumi migrate:rollback` refuses to roll that file back.
|
|
41
|
+
*/
|
|
42
|
+
interface MigrationModule {
|
|
43
|
+
up: (args: MigrateArgs) => Promise<void>;
|
|
44
|
+
down?: (args: MigrateArgs) => Promise<void>;
|
|
45
|
+
}
|
|
22
46
|
interface MigrationSource {
|
|
47
|
+
/** Absolute path to the migration file on disk. */
|
|
23
48
|
path: string;
|
|
49
|
+
/** `project` for user migrations, `toolkit` for shipped-with-toolkit ones. */
|
|
24
50
|
namespace: 'toolkit' | 'project';
|
|
51
|
+
/** Filename only, used for ordering + tracking. */
|
|
25
52
|
name: string;
|
|
26
53
|
}
|
|
27
54
|
interface MigrationStatus {
|
|
@@ -29,17 +56,48 @@ interface MigrationStatus {
|
|
|
29
56
|
pending: MigrationSource[];
|
|
30
57
|
}
|
|
31
58
|
/**
|
|
32
|
-
*
|
|
59
|
+
* Loader callback — imports a migration `.ts` file and returns its exports.
|
|
60
|
+
*
|
|
61
|
+
* The CLI owns `jiti` (already used to load `toolkit.config.ts`), so
|
|
62
|
+
* `@murumets-ee/db` stays free of TS-runtime loader dependencies.
|
|
63
|
+
* Callers who never apply migrations (read-only servers, tests) never
|
|
64
|
+
* pay the loader cost.
|
|
33
65
|
*/
|
|
34
|
-
|
|
66
|
+
type MigrationLoader = (absolutePath: string) => Promise<MigrationModule>;
|
|
35
67
|
/**
|
|
36
|
-
*
|
|
68
|
+
* Discover all migration files under `projectRoot/migrations`.
|
|
69
|
+
*
|
|
70
|
+
* Layout:
|
|
71
|
+
* migrations/
|
|
72
|
+
* 20260411_180000_add_articles.ts ← user migrations (sort chronologically)
|
|
73
|
+
* 20260411_180000_add_articles.snapshot.json
|
|
74
|
+
* .toolkit/
|
|
75
|
+
* 0001_initial_schema.ts ← toolkit-owned migrations (sort numerically)
|
|
37
76
|
*/
|
|
77
|
+
declare function discoverMigrations(projectRoot: string): Promise<MigrationSource[]>;
|
|
78
|
+
/** Split discovered migrations into applied vs pending by consulting the tracking table. */
|
|
38
79
|
declare function getMigrationStatus(db: PostgresJsDatabase, projectRoot: string): Promise<MigrationStatus>;
|
|
39
80
|
/**
|
|
40
|
-
*
|
|
81
|
+
* Apply every pending migration in order.
|
|
82
|
+
*
|
|
83
|
+
* Each file runs inside its own transaction: the migration's `up()`
|
|
84
|
+
* executes first, then the `_toolkit_migrations` insert commits. If
|
|
85
|
+
* `up()` throws, the transaction rolls back and the whole command
|
|
86
|
+
* aborts — nothing gets half-applied.
|
|
87
|
+
*
|
|
88
|
+
* The `loader` callback is injected so `@murumets-ee/db` itself doesn't
|
|
89
|
+
* depend on a TS-runtime loader. Callers pass a jiti-backed loader from
|
|
90
|
+
* the CLI or their own equivalent.
|
|
91
|
+
*/
|
|
92
|
+
declare function runMigrations(db: PostgresJsDatabase, projectRoot: string, loader: MigrationLoader): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Roll back the most recent applied migration (or N most recent).
|
|
95
|
+
*
|
|
96
|
+
* Each file's `down()` runs in its own transaction. Files without a
|
|
97
|
+
* `down` export cause the rollback to abort before running anything —
|
|
98
|
+
* we never roll back some but not all of a requested batch.
|
|
41
99
|
*/
|
|
42
|
-
declare function
|
|
100
|
+
declare function rollbackMigrations(db: PostgresJsDatabase, projectRoot: string, loader: MigrationLoader, count?: number): Promise<void>;
|
|
43
101
|
//#endregion
|
|
44
102
|
//#region src/schema-registry.d.ts
|
|
45
103
|
/**
|
|
@@ -74,5 +132,816 @@ declare const schemaRegistry: SchemaRegistryImpl;
|
|
|
74
132
|
*/
|
|
75
133
|
declare function createSchemaRegistry(): SchemaRegistry;
|
|
76
134
|
//#endregion
|
|
77
|
-
|
|
135
|
+
//#region src/table/types.d.ts
|
|
136
|
+
/**
|
|
137
|
+
* The discriminating "kind" tag for a column factory.
|
|
138
|
+
*
|
|
139
|
+
* Used at the type level to gate features that only make sense for certain
|
|
140
|
+
* kinds — e.g. only `'jsonb'` columns accept dotted-path keys in the
|
|
141
|
+
* where-builder.
|
|
142
|
+
*/
|
|
143
|
+
type ColumnKind = 'uuid' | 'varchar' | 'text' | 'integer' | 'bigint' | 'double' | 'boolean' | 'timestamp' | 'jsonb' | 'uuidArray';
|
|
144
|
+
/**
|
|
145
|
+
* Internal column factory.
|
|
146
|
+
*
|
|
147
|
+
* A `ColumnFactory` is a callable that, given a column name, produces the
|
|
148
|
+
* corresponding Drizzle column builder. The phantom type parameters
|
|
149
|
+
* (`__type`, `__kind`, `__notNull`) are used purely for compile-time
|
|
150
|
+
* inference of {@link Row} and {@link InsertRow} types — they have no runtime
|
|
151
|
+
* representation beyond a small metadata object.
|
|
152
|
+
*
|
|
153
|
+
* Users do not construct `ColumnFactory` values directly; they use the
|
|
154
|
+
* `column.*` builders from `./columns.ts`.
|
|
155
|
+
*/
|
|
156
|
+
interface ColumnFactory<TType = unknown, TKind extends ColumnKind = ColumnKind, TNotNull extends boolean = boolean, THasDefault extends boolean = boolean> {
|
|
157
|
+
/** Build the underlying Drizzle column for the given column name. */
|
|
158
|
+
(name: string): PgColumnBuilderBase;
|
|
159
|
+
readonly __type: TType;
|
|
160
|
+
readonly __kind: TKind;
|
|
161
|
+
readonly __notNull: TNotNull;
|
|
162
|
+
readonly __hasDefault: THasDefault;
|
|
163
|
+
/**
|
|
164
|
+
* Optional explicit Postgres column name. When set, the column uses this
|
|
165
|
+
* name in the database instead of the JS property name. Needed when porting
|
|
166
|
+
* existing tables that use snake_case column names to `defineTable`.
|
|
167
|
+
*/
|
|
168
|
+
readonly __pgName?: string;
|
|
169
|
+
/** True when the column is declared as a primary key at the column level. */
|
|
170
|
+
readonly __primaryKey?: boolean;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Convenience helper: extract the JS value type for a single column.
|
|
174
|
+
*
|
|
175
|
+
* - `notNull: true` → `T`
|
|
176
|
+
* - `notNull: false` → `T | null`
|
|
177
|
+
*/
|
|
178
|
+
type ColumnValue<C> = C extends ColumnFactory<infer T, ColumnKind, infer N> ? N extends true ? T : T | null : never;
|
|
179
|
+
/**
|
|
180
|
+
* Map a columns record to its row shape (the result of a SELECT).
|
|
181
|
+
*/
|
|
182
|
+
type Row<TCols extends Record<string, ColumnFactory>> = { [K in keyof TCols]: ColumnValue<TCols[K]> };
|
|
183
|
+
/**
|
|
184
|
+
* Map a columns record to its insert shape.
|
|
185
|
+
*
|
|
186
|
+
* - Required columns: must be supplied unless they have a default
|
|
187
|
+
* - Nullable columns: optional, accept `null`
|
|
188
|
+
* - Columns with defaults (`defaultRandom`, `defaultNow`, explicit `default`): optional
|
|
189
|
+
*/
|
|
190
|
+
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]> };
|
|
191
|
+
/**
|
|
192
|
+
* Operator object for a single column.
|
|
193
|
+
*
|
|
194
|
+
* Each property is a comparison operator. Combine with `$and` / `$or` /
|
|
195
|
+
* `$not` at the parent level to build composite predicates.
|
|
196
|
+
*
|
|
197
|
+
* `ilike` and `startsWith` accept user-supplied strings — the table client
|
|
198
|
+
* automatically escapes `%` and `_` in the value before wrapping with
|
|
199
|
+
* pattern wildcards, so callers cannot inject pattern characters by accident.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```ts
|
|
203
|
+
* { status: 'open' } // shorthand for { eq: 'open' }
|
|
204
|
+
* { status: { in: ['open', 'pending'] } }
|
|
205
|
+
* { createdAt: { gte: new Date('2026-01-01') } }
|
|
206
|
+
* { name: { ilike: 'foo%' } } // % escaped from value, only outer wildcards work
|
|
207
|
+
* { description: { isNull: true } }
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
type ColumnOperators<T> = T | null | {
|
|
211
|
+
eq: T;
|
|
212
|
+
} | {
|
|
213
|
+
ne: T;
|
|
214
|
+
} | {
|
|
215
|
+
in: T[];
|
|
216
|
+
} | {
|
|
217
|
+
notIn: T[];
|
|
218
|
+
} | {
|
|
219
|
+
gt: T;
|
|
220
|
+
} | {
|
|
221
|
+
gte: T;
|
|
222
|
+
} | {
|
|
223
|
+
lt: T;
|
|
224
|
+
} | {
|
|
225
|
+
lte: T;
|
|
226
|
+
} | {
|
|
227
|
+
isNull: true;
|
|
228
|
+
} | {
|
|
229
|
+
isNotNull: true;
|
|
230
|
+
} | {
|
|
231
|
+
ilike: string;
|
|
232
|
+
} | {
|
|
233
|
+
startsWith: string;
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* The full WhereClause type.
|
|
237
|
+
*
|
|
238
|
+
* Keys are either column names or dotted-path keys for jsonb columns
|
|
239
|
+
* (e.g. `'metadata.author.name'`). Values are operator objects per
|
|
240
|
+
* {@link ColumnOperators}.
|
|
241
|
+
*
|
|
242
|
+
* Combine with `$and` / `$or` / `$not` for composite predicates. All
|
|
243
|
+
* top-level keys are implicitly AND-ed together.
|
|
244
|
+
*
|
|
245
|
+
* The type intentionally accepts `string` for jsonb-path keys rather than
|
|
246
|
+
* a precisely-narrowed template literal, because the path's nested type is
|
|
247
|
+
* unknown to the column definition. Path segments are validated at runtime
|
|
248
|
+
* against `/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\d+$/` and the parent column must
|
|
249
|
+
* be a `jsonb` kind, otherwise the where-builder throws.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```ts
|
|
253
|
+
* // Simple equality
|
|
254
|
+
* { userId: 'u_123', status: 'active' }
|
|
255
|
+
*
|
|
256
|
+
* // Composite with $or
|
|
257
|
+
* { $or: [
|
|
258
|
+
* { status: 'open' },
|
|
259
|
+
* { status: 'pending', assignee: { isNull: true } },
|
|
260
|
+
* ] }
|
|
261
|
+
*
|
|
262
|
+
* // jsonb path access (column 'metadata' must be jsonb)
|
|
263
|
+
* { 'metadata.author.name': { ilike: 'alice%' } }
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
type WhereClause<TCols extends Record<string, ColumnFactory>> = { [K in keyof TCols]?: ColumnOperators<ColumnValue<TCols[K]>> } & {
|
|
267
|
+
[key: `${string}.${string}`]: ColumnOperators<unknown>;
|
|
268
|
+
} & {
|
|
269
|
+
$and?: WhereClause<TCols>[];
|
|
270
|
+
$or?: WhereClause<TCols>[];
|
|
271
|
+
$not?: WhereClause<TCols>;
|
|
272
|
+
};
|
|
273
|
+
/**
|
|
274
|
+
* Index definition for a table.
|
|
275
|
+
*
|
|
276
|
+
* Indexes are declared at the table level (not per-column) so composite
|
|
277
|
+
* indexes are first-class. Each index gets an auto-generated name unless
|
|
278
|
+
* `name` is supplied: `idx_<table>_<col1>_<col2>`.
|
|
279
|
+
*/
|
|
280
|
+
interface IndexDefinition<TCols extends Record<string, ColumnFactory>> {
|
|
281
|
+
on: (keyof TCols)[];
|
|
282
|
+
name?: string;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Unique constraint definition.
|
|
286
|
+
*
|
|
287
|
+
* Composite uniques (multi-column) are declared here. Single-column
|
|
288
|
+
* uniqueness is also supported here rather than as a column-level option,
|
|
289
|
+
* to keep the column builder API uniform.
|
|
290
|
+
*/
|
|
291
|
+
interface UniqueDefinition<TCols extends Record<string, ColumnFactory>> {
|
|
292
|
+
on: (keyof TCols)[];
|
|
293
|
+
name?: string;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* The shape passed to `defineTable`.
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* ```ts
|
|
300
|
+
* defineTable({
|
|
301
|
+
* name: 'ticket_read_state',
|
|
302
|
+
* columns: {
|
|
303
|
+
* ticketId: column.uuid({ notNull: true }),
|
|
304
|
+
* userId: column.varchar({ length: 255, notNull: true }),
|
|
305
|
+
* lastReadAt: column.timestamp({ notNull: true, defaultNow: true, withTimezone: true }),
|
|
306
|
+
* },
|
|
307
|
+
* primaryKey: ['ticketId', 'userId'],
|
|
308
|
+
* indexes: [{ on: ['userId'] }],
|
|
309
|
+
* })
|
|
310
|
+
* ```
|
|
311
|
+
*/
|
|
312
|
+
interface TableDefinition<TCols extends Record<string, ColumnFactory>> {
|
|
313
|
+
/**
|
|
314
|
+
* Postgres table name. Convention: snake_case, prefix with package name
|
|
315
|
+
* (e.g. `toolkit_jobs`, `ticketing_read_state`).
|
|
316
|
+
*
|
|
317
|
+
* Validated at definition time against `/^[a-z][a-z0-9_]*$/` to prevent
|
|
318
|
+
* accidental quoting issues and to enforce a consistent naming scheme.
|
|
319
|
+
*/
|
|
320
|
+
name: string;
|
|
321
|
+
columns: TCols;
|
|
322
|
+
/**
|
|
323
|
+
* Primary key column(s). May be a single column key or an array for
|
|
324
|
+
* composite keys.
|
|
325
|
+
*
|
|
326
|
+
* If omitted, the table has no primary key — only valid for tables that
|
|
327
|
+
* declare a column with `primaryKey: true` at the column level (e.g. via
|
|
328
|
+
* `column.uuid({ primaryKey: true, defaultRandom: true })`).
|
|
329
|
+
*/
|
|
330
|
+
primaryKey?: keyof TCols | (keyof TCols)[];
|
|
331
|
+
unique?: UniqueDefinition<TCols>[];
|
|
332
|
+
indexes?: IndexDefinition<TCols>[];
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/table/columns.d.ts
|
|
336
|
+
/**
|
|
337
|
+
* Type interface for the `column` builder namespace.
|
|
338
|
+
*
|
|
339
|
+
* Overloads discriminate on whether `default` is present, so
|
|
340
|
+
* `THasDefault` is inferred correctly:
|
|
341
|
+
*
|
|
342
|
+
* - `column.varchar({ length: 20, default: 'x' })` → `hasDefault = true`
|
|
343
|
+
* - `column.varchar({ length: 20 })` → `hasDefault = false`
|
|
344
|
+
*
|
|
345
|
+
* Object-literal methods don't support overloads in TypeScript, so we
|
|
346
|
+
* declare the overloaded signatures on an interface and cast the
|
|
347
|
+
* implementation object.
|
|
348
|
+
*/
|
|
349
|
+
interface ColumnBuilders {
|
|
350
|
+
uuid<TPrimaryKey extends boolean = false, TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {
|
|
351
|
+
primaryKey?: TPrimaryKey;
|
|
352
|
+
notNull?: TNotNull;
|
|
353
|
+
defaultRandom?: THasDefault;
|
|
354
|
+
pgName?: string;
|
|
355
|
+
}): ColumnFactory<string, 'uuid', TPrimaryKey extends true ? true : TNotNull extends true ? true : false, TPrimaryKey extends true ? true : THasDefault extends true ? true : false>;
|
|
356
|
+
varchar<TNotNull extends boolean = false>(opts: {
|
|
357
|
+
length: number;
|
|
358
|
+
notNull?: TNotNull;
|
|
359
|
+
default: string;
|
|
360
|
+
pgName?: string;
|
|
361
|
+
}): ColumnFactory<string, 'varchar', TNotNull extends true ? true : false, true>;
|
|
362
|
+
varchar<TNotNull extends boolean = false>(opts: {
|
|
363
|
+
length: number;
|
|
364
|
+
notNull?: TNotNull;
|
|
365
|
+
pgName?: string;
|
|
366
|
+
}): ColumnFactory<string, 'varchar', TNotNull extends true ? true : false, false>;
|
|
367
|
+
text<TNotNull extends boolean = false>(opts: {
|
|
368
|
+
notNull?: TNotNull;
|
|
369
|
+
default: string;
|
|
370
|
+
pgName?: string;
|
|
371
|
+
}): ColumnFactory<string, 'text', TNotNull extends true ? true : false, true>;
|
|
372
|
+
text<TNotNull extends boolean = false>(opts?: {
|
|
373
|
+
notNull?: TNotNull;
|
|
374
|
+
pgName?: string;
|
|
375
|
+
}): ColumnFactory<string, 'text', TNotNull extends true ? true : false, false>;
|
|
376
|
+
integer<TNotNull extends boolean = false>(opts: {
|
|
377
|
+
notNull?: TNotNull;
|
|
378
|
+
default: number;
|
|
379
|
+
pgName?: string;
|
|
380
|
+
}): ColumnFactory<number, 'integer', TNotNull extends true ? true : false, true>;
|
|
381
|
+
integer<TNotNull extends boolean = false>(opts?: {
|
|
382
|
+
notNull?: TNotNull;
|
|
383
|
+
pgName?: string;
|
|
384
|
+
}): ColumnFactory<number, 'integer', TNotNull extends true ? true : false, false>;
|
|
385
|
+
bigint<TMode extends 'number' | 'bigint' = 'number', TNotNull extends boolean = false>(opts: {
|
|
386
|
+
mode?: TMode;
|
|
387
|
+
notNull?: TNotNull;
|
|
388
|
+
default: TMode extends 'bigint' ? bigint : number;
|
|
389
|
+
pgName?: string;
|
|
390
|
+
}): ColumnFactory<TMode extends 'bigint' ? bigint : number, 'bigint', TNotNull extends true ? true : false, true>;
|
|
391
|
+
bigint<TMode extends 'number' | 'bigint' = 'number', TNotNull extends boolean = false>(opts?: {
|
|
392
|
+
mode?: TMode;
|
|
393
|
+
notNull?: TNotNull;
|
|
394
|
+
pgName?: string;
|
|
395
|
+
}): ColumnFactory<TMode extends 'bigint' ? bigint : number, 'bigint', TNotNull extends true ? true : false, false>;
|
|
396
|
+
double<TNotNull extends boolean = false>(opts: {
|
|
397
|
+
notNull?: TNotNull;
|
|
398
|
+
default: number;
|
|
399
|
+
pgName?: string;
|
|
400
|
+
}): ColumnFactory<number, 'double', TNotNull extends true ? true : false, true>;
|
|
401
|
+
double<TNotNull extends boolean = false>(opts?: {
|
|
402
|
+
notNull?: TNotNull;
|
|
403
|
+
pgName?: string;
|
|
404
|
+
}): ColumnFactory<number, 'double', TNotNull extends true ? true : false, false>;
|
|
405
|
+
boolean<TNotNull extends boolean = false>(opts: {
|
|
406
|
+
notNull?: TNotNull;
|
|
407
|
+
default: boolean;
|
|
408
|
+
pgName?: string;
|
|
409
|
+
}): ColumnFactory<boolean, 'boolean', TNotNull extends true ? true : false, true>;
|
|
410
|
+
boolean<TNotNull extends boolean = false>(opts?: {
|
|
411
|
+
notNull?: TNotNull;
|
|
412
|
+
pgName?: string;
|
|
413
|
+
}): ColumnFactory<boolean, 'boolean', TNotNull extends true ? true : false, false>;
|
|
414
|
+
timestamp<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {
|
|
415
|
+
notNull?: TNotNull;
|
|
416
|
+
defaultNow?: THasDefault;
|
|
417
|
+
withTimezone?: boolean;
|
|
418
|
+
pgName?: string;
|
|
419
|
+
}): ColumnFactory<Date, 'timestamp', TNotNull extends true ? true : false, THasDefault extends true ? true : false>;
|
|
420
|
+
jsonb<T = unknown, TNotNull extends boolean = false>(opts: {
|
|
421
|
+
notNull?: TNotNull;
|
|
422
|
+
default: T;
|
|
423
|
+
pgName?: string;
|
|
424
|
+
}): ColumnFactory<T, 'jsonb', TNotNull extends true ? true : false, true>;
|
|
425
|
+
jsonb<T = unknown, TNotNull extends boolean = false>(opts?: {
|
|
426
|
+
notNull?: TNotNull;
|
|
427
|
+
pgName?: string;
|
|
428
|
+
}): ColumnFactory<T, 'jsonb', TNotNull extends true ? true : false, false>;
|
|
429
|
+
uuidArray<TNotNull extends boolean = false>(opts?: {
|
|
430
|
+
notNull?: TNotNull;
|
|
431
|
+
pgName?: string;
|
|
432
|
+
}): ColumnFactory<string[], 'uuidArray', TNotNull extends true ? true : false, false>;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Column builder namespace — overloaded interface applied via single
|
|
436
|
+
* assertion. Each builder's implementation returns `makeFactory(...)` with
|
|
437
|
+
* widened boolean params; the `ColumnBuilders` overloads narrow them for
|
|
438
|
+
* callers based on whether `default`/`primaryKey` etc. are present.
|
|
439
|
+
*/
|
|
440
|
+
declare const column: ColumnBuilders;
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region src/table/client.d.ts
|
|
443
|
+
/**
|
|
444
|
+
* Default page size for `findMany` when no `limit` is supplied.
|
|
445
|
+
*
|
|
446
|
+
* Chosen as a safe upper bound for "show me a page of stuff" — anything
|
|
447
|
+
* above this should be paginated explicitly.
|
|
448
|
+
*/
|
|
449
|
+
declare const DEFAULT_LIMIT = 100;
|
|
450
|
+
/**
|
|
451
|
+
* Hard cap on `findMany.limit`. Caller-supplied values above this are
|
|
452
|
+
* clamped and a warning is emitted via `console.warn`.
|
|
453
|
+
*/
|
|
454
|
+
declare const MAX_LIMIT = 1000;
|
|
455
|
+
/**
|
|
456
|
+
* Hard cap on `insertMany.values.length` per single call.
|
|
457
|
+
*/
|
|
458
|
+
declare const MAX_BATCH = 1000;
|
|
459
|
+
/**
|
|
460
|
+
* A custom error thrown by the table client for any caller-side mistake
|
|
461
|
+
* (empty where on update/delete, batch too large, malformed where, …).
|
|
462
|
+
*
|
|
463
|
+
* Database errors are NOT wrapped — they propagate as-is from postgres-js
|
|
464
|
+
* so callers can match on Postgres error codes.
|
|
465
|
+
*/
|
|
466
|
+
declare class TableClientError extends Error {
|
|
467
|
+
constructor(message: string);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Order-by spec accepted by `findMany`.
|
|
471
|
+
*/
|
|
472
|
+
interface OrderBySpec<TCols extends Record<string, ColumnFactory>> {
|
|
473
|
+
column: keyof TCols & string;
|
|
474
|
+
dir?: 'asc' | 'desc';
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* `findMany` options.
|
|
478
|
+
*/
|
|
479
|
+
interface FindManyOptions<TCols extends Record<string, ColumnFactory>> {
|
|
480
|
+
where?: WhereClause<TCols>;
|
|
481
|
+
orderBy?: OrderBySpec<TCols>[];
|
|
482
|
+
limit?: number;
|
|
483
|
+
offset?: number;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Options for the `claim()` atomic locking operation.
|
|
487
|
+
*
|
|
488
|
+
* @see {@link TableClient.claim}
|
|
489
|
+
*/
|
|
490
|
+
interface ClaimOptions<TCols extends Record<string, ColumnFactory>> {
|
|
491
|
+
/** Filter for rows eligible to be claimed. Must be non-empty. */
|
|
492
|
+
where: WhereClause<TCols>;
|
|
493
|
+
/** Literal values to SET on claimed rows. */
|
|
494
|
+
set?: Partial<InsertRow<TCols>>;
|
|
495
|
+
/** Raw SQL expressions to SET (e.g. `{ attempts: sql\`attempts + 1\` }`). */
|
|
496
|
+
setSql?: Record<string, SQL>;
|
|
497
|
+
/** ORDER BY for the subselect — controls claim priority. */
|
|
498
|
+
orderBy?: OrderBySpec<TCols>[];
|
|
499
|
+
/** Maximum number of rows to claim. Must be 1..MAX_LIMIT. */
|
|
500
|
+
limit: number;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Specification for a single aggregate function.
|
|
504
|
+
*/
|
|
505
|
+
type AggregateSpec<TCols extends Record<string, ColumnFactory>> = {
|
|
506
|
+
fn: 'count';
|
|
507
|
+
} | {
|
|
508
|
+
fn: 'count';
|
|
509
|
+
column: keyof TCols & string;
|
|
510
|
+
} | {
|
|
511
|
+
fn: 'sum' | 'avg' | 'min' | 'max';
|
|
512
|
+
column: keyof TCols & string;
|
|
513
|
+
};
|
|
514
|
+
/**
|
|
515
|
+
* Options for the `aggregate()` method.
|
|
516
|
+
*/
|
|
517
|
+
interface AggregateOptions<TCols extends Record<string, ColumnFactory>> {
|
|
518
|
+
/** Named aggregate expressions. Each key becomes an output column. */
|
|
519
|
+
select: Record<string, AggregateSpec<TCols>>;
|
|
520
|
+
/** Columns to GROUP BY. Included automatically in the result. */
|
|
521
|
+
groupBy?: (keyof TCols & string)[];
|
|
522
|
+
/** Filter rows before aggregation (WHERE). */
|
|
523
|
+
where?: WhereClause<TCols>;
|
|
524
|
+
/** Order results. Can reference both grouped columns and aliases. */
|
|
525
|
+
orderBy?: OrderBySpec<TCols>[];
|
|
526
|
+
/** Limit the number of groups returned. */
|
|
527
|
+
limit?: number;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Generic CRUD client. Constructed by `defineTable` — never instantiated
|
|
531
|
+
* directly by callers.
|
|
532
|
+
*
|
|
533
|
+
* The same instance shape is exposed both at the top level (using the
|
|
534
|
+
* package's main `db` connection) and inside a `transaction()` callback
|
|
535
|
+
* (using the transactional `tx` handle), so user code is identical
|
|
536
|
+
* regardless of transactional context.
|
|
537
|
+
*
|
|
538
|
+
* @template TCols The columns record passed to `defineTable`.
|
|
539
|
+
*/
|
|
540
|
+
declare class TableClient<TCols extends Record<string, ColumnFactory>> {
|
|
541
|
+
/** The Drizzle table object. Exposed via `defineTable().table` for typed JOINs. */
|
|
542
|
+
readonly table: PgTable;
|
|
543
|
+
/** Per-column kind metadata. Used by the where-builder for jsonb path validation. */
|
|
544
|
+
private readonly columnKinds;
|
|
545
|
+
/**
|
|
546
|
+
* The active database handle. May be a top-level `PostgresJsDatabase`
|
|
547
|
+
* or a transaction handle of compatible shape — the client doesn't
|
|
548
|
+
* distinguish.
|
|
549
|
+
*/
|
|
550
|
+
private readonly db;
|
|
551
|
+
/** Primary key column names. Required by `claim()`. Empty for tables without a declared PK. */
|
|
552
|
+
private readonly primaryKeyColumns;
|
|
553
|
+
/**
|
|
554
|
+
* @internal — instances are created by `defineTable()`.
|
|
555
|
+
*/
|
|
556
|
+
constructor(/** The Drizzle table object. Exposed via `defineTable().table` for typed JOINs. */
|
|
557
|
+
|
|
558
|
+
table: PgTable, /** Per-column kind metadata. Used by the where-builder for jsonb path validation. */
|
|
559
|
+
|
|
560
|
+
columnKinds: Readonly<Record<string, ColumnKind>>,
|
|
561
|
+
/**
|
|
562
|
+
* The active database handle. May be a top-level `PostgresJsDatabase`
|
|
563
|
+
* or a transaction handle of compatible shape — the client doesn't
|
|
564
|
+
* distinguish.
|
|
565
|
+
*/
|
|
566
|
+
|
|
567
|
+
db: PostgresJsDatabase, /** Primary key column names. Required by `claim()`. Empty for tables without a declared PK. */
|
|
568
|
+
|
|
569
|
+
primaryKeyColumns?: readonly string[]);
|
|
570
|
+
/**
|
|
571
|
+
* Find a single row matching the given where clause, or `null` if no row
|
|
572
|
+
* matches. Throws if the where is empty.
|
|
573
|
+
*
|
|
574
|
+
* @example
|
|
575
|
+
* ```ts
|
|
576
|
+
* await readState.client.findOne({ ticketId: 't_1', userId: 'u_1' })
|
|
577
|
+
* ```
|
|
578
|
+
*/
|
|
579
|
+
findOne(where: WhereClause<TCols>): Promise<Row<TCols> | null>;
|
|
580
|
+
/**
|
|
581
|
+
* Find multiple rows. Pagination, ordering, and filtering are all
|
|
582
|
+
* optional but `limit` is hard-capped at {@link MAX_LIMIT}.
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* ```ts
|
|
586
|
+
* await jobs.client.findMany({
|
|
587
|
+
* where: { status: 'pending', runAt: { lte: new Date() } },
|
|
588
|
+
* orderBy: [{ column: 'priority', dir: 'desc' }, { column: 'runAt', dir: 'asc' }],
|
|
589
|
+
* limit: 25,
|
|
590
|
+
* })
|
|
591
|
+
* ```
|
|
592
|
+
*/
|
|
593
|
+
findMany(options?: FindManyOptions<TCols>): Promise<Row<TCols>[]>;
|
|
594
|
+
/**
|
|
595
|
+
* Count rows matching the where clause. Returns an exact count via
|
|
596
|
+
* `SELECT COUNT(*)`. For estimated/cached counts on large tables, see
|
|
597
|
+
* `countEstimate()` and `countCached()` (added in a follow-up PR).
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* ```ts
|
|
601
|
+
* const open = await jobs.client.count({ status: 'pending' })
|
|
602
|
+
* ```
|
|
603
|
+
*/
|
|
604
|
+
count(where?: WhereClause<TCols>): Promise<number>;
|
|
605
|
+
/**
|
|
606
|
+
* Returns `true` if at least one row matches the where clause. Always
|
|
607
|
+
* uses `LIMIT 1` and short-circuits — cheaper than `count() > 0` on
|
|
608
|
+
* large tables.
|
|
609
|
+
*/
|
|
610
|
+
exists(where: WhereClause<TCols>): Promise<boolean>;
|
|
611
|
+
/**
|
|
612
|
+
* Return unique non-null values for a single column.
|
|
613
|
+
*
|
|
614
|
+
* Null values are excluded by default — pass `includeNull: true` to
|
|
615
|
+
* include them. Results can be filtered with `where` and ordered with
|
|
616
|
+
* `orderBy` (ascending or descending on the distinct column).
|
|
617
|
+
*
|
|
618
|
+
* @example Get distinct entity types from the audit log
|
|
619
|
+
* ```ts
|
|
620
|
+
* const types = await audit.client.distinct('entityType', { orderBy: 'asc' })
|
|
621
|
+
* ```
|
|
622
|
+
*
|
|
623
|
+
* @example Distinct with a filter
|
|
624
|
+
* ```ts
|
|
625
|
+
* const actions = await audit.client.distinct('action', {
|
|
626
|
+
* where: { userId: 'u_1' },
|
|
627
|
+
* orderBy: 'asc',
|
|
628
|
+
* })
|
|
629
|
+
* ```
|
|
630
|
+
*/
|
|
631
|
+
distinct<K extends keyof TCols & string>(column: K, options?: {
|
|
632
|
+
where?: WhereClause<TCols>;
|
|
633
|
+
orderBy?: 'asc' | 'desc'; /** Include null values in the result. Defaults to `false`. */
|
|
634
|
+
includeNull?: boolean;
|
|
635
|
+
}): Promise<ColumnValue<TCols[K]>[]>;
|
|
636
|
+
/**
|
|
637
|
+
* Insert a single row. Returns the inserted row (server-generated
|
|
638
|
+
* defaults populated).
|
|
639
|
+
*/
|
|
640
|
+
insert(values: InsertRow<TCols>): Promise<Row<TCols>>;
|
|
641
|
+
/**
|
|
642
|
+
* Upsert: insert a row, or update an existing row if a unique constraint
|
|
643
|
+
* conflict occurs.
|
|
644
|
+
*
|
|
645
|
+
* `target` names the column(s) whose unique constraint should trigger
|
|
646
|
+
* the conflict path. `set` is the patch applied on conflict — when
|
|
647
|
+
* omitted, the conflict path applies the same `values` (i.e. "last
|
|
648
|
+
* write wins").
|
|
649
|
+
*
|
|
650
|
+
* `setWhere` adds a conditional WHERE to the conflict update path. If
|
|
651
|
+
* the condition does not match, the update is skipped and the existing
|
|
652
|
+
* row is returned unchanged (via a fallback SELECT). This enables
|
|
653
|
+
* patterns like "only update if the new version is higher than the
|
|
654
|
+
* existing one".
|
|
655
|
+
*
|
|
656
|
+
* @example Idempotent "mark as read" tracking
|
|
657
|
+
* ```ts
|
|
658
|
+
* await readState.client.upsert(
|
|
659
|
+
* { ticketId, userId, lastReadAt: new Date() },
|
|
660
|
+
* { target: ['ticketId', 'userId'] },
|
|
661
|
+
* )
|
|
662
|
+
* ```
|
|
663
|
+
*
|
|
664
|
+
* @example Conditional update — only bump if newer
|
|
665
|
+
* ```ts
|
|
666
|
+
* await state.client.upsert(
|
|
667
|
+
* { key: 'sync', version: 5, data: '...' },
|
|
668
|
+
* { target: 'key', setWhere: { version: { lt: 5 } } },
|
|
669
|
+
* )
|
|
670
|
+
* ```
|
|
671
|
+
*/
|
|
672
|
+
upsert(values: InsertRow<TCols>, opts: {
|
|
673
|
+
target: (keyof TCols & string) | (keyof TCols & string)[];
|
|
674
|
+
set?: Partial<InsertRow<TCols>>; /** Conditional WHERE on the conflict update path. Empty objects are ignored. */
|
|
675
|
+
setWhere?: WhereClause<TCols>;
|
|
676
|
+
}): Promise<Row<TCols>>;
|
|
677
|
+
/**
|
|
678
|
+
* Insert many rows in a single statement. Capped at {@link MAX_BATCH}.
|
|
679
|
+
* Returns all inserted rows in input order.
|
|
680
|
+
*/
|
|
681
|
+
insertMany(values: InsertRow<TCols>[]): Promise<Row<TCols>[]>;
|
|
682
|
+
/**
|
|
683
|
+
* Update **at most one** row matching the where clause. Returns the
|
|
684
|
+
* updated row, or `null` if no row matched.
|
|
685
|
+
*
|
|
686
|
+
* Throws `TableClientError` if:
|
|
687
|
+
* - the where is empty
|
|
688
|
+
* - the where matches more than one row
|
|
689
|
+
*
|
|
690
|
+
* The multi-row guard exists to prevent silent bulk updates from a
|
|
691
|
+
* mistakenly-broad where. If you genuinely want to update multiple
|
|
692
|
+
* rows in one call, use {@link updateMany}, which is explicit about
|
|
693
|
+
* its plurality.
|
|
694
|
+
*
|
|
695
|
+
* Implementation note: this issues a single `UPDATE … RETURNING` and
|
|
696
|
+
* inspects the returned row count *after* the fact. The extra rows
|
|
697
|
+
* over the wire are the cost of the safety check; the alternative
|
|
698
|
+
* (a SELECT round-trip first) would be more rows AND a race window.
|
|
699
|
+
* If the affected count is > 1 the update has *already* happened —
|
|
700
|
+
* the throw is to surface the bug, not to prevent it. Wrap in a
|
|
701
|
+
* transaction if you need atomic rollback.
|
|
702
|
+
*/
|
|
703
|
+
update(where: WhereClause<TCols>, patch: Partial<InsertRow<TCols>>): Promise<Row<TCols> | null>;
|
|
704
|
+
/**
|
|
705
|
+
* Update all rows matching the where clause. Returns every updated row.
|
|
706
|
+
* Throws if the where is empty — there is no "update everything" path.
|
|
707
|
+
*/
|
|
708
|
+
updateMany(where: WhereClause<TCols>, patch: Partial<InsertRow<TCols>>): Promise<Row<TCols>[]>;
|
|
709
|
+
/**
|
|
710
|
+
* Delete **at most one** row matching the where clause. Returns the
|
|
711
|
+
* deleted row, or `null` if no row matched.
|
|
712
|
+
*
|
|
713
|
+
* Throws `TableClientError` if:
|
|
714
|
+
* - the where is empty
|
|
715
|
+
* - the where matches more than one row
|
|
716
|
+
*
|
|
717
|
+
* Same multi-row safety guard as {@link update}. Use {@link deleteMany}
|
|
718
|
+
* for explicit bulk deletes.
|
|
719
|
+
*
|
|
720
|
+
* Implementation note: same as `update` — the delete has already
|
|
721
|
+
* happened by the time the throw fires. Wrap in a transaction if you
|
|
722
|
+
* need atomic rollback.
|
|
723
|
+
*/
|
|
724
|
+
delete(where: WhereClause<TCols>): Promise<Row<TCols> | null>;
|
|
725
|
+
/**
|
|
726
|
+
* Delete all rows matching the where clause. Returns every deleted row.
|
|
727
|
+
* Throws if the where is empty — there is no "delete everything" path.
|
|
728
|
+
*/
|
|
729
|
+
deleteMany(where: WhereClause<TCols>): Promise<Row<TCols>[]>;
|
|
730
|
+
/**
|
|
731
|
+
* Run a callback inside a database transaction. The callback receives a
|
|
732
|
+
* transactional `TableClient` instance with the same shape as the
|
|
733
|
+
* top-level client — write code identically inside or outside.
|
|
734
|
+
*
|
|
735
|
+
* Throwing from the callback rolls back; returning a value commits.
|
|
736
|
+
*
|
|
737
|
+
* @example
|
|
738
|
+
* ```ts
|
|
739
|
+
* await files.client.transaction(async (tx) => {
|
|
740
|
+
* const row = await tx.insert(values)
|
|
741
|
+
* await adapter.upload(row.key, buffer) // if this throws, the row is rolled back
|
|
742
|
+
* return row
|
|
743
|
+
* })
|
|
744
|
+
* ```
|
|
745
|
+
*/
|
|
746
|
+
transaction<T>(fn: (tx: TableClient<TCols>) => Promise<T>): Promise<T>;
|
|
747
|
+
/**
|
|
748
|
+
* Atomically claim rows using `FOR UPDATE SKIP LOCKED`.
|
|
749
|
+
*
|
|
750
|
+
* Selects rows matching `where`, locks them (skipping any already locked
|
|
751
|
+
* by another worker), updates them with `set`/`setSql`, and returns the
|
|
752
|
+
* claimed rows in a single atomic operation. This is the standard pattern
|
|
753
|
+
* for job queues, task distribution, and any producer-consumer table.
|
|
754
|
+
*
|
|
755
|
+
* Requires the table to have a declared primary key (either via
|
|
756
|
+
* `column.uuid({ primaryKey: true })` or `defineTable({ primaryKey })`.
|
|
757
|
+
*
|
|
758
|
+
* @example Job queue claim
|
|
759
|
+
* ```ts
|
|
760
|
+
* const jobs = await client.claim({
|
|
761
|
+
* where: { status: 'pending', runAt: { lte: new Date() } },
|
|
762
|
+
* set: { status: 'processing', lockedBy: workerId, lockedAt: new Date() },
|
|
763
|
+
* setSql: { attempts: sql`${jobsTable.table.attempts} + 1` },
|
|
764
|
+
* orderBy: [{ column: 'priority', dir: 'desc' }, { column: 'createdAt', dir: 'asc' }],
|
|
765
|
+
* limit: 5,
|
|
766
|
+
* })
|
|
767
|
+
* ```
|
|
768
|
+
*/
|
|
769
|
+
claim(options: ClaimOptions<TCols>): Promise<Row<TCols>[]>;
|
|
770
|
+
/**
|
|
771
|
+
* Run an aggregate query with GROUP BY.
|
|
772
|
+
*
|
|
773
|
+
* Each key in `select` is an output alias mapped to an aggregate
|
|
774
|
+
* specification. The `groupBy` columns are included automatically in
|
|
775
|
+
* the result. Returns typed rows with grouped column values plus the
|
|
776
|
+
* computed aggregates.
|
|
777
|
+
*
|
|
778
|
+
* @example Count jobs by status
|
|
779
|
+
* ```ts
|
|
780
|
+
* const rows = await client.aggregate({
|
|
781
|
+
* select: { count: { fn: 'count' } },
|
|
782
|
+
* groupBy: ['status'],
|
|
783
|
+
* })
|
|
784
|
+
* // → [{ status: 'pending', count: 12 }, { status: 'completed', count: 45 }]
|
|
785
|
+
* ```
|
|
786
|
+
*
|
|
787
|
+
* @example Average priority of open jobs
|
|
788
|
+
* ```ts
|
|
789
|
+
* const [row] = await client.aggregate({
|
|
790
|
+
* select: { avgPriority: { fn: 'avg', column: 'priority' } },
|
|
791
|
+
* where: { status: 'open' },
|
|
792
|
+
* })
|
|
793
|
+
* ```
|
|
794
|
+
*/
|
|
795
|
+
aggregate<TResult extends Record<string, unknown> = Record<string, unknown>>(options: AggregateOptions<TCols>): Promise<TResult[]>;
|
|
796
|
+
/**
|
|
797
|
+
* Fast approximate row count using Postgres `reltuples` statistics.
|
|
798
|
+
*
|
|
799
|
+
* Returns the estimated row count from `pg_class` — updated by
|
|
800
|
+
* `ANALYZE` and autovacuum. Much faster than `COUNT(*)` on large
|
|
801
|
+
* tables (no sequential scan), but may be stale by up to a few
|
|
802
|
+
* percent. Returns 0 for tables that have never been analyzed.
|
|
803
|
+
*
|
|
804
|
+
* Use `count()` when you need an exact number. Use `countEstimate()`
|
|
805
|
+
* for UI display, pagination hints, or "is this table large?" checks
|
|
806
|
+
* where ±5% accuracy is fine.
|
|
807
|
+
*
|
|
808
|
+
* @example
|
|
809
|
+
* ```ts
|
|
810
|
+
* const approx = await client.countEstimate()
|
|
811
|
+
* if (approx > 100_000) {
|
|
812
|
+
* // Switch to keyset pagination instead of offset
|
|
813
|
+
* }
|
|
814
|
+
* ```
|
|
815
|
+
*/
|
|
816
|
+
countEstimate(): Promise<number>;
|
|
817
|
+
/** Build a single aggregate SQL expression from a spec. */
|
|
818
|
+
private buildAggregateExpr;
|
|
819
|
+
private buildWhereOrThrow;
|
|
820
|
+
private validateLimit;
|
|
821
|
+
}
|
|
822
|
+
//#endregion
|
|
823
|
+
//#region src/table/define.d.ts
|
|
824
|
+
/**
|
|
825
|
+
* The result of `defineTable()`.
|
|
826
|
+
*
|
|
827
|
+
* @template TCols The columns record passed to `defineTable`.
|
|
828
|
+
*/
|
|
829
|
+
interface DefinedTable<TCols extends Record<string, ColumnFactory>> {
|
|
830
|
+
/**
|
|
831
|
+
* The underlying Drizzle `pgTable`. Exposed so callers can use it in
|
|
832
|
+
* typed JOINs (the one sanctioned escape valve), and so it can be
|
|
833
|
+
* included in `Plugin.tables` for migration discovery by `lumi migrate`.
|
|
834
|
+
*/
|
|
835
|
+
table: PgTableWithColumns<TableConfig>;
|
|
836
|
+
/**
|
|
837
|
+
* The original schema metadata. Useful for introspection / tooling
|
|
838
|
+
* that needs to know about indexes, unique constraints, etc.
|
|
839
|
+
*/
|
|
840
|
+
schema: TableDefinition<TCols>;
|
|
841
|
+
/**
|
|
842
|
+
* Per-column kind metadata (column name → kind tag). Used internally
|
|
843
|
+
* by the where-builder for jsonb path validation.
|
|
844
|
+
*/
|
|
845
|
+
columnKinds: Readonly<Record<string, ColumnKind>>;
|
|
846
|
+
/**
|
|
847
|
+
* Primary key column names (JS property names). Detected from either
|
|
848
|
+
* `schema.primaryKey` or column-level `{ primaryKey: true }`. Empty
|
|
849
|
+
* array if no PK is declared. Required by `claim()`.
|
|
850
|
+
*/
|
|
851
|
+
primaryKeyColumns: readonly string[];
|
|
852
|
+
/**
|
|
853
|
+
* Build a typed CRUD client wired to the given database handle.
|
|
854
|
+
*
|
|
855
|
+
* Callers typically construct one client per logical "module" and
|
|
856
|
+
* cache it. The client is stateless aside from the db handle, so it
|
|
857
|
+
* is safe to share.
|
|
858
|
+
*/
|
|
859
|
+
makeClient(db: PostgresJsDatabase): TableClient<TCols>;
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Define a typed table.
|
|
863
|
+
*
|
|
864
|
+
* The function signature uses two const-generic parameters to preserve
|
|
865
|
+
* column literal types end-to-end. This is what enables `Row`,
|
|
866
|
+
* `InsertRow`, and `WhereClause` to know exact column shapes at the call
|
|
867
|
+
* site.
|
|
868
|
+
*
|
|
869
|
+
* @throws if the table name doesn't match the snake_case regex, or if
|
|
870
|
+
* the same name is registered twice with different table objects.
|
|
871
|
+
*/
|
|
872
|
+
declare function defineTable<const TCols extends Record<string, ColumnFactory>>(def: TableDefinition<TCols>): DefinedTable<TCols>;
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/table/registry.d.ts
|
|
875
|
+
declare class TableRegistryImpl {
|
|
876
|
+
private byName;
|
|
877
|
+
/**
|
|
878
|
+
* Register a defined table. Throws if a table with the same name is
|
|
879
|
+
* already registered (catches accidental double-registration from a
|
|
880
|
+
* file being imported under two paths).
|
|
881
|
+
*/
|
|
882
|
+
register(name: string, table: PgTable, source?: string): void;
|
|
883
|
+
/**
|
|
884
|
+
* Get a defined table by name.
|
|
885
|
+
*/
|
|
886
|
+
get(name: string): PgTable | undefined;
|
|
887
|
+
/**
|
|
888
|
+
* Returns true if a table with the given name has been registered.
|
|
889
|
+
*/
|
|
890
|
+
has(name: string): boolean;
|
|
891
|
+
/**
|
|
892
|
+
* Return every registered table as a `{ [name]: PgTable }` map.
|
|
893
|
+
*
|
|
894
|
+
* Useful for `createTestDb().push(tableRegistry.allTables())` and
|
|
895
|
+
* runtime introspection.
|
|
896
|
+
*/
|
|
897
|
+
allTables(): Record<string, PgTable>;
|
|
898
|
+
/**
|
|
899
|
+
* Clear the registry. **Test-only.**
|
|
900
|
+
*
|
|
901
|
+
* Production code should never call this — clearing the registry
|
|
902
|
+
* silently breaks migration discovery and any code path that looks
|
|
903
|
+
* up a table by name. The runtime guard below refuses to run when
|
|
904
|
+
* `NODE_ENV === 'production'` to catch accidental misuse.
|
|
905
|
+
*
|
|
906
|
+
* Test runners typically set `NODE_ENV=test` (Vitest does), but the
|
|
907
|
+
* guard is permissive about other values — it only blocks the one
|
|
908
|
+
* environment where calling it is definitely wrong.
|
|
909
|
+
*/
|
|
910
|
+
clear(): void;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Process-wide table registry singleton.
|
|
914
|
+
*
|
|
915
|
+
* Tables register themselves on import via `defineTable()`. The registry
|
|
916
|
+
* is module-level state, which means a single Node process sees one
|
|
917
|
+
* consistent view of all defined tables across packages.
|
|
918
|
+
*/
|
|
919
|
+
declare const tableRegistry: TableRegistryImpl;
|
|
920
|
+
/**
|
|
921
|
+
* Create an isolated registry — useful for unit tests that want to
|
|
922
|
+
* verify registration behaviour without polluting the global registry.
|
|
923
|
+
*/
|
|
924
|
+
declare function createTableRegistry(): TableRegistryImpl;
|
|
925
|
+
//#endregion
|
|
926
|
+
//#region src/table/where-builder.d.ts
|
|
927
|
+
/**
|
|
928
|
+
* A custom error thrown by the where-builder for any caller-side mistake.
|
|
929
|
+
*
|
|
930
|
+
* Catching `WhereBuilderError` lets the table client surface a 400-style
|
|
931
|
+
* error to its caller without conflating it with database errors.
|
|
932
|
+
*/
|
|
933
|
+
declare class WhereBuilderError extends Error {
|
|
934
|
+
constructor(message: string);
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Returns true if a {@link WhereClause} produces a non-trivial filter (at
|
|
938
|
+
* least one column predicate or sub-clause). Useful for rejecting empty
|
|
939
|
+
* `where` arguments to `update`/`delete`/`updateMany`/`deleteMany`.
|
|
940
|
+
*
|
|
941
|
+
* Note: this does not check whether `buildWhere()` would actually emit SQL
|
|
942
|
+
* for the clause — it just checks that the user supplied something.
|
|
943
|
+
*/
|
|
944
|
+
declare function isNonEmptyWhere(clause: unknown): boolean;
|
|
945
|
+
//#endregion
|
|
946
|
+
export { type AnyPgColumn, type ClaimOptions, 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
947
|
//# sourceMappingURL=index.d.mts.map
|