palgres 0.0.0-stage → 0.1.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/5DSZT7RI.js +5 -0
  4. package/dist/chunks/7676QADW.js +2 -0
  5. package/dist/chunks/W7VRQDZA.js +2 -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,168 @@
1
+ /** The brand an HTTP refusal is known by, across package copies. */
2
+ export declare const HTTP_ERROR_BRAND: unique symbol;
3
+ /**
4
+ * Set on a refusal the ENGINE built — out of a driver failure, or of a filter
5
+ * it would not run — as opposed to one the author threw to answer a request.
6
+ * A status cannot tell them apart — the author's `Conflict` and the engine's
7
+ * `UniqueViolation` are both 409 — so the framework logs the engine's by this
8
+ * mark.
9
+ */
10
+ export declare const ENGINE_RAISED: unique symbol;
11
+ /** Postgres' five-character codes the engine maps or retries on. */
12
+ export declare const SQLSTATE: Readonly<{
13
+ readonly uniqueViolation: '23505';
14
+ readonly foreignKeyViolation: '23503';
15
+ readonly serializationFailure: '40001';
16
+ readonly deadlockDetected: '40P01';
17
+ readonly queryCancelled: '57014';
18
+ readonly lockNotAvailable: '55P03';
19
+ readonly inFailedTransaction: '25P02';
20
+ readonly insufficientPrivilege: '42501';
21
+ }>;
22
+ /**
23
+ * An HTTP refusal, in palbase/backend's shape — status, the wire code
24
+ * (`error`), its description, an optional typed `data` and `retryAfter` —
25
+ * so the framework sends it in its envelope. palbase/backend re-exports
26
+ * these classes rather than declaring its own: the class a handler imports is
27
+ * the class the engine throws, and `instanceof` holds.
28
+ */
29
+ export declare class HttpError extends Error {
30
+ readonly [HTTP_ERROR_BRAND] = true;
31
+ readonly status: number;
32
+ readonly error: string;
33
+ readonly errorDescription: string;
34
+ readonly data?: unknown;
35
+ /** Seconds the caller should wait, sent as Retry-After. */
36
+ readonly retryAfter?: number;
37
+ constructor(status: number, error: string, errorDescription: string, data?: unknown, retryAfter?: number);
38
+ }
39
+ export declare function markEngineRaised<E extends object>(e: E): E;
40
+ export declare function isEngineRaised(e: unknown): boolean;
41
+ export interface FieldError {
42
+ /** The field's name (a dotted path when nested). */
43
+ field: string;
44
+ message: string;
45
+ }
46
+ export interface BadRequestData {
47
+ fields: FieldError[];
48
+ }
49
+ /** 400 — malformed or invalid; carries the fields. */
50
+ export declare class BadRequest extends HttpError {
51
+ readonly data: BadRequestData;
52
+ constructor(data: BadRequestData, message?: string);
53
+ }
54
+ /** 404. */
55
+ export declare class NotFound extends HttpError {
56
+ constructor(message?: string, code?: string, data?: unknown);
57
+ }
58
+ /** 409 — the request conflicts with the current state. */
59
+ export declare class Conflict extends HttpError {
60
+ constructor(message?: string, code?: string, data?: unknown);
61
+ }
62
+ /**
63
+ * 409 — a write would duplicate a row: SQLSTATE 23505, converted by the
64
+ * engine so nobody has to match the driver's prose. `constraint` is the name
65
+ * Postgres gave ("" when the driver did not say). It is a field and stays out
66
+ * of the message: an uncaught duplicate puts the message in front of the
67
+ * app's user, and a constraint name tells them how the schema is built.
68
+ *
69
+ * try { await Database.public.users.insert({ email }); }
70
+ * catch (e) { if (UniqueViolation.is(e) && e.constraint === 'users_email_key') throw new Conflict('That email is taken', 'email_taken'); throw e; }
71
+ */
72
+ export declare class UniqueViolation extends Conflict {
73
+ readonly constraint: string;
74
+ readonly sqlstate: "23505";
75
+ /** Whether `e` is one, whichever copy of the package made it. */
76
+ static is(e: unknown): e is UniqueViolation;
77
+ constructor(constraint: string, message?: string, code?: string, data?: unknown);
78
+ }
79
+ /**
80
+ * A failure Postgres says is cured by running the whole transaction again
81
+ * (40001, 40P01). The set is narrow on purpose: retrying a duplicate (23505)
82
+ * gives the same answer forever.
83
+ */
84
+ export interface RetryableError extends Error {
85
+ readonly retryable: true;
86
+ /** The five-character SQLSTATE: which concurrency failure it was. */
87
+ readonly sqlstate: string;
88
+ }
89
+ /** By shape, not instanceof: the engine and the caller may hold different copies of the class. */
90
+ export declare function isRetryable(e: unknown): e is RetryableError;
91
+ /** 40001 — under SERIALIZABLE or REPEATABLE READ, a concurrent transaction invalidated this one. */
92
+ export declare class SerializationFailure extends Conflict implements RetryableError {
93
+ readonly retryable: true;
94
+ readonly sqlstate: string;
95
+ static is(e: unknown): e is SerializationFailure;
96
+ constructor(sqlstate?: string, message?: string, data?: unknown);
97
+ }
98
+ /** 40P01 — two transactions waited on each other's locks and Postgres cancelled this one: a lock-order signal. */
99
+ export declare class DeadlockDetected extends Conflict implements RetryableError {
100
+ readonly retryable: true;
101
+ readonly sqlstate: string;
102
+ static is(e: unknown): e is DeadlockDetected;
103
+ constructor(sqlstate?: string, message?: string, data?: unknown);
104
+ }
105
+ /**
106
+ * 25P02 at COMMIT: a statement of the transaction failed and the code went
107
+ * on, so Postgres answered the COMMIT with ROLLBACK — nothing the transaction
108
+ * wrote is kept. `cause` is the statement's failure. Bun reports such a
109
+ * COMMIT as a success; the engine refuses it itself.
110
+ */
111
+ export declare class TransactionAborted extends Error {
112
+ readonly sqlstate: "25P02";
113
+ static is(e: unknown): e is TransactionAborted;
114
+ constructor(message: string, cause: unknown);
115
+ }
116
+ export interface TooManyRequestsData {
117
+ /** Seconds the caller should wait. */
118
+ retryAfter: number;
119
+ }
120
+ /** 429 — the database queue is full or its wait ran out. */
121
+ export declare class TooManyRequests extends HttpError {
122
+ readonly data: TooManyRequestsData;
123
+ constructor(data: TooManyRequestsData, message?: string);
124
+ }
125
+ /**
126
+ * The five-character SQLSTATE, wherever the driver put it: postgres.js in
127
+ * `.code`, Bun in `.errno` (its `.code` is its own, "ERR_POSTGRES_SERVER_ERROR").
128
+ * The shape decides — a SQLSTATE is five of [0-9A-Z].
129
+ */
130
+ export declare function sqlstateOf(e: unknown): string | undefined;
131
+ /** Failures that prove the statement never reached a backend: replaying the request cannot apply anything twice. */
132
+ export declare const NEVER_REACHED_A_BACKEND: ReadonlySet<string>;
133
+ /** An established connection died under the request: the database is as unavailable, but a COMMIT may have landed. */
134
+ export declare const LOST_MID_REQUEST: ReadonlySet<string>;
135
+ /**
136
+ * A connection Bun's pool lent with no connection under it (Bun 1.4.2): one
137
+ * the server ended in the very read that finished opening it — its
138
+ * ReadyForQuery and a FATAL in one packet, as a switchover terminating a
139
+ * backend that has just started sends them. The pool hears the close, then the
140
+ * open, and keeps the slot as open with nothing under it: it lends it again and
141
+ * again and never opens it anew, and its close() waits for it forever
142
+ * (oven-sh/bun#42804; the fix, oven-sh/bun#40913, is not in a release). A
143
+ * statement given it is refused before anything is written, in these words
144
+ * and with no code. The database is reachable — only that connection is not —
145
+ * so it is no connectionFailure: the driver takes another (bunDriver).
146
+ */
147
+ export declare function bunSlotWithoutConnection(e: unknown): boolean;
148
+ /**
149
+ * How the connection failed, as opposed to Postgres refusing a statement on
150
+ * it: `lost` — an established one died, as a pooled connection the server
151
+ * closed does at once on its next use; `never` — a new one did not open, the
152
+ * database is not reachable now. Read off the driver's error, or the cause of
153
+ * the 503 the engine made of it.
154
+ */
155
+ export declare function connectionFailure(e: unknown): 'lost' | 'never' | undefined;
156
+ /**
157
+ * Whether Postgres itself refused a statement — the transaction lives on,
158
+ * aborted — as opposed to the connection failing under it. Read off the
159
+ * driver's SQLSTATE, or off a refusal the engine made of one (its `sqlstate`,
160
+ * its `cause`). Class 08 and 57P01–57P03 are the connection going away.
161
+ */
162
+ export declare function refusedByPostgres(e: unknown): boolean;
163
+ /**
164
+ * The constraint a 23505 names: the driver's own field first (Bun's
165
+ * `constraint`, postgres.js' `constraint_name`, a UniqueViolation's), the
166
+ * message's prose only as the fallback; "" when nothing says.
167
+ */
168
+ export declare function constraintOf(e: unknown): string;
@@ -0,0 +1,62 @@
1
+ import type { TxWireExpr } from './tx-plan.js';
2
+ export declare function brandRef<T extends object>(v: T, kind: 'col' | 'sql' | 'ref' | 'expr'): T;
3
+ export declare function isColRef(v: unknown): v is {
4
+ readonly $col: string;
5
+ };
6
+ export declare function isSqlFragment(v: unknown): v is {
7
+ readonly $sql: {
8
+ text: string[];
9
+ values: unknown[];
10
+ };
11
+ };
12
+ /** A plan reference — `{ $ref: { op, field } }` — that this process's plan builder made. */
13
+ export declare function isPlanRef(v: unknown): v is {
14
+ readonly $ref: {
15
+ op: number;
16
+ field: string;
17
+ };
18
+ };
19
+ /** A plan's `{ $expr }` that its builder made: a request body's look-alike is data, never an expression. */
20
+ export declare function isPlanExpr(v: unknown): v is {
21
+ readonly $expr: TxWireExpr['$expr'];
22
+ };
23
+ /** `{ $ref: … }` without the mark: refused by name rather than bound as a value, which would compare against another row's value. */
24
+ export declare function looksLikeUnbrandedPlanRef(v: unknown): boolean;
25
+ /** `{ $col }` / `{ $sql }` / `{ $expr }` without the mark, as an operator's operand. */
26
+ export declare function looksLikeUnbrandedRef(v: unknown): 'col' | 'sql' | 'expr' | null;
27
+ export declare function isColumnExpr(v: unknown): boolean;
28
+ /** now(): the server's clock — a comparison value too, where increment() is only a write. */
29
+ export declare function isNowExpr(v: unknown): boolean;
30
+ /** A value-taking write path given an expression handle or col(), refused with the path that takes it. */
31
+ export declare function assertNoExpressionHandles(caller: string, table: string, cols: readonly string[], data: Record<string, unknown>): void;
32
+ /**
33
+ * A filter that would compile to something other than what it reads like:
34
+ * `{ col: undefined }` binds NULL, and `= NULL` matches nothing, silently;
35
+ * `{ col: {} }` produces no term — every row on a read, a dropped condition
36
+ * on a write; an undefined operand or `in` element is the same NULL, one
37
+ * level down.
38
+ */
39
+ export declare function assertUsableFilter(caller: string, table: string, where: Record<string, unknown> | undefined): void;
40
+ /** ago()'s units: the only keys of a span anything reads. */
41
+ export declare const SPAN_UNITS: readonly string[];
42
+ /**
43
+ * A span as one rule reads it — where ago() makes it, and again where a
44
+ * statement is written, since a handle can be made by hand: a plain object of
45
+ * the four units alone, each a whole number from 0 to what an int holds, and
46
+ * at least one. The answer is a copy, each value read once, so a getter
47
+ * cannot tell the check one thing and the statement another; or why the
48
+ * span is none.
49
+ */
50
+ export declare function readSpan(span: unknown): {
51
+ span: Readonly<Record<string, number>>;
52
+ } | {
53
+ refused: string;
54
+ };
55
+ /** undefined is nobody saying anything; null is someone saying "empty this column". Only the second is a write. */
56
+ export declare function assertUsableWriteValues(caller: string, table: string, cols: readonly string[], data: Record<string, unknown>): void;
57
+ /**
58
+ * The id `update({ where, set })` updates by — one row, by its id alone. A
59
+ * key beside it, `{ id, org_id }` meant as a scope, would be dropped and the
60
+ * row updated whatever its org.
61
+ */
62
+ export declare function updateTarget(table: string, where: unknown): string;
@@ -0,0 +1,76 @@
1
+ import type { Binding } from '../connect.js';
2
+ import type { Roles } from '../plan.js';
3
+ import type { SchemaDef } from '../dsl/schema.js';
4
+ import { type DatabaseAdmissionOptions } from './admission.js';
5
+ import type { DBClient } from './client.js';
6
+ import { type SqlDriver } from './driver.js';
7
+ import { type Identity, type RequestOptions } from './request.js';
8
+ import { type Embed } from './search.js';
9
+ import type { EnvTypedDatabase } from './typed.js';
10
+ export interface DatabaseOptions {
11
+ /**
12
+ * The app's pool, logged in as the app's role (a member of anon,
13
+ * authenticated and service_role). It must send text parameters as they
14
+ * are, and run the opening of a transaction (its role, its caller) before
15
+ * the work: Bun.SQL with { prepare: false } through bunDriver(), postgres.js
16
+ * with { types: POSTGRES_JS_TYPES } through postgresJsDriver() —
17
+ * createDatabase checks.
18
+ */
19
+ sql: SqlDriver;
20
+ /** The schema files' default exports — the schema the binding's version was migrated from. */
21
+ schemas: readonly SchemaDef[];
22
+ /** The version this app serves: bind(sql, toModel(schemas)). */
23
+ binding: Binding;
24
+ /** The database's roles, when they are not anon, authenticated and service_role. */
25
+ roles?: Partial<Roles>;
26
+ /** The pool's queue: its size (default 10) and limits. Every database on one pool shares one queue. */
27
+ admission?: DatabaseAdmissionOptions;
28
+ /** The secret page() cursors are sealed with, the same on every replica; page() refuses without it. */
29
+ cursorSecret?: string;
30
+ /**
31
+ * What embeds a search's query — and, given to createSearchWorker, the
32
+ * rows: Palgres reaches no network and holds no key of its own. Without
33
+ * it, search() answers by word alone only when asked to (mode: "text").
34
+ */
35
+ embed?: Embed;
36
+ /** What the engine has to say outside any request's answer: a search answered by word alone because its query could not be embedded. */
37
+ log?: (line: string) => void;
38
+ }
39
+ /** One request's — or one job's — database. `T`, `S`: the project's tables (the framework's `Tables`, `Schemas`). */
40
+ export interface RequestDatabase<T, S> {
41
+ /** What a handler reaches as `Database`: typed tables, the `$` operations, $atomic, $asService(). */
42
+ readonly database: EnvTypedDatabase<T, S>;
43
+ /** The string-keyed client the typed surface is built on. */
44
+ readonly client: DBClient;
45
+ /** Commits what was opened — nothing, if nothing was. Once, after the handler returns. */
46
+ commit(): Promise<void>;
47
+ /** Rolls back what was opened. Once, when the handler throws. */
48
+ rollback(reason: unknown): Promise<void>;
49
+ }
50
+ export interface AppDatabase<T, S> {
51
+ readonly binding: Binding;
52
+ request(identity: Identity, options?: RequestOptions): RequestDatabase<T, S>;
53
+ }
54
+ /** The engine over `binding`'s version, its `Database` typed by `T` (public's tables) and `S` (the other schemas'). */
55
+ export declare function createDatabase<T = Record<never, never>, S = Record<never, never>>(options: DatabaseOptions): Promise<AppDatabase<T, S>>;
56
+ export type { DatabaseAdmissionOptions } from './admission.js';
57
+ export type { AtomicClient, DBClient, DBOps, FindManyOptions, RowLock, SearchOptions } from './client.js';
58
+ export type { Embed } from './search.js';
59
+ export type { DatabaseBudget, DatabaseDiagnostics, DatabaseDiagnosticsOptions, DatabaseQueryEvent } from './diagnostics.js';
60
+ export { BUN_CLOSE_GRACE_MS, bunDriver, cancelBackendThrough, cancelOpeningThrough, closeBunPool, DriverRefused, encodeParam, encodePgArray, inlineParams, POSTGRES_JS_TYPES, postgresJsDriver, renewingBunPool, verifyDriver, type PostgresJs } from './driver.js';
61
+ export type { BunPool, BunReserved, BunSqlPool, Opening, RenewingBunPool, Statement, StatementHint, SqlDriver, SqlTx } from './driver.js';
62
+ export { makeEnvDts, ENV_BLOCK_END, ENV_BLOCK_START } from './env-gen.js';
63
+ export { BadRequest, Conflict, connectionFailure, constraintOf, DeadlockDetected, ENGINE_RAISED, HTTP_ERROR_BRAND, HttpError, isEngineRaised, isRetryable, markEngineRaised, NotFound, SerializationFailure, SQLSTATE, sqlstateOf, TooManyRequests, TransactionAborted, UniqueViolation, } from './errors.js';
64
+ export type { BadRequestData, FieldError, RetryableError, TooManyRequestsData } from './errors.js';
65
+ export type { AtomicOptions, InsertConflict, InsertManyOptions, Page, PageInfo, PageNavigation, RawPageOptions, TransactionIsolation } from './options.js';
66
+ export { buildRelations } from './relations.js';
67
+ export { isData, markData } from './data-mark.js';
68
+ export type { Relation } from './relations.js';
69
+ export { defineRepository } from './repository.js';
70
+ export type { DefaultRowKey, MissingRowKey, RepositoryOf, RepositoryTable, RowKey } from './repository.js';
71
+ export type { Identity, RequestOptions } from './request.js';
72
+ export { ago, decrement, increment, now, TxPlanError, TxRefError } from './tx-plan.js';
73
+ export type { Materialized, Ref, TxColumnExpr, TxInsertConflict, TxNow, TxRow, TxRows, TxSpan, TxTable, TxWhere } from './tx-plan.js';
74
+ export { col, isColRef, makeTypedDB, sqlFragment, withRetry } from './typed.js';
75
+ export type { AggregateInput, AggregateResult, AtomicDatabase, ColRef, Env, EnvSchemaSurface, EnvServiceDatabase, EnvTables, EnvTypedDatabase, EnvTypedTable, InsertShape, InsertValues, MutateInput, NoEnv, QueryInput, RowShape, Searchable, SearchParams, SetShape, SqlFragment, TableTypes, TablesOf, TxPlan, TxSchemaSurface, TxTables, TypedDB, TypedTable, WhereFilter, WhereOp, WithInput, } from './typed.js';
76
+ export type { UniqueWhere } from './unique.js';
@@ -0,0 +1,75 @@
1
+ export type TransactionIsolation = 'read committed' | 'repeatable read' | 'serializable';
2
+ export interface AtomicOptions {
3
+ /** Each retry opens a fresh transaction and runs the whole callback again. Zero unless given: keep external effects outside, persist an outbox instead. */
4
+ retry?: number;
5
+ isolation?: TransactionIsolation;
6
+ readOnly?: boolean;
7
+ /** Longest wait for a lock (default 5000 ms). */
8
+ lockTimeoutMs?: number;
9
+ /** statement_timeout of every statement in the scope. */
10
+ statementTimeoutMs?: number;
11
+ /** Deadline of the whole scope, queueing and retries included. Statements in flight are cancelled; an in-flight COMMIT is awaited. */
12
+ timeoutMs?: number;
13
+ signal?: AbortSignal;
14
+ }
15
+ export declare function retryBudget(value: number | undefined): number;
16
+ export declare function validateAtomicOptions(options: AtomicOptions): void;
17
+ /** A plan is a savepoint: it cannot take a new snapshot or retry a COMMIT. */
18
+ export declare function assertPlanRetry(options: {
19
+ retry?: number;
20
+ } | undefined): void;
21
+ /**
22
+ * What a collision does. Matched on `onConflict` — columns a unique
23
+ * constraint or index covers — it is ignored or overwritten ("ignore" unless
24
+ * said). `action: "ignore"` alone skips a row that collides with ANY unique
25
+ * key: Prisma's skipDuplicates, Drizzle's onConflictDoNothing(). An overwrite
26
+ * has to say which row it overwrites, so it needs the columns.
27
+ */
28
+ export type InsertConflict<Key extends string = string> = {
29
+ onConflict: readonly Key[];
30
+ action?: 'ignore' | 'update';
31
+ } | {
32
+ onConflict?: undefined;
33
+ action?: 'ignore';
34
+ };
35
+ export type InsertManyOptions<Key extends string = string> = InsertConflict<Key> & {
36
+ /** false: the affected row count, no rows serialized. */
37
+ returning?: boolean;
38
+ };
39
+ export declare function validateInsertManyOptions(opts: InsertManyOptions | undefined): void;
40
+ /** Forward and backward are exclusive; without a size, 50 rows. */
41
+ export type PageNavigation = {
42
+ first?: number;
43
+ after?: string;
44
+ last?: never;
45
+ before?: never;
46
+ } | {
47
+ last?: number;
48
+ before?: string;
49
+ first?: never;
50
+ after?: never;
51
+ };
52
+ export interface PageInfo {
53
+ startCursor: string | null;
54
+ endCursor: string | null;
55
+ hasNextPage: boolean;
56
+ hasPreviousPage: boolean;
57
+ }
58
+ export interface Page<Row> {
59
+ rows: Row[];
60
+ pageInfo: PageInfo;
61
+ }
62
+ /** page()'s untyped options; the typed tables narrow column and relation names. */
63
+ export type RawPageOptions = PageNavigation & {
64
+ orderBy?: {
65
+ column: string;
66
+ direction?: 'asc' | 'desc';
67
+ nulls?: 'first' | 'last';
68
+ } | readonly {
69
+ column: string;
70
+ direction?: 'asc' | 'desc';
71
+ nulls?: 'first' | 'last';
72
+ }[];
73
+ select?: readonly string[];
74
+ with?: Record<string, unknown>;
75
+ };
@@ -0,0 +1,15 @@
1
+ import { type SchemaDef } from '../dsl/schema.js';
2
+ export type Relation = {
3
+ name: string;
4
+ /** The target's table key: bare in public, "schema.table" otherwise. */
5
+ to: string;
6
+ kind: 'one' | 'many';
7
+ /** The foreign-key column the relation runs through — on this table for `one`, on the target for `many`. */
8
+ via: string;
9
+ };
10
+ /**
11
+ * Every table's relations by table key. A name two relations would take is
12
+ * refused, never settled by declaration order: the forward name comes from
13
+ * the column (or `as`), the reverse one from the child table (or `reverseAs`).
14
+ */
15
+ export declare function buildRelations(schemas: readonly SchemaDef[]): Map<string, Relation[]>;
@@ -0,0 +1,42 @@
1
+ /** The four members a repository asks of `Database.public.<t>`. `NoInfer`: Row comes from insert alone — findMany's generics would widen it. */
2
+ export interface RepositoryTable<Row, Insert, Patch> {
3
+ insert(data: Insert): Promise<Row>;
4
+ findMany(q: {
5
+ where: Record<string, unknown>;
6
+ limit?: number;
7
+ }): Promise<NoInfer<Row>[]>;
8
+ updateMany(q: {
9
+ where: Record<string, unknown>;
10
+ set: Patch;
11
+ }): Promise<NoInfer<Row>[]>;
12
+ deleteMany(q: {
13
+ where: Record<string, unknown>;
14
+ }): Promise<number>;
15
+ }
16
+ /** The row key by default: `id`, when the row has one. */
17
+ export type DefaultRowKey<Row> = Extract<keyof Row & string, 'id'>;
18
+ /** The refusal for a row without `id` and no `key`, with the remedy in its name. */
19
+ export interface MissingRowKey {
20
+ 'defineRepository: this row has no `id` column — name its key with { key: "..." }': never;
21
+ }
22
+ export type RowKey<Row, PK extends keyof Row & string> = [PK] extends [never] ? MissingRowKey : Row[PK];
23
+ /**
24
+ * A tenant-scoped repository. The insert payload has no tenant column: the
25
+ * repository writes it. A patch has neither the tenant column nor the row key:
26
+ * the row stays the tenant's, under its key.
27
+ */
28
+ export interface RepositoryOf<Row, Insert, Patch, K extends keyof Row & string, PK extends keyof Row & string = DefaultRowKey<Row>> {
29
+ list(tenant: Row[K]): Promise<Row[]>;
30
+ /** The tenant's row with this key, or null — absence is a value. */
31
+ find(tenant: Row[K], id: RowKey<Row, PK>): Promise<Row | null>;
32
+ insert(tenant: Row[K], values: Omit<Insert, K>): Promise<Row>;
33
+ /** The tenant's row updated; NotFound when there is none — updateScoped for null instead. */
34
+ update(tenant: Row[K], id: RowKey<Row, PK>, patch: Omit<Patch, K | PK>): Promise<Row>;
35
+ updateScoped(tenant: Row[K], id: RowKey<Row, PK>, patch: Omit<Patch, K | PK>): Promise<Row | null>;
36
+ delete(tenant: Row[K], id: RowKey<Row, PK>): Promise<void>;
37
+ }
38
+ /** An abstract base: the container resolves the subclass's name, not this anonymous one. */
39
+ export declare function defineRepository<Row, Insert, Patch, K extends keyof Row & string, PK extends keyof Row & string = DefaultRowKey<Row>>(table: RepositoryTable<Row, Insert, Patch>, opts: {
40
+ tenant: K;
41
+ key?: PK;
42
+ }): abstract new () => RepositoryOf<Row, Insert, Patch, K, PK>;
@@ -0,0 +1,54 @@
1
+ import type { Binding } from '../connect.js';
2
+ import type { Roles } from '../plan.js';
3
+ import type { DatabaseAdmission } from './admission.js';
4
+ import type { Catalog } from './catalog.js';
5
+ import type { DBClient } from './client.js';
6
+ import { type DatabaseDiagnosticsOptions } from './diagnostics.js';
7
+ import type { SqlDriver } from './driver.js';
8
+ import type { SearchRuntime } from './search.js';
9
+ /**
10
+ * Who a request is, as Postgres will see it: a signed-in caller is
11
+ * `authenticated` with their claims (sub, role, …), anyone else `anon`. An
12
+ * app whose every request is its operator's — Studio, whose tables are all
13
+ * service_role's (v2 ran it with DB_ROLE=service_role) — binds its requests
14
+ * as `service`.
15
+ */
16
+ export interface Identity {
17
+ role: 'anon' | 'authenticated' | 'service';
18
+ claims: Record<string, unknown>;
19
+ }
20
+ export interface RequestOptions {
21
+ /** Statement events, totals and a budget — allocated only when given. */
22
+ diagnostics?: DatabaseDiagnosticsOptions;
23
+ /**
24
+ * Told once, with how long it has waited, when a transaction of the
25
+ * request that cannot be given back — it wrote, locked or took a
26
+ * savepoint — has sat HELD_IDLE_MS with nothing in flight: the handler is
27
+ * waiting on something else, and a database connection waits with it.
28
+ */
29
+ heldIdle?: (waitedMs: number) => void;
30
+ /**
31
+ * The request's signal. When it aborts, the statement in flight is
32
+ * cancelled (through the driver's cancelBackend) and every later one is
33
+ * refused; the commit is not — a handler that returned has its work
34
+ * committed, unless the cancelled statement aborted the transaction.
35
+ */
36
+ signal?: AbortSignal;
37
+ }
38
+ /** The two transactions of one request, and the client over them. */
39
+ export interface RequestTransactions {
40
+ readonly client: DBClient;
41
+ /** Commits what was opened — the caller's transaction, then the service one. Once, after the handler returns. */
42
+ commit(): Promise<void>;
43
+ /** Rolls back what was opened. Once, when the handler throws. */
44
+ rollback(reason: unknown): Promise<void>;
45
+ }
46
+ export interface EngineContext {
47
+ readonly cat: Catalog;
48
+ readonly binding: Binding;
49
+ readonly roles: Roles;
50
+ readonly admission: DatabaseAdmission;
51
+ readonly cursorKey: Buffer | null;
52
+ readonly search: SearchRuntime;
53
+ }
54
+ export declare function createRequestTransactions(sql: SqlDriver, ctx: EngineContext, identity: Identity, options?: RequestOptions): RequestTransactions;
@@ -0,0 +1,55 @@
1
+ import type { SearchMetric } from '../model.js';
2
+ import type { SearchOptions } from './client.js';
3
+ /**
4
+ * Texts as vectors, by the model and in the dimensions a table declares:
5
+ * `document` for a row's text, `query` for what is searched for. The answer
6
+ * names the model that embedded them and holds one vector per text, in
7
+ * their order.
8
+ */
9
+ export type Embed = (input: {
10
+ texts: string[];
11
+ model: string;
12
+ dimensions: number;
13
+ purpose: 'document' | 'query';
14
+ }) => Promise<{
15
+ model: string;
16
+ vectors: number[][];
17
+ }>;
18
+ /** The generation of a table's vectors that serves, and the schema pgvector's types are in. */
19
+ export interface ServingVectors {
20
+ gen: number;
21
+ model: string;
22
+ dimensions: number;
23
+ metric: SearchMetric;
24
+ types: string;
25
+ }
26
+ /** What every request's search of one database shares. */
27
+ export interface SearchRuntime {
28
+ embed?: Embed;
29
+ /** The generation that serves each table ("schema.table"), as last read. */
30
+ readonly serving: Map<string, ServingVectors>;
31
+ /** When each table's search last said it fell back to words alone. */
32
+ readonly said: Map<string, number>;
33
+ log?: (line: string) => void;
34
+ }
35
+ export declare const searchRuntime: (embed: Embed | undefined, log: ((line: string) => void) | undefined) => SearchRuntime;
36
+ /** A text longer than this is cut to it before it is embedded: a model reads so much at once. */
37
+ export declare const SEARCH_TEXT_LIMIT = 30000;
38
+ /**
39
+ * Which generation serves a table now, with what its index scan is told for
40
+ * this transaction: go on past the first candidates when a filter or a policy
41
+ * turns them away (iterative_scan), and how far.
42
+ */
43
+ export declare const SERVING_SQL: string;
44
+ export declare function servingOf(row: Record<string, unknown> | undefined): ServingVectors | undefined;
45
+ /** What a caller asked, checked before anything is embedded or sent: a search box's text is the request's, and so is a 400. */
46
+ export declare function searchRequest(key: string, query: unknown, opts: SearchOptions): {
47
+ text: string;
48
+ mode: NonNullable<SearchOptions['mode']>;
49
+ limit: number;
50
+ minScore?: number;
51
+ };
52
+ /** One embedded text as pgvector reads it, or why what the embedder answered is no vector of the generation's. */
53
+ export declare function vectorLiteral(vector: unknown, dimensions: number): string;
54
+ /** The query as a vector of the generation that serves. */
55
+ export declare function embedQuery(embed: Embed, text: string, serving: ServingVectors): Promise<string>;
@@ -0,0 +1,9 @@
1
+ import type { DBClient } from './client.js';
2
+ import type { EnvTypedDatabase } from './typed.js';
3
+ /**
4
+ * The typed `Database` over one request's client — what palbase/backend
5
+ * puts in its request scope, typed by the project's tables (`T` public's,
6
+ * `S` the other schemas'). `$asService()` is built on each call, over the
7
+ * request's one service transaction.
8
+ */
9
+ export declare function typedDatabase<T, S>(client: DBClient): EnvTypedDatabase<T, S>;