@btravstack/prisma 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -8
- package/dist/index.cjs +28 -1
- package/dist/index.d.cts +26 -2
- package/dist/index.d.mts +26 -2
- package/dist/index.mjs +28 -2
- package/dist/result.cjs +39 -6
- package/dist/result.d.cts +46 -3
- package/dist/result.d.mts +46 -3
- package/dist/result.mjs +39 -7
- package/dist/rls.cjs +3 -2
- package/dist/rls.d.cts +9 -5
- package/dist/rls.d.mts +9 -5
- package/dist/rls.mjs +3 -2
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -79,6 +79,11 @@ the per-query observation below comes from.
|
|
|
79
79
|
transactions, and a database that does not answer fails the boot with
|
|
80
80
|
`DatabaseUnreachable` — the database's name and the driver's cause, never the
|
|
81
81
|
URL.
|
|
82
|
+
- **A role row security applies to.** Where any table has row-level security
|
|
83
|
+
enabled, a role that is a superuser, has `BYPASSRLS`, or owns a row-secured
|
|
84
|
+
table that is not `FORCE`d fails the boot with `RowSecurityBypassed`, naming
|
|
85
|
+
the role and the tables it would bypass — every policy would admit every row
|
|
86
|
+
for it.
|
|
82
87
|
- **A health check**, named after the starter — `SELECT 1` through the raw
|
|
83
88
|
lane, folded into the kernel's `GET /healthz` with nothing wired. `/readyz`
|
|
84
89
|
does not read it: failing readiness on a dependency every replica shares
|
|
@@ -95,7 +100,9 @@ Prisma 8 throws a **structured** error carrying PostgreSQL's own SQLSTATE.
|
|
|
95
100
|
`tryQuery` turns that into a `Result` with three modeled arms —
|
|
96
101
|
`UniqueConstraintViolation` (`23505`, with the `constraint` the database
|
|
97
102
|
named), `ForeignKeyViolation` (`23503`) and `NotAuthorized` (`42501`,
|
|
98
|
-
including a write a row-security policy refused). Anything else is a defect
|
|
103
|
+
including a write a row-security policy refused). Anything else is a defect,
|
|
104
|
+
unless you list it in `raises` — a SQLSTATE your own trigger raises on purpose
|
|
105
|
+
then arrives as a `RaisedException` carrying that code.
|
|
99
106
|
|
|
100
107
|
<!-- doctest: isolate
|
|
101
108
|
import { tryQuery } from "@btravstack/prisma/result";
|
|
@@ -151,7 +158,7 @@ Two halves stay the deployment's, and both fail quietly when forgotten: Prisma 8
|
|
|
151
158
|
authors policies but no `GRANT`s, and it emits `ENABLE ROW LEVEL SECURITY` with
|
|
152
159
|
no way to say `FORCE` — so the table's **owner**, which is the role that ran the
|
|
153
160
|
migrations, bypasses every policy. Connect as a role that is neither the owner
|
|
154
|
-
nor a superuser
|
|
161
|
+
nor a superuser — the boot refuses either with `RowSecurityBypassed`. The
|
|
155
162
|
[reference page](https://btravstack.github.io/btravstack/reference/prisma)
|
|
156
163
|
carries the DDL and the smaller lines that fail the same way.
|
|
157
164
|
|
|
@@ -177,12 +184,13 @@ package's.
|
|
|
177
184
|
|
|
178
185
|
## Options
|
|
179
186
|
|
|
180
|
-
| Option | Where
|
|
181
|
-
| -------------- |
|
|
182
|
-
| `name` | `prismaDatabase(name)`
|
|
183
|
-
| `client` | `prismaDatabase(name)({ client })`
|
|
184
|
-
| `DATABASE_URL` | environment, read by `prismaDatabase(name)`
|
|
185
|
-
| `setting` | `tenantPinned(db, tenant, work, { setting })`
|
|
187
|
+
| Option | Where | What it is |
|
|
188
|
+
| -------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
189
|
+
| `name` | `prismaDatabase(name)` | the port's id and the health check's name — required |
|
|
190
|
+
| `client` | `prismaDatabase(name)({ client })` | builds your client from the URL and middleware this package bound — required |
|
|
191
|
+
| `DATABASE_URL` | environment, read by `prismaDatabase(name)` | the connection string — required, validated at graph build; blank is an error, exit `78` |
|
|
192
|
+
| `setting` | `tenantPinned(db, tenant, work, { setting })` | the PostgreSQL run-time setting the policy reads — default `app.tenant_id` |
|
|
193
|
+
| `raises` | `tryQuery(run, { raises })`, `tenantPinned(…, { raises })` | SQLSTATEs modeled as `RaisedException` rather than defects — default none |
|
|
186
194
|
|
|
187
195
|
There is **no `instrumented` flag**: observation is a set port every call is
|
|
188
196
|
handed to, so a graph composing no observability pays one inert call and no
|
package/dist/index.cjs
CHANGED
|
@@ -66,6 +66,21 @@ const queryObserver = (observers) => {
|
|
|
66
66
|
/** The one place the cast above is spelled. */
|
|
67
67
|
const probeable = (db) => db;
|
|
68
68
|
const probe = (db) => probeable(db).runtime().query(probeable(db).raw.sql`SELECT 1`.affectedCount().build());
|
|
69
|
+
const exemptions = (db) => probeable(db).runtime().query(probeable(db).raw.sql`SELECT current_user::text AS role, c.oid::regclass::text AS relation, CASE WHEN r.rolsuper THEN 'superuser' WHEN r.rolbypassrls THEN 'bypassrls' ELSE 'owner' END AS bypass FROM pg_class c JOIN pg_roles r ON r.rolname = current_user WHERE c.relrowsecurity AND (r.rolsuper OR r.rolbypassrls OR (NOT c.relforcerowsecurity AND pg_has_role(current_user, c.relowner, 'USAGE'))) ORDER BY 2`.returnsRow({
|
|
70
|
+
role: "pg/text@1",
|
|
71
|
+
relation: "pg/text@1",
|
|
72
|
+
bypass: "pg/text@1"
|
|
73
|
+
}).build());
|
|
74
|
+
/**
|
|
75
|
+
* The role the client connected as is exempt from row-level security on a
|
|
76
|
+
* table that has it enabled: a superuser, a role with `BYPASSRLS`, or the
|
|
77
|
+
* owner of a table whose security is not `FORCE`d — which Prisma 8 cannot
|
|
78
|
+
* declare, so every table a migration created qualifies for the role that ran
|
|
79
|
+
* it. Every policy would admit every row, so the boot is refused rather than
|
|
80
|
+
* served without isolation. `tables` are the ones it would bypass; the URL is
|
|
81
|
+
* never carried.
|
|
82
|
+
*/
|
|
83
|
+
var RowSecurityBypassed = class extends (0, unthrown.TaggedError)("RowSecurityBypassed") {};
|
|
69
84
|
/**
|
|
70
85
|
* The database did not answer when the scope opened. Carries the database's
|
|
71
86
|
* name and the driver's own failure, never the URL, which holds credentials.
|
|
@@ -84,6 +99,12 @@ var DatabaseUnreachable = class extends (0, unthrown.TaggedError)("DatabaseUnrea
|
|
|
84
99
|
* database that does not answer fails the boot with
|
|
85
100
|
* {@link DatabaseUnreachable}.
|
|
86
101
|
*
|
|
102
|
+
* **Row-level security has to apply to the role it connected as.** Where any
|
|
103
|
+
* table has it enabled, a role that is a superuser, has `BYPASSRLS`, or owns
|
|
104
|
+
* such a table without `FORCE` fails the boot with {@link RowSecurityBypassed}.
|
|
105
|
+
* A database with no row-secured table passes as it is. The health check does
|
|
106
|
+
* not repeat it: a role does not change under a running pool.
|
|
107
|
+
*
|
|
87
108
|
* **The pool closes on every exit path**, including a boot that fails after
|
|
88
109
|
* this provider ran — that is what makes it resourceful rather than a plain
|
|
89
110
|
* value. `runtime().close()` ends the pool; Prisma dials again lazily on the
|
|
@@ -97,7 +118,12 @@ var DatabaseUnreachable = class extends (0, unthrown.TaggedError)("DatabaseUnrea
|
|
|
97
118
|
const prismaDatabase = (name) => ({ client }) => {
|
|
98
119
|
const config = _btravstack_config.Config.provider(`${name}Config`)(_btravstack_config.Config.object({ url: _btravstack_config.Config.string("DATABASE_URL") }));
|
|
99
120
|
const DatabasePort = (0, _btravstack_di.Port)(name);
|
|
100
|
-
const verified = (db) => (0, unthrown.fromPromise)(probe(db).then(() =>
|
|
121
|
+
const verified = (db) => (0, unthrown.fromPromise)(probe(db).then(() => exemptions(db)).then((exempt) => exempt[0] === void 0 ? db : Promise.reject(new RowSecurityBypassed({
|
|
122
|
+
database: name,
|
|
123
|
+
role: exempt[0].role,
|
|
124
|
+
bypass: exempt[0].bypass,
|
|
125
|
+
tables: exempt.map(({ relation }) => relation)
|
|
126
|
+
}))).catch((cause) => probeable(db).runtime().close().finally(() => Promise.reject(cause))), (cause) => cause instanceof RowSecurityBypassed ? cause : new DatabaseUnreachable({
|
|
101
127
|
database: name,
|
|
102
128
|
cause
|
|
103
129
|
}));
|
|
@@ -134,4 +160,5 @@ const prismaDatabase = (name) => ({ client }) => {
|
|
|
134
160
|
};
|
|
135
161
|
//#endregion
|
|
136
162
|
exports.DatabaseUnreachable = DatabaseUnreachable;
|
|
163
|
+
exports.RowSecurityBypassed = RowSecurityBypassed;
|
|
137
164
|
exports.prismaDatabase = prismaDatabase;
|
package/dist/index.d.cts
CHANGED
|
@@ -66,6 +66,24 @@ type PrismaLike = {
|
|
|
66
66
|
*/
|
|
67
67
|
readonly runtime: unknown;
|
|
68
68
|
};
|
|
69
|
+
/** Why row-level security does not apply to the role, as {@link RowSecurityBypassed} names it. */
|
|
70
|
+
type RowSecurityBypass = "superuser" | "bypassrls" | "owner";
|
|
71
|
+
declare const RowSecurityBypassed_base: import("unthrown").TaggedErrorConstructor<"RowSecurityBypassed">;
|
|
72
|
+
/**
|
|
73
|
+
* The role the client connected as is exempt from row-level security on a
|
|
74
|
+
* table that has it enabled: a superuser, a role with `BYPASSRLS`, or the
|
|
75
|
+
* owner of a table whose security is not `FORCE`d — which Prisma 8 cannot
|
|
76
|
+
* declare, so every table a migration created qualifies for the role that ran
|
|
77
|
+
* it. Every policy would admit every row, so the boot is refused rather than
|
|
78
|
+
* served without isolation. `tables` are the ones it would bypass; the URL is
|
|
79
|
+
* never carried.
|
|
80
|
+
*/
|
|
81
|
+
export declare class RowSecurityBypassed extends RowSecurityBypassed_base<{
|
|
82
|
+
readonly database: string;
|
|
83
|
+
readonly role: string;
|
|
84
|
+
readonly bypass: RowSecurityBypass;
|
|
85
|
+
readonly tables: readonly string[];
|
|
86
|
+
}> {}
|
|
69
87
|
declare const DatabaseUnreachable_base: import("unthrown").TaggedErrorConstructor<"DatabaseUnreachable">;
|
|
70
88
|
/**
|
|
71
89
|
* The database did not answer when the scope opened. Carries the database's
|
|
@@ -119,6 +137,12 @@ type PrismaOptions<C extends PrismaLike> = {
|
|
|
119
137
|
* database that does not answer fails the boot with
|
|
120
138
|
* {@link DatabaseUnreachable}.
|
|
121
139
|
*
|
|
140
|
+
* **Row-level security has to apply to the role it connected as.** Where any
|
|
141
|
+
* table has it enabled, a role that is a superuser, has `BYPASSRLS`, or owns
|
|
142
|
+
* such a table without `FORCE` fails the boot with {@link RowSecurityBypassed}.
|
|
143
|
+
* A database with no row-secured table passes as it is. The health check does
|
|
144
|
+
* not repeat it: a role does not change under a running pool.
|
|
145
|
+
*
|
|
122
146
|
* **The pool closes on every exit path**, including a boot that fails after
|
|
123
147
|
* this provider ran — that is what makes it resourceful rather than a plain
|
|
124
148
|
* value. `runtime().close()` ends the pool; Prisma dials again lazily on the
|
|
@@ -129,10 +153,10 @@ type PrismaOptions<C extends PrismaLike> = {
|
|
|
129
153
|
* against this same URL *before the process starts*; an application that
|
|
130
154
|
* migrates itself at boot races every other replica.
|
|
131
155
|
*/
|
|
132
|
-
export declare const prismaDatabase: <const N extends string>(name: N) => <C extends PrismaLike>({ client }: PrismaOptions<C>) => Module<HealthChecks | (PortClassOf<N, C> extends (infer T) ? T extends PortClassOf<N, C> ? T extends import("@btravstack/di").AnyPort ? InstanceType<T> : T extends import("@btravstack/di").AnyProvider ? InstanceType<T["port"]> : never : never : never), import("@btravstack/config").ConfigInvalid | DatabaseUnreachable, Exclude<import("@btravstack/config").EnvReading<"DATABASE_URL", never>, HealthChecks | Observers | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
|
|
156
|
+
export declare const prismaDatabase: <const N extends string>(name: N) => <C extends PrismaLike>({ client }: PrismaOptions<C>) => Module<HealthChecks | (PortClassOf<N, C> extends (infer T) ? T extends PortClassOf<N, C> ? T extends import("@btravstack/di").AnyPort ? InstanceType<T> : T extends import("@btravstack/di").AnyProvider ? InstanceType<T["port"]> : never : never : never), import("@btravstack/config").ConfigInvalid | DatabaseUnreachable | RowSecurityBypassed, Exclude<import("@btravstack/config").EnvReading<"DATABASE_URL", never>, HealthChecks | Observers | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
|
|
133
157
|
readonly url: string;
|
|
134
158
|
}>>> & {
|
|
135
159
|
readonly port: PortClassOf<N, C>;
|
|
136
160
|
};
|
|
137
161
|
//#endregion
|
|
138
|
-
export type { PrismaBinding, PrismaLike, PrismaOptions, SqlMiddlewareLike };
|
|
162
|
+
export type { PrismaBinding, PrismaLike, PrismaOptions, RowSecurityBypass, SqlMiddlewareLike };
|
package/dist/index.d.mts
CHANGED
|
@@ -66,6 +66,24 @@ type PrismaLike = {
|
|
|
66
66
|
*/
|
|
67
67
|
readonly runtime: unknown;
|
|
68
68
|
};
|
|
69
|
+
/** Why row-level security does not apply to the role, as {@link RowSecurityBypassed} names it. */
|
|
70
|
+
type RowSecurityBypass = "superuser" | "bypassrls" | "owner";
|
|
71
|
+
declare const RowSecurityBypassed_base: import("unthrown").TaggedErrorConstructor<"RowSecurityBypassed">;
|
|
72
|
+
/**
|
|
73
|
+
* The role the client connected as is exempt from row-level security on a
|
|
74
|
+
* table that has it enabled: a superuser, a role with `BYPASSRLS`, or the
|
|
75
|
+
* owner of a table whose security is not `FORCE`d — which Prisma 8 cannot
|
|
76
|
+
* declare, so every table a migration created qualifies for the role that ran
|
|
77
|
+
* it. Every policy would admit every row, so the boot is refused rather than
|
|
78
|
+
* served without isolation. `tables` are the ones it would bypass; the URL is
|
|
79
|
+
* never carried.
|
|
80
|
+
*/
|
|
81
|
+
export declare class RowSecurityBypassed extends RowSecurityBypassed_base<{
|
|
82
|
+
readonly database: string;
|
|
83
|
+
readonly role: string;
|
|
84
|
+
readonly bypass: RowSecurityBypass;
|
|
85
|
+
readonly tables: readonly string[];
|
|
86
|
+
}> {}
|
|
69
87
|
declare const DatabaseUnreachable_base: import("unthrown").TaggedErrorConstructor<"DatabaseUnreachable">;
|
|
70
88
|
/**
|
|
71
89
|
* The database did not answer when the scope opened. Carries the database's
|
|
@@ -119,6 +137,12 @@ type PrismaOptions<C extends PrismaLike> = {
|
|
|
119
137
|
* database that does not answer fails the boot with
|
|
120
138
|
* {@link DatabaseUnreachable}.
|
|
121
139
|
*
|
|
140
|
+
* **Row-level security has to apply to the role it connected as.** Where any
|
|
141
|
+
* table has it enabled, a role that is a superuser, has `BYPASSRLS`, or owns
|
|
142
|
+
* such a table without `FORCE` fails the boot with {@link RowSecurityBypassed}.
|
|
143
|
+
* A database with no row-secured table passes as it is. The health check does
|
|
144
|
+
* not repeat it: a role does not change under a running pool.
|
|
145
|
+
*
|
|
122
146
|
* **The pool closes on every exit path**, including a boot that fails after
|
|
123
147
|
* this provider ran — that is what makes it resourceful rather than a plain
|
|
124
148
|
* value. `runtime().close()` ends the pool; Prisma dials again lazily on the
|
|
@@ -129,10 +153,10 @@ type PrismaOptions<C extends PrismaLike> = {
|
|
|
129
153
|
* against this same URL *before the process starts*; an application that
|
|
130
154
|
* migrates itself at boot races every other replica.
|
|
131
155
|
*/
|
|
132
|
-
export declare const prismaDatabase: <const N extends string>(name: N) => <C extends PrismaLike>({ client }: PrismaOptions<C>) => Module<HealthChecks | (PortClassOf<N, C> extends (infer T) ? T extends PortClassOf<N, C> ? T extends import("@btravstack/di").AnyPort ? InstanceType<T> : T extends import("@btravstack/di").AnyProvider ? InstanceType<T["port"]> : never : never : never), import("@btravstack/config").ConfigInvalid | DatabaseUnreachable, Exclude<import("@btravstack/config").EnvReading<"DATABASE_URL", never>, HealthChecks | Observers | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
|
|
156
|
+
export declare const prismaDatabase: <const N extends string>(name: N) => <C extends PrismaLike>({ client }: PrismaOptions<C>) => Module<HealthChecks | (PortClassOf<N, C> extends (infer T) ? T extends PortClassOf<N, C> ? T extends import("@btravstack/di").AnyPort ? InstanceType<T> : T extends import("@btravstack/di").AnyProvider ? InstanceType<T["port"]> : never : never : never), import("@btravstack/config").ConfigInvalid | DatabaseUnreachable | RowSecurityBypassed, Exclude<import("@btravstack/config").EnvReading<"DATABASE_URL", never>, HealthChecks | Observers | import("@btravstack/di").PortInstance<string, C> | import("@btravstack/di").PortInstance<`${N}Config`, {
|
|
133
157
|
readonly url: string;
|
|
134
158
|
}>>> & {
|
|
135
159
|
readonly port: PortClassOf<N, C>;
|
|
136
160
|
};
|
|
137
161
|
//#endregion
|
|
138
|
-
export type { PrismaBinding, PrismaLike, PrismaOptions, SqlMiddlewareLike };
|
|
162
|
+
export type { PrismaBinding, PrismaLike, PrismaOptions, RowSecurityBypass, SqlMiddlewareLike };
|
package/dist/index.mjs
CHANGED
|
@@ -65,6 +65,21 @@ const queryObserver = (observers) => {
|
|
|
65
65
|
/** The one place the cast above is spelled. */
|
|
66
66
|
const probeable = (db) => db;
|
|
67
67
|
const probe = (db) => probeable(db).runtime().query(probeable(db).raw.sql`SELECT 1`.affectedCount().build());
|
|
68
|
+
const exemptions = (db) => probeable(db).runtime().query(probeable(db).raw.sql`SELECT current_user::text AS role, c.oid::regclass::text AS relation, CASE WHEN r.rolsuper THEN 'superuser' WHEN r.rolbypassrls THEN 'bypassrls' ELSE 'owner' END AS bypass FROM pg_class c JOIN pg_roles r ON r.rolname = current_user WHERE c.relrowsecurity AND (r.rolsuper OR r.rolbypassrls OR (NOT c.relforcerowsecurity AND pg_has_role(current_user, c.relowner, 'USAGE'))) ORDER BY 2`.returnsRow({
|
|
69
|
+
role: "pg/text@1",
|
|
70
|
+
relation: "pg/text@1",
|
|
71
|
+
bypass: "pg/text@1"
|
|
72
|
+
}).build());
|
|
73
|
+
/**
|
|
74
|
+
* The role the client connected as is exempt from row-level security on a
|
|
75
|
+
* table that has it enabled: a superuser, a role with `BYPASSRLS`, or the
|
|
76
|
+
* owner of a table whose security is not `FORCE`d — which Prisma 8 cannot
|
|
77
|
+
* declare, so every table a migration created qualifies for the role that ran
|
|
78
|
+
* it. Every policy would admit every row, so the boot is refused rather than
|
|
79
|
+
* served without isolation. `tables` are the ones it would bypass; the URL is
|
|
80
|
+
* never carried.
|
|
81
|
+
*/
|
|
82
|
+
var RowSecurityBypassed = class extends TaggedError("RowSecurityBypassed") {};
|
|
68
83
|
/**
|
|
69
84
|
* The database did not answer when the scope opened. Carries the database's
|
|
70
85
|
* name and the driver's own failure, never the URL, which holds credentials.
|
|
@@ -83,6 +98,12 @@ var DatabaseUnreachable = class extends TaggedError("DatabaseUnreachable") {};
|
|
|
83
98
|
* database that does not answer fails the boot with
|
|
84
99
|
* {@link DatabaseUnreachable}.
|
|
85
100
|
*
|
|
101
|
+
* **Row-level security has to apply to the role it connected as.** Where any
|
|
102
|
+
* table has it enabled, a role that is a superuser, has `BYPASSRLS`, or owns
|
|
103
|
+
* such a table without `FORCE` fails the boot with {@link RowSecurityBypassed}.
|
|
104
|
+
* A database with no row-secured table passes as it is. The health check does
|
|
105
|
+
* not repeat it: a role does not change under a running pool.
|
|
106
|
+
*
|
|
86
107
|
* **The pool closes on every exit path**, including a boot that fails after
|
|
87
108
|
* this provider ran — that is what makes it resourceful rather than a plain
|
|
88
109
|
* value. `runtime().close()` ends the pool; Prisma dials again lazily on the
|
|
@@ -96,7 +117,12 @@ var DatabaseUnreachable = class extends TaggedError("DatabaseUnreachable") {};
|
|
|
96
117
|
const prismaDatabase = (name) => ({ client }) => {
|
|
97
118
|
const config = Config.provider(`${name}Config`)(Config.object({ url: Config.string("DATABASE_URL") }));
|
|
98
119
|
const DatabasePort = Port(name);
|
|
99
|
-
const verified = (db) => fromPromise(probe(db).then(() =>
|
|
120
|
+
const verified = (db) => fromPromise(probe(db).then(() => exemptions(db)).then((exempt) => exempt[0] === void 0 ? db : Promise.reject(new RowSecurityBypassed({
|
|
121
|
+
database: name,
|
|
122
|
+
role: exempt[0].role,
|
|
123
|
+
bypass: exempt[0].bypass,
|
|
124
|
+
tables: exempt.map(({ relation }) => relation)
|
|
125
|
+
}))).catch((cause) => probeable(db).runtime().close().finally(() => Promise.reject(cause))), (cause) => cause instanceof RowSecurityBypassed ? cause : new DatabaseUnreachable({
|
|
100
126
|
database: name,
|
|
101
127
|
cause
|
|
102
128
|
}));
|
|
@@ -132,4 +158,4 @@ const prismaDatabase = (name) => ({ client }) => {
|
|
|
132
158
|
return Object.assign(database, { port: DatabasePort });
|
|
133
159
|
};
|
|
134
160
|
//#endregion
|
|
135
|
-
export { DatabaseUnreachable, prismaDatabase };
|
|
161
|
+
export { DatabaseUnreachable, RowSecurityBypassed, prismaDatabase };
|
package/dist/result.cjs
CHANGED
|
@@ -27,16 +27,31 @@ var ForeignKeyViolation = class extends (0, unthrown.TaggedError)("ForeignKeyVio
|
|
|
27
27
|
*/
|
|
28
28
|
var NotAuthorized = class extends (0, unthrown.TaggedError)("NotAuthorized") {};
|
|
29
29
|
/**
|
|
30
|
+
* A SQLSTATE the application raises on purpose — a trigger or a function
|
|
31
|
+
* refusing a write the schema cannot express with `RAISE EXCEPTION … USING
|
|
32
|
+
* ERRCODE = '…'` — modeled because the caller listed it in `raises`.
|
|
33
|
+
*
|
|
34
|
+
* `message` is the one the `RAISE` wrote. Raise in a class of your own rather
|
|
35
|
+
* than in `XX`, which PostgreSQL reserves for its internal errors.
|
|
36
|
+
*/
|
|
37
|
+
var RaisedException = class extends (0, unthrown.TaggedError)("RaisedException") {
|
|
38
|
+
constructor({ message, ...fields }) {
|
|
39
|
+
super(fields);
|
|
40
|
+
this.message = message;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
30
44
|
* SQLSTATE → a tagged error, or the caller's defect for anything unmodeled.
|
|
31
45
|
*
|
|
32
46
|
* The three arms are the ones an application triages. Everything else — a
|
|
33
47
|
* syntax error, a dead connection, a deadlock — is infrastructure a caller
|
|
34
48
|
* cannot act on differently, and belongs on the defect channel where an
|
|
35
|
-
* unexpected failure already goes
|
|
49
|
+
* unexpected failure already goes — unless the caller listed its SQLSTATE in
|
|
50
|
+
* `raises`.
|
|
36
51
|
*/
|
|
37
|
-
const qualify = (cause, defect) => {
|
|
38
|
-
const error = cause;
|
|
39
|
-
switch (error
|
|
52
|
+
const qualify = (cause, defect, options) => {
|
|
53
|
+
const error = cause ?? {};
|
|
54
|
+
switch (error.sqlState) {
|
|
40
55
|
case "23505": return new UniqueConstraintViolation({
|
|
41
56
|
constraint: error.constraint,
|
|
42
57
|
table: error.table
|
|
@@ -46,7 +61,15 @@ const qualify = (cause, defect) => {
|
|
|
46
61
|
table: error.table
|
|
47
62
|
});
|
|
48
63
|
case "42501": return new NotAuthorized({ table: error.table });
|
|
49
|
-
default:
|
|
64
|
+
default: {
|
|
65
|
+
const raised = options?.raises?.find((code) => code === error.sqlState);
|
|
66
|
+
return raised === void 0 ? defect(cause) : new RaisedException({
|
|
67
|
+
sqlState: raised,
|
|
68
|
+
message: error.message,
|
|
69
|
+
constraint: error.constraint,
|
|
70
|
+
table: error.table
|
|
71
|
+
});
|
|
72
|
+
}
|
|
50
73
|
}
|
|
51
74
|
};
|
|
52
75
|
/**
|
|
@@ -68,11 +91,21 @@ const qualify = (cause, defect) => {
|
|
|
68
91
|
* .with(P.tag("NotAuthorized"), (refused) => defect(refused)),
|
|
69
92
|
* );
|
|
70
93
|
* ```
|
|
94
|
+
*
|
|
95
|
+
* A trigger's own refusal arrives as a {@link RaisedException} once its
|
|
96
|
+
* SQLSTATE is listed:
|
|
97
|
+
*
|
|
98
|
+
* ```ts
|
|
99
|
+
* tryQuery(() => db.orm.orders.Order.create(row), { raises: ["OR001"] });
|
|
100
|
+
* ```
|
|
71
101
|
*/
|
|
72
|
-
const tryQuery = (run) =>
|
|
102
|
+
const tryQuery = (run, options) => {
|
|
103
|
+
return (0, unthrown.fromPromise)(Promise.resolve().then(run), (cause, defect) => qualify(cause, defect, options));
|
|
104
|
+
};
|
|
73
105
|
//#endregion
|
|
74
106
|
exports.ForeignKeyViolation = ForeignKeyViolation;
|
|
75
107
|
exports.NotAuthorized = NotAuthorized;
|
|
108
|
+
exports.RaisedException = RaisedException;
|
|
76
109
|
exports.UniqueConstraintViolation = UniqueConstraintViolation;
|
|
77
110
|
exports.qualify = qualify;
|
|
78
111
|
exports.tryQuery = tryQuery;
|
package/dist/result.d.cts
CHANGED
|
@@ -36,17 +36,53 @@ declare const NotAuthorized_base: import("unthrown").TaggedErrorConstructor<"Not
|
|
|
36
36
|
export declare class NotAuthorized extends NotAuthorized_base<{
|
|
37
37
|
readonly table: string | undefined;
|
|
38
38
|
}> {}
|
|
39
|
+
declare const RaisedException_base: import("unthrown").TaggedErrorConstructor<"RaisedException">;
|
|
40
|
+
/**
|
|
41
|
+
* A SQLSTATE the application raises on purpose — a trigger or a function
|
|
42
|
+
* refusing a write the schema cannot express with `RAISE EXCEPTION … USING
|
|
43
|
+
* ERRCODE = '…'` — modeled because the caller listed it in `raises`.
|
|
44
|
+
*
|
|
45
|
+
* `message` is the one the `RAISE` wrote. Raise in a class of your own rather
|
|
46
|
+
* than in `XX`, which PostgreSQL reserves for its internal errors.
|
|
47
|
+
*/
|
|
48
|
+
export declare class RaisedException<S extends string = string> extends RaisedException_base<{
|
|
49
|
+
readonly sqlState: S;
|
|
50
|
+
readonly constraint: string | undefined;
|
|
51
|
+
readonly table: string | undefined;
|
|
52
|
+
}> {
|
|
53
|
+
constructor({ message, ...fields }: {
|
|
54
|
+
readonly sqlState: S;
|
|
55
|
+
readonly message: string;
|
|
56
|
+
readonly constraint: string | undefined;
|
|
57
|
+
readonly table: string | undefined;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
39
60
|
/** What {@link qualify} models. Anything else is a defect. */
|
|
40
61
|
export type SqlError = UniqueConstraintViolation | ForeignKeyViolation | NotAuthorized;
|
|
62
|
+
/**
|
|
63
|
+
* One {@link RaisedException} per SQLSTATE listed, and nothing when none is,
|
|
64
|
+
* so a caller that lists no code keeps the three arms it already matches.
|
|
65
|
+
*/
|
|
66
|
+
export type Raised<S extends string> = S extends string ? RaisedException<S> : never;
|
|
67
|
+
/** Options of {@link tryQuery} and {@link qualify}. */
|
|
68
|
+
export type QualifyOptions<S extends string> = {
|
|
69
|
+
/**
|
|
70
|
+
* The SQLSTATEs the application raises on purpose and wants as a
|
|
71
|
+
* {@link RaisedException} rather than a defect. The three SQLSTATEs
|
|
72
|
+
* {@link SqlError} already models keep their own arms.
|
|
73
|
+
*/
|
|
74
|
+
readonly raises?: readonly S[];
|
|
75
|
+
};
|
|
41
76
|
/**
|
|
42
77
|
* SQLSTATE → a tagged error, or the caller's defect for anything unmodeled.
|
|
43
78
|
*
|
|
44
79
|
* The three arms are the ones an application triages. Everything else — a
|
|
45
80
|
* syntax error, a dead connection, a deadlock — is infrastructure a caller
|
|
46
81
|
* cannot act on differently, and belongs on the defect channel where an
|
|
47
|
-
* unexpected failure already goes
|
|
82
|
+
* unexpected failure already goes — unless the caller listed its SQLSTATE in
|
|
83
|
+
* `raises`.
|
|
48
84
|
*/
|
|
49
|
-
export declare const qualify: <D>(cause: unknown, defect: (cause: unknown) => D) => SqlError | D;
|
|
85
|
+
export declare const qualify: <D, const S extends string = never>(cause: unknown, defect: (cause: unknown) => D, options?: QualifyOptions<S>) => SqlError | Raised<S> | D;
|
|
50
86
|
/**
|
|
51
87
|
* Runs a query and answers a `Result` instead of rejecting.
|
|
52
88
|
*
|
|
@@ -66,6 +102,13 @@ export declare const qualify: <D>(cause: unknown, defect: (cause: unknown) => D)
|
|
|
66
102
|
* .with(P.tag("NotAuthorized"), (refused) => defect(refused)),
|
|
67
103
|
* );
|
|
68
104
|
* ```
|
|
105
|
+
*
|
|
106
|
+
* A trigger's own refusal arrives as a {@link RaisedException} once its
|
|
107
|
+
* SQLSTATE is listed:
|
|
108
|
+
*
|
|
109
|
+
* ```ts
|
|
110
|
+
* tryQuery(() => db.orm.orders.Order.create(row), { raises: ["OR001"] });
|
|
111
|
+
* ```
|
|
69
112
|
*/
|
|
70
|
-
export declare const tryQuery: <T>(run: () => PromiseLike<T>) => AsyncResult<T, SqlError
|
|
113
|
+
export declare const tryQuery: <T, const S extends string = never>(run: () => PromiseLike<T>, options?: QualifyOptions<S>) => AsyncResult<T, SqlError | Raised<S>>;
|
|
71
114
|
//#endregion
|
package/dist/result.d.mts
CHANGED
|
@@ -36,17 +36,53 @@ declare const NotAuthorized_base: import("unthrown").TaggedErrorConstructor<"Not
|
|
|
36
36
|
export declare class NotAuthorized extends NotAuthorized_base<{
|
|
37
37
|
readonly table: string | undefined;
|
|
38
38
|
}> {}
|
|
39
|
+
declare const RaisedException_base: import("unthrown").TaggedErrorConstructor<"RaisedException">;
|
|
40
|
+
/**
|
|
41
|
+
* A SQLSTATE the application raises on purpose — a trigger or a function
|
|
42
|
+
* refusing a write the schema cannot express with `RAISE EXCEPTION … USING
|
|
43
|
+
* ERRCODE = '…'` — modeled because the caller listed it in `raises`.
|
|
44
|
+
*
|
|
45
|
+
* `message` is the one the `RAISE` wrote. Raise in a class of your own rather
|
|
46
|
+
* than in `XX`, which PostgreSQL reserves for its internal errors.
|
|
47
|
+
*/
|
|
48
|
+
export declare class RaisedException<S extends string = string> extends RaisedException_base<{
|
|
49
|
+
readonly sqlState: S;
|
|
50
|
+
readonly constraint: string | undefined;
|
|
51
|
+
readonly table: string | undefined;
|
|
52
|
+
}> {
|
|
53
|
+
constructor({ message, ...fields }: {
|
|
54
|
+
readonly sqlState: S;
|
|
55
|
+
readonly message: string;
|
|
56
|
+
readonly constraint: string | undefined;
|
|
57
|
+
readonly table: string | undefined;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
39
60
|
/** What {@link qualify} models. Anything else is a defect. */
|
|
40
61
|
export type SqlError = UniqueConstraintViolation | ForeignKeyViolation | NotAuthorized;
|
|
62
|
+
/**
|
|
63
|
+
* One {@link RaisedException} per SQLSTATE listed, and nothing when none is,
|
|
64
|
+
* so a caller that lists no code keeps the three arms it already matches.
|
|
65
|
+
*/
|
|
66
|
+
export type Raised<S extends string> = S extends string ? RaisedException<S> : never;
|
|
67
|
+
/** Options of {@link tryQuery} and {@link qualify}. */
|
|
68
|
+
export type QualifyOptions<S extends string> = {
|
|
69
|
+
/**
|
|
70
|
+
* The SQLSTATEs the application raises on purpose and wants as a
|
|
71
|
+
* {@link RaisedException} rather than a defect. The three SQLSTATEs
|
|
72
|
+
* {@link SqlError} already models keep their own arms.
|
|
73
|
+
*/
|
|
74
|
+
readonly raises?: readonly S[];
|
|
75
|
+
};
|
|
41
76
|
/**
|
|
42
77
|
* SQLSTATE → a tagged error, or the caller's defect for anything unmodeled.
|
|
43
78
|
*
|
|
44
79
|
* The three arms are the ones an application triages. Everything else — a
|
|
45
80
|
* syntax error, a dead connection, a deadlock — is infrastructure a caller
|
|
46
81
|
* cannot act on differently, and belongs on the defect channel where an
|
|
47
|
-
* unexpected failure already goes
|
|
82
|
+
* unexpected failure already goes — unless the caller listed its SQLSTATE in
|
|
83
|
+
* `raises`.
|
|
48
84
|
*/
|
|
49
|
-
export declare const qualify: <D>(cause: unknown, defect: (cause: unknown) => D) => SqlError | D;
|
|
85
|
+
export declare const qualify: <D, const S extends string = never>(cause: unknown, defect: (cause: unknown) => D, options?: QualifyOptions<S>) => SqlError | Raised<S> | D;
|
|
50
86
|
/**
|
|
51
87
|
* Runs a query and answers a `Result` instead of rejecting.
|
|
52
88
|
*
|
|
@@ -66,6 +102,13 @@ export declare const qualify: <D>(cause: unknown, defect: (cause: unknown) => D)
|
|
|
66
102
|
* .with(P.tag("NotAuthorized"), (refused) => defect(refused)),
|
|
67
103
|
* );
|
|
68
104
|
* ```
|
|
105
|
+
*
|
|
106
|
+
* A trigger's own refusal arrives as a {@link RaisedException} once its
|
|
107
|
+
* SQLSTATE is listed:
|
|
108
|
+
*
|
|
109
|
+
* ```ts
|
|
110
|
+
* tryQuery(() => db.orm.orders.Order.create(row), { raises: ["OR001"] });
|
|
111
|
+
* ```
|
|
69
112
|
*/
|
|
70
|
-
export declare const tryQuery: <T>(run: () => PromiseLike<T>) => AsyncResult<T, SqlError
|
|
113
|
+
export declare const tryQuery: <T, const S extends string = never>(run: () => PromiseLike<T>, options?: QualifyOptions<S>) => AsyncResult<T, SqlError | Raised<S>>;
|
|
71
114
|
//#endregion
|
package/dist/result.mjs
CHANGED
|
@@ -26,16 +26,31 @@ var ForeignKeyViolation = class extends TaggedError("ForeignKeyViolation") {};
|
|
|
26
26
|
*/
|
|
27
27
|
var NotAuthorized = class extends TaggedError("NotAuthorized") {};
|
|
28
28
|
/**
|
|
29
|
+
* A SQLSTATE the application raises on purpose — a trigger or a function
|
|
30
|
+
* refusing a write the schema cannot express with `RAISE EXCEPTION … USING
|
|
31
|
+
* ERRCODE = '…'` — modeled because the caller listed it in `raises`.
|
|
32
|
+
*
|
|
33
|
+
* `message` is the one the `RAISE` wrote. Raise in a class of your own rather
|
|
34
|
+
* than in `XX`, which PostgreSQL reserves for its internal errors.
|
|
35
|
+
*/
|
|
36
|
+
var RaisedException = class extends TaggedError("RaisedException") {
|
|
37
|
+
constructor({ message, ...fields }) {
|
|
38
|
+
super(fields);
|
|
39
|
+
this.message = message;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
29
43
|
* SQLSTATE → a tagged error, or the caller's defect for anything unmodeled.
|
|
30
44
|
*
|
|
31
45
|
* The three arms are the ones an application triages. Everything else — a
|
|
32
46
|
* syntax error, a dead connection, a deadlock — is infrastructure a caller
|
|
33
47
|
* cannot act on differently, and belongs on the defect channel where an
|
|
34
|
-
* unexpected failure already goes
|
|
48
|
+
* unexpected failure already goes — unless the caller listed its SQLSTATE in
|
|
49
|
+
* `raises`.
|
|
35
50
|
*/
|
|
36
|
-
const qualify = (cause, defect) => {
|
|
37
|
-
const error = cause;
|
|
38
|
-
switch (error
|
|
51
|
+
const qualify = (cause, defect, options) => {
|
|
52
|
+
const error = cause ?? {};
|
|
53
|
+
switch (error.sqlState) {
|
|
39
54
|
case "23505": return new UniqueConstraintViolation({
|
|
40
55
|
constraint: error.constraint,
|
|
41
56
|
table: error.table
|
|
@@ -45,7 +60,15 @@ const qualify = (cause, defect) => {
|
|
|
45
60
|
table: error.table
|
|
46
61
|
});
|
|
47
62
|
case "42501": return new NotAuthorized({ table: error.table });
|
|
48
|
-
default:
|
|
63
|
+
default: {
|
|
64
|
+
const raised = options?.raises?.find((code) => code === error.sqlState);
|
|
65
|
+
return raised === void 0 ? defect(cause) : new RaisedException({
|
|
66
|
+
sqlState: raised,
|
|
67
|
+
message: error.message,
|
|
68
|
+
constraint: error.constraint,
|
|
69
|
+
table: error.table
|
|
70
|
+
});
|
|
71
|
+
}
|
|
49
72
|
}
|
|
50
73
|
};
|
|
51
74
|
/**
|
|
@@ -67,7 +90,16 @@ const qualify = (cause, defect) => {
|
|
|
67
90
|
* .with(P.tag("NotAuthorized"), (refused) => defect(refused)),
|
|
68
91
|
* );
|
|
69
92
|
* ```
|
|
93
|
+
*
|
|
94
|
+
* A trigger's own refusal arrives as a {@link RaisedException} once its
|
|
95
|
+
* SQLSTATE is listed:
|
|
96
|
+
*
|
|
97
|
+
* ```ts
|
|
98
|
+
* tryQuery(() => db.orm.orders.Order.create(row), { raises: ["OR001"] });
|
|
99
|
+
* ```
|
|
70
100
|
*/
|
|
71
|
-
const tryQuery = (run) =>
|
|
101
|
+
const tryQuery = (run, options) => {
|
|
102
|
+
return fromPromise(Promise.resolve().then(run), (cause, defect) => qualify(cause, defect, options));
|
|
103
|
+
};
|
|
72
104
|
//#endregion
|
|
73
|
-
export { ForeignKeyViolation, NotAuthorized, UniqueConstraintViolation, qualify, tryQuery };
|
|
105
|
+
export { ForeignKeyViolation, NotAuthorized, RaisedException, UniqueConstraintViolation, qualify, tryQuery };
|
package/dist/rls.cjs
CHANGED
|
@@ -13,7 +13,8 @@ const require_result = require("./result.cjs");
|
|
|
13
13
|
* the first time it does not; it is the shape a middleware would take, and it
|
|
14
14
|
* is why there is no middleware here.
|
|
15
15
|
*
|
|
16
|
-
* It answers an `AsyncResult`, qualified by {@link tryQuery}
|
|
16
|
+
* It answers an `AsyncResult`, qualified by {@link tryQuery} — with
|
|
17
|
+
* `raises` as it takes it: the pin and the
|
|
17
18
|
* work run on one connection and fail on one channel, so a caller never wraps
|
|
18
19
|
* this in a second combinator. `work` still hands back a `PromiseLike`, because
|
|
19
20
|
* that is what Prisma's own transaction callback is.
|
|
@@ -34,7 +35,7 @@ const tenantPinned = (db, tenant, work, options) => {
|
|
|
34
35
|
return require_result.tryQuery(() => db.transaction(async (tx) => {
|
|
35
36
|
await tx.query(plan);
|
|
36
37
|
return work(tx);
|
|
37
|
-
}));
|
|
38
|
+
}), options);
|
|
38
39
|
};
|
|
39
40
|
//#endregion
|
|
40
41
|
exports.tenantPinned = tenantPinned;
|
package/dist/rls.d.cts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { SqlError } from "./result.cjs";
|
|
1
|
+
import { QualifyOptions, Raised, SqlError } from "./result.cjs";
|
|
2
2
|
import { AsyncResult } from "unthrown";
|
|
3
3
|
//#region src/rls.d.ts
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Options of {@link tenantPinned}: `raises`, as {@link tryQuery} takes it,
|
|
6
|
+
* and the setting the pin writes.
|
|
7
|
+
*/
|
|
8
|
+
export type TenantPinnedOptions<S extends string = never> = QualifyOptions<S> & {
|
|
6
9
|
/**
|
|
7
10
|
* The PostgreSQL run-time setting the policy reads. Defaults to
|
|
8
11
|
* `app.tenant_id`, matching `current_setting('app.tenant_id', true)`.
|
|
@@ -43,7 +46,8 @@ type Queryable = {
|
|
|
43
46
|
* the first time it does not; it is the shape a middleware would take, and it
|
|
44
47
|
* is why there is no middleware here.
|
|
45
48
|
*
|
|
46
|
-
* It answers an `AsyncResult`, qualified by {@link tryQuery}
|
|
49
|
+
* It answers an `AsyncResult`, qualified by {@link tryQuery} — with
|
|
50
|
+
* `raises` as it takes it: the pin and the
|
|
47
51
|
* work run on one connection and fail on one channel, so a caller never wraps
|
|
48
52
|
* this in a second combinator. `work` still hands back a `PromiseLike`, because
|
|
49
53
|
* that is what Prisma's own transaction callback is.
|
|
@@ -58,5 +62,5 @@ type Queryable = {
|
|
|
58
62
|
* const orders = await tenantPinned(db, tenant, (tx) => tx.orm.orders.Order.all());
|
|
59
63
|
* ```
|
|
60
64
|
*/
|
|
61
|
-
export declare const tenantPinned: <Tx extends Queryable, R>(db: Pinnable<Tx>, tenant: string, work: (tx: Tx) => PromiseLike<R>, options?: TenantPinnedOptions) => AsyncResult<R, SqlError
|
|
65
|
+
export declare const tenantPinned: <Tx extends Queryable, R, const S extends string = never>(db: Pinnable<Tx>, tenant: string, work: (tx: Tx) => PromiseLike<R>, options?: TenantPinnedOptions<S>) => AsyncResult<R, SqlError | Raised<S>>;
|
|
62
66
|
//#endregion
|
package/dist/rls.d.mts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { SqlError } from "./result.mjs";
|
|
1
|
+
import { QualifyOptions, Raised, SqlError } from "./result.mjs";
|
|
2
2
|
import { AsyncResult } from "unthrown";
|
|
3
3
|
//#region src/rls.d.ts
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Options of {@link tenantPinned}: `raises`, as {@link tryQuery} takes it,
|
|
6
|
+
* and the setting the pin writes.
|
|
7
|
+
*/
|
|
8
|
+
export type TenantPinnedOptions<S extends string = never> = QualifyOptions<S> & {
|
|
6
9
|
/**
|
|
7
10
|
* The PostgreSQL run-time setting the policy reads. Defaults to
|
|
8
11
|
* `app.tenant_id`, matching `current_setting('app.tenant_id', true)`.
|
|
@@ -43,7 +46,8 @@ type Queryable = {
|
|
|
43
46
|
* the first time it does not; it is the shape a middleware would take, and it
|
|
44
47
|
* is why there is no middleware here.
|
|
45
48
|
*
|
|
46
|
-
* It answers an `AsyncResult`, qualified by {@link tryQuery}
|
|
49
|
+
* It answers an `AsyncResult`, qualified by {@link tryQuery} — with
|
|
50
|
+
* `raises` as it takes it: the pin and the
|
|
47
51
|
* work run on one connection and fail on one channel, so a caller never wraps
|
|
48
52
|
* this in a second combinator. `work` still hands back a `PromiseLike`, because
|
|
49
53
|
* that is what Prisma's own transaction callback is.
|
|
@@ -58,5 +62,5 @@ type Queryable = {
|
|
|
58
62
|
* const orders = await tenantPinned(db, tenant, (tx) => tx.orm.orders.Order.all());
|
|
59
63
|
* ```
|
|
60
64
|
*/
|
|
61
|
-
export declare const tenantPinned: <Tx extends Queryable, R>(db: Pinnable<Tx>, tenant: string, work: (tx: Tx) => PromiseLike<R>, options?: TenantPinnedOptions) => AsyncResult<R, SqlError
|
|
65
|
+
export declare const tenantPinned: <Tx extends Queryable, R, const S extends string = never>(db: Pinnable<Tx>, tenant: string, work: (tx: Tx) => PromiseLike<R>, options?: TenantPinnedOptions<S>) => AsyncResult<R, SqlError | Raised<S>>;
|
|
62
66
|
//#endregion
|
package/dist/rls.mjs
CHANGED
|
@@ -12,7 +12,8 @@ import { tryQuery } from "./result.mjs";
|
|
|
12
12
|
* the first time it does not; it is the shape a middleware would take, and it
|
|
13
13
|
* is why there is no middleware here.
|
|
14
14
|
*
|
|
15
|
-
* It answers an `AsyncResult`, qualified by {@link tryQuery}
|
|
15
|
+
* It answers an `AsyncResult`, qualified by {@link tryQuery} — with
|
|
16
|
+
* `raises` as it takes it: the pin and the
|
|
16
17
|
* work run on one connection and fail on one channel, so a caller never wraps
|
|
17
18
|
* this in a second combinator. `work` still hands back a `PromiseLike`, because
|
|
18
19
|
* that is what Prisma's own transaction callback is.
|
|
@@ -33,7 +34,7 @@ const tenantPinned = (db, tenant, work, options) => {
|
|
|
33
34
|
return tryQuery(() => db.transaction(async (tx) => {
|
|
34
35
|
await tx.query(plan);
|
|
35
36
|
return work(tx);
|
|
36
|
-
}));
|
|
37
|
+
}), options);
|
|
37
38
|
};
|
|
38
39
|
//#endregion
|
|
39
40
|
export { tenantPinned };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btravstack/prisma",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "The Prisma starter for @btravstack/core: DATABASE_URL through Config, the Postgres driver adapter, and a client whose pool is the application scope's",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"database",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"./package.json": "./package.json"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@btravstack/config": "0.
|
|
67
|
-
"@btravstack/core": "0.
|
|
68
|
-
"@btravstack/di": "0.
|
|
66
|
+
"@btravstack/config": "0.29.0",
|
|
67
|
+
"@btravstack/core": "0.29.0",
|
|
68
|
+
"@btravstack/di": "0.29.0",
|
|
69
69
|
"@btravstack/tsconfig": "0.4.0",
|
|
70
70
|
"@prisma/orm-postgres": "8.0.0-rc.11",
|
|
71
71
|
"@types/node": "26.6.4",
|
|
@@ -77,9 +77,9 @@
|
|
|
77
77
|
"vitest": "5.0.3"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
|
-
"@btravstack/config": "^0.
|
|
81
|
-
"@btravstack/core": "^0.
|
|
82
|
-
"@btravstack/di": "^0.
|
|
80
|
+
"@btravstack/config": "^0.29.0",
|
|
81
|
+
"@btravstack/core": "^0.29.0",
|
|
82
|
+
"@btravstack/di": "^0.29.0",
|
|
83
83
|
"@prisma/orm-postgres": "^8.0.0-rc.11",
|
|
84
84
|
"unthrown": "^5.0.0"
|
|
85
85
|
},
|