@geonosis/db 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.
@@ -0,0 +1,336 @@
1
+ import { QueryAnswer } from '@geonosis/conformance';
2
+ export { QueryAnswer } from '@geonosis/conformance';
3
+
4
+ type AppRoleOptions = {
5
+ /**
6
+ * How long a session may sit inside an open transaction. No default: the number that fits is a
7
+ * fact about one deployment's traffic, and a wrong one closes a working connection mid-request.
8
+ */
9
+ idleInTransactionTimeout?: string;
10
+ password: string;
11
+ /** The name the application logs in as. A parameter, because a default here names one repo. */
12
+ role: string;
13
+ schema?: string;
14
+ /** How long one of that role's statements may run. No default, for the same reason. */
15
+ statementTimeout?: string;
16
+ };
17
+ /**
18
+ * The role an application connects as, as statements to run after every migration.
19
+ *
20
+ * NOSUPERUSER and NOBYPASSRLS are the point: the policies are the wall, and a superuser or a role
21
+ * with BYPASSRLS walks around it without a single query changing. The migrator stays the owner.
22
+ */
23
+ declare const appRoleStatements: (options: AppRoleOptions) => string[];
24
+ /** The same database, entered as the application role rather than as its owner. */
25
+ declare const appConnectionString: (ownerConnectionString: string, credentials: {
26
+ password: string;
27
+ role: string;
28
+ }) => string;
29
+
30
+ /**
31
+ * A parameterised statement: the text a driver sends, and the values that fill its placeholders.
32
+ *
33
+ * Apart, never interpolated. A provider writes SQL for a database whose connection belongs to
34
+ * somebody else, and numbered placeholders are the only spelling that survives a value it did not
35
+ * choose.
36
+ */
37
+ type Statement = {
38
+ params?: readonly unknown[];
39
+ text: string;
40
+ };
41
+
42
+ /**
43
+ * Query in a session — the whole port a domain provider needs from a database.
44
+ *
45
+ * It says nothing about connections, pools, transactions or tenancy on purpose: the consumer owns
46
+ * those, and a provider that could reach them would be deciding how somebody else's application
47
+ * talks to their database. What arrives here is already inside whatever session the caller opened.
48
+ */
49
+ type Executor = {
50
+ execute: (statement: Statement) => Promise<QueryAnswer>;
51
+ };
52
+ /** An executor that can also open a transaction: a handle, rather than a session inside one. */
53
+ type Connection = Executor & {
54
+ transaction: <Result>(run: (tx: Executor) => Promise<Result>) => Promise<Result>;
55
+ };
56
+
57
+ type ConformanceCase = {
58
+ name: string;
59
+ run: () => Promise<void>;
60
+ };
61
+ /** A handle that reports every statement it sends, including the ones a transaction is made of. */
62
+ type RecordingConnection = {
63
+ close: () => Promise<void>;
64
+ connection: Connection;
65
+ statements: string[];
66
+ };
67
+ /** As much of a session seam as the exam asks about. */
68
+ type SeamUnderTest = {
69
+ inOps: <Result>(executor: Executor, run: (tx: Executor) => Promise<Result>) => Promise<Result>;
70
+ inTenant: <Result>(executor: Executor, tenantId: string, run: (tx: Executor) => Promise<Result>) => Promise<Result>;
71
+ };
72
+ /**
73
+ * A table under the policies under test, whose only required columns are these two.
74
+ *
75
+ * The exam writes to it, so it cannot be one of the consumer's own tables with its own NOT NULLs;
76
+ * and it has to be a real table under the real policies, or the exam is grading a fixture.
77
+ */
78
+ type ProbeTable = {
79
+ idColumn: string;
80
+ name: string;
81
+ schema?: string;
82
+ tenantColumn: string;
83
+ };
84
+ type SessionSubject = {
85
+ /** A handle OUTSIDE every session: what the policies alone let through. */
86
+ connection: Connection;
87
+ /** The tables whose FORCE is checked. The probe table alone, when nothing else is named. */
88
+ guardedTables?: readonly string[];
89
+ probe: ProbeTable;
90
+ recording: () => Promise<RecordingConnection>;
91
+ /** Empty the probe table. Whatever role can do that — the exam's own rows are its business. */
92
+ reset: () => Promise<void>;
93
+ seam: SeamUnderTest;
94
+ };
95
+ /**
96
+ * The tenant wall, as the source proof took it: what a query with NO predicate can still see.
97
+ *
98
+ * Every read here is unqualified on purpose. A test that filters by tenant proves its own WHERE
99
+ * clause works; only an unfiltered one asks what the policy lets through.
100
+ */
101
+ declare const tenantIsolationConformance: (subject: SessionSubject) => ConformanceCase[];
102
+ /**
103
+ * The census: a read costs what it says.
104
+ *
105
+ * Four statements — the transaction, the tenant, the read, the commit. It is a literal number
106
+ * because the failures it catches are structural: a savepoint per query, a set_config per query, or
107
+ * a second transaction nested inside the first. Twenty scoped reads against a pool of five is how
108
+ * the source repo found this one.
109
+ */
110
+ declare const statementCensusConformance: (subject: SessionSubject) => ConformanceCase[];
111
+ /**
112
+ * Two tenants at once on one pool — the case a suite that runs one tenant at a time never reaches,
113
+ * and the one a pooled connection carrying a stale setting would fail.
114
+ */
115
+ declare const concurrentTenantsConformance: (subject: SessionSubject) => ConformanceCase[];
116
+ /**
117
+ * ENABLE alone exempts the table's OWNER, and the owner is the role that migrates and the role an
118
+ * ops sweep connects as. A wall only the application is behind is a wall with a door in it.
119
+ */
120
+ declare const forcedRowLevelSecurityConformance: (subject: SessionSubject) => ConformanceCase[];
121
+ /** The whole exam: what a consumer runs against their own session before depending on it. */
122
+ declare const sessionConformance: (subject: SessionSubject) => ConformanceCase[];
123
+
124
+ type OpenOptions = {
125
+ /**
126
+ * Every statement this handle sends, as it is sent — the transaction's own begin and commit
127
+ * included, because a count of round trips that leaves those out is not a count of round trips.
128
+ */
129
+ onStatement?: (statement: string) => void;
130
+ /**
131
+ * A pooled connection that died while nobody was using it. Nothing is waiting on it and the pool
132
+ * replaces it by itself, so this is how such a death is seen at all rather than something to act
133
+ * on.
134
+ */
135
+ onIdleError?: (error: unknown) => void;
136
+ /**
137
+ * How many connections one handle may hold. No default: the number that fits is the fan-out of
138
+ * one invocation against the concurrency the database was sized for, and a driver already has
139
+ * an opinion when nobody states one.
140
+ */
141
+ poolSize?: number;
142
+ };
143
+ /** A handle, and the way to give its connections back. */
144
+ type OpenConnection = {
145
+ close: () => Promise<void>;
146
+ connection: Connection;
147
+ };
148
+ /** The one thing in a deployment that knows which package speaks the wire protocol. */
149
+ type Driver = {
150
+ migrate: (connectionString: string, migrationsFolder: string) => Promise<void>;
151
+ open: (connectionString: string, options?: OpenOptions) => OpenConnection;
152
+ };
153
+ type Connections = {
154
+ /** Whether this call is running inside an invocation frame. */
155
+ inInvocation: () => boolean;
156
+ /** A handle outside every frame, for a caller who will close it themselves. */
157
+ open: () => OpenConnection;
158
+ /** The handle this invocation is using, opened on first ask. */
159
+ sessionDb: () => Connection;
160
+ withConnection: <Result>(run: () => Promise<Result>, release?: (closing: Promise<void>) => void) => Promise<Result>;
161
+ };
162
+ type ConnectionsConfig = {
163
+ connectionString: string;
164
+ driver: Driver;
165
+ open?: OpenOptions;
166
+ };
167
+ /**
168
+ * The handles of one database, and the discipline that closes them.
169
+ *
170
+ * A serverless invocation hibernates and dies; a handle opened once per process is dead by the
171
+ * second request, and a handle opened per query is a pool per query. One per invocation is the
172
+ * lifetime that matches, and the frame is what makes "this invocation" a thing the code can ask
173
+ * about rather than a thing the caller has to thread through.
174
+ */
175
+ declare const createConnections: (config: ConnectionsConfig) => Connections;
176
+ /**
177
+ * The `scope.perStep` hook a workflow engine asks for, answered with a connection per unit of work.
178
+ *
179
+ * A durable run hibernates between steps, so the handle its first step opened is dead by the fifth:
180
+ * each step body and each undo opens its own. The engine's own writes — the trail, the closing
181
+ * batch — are not units of work and stay outside, on whatever connection the runner holds.
182
+ *
183
+ * The type is not imported. `@geonosis/workflows` is a sibling foundation and the floors forbid an
184
+ * edge between two of them in either direction; what crosses is the shape.
185
+ */
186
+ declare const perStepConnection: (connections: Pick<Connections, "withConnection">) => {
187
+ perStep: () => <Output>(body: () => Promise<Output>) => Promise<Output>;
188
+ };
189
+
190
+ /**
191
+ * What this seam throws when it refuses, so a caller can catch it by class (#211).
192
+ *
193
+ * Every sibling has one — `WorkflowsError`, `ConformanceFailure`, `LearningError` — and this
194
+ * package threw a bare `Error`, which left a caller matching prose and a conformance case unable to
195
+ * tell a refusal from a driver that simply crashed (#213: a `TypeError` reads exactly like "the
196
+ * seam said no"). The name is on the instance so a log line says which package refused.
197
+ */
198
+ declare class DbRefusal extends Error {
199
+ constructor(message: string, options?: {
200
+ cause?: unknown;
201
+ });
202
+ }
203
+
204
+ /**
205
+ * As much of `pg` as this driver uses, declared here rather than imported.
206
+ *
207
+ * `pg` is an OPTIONAL peer, and a type imported from it would put it in this package's published
208
+ * `.d.ts` — a consumer who wanted the executor port and nothing else would then need `@types/pg` to
209
+ * typecheck. Structural is also what lets a Workers deployment hand in its own patched copy.
210
+ */
211
+ type PgModuleLike = {
212
+ Pool: new (config: {
213
+ connectionString: string;
214
+ max?: number;
215
+ }) => PgPoolLike;
216
+ };
217
+ type PgPoolLike = {
218
+ connect: () => Promise<PgClientLike>;
219
+ end: () => Promise<void>;
220
+ on: (event: 'error', listener: (error: unknown) => void) => void;
221
+ query: (text: string, values?: unknown[]) => Promise<{
222
+ rows: unknown[];
223
+ }>;
224
+ };
225
+ type PgClientLike = {
226
+ query: (text: string, values?: unknown[]) => Promise<{
227
+ rows: unknown[];
228
+ }>;
229
+ release: () => void;
230
+ };
231
+ type NodePostgresOptions = {
232
+ /**
233
+ * The table recording which migration files have been applied. Named after this package so it
234
+ * cannot collide with a consumer's own; a consumer whose migrator already keeps one names theirs.
235
+ */
236
+ migrationsTable?: string;
237
+ /** The `pg` module itself, for a runtime whose copy is patched. Loaded on demand when absent. */
238
+ pg?: PgModuleLike;
239
+ };
240
+ /**
241
+ * The one file in this package that names `pg`.
242
+ *
243
+ * It is a factory rather than a constant because the module is loaded on demand: a consumer using
244
+ * the executor port with their own database library never resolves `pg` at all.
245
+ */
246
+ declare const nodePostgresDriver: (options?: NodePostgresOptions) => Promise<Driver>;
247
+
248
+ /**
249
+ * The two session settings a tenant wall is built on, spelled once at the composition root.
250
+ *
251
+ * Neither has a default. A default here would be one repo's name — `app.org_id` at the repo this
252
+ * came from — compiled into every other repo's policies, and the seam and the policies must agree
253
+ * on it exactly or the wall is open while every test still passes.
254
+ */
255
+ type SessionSettings = {
256
+ /** What maintenance sets to see every tenant, through a lever no request path can reach. */
257
+ opsSetting: string;
258
+ /** What the ops setting holds while the lever is pulled. */
259
+ opsValue?: string;
260
+ /** What a transaction sets to name the tenant it is open for, and what the policies read. */
261
+ tenantSetting: string;
262
+ };
263
+ declare const DEFAULT_OPS_VALUE = "on";
264
+
265
+ type FreezeRegister = {
266
+ /** The column of that register holding the tenant a row names. */
267
+ tenantColumn: string;
268
+ /** The table listing the tenants whose rows are read-only. */
269
+ table: string;
270
+ };
271
+ type TenantPolicyNames = {
272
+ freeze?: string;
273
+ isolation?: string;
274
+ opsMaintenance?: string;
275
+ };
276
+ type TenantPolicyOptions = {
277
+ /**
278
+ * A register of frozen tenants, and the restrictive policy that consults it. Off unless named:
279
+ * a restrictive policy is a second predicate every statement on the table pays for, and freezing
280
+ * a tenant's rows is a product feature rather than a property of tenancy.
281
+ */
282
+ freeze?: FreezeRegister;
283
+ /**
284
+ * What these policies are called. A consumer whose tables already carry a policy of the same name
285
+ * would otherwise have it dropped by the line above the one that creates ours.
286
+ */
287
+ names?: TenantPolicyNames;
288
+ schema?: string;
289
+ settings: SessionSettings;
290
+ /** The column holding the tenant a row belongs to. */
291
+ tenantColumn: string;
292
+ };
293
+ /**
294
+ * The row-level-security DDL for one tenant table, as statements to apply after the migrator ran.
295
+ *
296
+ * It is DDL rather than schema-builder calls because FORCE is not sayable in one: drizzle-orm
297
+ * 0.45.2 offers `enableRLS()` and drizzle-kit 0.31.10 emits `ENABLE ROW LEVEL SECURITY`, and
298
+ * neither package contains the string `FORCE ROW LEVEL SECURITY` anywhere (measured 2026-09-02).
299
+ * ENABLE alone exempts the table's OWNER — which is the role a migration and an ops sweep connect
300
+ * as — so a wall that is only enabled is a wall with a door in it for the busiest user.
301
+ *
302
+ * Every statement is re-runnable, because it runs again after the next migration: a policy is
303
+ * dropped if present before it is created. Apply the list in ONE transaction, or a failure halfway
304
+ * leaves a table whose policy was dropped and not remade.
305
+ */
306
+ declare const tenantPolicies: (table: string, options: TenantPolicyOptions) => string[];
307
+
308
+ /** A query that takes its session as an argument: what the seam wraps and what a provider writes. */
309
+ type Query<Params, Result> = (executor: Executor, params: Params) => Promise<Result>;
310
+ type SessionSeam = {
311
+ inOps: <Result>(executor: Executor, run: (tx: Executor) => Promise<Result>) => Promise<Result>;
312
+ inTenant: <Result>(executor: Executor, tenantId: string, run: (tx: Executor) => Promise<Result>) => Promise<Result>;
313
+ scoped: <Params extends {
314
+ tenantId: string;
315
+ }, Result>(query: Query<Params, Result>) => Query<Params, Result>;
316
+ scopedAsOps: <Params, Result>(query: Query<Params, Result>) => Query<Params, Result>;
317
+ };
318
+ type SessionSeamConfig = {
319
+ /**
320
+ * How long a statement may run under the ops lever, as Postgres reads it. No default: a sweep's
321
+ * budget is a fact about one deployment's data, and the value that fits belongs to whoever runs
322
+ * it. Absent, the connection's own timeout stands.
323
+ */
324
+ opsStatementTimeout?: string;
325
+ settings: SessionSettings;
326
+ };
327
+ /**
328
+ * The seam every query crosses: one transaction, the scope named as its first statement.
329
+ *
330
+ * `set_config(..., true)` is local to the transaction, which is what makes this safe on a pool —
331
+ * the name is gone when the transaction ends, and a connection handed to the next request carries
332
+ * nothing. Naming it second would leave the statements before it running under no tenant at all.
333
+ */
334
+ declare const createSessionSeam: (config: SessionSeamConfig) => SessionSeam;
335
+
336
+ export { type AppRoleOptions, type ConformanceCase, type Connection, type Connections, type ConnectionsConfig, DEFAULT_OPS_VALUE, DbRefusal, type Driver, type Executor, type FreezeRegister, type NodePostgresOptions, type OpenConnection, type OpenOptions, type PgClientLike, type PgModuleLike, type PgPoolLike, type ProbeTable, type Query, type RecordingConnection, type SeamUnderTest, type SessionSeam, type SessionSeamConfig, type SessionSettings, type SessionSubject, type Statement, type TenantPolicyNames, type TenantPolicyOptions, appConnectionString, appRoleStatements, concurrentTenantsConformance, createConnections, createSessionSeam, forcedRowLevelSecurityConformance, nodePostgresDriver, perStepConnection, sessionConformance, statementCensusConformance, tenantIsolationConformance, tenantPolicies };
@@ -0,0 +1,336 @@
1
+ import { QueryAnswer } from '@geonosis/conformance';
2
+ export { QueryAnswer } from '@geonosis/conformance';
3
+
4
+ type AppRoleOptions = {
5
+ /**
6
+ * How long a session may sit inside an open transaction. No default: the number that fits is a
7
+ * fact about one deployment's traffic, and a wrong one closes a working connection mid-request.
8
+ */
9
+ idleInTransactionTimeout?: string;
10
+ password: string;
11
+ /** The name the application logs in as. A parameter, because a default here names one repo. */
12
+ role: string;
13
+ schema?: string;
14
+ /** How long one of that role's statements may run. No default, for the same reason. */
15
+ statementTimeout?: string;
16
+ };
17
+ /**
18
+ * The role an application connects as, as statements to run after every migration.
19
+ *
20
+ * NOSUPERUSER and NOBYPASSRLS are the point: the policies are the wall, and a superuser or a role
21
+ * with BYPASSRLS walks around it without a single query changing. The migrator stays the owner.
22
+ */
23
+ declare const appRoleStatements: (options: AppRoleOptions) => string[];
24
+ /** The same database, entered as the application role rather than as its owner. */
25
+ declare const appConnectionString: (ownerConnectionString: string, credentials: {
26
+ password: string;
27
+ role: string;
28
+ }) => string;
29
+
30
+ /**
31
+ * A parameterised statement: the text a driver sends, and the values that fill its placeholders.
32
+ *
33
+ * Apart, never interpolated. A provider writes SQL for a database whose connection belongs to
34
+ * somebody else, and numbered placeholders are the only spelling that survives a value it did not
35
+ * choose.
36
+ */
37
+ type Statement = {
38
+ params?: readonly unknown[];
39
+ text: string;
40
+ };
41
+
42
+ /**
43
+ * Query in a session — the whole port a domain provider needs from a database.
44
+ *
45
+ * It says nothing about connections, pools, transactions or tenancy on purpose: the consumer owns
46
+ * those, and a provider that could reach them would be deciding how somebody else's application
47
+ * talks to their database. What arrives here is already inside whatever session the caller opened.
48
+ */
49
+ type Executor = {
50
+ execute: (statement: Statement) => Promise<QueryAnswer>;
51
+ };
52
+ /** An executor that can also open a transaction: a handle, rather than a session inside one. */
53
+ type Connection = Executor & {
54
+ transaction: <Result>(run: (tx: Executor) => Promise<Result>) => Promise<Result>;
55
+ };
56
+
57
+ type ConformanceCase = {
58
+ name: string;
59
+ run: () => Promise<void>;
60
+ };
61
+ /** A handle that reports every statement it sends, including the ones a transaction is made of. */
62
+ type RecordingConnection = {
63
+ close: () => Promise<void>;
64
+ connection: Connection;
65
+ statements: string[];
66
+ };
67
+ /** As much of a session seam as the exam asks about. */
68
+ type SeamUnderTest = {
69
+ inOps: <Result>(executor: Executor, run: (tx: Executor) => Promise<Result>) => Promise<Result>;
70
+ inTenant: <Result>(executor: Executor, tenantId: string, run: (tx: Executor) => Promise<Result>) => Promise<Result>;
71
+ };
72
+ /**
73
+ * A table under the policies under test, whose only required columns are these two.
74
+ *
75
+ * The exam writes to it, so it cannot be one of the consumer's own tables with its own NOT NULLs;
76
+ * and it has to be a real table under the real policies, or the exam is grading a fixture.
77
+ */
78
+ type ProbeTable = {
79
+ idColumn: string;
80
+ name: string;
81
+ schema?: string;
82
+ tenantColumn: string;
83
+ };
84
+ type SessionSubject = {
85
+ /** A handle OUTSIDE every session: what the policies alone let through. */
86
+ connection: Connection;
87
+ /** The tables whose FORCE is checked. The probe table alone, when nothing else is named. */
88
+ guardedTables?: readonly string[];
89
+ probe: ProbeTable;
90
+ recording: () => Promise<RecordingConnection>;
91
+ /** Empty the probe table. Whatever role can do that — the exam's own rows are its business. */
92
+ reset: () => Promise<void>;
93
+ seam: SeamUnderTest;
94
+ };
95
+ /**
96
+ * The tenant wall, as the source proof took it: what a query with NO predicate can still see.
97
+ *
98
+ * Every read here is unqualified on purpose. A test that filters by tenant proves its own WHERE
99
+ * clause works; only an unfiltered one asks what the policy lets through.
100
+ */
101
+ declare const tenantIsolationConformance: (subject: SessionSubject) => ConformanceCase[];
102
+ /**
103
+ * The census: a read costs what it says.
104
+ *
105
+ * Four statements — the transaction, the tenant, the read, the commit. It is a literal number
106
+ * because the failures it catches are structural: a savepoint per query, a set_config per query, or
107
+ * a second transaction nested inside the first. Twenty scoped reads against a pool of five is how
108
+ * the source repo found this one.
109
+ */
110
+ declare const statementCensusConformance: (subject: SessionSubject) => ConformanceCase[];
111
+ /**
112
+ * Two tenants at once on one pool — the case a suite that runs one tenant at a time never reaches,
113
+ * and the one a pooled connection carrying a stale setting would fail.
114
+ */
115
+ declare const concurrentTenantsConformance: (subject: SessionSubject) => ConformanceCase[];
116
+ /**
117
+ * ENABLE alone exempts the table's OWNER, and the owner is the role that migrates and the role an
118
+ * ops sweep connects as. A wall only the application is behind is a wall with a door in it.
119
+ */
120
+ declare const forcedRowLevelSecurityConformance: (subject: SessionSubject) => ConformanceCase[];
121
+ /** The whole exam: what a consumer runs against their own session before depending on it. */
122
+ declare const sessionConformance: (subject: SessionSubject) => ConformanceCase[];
123
+
124
+ type OpenOptions = {
125
+ /**
126
+ * Every statement this handle sends, as it is sent — the transaction's own begin and commit
127
+ * included, because a count of round trips that leaves those out is not a count of round trips.
128
+ */
129
+ onStatement?: (statement: string) => void;
130
+ /**
131
+ * A pooled connection that died while nobody was using it. Nothing is waiting on it and the pool
132
+ * replaces it by itself, so this is how such a death is seen at all rather than something to act
133
+ * on.
134
+ */
135
+ onIdleError?: (error: unknown) => void;
136
+ /**
137
+ * How many connections one handle may hold. No default: the number that fits is the fan-out of
138
+ * one invocation against the concurrency the database was sized for, and a driver already has
139
+ * an opinion when nobody states one.
140
+ */
141
+ poolSize?: number;
142
+ };
143
+ /** A handle, and the way to give its connections back. */
144
+ type OpenConnection = {
145
+ close: () => Promise<void>;
146
+ connection: Connection;
147
+ };
148
+ /** The one thing in a deployment that knows which package speaks the wire protocol. */
149
+ type Driver = {
150
+ migrate: (connectionString: string, migrationsFolder: string) => Promise<void>;
151
+ open: (connectionString: string, options?: OpenOptions) => OpenConnection;
152
+ };
153
+ type Connections = {
154
+ /** Whether this call is running inside an invocation frame. */
155
+ inInvocation: () => boolean;
156
+ /** A handle outside every frame, for a caller who will close it themselves. */
157
+ open: () => OpenConnection;
158
+ /** The handle this invocation is using, opened on first ask. */
159
+ sessionDb: () => Connection;
160
+ withConnection: <Result>(run: () => Promise<Result>, release?: (closing: Promise<void>) => void) => Promise<Result>;
161
+ };
162
+ type ConnectionsConfig = {
163
+ connectionString: string;
164
+ driver: Driver;
165
+ open?: OpenOptions;
166
+ };
167
+ /**
168
+ * The handles of one database, and the discipline that closes them.
169
+ *
170
+ * A serverless invocation hibernates and dies; a handle opened once per process is dead by the
171
+ * second request, and a handle opened per query is a pool per query. One per invocation is the
172
+ * lifetime that matches, and the frame is what makes "this invocation" a thing the code can ask
173
+ * about rather than a thing the caller has to thread through.
174
+ */
175
+ declare const createConnections: (config: ConnectionsConfig) => Connections;
176
+ /**
177
+ * The `scope.perStep` hook a workflow engine asks for, answered with a connection per unit of work.
178
+ *
179
+ * A durable run hibernates between steps, so the handle its first step opened is dead by the fifth:
180
+ * each step body and each undo opens its own. The engine's own writes — the trail, the closing
181
+ * batch — are not units of work and stay outside, on whatever connection the runner holds.
182
+ *
183
+ * The type is not imported. `@geonosis/workflows` is a sibling foundation and the floors forbid an
184
+ * edge between two of them in either direction; what crosses is the shape.
185
+ */
186
+ declare const perStepConnection: (connections: Pick<Connections, "withConnection">) => {
187
+ perStep: () => <Output>(body: () => Promise<Output>) => Promise<Output>;
188
+ };
189
+
190
+ /**
191
+ * What this seam throws when it refuses, so a caller can catch it by class (#211).
192
+ *
193
+ * Every sibling has one — `WorkflowsError`, `ConformanceFailure`, `LearningError` — and this
194
+ * package threw a bare `Error`, which left a caller matching prose and a conformance case unable to
195
+ * tell a refusal from a driver that simply crashed (#213: a `TypeError` reads exactly like "the
196
+ * seam said no"). The name is on the instance so a log line says which package refused.
197
+ */
198
+ declare class DbRefusal extends Error {
199
+ constructor(message: string, options?: {
200
+ cause?: unknown;
201
+ });
202
+ }
203
+
204
+ /**
205
+ * As much of `pg` as this driver uses, declared here rather than imported.
206
+ *
207
+ * `pg` is an OPTIONAL peer, and a type imported from it would put it in this package's published
208
+ * `.d.ts` — a consumer who wanted the executor port and nothing else would then need `@types/pg` to
209
+ * typecheck. Structural is also what lets a Workers deployment hand in its own patched copy.
210
+ */
211
+ type PgModuleLike = {
212
+ Pool: new (config: {
213
+ connectionString: string;
214
+ max?: number;
215
+ }) => PgPoolLike;
216
+ };
217
+ type PgPoolLike = {
218
+ connect: () => Promise<PgClientLike>;
219
+ end: () => Promise<void>;
220
+ on: (event: 'error', listener: (error: unknown) => void) => void;
221
+ query: (text: string, values?: unknown[]) => Promise<{
222
+ rows: unknown[];
223
+ }>;
224
+ };
225
+ type PgClientLike = {
226
+ query: (text: string, values?: unknown[]) => Promise<{
227
+ rows: unknown[];
228
+ }>;
229
+ release: () => void;
230
+ };
231
+ type NodePostgresOptions = {
232
+ /**
233
+ * The table recording which migration files have been applied. Named after this package so it
234
+ * cannot collide with a consumer's own; a consumer whose migrator already keeps one names theirs.
235
+ */
236
+ migrationsTable?: string;
237
+ /** The `pg` module itself, for a runtime whose copy is patched. Loaded on demand when absent. */
238
+ pg?: PgModuleLike;
239
+ };
240
+ /**
241
+ * The one file in this package that names `pg`.
242
+ *
243
+ * It is a factory rather than a constant because the module is loaded on demand: a consumer using
244
+ * the executor port with their own database library never resolves `pg` at all.
245
+ */
246
+ declare const nodePostgresDriver: (options?: NodePostgresOptions) => Promise<Driver>;
247
+
248
+ /**
249
+ * The two session settings a tenant wall is built on, spelled once at the composition root.
250
+ *
251
+ * Neither has a default. A default here would be one repo's name — `app.org_id` at the repo this
252
+ * came from — compiled into every other repo's policies, and the seam and the policies must agree
253
+ * on it exactly or the wall is open while every test still passes.
254
+ */
255
+ type SessionSettings = {
256
+ /** What maintenance sets to see every tenant, through a lever no request path can reach. */
257
+ opsSetting: string;
258
+ /** What the ops setting holds while the lever is pulled. */
259
+ opsValue?: string;
260
+ /** What a transaction sets to name the tenant it is open for, and what the policies read. */
261
+ tenantSetting: string;
262
+ };
263
+ declare const DEFAULT_OPS_VALUE = "on";
264
+
265
+ type FreezeRegister = {
266
+ /** The column of that register holding the tenant a row names. */
267
+ tenantColumn: string;
268
+ /** The table listing the tenants whose rows are read-only. */
269
+ table: string;
270
+ };
271
+ type TenantPolicyNames = {
272
+ freeze?: string;
273
+ isolation?: string;
274
+ opsMaintenance?: string;
275
+ };
276
+ type TenantPolicyOptions = {
277
+ /**
278
+ * A register of frozen tenants, and the restrictive policy that consults it. Off unless named:
279
+ * a restrictive policy is a second predicate every statement on the table pays for, and freezing
280
+ * a tenant's rows is a product feature rather than a property of tenancy.
281
+ */
282
+ freeze?: FreezeRegister;
283
+ /**
284
+ * What these policies are called. A consumer whose tables already carry a policy of the same name
285
+ * would otherwise have it dropped by the line above the one that creates ours.
286
+ */
287
+ names?: TenantPolicyNames;
288
+ schema?: string;
289
+ settings: SessionSettings;
290
+ /** The column holding the tenant a row belongs to. */
291
+ tenantColumn: string;
292
+ };
293
+ /**
294
+ * The row-level-security DDL for one tenant table, as statements to apply after the migrator ran.
295
+ *
296
+ * It is DDL rather than schema-builder calls because FORCE is not sayable in one: drizzle-orm
297
+ * 0.45.2 offers `enableRLS()` and drizzle-kit 0.31.10 emits `ENABLE ROW LEVEL SECURITY`, and
298
+ * neither package contains the string `FORCE ROW LEVEL SECURITY` anywhere (measured 2026-09-02).
299
+ * ENABLE alone exempts the table's OWNER — which is the role a migration and an ops sweep connect
300
+ * as — so a wall that is only enabled is a wall with a door in it for the busiest user.
301
+ *
302
+ * Every statement is re-runnable, because it runs again after the next migration: a policy is
303
+ * dropped if present before it is created. Apply the list in ONE transaction, or a failure halfway
304
+ * leaves a table whose policy was dropped and not remade.
305
+ */
306
+ declare const tenantPolicies: (table: string, options: TenantPolicyOptions) => string[];
307
+
308
+ /** A query that takes its session as an argument: what the seam wraps and what a provider writes. */
309
+ type Query<Params, Result> = (executor: Executor, params: Params) => Promise<Result>;
310
+ type SessionSeam = {
311
+ inOps: <Result>(executor: Executor, run: (tx: Executor) => Promise<Result>) => Promise<Result>;
312
+ inTenant: <Result>(executor: Executor, tenantId: string, run: (tx: Executor) => Promise<Result>) => Promise<Result>;
313
+ scoped: <Params extends {
314
+ tenantId: string;
315
+ }, Result>(query: Query<Params, Result>) => Query<Params, Result>;
316
+ scopedAsOps: <Params, Result>(query: Query<Params, Result>) => Query<Params, Result>;
317
+ };
318
+ type SessionSeamConfig = {
319
+ /**
320
+ * How long a statement may run under the ops lever, as Postgres reads it. No default: a sweep's
321
+ * budget is a fact about one deployment's data, and the value that fits belongs to whoever runs
322
+ * it. Absent, the connection's own timeout stands.
323
+ */
324
+ opsStatementTimeout?: string;
325
+ settings: SessionSettings;
326
+ };
327
+ /**
328
+ * The seam every query crosses: one transaction, the scope named as its first statement.
329
+ *
330
+ * `set_config(..., true)` is local to the transaction, which is what makes this safe on a pool —
331
+ * the name is gone when the transaction ends, and a connection handed to the next request carries
332
+ * nothing. Naming it second would leave the statements before it running under no tenant at all.
333
+ */
334
+ declare const createSessionSeam: (config: SessionSeamConfig) => SessionSeam;
335
+
336
+ export { type AppRoleOptions, type ConformanceCase, type Connection, type Connections, type ConnectionsConfig, DEFAULT_OPS_VALUE, DbRefusal, type Driver, type Executor, type FreezeRegister, type NodePostgresOptions, type OpenConnection, type OpenOptions, type PgClientLike, type PgModuleLike, type PgPoolLike, type ProbeTable, type Query, type RecordingConnection, type SeamUnderTest, type SessionSeam, type SessionSeamConfig, type SessionSettings, type SessionSubject, type Statement, type TenantPolicyNames, type TenantPolicyOptions, appConnectionString, appRoleStatements, concurrentTenantsConformance, createConnections, createSessionSeam, forcedRowLevelSecurityConformance, nodePostgresDriver, perStepConnection, sessionConformance, statementCensusConformance, tenantIsolationConformance, tenantPolicies };