@fabrika/db 0.8.2 → 0.9.0-cloudflare.2

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 (71) hide show
  1. package/package.json +44 -12
  2. package/src/admissions.ts +78 -12
  3. package/src/application-rows.ts +108 -0
  4. package/src/capability.ts +5 -0
  5. package/src/codegen-sqlite.ts +154 -0
  6. package/src/codegen.ts +59 -9
  7. package/src/database.ts +208 -56
  8. package/src/errors.ts +1 -0
  9. package/src/file-columns.ts +4 -1
  10. package/src/framework-tables.ts +28 -8
  11. package/src/framework.ts +10 -2
  12. package/src/guard.ts +8 -0
  13. package/src/index.ts +9 -1
  14. package/src/job-leases.ts +157 -37
  15. package/src/links.ts +8 -1
  16. package/src/migrations-sqlite.ts +132 -0
  17. package/src/nodes.ts +45 -9
  18. package/src/orm.ts +25 -15
  19. package/src/policy.ts +8 -2
  20. package/src/postgres.ts +26 -0
  21. package/src/registry.ts +8 -2
  22. package/src/sql.ts +15 -7
  23. package/src/sqlite-bun.ts +122 -0
  24. package/src/sqlite-migrate.ts +242 -0
  25. package/src/sqlite.ts +224 -0
  26. package/src/store.ts +336 -62
  27. package/src/stored-files.ts +149 -46
  28. package/src/tables.ts +16 -0
  29. package/src/temporal.ts +115 -9
  30. package/src/testing-shared.ts +733 -0
  31. package/src/testing-sqlite.ts +513 -0
  32. package/src/testing.ts +170 -482
  33. package/types/src/admissions.d.ts +21 -0
  34. package/types/src/application-rows.d.ts +58 -0
  35. package/types/src/builtins.d.ts +42 -0
  36. package/types/src/capability.d.ts +119 -0
  37. package/types/src/codegen-sqlite.d.ts +6 -0
  38. package/types/src/codegen.d.ts +82 -0
  39. package/types/src/database.d.ts +179 -0
  40. package/types/src/description.d.ts +109 -0
  41. package/types/src/errors.d.ts +98 -0
  42. package/types/src/file-columns.d.ts +37 -0
  43. package/types/src/file-references.d.ts +27 -0
  44. package/types/src/framework-tables.d.ts +59 -0
  45. package/types/src/framework.d.ts +37 -0
  46. package/types/src/guard.d.ts +23 -0
  47. package/types/src/identifier.d.ts +4 -0
  48. package/types/src/index.d.ts +10 -0
  49. package/types/src/job-leases.d.ts +93 -0
  50. package/types/src/links.d.ts +13 -0
  51. package/types/src/migrations-sqlite.d.ts +4 -0
  52. package/types/src/migrations.d.ts +25 -0
  53. package/types/src/nodes.d.ts +63 -0
  54. package/types/src/orm-types.d.ts +638 -0
  55. package/types/src/orm.d.ts +77 -0
  56. package/types/src/policy.d.ts +354 -0
  57. package/types/src/postgres.d.ts +12 -0
  58. package/types/src/production.d.ts +5 -0
  59. package/types/src/registry.d.ts +40 -0
  60. package/types/src/sql.d.ts +59 -0
  61. package/types/src/sqlite-bun.d.ts +13 -0
  62. package/types/src/sqlite-migrate.d.ts +28 -0
  63. package/types/src/sqlite.d.ts +24 -0
  64. package/types/src/store.d.ts +345 -0
  65. package/types/src/stored-files.d.ts +98 -0
  66. package/types/src/tables.d.ts +21 -0
  67. package/types/src/temporal.d.ts +33 -0
  68. package/types/src/testing-shared.d.ts +201 -0
  69. package/types/src/testing-sqlite.d.ts +22 -0
  70. package/types/src/testing.d.ts +35 -0
  71. package/types/src/vocabulary-tables.d.ts +225 -0
