@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.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +32 -11
  3. 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.values(models))
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 delete: the fields
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
- if (v && typeof v === "object" && Object.values(v).every(isScalar))
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 c.verdict(table, operation, key).catch(() => undefined);
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 key = keyOf(model, args.where);
218
- throw new NotFound(tableOf(model), key === undefined ? undefined : idShown(key), { cause: e });
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.6",
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.6" },
15
+ "dependencies": { "@rowstile/client": "0.1.0-alpha.7" },
16
16
  "peerDependencies": { "@prisma/client": ">=7" }
17
17
  }