palgres 0.0.0-stage → 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.
Files changed (50) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +2 -2
  3. package/dist/chunks/JJVO5DTA.js +2 -0
  4. package/dist/chunks/JZLQMXDV.js +2 -0
  5. package/dist/chunks/XZNL22V6.js +5 -0
  6. package/dist/dsl.js +2 -0
  7. package/dist/index.js +372 -0
  8. package/dist/testing.js +2 -0
  9. package/package.json +32 -4
  10. package/types/internal/src/data.d.ts +15 -0
  11. package/types/palgres/src/adopt.d.ts +17 -0
  12. package/types/palgres/src/connect.d.ts +37 -0
  13. package/types/palgres/src/dsl/columns.d.ts +388 -0
  14. package/types/palgres/src/dsl/foreign-key.d.ts +33 -0
  15. package/types/palgres/src/dsl/index.d.ts +11 -0
  16. package/types/palgres/src/dsl/policy.d.ts +181 -0
  17. package/types/palgres/src/dsl/raw.d.ts +43 -0
  18. package/types/palgres/src/dsl/schema.d.ts +288 -0
  19. package/types/palgres/src/dsl/to-model.d.ts +6 -0
  20. package/types/palgres/src/forget.d.ts +12 -0
  21. package/types/palgres/src/index.d.ts +15 -0
  22. package/types/palgres/src/introspect.d.ts +17 -0
  23. package/types/palgres/src/migrate.d.ts +189 -0
  24. package/types/palgres/src/model.d.ts +181 -0
  25. package/types/palgres/src/plan.d.ts +107 -0
  26. package/types/palgres/src/pull.d.ts +13 -0
  27. package/types/palgres/src/query/admission.d.ts +80 -0
  28. package/types/palgres/src/query/catalog.d.ts +50 -0
  29. package/types/palgres/src/query/client.d.ts +154 -0
  30. package/types/palgres/src/query/data-mark.d.ts +7 -0
  31. package/types/palgres/src/query/diagnostics.d.ts +76 -0
  32. package/types/palgres/src/query/driver.d.ts +299 -0
  33. package/types/palgres/src/query/env-gen.d.ts +14 -0
  34. package/types/palgres/src/query/errors.d.ts +168 -0
  35. package/types/palgres/src/query/guards.d.ts +62 -0
  36. package/types/palgres/src/query/index.d.ts +76 -0
  37. package/types/palgres/src/query/options.d.ts +75 -0
  38. package/types/palgres/src/query/relations.d.ts +15 -0
  39. package/types/palgres/src/query/repository.d.ts +42 -0
  40. package/types/palgres/src/query/request.d.ts +54 -0
  41. package/types/palgres/src/query/search.d.ts +55 -0
  42. package/types/palgres/src/query/surface.d.ts +9 -0
  43. package/types/palgres/src/query/tx-plan.d.ts +229 -0
  44. package/types/palgres/src/query/typed.d.ts +537 -0
  45. package/types/palgres/src/query/unique.d.ts +16 -0
  46. package/types/palgres/src/search-worker.d.ts +36 -0
  47. package/types/palgres/src/search.d.ts +107 -0
  48. package/types/palgres/src/testing/fake.d.ts +22 -0
  49. package/types/palgres/src/testing/index.d.ts +2 -0
  50. package/types/palgres/src/units.d.ts +24 -0