@@ -0,0 +1,21 @@
1
+ import type { DataLayerRuntime } from './store';
2
+ export interface Admissions {
3
+ /**
4
+ * Takes one from `bucket` of `endpoint` in the database's current minute; `false` when `limit` were already taken
5
+ * there. The counter and the check are one statement.
6
+ */
7
+ take(endpoint: string, bucket: string, limit: number): Promise<boolean>;
8
+ /**
9
+ * Records the digest of a challenge token `endpoint` accepted, kept for `retainMs`; `false` when it was already
10
+ * recorded and has not expired, which makes the token's second use a replay.
11
+ */
12
+ recordChallenge(endpoint: string, tokenDigest: string, retainMs: number): Promise<boolean>;
13
+ /**
14
+ * Reserves `bytes` of `endpoint`'s hourly byte budget `limit`, in the database's current hour; `false`, with nothing
15
+ * taken, when they no longer fit. The check and the counter are one statement.
16
+ */
17
+ reserveBytes(endpoint: string, bytes: number, limit: number): Promise<boolean>;
18
+ /** Gives back `bytes` of a reservation of the current hour that the request did not need. */
19
+ releaseBytes(endpoint: string, bytes: number): Promise<void>;
20
+ }
21
+ export declare function admissions(runtime: () => DataLayerRuntime): Admissions;
@@ -0,0 +1,58 @@
1
+ import { type ForeignKey, type TableModels } from './policy';
2
+ /** Why the values of a set of application rows cannot be seeded as given. */
3
+ export type ApplicationRowRefusal =
4
+ /** `table` is no application table: `mode` is its scope mode, or `null` when no policy names it. */
5
+ {
6
+ readonly kind: 'table';
7
+ readonly table: string;
8
+ readonly mode: string | null;
9
+ }
10
+ /** `column` is no column of `table`'s column list. */
11
+ | {
12
+ readonly kind: 'column';
13
+ readonly table: string;
14
+ readonly column: string;
15
+ }
16
+ /** `column` is the row's key, version or creator, which the seeding stamps. */
17
+ | {
18
+ readonly kind: 'stamped';
19
+ readonly table: string;
20
+ readonly column: string;
21
+ }
22
+ /** `column` is a file column, which the seeding fills with a live, scanned file. */
23
+ | {
24
+ readonly kind: 'file';
25
+ readonly table: string;
26
+ readonly column: string;
27
+ }
28
+ /** `column` is given an array or an object, where a column takes one value. */
29
+ | {
30
+ readonly kind: 'value';
31
+ readonly table: string;
32
+ readonly column: string;
33
+ readonly given: 'array' | 'object';
34
+ }
35
+ /** The values give `columns`, some but not all columns of the foreign key `key`. */
36
+ | {
37
+ readonly kind: 'partial-key';
38
+ readonly table: string;
39
+ readonly columns: readonly string[];
40
+ readonly key: ForeignKey;
41
+ }
42
+ /** The foreign keys that link `tables` form a cycle, so no order seeds each after the rows it links to. */
43
+ | {
44
+ readonly kind: 'cycle';
45
+ readonly tables: readonly string[];
46
+ };
47
+ export interface ApplicationRowsPlan {
48
+ /** The application tables, each after every given table it links to. */
49
+ readonly order: readonly string[];
50
+ /** Each application table's foreign keys to another given table that the values leave to the link. */
51
+ readonly links: Readonly<Record<string, readonly ForeignKey[]>>;
52
+ /** Every refusal, by table then column; a cycle last. Empty when the rows can be seeded. */
53
+ readonly refusals: readonly ApplicationRowRefusal[];
54
+ }
55
+ /** The plan of seeding one row of each table `rows` names (table → column → value). */
56
+ export declare function planApplicationRows(models: TableModels, rows: Readonly<Record<string, Readonly<Record<string, unknown>>>>): ApplicationRowsPlan;
57
+ /** A refusal in the words both the checker and the test kit use, so the two never describe one rule differently. */
58
+ export declare function applicationRowRefusalText(refusal: ApplicationRowRefusal): string;
@@ -0,0 +1,42 @@
1
+ export declare const ownKeys: {
2
+ (o: object): string[];
3
+ (o: {}): string[];
4
+ };
5
+ export declare const ownDescriptor: (o: any, p: PropertyKey) => PropertyDescriptor | undefined;
6
+ export declare const hasOwn: (o: object, v: PropertyKey) => boolean;
7
+ export declare const isArray: (arg: any) => arg is any[];
8
+ export declare const freeze: {
9
+ <T extends Function>(f: T): T;
10
+ <T extends {
11
+ [idx: string]: U | null | undefined | object;
12
+ }, U extends string | bigint | number | boolean | symbol>(o: T): Readonly<T>;
13
+ <T>(o: T): Readonly<T>;
14
+ };
15
+ export declare const prototypeOf: (o: any) => any;
16
+ export declare const isSafeInteger: (number: unknown) => boolean;
17
+ export declare const OBJECT_PROTOTYPE: unknown;
18
+ export declare const ownNames: (o: any) => string[];
19
+ export declare const ownSymbols: (o: any) => symbol[];
20
+ /**
21
+ * A frozen set of names, read through the captured `Object.hasOwn` only. Entries are defined, not assigned, so
22
+ * `__proto__` or an inherited setter cannot drop one.
23
+ */
24
+ export type NameSet = Readonly<Record<string, true>>;
25
+ export declare function nameSet(names: readonly string[]): NameSet;
26
+ export declare function inSet(set: NameSet, name: unknown): name is string;
27
+ /** The names in the order they were added. */
28
+ export declare function namesOf(set: NameSet): readonly string[];
29
+ /**
30
+ * The elements of a plain array, each passed through `parse` before it is copied; undefined for anything else: a
31
+ * subclass, an array with an own property besides its indexes and `length` (an own `map` or `Symbol.iterator`),
32
+ * a hole, or an accessor element. The input's own methods never run.
33
+ */
34
+ export declare function parseList<T>(value: unknown, parse: (item: unknown) => T): T[] | undefined;
35
+ /** A frozen copy of a string map, read through the captured `Map.prototype.forEach`. */
36
+ export declare function recordOf(map: ReadonlyMap<string, string>): Readonly<Record<string, string>>;
37
+ /** Defines an own property: unlike an assignment, it cannot hit `__proto__` or an inherited setter. */
38
+ export declare function setOwn<V>(record: Record<string, V>, key: string, value: V): void;
39
+ /** Redefines an own data property of any object, as `setOwn` does for a record. */
40
+ export declare function defineValue(target: object, key: string, value: unknown): void;
41
+ /** An own property of a record, or undefined: an inherited one is never read. */
42
+ export declare function ownEntry<V>(record: Readonly<Record<string, V>>, key: string): V | undefined;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * One authorized object, for one action. The framework mints the class that implements it; this package never
3
+ * constructs one. The `~capability` member is type-only: the implementing class declares it so that
4
+ * `Ref<'note', 'notes.read'>` and `Ref<'note', 'notes.delete'>` stay distinct types, and nothing reads it at runtime.
5
+ */
6
+ export interface Ref<Dim extends string, Action extends string> {
7
+ readonly '~capability': {
8
+ readonly kind: 'ref';
9
+ readonly dimension: Dim;
10
+ readonly action: Action;
11
+ };
12
+ }
13
+ /** The scope values of one dimension in which the caller holds one action. */
14
+ export interface Within<Dim extends string, Action extends string> {
15
+ readonly '~capability': {
16
+ readonly kind: 'within';
17
+ readonly dimension: Dim;
18
+ readonly action: Action;
19
+ };
20
+ }
21
+ /**
22
+ * One action held as a global grant: the capability of an `app` endpoint, for data of the whole application. Like a
23
+ * `Ref`, the member is type-only.
24
+ */
25
+ export interface AppCapability<Action extends string> {
26
+ readonly '~capability': {
27
+ readonly kind: 'app';
28
+ readonly action: Action;
29
+ };
30
+ }
31
+ /**
32
+ * One claimed row of one queue table, for one action: what a claim answers as `row.ref`, and what `tx.db.on(row.ref)`
33
+ * finishes the row through. Like a `Ref`, the member is type-only.
34
+ */
35
+ export interface RowRef<Table extends string, Action extends string> {
36
+ readonly '~capability': {
37
+ readonly kind: 'row';
38
+ readonly table: Table;
39
+ readonly action: Action;
40
+ };
41
+ }
42
+ export interface RefGrant {
43
+ readonly kind: 'ref';
44
+ readonly dimension: string;
45
+ readonly action: string;
46
+ readonly principal: string;
47
+ readonly id: string;
48
+ /** The values of the containers this object lies in, by dimension (`workspace → w1` for a note). */
49
+ readonly chain: ReadonlyMap<string, string>;
50
+ }
51
+ /** The values of one container dimension in which the caller holds the action: `organization → [o1]`. */
52
+ export interface ContainerGrant {
53
+ readonly dimension: string;
54
+ readonly values: readonly string[];
55
+ }
56
+ /**
57
+ * The most scope values one `Within` carries, its own and its containers' together. Each becomes one bound
58
+ * parameter of every statement the `Within` scopes; Postgres binds at most 65535.
59
+ */
60
+ export declare const MAX_WITHIN_VALUES = 1000;
61
+ export interface WithinGrant {
62
+ readonly kind: 'within';
63
+ readonly dimension: string;
64
+ readonly action: string;
65
+ readonly principal: string;
66
+ /** The values of `dimension` itself; `all` comes from a grant that is not limited to scope values. */
67
+ readonly values: readonly string[] | 'all';
68
+ /**
69
+ * The containers of `dimension` whose grants also authorise this `Within`. They stay separate from `values`, so
70
+ * every statement judges a row by the chain columns it carries now: a scope moved out of a container is out.
71
+ */
72
+ readonly containers: readonly ContainerGrant[];
73
+ /**
74
+ * The dimensions inside `dimension` (the listed object's own and the containers between) in which the caller holds
75
+ * the action: a row that lies in one of their values is listed, as a single-object read of it is allowed.
76
+ */
77
+ readonly contained: readonly ContainerGrant[];
78
+ }
79
+ /** An `app` endpoint's grant: the action everywhere in the application, which the proxy checked was held globally. */
80
+ export interface AppGrant {
81
+ readonly kind: 'app';
82
+ readonly action: string;
83
+ readonly principal: string;
84
+ }
85
+ /** A row a claim leased: the table, its key, and the containment it had then, column by column. */
86
+ export interface RowGrant {
87
+ readonly kind: 'row';
88
+ readonly table: string;
89
+ readonly action: string;
90
+ readonly principal: string;
91
+ readonly id: string;
92
+ /** The row's containment columns and their values when it was claimed: a row moved since is out of reach. */
93
+ readonly chain: ReadonlyMap<string, string>;
94
+ }
95
+ export type CapabilityGrant = RefGrant | WithinGrant | AppGrant | RowGrant;
96
+ /** How a refusal names a capability: `a Ref of note`, `a Within of workspace`, `an app capability`, `a row of outbox`. */
97
+ export declare function describeGrant(grant: CapabilityGrant): string;
98
+ /** One generation of a job's lease, held by one attempt. A write of the attempt commits only while it still holds it. */
99
+ export interface LeaseFence {
100
+ readonly job: string;
101
+ /** The attempt holding the lease. */
102
+ readonly holder: string;
103
+ readonly generation: number;
104
+ }
105
+ /**
106
+ * The framework side of a capability. `resolve` returns what an authentic capability of the current request grants,
107
+ * or throws when the value is not one, is forged, or belongs to a request that has ended or is not the current one.
108
+ * The data layer checks the kind, the dimension and the action itself, on every operation.
109
+ */
110
+ export interface CapabilityAuthority {
111
+ resolve(capability: unknown): CapabilityGrant;
112
+ /** Mints the capability of one claimed row, bound to the work running now as every capability is. */
113
+ row(grant: Omit<RowGrant, 'kind'>): unknown;
114
+ /**
115
+ * The lease the work running now must still hold for a write to commit: the job attempt's, or `null` in a
116
+ * request. Every mutation checks it inside its own transaction.
117
+ */
118
+ fence(): LeaseFence | null;
119
+ }
@@ -0,0 +1,6 @@
1
+ import { type SqliteMigrationBundle } from './sqlite-migrate';
2
+ /**
3
+ * Applies `bundles` (the framework's `fabrika`, then the application's `app`) to a fresh in-memory database and renders
4
+ * the module for `SCHEMA_FILE` from its application tables. Deterministic for given migrations.
5
+ */
6
+ export declare function generateSqliteSchema(bundles: readonly SqliteMigrationBundle[]): Promise<string>;
@@ -0,0 +1,82 @@
1
+ import { type DatabaseObject } from '@fabrika/platform-node';
2
+ import { z } from 'zod/v4';
3
+ /** Where an application keeps the generated types and column list; committed, never hand-edited. */
4
+ export declare const SCHEMA_FILE = "src/db/schema.generated.ts";
5
+ /** A column's TypeScript types: what a read returns, and what an insert or update may bind when that differs. */
6
+ export interface ScalarType {
7
+ readonly select: string;
8
+ readonly write?: string;
9
+ }
10
+ export declare class CodegenError extends Error {
11
+ name: string;
12
+ }
13
+ declare const catalogColumnSchema: z.ZodObject<{
14
+ table_name: z.ZodString;
15
+ column_name: z.ZodString;
16
+ type_name: z.ZodString;
17
+ type_type: z.ZodString;
18
+ type_category: z.ZodString;
19
+ nullable: z.ZodBoolean;
20
+ generated: z.ZodEnum<{
21
+ default: "default";
22
+ no: "no";
23
+ always: "always";
24
+ }>;
25
+ enum_labels: z.ZodArray<z.ZodString>;
26
+ }, z.core.$strip>;
27
+ export type CatalogColumn = z.infer<typeof catalogColumnSchema>;
28
+ declare const catalogForeignKeySchema: z.ZodObject<{
29
+ table_name: z.ZodString;
30
+ constraint_name: z.ZodString;
31
+ columns: z.ZodArray<z.ZodString>;
32
+ referenced_table: z.ZodString;
33
+ referenced_columns: z.ZodArray<z.ZodString>;
34
+ }, z.core.$strip>;
35
+ export type CatalogForeignKey = z.infer<typeof catalogForeignKeySchema>;
36
+ /** What codegen reads of the schema: every column, and every foreign key between its tables. */
37
+ export interface Catalog {
38
+ readonly columns: readonly CatalogColumn[];
39
+ readonly foreignKeys: readonly CatalogForeignKey[];
40
+ }
41
+ /**
42
+ * Reads the columns of the application's schema, refusing one that escapes it or holds a database object that
43
+ * `objects`, the migrations' manifest (`PostgresApplicationMigrations.objects`), does not name. The URL is a
44
+ * credential: no message here contains it.
45
+ */
46
+ export declare function introspectSchema(url: string, schema: string, objects: readonly DatabaseObject[]): Promise<readonly CatalogColumn[]>;
47
+ /** `introspectSchema`, with the foreign keys between the schema's tables. */
48
+ export declare function introspectCatalog(url: string, schema: string, objects: readonly DatabaseObject[]): Promise<Catalog>;
49
+ /** What the renderer needs of a column beyond its name, whichever database it was read from. */
50
+ export interface DescribedColumn {
51
+ readonly scalar: ScalarType;
52
+ readonly nullable: boolean;
53
+ readonly generated: 'no' | 'default' | 'always';
54
+ /** The column list field that names the column: a day, a timestamp, or (on SQLite) a boolean. */
55
+ readonly list: 'dates' | 'timestamps' | 'booleans' | undefined;
56
+ }
57
+ /** A foreign key as the renderer reads it: the columns of `table_name` that reference `referenced_table`. */
58
+ export interface ForeignKeyColumns {
59
+ readonly table_name: string;
60
+ readonly columns: readonly string[];
61
+ readonly referenced_table: string;
62
+ readonly referenced_columns: readonly string[];
63
+ }
64
+ /** A column as the renderer groups it, before its type is described. */
65
+ export interface NamedColumn {
66
+ readonly table_name: string;
67
+ readonly column_name: string;
68
+ }
69
+ /**
70
+ * The generated module: one interface per application table, the `DB` map Kysely and `defineTables` take, and the
71
+ * runtime column list with each table's foreign keys to application tables. Deterministic for a given catalog, so
72
+ * `fabrika db codegen --verify` can compare text.
73
+ */
74
+ export declare function renderSchema(catalog: readonly CatalogColumn[], foreignKeys?: readonly CatalogForeignKey[]): string;
75
+ /**
76
+ * `renderSchema` over columns of any database: `describe` gives each column's types and list field, and refuses a
77
+ * type the data layer does not read. A foreign key's `constraint_name` is not rendered; keys keep the given order.
78
+ */
79
+ export declare function renderTables<C extends NamedColumn>(catalog: readonly C[], foreignKeys: readonly ForeignKeyColumns[], describe: (column: C) => DescribedColumn): string;
80
+ /** Introspects the migrated schema, checked against the migrations' manifest, and renders the module for `SCHEMA_FILE`. */
81
+ export declare function generateSchema(url: string, schema: string, objects: readonly DatabaseObject[]): Promise<string>;
82
+ export {};
@@ -0,0 +1,179 @@
1
+ import { type Dialect, Kysely, type QueryCompiler, type RootOperationNode } from 'kysely';
2
+ import { type GuardContext } from './guard';
3
+ export type LooseDB = Record<string, Record<string, unknown>>;
4
+ /** What a statement runs on: the database's own executor, or the one of the transaction it runs in. */
5
+ export type Executor = Pick<Kysely<LooseDB>, 'executeQuery'>;
6
+ /** The SQL a database speaks: Postgres on Bun, SQLite in a Cloudflare Durable Object (ADR-0065). */
7
+ export type SqlDialect = 'postgres' | 'sqlite';
8
+ export interface TransactionOptions {
9
+ /**
10
+ * A `READ ONLY` transaction on Postgres. SQLite opens none: each statement runs in a transaction of its own, which
11
+ * rolls back a statement that wrote. A read-only session refuses writes either way.
12
+ */
13
+ readonly readOnly: boolean;
14
+ /** How long the transaction may sit idle before the database ends it; a database without the setting ignores it. */
15
+ readonly idleTimeoutMs?: number;
16
+ }
17
+ /**
18
+ * Runs `body` in one transaction, on the executor it hands it: commits when `body` resolves, rolls back when it
19
+ * rejects, and rejects with `body`'s error. Its BEGIN, COMMIT and ROLLBACK failures reject with the driver's error.
20
+ */
21
+ export type TransactionRunner = <T>(options: TransactionOptions, body: (executor: Executor) => Promise<T>) => Promise<T>;
22
+ /** What a `Database` runs on: the driver, the compiler of its dialect, and how it opens a transaction. */
23
+ export interface DatabaseEngine {
24
+ readonly dialect: SqlDialect;
25
+ readonly kysely: Kysely<LooseDB>;
26
+ /** Compiles every guarded tree; frozen, since a patch to it after the guard ran would bypass the guard. */
27
+ readonly compiler: QueryCompiler;
28
+ readonly transaction: TransactionRunner;
29
+ /** The application's schema, the connection's `search_path`; `undefined` where the database has none. */
30
+ readonly schema?: string;
31
+ }
32
+ /**
33
+ * The compiler turns the guarded tree into SQL, so a patch to it after the guard ran would bypass the guard.
34
+ * Freezing it when this module loads, before application modules, makes such a patch throw instead.
35
+ */
36
+ export declare function freezePrototypeChain(object: object): void;
37
+ /**
38
+ * `app_<id>`, the schema an application's migrations create (ADR-0017). `@fabrika/platform-node`'s
39
+ * `applicationDatabaseSchema` derives the same name for the migration runner; the two must agree.
40
+ */
41
+ export declare function applicationSchema(appId: string): string;
42
+ /** The connection the data layer runs on. It has no public query method: everything goes through a store. */
43
+ export declare class Database {
44
+ #private;
45
+ private constructor();
46
+ /**
47
+ * Any Kysely Postgres dialect: `@fabrika/db/postgres` opens Bun.SQL through it, tests use one that records statements
48
+ * instead of sending them.
49
+ */
50
+ static fromDialect(dialect: Dialect, options?: {
51
+ readonly schema?: string;
52
+ }): Database;
53
+ /** A database of another dialect (`@fabrika/db/sqlite`), with its own frozen compiler and transaction runner. */
54
+ static fromEngine(engine: DatabaseEngine): Database;
55
+ get dialect(): SqlDialect;
56
+ /** The application's schema, the connection's `search_path`; `undefined` over a bare dialect and on SQLite. */
57
+ get schema(): string | undefined;
58
+ close(): Promise<void>;
59
+ }
60
+ /**
61
+ * Opens the pool's first connection, which stays open. A pool with one open connection makes a statement wait for it
62
+ * when the database refuses another (Bun 1.3.14), so only a pool with none fails on a database at its limit: the test
63
+ * kit opens it before a file's first case, where a refusal is waited out instead of failing a case.
64
+ */
65
+ export declare function openFirstConnection(database: Database): Promise<void>;
66
+ /** The connection a session runs on, and whether the request running now may only read. */
67
+ export interface SessionSource {
68
+ readonly database: Database;
69
+ readonly readOnly?: () => boolean;
70
+ }
71
+ /**
72
+ * The one way to a session: every statement of a request — a store's, a locator's, a framework table's — asks the
73
+ * runtime whether the request is read-only. A read-only session refuses every write and lock before it reaches the
74
+ * database, and reads in `READ ONLY` transactions.
75
+ */
76
+ export declare function sessionOf(source: SessionSource): Session;
77
+ /** What a session reports to, and asks, the transaction it runs in. */
78
+ interface SessionHooks {
79
+ /** Hears of every statement the database refused: in a transaction, Postgres aborts the rest of it. */
80
+ readonly failed?: () => void;
81
+ /** Whether the transaction is still the work's: a session kept past it runs nothing on a connection given back. */
82
+ readonly open?: () => boolean;
83
+ }
84
+ /**
85
+ * Runs guarded operation trees; the only path from the layer to the connection. A driver failure leaves it as a
86
+ * `DatabaseError`, which carries the SQLSTATE and nothing the driver said about the row.
87
+ */
88
+ export declare class Session {
89
+ #private;
90
+ constructor(executor: Executor, engine: DatabaseEngine, inTransaction: boolean, readOnly: boolean, hooks?: SessionHooks);
91
+ get readOnly(): boolean;
92
+ /** The dialect of the database this session runs on: a framework statement picks its text by it. */
93
+ get dialect(): SqlDialect;
94
+ /** Fails the transaction this session runs in, so it commits nothing: a statement refused before it was sent. */
95
+ fail(): void;
96
+ run<O>(node: RootOperationNode, context: GuardContext): Promise<O[]>;
97
+ /**
98
+ * Runs `work` in one transaction (`READ ONLY` in a read-only session); inside one, it runs in the transaction already
99
+ * open. When `signal` aborts, the transaction rolls back and its connection goes back to the pool at once, whether
100
+ * `work` returned or not; its session then runs nothing more.
101
+ */
102
+ transaction<T>(work: (session: Session) => Promise<T>, signal?: AbortSignal): Promise<T>;
103
+ }
104
+ /** How long an invocation's transaction may sit idle before Postgres ends it, and its connection with it. */
105
+ export declare const INVOCATION_IDLE_TIMEOUT_MS = 30000;
106
+ /** A writing invocation that holds its object's storage transaction longer than this logs one line. */
107
+ export declare const TRANSACTION_HELD_LOG_MS = 1000;
108
+ /** A writing invocation that holds its object's storage transaction longer than this is also reported. */
109
+ export declare const TRANSACTION_HELD_REPORT_MS = 5000;
110
+ /** Where an enclosure tells how long it held its object: every other event of the object waited that long. */
111
+ export interface HoldWatch {
112
+ /** The site the invocation serves, which the log line names. */
113
+ readonly site: string;
114
+ /** Hears a hold longer than `TRANSACTION_HELD_REPORT_MS`, in whole ms. */
115
+ readonly held: (heldMs: number) => void;
116
+ }
117
+ /**
118
+ * The one transaction of an invocation (a request, a webhook delivery): opened by its first statement, then ended
119
+ * once by the framework — committed after the handler returned and its output passed validation, rolled back
120
+ * otherwise. Every handle of the invocation runs in `session()`. Ending it and giving its connection back are two
121
+ * states: once ended no statement runs in it, and the connection is back in the pool whether the COMMIT succeeded,
122
+ * failed, or the connection died.
123
+ */
124
+ export declare class InvocationTransaction {
125
+ #private;
126
+ /** `now` is the clock an enclosure's hold is measured by. */
127
+ constructor(database: Database, readOnly: boolean, now?: () => number);
128
+ /** A `READ ONLY` transaction: a query's or an export's. */
129
+ get readOnly(): boolean;
130
+ /** Whether the transaction was opened: an invocation that reached no data commits nothing. */
131
+ get opened(): boolean;
132
+ /** Marks it failed, so it commits nothing: a statement refused before it was sent, even when the handler caught it. */
133
+ fail(): void;
134
+ /** No statement runs in it any more. */
135
+ get ended(): boolean;
136
+ /** Its connection is back in the pool (or it never took one). */
137
+ get released(): boolean;
138
+ /**
139
+ * Runs `work`: the invocation from its handler to its `commit()`. On SQLite a writing invocation's transaction
140
+ * encloses `work`, opened before it: a Durable Object's transaction holds back every other event, the answers to the
141
+ * object's own I/O included, until its callback ends, so the handler runs inside it. There `commit()` and `rollback()`
142
+ * decide, the COMMIT runs once `work` returned, and a failed COMMIT rejects with a `DatabaseError`. A read-only
143
+ * invocation, and every invocation elsewhere, runs `work` as is: a read-only one on SQLite holds no storage
144
+ * transaction, so the object serves other events while it awaits.
145
+ *
146
+ * Only the SQLite enclosure is measured for `watch`, from entering the transaction until it settled: it holds the
147
+ * whole object, where a Postgres transaction holds one pooled connection and other requests go on.
148
+ */
149
+ enclose<T>(work: () => Promise<T>, watch: HoldWatch): Promise<T>;
150
+ /** The session the invocation's statements run in; the first call opens the transaction. */
151
+ session(): Promise<Session>;
152
+ /**
153
+ * Commits what the invocation wrote. A statement the database refused aborted the transaction, so it rolls back
154
+ * and throws instead: Postgres would answer the COMMIT with a silent ROLLBACK. A failed COMMIT throws a
155
+ * `DatabaseError`, after the connection is back in the pool.
156
+ */
157
+ commit(): Promise<void>;
158
+ /** Discards what the invocation wrote; idempotent, never throws, and returns once the connection is back (inside `enclose`, it only decides). */
159
+ rollback(): Promise<void>;
160
+ }
161
+ /**
162
+ * A scalar parameter of a framework statement; Bun.SQL mis-binds arrays and JSON strings. No Date: these texts skip
163
+ * the compiler that binds one, and a lease time is the database's clock, never the process's.
164
+ */
165
+ export type FixedParameter = string | number | null;
166
+ /**
167
+ * One of the framework's own fixed statements (`job-leases.ts`): a constant text with scalar parameters. They need
168
+ * what the guard refuses on purpose (`now()`, `set_config`, `pg_backend_pid()`, `on conflict`), so they bypass it;
169
+ * no subpath exports this, and no text is ever built from a caller's value.
170
+ */
171
+ export declare function runFixedStatement(session: Session, text: string, parameters: readonly FixedParameter[]): Promise<readonly unknown[]>;
172
+ /** A bound parameter of an application's own statement (`sql.ts`), after its value was encoded for the driver. */
173
+ export type SqlParameter = string | number | boolean | bigint | null;
174
+ /**
175
+ * One `ctx.sql` statement, whose text `sql.ts` classified and whose values are bound parameters only. It runs through the
176
+ * session's one path to the connection, so a statement the database refuses fails the invocation it runs in.
177
+ */
178
+ export declare function runApplicationStatement(session: Session, text: string, parameters: readonly SqlParameter[]): Promise<readonly unknown[]>;
179
+ export {};
@@ -0,0 +1,109 @@
1
+ import { type NameSet } from './builtins';
2
+ export type Primitive = string | number | bigint | boolean | Date;
3
+ export type ComparisonOperator = '=' | '<>' | '<' | '<=' | '>' | '>=';
4
+ export type PatternOperator = 'like' | 'ilike' | 'not like' | 'not ilike';
5
+ export type ListOperator = 'in' | 'not in';
6
+ export type NullOperator = 'is' | 'is not';
7
+ export type AllowedOperator = ComparisonOperator | PatternOperator | ListOperator | NullOperator;
8
+ export interface Compare {
9
+ readonly kind: 'compare';
10
+ readonly column: string;
11
+ readonly operator: AllowedOperator;
12
+ readonly value: Primitive | readonly Primitive[] | null;
13
+ }
14
+ /** A case-insensitive substring match; the text's `%`, `_` and `\` match literally. */
15
+ export interface Contains {
16
+ readonly kind: 'contains';
17
+ readonly column: string;
18
+ readonly text: string;
19
+ }
20
+ export interface AnyOf {
21
+ readonly kind: 'any';
22
+ readonly conditions: readonly (Compare | Contains)[];
23
+ }
24
+ export type Condition = Compare | Contains | AnyOf;
25
+ /** What a select may name: the visible columns of its table and of the children it may join. */
26
+ export interface TableShape {
27
+ readonly table: string;
28
+ readonly visible: NameSet;
29
+ readonly joinable: Readonly<Record<string, JoinShape>>;
30
+ /** The `date` columns, which a read answers as `YYYY-MM-DD` text. */
31
+ readonly dates: NameSet;
32
+ }
33
+ export interface JoinShape {
34
+ readonly shape: TableShape;
35
+ /** The child's read actions: a select of the parent joins it only with one of them. */
36
+ readonly readActions: readonly string[];
37
+ /** The composite foreign key: child column = parent column, for each pair. */
38
+ readonly on: readonly {
39
+ readonly childColumn: string;
40
+ readonly parentColumn: string;
41
+ }[];
42
+ }
43
+ export interface JoinState {
44
+ readonly join: JoinShape;
45
+ readonly select: SelectState;
46
+ }
47
+ export interface SelectState {
48
+ /** Identifies the description a callback received, so it cannot return one from elsewhere. */
49
+ readonly origin: object;
50
+ readonly table: string;
51
+ readonly columns: readonly string[] | undefined;
52
+ readonly conditions: readonly Condition[];
53
+ readonly orderBy: readonly {
54
+ readonly column: string;
55
+ readonly direction: 'asc' | 'desc';
56
+ }[];
57
+ readonly limit: number | undefined;
58
+ readonly offset: number | undefined;
59
+ readonly joins: readonly JoinState[];
60
+ }
61
+ type Comparable<V> = Exclude<V, null | undefined>;
62
+ type Columns<Row> = keyof Row & string;
63
+ type StringColumn<Row> = {
64
+ [K in Columns<Row>]: Comparable<Row[K]> extends string ? K : never;
65
+ }[Columns<Row>];
66
+ type NullableColumn<Row> = {
67
+ [K in Columns<Row>]: null extends Row[K] ? K : never;
68
+ }[Columns<Row>];
69
+ export type ConditionTuple<Row> = {
70
+ [K in Columns<Row>]: readonly [K, ComparisonOperator, Comparable<Row[K]>] | readonly [K, ListOperator, readonly Comparable<Row[K]>[]];
71
+ }[Columns<Row>] | {
72
+ [K in StringColumn<Row>]: readonly [K, PatternOperator | 'contains', string];
73
+ }[StringColumn<Row>];
74
+ type Prefixed<C extends string, J> = {
75
+ [K in keyof J & string as `${C}.${K}`]: J[K];
76
+ };
77
+ export type NoColumns = Record<never, never>;
78
+ /**
79
+ * The only query surface a store callback sees: a frozen description, not a Kysely builder. There is nothing to
80
+ * execute, no raw SQL entry point, and no way to name a table other than this one and its declared children.
81
+ */
82
+ export declare class ScopedSelect<Row, Selected, Children> {
83
+ #private;
84
+ constructor(token: symbol, state: SelectState, shape: TableShape);
85
+ /** The columns to return; without it, every visible column. */
86
+ columns<const C extends Columns<Row>>(columns: readonly C[]): ScopedSelect<Row, Pick<Row, C>, Children>;
87
+ where<C extends Columns<Row>>(column: C, operator: ComparisonOperator, value: Comparable<Row[C]>): ScopedSelect<Row, Selected, Children>;
88
+ where<C extends Columns<Row>>(column: C, operator: ListOperator, values: readonly Comparable<Row[C]>[]): ScopedSelect<Row, Selected, Children>;
89
+ where<C extends StringColumn<Row>>(column: C, operator: PatternOperator, pattern: string): ScopedSelect<Row, Selected, Children>;
90
+ where<C extends NullableColumn<Row>>(column: C, operator: NullOperator, value: null): ScopedSelect<Row, Selected, Children>;
91
+ /** One OR group of conditions on this table's columns. */
92
+ whereAny(conditions: readonly ConditionTuple<Row>[]): ScopedSelect<Row, Selected, Children>;
93
+ /** A case-insensitive substring match in which `%` and `_` in the text match themselves. */
94
+ contains(column: StringColumn<Row>, text: string): ScopedSelect<Row, Selected, Children>;
95
+ orderBy(column: Columns<Row>, direction?: 'asc' | 'desc'): ScopedSelect<Row, Selected, Children>;
96
+ limit(limit: number): ScopedSelect<Row, Selected, Children>;
97
+ offset(offset: number): ScopedSelect<Row, Selected, Children>;
98
+ /** An inner join to a declared child; its columns come back prefixed (`note_comments.body`). */
99
+ join<C extends keyof Children & string, J>(child: C, build: (q: ScopedSelect<Children[C], Children[C], NoColumns>) => ScopedSelect<Children[C], J, NoColumns>): ScopedSelect<Row, Selected & Prefixed<C, J>, Children>;
100
+ }
101
+ /** A column the shape lets a query name; a hidden one is refused like a missing one. */
102
+ export declare function columnOf(shape: TableShape, name: unknown): string;
103
+ /** One `[column, operator, value]` condition over a caller's values, checked before anything reads it further. */
104
+ export declare function parseCondition(shape: TableShape, column: unknown, operator: unknown, value: unknown): Compare | Contains;
105
+ export declare function startSelect<Row, Children>(shape: TableShape): ScopedSelect<Row, Row, Children>;
106
+ /** The description a callback returned; it must derive from the one it received. */
107
+ export declare function finishSelect<Row, Children>(built: unknown, start: ScopedSelect<Row, Row, Children>): SelectState;
108
+ export declare function isPrimitive(value: unknown): value is Primitive;
109
+ export {};