@rowstile/prisma 0.1.0-alpha.6 → 0.1.0-alpha.7
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 +2 -1
- package/dist/index.js +32 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -47,7 +47,8 @@ and a query by permission is one call:
|
|
|
47
47
|
|
|
48
48
|
- Use the client `$extends(authz())` returns, and only that one.
|
|
49
49
|
- `$transaction([...])` is refused: Prisma starts an array's transaction from whichever request came first.
|
|
50
|
-
Use `$transaction(async (tx) => ...)`.
|
|
50
|
+
Use `$transaction(async (tx) => ...)`. Inside it, why a write was refused is asked through the transaction:
|
|
51
|
+
it needs no second connection.
|
|
51
52
|
- A `create` reads its row back, so it needs the select rule too. A refusal there is `Refused` naming it.
|
|
52
53
|
- Policy changes ship as Prisma migrations: `tool = "prisma"` in `rowstile.toml`, then `npx rowstile migrate`.
|
|
53
54
|
|
package/dist/index.js
CHANGED
|
@@ -112,8 +112,11 @@ export function signedIn(factory, options = {}) {
|
|
|
112
112
|
async function transactionOf(tx) {
|
|
113
113
|
let found;
|
|
114
114
|
const make = tx._createPrismaPromise;
|
|
115
|
+
// (the two guards are for another version of Prisma than the one the checks run: left out of the measure)
|
|
116
|
+
/* v8 ignore else */
|
|
115
117
|
if (typeof make === "function")
|
|
116
118
|
await make(async (t) => { found = t; });
|
|
119
|
+
/* v8 ignore next */
|
|
117
120
|
if (found?.kind !== "itx") {
|
|
118
121
|
throw new Error("@rowstile/prisma: this version of Prisma starts its transactions another way; " +
|
|
119
122
|
"report it at https://github.com/rowstile/rowstile/issues with the version of @prisma/client");
|
|
@@ -124,10 +127,13 @@ async function transactionOf(tx) {
|
|
|
124
127
|
export function authz(options = {}) {
|
|
125
128
|
return Prisma.defineExtension((client) => {
|
|
126
129
|
const raw = client;
|
|
130
|
+
// (Prisma 7 always has the data model: without it, another version, left out of the measure)
|
|
131
|
+
/* v8 ignore next */
|
|
127
132
|
const models = raw._runtimeDataModel?.models ?? {};
|
|
133
|
+
// the schema of a table the database's words name without one: a model's table is its @@map, else its name
|
|
128
134
|
const schemaOf = (table) => {
|
|
129
|
-
for (const m of Object.
|
|
130
|
-
if (m.dbName === table && m.schema)
|
|
135
|
+
for (const [name, m] of Object.entries(models))
|
|
136
|
+
if ((m.dbName ?? name) === table && m.schema)
|
|
131
137
|
return m.schema;
|
|
132
138
|
return undefined;
|
|
133
139
|
};
|
|
@@ -136,11 +142,10 @@ export function authz(options = {}) {
|
|
|
136
142
|
const name = m?.dbName ?? model;
|
|
137
143
|
return m?.schema ? `${m.schema}.${name}` : name;
|
|
138
144
|
};
|
|
139
|
-
// the row's key (the policy's key for the table) from the where of an update or
|
|
140
|
-
// options.keys names for the model, else id, else its one unique field or compound key
|
|
145
|
+
// the row's key (the policy's key for the table) from the where of an update, a delete or a findUnique: the
|
|
146
|
+
// fields options.keys names for the model, else id, else its one unique field or compound key (Prisma refuses
|
|
147
|
+
// any of them without a where before a query)
|
|
141
148
|
const keyOf = (model, where) => {
|
|
142
|
-
if (!where)
|
|
143
|
-
return undefined;
|
|
144
149
|
const named = options.keys?.[model];
|
|
145
150
|
if (named) {
|
|
146
151
|
const vals = named.map((k) => where[k]);
|
|
@@ -161,8 +166,11 @@ export function authz(options = {}) {
|
|
|
161
166
|
const [, v] = entries[0];
|
|
162
167
|
if (isScalar(v))
|
|
163
168
|
return v;
|
|
164
|
-
|
|
169
|
+
// a compound key's fields, in a plain object: not a date (or bytes, a decimal), which isn't written as the
|
|
170
|
+
// database writes it
|
|
171
|
+
if (v && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype && Object.values(v).every(isScalar)) {
|
|
165
172
|
return Object.values(v);
|
|
173
|
+
}
|
|
166
174
|
return undefined;
|
|
167
175
|
};
|
|
168
176
|
// through the client this returns (set below), whose last hook lets the query through
|
|
@@ -171,6 +179,15 @@ export function authz(options = {}) {
|
|
|
171
179
|
query: async (text, values = []) => ({ rows: await self.$queryRawUnsafe(text, ...values) }),
|
|
172
180
|
};
|
|
173
181
|
const c = calls(q);
|
|
182
|
+
// The runtime's functions asked inside the app's interactive transaction, for a call made in one: an update or a
|
|
183
|
+
// read that found no row leaves it usable, and a question outside it would wait for a connection of its own,
|
|
184
|
+
// which on a pool of one the transaction holds until Prisma's timeout. A Prisma promise runs in the transaction
|
|
185
|
+
// it is handed (requestTransaction: how Prisma runs an array's queries in theirs).
|
|
186
|
+
const askingIn = (transaction) => transaction?.kind !== "itx" ? c : calls({
|
|
187
|
+
query: async (text, values = []) => ({
|
|
188
|
+
rows: await self.$queryRawUnsafe(text, ...values).requestTransaction(transaction),
|
|
189
|
+
}),
|
|
190
|
+
});
|
|
174
191
|
const $authz = {
|
|
175
192
|
...c,
|
|
176
193
|
ids: async (type, perm, as) => {
|
|
@@ -204,7 +221,7 @@ export function authz(options = {}) {
|
|
|
204
221
|
const table = tableOf(model);
|
|
205
222
|
const key = keyOf(model, args.where);
|
|
206
223
|
if (key !== undefined) {
|
|
207
|
-
const v = await
|
|
224
|
+
const v = await askingIn(internal?.transaction).verdict(table, operation, key).catch(() => undefined);
|
|
208
225
|
if (v !== undefined) {
|
|
209
226
|
v.cause = e;
|
|
210
227
|
throw v;
|
|
@@ -212,10 +229,14 @@ export function authz(options = {}) {
|
|
|
212
229
|
}
|
|
213
230
|
}
|
|
214
231
|
// a read that must find a row and found none: the row isn't there, or this user can't see it (the
|
|
215
|
-
// two answer alike). Left as Prisma's P2025 it is a 500, where an update or a delete is a 404
|
|
232
|
+
// two answer alike). Left as Prisma's P2025 it is a 500, where an update or a delete is a 404.
|
|
233
|
+
// findUnique's where is a key; findFirst's is any filter, which names a row only by its id
|
|
216
234
|
if (model && isP2025(e) && (operation === "findUniqueOrThrow" || operation === "findFirstOrThrow")) {
|
|
217
|
-
const
|
|
218
|
-
|
|
235
|
+
const where = args.where;
|
|
236
|
+
const key = operation === "findUniqueOrThrow" ? keyOf(model, where)
|
|
237
|
+
: isScalar(where?.id) ? where?.id : undefined;
|
|
238
|
+
const table = await askingIn(internal?.transaction).tableName(tableOf(model)).catch(() => tableOf(model));
|
|
239
|
+
throw new NotFound(table, key === undefined ? undefined : idShown(key), { cause: e });
|
|
219
240
|
}
|
|
220
241
|
throw translate(e, schemaOf) ?? e;
|
|
221
242
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rowstile/prisma",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.7",
|
|
4
4
|
"description": "rowstile with Prisma: a client extension that signs every query in, and turns refusals into errors",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://rowstile.dev",
|
|
@@ -12,6 +12,6 @@
|
|
|
12
12
|
"exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
|
|
13
13
|
"files": ["dist"],
|
|
14
14
|
"sideEffects": false,
|
|
15
|
-
"dependencies": { "@rowstile/client": "0.1.0-alpha.
|
|
15
|
+
"dependencies": { "@rowstile/client": "0.1.0-alpha.7" },
|
|
16
16
|
"peerDependencies": { "@prisma/client": ">=7" }
|
|
17
17
|
}
|