@@ -0,0 +1,537 @@
1
+ import type { ColIsOptionalOnInsert, ColumnBuilder, ColumnType, ColValue } from '../dsl/columns.js';
2
+ import { type SchemaDef, type TableDef } from '../dsl/schema.js';
3
+ import type { AtomicClient, DBClient, DBOps, RowLock, SearchOptions } from './client.js';
4
+ import type { Search } from '../model.js';
5
+ import { isColRef } from './guards.js';
6
+ import { type AtomicOptions, type InsertManyOptions, type Page, type PageNavigation } from './options.js';
7
+ import { type Materialized, type TxColumnExpr, type TxNow, type TxPlanHandle, type TxTable } from './tx-plan.js';
8
+ import type { UniqueWhere } from './unique.js';
9
+ /**
10
+ * One table's shapes: the full row, the insert payload, its unique keys and
11
+ * its relations — every foreign key it takes part in, both directions:
12
+ * `{ list: { to: "lists", kind: "one", via: "list_id" } }` on the child,
13
+ * `{ todos: { to: "todos", kind: "many", via: "list_id" } }` on the parent;
14
+ * `to` is the target's key, bare in public and "schema.table" otherwise.
15
+ */
16
+ export interface TableTypes {
17
+ row: Record<string, unknown>;
18
+ insert: Record<string, unknown>;
19
+ uniqueKeys?: readonly (readonly string[])[];
20
+ relations: Record<string, {
21
+ to: string;
22
+ kind: 'one' | 'many';
23
+ via: string;
24
+ }>;
25
+ /** The table declares search: its entry has `search()`, and no other's does. */
26
+ searchable?: true;
27
+ }
28
+ /**
29
+ * The project's tables: `tables` by name (the framework's `Tables`), and
30
+ * `schemas` by schema then table (its `Schemas`). Unconstrained on purpose —
31
+ * an interface has no index signature, so it would not meet one.
32
+ */
33
+ export interface Env<T = unknown, S = unknown> {
34
+ tables: T;
35
+ schemas: S;
36
+ }
37
+ /**
38
+ * A schema's tables as its own file types them — what palbase-env.d.ts says,
39
+ * read from the schema's type with nothing generated, so an app types
40
+ * `Database.public.<table>` from db/public.ts alone:
41
+ *
42
+ * declare module "palbase/backend/env" { interface Tables extends TablesOf<typeof schema> {} }
43
+ *
44
+ * Relations cross files (a foreign key names another schema's table), so they
45
+ * are not read this way: `with` and `has` take none.
46
+ */
47
+ export type TablesOf<S extends SchemaDef> = {
48
+ [K in keyof S['tables']]: {
49
+ row: RowShape<S['tables'][K]>;
50
+ insert: InsertShape<S['tables'][K]>;
51
+ relations: Record<never, never>;
52
+ } & (S['tables'][K] extends {
53
+ search: Search;
54
+ } ? {
55
+ searchable: true;
56
+ } : unknown);
57
+ };
58
+ /** No project tables: for a surface that names none (makeTypedDB's, a raw client's). */
59
+ export type NoEnv = Env<Record<never, never>, Record<never, never>>;
60
+ /** Columns only the database writes (an ALWAYS identity): on the row, on no insert or update shape. */
61
+ type DatabaseOnlyKeys<C> = {
62
+ [K in keyof C]: C[K] extends ColumnBuilder<ColumnType, boolean, boolean, unknown, unknown, unknown, 'readonly'> ? K : never;
63
+ }[keyof C];
64
+ type RequiredKeys<C> = Exclude<{
65
+ [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? never : K;
66
+ }[keyof C], DatabaseOnlyKeys<C>>;
67
+ type OptionalKeys<C> = Exclude<{
68
+ [K in keyof C]: ColIsOptionalOnInsert<C[K]> extends true ? K : never;
69
+ }[keyof C], DatabaseOnlyKeys<C>>;
70
+ /** An INSERT payload: NOT NULL columns without a default required, the rest optional. */
71
+ export type InsertShape<T extends TableDef> = {
72
+ [K in RequiredKeys<T['columns']>]: ColValue<T['columns'][K]>;
73
+ } & {
74
+ [K in OptionalKeys<T['columns']>]?: ColValue<T['columns'][K]>;
75
+ };
76
+ /** A full row; a nullable column is `T | null`. */
77
+ export type RowShape<T extends TableDef> = {
78
+ [K in keyof T['columns']]: ColValue<T['columns'][K]>;
79
+ };
80
+ /**
81
+ * A reference to a column — the one thing that may stand where a value goes
82
+ * in a comparison. Known by its brand, not its shape (guards.ts): a request
83
+ * body's `{"$col":"tenant_id"}` is not one.
84
+ */
85
+ export type ColRef<N extends string = string> = {
86
+ readonly $col: N;
87
+ };
88
+ export { isColRef };
89
+ /** `total > amount_paid`: the right side is a column, not a value. A wrong name is a compile error where the filter uses it. */
90
+ export declare function col<const N extends string>(name: N): ColRef<N>;
91
+ type PgFamily<V> = [NonNullable<V>] extends [boolean] ? 'bool' : [NonNullable<V>] extends [readonly (number | string)[]] ? 'array' : [NonNullable<V>] extends [Date] ? 'date' : [NonNullable<V>] extends [string | number] ? 'scalar' : 'opaque';
92
+ type PgTag<V> = [NonNullable<V>] extends [{
93
+ readonly __pg?: infer N;
94
+ }] ? ([N] extends [string] ? N : '?') : '?';
95
+ /** integer, bigint and numeric compare with each other; every other type only with itself. */
96
+ type PgKind<P> = P extends 'integer' | 'bigint' | 'numeric' ? 'num' : P;
97
+ type BothBranded<A, B> = '?' extends PgTag<A> ? false : '?' extends PgTag<B> ? false : true;
98
+ type PgSameKind<A, B> = [PgKind<PgTag<A>>] extends [PgKind<PgTag<B>>] ? ([PgKind<PgTag<B>>] extends [PgKind<PgTag<A>>] ? true : false) : false;
99
+ type ColComparable<A, B> = BothBranded<A, B> extends true ? PgSameKind<A, B> : [PgFamily<A>] extends [PgFamily<B>] ? true : false;
100
+ type ColumnsComparableTo<Row, V> = {
101
+ [K in keyof Row]-?: ColComparable<Row[K], V> extends true ? K : never;
102
+ }[keyof Row];
103
+ declare const NO_CONSTRAINT: unique symbol;
104
+ export type NoColConstraint = {
105
+ readonly [NO_CONSTRAINT]: true;
106
+ };
107
+ /** A reference to one of Row's columns — those comparable with V, when V is given. */
108
+ export type ColRefOf<Row, V = NoColConstraint> = [keyof Row] extends [never] ? never : [V] extends [NoColConstraint] ? ColRef<Extract<keyof Row, string>> : ColRef<Extract<ColumnsComparableTo<Row, V>, string>>;
109
+ /** SQL that goes INTO the query: text in parts, values between them, never joined into one string. */
110
+ export type SqlFragment = {
111
+ readonly $sql: {
112
+ readonly text: readonly string[];
113
+ readonly values: readonly unknown[];
114
+ };
115
+ };
116
+ /**
117
+ * The way into a query rather than out of it:
118
+ * `where: sqlFragment\`similarity(title, ${q}) > 0.3\`` keeps select, orderBy,
119
+ * limit and RLS. There is no identifier-embedding surface (no .raw): that is
120
+ * where Drizzle's CVE-2026-39356 and Knex's CVE-2019-10757 were born. Branded,
121
+ * so a fragment cannot be made from JSON.
122
+ */
123
+ export declare function sqlFragment(strings: TemplateStringsArray, ...values: unknown[]): SqlFragment;
124
+ /** Retries `fn` on a retryable failure (40001, 40P01). Zero unless asked: a silent retry would run a transfer twice. `fn` must open a NEW transaction each time — $atomic does. */
125
+ export declare function withRetry<T>(fn: () => Promise<T>, opts?: {
126
+ retry?: number;
127
+ }): Promise<T>;
128
+ /** Text operators only on text columns. */
129
+ export type TextOps<V> = V extends string ? {
130
+ contains?: string;
131
+ icontains?: string;
132
+ startsWith?: string;
133
+ endsWith?: string;
134
+ } : {
135
+ contains?: never;
136
+ icontains?: never;
137
+ startsWith?: never;
138
+ endsWith?: never;
139
+ };
140
+ /** An array column's filters: `contains` holds every given element (@>), `overlaps` any (&&). */
141
+ export type ArrayOps<V> = PgTag<V> extends `${string}[]` ? [NonNullable<V>] extends [readonly (infer El)[]] ? {
142
+ contains?: El[];
143
+ overlaps?: El[];
144
+ icontains?: never;
145
+ startsWith?: never;
146
+ endsWith?: never;
147
+ } : never : PgTag<V> extends '?' ? [NonNullable<V>] extends [readonly (infer El)[]] ? {
148
+ contains?: El[];
149
+ overlaps?: El[];
150
+ icontains?: never;
151
+ startsWith?: never;
152
+ endsWith?: never;
153
+ } : never : never;
154
+ type ListOrTextOps<V> = [ArrayOps<V>] extends [never] ? TextOps<V> & {
155
+ overlaps?: never;
156
+ } : ArrayOps<V>;
157
+ /** Where now() may be a comparison value: a timestamp column (or, unbranded, a string/Date one). */
158
+ type NowComparable<V> = '?' extends PgTag<V> ? ([NonNullable<V>] extends [string | Date] ? TxNow : never) : PgTag<V> extends 'timestamp' ? TxNow : never;
159
+ type WhereOpBody<V, C> = {
160
+ gt?: V | C | NowComparable<V>;
161
+ gte?: V | C | NowComparable<V>;
162
+ lt?: V | C | NowComparable<V>;
163
+ lte?: V | C | NowComparable<V>;
164
+ neq?: V | C | NowComparable<V>;
165
+ in?: V[];
166
+ /** IS NULL / IS NOT NULL — `= NULL` is never true. */
167
+ isNull?: boolean;
168
+ } & ListOrTextOps<V>;
169
+ export type WhereOp<V, Row = unknown> = WhereOpWith<V, ColRefOf<Row, V>>;
170
+ /** The operator vocabulary with the column-reference position given from outside — the plan adds `Ref` there. */
171
+ export type WhereOpWith<V, C> = V | C | NowComparable<V> | WhereOpBody<V, C>;
172
+ /** THE filter: findMany, updateMany, deleteMany, count and the plans take this one. */
173
+ export type WhereFilter<Row> = {
174
+ [K in keyof Row]?: WhereOp<Row[K], Row>;
175
+ } & {
176
+ OR?: (WhereFilter<Row> | SqlFragment)[];
177
+ AND?: (WhereFilter<Row> | SqlFragment)[];
178
+ NOT?: WhereFilter<Row> | SqlFragment;
179
+ };
180
+ export type OrderBySpec<Row> = {
181
+ column: Extract<keyof Row, string>;
182
+ direction?: 'asc' | 'desc';
183
+ /** Where NULLs go; Postgres' default changes with the direction. */
184
+ nulls?: 'first' | 'last';
185
+ };
186
+ export type FindManyOpts<Row> = {
187
+ orderBy?: OrderBySpec<Row> | OrderBySpec<Row>[];
188
+ limit?: number;
189
+ offset?: number;
190
+ };
191
+ type RelatedTable<To, E extends Env> = To extends `${infer S}.${infer T}` ? S extends keyof E['schemas'] ? T extends keyof E['schemas'][S] ? E['schemas'][S][T] : never : never : To extends keyof E['tables'] ? E['tables'][To] : never;
192
+ type RelatedRow<To, E extends Env> = RelatedTable<To, E> extends {
193
+ row: infer R;
194
+ } ? R : Record<string, unknown>;
195
+ type RelatedRels<To, E extends Env> = RelatedTable<To, E> extends {
196
+ relations: infer R;
197
+ } ? R : unknown;
198
+ type HasDepth = 0 | 1 | 2 | 3;
199
+ type HasDec = [0, 0, 1, 2];
200
+ type HasNest<Rels, D extends HasDepth, E extends Env> = [keyof Rels] extends [never] ? unknown : D extends 0 ? unknown : {
201
+ has?: HasFilter<Rels, E, HasDec[D]>;
202
+ };
203
+ type HasFilter<Rels, E extends Env, D extends HasDepth = 3> = {
204
+ [R in keyof Rels]?: Rels[R] extends {
205
+ to: infer To;
206
+ } ? WhereFilter<RelatedRow<To, E>> & HasNest<RelatedRels<To, E>, D, E> : never;
207
+ };
208
+ /** A filter through a relation — `{ has: { user_interests: { user_id } } }` is `id IN (SELECT …)`. */
209
+ export type WhereWithRelations<Row, Rels, E extends Env = NoEnv> = WhereFilter<Row> & {
210
+ has?: HasFilter<Rels, E>;
211
+ };
212
+ /** The `has` branch alone, for the plan's filter; nothing for a table without relations. */
213
+ export type HasOnly<Rels, E extends Env = NoEnv> = [unknown] extends [Rels] ? unknown : [keyof Rels] extends [never] ? unknown : {
214
+ has?: HasFilter<Rels, E>;
215
+ };
216
+ type HasBranch<Row, Rels, E extends Env> = [unknown] extends [Rels] ? never : [keyof Rels] extends [never] ? never : WhereWithRelations<Row, Rels, E>;
217
+ type WithOpts<To, D extends HasDepth, E extends Env> = {
218
+ select?: readonly Extract<keyof RelatedRow<To, E>, string>[];
219
+ where?: WhereFilter<RelatedRow<To, E>>;
220
+ orderBy?: OrderBySpec<RelatedRow<To, E>> | readonly OrderBySpec<RelatedRow<To, E>>[];
221
+ limit?: number;
222
+ with?: D extends 1 ? never : [keyof RelatedRels<To, E>] extends [never] ? never : WithInput<RelatedRels<To, E>, HasDec[D], E>;
223
+ };
224
+ /** `with`: related rows fetched — `true` all columns, or narrowed. */
225
+ export type WithInput<Rels, D extends HasDepth = 3, E extends Env = NoEnv> = {
226
+ [R in keyof Rels]?: Rels[R] extends {
227
+ to: infer To;
228
+ } ? true | WithOpts<To, D, E> : never;
229
+ };
230
+ type WithRowOf<Rels, R extends keyof Rels, Q, E extends Env> = Rels[R] extends {
231
+ to: infer To;
232
+ kind: infer K;
233
+ } ? (Q extends {
234
+ select: readonly (infer C extends keyof RelatedRow<To, E>)[];
235
+ } ? Pick<RelatedRow<To, E>, C> : RelatedRow<To, E>) & (Q extends {
236
+ with: infer W;
237
+ } ? WithResult<RelatedRels<To, E>, W, E> : unknown) extends infer Row ? [K] extends ['many'] ? Row[] : Row | null : never : never;
238
+ type RequestedWith<W> = {
239
+ [R in keyof W]-?: undefined extends W[R] ? never : R;
240
+ }[keyof W];
241
+ export type WithResult<Rels, W, E extends Env = NoEnv> = [RequestedWith<W>] extends [never] ? unknown : {
242
+ [R in RequestedWith<W> & keyof Rels]: WithRowOf<Rels, R, W[R], E>;
243
+ };
244
+ /** A read's one parameter: where, with, select (which narrows the row type), orderBy, limit, offset. */
245
+ export type QueryInput<Row, K extends keyof Row = keyof Row, Rels = unknown, E extends Env = NoEnv> = {
246
+ where?: WhereFilter<Row> | SqlFragment | HasBranch<Row, Rels, E>;
247
+ /** Related rows in the same query; `many` is an array ([] when none), `one` a row or null. numeric and bigint stay exact. */
248
+ with?: [unknown] extends [Rels] ? never : [keyof Rels] extends [never] ? never : WithInput<Rels, 3, E>;
249
+ select?: readonly K[];
250
+ } & FindManyOpts<Row>;
251
+ export type PageInput<Row, K extends keyof Row = keyof Row, Rels = unknown, E extends Env = NoEnv> = Omit<QueryInput<Row, keyof Row, Rels, E>, 'limit' | 'offset' | 'select'> & {
252
+ select?: readonly K[];
253
+ } & PageNavigation;
254
+ /** A write's one parameter: which rows (`where`), what they become (`set`); `returning: false` for a count. */
255
+ export type MutateInput<Row, Insert, Rels = unknown, E extends Env = NoEnv> = {
256
+ where: WhereFilter<Row> | SqlFragment | HasBranch<Row, Rels, E>;
257
+ set: SetShape<Insert>;
258
+ returning?: boolean;
259
+ };
260
+ type NumericCol<V> = '?' extends PgTag<V> ? (NonNullable<V> extends number | string ? TxColumnExpr : never) : PgKind<PgTag<V>> extends 'num' ? TxColumnExpr : never;
261
+ /** An insert's value: the column's own, or — for a timestamp — the server's clock. */
262
+ export type InsertValues<Insert> = {
263
+ [K in keyof Insert]: Insert[K] | NowComparable<Insert[K]>;
264
+ };
265
+ /** An update's value: the column's own, increment()/decrement() on a numeric column, now() on a timestamp. */
266
+ export type SetValue<V> = V | NumericCol<V> | NowComparable<V>;
267
+ export type SetShape<Insert> = {
268
+ [K in keyof Insert]?: SetValue<Insert[K]>;
269
+ };
270
+ type RelsOf<T> = T extends {
271
+ relations: infer R;
272
+ } ? R : unknown;
273
+ type SummableCols<Row> = {
274
+ [K in keyof Row]-?: '?' extends PgTag<Row[K]> ? (NonNullable<Row[K]> extends number | string ? K : never) : PgKind<PgTag<Row[K]>> extends 'num' ? K : never;
275
+ }[keyof Row];
276
+ type OrderableCols<Row> = {
277
+ [K in keyof Row]-?: '?' extends PgTag<Row[K]> ? (NonNullable<Row[K]> extends number | string | Date ? K : never) : PgTag<Row[K]> extends 'jsonb' ? never : K;
278
+ }[keyof Row];
279
+ type AggCols<Row> = Extract<SummableCols<Row>, string>;
280
+ type AggOrderCols<Row> = Extract<OrderableCols<Row>, string>;
281
+ export type AggregateInput<Row, Rels = unknown, E extends Env = NoEnv> = {
282
+ where?: WhereFilter<Row> | SqlFragment | HasBranch<Row, Rels, E>;
283
+ sum?: readonly AggCols<Row>[];
284
+ avg?: readonly AggCols<Row>[];
285
+ min?: readonly AggOrderCols<Row>[];
286
+ max?: readonly AggOrderCols<Row>[];
287
+ /**
288
+ * `true`: how many rows. Named filters — `{ open: { done: false }, mine: { owner } }` —
289
+ * how many rows each matches, every one in the same statement (count(*)
290
+ * FILTER (WHERE …)), within `where` when it is given; `{}` is every row.
291
+ */
292
+ count?: boolean | {
293
+ readonly [name: string]: WhereFilter<Row> | SqlFragment | HasBranch<Row, Rels, E>;
294
+ };
295
+ /** Given: rows, each carrying its group's columns. */
296
+ groupBy?: readonly Extract<keyof Row, string>[];
297
+ };
298
+ type AggPart<Q, Row, F extends 'sum' | 'avg' | 'min' | 'max'> = Q extends {
299
+ [K in F]: readonly (infer C extends keyof Row)[];
300
+ } ? {
301
+ [P in C]: F extends 'sum' | 'avg' ? string | null : Row[P] | null;
302
+ } : never;
303
+ type AggBody<Q, Row> = ([AggPart<Q, Row, 'sum'>] extends [never] ? unknown : {
304
+ sum: AggPart<Q, Row, 'sum'>;
305
+ }) & ([AggPart<Q, Row, 'avg'>] extends [never] ? unknown : {
306
+ avg: AggPart<Q, Row, 'avg'>;
307
+ }) & ([AggPart<Q, Row, 'min'>] extends [never] ? unknown : {
308
+ min: AggPart<Q, Row, 'min'>;
309
+ }) & ([AggPart<Q, Row, 'max'>] extends [never] ? unknown : {
310
+ max: AggPart<Q, Row, 'max'>;
311
+ }) & CountPart<Q>;
312
+ type CountPart<Q> = Q extends {
313
+ count: true;
314
+ } ? {
315
+ count: number;
316
+ } : Q extends {
317
+ count: infer M extends {
318
+ readonly [name: string]: unknown;
319
+ };
320
+ } ? {
321
+ count: {
322
+ [K in keyof M]: number;
323
+ };
324
+ } : unknown;
325
+ export type AggregateResult<Q, Row> = Q extends {
326
+ groupBy: readonly (infer G extends keyof Row)[];
327
+ } ? ({
328
+ [P in G]: Row[P];
329
+ } & AggBody<Q, Row>)[] : AggBody<Q, Row>;
330
+ /** A search's options, its filter typed as findMany's. */
331
+ export type SearchParams<Row, Rels = unknown, E extends Env = NoEnv> = Omit<SearchOptions, 'where'> & {
332
+ where?: WhereFilter<Row> | SqlFragment | HasBranch<Row, Rels, E>;
333
+ };
334
+ /** What only a table that declares search has. */
335
+ export interface Searchable<Row, Rels = unknown, E extends Env = NoEnv> {
336
+ /**
337
+ * The rows nearest to `query`, best first, each with its `_score`: by
338
+ * meaning and by word, fused ("hybrid"), under the caller's row-level
339
+ * security — a row they may not read is never found.
340
+ */
341
+ search(query: string, opts?: SearchParams<Row, Rels, E>): Promise<Array<Row & {
342
+ _score: number;
343
+ }>>;
344
+ }
345
+ /**
346
+ * One table of the project, typed by its palbase-env.d.ts entry; `E` resolves
347
+ * its relations' targets. `search()` is there for an entry that says
348
+ * `searchable: true` — a table that declares search — and for no other.
349
+ */
350
+ export type EnvTypedTable<T extends TableTypes, E extends Env = NoEnv> = EnvTypedTableOps<T, E> & (T extends {
351
+ searchable: true;
352
+ } ? Searchable<T['row'], RelsOf<T>, E> : unknown);
353
+ /** What every table of the project has. */
354
+ export interface EnvTypedTableOps<T extends TableTypes, E extends Env = NoEnv> {
355
+ /** The row with this unique key, or null; with `lock`, the row is locked in the same statement until the transaction ends. */
356
+ findUnique<K extends keyof T['row'] = keyof T['row'], W extends WithInput<RelsOf<T>, 3, E> = Record<never, never>>(q: {
357
+ where: UniqueWhere<T['row'], T['uniqueKeys']>;
358
+ select?: readonly K[];
359
+ with?: QueryInput<T['row'], keyof T['row'], RelsOf<T>, E>['with'] & W;
360
+ lock?: RowLock;
361
+ }): Promise<(Pick<T['row'], K> & WithResult<RelsOf<T>, W, E>) | null>;
362
+ /** Index-backed pages: the primary key is appended to orderBy, cursors are sealed to the query and the caller. */
363
+ page<K extends keyof T['row'] = keyof T['row'], W extends WithInput<RelsOf<T>, 3, E> = Record<never, never>>(q?: PageInput<T['row'], K, RelsOf<T>, E> & {
364
+ with?: W;
365
+ }): Promise<Page<Pick<T['row'], K> & WithResult<RelsOf<T>, W, E>>>;
366
+ insert(data: InsertValues<T['insert']>): Promise<T['row']>;
367
+ /** Many rows in one statement; every row the same keys (null for a missing value). An empty list is an empty result. */
368
+ insertMany(rows: readonly InsertValues<T['insert']>[], opts: InsertManyOptions<Extract<keyof T['row'], string>> & {
369
+ returning: false;
370
+ }): Promise<number>;
371
+ insertMany(rows: readonly InsertValues<T['insert']>[], opts?: InsertManyOptions<Extract<keyof T['row'], string>> & {
372
+ returning?: true;
373
+ }): Promise<T['row'][]>;
374
+ insertMany(rows: readonly InsertValues<T['insert']>[], opts?: InsertManyOptions<Extract<keyof T['row'], string>>): Promise<T['row'][] | number>;
375
+ /** Claims an idempotency key; `inserted: false` is not an error — the row is the first call's. */
376
+ claim(unique: Partial<T['insert']>, extra?: Partial<InsertValues<T['insert']>>): Promise<{
377
+ inserted: boolean;
378
+ row: T['row'];
379
+ }>;
380
+ /** Insert, or overwrite on a collision of `onConflict` (columns a unique constraint or index covers). */
381
+ put(q: {
382
+ data: InsertValues<T['insert']>;
383
+ onConflict: readonly Extract<keyof T['row'], string>[];
384
+ }): Promise<T['row']>;
385
+ /** The row with this id updated, or null when none matched. */
386
+ update(q: {
387
+ where: {
388
+ id: string;
389
+ };
390
+ set: SetShape<T['insert']>;
391
+ }): Promise<T['row'] | null>;
392
+ delete(id: string): Promise<void>;
393
+ findById(id: string): Promise<T['row'] | null>;
394
+ findMany<K extends keyof T['row'] = keyof T['row'], W extends WithInput<RelsOf<T>, 3, E> = Record<never, never>>(q?: QueryInput<T['row'], K, RelsOf<T>, E> & {
395
+ with?: W;
396
+ }): Promise<Array<Pick<T['row'], K> & WithResult<RelsOf<T>, W, E>>>;
397
+ /** Every matching row updated in one statement; an empty filter is refused, zero rows is "the condition did not hold". */
398
+ updateMany<const Q extends MutateInput<T['row'], T['insert'], RelsOf<T>, E>>(q: Q): Promise<Q extends {
399
+ returning: false;
400
+ } ? number : T['row'][]>;
401
+ deleteMany(q: {
402
+ where: WhereFilter<T['row']> | SqlFragment | HasBranch<T['row'], RelsOf<T>, E>;
403
+ }): Promise<number>;
404
+ aggregate<const Q extends AggregateInput<T['row'], RelsOf<T>, E>>(q: Q): Promise<AggregateResult<Q, T['row']>>;
405
+ count(q?: {
406
+ where?: WhereFilter<T['row']> | SqlFragment | HasBranch<T['row'], RelsOf<T>, E>;
407
+ }): Promise<number>;
408
+ }
409
+ type Entry<X> = Extract<X, TableTypes>;
410
+ /** Public's tables by name: `Database.public`. */
411
+ export type EnvTables<T, S> = {
412
+ [K in keyof T]: EnvTypedTable<Entry<T[K]>, Env<T, S>>;
413
+ };
414
+ /** Every schema's tables: `Database.public.<table>` and `Database.<schema>.<table>`. */
415
+ export type EnvSchemaSurface<T, S> = {
416
+ public: EnvTables<T, S>;
417
+ } & {
418
+ [N in keyof S]: {
419
+ [K in keyof S[N]]: EnvTypedTable<Entry<S[N][K]>, Env<T, S>>;
420
+ };
421
+ };
422
+ type TxTablesOf<X, E extends Env> = {
423
+ [K in keyof X]: TxTable<Entry<X[K]>['row'], Entry<X[K]>['insert'], RelsOf<Entry<X[K]>>, E>;
424
+ };
425
+ /** Public's tables as PLAN operations. */
426
+ export type TxTables<T, S> = TxTablesOf<T, Env<T, S>>;
427
+ /** The plan handle's schemas — the same shape as Database's: `tx.public.x`, `tx.<schema>.x`. */
428
+ export type TxSchemaSurface<T, S> = {
429
+ public: TxTables<T, S>;
430
+ } & {
431
+ [N in keyof S]: TxTablesOf<S[N], Env<T, S>>;
432
+ };
433
+ /** What a `$transaction` callback gets: tables only. */
434
+ export type TxPlan<T, S> = TxPlanHandle<TxTables<T, S>> & TxSchemaSurface<T, S>;
435
+ /** `$` keeps the system's members apart from table and schema names: no schema name may start with it. */
436
+ export type DollarOps<T> = {
437
+ [K in keyof T as `$${string & K}`]: T[K];
438
+ };
439
+ /** The service_role sibling, and an $atomic scope's handle: the same surface, without $asService. */
440
+ export type EnvServiceDatabase<T, S> = EnvSchemaSurface<T, S> & {
441
+ /** Commits independently of the request; every retry is a new BEGIN … COMMIT. */
442
+ $atomic<R>(fn: (tx: AtomicDatabase<T, S>) => Promise<R>, options?: AtomicOptions): Promise<R>;
443
+ $query: DBClient['query'];
444
+ $insert: DBClient['insert'];
445
+ $insertMany: DBClient['insertMany'];
446
+ $update: DBClient['update'];
447
+ $delete: DBClient['delete'];
448
+ $findById: DBClient['findById'];
449
+ $findUnique: DBClient['findUnique'];
450
+ $diagnostics: DBClient['diagnostics'];
451
+ $findMany: DBClient['findMany'];
452
+ $page: DBClient['page'];
453
+ $put: DBClient['put'];
454
+ $updateMany: DBClient['updateMany'];
455
+ $deleteMany: DBClient['deleteMany'];
456
+ $count: DBClient['count'];
457
+ $search: DBClient['search'];
458
+ $aggregate: DBClient['aggregate'];
459
+ $claim: DBClient['claim'];
460
+ $lockRows: DBClient['lockRows'];
461
+ $lockRowsWhere: DBClient['lockRowsWhere'];
462
+ $advisoryXactLock: DBClient['advisoryXactLock'];
463
+ $attempt: DBClient['attempt'];
464
+ /** A plan: a savepoint in the current transaction. A nonzero `retry` is refused — use $atomic for whole-transaction retry. */
465
+ $transaction<R>(fn: (tx: TxPlan<T, S>) => R extends Promise<unknown> ? never : R, opts?: {
466
+ retry?: number;
467
+ }): Promise<Materialized<R>>;
468
+ };
469
+ /**
470
+ * The project's data: `Database.<schema>.<table>` plus the `$` members, for
471
+ * `T` the public tables (the framework's `Tables`) and `S` the other schemas'
472
+ * (`Schemas`). RLS holds by default — a signed-in caller is `authenticated`
473
+ * with their claims, anyone else `anon`; `$asService()` is the explicit,
474
+ * greppable way past it (service_role).
475
+ */
476
+ export type EnvTypedDatabase<T, S> = EnvServiceDatabase<T, S> & {
477
+ /** The RLS-bypassing sibling; it has no $asService of its own. */
478
+ $asService(): EnvServiceDatabase<T, S>;
479
+ };
480
+ /** One root transaction's handle. */
481
+ export type AtomicDatabase<T, S> = Omit<EnvServiceDatabase<T, S>, '$atomic'>;
482
+ /** A table of a SchemaDef: `search()` where its declaration has one. */
483
+ export type TypedTable<T extends TableDef> = TypedTableOps<T> & (T extends {
484
+ search: Search;
485
+ } ? Searchable<RowShape<T>> : unknown);
486
+ export interface TypedTableOps<T extends TableDef> {
487
+ findUnique<K extends keyof RowShape<T> = keyof RowShape<T>>(q: {
488
+ where: Partial<RowShape<T>>;
489
+ select?: readonly K[];
490
+ lock?: RowLock;
491
+ }): Promise<Pick<RowShape<T>, K> | null>;
492
+ page<K extends keyof RowShape<T> = keyof RowShape<T>>(q?: PageInput<RowShape<T>, K>): Promise<Page<Pick<RowShape<T>, K>>>;
493
+ insert(data: InsertValues<InsertShape<T>>): Promise<RowShape<T>>;
494
+ insertMany(rows: readonly InsertValues<InsertShape<T>>[], opts: InsertManyOptions & {
495
+ returning: false;
496
+ }): Promise<number>;
497
+ insertMany(rows: readonly InsertValues<InsertShape<T>>[], opts?: InsertManyOptions & {
498
+ returning?: true;
499
+ }): Promise<RowShape<T>[]>;
500
+ insertMany(rows: readonly InsertValues<InsertShape<T>>[], opts?: InsertManyOptions): Promise<RowShape<T>[] | number>;
501
+ put(q: {
502
+ data: InsertShape<T>;
503
+ onConflict: readonly string[];
504
+ }): Promise<RowShape<T>>;
505
+ update(q: {
506
+ where: {
507
+ id: string;
508
+ };
509
+ set: SetShape<InsertShape<T>>;
510
+ }): Promise<RowShape<T> | null>;
511
+ delete(id: string): Promise<void>;
512
+ findById(id: string): Promise<RowShape<T> | null>;
513
+ findMany<K extends keyof RowShape<T> = keyof RowShape<T>>(q?: QueryInput<RowShape<T>, K>): Promise<Pick<RowShape<T>, K>[]>;
514
+ updateMany<const Q extends MutateInput<RowShape<T>, InsertShape<T>>>(q: Q): Promise<Q extends {
515
+ returning: false;
516
+ } ? number : RowShape<T>[]>;
517
+ deleteMany(q: {
518
+ where: WhereFilter<RowShape<T>> | SqlFragment;
519
+ }): Promise<number>;
520
+ aggregate<const Q extends AggregateInput<RowShape<T>>>(q: Q): Promise<AggregateResult<Q, RowShape<T>>>;
521
+ count(q?: {
522
+ where?: WhereFilter<RowShape<T>> | SqlFragment;
523
+ }): Promise<number>;
524
+ }
525
+ /** A plan callback's handle over one schema's tables. */
526
+ export type TypedTx<S extends SchemaDef> = TxPlanHandle<{
527
+ [K in keyof S['tables']]: TxTable<RowShape<S['tables'][K]>, InsertShape<S['tables'][K]>>;
528
+ }>;
529
+ export interface TypedDB<S extends SchemaDef> {
530
+ diagnostics: DBOps['diagnostics'];
531
+ tables: {
532
+ [K in keyof S['tables']]: TypedTable<S['tables'][K]>;
533
+ };
534
+ transaction<T>(fn: (tx: TypedTx<S>) => T extends Promise<unknown> ? never : T): Promise<Materialized<T>>;
535
+ }
536
+ /** A raw client typed by one SchemaDef: its tables by name, and a plan over them. */
537
+ export declare function makeTypedDB<S extends SchemaDef>(schema: S, raw: AtomicClient): TypedDB<S>;
@@ -0,0 +1,16 @@
1
+ /** Every declared unique key, primary first, each once. */
2
+ export declare function uniqueKeysOf(table: {
3
+ columns?: Record<string, unknown>;
4
+ primaryKey?: readonly string[];
5
+ unique?: readonly {
6
+ columns: readonly string[];
7
+ }[];
8
+ }): string[][];
9
+ /** Exactly one declared key, every column of it equal to a non-null scalar: NULLs in a UNIQUE constraint are not unique. */
10
+ export declare function assertUniqueLookup(table: string, keys: readonly (readonly string[])[], where: Record<string, unknown>): void;
11
+ /** A union of exact keys, not a partial row that could match many. */
12
+ export type UniqueWhere<Row, Keys> = Keys extends readonly (infer Key extends readonly string[])[] ? Key extends unknown ? {
13
+ [K in Extract<Key[number], keyof Row>]-?: NonNullable<Row[K]>;
14
+ } & {
15
+ [K in Exclude<keyof Row, Key[number]>]?: never;
16
+ } : never : never;
@@ -0,0 +1,36 @@
1
+ import type { Roles } from './plan.js';
2
+ import { type SqlDriver } from './query/driver.js';
3
+ import { type Embed } from './query/search.js';
4
+ export interface SearchWorkerOptions {
5
+ /** The app's pool, as createDatabase takes it; the worker's transactions are the service role's. */
6
+ sql: SqlDriver;
7
+ /** What embeds: the same the app's createDatabase was given. */
8
+ embed: Embed;
9
+ /** The database's roles, when they are not anon, authenticated and service_role. */
10
+ roles?: Partial<Roles>;
11
+ log?: (line: string) => void;
12
+ }
13
+ export interface SearchWorker {
14
+ /**
15
+ * Embeds every row that is due, once, and promotes each generation that is
16
+ * full: how many vectors it wrote, and the tables ("schema.table") whose
17
+ * new generation took over. A row whose call fails is left for its next
18
+ * attempt, and nothing more is claimed after a batch in which every call
19
+ * failed. `max`: stop once that many are written; `timeoutMs`: claim
20
+ * nothing more after it.
21
+ */
22
+ drain(options?: {
23
+ max?: number;
24
+ timeoutMs?: number;
25
+ }): Promise<{
26
+ embedded: number;
27
+ promoted: string[];
28
+ }>;
29
+ /** Drains from now on: at once when a write to a table with search commits in this process, and by itself — a second after it found work, up to fifteen when it finds none. */
30
+ start(): void;
31
+ /** Stops: nothing more is claimed, and what is being embedded is given a few seconds to end. */
32
+ stop(): Promise<void>;
33
+ /** Drain now, not at the next look. */
34
+ kick(): void;
35
+ }
36
+ export declare function createSearchWorker(options: SearchWorkerOptions): SearchWorker;