@cerebruminc/yates 3.7.1 → 3.7.2-beta.dangerous.30d5b2f

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/dist/index.js CHANGED
@@ -113,9 +113,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
113
113
  return (mod && mod.__esModule) ? mod : { "default": mod };
114
114
  };
115
115
  Object.defineProperty(exports, "__esModule", { value: true });
116
- exports.setup = exports.createRoles = exports.createClient = exports.getBatchId = exports.createRoleName = exports.createAbilityName = exports.sanitizeSlug = void 0;
116
+ exports.setup = exports.Yates = exports.sanitizeSlug = void 0;
117
117
  var crypto = __importStar(require("crypto"));
118
118
  var debug_1 = __importDefault(require("debug"));
119
+ var cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
119
120
  var difference_1 = __importDefault(require("lodash/difference"));
120
121
  var flatMap_1 = __importDefault(require("lodash/flatMap"));
121
122
  var map_1 = __importDefault(require("lodash/map"));
@@ -130,23 +131,6 @@ var debug = (0, debug_1.default)("yates");
130
131
  var takeLock = function (prisma) {
131
132
  return prisma.$executeRawUnsafe("SELECT pg_advisory_xact_lock(2142616474639426746);");
132
133
  };
133
- /*
134
- * This function creates a table used to track the abilities that have been
135
- * defined in the system. We can use this to see if an ability needs to be updated.
136
- * We can't look up the pg policy table for this, as pg performs formatting on
137
- * the expression, making it very hard to check if the two expressions are equivalent.
138
- *
139
- * We also need to create a schema for this table, as we don't want to pollute the public schema.
140
- * If we use the public schema, we could potentially conflict with a user's table and we will
141
- * also cause issues for Prisma's migrate tooling, as it will detect a DB drift.
142
- */
143
- var setupAbilityTable = function (prisma) {
144
- return prisma.$transaction([
145
- takeLock(prisma),
146
- prisma.$executeRawUnsafe("\n\t\tCREATE SCHEMA IF NOT EXISTS _yates;\n\t\t"),
147
- prisma.$executeRawUnsafe("\n\t\tCREATE TABLE IF NOT EXISTS _yates._yates_abilities (\n\t\t\tid SERIAL PRIMARY KEY,\n\t\t\tability_model TEXT NOT NULL,\n\t\t\tability_name TEXT NOT NULL,\n\t\t\tability_policy_name TEXT NOT NULL UNIQUE,\n\t\t\tability_description TEXT NOT NULL,\n\t\t\tability_operation TEXT NOT NULL,\n\t\t\tability_expression TEXT NOT NULL,\n\t\t\tcreated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n\t\t\tupdated_at TIMESTAMP\n\t\t);\n\t"),
148
- ]);
149
- };
150
134
  var upsertAbility = function (prisma, ability) {
151
135
  var ability_model = ability.ability_model, ability_name = ability.ability_name, ability_policy_name = ability.ability_policy_name, ability_description = ability.ability_description, ability_operation = ability.ability_operation, ability_expression = ability.ability_expression;
152
136
  return prisma.$queryRaw(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n\t\tINSERT INTO _yates._yates_abilities (ability_model, ability_name, ability_policy_name, ability_description, ability_operation, ability_expression)\n\t\tVALUES (", ", ", ", ", ", ", ", ", ", ", ")\n\t\tON CONFLICT (ability_policy_name) DO UPDATE\n\t\tSET ability_model = EXCLUDED.ability_model, ability_name = EXCLUDED.ability_name, ability_description = EXCLUDED.ability_description, ability_operation = EXCLUDED.ability_operation, ability_expression = EXCLUDED.ability_expression, updated_at = now();\n\t"], ["\n\t\tINSERT INTO _yates._yates_abilities (ability_model, ability_name, ability_policy_name, ability_description, ability_operation, ability_expression)\n\t\tVALUES (", ", ", ", ", ", ", ", ", ", ", ")\n\t\tON CONFLICT (ability_policy_name) DO UPDATE\n\t\tSET ability_model = EXCLUDED.ability_model, ability_name = EXCLUDED.ability_name, ability_description = EXCLUDED.ability_description, ability_operation = EXCLUDED.ability_operation, ability_expression = EXCLUDED.ability_expression, updated_at = now();\n\t"])), ability_model, ability_name, ability_policy_name, ability_description, ability_operation, ability_expression);
@@ -173,660 +157,786 @@ var sanitizeSlug = function (slug) {
173
157
  .replace(/[^a-z0-9_]/gi, "");
174
158
  };
175
159
  exports.sanitizeSlug = sanitizeSlug;
176
- var createAbilityName = function (model, ability) {
177
- return (0, exports.sanitizeSlug)(hashWithPrefix("yates_ability_", "".concat(model, "_").concat(ability)));
178
- };
179
- exports.createAbilityName = createAbilityName;
180
- var createRoleName = function (name) {
181
- return (0, exports.sanitizeSlug)(hashWithPrefix("yates_role_", "".concat(name)));
182
- };
183
- exports.createRoleName = createRoleName;
184
- // @ts-ignore
185
- function getBatchId(query) {
186
- if (query.action !== "findUnique" && query.action !== "findUniqueOrThrow") {
187
- return undefined;
188
- }
189
- var parts = [];
190
- if (query.modelName) {
191
- parts.push(query.modelName);
192
- }
193
- if (query.query.arguments) {
194
- parts.push(buildKeysString(query.query.arguments));
195
- }
196
- parts.push(buildKeysString(query.query.selection));
197
- return parts.join("");
198
- }
199
- exports.getBatchId = getBatchId;
200
- function buildKeysString(obj) {
201
- var keysArray = Object.keys(obj)
202
- .sort()
203
- .map(function (key) {
204
- // @ts-ignore
205
- var value = obj[key];
206
- if (typeof value === "object" && value !== null) {
207
- return "(".concat(key, " ").concat(buildKeysString(value), ")");
208
- }
209
- return key;
210
- });
211
- return "(".concat(keysArray.join(" "), ")");
212
- }
213
- // This uses client extensions to set the role and context for the current user so that RLS can be applied
214
- var createClient = function (prisma, getContext, options) {
215
- if (options === void 0) { options = {}; }
216
- // Set default options
217
- var _a = options.txMaxWait, txMaxWait = _a === void 0 ? 30000 : _a, _b = options.txTimeout, txTimeout = _b === void 0 ? 30000 : _b;
218
- // By default, Prisma will batch requests by the transaction ID if it is present.
219
- // This behaviour prevents automatic batching from working when using Yates, since all queries are executed inside an interactive transaction.
220
- // To get around this we by monkey patching the batching function to use the Yates ID as the batch ID.
221
- // To get the batching to work we also need to ensure that all the requests we might want to batch together are generated inside the same tick.
222
- // This means that all the requests per-tick that have the same role and context values will be batched together,
223
- // allowing the in-built prisma batch optimizations to work for us.
224
- // This is why we use process.nextTick and the tickActive flag to ensure we only tick once at a time.
225
- // See:
226
- // - https://github.com/prisma/prisma/blob/5.21.1/packages/client/src/runtime/RequestHandler.ts#L122
227
- // - https://www.prisma.io/docs/orm/prisma-client/queries/query-optimization-performance
228
- prisma._requestHandler.dataloader.options.batchBy = function (request) {
229
- var _a;
230
- var batchIdPQ = getBatchId(request.protocolQuery);
231
- if ((_a = request.transaction) === null || _a === void 0 ? void 0 : _a.id) {
232
- return "transaction-".concat(request.transaction.id).concat(batchIdPQ ? "-".concat(batchIdPQ) : "");
233
- }
234
- return getBatchId(request.protocolQuery);
235
- };
236
- var tickActive = false;
237
- var batches = {};
238
- // This function is called once per tick, and processes all the batches that have been created during that tick.
239
- // Each batch represents a unique role + context combination, and contains all the requests that need to be executed with that role + context.
240
- var dispatchBatches = function () {
241
- var e_1, _a;
242
- var _loop_1 = function (key, batch) {
243
- delete batches[key];
244
- // Because batch transactions inside a prisma client query extension can run out of order if used with async middleware,
245
- // we need to run the logic inside an interactive transaction, however this brings a different set of problems in that the
246
- // main query will no longer automatically run inside the transaction. We resolve this issue by manually executing the prisma request.
247
- // See https://github.com/prisma/prisma/issues/18276
248
- prisma
249
- .$transaction(function (tx) { return __awaiter(void 0, void 0, void 0, function () {
250
- var _a, _b, _c, key_1, value, e_2_1, txId, results;
251
- var e_2, _d;
252
- return __generator(this, function (_e) {
253
- switch (_e.label) {
254
- case 0:
255
- // Switch to the user role, We can't use a prepared statement here, due to limitations in PG not allowing prepared statements to be used in SET LOCAL ROLE
256
- return [4 /*yield*/, tx.$queryRawUnsafe("SET LOCAL ROLE ".concat(batch.pgRole))];
257
- case 1:
258
- // Switch to the user role, We can't use a prepared statement here, due to limitations in PG not allowing prepared statements to be used in SET LOCAL ROLE
259
- _e.sent();
260
- _e.label = 2;
261
- case 2:
262
- _e.trys.push([2, 7, 8, 9]);
263
- _a = __values((0, toPairs_1.default)(batch.context)), _b = _a.next();
264
- _e.label = 3;
265
- case 3:
266
- if (!!_b.done) return [3 /*break*/, 6];
267
- _c = __read(_b.value, 2), key_1 = _c[0], value = _c[1];
268
- return [4 /*yield*/, tx.$queryRaw(templateObject_2 || (templateObject_2 = __makeTemplateObject(["SELECT set_config(", ", ", ", true);"], ["SELECT set_config(", ", ", ", true);"])), key_1, value.toString())];
269
- case 4:
270
- _e.sent();
271
- _e.label = 5;
272
- case 5:
273
- _b = _a.next();
274
- return [3 /*break*/, 3];
275
- case 6: return [3 /*break*/, 9];
276
- case 7:
277
- e_2_1 = _e.sent();
278
- e_2 = { error: e_2_1 };
279
- return [3 /*break*/, 9];
280
- case 8:
281
- try {
282
- if (_b && !_b.done && (_d = _a.return)) _d.call(_a);
283
- }
284
- finally { if (e_2) throw e_2.error; }
285
- return [7 /*endfinally*/];
286
- case 9:
287
- txId = tx[Symbol.for("prisma.client.transaction.id")];
288
- return [4 /*yield*/, Promise.all(batch.requests.map(function (request) {
289
- return prisma._executeRequest(__assign(__assign({}, request.params), { transaction: {
290
- kind: "itx",
291
- id: txId,
292
- } }));
293
- }))];
294
- case 10:
295
- results = _e.sent();
296
- return [2 /*return*/, results];
297
- }
298
- });
299
- }); }, {
300
- maxWait: txMaxWait,
301
- timeout: txTimeout,
302
- })
303
- .then(function (results) {
304
- results.forEach(function (result, index) {
305
- batch.requests[index].resolve(result);
306
- });
307
- })
308
- .catch(function (e) {
309
- var e_3, _a;
310
- try {
311
- for (var _b = (e_3 = void 0, __values(batch.requests)), _c = _b.next(); !_c.done; _c = _b.next()) {
312
- var request = _c.value;
313
- request.reject(e);
314
- }
160
+ var Yates = /** @class */ (function () {
161
+ function Yates(prisma) {
162
+ var _this = this;
163
+ this.prisma = prisma;
164
+ this.databaseScope = null;
165
+ this.init = function () { return __awaiter(_this, void 0, void 0, function () {
166
+ return __generator(this, function (_a) {
167
+ switch (_a.label) {
168
+ case 0: return [4 /*yield*/, this.ensureDatabaseScope()];
169
+ case 1:
170
+ _a.sent();
171
+ debug("Setting up ability table");
172
+ return [4 /*yield*/, this.setupAbilityTable()];
173
+ case 2:
174
+ _a.sent();
175
+ return [2 /*return*/];
315
176
  }
316
- catch (e_3_1) { e_3 = { error: e_3_1 }; }
317
- finally {
318
- try {
319
- if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
320
- }
321
- finally { if (e_3) throw e_3.error; }
322
- }
323
- delete batches[key];
324
177
  });
178
+ }); };
179
+ this.createDatabaseScope = function (databaseName) {
180
+ var sanitizedName = (0, exports.sanitizeSlug)(databaseName);
181
+ if (sanitizedName.length > 0) {
182
+ return sanitizedName;
183
+ }
184
+ var hash = crypto.createHash("sha256");
185
+ hash.update(databaseName);
186
+ return hash.digest("hex").slice(0, 8);
325
187
  };
326
- try {
327
- for (var _b = __values(Object.entries(batches)), _c = _b.next(); !_c.done; _c = _b.next()) {
328
- var _d = __read(_c.value, 2), key = _d[0], batch = _d[1];
329
- _loop_1(key, batch);
188
+ this.getDatabaseScope = function () {
189
+ if (!_this.databaseScope) {
190
+ throw new Error("Yates database scope has not been initialised. Ensure setup() has been called before using the client.");
330
191
  }
331
- }
332
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
333
- finally {
192
+ return _this.databaseScope;
193
+ };
194
+ this.ensureDatabaseScope = function () { return __awaiter(_this, void 0, void 0, function () {
195
+ var result, currentDatabase;
196
+ var _a;
197
+ return __generator(this, function (_b) {
198
+ switch (_b.label) {
199
+ case 0:
200
+ if (this.databaseScope) {
201
+ return [2 /*return*/, this.databaseScope];
202
+ }
203
+ return [4 /*yield*/, this.prisma.$queryRawUnsafe("select current_database() as current_database;")];
204
+ case 1:
205
+ result = _b.sent();
206
+ currentDatabase = (_a = result[0]) === null || _a === void 0 ? void 0 : _a.current_database;
207
+ debug("Current database for Yates:", currentDatabase);
208
+ if (!currentDatabase) {
209
+ throw new Error("Failed to determine the current database for scoping Yates roles.");
210
+ }
211
+ this.databaseScope = this.createDatabaseScope(currentDatabase);
212
+ return [2 /*return*/, this.databaseScope];
213
+ }
214
+ });
215
+ }); };
216
+ /*
217
+ * This function creates a table used to track the abilities that have been
218
+ * defined in the system. We can use this to see if an ability needs to be updated.
219
+ * We can't look up the pg policy table for this, as pg performs formatting on
220
+ * the expression, making it very hard to check if the two expressions are equivalent.
221
+ *
222
+ * We also need to create a schema for this table, as we don't want to pollute the public schema.
223
+ * If we use the public schema, we could potentially conflict with a user's table and we will
224
+ * also cause issues for Prisma's migrate tooling, as it will detect a DB drift.
225
+ */
226
+ this.setupAbilityTable = function () {
227
+ return _this.prisma.$transaction([
228
+ takeLock(_this.prisma),
229
+ _this.prisma.$executeRawUnsafe("\n\t\t\t\tCREATE SCHEMA IF NOT EXISTS _yates;\n\t\t\t"),
230
+ _this.prisma.$executeRawUnsafe("\n\t\t\t\tCREATE TABLE IF NOT EXISTS _yates._yates_abilities (\n\t\t\t\t\tid SERIAL PRIMARY KEY,\n\t\t\t\t\tability_model TEXT NOT NULL,\n\t\t\t\t\tability_name TEXT NOT NULL,\n\t\t\t\t\tability_policy_name TEXT NOT NULL UNIQUE,\n\t\t\t\t\tability_description TEXT NOT NULL,\n\t\t\t\t\tability_operation TEXT NOT NULL,\n\t\t\t\t\tability_expression TEXT NOT NULL,\n\t\t\t\t\tcreated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n\t\t\t\t\tupdated_at TIMESTAMP\n\t\t\t\t);\n\t\t\t"),
231
+ ]);
232
+ };
233
+ this.createAbilityName = function (model, ability) {
234
+ var scope = _this.getDatabaseScope();
235
+ return (0, exports.sanitizeSlug)(hashWithPrefix("yates_ability_", "".concat(scope, "_").concat(model, "_").concat(ability)));
236
+ };
237
+ this.createRoleName = function (name) {
238
+ var scope = _this.getDatabaseScope();
239
+ return (0, exports.sanitizeSlug)(hashWithPrefix("yates_role_", "".concat(scope, "_").concat(name)));
240
+ };
241
+ this.getDefaultAbilities = function (models) {
242
+ var e_1, _a;
243
+ var abilities = {};
334
244
  try {
335
- if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
245
+ for (var models_1 = __values(models), models_1_1 = models_1.next(); !models_1_1.done; models_1_1 = models_1.next()) {
246
+ var model = models_1_1.value;
247
+ abilities[model] = {
248
+ create: {
249
+ description: "Create ".concat(model),
250
+ expression: "true",
251
+ operation: "INSERT",
252
+ model: model,
253
+ slug: "create",
254
+ },
255
+ read: {
256
+ description: "Read ".concat(model),
257
+ expression: "true",
258
+ operation: "SELECT",
259
+ model: model,
260
+ slug: "read",
261
+ },
262
+ update: {
263
+ description: "Update ".concat(model),
264
+ expression: "true",
265
+ operation: "UPDATE",
266
+ model: model,
267
+ slug: "update",
268
+ },
269
+ delete: {
270
+ description: "Delete ".concat(model),
271
+ expression: "true",
272
+ operation: "DELETE",
273
+ model: model,
274
+ slug: "delete",
275
+ },
276
+ };
277
+ }
336
278
  }
337
- finally { if (e_1) throw e_1.error; }
338
- }
339
- };
340
- var client = prisma.$extends({
341
- name: "Yates client",
342
- query: {
343
- $allModels: {
344
- $allOperations: function (params) {
345
- return __awaiter(this, void 0, void 0, function () {
346
- var model, args, query, operation, ctx, role, context, pgRole, _a, _b, k, _c, _d, v, txId, hash, __internalParams;
347
- var e_4, _e, e_5, _f;
348
- return __generator(this, function (_g) {
349
- model = params.model, args = params.args, query = params.query, operation = params.operation;
350
- if (!model) {
351
- // If the model is not defined, we can't apply RLS
352
- // This can occur when you are making a call with Prisma's $queryRaw method
353
- return [2 /*return*/, query(args)];
354
- }
355
- ctx = getContext();
356
- // If ctx is null, the middleware is explicitly skipped
357
- if (ctx === null) {
358
- return [2 /*return*/, query(args)];
359
- }
360
- role = ctx.role, context = ctx.context;
361
- pgRole = (0, exports.createRoleName)(role);
362
- if (context) {
363
- try {
364
- for (_a = __values(Object.keys(context)), _b = _a.next(); !_b.done; _b = _a.next()) {
365
- k = _b.value;
366
- if (!k.match(/^[a-z_\.]+$/)) {
367
- throw new Error("Context variable \"".concat(k, "\" contains invalid characters. Context variables must only contain lowercase letters, numbers, periods and underscores."));
368
- }
369
- if (typeof context[k] !== "number" &&
370
- typeof context[k] !== "string" &&
371
- !Array.isArray(context[k])) {
372
- throw new Error("Context variable \"".concat(k, "\" must be a string, number or array. Got ").concat(typeof context[k]));
373
- }
374
- if (Array.isArray(context[k])) {
375
- try {
376
- for (_c = (e_5 = void 0, __values(context[k])), _d = _c.next(); !_d.done; _d = _c.next()) {
377
- v = _d.value;
378
- if (typeof v !== "string") {
379
- throw new Error("Context variable \"".concat(k, "\" must be an array of strings. Got ").concat(typeof v));
380
- }
381
- }
382
- }
383
- catch (e_5_1) { e_5 = { error: e_5_1 }; }
384
- finally {
385
- try {
386
- if (_d && !_d.done && (_f = _c.return)) _f.call(_c);
387
- }
388
- finally { if (e_5) throw e_5.error; }
389
- }
390
- // Cast to a JSON string so that it can be used in RLS expressions
391
- context[k] = JSON.stringify(context[k]);
392
- }
393
- }
394
- }
395
- catch (e_4_1) { e_4 = { error: e_4_1 }; }
396
- finally {
397
- try {
398
- if (_b && !_b.done && (_e = _a.return)) _e.call(_a);
399
- }
400
- finally { if (e_4) throw e_4.error; }
401
- }
402
- }
403
- txId = hashWithPrefix("yates_tx_", JSON.stringify(ctx));
404
- hash = txId;
405
- if (!batches[hash]) {
406
- // Create a new batch for this role + context combination
407
- batches[hash] = {
408
- pgRole: pgRole,
409
- context: context,
410
- requests: [],
411
- };
412
- // make sure, that we only tick once at a time
413
- if (!tickActive) {
414
- tickActive = true;
415
- process.nextTick(function () {
416
- dispatchBatches();
417
- tickActive = false;
418
- });
419
- }
420
- }
421
- __internalParams = params.__internalParams;
422
- // Add the request to the batch, and return a promise that will be resolved or rejected in dispatchBatches
423
- return [2 /*return*/, new Promise(function (resolve, reject) {
424
- batches[hash].requests.push({
425
- params: __internalParams,
426
- query: query,
427
- args: args,
428
- resolve: resolve,
429
- reject: reject,
430
- });
431
- }).catch(function (e) {
432
- var _a;
433
- // Normalize RLS errors to make them a bit more readable.
434
- if ((_a = e.message) === null || _a === void 0 ? void 0 : _a.includes("new row violates row-level security policy for table")) {
435
- throw new Error("You do not have permission to perform this action: ".concat(model, ".").concat(operation, "(...)"));
436
- }
437
- throw e;
438
- })];
439
- });
440
- });
441
- },
442
- },
443
- },
444
- });
445
- return client;
446
- };
447
- exports.createClient = createClient;
448
- var setRLS = function (prisma, table, roleName, slug, ability) { return __awaiter(void 0, void 0, void 0, function () {
449
- var operation, rawExpression, description;
450
- return __generator(this, function (_a) {
451
- switch (_a.label) {
452
- case 0:
453
- operation = ability.operation, rawExpression = ability.expression, description = ability.description;
454
- if (!rawExpression) {
455
- throw new Error("Expression must be defined for RLS abilities");
279
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
280
+ finally {
281
+ try {
282
+ if (models_1_1 && !models_1_1.done && (_a = models_1.return)) _a.call(models_1);
456
283
  }
457
- // Take a lock and run the RLS setup in a transaction to prevent conflicts
458
- // in a multi-server environment
459
- return [4 /*yield*/, prisma.$transaction(function (tx) { return __awaiter(void 0, void 0, void 0, function () {
460
- var policyName, existingAbilities, existingAbility, shouldUpdateAbilityTable, expression, expression;
461
- return __generator(this, function (_a) {
462
- switch (_a.label) {
463
- case 0: return [4 /*yield*/, takeLock(tx)];
284
+ finally { if (e_1) throw e_1.error; }
285
+ }
286
+ return abilities;
287
+ };
288
+ // This uses client extensions to set the role and context for the current user so that RLS can be applied
289
+ this.createClient = function (getContext, options) {
290
+ if (options === void 0) { options = {}; }
291
+ var prisma = _this.prisma;
292
+ // Set default options
293
+ var _a = options.txMaxWait, txMaxWait = _a === void 0 ? 30000 : _a, _b = options.txTimeout, txTimeout = _b === void 0 ? 30000 : _b;
294
+ // By default, Prisma will batch requests by the transaction ID if it is present.
295
+ // This behaviour prevents automatic batching from working when using Yates, since all queries are executed inside an interactive transaction.
296
+ // To get around this we by monkey patching the batching function to use the Yates ID as the batch ID.
297
+ // To get the batching to work we also need to ensure that all the requests we might want to batch together are generated inside the same tick.
298
+ // This means that all the requests per-tick that have the same role and context values will be batched together,
299
+ // allowing the in-built prisma batch optimizations to work for us.
300
+ // This is why we use process.nextTick and the tickActive flag to ensure we only tick once at a time.
301
+ // See:
302
+ // - https://github.com/prisma/prisma/blob/5.21.1/packages/client/src/runtime/RequestHandler.ts#L122
303
+ // - https://www.prisma.io/docs/orm/prisma-client/queries/query-optimization-performance
304
+ prisma._requestHandler.dataloader.options.batchBy = function (request) {
305
+ var _a;
306
+ var batchIdPQ = _this.getBatchId(request.protocolQuery);
307
+ if ((_a = request.transaction) === null || _a === void 0 ? void 0 : _a.id) {
308
+ return "transaction-".concat(request.transaction.id).concat(batchIdPQ ? "-".concat(batchIdPQ) : "");
309
+ }
310
+ return _this.getBatchId(request.protocolQuery);
311
+ };
312
+ var tickActive = false;
313
+ var batches = {};
314
+ // This function is called once per tick, and processes all the batches that have been created during that tick.
315
+ // Each batch represents a unique role + context combination, and contains all the requests that need to be executed with that role + context.
316
+ var dispatchBatches = function () {
317
+ var e_2, _a;
318
+ var _loop_1 = function (key, batch) {
319
+ delete batches[key];
320
+ // Because batch transactions inside a prisma client query extension can run out of order if used with async middleware,
321
+ // we need to run the logic inside an interactive transaction, however this brings a different set of problems in that the
322
+ // main query will no longer automatically run inside the transaction. We resolve this issue by manually executing the prisma request.
323
+ // See https://github.com/prisma/prisma/issues/18276
324
+ prisma
325
+ .$transaction(function (tx) { return __awaiter(_this, void 0, void 0, function () {
326
+ var _a, _b, _c, key_1, value, e_3_1, txId, results;
327
+ var e_3, _d;
328
+ return __generator(this, function (_e) {
329
+ switch (_e.label) {
330
+ case 0:
331
+ // Switch to the user role, We can't use a prepared statement here, due to limitations in PG not allowing prepared statements to be used in SET LOCAL ROLE
332
+ return [4 /*yield*/, tx.$queryRawUnsafe("SET LOCAL ROLE ".concat(batch.pgRole))];
464
333
  case 1:
465
- _a.sent();
466
- policyName = roleName;
467
- return [4 /*yield*/, tx.$queryRaw(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n\t\t\tselect * from _yates._yates_abilities where ability_model = ", " and ability_policy_name = ", "\n\t\t"], ["\n\t\t\tselect * from _yates._yates_abilities where ability_model = ", " and ability_policy_name = ", "\n\t\t"])), table, policyName)];
334
+ // Switch to the user role, We can't use a prepared statement here, due to limitations in PG not allowing prepared statements to be used in SET LOCAL ROLE
335
+ _e.sent();
336
+ _e.label = 2;
468
337
  case 2:
469
- existingAbilities = _a.sent();
470
- existingAbility = existingAbilities[0];
471
- shouldUpdateAbilityTable = false;
472
- if (!!existingAbility) return [3 /*break*/, 8];
473
- debug("Creating RLS policy for", roleName, "on", table, "for", operation);
474
- return [4 /*yield*/, (0, expressions_1.expressionToSQL)(rawExpression, table)];
338
+ _e.trys.push([2, 7, 8, 9]);
339
+ _a = __values((0, toPairs_1.default)(batch.context)), _b = _a.next();
340
+ _e.label = 3;
475
341
  case 3:
476
- expression = _a.sent();
477
- if (!(operation === "INSERT")) return [3 /*break*/, 5];
478
- return [4 /*yield*/, tx.$queryRawUnsafe("\n\t\t\t\tCREATE POLICY ".concat(policyName, " ON \"public\".\"").concat(table, "\" FOR ").concat(operation, " TO ").concat(roleName, " WITH CHECK (").concat(expression, ");\n\t\t\t"))];
342
+ if (!!_b.done) return [3 /*break*/, 6];
343
+ _c = __read(_b.value, 2), key_1 = _c[0], value = _c[1];
344
+ return [4 /*yield*/, tx.$queryRaw(templateObject_2 || (templateObject_2 = __makeTemplateObject(["SELECT set_config(", ", ", ", true);"], ["SELECT set_config(", ", ", ", true);"])), key_1, value.toString())];
479
345
  case 4:
480
- _a.sent();
481
- return [3 /*break*/, 7];
482
- case 5: return [4 /*yield*/, tx.$queryRawUnsafe("\n\t\t\t\tCREATE POLICY ".concat(policyName, " ON \"public\".\"").concat(table, "\" FOR ").concat(operation, " TO ").concat(roleName, " USING (").concat(expression, ");\n\t\t\t"))];
483
- case 6:
484
- _a.sent();
485
- _a.label = 7;
346
+ _e.sent();
347
+ _e.label = 5;
348
+ case 5:
349
+ _b = _a.next();
350
+ return [3 /*break*/, 3];
351
+ case 6: return [3 /*break*/, 9];
486
352
  case 7:
487
- shouldUpdateAbilityTable = true;
488
- return [3 /*break*/, 14];
353
+ e_3_1 = _e.sent();
354
+ e_3 = { error: e_3_1 };
355
+ return [3 /*break*/, 9];
489
356
  case 8:
490
- if (!(existingAbility.ability_expression !== rawExpression.toString())) return [3 /*break*/, 14];
491
- debug("Updating RLS policy for", roleName, "on", table, "for", operation);
492
- return [4 /*yield*/, (0, expressions_1.expressionToSQL)(rawExpression, table)];
357
+ try {
358
+ if (_b && !_b.done && (_d = _a.return)) _d.call(_a);
359
+ }
360
+ finally { if (e_3) throw e_3.error; }
361
+ return [7 /*endfinally*/];
493
362
  case 9:
494
- expression = _a.sent();
495
- if (!(operation === "INSERT")) return [3 /*break*/, 11];
496
- return [4 /*yield*/, tx.$queryRawUnsafe("\n\t\t\t\tALTER POLICY ".concat(policyName, " ON \"public\".\"").concat(table, "\" TO ").concat(roleName, " WITH CHECK (").concat(expression, ");\n\t\t\t"))];
363
+ txId = tx[Symbol.for("prisma.client.transaction.id")];
364
+ return [4 /*yield*/, Promise.all(batch.requests.map(function (request) {
365
+ return prisma._executeRequest(__assign(__assign({}, request.params), { transaction: {
366
+ kind: "itx",
367
+ id: txId,
368
+ } }));
369
+ }))];
497
370
  case 10:
498
- _a.sent();
499
- return [3 /*break*/, 13];
500
- case 11: return [4 /*yield*/, tx.$queryRawUnsafe("\n\t\t\t\tALTER POLICY ".concat(policyName, " ON \"public\".\"").concat(table, "\" TO ").concat(roleName, " USING (").concat(expression, ");\n\t\t\t"))];
501
- case 12:
502
- _a.sent();
503
- _a.label = 13;
504
- case 13:
505
- shouldUpdateAbilityTable = true;
506
- _a.label = 14;
507
- case 14:
508
- if (!shouldUpdateAbilityTable) return [3 /*break*/, 16];
509
- return [4 /*yield*/, upsertAbility(tx, {
510
- ability_model: table,
511
- ability_name: slug,
512
- ability_policy_name: policyName,
513
- ability_description: description !== null && description !== void 0 ? description : "",
514
- ability_operation: operation,
515
- // We store the string representation of the expression so that
516
- // we can compare it later without having to recompute the SQL
517
- ability_expression: rawExpression.toString(),
518
- })];
519
- case 15:
520
- _a.sent();
521
- _a.label = 16;
522
- case 16: return [2 /*return*/];
371
+ results = _e.sent();
372
+ return [2 /*return*/, results];
523
373
  }
524
374
  });
525
- }); })];
526
- case 1:
527
- // Take a lock and run the RLS setup in a transaction to prevent conflicts
528
- // in a multi-server environment
529
- _a.sent();
530
- return [2 /*return*/];
531
- }
532
- });
533
- }); };
534
- var createRoles = function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
535
- var abilities, runtimeDataModel, models, diff, models_1, models_1_1, model, ability, operation, roles, pgRoles, existingAbilities, pgPolicies, migratedAbilities, _c, _d, _e, _i, model, table, _loop_2, _f, _g, _h, _j, slug, _loop_3, _k, _l, _m, _o, key;
536
- var e_6, _p;
537
- var _q;
538
- var prisma = _b.prisma, customAbilities = _b.customAbilities, getRoles = _b.getRoles;
539
- return __generator(this, function (_r) {
540
- switch (_r.label) {
541
- case 0:
542
- abilities = {};
543
- runtimeDataModel = prisma
544
- ._runtimeDataModel;
545
- models = Object.keys(runtimeDataModel.models).map(function (m) { return runtimeDataModel.models[m].dbName || m; });
546
- if (customAbilities) {
547
- diff = (0, difference_1.default)(Object.keys(customAbilities), models);
548
- if (diff.length) {
549
- throw new Error("Invalid models in custom abilities: ".concat(diff.join(", ")));
550
- }
551
- }
552
- try {
553
- for (models_1 = __values(models), models_1_1 = models_1.next(); !models_1_1.done; models_1_1 = models_1.next()) {
554
- model = models_1_1.value;
555
- abilities[model] = {
556
- create: {
557
- description: "Create ".concat(model),
558
- expression: "true",
559
- operation: "INSERT",
560
- model: model,
561
- slug: "create",
562
- },
563
- read: {
564
- description: "Read ".concat(model),
565
- expression: "true",
566
- operation: "SELECT",
567
- model: model,
568
- slug: "read",
569
- },
570
- update: {
571
- description: "Update ".concat(model),
572
- expression: "true",
573
- operation: "UPDATE",
574
- model: model,
575
- slug: "update",
576
- },
577
- delete: {
578
- description: "Delete ".concat(model),
579
- expression: "true",
580
- operation: "DELETE",
581
- model: model,
582
- slug: "delete",
583
- },
584
- };
585
- if (customAbilities === null || customAbilities === void 0 ? void 0 : customAbilities[model]) {
586
- for (ability in customAbilities[model]) {
587
- operation =
588
- // biome-ignore lint/style/noNonNullAssertion: TODO fix this
589
- (_q = customAbilities[model][ability]) === null || _q === void 0 ? void 0 : _q.operation;
590
- if (!operation)
591
- continue;
592
- // biome-ignore lint/style/noNonNullAssertion: TODO fix this
593
- abilities[model][ability] = __assign(__assign({}, customAbilities[model][ability]), { operation: operation, model: model, slug: ability });
375
+ }); }, {
376
+ maxWait: txMaxWait,
377
+ timeout: txTimeout,
378
+ })
379
+ .then(function (results) {
380
+ results.forEach(function (result, index) {
381
+ batch.requests[index].resolve(result);
382
+ });
383
+ })
384
+ .catch(function (e) {
385
+ var e_4, _a;
386
+ try {
387
+ for (var _b = (e_4 = void 0, __values(batch.requests)), _c = _b.next(); !_c.done; _c = _b.next()) {
388
+ var request = _c.value;
389
+ request.reject(e);
390
+ }
391
+ }
392
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
393
+ finally {
394
+ try {
395
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
594
396
  }
397
+ finally { if (e_4) throw e_4.error; }
595
398
  }
399
+ delete batches[key];
400
+ });
401
+ };
402
+ try {
403
+ for (var _b = __values(Object.entries(batches)), _c = _b.next(); !_c.done; _c = _b.next()) {
404
+ var _d = __read(_c.value, 2), key = _d[0], batch = _d[1];
405
+ _loop_1(key, batch);
596
406
  }
597
407
  }
598
- catch (e_6_1) { e_6 = { error: e_6_1 }; }
408
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
599
409
  finally {
600
410
  try {
601
- if (models_1_1 && !models_1_1.done && (_p = models_1.return)) _p.call(models_1);
411
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
602
412
  }
603
- finally { if (e_6) throw e_6.error; }
413
+ finally { if (e_2) throw e_2.error; }
604
414
  }
605
- debug("Setting up ability table");
606
- return [4 /*yield*/, setupAbilityTable(prisma)];
607
- case 1:
608
- _r.sent();
609
- roles = getRoles(abilities);
610
- return [4 /*yield*/, prisma.$queryRawUnsafe("\n\t\tselect * from pg_catalog.pg_roles where rolname like 'yates%'\n\t")];
611
- case 2:
612
- pgRoles = _r.sent();
613
- return [4 /*yield*/, prisma.$queryRawUnsafe("\n\t\tselect * from _yates._yates_abilities;\n\t")];
614
- case 3:
615
- existingAbilities = _r.sent();
616
- if (!(existingAbilities.length === 0)) return [3 /*break*/, 6];
617
- debug('No existing abilities found, importing from "pg_policies" table');
618
- return [4 /*yield*/, prisma.$queryRawUnsafe("\n\t\t\tselect * from pg_catalog.pg_policies where policyname like 'yates%'\n\t\t")];
619
- case 4:
620
- pgPolicies = _r.sent();
621
- if (!pgPolicies.length) return [3 /*break*/, 6];
622
- migratedAbilities = pgPolicies.map(function (policy) {
623
- var _a, _b;
624
- return ({
625
- ability_model: policy.tablename,
626
- ability_name: policy.policyname,
627
- ability_policy_name: policy.policyname,
628
- ability_description: "",
629
- ability_operation: policy.cmd,
630
- ability_expression: (_b = (_a = policy.qual) !== null && _a !== void 0 ? _a : policy.with_check) !== null && _b !== void 0 ? _b : "",
631
- });
632
- });
633
- return [4 /*yield*/, prisma.$transaction(__spreadArray([
634
- takeLock(prisma)
635
- ], __read(migratedAbilities.map(function (ma) { return upsertAbility(prisma, ma); })), false))];
636
- case 5:
637
- _r.sent();
638
- existingAbilities.push.apply(existingAbilities, __spreadArray([], __read(migratedAbilities), false));
639
- _r.label = 6;
640
- case 6:
641
- _c = abilities;
642
- _d = [];
643
- for (_e in _c)
644
- _d.push(_e);
645
- _i = 0;
646
- _r.label = 7;
647
- case 7:
648
- if (!(_i < _d.length)) return [3 /*break*/, 13];
649
- _e = _d[_i];
650
- if (!(_e in _c)) return [3 /*break*/, 12];
651
- model = _e;
652
- table = model;
653
- return [4 /*yield*/, prisma.$transaction([
654
- takeLock(prisma),
655
- prisma.$queryRawUnsafe("ALTER table \"".concat(table, "\" enable row level security;")),
656
- ])];
657
- case 8:
658
- _r.sent();
659
- _loop_2 = function (slug) {
660
- var ability, roleName;
661
- return __generator(this, function (_s) {
662
- switch (_s.label) {
663
- case 0:
664
- ability =
665
- // biome-ignore lint/style/noNonNullAssertion: TODO fix this
666
- abilities[model][slug];
667
- if (!VALID_OPERATIONS.includes(ability.operation)) {
668
- throw new Error("Invalid operation: ".concat(ability.operation));
669
- }
670
- roleName = (0, exports.createAbilityName)(model, slug);
671
- if (!pgRoles.find(function (role) { return role.rolname === roleName; })) return [3 /*break*/, 1];
672
- debug("Role already exists", roleName);
673
- return [3 /*break*/, 3];
674
- case 1: return [4 /*yield*/, prisma.$transaction([
675
- takeLock(prisma),
676
- prisma.$queryRawUnsafe("\n\t\t\t\t\tdo\n\t\t\t\t\t$$\n\t\t\t\t\tbegin\n\t\t\t\t\tif not exists (select * from pg_catalog.pg_roles where rolname = '".concat(roleName, "') then \n\t\t\t\t\t\tcreate role ").concat(roleName, ";\n\t\t\t\t\tend if;\n\t\t\t\t\tend\n\t\t\t\t\t$$\n\t\t\t\t\t;\n\t\t\t\t")),
677
- prisma.$queryRawUnsafe("\n\t\t\t\t\tGRANT ".concat(ability.operation, " ON \"").concat(table, "\" TO ").concat(roleName, ";\n\t\t\t\t")),
678
- ])];
679
- case 2:
680
- _s.sent();
681
- _s.label = 3;
682
- case 3:
683
- if (!ability.expression) return [3 /*break*/, 5];
684
- return [4 /*yield*/, setRLS(prisma, table, roleName, slug, ability)];
685
- case 4:
686
- _s.sent();
687
- _s.label = 5;
688
- case 5: return [2 /*return*/];
415
+ };
416
+ var createRoleName = _this.createRoleName.bind(_this);
417
+ var client = prisma.$extends({
418
+ name: "Yates client",
419
+ query: {
420
+ $allModels: {
421
+ $allOperations: function (params) {
422
+ return __awaiter(this, void 0, void 0, function () {
423
+ var model, args, query, operation, ctx, role, context, pgRole, _a, _b, k, _c, _d, v, txId, hash, __internalParams;
424
+ var e_5, _e, e_6, _f;
425
+ return __generator(this, function (_g) {
426
+ model = params.model, args = params.args, query = params.query, operation = params.operation;
427
+ if (!model) {
428
+ // If the model is not defined, we can't apply RLS
429
+ // This can occur when you are making a call with Prisma's $queryRaw method
430
+ return [2 /*return*/, query(args)];
431
+ }
432
+ ctx = getContext();
433
+ // If ctx is null, the middleware is explicitly skipped
434
+ if (ctx === null) {
435
+ return [2 /*return*/, query(args)];
436
+ }
437
+ role = ctx.role, context = ctx.context;
438
+ pgRole = createRoleName(role);
439
+ if (context) {
440
+ try {
441
+ for (_a = __values(Object.keys(context)), _b = _a.next(); !_b.done; _b = _a.next()) {
442
+ k = _b.value;
443
+ if (!k.match(/^[a-z_\.]+$/)) {
444
+ throw new Error("Context variable \"".concat(k, "\" contains invalid characters. Context variables must only contain lowercase letters, numbers, periods and underscores."));
445
+ }
446
+ if (typeof context[k] !== "number" &&
447
+ typeof context[k] !== "string" &&
448
+ !Array.isArray(context[k])) {
449
+ throw new Error("Context variable \"".concat(k, "\" must be a string, number or array. Got ").concat(typeof context[k]));
450
+ }
451
+ if (Array.isArray(context[k])) {
452
+ try {
453
+ for (_c = (e_6 = void 0, __values(context[k])), _d = _c.next(); !_d.done; _d = _c.next()) {
454
+ v = _d.value;
455
+ if (typeof v !== "string") {
456
+ throw new Error("Context variable \"".concat(k, "\" must be an array of strings. Got ").concat(typeof v));
457
+ }
458
+ }
459
+ }
460
+ catch (e_6_1) { e_6 = { error: e_6_1 }; }
461
+ finally {
462
+ try {
463
+ if (_d && !_d.done && (_f = _c.return)) _f.call(_c);
464
+ }
465
+ finally { if (e_6) throw e_6.error; }
466
+ }
467
+ // Cast to a JSON string so that it can be used in RLS expressions
468
+ context[k] = JSON.stringify(context[k]);
469
+ }
470
+ }
471
+ }
472
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
473
+ finally {
474
+ try {
475
+ if (_b && !_b.done && (_e = _a.return)) _e.call(_a);
476
+ }
477
+ finally { if (e_5) throw e_5.error; }
478
+ }
479
+ }
480
+ txId = hashWithPrefix("yates_tx_", JSON.stringify(ctx));
481
+ hash = txId;
482
+ if (!batches[hash]) {
483
+ // Create a new batch for this role + context combination
484
+ batches[hash] = {
485
+ pgRole: pgRole,
486
+ context: context,
487
+ requests: [],
488
+ };
489
+ // make sure, that we only tick once at a time
490
+ if (!tickActive) {
491
+ tickActive = true;
492
+ process.nextTick(function () {
493
+ dispatchBatches();
494
+ tickActive = false;
495
+ });
496
+ }
497
+ }
498
+ __internalParams = params.__internalParams;
499
+ // Add the request to the batch, and return a promise that will be resolved or rejected in dispatchBatches
500
+ return [2 /*return*/, new Promise(function (resolve, reject) {
501
+ batches[hash].requests.push({
502
+ params: __internalParams,
503
+ query: query,
504
+ args: args,
505
+ resolve: resolve,
506
+ reject: reject,
507
+ });
508
+ }).catch(function (e) {
509
+ var _a;
510
+ // Normalize RLS errors to make them a bit more readable.
511
+ if ((_a = e.message) === null || _a === void 0 ? void 0 : _a.includes("new row violates row-level security policy for table")) {
512
+ throw new Error("You do not have permission to perform this action: ".concat(model, ".").concat(operation, "(...)"));
513
+ }
514
+ throw e;
515
+ })];
516
+ });
517
+ });
518
+ },
519
+ },
520
+ },
521
+ });
522
+ return client;
523
+ };
524
+ this.setRLS = function (prisma, table, roleName, slug, ability) { return __awaiter(_this, void 0, void 0, function () {
525
+ var operation, rawExpression, description;
526
+ var _this = this;
527
+ return __generator(this, function (_a) {
528
+ switch (_a.label) {
529
+ case 0:
530
+ operation = ability.operation, rawExpression = ability.expression, description = ability.description;
531
+ if (!rawExpression) {
532
+ throw new Error("Expression must be defined for RLS abilities");
689
533
  }
690
- });
691
- };
692
- _f = abilities[model];
693
- _g = [];
694
- for (_h in _f)
695
- _g.push(_h);
696
- _j = 0;
697
- _r.label = 9;
698
- case 9:
699
- if (!(_j < _g.length)) return [3 /*break*/, 12];
700
- _h = _g[_j];
701
- if (!(_h in _f)) return [3 /*break*/, 11];
702
- slug = _h;
703
- return [5 /*yield**/, _loop_2(slug)];
704
- case 10:
705
- _r.sent();
706
- _r.label = 11;
707
- case 11:
708
- _j++;
709
- return [3 /*break*/, 9];
710
- case 12:
711
- _i++;
712
- return [3 /*break*/, 7];
713
- case 13:
714
- _loop_3 = function (key) {
715
- var role, wildCardAbilities, roleAbilities, rlsRoles, userRoles, oldRoles, policies;
716
- return __generator(this, function (_t) {
717
- switch (_t.label) {
718
- case 0:
719
- role = (0, exports.createRoleName)(key);
720
- return [4 /*yield*/, prisma.$executeRawUnsafe("\n\t\t\tdo\n\t\t\t$$\n\t\t\tbegin\n\t\t\tif not exists (select * from pg_catalog.pg_roles where rolname = '".concat(role, "') then \n\t\t\t\tcreate role ").concat(role, ";\n\t\t\tend if;\n\t\t\tend\n\t\t\t$$\n\t\t\t;\n\t\t"))];
721
- case 1:
722
- _t.sent();
723
- wildCardAbilities = (0, flatMap_1.default)(abilities, function (model, modelName) {
724
- return (0, map_1.default)(model, function (_params, slug) {
725
- return (0, exports.createAbilityName)(modelName, slug);
726
- });
534
+ // Take a lock and run the RLS setup in a transaction to prevent conflicts
535
+ // in a multi-server environment
536
+ return [4 /*yield*/, prisma.$transaction(function (tx) { return __awaiter(_this, void 0, void 0, function () {
537
+ var policyName, existingAbilities, existingAbility, shouldUpdateAbilityTable, expression, expression;
538
+ return __generator(this, function (_a) {
539
+ switch (_a.label) {
540
+ case 0: return [4 /*yield*/, takeLock(tx)];
541
+ case 1:
542
+ _a.sent();
543
+ policyName = roleName;
544
+ return [4 /*yield*/, tx.$queryRaw(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n\t\t\t\tselect * from _yates._yates_abilities where ability_model = ", " and ability_policy_name = ", "\n\t\t\t"], ["\n\t\t\t\tselect * from _yates._yates_abilities where ability_model = ", " and ability_policy_name = ", "\n\t\t\t"])), table, policyName)];
545
+ case 2:
546
+ existingAbilities = _a.sent();
547
+ existingAbility = existingAbilities[0];
548
+ shouldUpdateAbilityTable = false;
549
+ if (!!existingAbility) return [3 /*break*/, 8];
550
+ debug("Creating RLS policy for", roleName, "on", table, "for", operation);
551
+ return [4 /*yield*/, (0, expressions_1.expressionToSQL)(rawExpression, table)];
552
+ case 3:
553
+ expression = _a.sent();
554
+ if (!(operation === "INSERT")) return [3 /*break*/, 5];
555
+ return [4 /*yield*/, tx.$queryRawUnsafe("\n\t\t\t\t\tCREATE POLICY ".concat(policyName, " ON \"public\".\"").concat(table, "\" FOR ").concat(operation, " TO ").concat(roleName, " WITH CHECK (").concat(expression, ");\n\t\t\t\t"))];
556
+ case 4:
557
+ _a.sent();
558
+ return [3 /*break*/, 7];
559
+ case 5: return [4 /*yield*/, tx.$queryRawUnsafe("\n\t\t\t\t\tCREATE POLICY ".concat(policyName, " ON \"public\".\"").concat(table, "\" FOR ").concat(operation, " TO ").concat(roleName, " USING (").concat(expression, ");\n\t\t\t\t"))];
560
+ case 6:
561
+ _a.sent();
562
+ _a.label = 7;
563
+ case 7:
564
+ shouldUpdateAbilityTable = true;
565
+ return [3 /*break*/, 14];
566
+ case 8:
567
+ if (!(existingAbility.ability_expression !== rawExpression.toString())) return [3 /*break*/, 14];
568
+ debug("Updating RLS policy for", roleName, "on", table, "for", operation);
569
+ return [4 /*yield*/, (0, expressions_1.expressionToSQL)(rawExpression, table)];
570
+ case 9:
571
+ expression = _a.sent();
572
+ if (!(operation === "INSERT")) return [3 /*break*/, 11];
573
+ return [4 /*yield*/, tx.$queryRawUnsafe("\n\t\t\t\t\tALTER POLICY ".concat(policyName, " ON \"public\".\"").concat(table, "\" TO ").concat(roleName, " WITH CHECK (").concat(expression, ");\n\t\t\t\t"))];
574
+ case 10:
575
+ _a.sent();
576
+ return [3 /*break*/, 13];
577
+ case 11: return [4 /*yield*/, tx.$queryRawUnsafe("\n\t\t\t\t\tALTER POLICY ".concat(policyName, " ON \"public\".\"").concat(table, "\" TO ").concat(roleName, " USING (").concat(expression, ");\n\t\t\t\t"))];
578
+ case 12:
579
+ _a.sent();
580
+ _a.label = 13;
581
+ case 13:
582
+ shouldUpdateAbilityTable = true;
583
+ _a.label = 14;
584
+ case 14:
585
+ if (!shouldUpdateAbilityTable) return [3 /*break*/, 16];
586
+ return [4 /*yield*/, upsertAbility(tx, {
587
+ ability_model: table,
588
+ ability_name: slug,
589
+ ability_policy_name: policyName,
590
+ ability_description: description !== null && description !== void 0 ? description : "",
591
+ ability_operation: operation,
592
+ // We store the string representation of the expression so that
593
+ // we can compare it later without having to recompute the SQL
594
+ ability_expression: rawExpression.toString(),
595
+ })];
596
+ case 15:
597
+ _a.sent();
598
+ _a.label = 16;
599
+ case 16: return [2 /*return*/];
600
+ }
727
601
  });
728
- roleAbilities = roles[key];
729
- rlsRoles = roleAbilities === "*"
730
- ? wildCardAbilities
731
- : roleAbilities.map(function (ability) {
602
+ }); })];
603
+ case 1:
604
+ // Take a lock and run the RLS setup in a transaction to prevent conflicts
605
+ // in a multi-server environment
606
+ _a.sent();
607
+ return [2 /*return*/];
608
+ }
609
+ });
610
+ }); };
611
+ this.createRoles = function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
612
+ var runtimeDataModel, models, diff, defaultAbilities, abilities, models_2, models_2_1, model, ability, operation, roles, pgRoles, existingAbilities, pgPolicies, migratedAbilities, _c, _d, _e, _i, model, table, _loop_2, this_1, _f, _g, _h, _j, slug, _loop_3, this_2, _k, _l, _m, _o, key;
613
+ var e_7, _p;
614
+ var _this = this;
615
+ var _q;
616
+ var customAbilities = _b.customAbilities, getRoles = _b.getRoles;
617
+ return __generator(this, function (_r) {
618
+ switch (_r.label) {
619
+ case 0: return [4 /*yield*/, this.ensureDatabaseScope()];
620
+ case 1:
621
+ _r.sent();
622
+ runtimeDataModel = this.inspectRunTimeDataModel();
623
+ models = Object.keys(runtimeDataModel.models).map(function (m) { return runtimeDataModel.models[m].dbName || m; });
624
+ if (customAbilities) {
625
+ diff = (0, difference_1.default)(Object.keys(customAbilities), models);
626
+ if (diff.length) {
627
+ throw new Error("Invalid models in custom abilities: ".concat(diff.join(", ")));
628
+ }
629
+ }
630
+ defaultAbilities = this.getDefaultAbilities(models);
631
+ abilities = (0, cloneDeep_1.default)(defaultAbilities);
632
+ try {
633
+ for (models_2 = __values(models), models_2_1 = models_2.next(); !models_2_1.done; models_2_1 = models_2.next()) {
634
+ model = models_2_1.value;
635
+ if (customAbilities === null || customAbilities === void 0 ? void 0 : customAbilities[model]) {
636
+ for (ability in customAbilities[model]) {
637
+ operation =
732
638
  // biome-ignore lint/style/noNonNullAssertion: TODO fix this
733
- return (0, exports.createAbilityName)(ability.model, ability.slug);
734
- });
735
- // Note: We need to GRANT all on schema public so that we can resolve relation queries with prisma, as they will sometimes use a join table.
736
- // This is not ideal, but because we are using RLS, it's not a security risk. Any table with RLS also needs a corresponding policy for the role to have access.
737
- return [4 /*yield*/, prisma.$transaction([
738
- takeLock(prisma),
739
- prisma.$executeRawUnsafe("GRANT ALL ON ALL TABLES IN SCHEMA public TO ".concat(role, ";")),
740
- prisma.$executeRawUnsafe("\n\t\t\t\tGRANT ALL ON ALL SEQUENCES IN SCHEMA public TO ".concat(role, ";\n\t\t\t")),
741
- prisma.$executeRawUnsafe("\n\t\t\t\tGRANT ALL ON SCHEMA public TO ".concat(role, ";\n\t\t\t")),
742
- prisma.$queryRawUnsafe("GRANT ".concat(rlsRoles.join(", "), " TO ").concat(role)),
743
- ])];
744
- case 2:
745
- // Note: We need to GRANT all on schema public so that we can resolve relation queries with prisma, as they will sometimes use a join table.
746
- // This is not ideal, but because we are using RLS, it's not a security risk. Any table with RLS also needs a corresponding policy for the role to have access.
747
- _t.sent();
748
- return [4 /*yield*/, prisma.$queryRawUnsafe("\n\t\t\tWITH RECURSIVE cte AS (\n\t\t\t\tSELECT oid FROM pg_roles where rolname = '".concat(role, "'\n\t\t\t\tUNION ALL\n\t\t\t\tSELECT m.roleid\n\t\t\t\tFROM cte\n\t\t\t\tJOIN pg_auth_members m ON m.member = cte.oid\n\t\t\t\t)\n\t\t\tSELECT oid, oid::regrole::text AS rolename FROM cte where oid::regrole::text != '").concat(role, "'; \n\t "))];
749
- case 3:
750
- userRoles = _t.sent();
751
- oldRoles = userRoles
752
- .filter(function (_a) {
753
- var rolename = _a.rolename;
754
- return !rlsRoles.includes(rolename);
755
- })
756
- .map(function (_a) {
757
- var rolename = _a.rolename;
758
- return rolename;
759
- });
760
- if (!oldRoles.length) return [3 /*break*/, 8];
761
- // Now revoke old roles from the user role
762
- debug("Revoking old roles", oldRoles.join(", "));
763
- return [4 /*yield*/, prisma.$executeRawUnsafe("REVOKE ".concat(oldRoles.join(", "), " FROM ").concat(role))];
764
- case 4:
765
- _t.sent();
766
- return [4 /*yield*/, prisma.$queryRawUnsafe("SELECT * FROM pg_catalog.pg_policies WHERE policyname IN (".concat(oldRoles
767
- .map(function (or) { return "'".concat(or, "'"); })
768
- .join(", "), ")"))];
769
- case 5:
770
- policies = _t.sent();
771
- return [4 /*yield*/, prisma.$transaction(__spreadArray([
772
- takeLock(prisma)
773
- ], __read(policies.map(function (oldPolicy) {
774
- return prisma.$executeRawUnsafe("DROP POLICY ".concat(oldPolicy.policyname, " ON \"").concat(oldPolicy.tablename, "\""));
775
- })), false))];
776
- case 6:
777
- _t.sent();
778
- debug("Revoked old rows from ability table", oldRoles.join(", "));
779
- return [4 /*yield*/, prisma.$executeRawUnsafe("DELETE FROM _yates._yates_abilities WHERE ability_policy_name IN (".concat(oldRoles
780
- .map(function (or) { return "'".concat(or, "'"); })
781
- .join(", "), ")"))];
782
- case 7:
783
- _t.sent();
784
- _t.label = 8;
785
- case 8: return [2 /*return*/];
639
+ (_q = customAbilities[model][ability]) === null || _q === void 0 ? void 0 : _q.operation;
640
+ if (!operation)
641
+ continue;
642
+ // biome-ignore lint/style/noNonNullAssertion: TODO fix this
643
+ abilities[model][ability] = __assign(__assign({}, customAbilities[model][ability]), { operation: operation, model: model, slug: ability });
644
+ }
645
+ }
646
+ }
786
647
  }
787
- });
788
- };
789
- _k = roles;
790
- _l = [];
791
- for (_m in _k)
792
- _l.push(_m);
793
- _o = 0;
794
- _r.label = 14;
795
- case 14:
796
- if (!(_o < _l.length)) return [3 /*break*/, 17];
797
- _m = _l[_o];
798
- if (!(_m in _k)) return [3 /*break*/, 16];
799
- key = _m;
800
- return [5 /*yield**/, _loop_3(key)];
801
- case 15:
802
- _r.sent();
803
- _r.label = 16;
804
- case 16:
805
- _o++;
806
- return [3 /*break*/, 14];
807
- case 17: return [2 /*return*/];
648
+ catch (e_7_1) { e_7 = { error: e_7_1 }; }
649
+ finally {
650
+ try {
651
+ if (models_2_1 && !models_2_1.done && (_p = models_2.return)) _p.call(models_2);
652
+ }
653
+ finally { if (e_7) throw e_7.error; }
654
+ }
655
+ roles = getRoles(abilities);
656
+ return [4 /*yield*/, this.prisma.$queryRawUnsafe("\n\t\t\tselect * from pg_catalog.pg_roles where rolname like 'yates%'\n\t\t")];
657
+ case 2:
658
+ pgRoles = _r.sent();
659
+ return [4 /*yield*/, this.prisma.$queryRawUnsafe("\n\t\t\tselect * from _yates._yates_abilities;\n\t\t")];
660
+ case 3:
661
+ existingAbilities = _r.sent();
662
+ if (!(existingAbilities.length === 0)) return [3 /*break*/, 6];
663
+ debug('No existing abilities found, importing from "pg_policies" table');
664
+ return [4 /*yield*/, this.prisma.$queryRawUnsafe("\n\t\t\t\tselect * from pg_catalog.pg_policies where policyname like 'yates%'\n\t\t\t")];
665
+ case 4:
666
+ pgPolicies = _r.sent();
667
+ if (!pgPolicies.length) return [3 /*break*/, 6];
668
+ migratedAbilities = pgPolicies.map(function (policy) {
669
+ var _a, _b;
670
+ return ({
671
+ ability_model: policy.tablename,
672
+ ability_name: policy.policyname,
673
+ ability_policy_name: policy.policyname,
674
+ ability_description: "",
675
+ ability_operation: policy.cmd,
676
+ ability_expression: (_b = (_a = policy.qual) !== null && _a !== void 0 ? _a : policy.with_check) !== null && _b !== void 0 ? _b : "",
677
+ });
678
+ });
679
+ return [4 /*yield*/, this.prisma.$transaction(__spreadArray([
680
+ takeLock(this.prisma)
681
+ ], __read(migratedAbilities.map(function (ma) { return upsertAbility(_this.prisma, ma); })), false))];
682
+ case 5:
683
+ _r.sent();
684
+ existingAbilities.push.apply(existingAbilities, __spreadArray([], __read(migratedAbilities), false));
685
+ _r.label = 6;
686
+ case 6:
687
+ _c = abilities;
688
+ _d = [];
689
+ for (_e in _c)
690
+ _d.push(_e);
691
+ _i = 0;
692
+ _r.label = 7;
693
+ case 7:
694
+ if (!(_i < _d.length)) return [3 /*break*/, 13];
695
+ _e = _d[_i];
696
+ if (!(_e in _c)) return [3 /*break*/, 12];
697
+ model = _e;
698
+ table = model;
699
+ return [4 /*yield*/, this.prisma.$transaction([
700
+ takeLock(this.prisma),
701
+ this.prisma.$queryRawUnsafe("ALTER table \"".concat(table, "\" enable row level security;")),
702
+ ])];
703
+ case 8:
704
+ _r.sent();
705
+ _loop_2 = function (slug) {
706
+ var ability, roleName;
707
+ return __generator(this, function (_s) {
708
+ switch (_s.label) {
709
+ case 0:
710
+ ability =
711
+ // biome-ignore lint/style/noNonNullAssertion: TODO fix this
712
+ abilities[model][slug];
713
+ if (!VALID_OPERATIONS.includes(ability.operation)) {
714
+ throw new Error("Invalid operation: ".concat(ability.operation));
715
+ }
716
+ roleName = this_1.createAbilityName(model, slug);
717
+ if (!pgRoles.find(function (role) { return role.rolname === roleName; })) return [3 /*break*/, 1];
718
+ debug("Role already exists", roleName, model, slug);
719
+ return [3 /*break*/, 3];
720
+ case 1: return [4 /*yield*/, this_1.prisma.$transaction([
721
+ takeLock(this_1.prisma),
722
+ this_1.prisma.$queryRawUnsafe("\n\t\t\t\t\t\tdo\n\t\t\t\t\t\t$$\n\t\t\t\t\t\tbegin\n\t\t\t\t\t\tif not exists (select * from pg_catalog.pg_roles where rolname = '".concat(roleName, "') then \n\t\t\t\t\t\t\tcreate role ").concat(roleName, ";\n\t\t\t\t\t\tend if;\n\t\t\t\t\t\tend\n\t\t\t\t\t\t$$\n\t\t\t\t\t\t;\n\t\t\t\t\t")),
723
+ this_1.prisma.$queryRawUnsafe("\n\t\t\t\t\t\tGRANT ".concat(ability.operation, " ON \"").concat(table, "\" TO ").concat(roleName, ";\n\t\t\t\t\t")),
724
+ ])];
725
+ case 2:
726
+ _s.sent();
727
+ _s.label = 3;
728
+ case 3:
729
+ if (!ability.expression) return [3 /*break*/, 5];
730
+ return [4 /*yield*/, this_1.setRLS(this_1.prisma, table, roleName, slug, ability)];
731
+ case 4:
732
+ _s.sent();
733
+ _s.label = 5;
734
+ case 5: return [2 /*return*/];
735
+ }
736
+ });
737
+ };
738
+ this_1 = this;
739
+ _f = abilities[model];
740
+ _g = [];
741
+ for (_h in _f)
742
+ _g.push(_h);
743
+ _j = 0;
744
+ _r.label = 9;
745
+ case 9:
746
+ if (!(_j < _g.length)) return [3 /*break*/, 12];
747
+ _h = _g[_j];
748
+ if (!(_h in _f)) return [3 /*break*/, 11];
749
+ slug = _h;
750
+ return [5 /*yield**/, _loop_2(slug)];
751
+ case 10:
752
+ _r.sent();
753
+ _r.label = 11;
754
+ case 11:
755
+ _j++;
756
+ return [3 /*break*/, 9];
757
+ case 12:
758
+ _i++;
759
+ return [3 /*break*/, 7];
760
+ case 13:
761
+ _loop_3 = function (key) {
762
+ var role, wildCardAbilities, roleAbilities, rlsRoles, userRoles, oldRoles, policies;
763
+ return __generator(this, function (_t) {
764
+ switch (_t.label) {
765
+ case 0:
766
+ role = this_2.createRoleName(key);
767
+ return [4 /*yield*/, this_2.prisma.$executeRawUnsafe("\n\t\t\t\tdo\n\t\t\t\t$$\n\t\t\t\tbegin\n\t\t\t\tif not exists (select * from pg_catalog.pg_roles where rolname = '".concat(role, "') then \n\t\t\t\t\tcreate role ").concat(role, ";\n\t\t\t\tend if;\n\t\t\t\tend\n\t\t\t\t$$\n\t\t\t\t;\n\t\t\t"))];
768
+ case 1:
769
+ _t.sent();
770
+ wildCardAbilities = (0, flatMap_1.default)(defaultAbilities, function (model, modelName) {
771
+ return (0, map_1.default)(model, function (_params, slug) {
772
+ return _this.createAbilityName(modelName, slug);
773
+ });
774
+ });
775
+ roleAbilities = roles[key];
776
+ rlsRoles = roleAbilities === "*"
777
+ ? wildCardAbilities
778
+ : roleAbilities.map(function (ability) {
779
+ // biome-ignore lint/style/noNonNullAssertion: TODO fix this
780
+ return _this.createAbilityName(ability.model, ability.slug);
781
+ });
782
+ debug("Setting up role", key, role, "with abilities", rlsRoles.join(", "));
783
+ // Note: We need to GRANT all on schema public so that we can resolve relation queries with prisma, as they will sometimes use a join table.
784
+ // This is not ideal, but because we are using RLS, it's not a security risk. Any table with RLS also needs a corresponding policy for the role to have access.
785
+ return [4 /*yield*/, this_2.prisma.$transaction([
786
+ takeLock(this_2.prisma),
787
+ this_2.prisma.$executeRawUnsafe("GRANT ALL ON ALL TABLES IN SCHEMA public TO ".concat(role, ";")),
788
+ this_2.prisma.$executeRawUnsafe("\n\t\t\t\t\tGRANT ALL ON ALL SEQUENCES IN SCHEMA public TO ".concat(role, ";\n\t\t\t\t")),
789
+ this_2.prisma.$executeRawUnsafe("\n\t\t\t\t\tGRANT ALL ON SCHEMA public TO ".concat(role, ";\n\t\t\t\t")),
790
+ this_2.prisma.$queryRawUnsafe("GRANT ".concat(rlsRoles.join(", "), " TO ").concat(role)),
791
+ ])];
792
+ case 2:
793
+ // Note: We need to GRANT all on schema public so that we can resolve relation queries with prisma, as they will sometimes use a join table.
794
+ // This is not ideal, but because we are using RLS, it's not a security risk. Any table with RLS also needs a corresponding policy for the role to have access.
795
+ _t.sent();
796
+ return [4 /*yield*/, this_2.prisma.$queryRawUnsafe("\n\t\t\t\tWITH RECURSIVE cte AS (\n\t\t\t\t\tSELECT oid FROM pg_roles where rolname = '".concat(role, "'\n\t\t\t\t\tUNION ALL\n\t\t\t\t\tSELECT m.roleid\n\t\t\t\t\tFROM cte\n\t\t\t\t\tJOIN pg_auth_members m ON m.member = cte.oid\n\t\t\t\t\t)\n\t\t\t\tSELECT oid, oid::regrole::text AS rolename FROM cte where oid::regrole::text != '").concat(role, "'; \n\t\t"))];
797
+ case 3:
798
+ userRoles = _t.sent();
799
+ oldRoles = userRoles
800
+ .filter(function (_a) {
801
+ var rolename = _a.rolename;
802
+ return !rlsRoles.includes(rolename);
803
+ })
804
+ .map(function (_a) {
805
+ var rolename = _a.rolename;
806
+ return rolename;
807
+ });
808
+ if (!oldRoles.length) return [3 /*break*/, 8];
809
+ // Now revoke old roles from the user role
810
+ debug("Revoking old roles", key, role, oldRoles.join(", "));
811
+ return [4 /*yield*/, this_2.prisma.$executeRawUnsafe("REVOKE ".concat(oldRoles.join(", "), " FROM ").concat(role))];
812
+ case 4:
813
+ _t.sent();
814
+ return [4 /*yield*/, this_2.prisma.$queryRawUnsafe("SELECT * FROM pg_catalog.pg_policies WHERE policyname IN (".concat(oldRoles
815
+ .map(function (or) { return "'".concat(or, "'"); })
816
+ .join(", "), ")"))];
817
+ case 5:
818
+ policies = _t.sent();
819
+ return [4 /*yield*/, this_2.prisma.$transaction(__spreadArray([
820
+ takeLock(this_2.prisma)
821
+ ], __read(policies.map(function (oldPolicy) {
822
+ return _this.prisma.$executeRawUnsafe("DROP POLICY ".concat(oldPolicy.policyname, " ON \"").concat(oldPolicy.tablename, "\""));
823
+ })), false))];
824
+ case 6:
825
+ _t.sent();
826
+ debug("Revoked old rows from ability table", oldRoles.join(", "));
827
+ return [4 /*yield*/, this_2.prisma.$executeRawUnsafe("DELETE FROM _yates._yates_abilities WHERE ability_policy_name IN (".concat(oldRoles
828
+ .map(function (or) { return "'".concat(or, "'"); })
829
+ .join(", "), ")"))];
830
+ case 7:
831
+ _t.sent();
832
+ _t.label = 8;
833
+ case 8: return [2 /*return*/];
834
+ }
835
+ });
836
+ };
837
+ this_2 = this;
838
+ _k = roles;
839
+ _l = [];
840
+ for (_m in _k)
841
+ _l.push(_m);
842
+ _o = 0;
843
+ _r.label = 14;
844
+ case 14:
845
+ if (!(_o < _l.length)) return [3 /*break*/, 17];
846
+ _m = _l[_o];
847
+ if (!(_m in _k)) return [3 /*break*/, 16];
848
+ key = _m;
849
+ return [5 /*yield**/, _loop_3(key)];
850
+ case 15:
851
+ _r.sent();
852
+ _r.label = 16;
853
+ case 16:
854
+ _o++;
855
+ return [3 /*break*/, 14];
856
+ case 17: return [2 /*return*/];
857
+ }
858
+ });
859
+ }); };
860
+ this.inspectDBRoles = function (role) { return __awaiter(_this, void 0, void 0, function () {
861
+ var hashedRoleName, roles;
862
+ return __generator(this, function (_a) {
863
+ switch (_a.label) {
864
+ case 0: return [4 /*yield*/, this.ensureDatabaseScope()];
865
+ case 1:
866
+ _a.sent();
867
+ hashedRoleName = this.createRoleName(role);
868
+ return [4 /*yield*/, this.prisma.$queryRawUnsafe("\n WITH RECURSIVE role_tree AS(\n --Start from your role\n SELECT \n r.oid,\n r.rolname\n FROM pg_roles r\n WHERE r.rolname = '".concat(hashedRoleName, "'\n\n UNION\n\n --Walk \"upwards\": all parent roles granted to it\n SELECT \n parent.oid,\n parent.rolname\n FROM pg_auth_members m\n JOIN role_tree rt\n ON m.member = rt.oid\n JOIN pg_roles parent\n ON parent.oid = m.roleid\n )\n SELECT\n p.tablename,\n p.policyname,\n p.cmd,\n p.roles:: text[] AS policy_roles,\n rt.rolname AS matched_role\n FROM pg_policies p\n JOIN role_tree rt\n ON(\n p.roles IS NULL\n OR array_length(p.roles, 1) = 0\n OR rt.rolname = ANY(p.roles:: text[])\n )\n WHERE p.schemaname = 'public'\n ORDER BY p.policyname, matched_role;\n "))];
869
+ case 2:
870
+ roles = _a.sent();
871
+ return [2 /*return*/, roles];
872
+ }
873
+ });
874
+ }); };
875
+ this.inspectRunTimeDataModel = function () {
876
+ // See https://github.com/prisma/prisma/discussions/14777
877
+ // We are reaching into the prisma internals to get the data model.
878
+ // This is a bit sketchy, but we can get the internal type definition from the runtime library
879
+ // and there is even a test case in prisma that checks that this value is exported
880
+ // See https://github.com/prisma/prisma/blob/5.1.0/packages/client/tests/functional/extensions/pdp.ts#L51
881
+ // This is a private API, so not much we can do about the cast
882
+ var runtimeDataModel = _this.prisma
883
+ ._runtimeDataModel;
884
+ return runtimeDataModel;
885
+ };
886
+ }
887
+ // @ts-ignore
888
+ Yates.prototype.getBatchId = function (query) {
889
+ if (query.action !== "findUnique" && query.action !== "findUniqueOrThrow") {
890
+ return undefined;
808
891
  }
809
- });
810
- }); };
811
- exports.createRoles = createRoles;
892
+ var parts = [];
893
+ if (query.modelName) {
894
+ parts.push(query.modelName);
895
+ }
896
+ if (query.query.arguments) {
897
+ parts.push(this.buildKeysString(query.query.arguments));
898
+ }
899
+ parts.push(this.buildKeysString(query.query.selection));
900
+ return parts.join("");
901
+ };
902
+ Yates.prototype.buildKeysString = function (obj) {
903
+ var _this = this;
904
+ var keysArray = Object.keys(obj)
905
+ .sort()
906
+ .map(function (key) {
907
+ // @ts-ignore
908
+ var value = obj[key];
909
+ if (typeof value === "object" && value !== null) {
910
+ return "(".concat(key, " ").concat(_this.buildKeysString(value), ")");
911
+ }
912
+ return key;
913
+ });
914
+ return "(".concat(keysArray.join(" "), ")");
915
+ };
916
+ return Yates;
917
+ }());
918
+ exports.Yates = Yates;
812
919
  /**
813
920
  * Creates an extended client that sets contextual parameters and user role on every query
814
921
  **/
815
922
  var setup = function (params) { return __awaiter(void 0, void 0, void 0, function () {
816
- var start, prisma, customAbilities, getRoles, getContext, client;
923
+ var start, prisma, customAbilities, getRoles, getContext, yates, client;
817
924
  return __generator(this, function (_a) {
818
925
  switch (_a.label) {
819
926
  case 0:
820
927
  start = performance.now();
821
928
  prisma = params.prisma, customAbilities = params.customAbilities, getRoles = params.getRoles, getContext = params.getContext;
822
- return [4 /*yield*/, (0, exports.createRoles)({
823
- prisma: prisma,
929
+ yates = new Yates(prisma);
930
+ return [4 /*yield*/, yates.init()];
931
+ case 1:
932
+ _a.sent();
933
+ return [4 /*yield*/, yates.createRoles({
824
934
  customAbilities: customAbilities,
825
935
  getRoles: getRoles,
826
936
  })];
827
- case 1:
937
+ case 2:
828
938
  _a.sent();
829
- client = (0, exports.createClient)(prisma, getContext, params.options);
939
+ client = yates.createClient(getContext, params.options);
830
940
  debug("Setup completed in", performance.now() - start, "ms");
831
941
  return [2 /*return*/, client];
832
942
  }