@restura/core 0.2.1 → 1.0.0-beta.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/dist/index.mjs CHANGED
@@ -1,35 +1,5 @@
1
1
  var __defProp = Object.defineProperty;
2
- var __defProps = Object.defineProperties;
3
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
5
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
- var __spreadValues = (a, b) => {
10
- for (var prop in b || (b = {}))
11
- if (__hasOwnProp.call(b, prop))
12
- __defNormalProp(a, prop, b[prop]);
13
- if (__getOwnPropSymbols)
14
- for (var prop of __getOwnPropSymbols(b)) {
15
- if (__propIsEnum.call(b, prop))
16
- __defNormalProp(a, prop, b[prop]);
17
- }
18
- return a;
19
- };
20
- var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
- var __objRest = (source, exclude) => {
22
- var target = {};
23
- for (var prop in source)
24
- if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
25
- target[prop] = source[prop];
26
- if (source != null && __getOwnPropSymbols)
27
- for (var prop of __getOwnPropSymbols(source)) {
28
- if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
29
- target[prop] = source[prop];
30
- }
31
- return target;
32
- };
33
3
  var __decorateClass = (decorators, target, key, kind) => {
34
4
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
35
5
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
@@ -45,13 +15,12 @@ import winston from "winston";
45
15
  import { format } from "logform";
46
16
 
47
17
  // src/logger/loggerConfigSchema.ts
48
- import { z } from "zod";
18
+ import { z } from "zod/v4";
49
19
  var loggerConfigSchema = z.object({
50
20
  level: z.enum(["info", "warn", "error", "debug", "silly"]).default("info")
51
21
  });
52
22
 
53
23
  // src/logger/logger.ts
54
- var loggerConfig = config.validate("logger", loggerConfigSchema);
55
24
  var consoleFormat = format.combine(
56
25
  format.timestamp({
57
26
  format: "YYYY-MM-DD HH:mm:ss.sss"
@@ -64,7 +33,7 @@ var consoleFormat = format.combine(
64
33
  })
65
34
  );
66
35
  var logger = winston.createLogger({
67
- level: loggerConfig.level,
36
+ level: "info",
68
37
  format: format.combine(
69
38
  format.timestamp({
70
39
  format: "YYYY-MM-DD HH:mm:ss.sss"
@@ -72,29 +41,24 @@ var logger = winston.createLogger({
72
41
  format.errors({ stack: true }),
73
42
  format.json()
74
43
  ),
75
- //defaultMeta: { service: 'user-service' },
76
- transports: [
77
- //
78
- // - Write to all logs with level `info` and below to `combined.log`
79
- // - Write all logs error (and below) to `error.log`.
80
- // - Write all logs to standard out.
81
- //
82
- // new winston.transports.File({ filename: 'error.log', level: 'error' }),
83
- // new winston.transports.File({ filename: 'combined.log' }),
84
- new winston.transports.Console({ format: consoleFormat })
85
- ]
44
+ transports: [new winston.transports.Console({ format: consoleFormat })]
45
+ });
46
+ config.validate("logger", loggerConfigSchema).then((loggerConfig) => {
47
+ if (loggerConfig.level) {
48
+ logger.level = loggerConfig.level;
49
+ }
50
+ }).catch((error) => {
51
+ logger.error("Failed to load logger config:", error);
86
52
  });
87
53
 
88
54
  // src/restura/eventManager.ts
89
55
  import Bluebird from "bluebird";
90
56
  var EventManager = class {
91
- constructor() {
92
- this.actionHandlers = {
93
- DATABASE_ROW_DELETE: [],
94
- DATABASE_ROW_INSERT: [],
95
- DATABASE_COLUMN_UPDATE: []
96
- };
97
- }
57
+ actionHandlers = {
58
+ DATABASE_ROW_DELETE: [],
59
+ DATABASE_ROW_INSERT: [],
60
+ DATABASE_COLUMN_UPDATE: []
61
+ };
98
62
  addRowInsertHandler(onInsert, filter) {
99
63
  this.actionHandlers.DATABASE_ROW_INSERT.push({
100
64
  callback: onInsert,
@@ -291,6 +255,10 @@ var HtmlStatusCodes = /* @__PURE__ */ ((HtmlStatusCodes2) => {
291
255
  return HtmlStatusCodes2;
292
256
  })(HtmlStatusCodes || {});
293
257
  var RsError = class _RsError {
258
+ err;
259
+ msg;
260
+ status;
261
+ stack;
294
262
  constructor(errCode, message) {
295
263
  this.err = errCode;
296
264
  this.msg = message || "";
@@ -443,9 +411,7 @@ import fs from "fs";
443
411
  import path from "path";
444
412
  import { FileUtils } from "@restura/internal";
445
413
  var CustomApiFactory = class {
446
- constructor() {
447
- this.customApis = {};
448
- }
414
+ customApis = {};
449
415
  async loadApiFiles(baseFolderPath) {
450
416
  const apiVersions = ["v1"];
451
417
  for (const apiVersion of apiVersions) {
@@ -459,11 +425,10 @@ var CustomApiFactory = class {
459
425
  return this.customApis[customApiName];
460
426
  }
461
427
  async addDirectory(directoryPath, apiVersion) {
462
- var _a2;
463
428
  const entries = await fs.promises.readdir(directoryPath, {
464
429
  withFileTypes: true
465
430
  });
466
- const isTsx2 = (_a2 = process.argv[1]) == null ? void 0 : _a2.endsWith(".ts");
431
+ const isTsx2 = process.argv[1]?.endsWith(".ts");
467
432
  const isTsNode2 = process.env.TS_NODE_DEV || process.env.TS_NODE_PROJECT;
468
433
  const extension = isTsx2 || isTsNode2 ? "ts" : "js";
469
434
  const shouldEndWith = `.api.${apiVersion}.${extension}`;
@@ -528,6 +493,8 @@ var SqlUtils = class _SqlUtils {
528
493
 
529
494
  // src/restura/validators/ResponseValidator.ts
530
495
  var ResponseValidator = class _ResponseValidator {
496
+ rootMap;
497
+ database;
531
498
  constructor(schema) {
532
499
  this.database = schema.database;
533
500
  this.rootMap = {};
@@ -603,7 +570,7 @@ var ResponseValidator = class _ResponseValidator {
603
570
  if (path5.length === 0 || path5.length > 2 || path5[0] === "") return { validator: "any", isOptional: false };
604
571
  const tableName = path5.length == 2 ? path5[0] : name, columnName = path5.length == 2 ? path5[1] : path5[0];
605
572
  const table = this.database.find((t) => t.name == tableName);
606
- const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
573
+ const column = table?.columns.find((c) => c.name == columnName);
607
574
  if (!table || !column) return { validator: "any", isOptional: false };
608
575
  let validator = SqlUtils.convertDatabaseTypeToTypescript(
609
576
  column.type,
@@ -687,10 +654,12 @@ var ResponseValidator = class _ResponseValidator {
687
654
  var ApiTree = class _ApiTree {
688
655
  constructor(namespace, database) {
689
656
  this.database = database;
690
- this.data = [];
691
657
  this.namespace = namespace;
692
658
  this.children = /* @__PURE__ */ new Map();
693
659
  }
660
+ namespace;
661
+ data = [];
662
+ children;
694
663
  static createRootNode(database) {
695
664
  return new _ApiTree(null, database);
696
665
  }
@@ -834,7 +803,7 @@ var ApiTree = class _ApiTree {
834
803
  tableName = tableAliasSplit[1];
835
804
  table = this.database.find((t) => t.name == tableName);
836
805
  }
837
- const column = table == null ? void 0 : table.columns.find((c) => c.name == columnName);
806
+ const column = table?.columns.find((c) => c.name == columnName);
838
807
  if (!table || !column) return { responseType: "any", isNullable: false };
839
808
  return {
840
809
  responseType: SqlUtils.convertDatabaseTypeToTypescript(column.type, column.value),
@@ -865,16 +834,17 @@ function apiGenerator(schema) {
865
834
  ${schema.customTypes.join("\n")}
866
835
  }`;
867
836
  }
868
- return prettier.format(apiString, __spreadValues({
869
- parser: "typescript"
870
- }, {
871
- trailingComma: "none",
872
- tabWidth: 4,
873
- useTabs: true,
874
- endOfLine: "lf",
875
- printWidth: 120,
876
- singleQuote: true
877
- }));
837
+ return prettier.format(apiString, {
838
+ parser: "typescript",
839
+ ...{
840
+ trailingComma: "none",
841
+ tabWidth: 4,
842
+ useTabs: true,
843
+ endOfLine: "lf",
844
+ printWidth: 120,
845
+ singleQuote: true
846
+ }
847
+ });
878
848
  }
879
849
 
880
850
  // src/restura/generators/customTypeValidationGenerator.ts
@@ -885,7 +855,7 @@ import * as TJS from "typescript-json-schema";
885
855
  function customTypeValidationGenerator(currentSchema) {
886
856
  const schemaObject = {};
887
857
  const customInterfaceNames = currentSchema.customTypes.map((customType) => {
888
- const matches = customType.match(new RegExp("(?<=interface\\s)(\\w+)|(?<=type\\s)(\\w+)", "g"));
858
+ const matches = customType.match(/(?<=interface\s)(\w+)|(?<=type\s)(\w+)/g);
889
859
  if (matches && matches.length > 0) return matches[0];
890
860
  return "";
891
861
  }).filter(Boolean);
@@ -929,16 +899,17 @@ function modelGenerator(schema) {
929
899
  modelString += convertTable(table);
930
900
  }
931
901
  modelString += `}`;
932
- return prettier2.format(modelString, __spreadValues({
933
- parser: "typescript"
934
- }, {
935
- trailingComma: "none",
936
- tabWidth: 4,
937
- useTabs: true,
938
- endOfLine: "lf",
939
- printWidth: 120,
940
- singleQuote: true
941
- }));
902
+ return prettier2.format(modelString, {
903
+ parser: "typescript",
904
+ ...{
905
+ trailingComma: "none",
906
+ tabWidth: 4,
907
+ useTabs: true,
908
+ endOfLine: "lf",
909
+ printWidth: 120,
910
+ singleQuote: true
911
+ }
912
+ });
942
913
  }
943
914
  function convertTable(table) {
944
915
  let modelString = ` export interface ${StringUtils2.capitalizeFirst(table.name)} {
@@ -990,20 +961,21 @@ function addApiResponseFunctions(req, res, next) {
990
961
  htmlStatusCode = 500;
991
962
  }
992
963
  }
993
- const errorData = __spreadValues({
964
+ const errorData = {
994
965
  err: shortError,
995
- msg
996
- }, restura.resturaConfig.sendErrorStackTrace && stack ? { stack } : {});
966
+ msg,
967
+ ...restura.resturaConfig.sendErrorStackTrace && stack ? { stack } : {}
968
+ };
997
969
  res.status(htmlStatusCode).send(errorData);
998
970
  };
999
971
  next();
1000
972
  }
1001
973
 
1002
- // src/restura/middleware/authenticateUser.ts
1003
- function authenticateUser(applicationAuthenticateHandler) {
974
+ // src/restura/middleware/authenticateRequester.ts
975
+ function authenticateRequester(applicationAuthenticateHandler) {
1004
976
  return (req, res, next) => {
1005
- applicationAuthenticateHandler(req, res, (userDetails) => {
1006
- req.requesterDetails = __spreadValues({ host: req.hostname, ipAddress: req.ip || "" }, userDetails);
977
+ applicationAuthenticateHandler(req, res, (authenticatedRequesterDetails) => {
978
+ req.requesterDetails = { host: req.hostname, ipAddress: req.ip || "", ...authenticatedRequesterDetails };
1007
979
  next();
1008
980
  });
1009
981
  };
@@ -1058,22 +1030,7 @@ var groupBySchema = z3.object({
1058
1030
  var whereDataSchema = z3.object({
1059
1031
  tableName: z3.string().optional(),
1060
1032
  columnName: z3.string().optional(),
1061
- operator: z3.enum([
1062
- "=",
1063
- "<",
1064
- ">",
1065
- "<=",
1066
- ">=",
1067
- "!=",
1068
- "LIKE",
1069
- "NOT LIKE",
1070
- "IN",
1071
- "NOT IN",
1072
- "STARTS WITH",
1073
- "ENDS WITH",
1074
- "IS",
1075
- "IS NOT"
1076
- ]).optional(),
1033
+ operator: z3.enum(["=", "<", ">", "<=", ">=", "!=", "LIKE", "IN", "NOT IN", "STARTS WITH", "ENDS WITH", "IS", "IS NOT"]).optional(),
1077
1034
  value: z3.string().or(z3.number()).optional(),
1078
1035
  custom: z3.string().optional(),
1079
1036
  conjunction: z3.enum(["AND", "OR"]).optional()
@@ -1109,13 +1066,15 @@ var responseDataSchema = z3.object({
1109
1066
  orderBy: orderBySchema.optional()
1110
1067
  }).optional(),
1111
1068
  type: z3.string().optional()
1069
+ // Type allows you to override the type of the response, used in custom selectors
1112
1070
  }).strict();
1113
1071
  var routeDataBaseSchema = z3.object({
1114
1072
  method: z3.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
1115
1073
  name: z3.string(),
1116
1074
  description: z3.string(),
1117
1075
  path: z3.string(),
1118
- roles: z3.array(z3.string())
1076
+ roles: z3.array(z3.string()),
1077
+ scopes: z3.array(z3.string())
1119
1078
  }).strict();
1120
1079
  var standardRouteSchema = routeDataBaseSchema.extend({
1121
1080
  type: z3.enum(["ONE", "ARRAY", "PAGED"]),
@@ -1255,6 +1214,7 @@ var columnDataSchema = z3.object({
1255
1214
  ]),
1256
1215
  isNullable: z3.boolean(),
1257
1216
  roles: z3.array(z3.string()),
1217
+ scopes: z3.array(z3.string()),
1258
1218
  comment: z3.string().optional(),
1259
1219
  default: z3.string().optional(),
1260
1220
  value: z3.string().optional(),
@@ -1301,6 +1261,7 @@ var tableDataSchema = z3.object({
1301
1261
  foreignKeys: z3.array(foreignKeyDataSchema),
1302
1262
  checkConstraints: z3.array(checkConstraintDataSchema),
1303
1263
  roles: z3.array(z3.string()),
1264
+ scopes: z3.array(z3.string()),
1304
1265
  notify: z3.union([z3.literal("ALL"), z3.array(z3.string())]).optional()
1305
1266
  }).strict();
1306
1267
  var endpointDataSchema = z3.object({
@@ -1314,6 +1275,7 @@ var resturaSchema = z3.object({
1314
1275
  endpoints: z3.array(endpointDataSchema),
1315
1276
  globalParams: z3.array(z3.string()),
1316
1277
  roles: z3.array(z3.string()),
1278
+ scopes: z3.array(z3.string()),
1317
1279
  customTypes: z3.array(z3.string())
1318
1280
  }).strict();
1319
1281
  async function isSchemaValid(schemaToCheck) {
@@ -1411,7 +1373,7 @@ function isValidType(type, requestValue) {
1411
1373
  try {
1412
1374
  expectValidType(type, requestValue);
1413
1375
  return true;
1414
- } catch (e) {
1376
+ } catch {
1415
1377
  return false;
1416
1378
  }
1417
1379
  }
@@ -1447,7 +1409,7 @@ function performTypeCheck(requestValue, validator, requestParamName) {
1447
1409
  }
1448
1410
  try {
1449
1411
  validatorDataSchemeValue.parse(validator.value);
1450
- } catch (e) {
1412
+ } catch {
1451
1413
  throw new RsError("SCHEMA_ERROR", `Schema validator value (${validator.value}) is not a valid type`);
1452
1414
  }
1453
1415
  }
@@ -1538,9 +1500,8 @@ async function schemaValidation(req, res, next) {
1538
1500
  }
1539
1501
 
1540
1502
  // src/restura/schemas/resturaConfigSchema.ts
1541
- import { z as z5 } from "zod";
1542
- var _a;
1543
- var isTsx = (_a = process.argv[1]) == null ? void 0 : _a.endsWith(".ts");
1503
+ import { z as z5 } from "zod/v4";
1504
+ var isTsx = process.argv[1]?.endsWith(".ts");
1544
1505
  var isTsNode = process.env.TS_NODE_DEV || process.env.TS_NODE_PROJECT;
1545
1506
  var customApiFolderPath = isTsx || isTsNode ? "/src/api" : "/dist/api";
1546
1507
  var resturaConfigSchema = z5.object({
@@ -1549,7 +1510,8 @@ var resturaConfigSchema = z5.object({
1549
1510
  schemaFilePath: z5.string().default(process.cwd() + "/restura.schema.json"),
1550
1511
  customApiFolderPath: z5.string().default(process.cwd() + customApiFolderPath),
1551
1512
  generatedTypesPath: z5.string().default(process.cwd() + "/src/@types"),
1552
- fileTempCachePath: z5.string().optional()
1513
+ fileTempCachePath: z5.string().optional(),
1514
+ scratchDatabaseSuffix: z5.string().optional()
1553
1515
  });
1554
1516
 
1555
1517
  // src/restura/sql/PsqlEngine.ts
@@ -1635,13 +1597,14 @@ function SQL(strings, ...values) {
1635
1597
 
1636
1598
  // src/restura/sql/PsqlConnection.ts
1637
1599
  var PsqlConnection = class {
1600
+ instanceId;
1638
1601
  constructor(instanceId) {
1639
1602
  this.instanceId = instanceId || crypto.randomUUID();
1640
1603
  }
1641
1604
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1642
1605
  async queryOne(query, options, requesterDetails) {
1643
1606
  const formattedQuery = questionMarksToOrderedParams(query);
1644
- const meta = __spreadValues({ connectionInstanceId: this.instanceId }, requesterDetails);
1607
+ const meta = { connectionInstanceId: this.instanceId, ...requesterDetails };
1645
1608
  this.logSqlStatement(formattedQuery, options, meta);
1646
1609
  const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
1647
1610
  `;
@@ -1654,7 +1617,7 @@ var PsqlConnection = class {
1654
1617
  return response.rows[0];
1655
1618
  } catch (error) {
1656
1619
  if (RsError.isRsError(error)) throw error;
1657
- if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
1620
+ if (error?.routine === "_bt_check_unique") {
1658
1621
  throw new RsError("DUPLICATE", error.message);
1659
1622
  }
1660
1623
  throw new RsError("DATABASE_ERROR", `${error.message}`);
@@ -1663,7 +1626,7 @@ var PsqlConnection = class {
1663
1626
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1664
1627
  async runQuery(query, options, requesterDetails) {
1665
1628
  const formattedQuery = questionMarksToOrderedParams(query);
1666
- const meta = __spreadValues({ connectionInstanceId: this.instanceId }, requesterDetails);
1629
+ const meta = { connectionInstanceId: this.instanceId, ...requesterDetails };
1667
1630
  this.logSqlStatement(formattedQuery, options, meta);
1668
1631
  const queryMetadata = `--QUERY_METADATA(${JSON.stringify(meta)})
1669
1632
  `;
@@ -1673,7 +1636,7 @@ var PsqlConnection = class {
1673
1636
  this.logQueryDuration(startTime);
1674
1637
  return response.rows;
1675
1638
  } catch (error) {
1676
- if ((error == null ? void 0 : error.routine) === "_bt_check_unique") {
1639
+ if (error?.routine === "_bt_check_unique") {
1677
1640
  throw new RsError("DUPLICATE", error.message);
1678
1641
  }
1679
1642
  throw new RsError("DATABASE_ERROR", `${error.message}`);
@@ -1723,13 +1686,20 @@ var PsqlPool = class extends PsqlConnection {
1723
1686
  super();
1724
1687
  this.poolConfig = poolConfig;
1725
1688
  this.pool = new Pool(poolConfig);
1726
- this.queryOne("SELECT NOW();", [], { isSystemUser: true, role: "", host: "localhost", ipAddress: "" }).then(() => {
1689
+ this.queryOne("SELECT NOW();", [], {
1690
+ isSystemUser: true,
1691
+ role: "",
1692
+ host: "localhost",
1693
+ ipAddress: "",
1694
+ scopes: []
1695
+ }).then(() => {
1727
1696
  logger.info("Connected to PostgreSQL database");
1728
1697
  }).catch((error) => {
1729
1698
  logger.error("Error connecting to database", error);
1730
1699
  process.exit(1);
1731
1700
  });
1732
1701
  }
1702
+ pool;
1733
1703
  async query(query, values) {
1734
1704
  return this.pool.query(query, values);
1735
1705
  }
@@ -1739,7 +1709,12 @@ var PsqlPool = class extends PsqlConnection {
1739
1709
  import { ObjectUtils as ObjectUtils3 } from "@redskytech/core-utils";
1740
1710
  var SqlEngine = class {
1741
1711
  async runQueryForRoute(req, routeData, schema) {
1742
- if (!this.doesRoleHavePermissionToTable(req.requesterDetails.role, schema, routeData.table))
1712
+ if (!this.canRequesterAccessTable(
1713
+ req.requesterDetails.role,
1714
+ req.requesterDetails.scopes,
1715
+ schema,
1716
+ routeData.table
1717
+ ))
1743
1718
  throw new RsError("FORBIDDEN", "You do not have permission to access this table");
1744
1719
  switch (routeData.method) {
1745
1720
  case "POST":
@@ -1758,7 +1733,7 @@ var SqlEngine = class {
1758
1733
  if (!tableSchema) throw new RsError("SCHEMA_ERROR", `Table ${tableName} not found in schema`);
1759
1734
  return tableSchema;
1760
1735
  }
1761
- doesRoleHavePermissionToColumn(role, schema, item, joins) {
1736
+ canRequesterAccessColumn(requesterRole, requesterScopes, schema, item, joins) {
1762
1737
  if (item.type) return true;
1763
1738
  if (item.selector) {
1764
1739
  let tableName = item.selector.split(".")[0];
@@ -1774,26 +1749,36 @@ var SqlEngine = class {
1774
1749
  const columnSchema = tableSchema.columns.find((item2) => item2.name === columnName);
1775
1750
  if (!columnSchema)
1776
1751
  throw new RsError("SCHEMA_ERROR", `Column ${columnName} not found in table ${tableName}`);
1777
- const doesColumnHaveRoles = ObjectUtils3.isArrayWithData(columnSchema.roles);
1778
- if (!doesColumnHaveRoles) return true;
1779
- if (!role) return false;
1780
- return columnSchema.roles.includes(role);
1752
+ if (ObjectUtils3.isArrayWithData(columnSchema.roles)) {
1753
+ if (!requesterRole) return false;
1754
+ return columnSchema.roles.includes(requesterRole);
1755
+ }
1756
+ if (ObjectUtils3.isArrayWithData(columnSchema.scopes)) {
1757
+ if (!requesterScopes) return false;
1758
+ return columnSchema.scopes.some((scope) => requesterScopes.includes(scope));
1759
+ }
1760
+ return true;
1781
1761
  }
1782
1762
  if (item.subquery) {
1783
1763
  return ObjectUtils3.isArrayWithData(
1784
1764
  item.subquery.properties.filter((nestedItem) => {
1785
- return this.doesRoleHavePermissionToColumn(role, schema, nestedItem, joins);
1765
+ return this.canRequesterAccessColumn(requesterRole, requesterScopes, schema, nestedItem, joins);
1786
1766
  })
1787
1767
  );
1788
1768
  }
1789
1769
  return false;
1790
1770
  }
1791
- doesRoleHavePermissionToTable(userRole, schema, tableName) {
1771
+ canRequesterAccessTable(requesterRole, requesterScopes, schema, tableName) {
1792
1772
  const tableSchema = this.getTableSchema(schema, tableName);
1793
- const doesTableHaveRoles = ObjectUtils3.isArrayWithData(tableSchema.roles);
1794
- if (!doesTableHaveRoles) return true;
1795
- if (!userRole) return false;
1796
- return tableSchema.roles.includes(userRole);
1773
+ if (ObjectUtils3.isArrayWithData(tableSchema.roles)) {
1774
+ if (!requesterRole) return false;
1775
+ return tableSchema.roles.includes(requesterRole);
1776
+ }
1777
+ if (ObjectUtils3.isArrayWithData(tableSchema.scopes)) {
1778
+ if (!requesterScopes) return false;
1779
+ return tableSchema.scopes.some((scope) => requesterScopes.includes(scope));
1780
+ }
1781
+ return true;
1797
1782
  }
1798
1783
  replaceParamKeywords(value, routeData, req, sqlParams) {
1799
1784
  let returnValue = value;
@@ -1802,11 +1787,10 @@ var SqlEngine = class {
1802
1787
  return returnValue;
1803
1788
  }
1804
1789
  replaceLocalParamKeywords(value, routeData, req, sqlParams) {
1805
- var _a2;
1806
1790
  if (!routeData.request) return value;
1807
1791
  const data = req.data;
1808
1792
  if (typeof value === "string") {
1809
- (_a2 = value.match(/\$[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a2.forEach((param) => {
1793
+ value.match(/\$[a-zA-Z][a-zA-Z0-9_]+/g)?.forEach((param) => {
1810
1794
  const requestParam = routeData.request.find((item) => {
1811
1795
  return item.name === param.replace("$", "");
1812
1796
  });
@@ -1819,9 +1803,8 @@ var SqlEngine = class {
1819
1803
  return value;
1820
1804
  }
1821
1805
  replaceGlobalParamKeywords(value, routeData, req, sqlParams) {
1822
- var _a2;
1823
1806
  if (typeof value === "string") {
1824
- (_a2 = value.match(/#[a-zA-Z][a-zA-Z0-9_]+/g)) == null ? void 0 : _a2.forEach((param) => {
1807
+ value.match(/#[a-zA-Z][a-zA-Z0-9_]+/g)?.forEach((param) => {
1825
1808
  param = param.replace("#", "");
1826
1809
  const globalParamValue = req.requesterDetails[param];
1827
1810
  if (!globalParamValue)
@@ -1911,10 +1894,32 @@ negate = "!"
1911
1894
  operator = "and"i / "or"i
1912
1895
 
1913
1896
 
1914
- column = left:text "." right:text { return \`\${quoteSqlIdentity(left)}.\${quoteSqlIdentity(right)}\`; }
1915
- /
1916
- text:text { return quoteSqlIdentity(text); }
1897
+ column = first:text rest:("." text)* {
1898
+ const partsArray = [first];
1899
+ if (rest && rest.length > 0) {
1900
+ partsArray.push(...rest.map(item => item[1]));
1901
+ }
1902
+
1903
+ if (partsArray.length > 3) {
1904
+ throw new SyntaxError('Column path cannot have more than 3 parts (table.column.jsonField)');
1905
+ }
1906
+
1907
+ if (partsArray.length === 1) {
1908
+ return quoteSqlIdentity(partsArray[0]);
1909
+ }
1910
+ const tableName = quoteSqlIdentity(partsArray[0]);
1917
1911
 
1912
+ // If we only have two parts (table.column), use regular dot notation
1913
+ if (partsArray.length === 2) {
1914
+ return tableName + "." + quoteSqlIdentity(partsArray[1]);
1915
+ }
1916
+
1917
+ // For JSON paths (more than 2 parts), first part is a column, last part uses ->>
1918
+ const jsonColumn = quoteSqlIdentity(partsArray[1]);
1919
+ const lastPart = partsArray[partsArray.length - 1];
1920
+ const result = tableName + "." + jsonColumn + "->>'" + lastPart + "'";
1921
+ return result;
1922
+ }
1918
1923
 
1919
1924
  text = text:[a-z0-9 \\t\\r\\n\\-_:@']i+ { return text.join(""); }
1920
1925
 
@@ -1944,19 +1949,24 @@ var filterPsqlParser_default = filterPsqlParser;
1944
1949
  var { Client, types } = pg2;
1945
1950
  var systemUser = {
1946
1951
  role: "",
1952
+ scopes: [],
1947
1953
  host: "",
1948
1954
  ipAddress: "",
1949
1955
  isSystemUser: true
1950
1956
  };
1951
1957
  var PsqlEngine = class extends SqlEngine {
1952
- constructor(psqlConnectionPool, shouldListenForDbTriggers = false) {
1958
+ constructor(psqlConnectionPool, shouldListenForDbTriggers = false, scratchDatabaseSuffix = "") {
1953
1959
  super();
1954
1960
  this.psqlConnectionPool = psqlConnectionPool;
1955
1961
  this.setupPgReturnTypes();
1956
1962
  if (shouldListenForDbTriggers) {
1957
1963
  this.setupTriggerListeners = this.listenForDbTriggers();
1958
1964
  }
1965
+ this.scratchDbName = `${psqlConnectionPool.poolConfig.database}_scratch${scratchDatabaseSuffix ? `_${scratchDatabaseSuffix}` : ""}`;
1959
1966
  }
1967
+ setupTriggerListeners;
1968
+ triggerClient;
1969
+ scratchDbName = "";
1960
1970
  async close() {
1961
1971
  if (this.triggerClient) {
1962
1972
  await this.triggerClient.end();
@@ -2085,27 +2095,22 @@ var PsqlEngine = class extends SqlEngine {
2085
2095
  sqlStatements.push(triggers.join("\n"));
2086
2096
  return sqlStatements.join("\n\n");
2087
2097
  }
2088
- async getScratchPool() {
2089
- var _a2, _b;
2098
+ async getNewPublicSchemaAndScratchPool() {
2090
2099
  const scratchDbExists = await this.psqlConnectionPool.runQuery(
2091
2100
  `SELECT *
2092
2101
  FROM pg_database
2093
- WHERE datname = '${this.psqlConnectionPool.poolConfig.database}_scratch';`,
2102
+ WHERE datname = '${this.scratchDbName}';`,
2094
2103
  [],
2095
2104
  systemUser
2096
2105
  );
2097
2106
  if (scratchDbExists.length === 0) {
2098
- await this.psqlConnectionPool.runQuery(
2099
- `CREATE DATABASE ${this.psqlConnectionPool.poolConfig.database}_scratch;`,
2100
- [],
2101
- systemUser
2102
- );
2107
+ await this.psqlConnectionPool.runQuery(`CREATE DATABASE ${this.scratchDbName};`, [], systemUser);
2103
2108
  }
2104
2109
  const scratchPool = new PsqlPool({
2105
2110
  host: this.psqlConnectionPool.poolConfig.host,
2106
2111
  port: this.psqlConnectionPool.poolConfig.port,
2107
2112
  user: this.psqlConnectionPool.poolConfig.user,
2108
- database: this.psqlConnectionPool.poolConfig.database + "_scratch",
2113
+ database: this.scratchDbName,
2109
2114
  password: this.psqlConnectionPool.poolConfig.password,
2110
2115
  max: this.psqlConnectionPool.poolConfig.max,
2111
2116
  idleTimeoutMillis: this.psqlConnectionPool.poolConfig.idleTimeoutMillis,
@@ -2118,16 +2123,17 @@ var PsqlEngine = class extends SqlEngine {
2118
2123
  systemUser
2119
2124
  );
2120
2125
  const schemaComment = await this.psqlConnectionPool.runQuery(
2121
- `SELECT pg_description.description
2122
- FROM pg_description
2123
- JOIN pg_namespace ON pg_namespace.oid = pg_description.objoid
2124
- WHERE pg_namespace.nspname = 'public';`,
2126
+ `
2127
+ SELECT pg_description.description
2128
+ FROM pg_description
2129
+ JOIN pg_namespace ON pg_namespace.oid = pg_description.objoid
2130
+ WHERE pg_namespace.nspname = 'public';`,
2125
2131
  [],
2126
2132
  systemUser
2127
2133
  );
2128
- if ((_a2 = schemaComment[0]) == null ? void 0 : _a2.description) {
2134
+ if (schemaComment[0]?.description) {
2129
2135
  await scratchPool.runQuery(
2130
- `COMMENT ON SCHEMA public IS '${(_b = schemaComment[0]) == null ? void 0 : _b.description}';`,
2136
+ `COMMENT ON SCHEMA public IS '${schemaComment[0]?.description}';`,
2131
2137
  [],
2132
2138
  systemUser
2133
2139
  );
@@ -2135,7 +2141,7 @@ var PsqlEngine = class extends SqlEngine {
2135
2141
  return scratchPool;
2136
2142
  }
2137
2143
  async diffDatabaseToSchema(schema) {
2138
- const scratchPool = await this.getScratchPool();
2144
+ const scratchPool = await this.getNewPublicSchemaAndScratchPool();
2139
2145
  await this.createDatabaseFromSchema(schema, scratchPool);
2140
2146
  const originalClient = new Client({
2141
2147
  database: this.psqlConnectionPool.poolConfig.database,
@@ -2145,7 +2151,7 @@ var PsqlEngine = class extends SqlEngine {
2145
2151
  port: this.psqlConnectionPool.poolConfig.port
2146
2152
  });
2147
2153
  const scratchClient = new Client({
2148
- database: this.psqlConnectionPool.poolConfig.database + "_scratch",
2154
+ database: this.scratchDbName,
2149
2155
  user: this.psqlConnectionPool.poolConfig.user,
2150
2156
  password: this.psqlConnectionPool.poolConfig.password,
2151
2157
  host: this.psqlConnectionPool.poolConfig.host,
@@ -2160,24 +2166,30 @@ var PsqlEngine = class extends SqlEngine {
2160
2166
  await Promise.all(endPromises);
2161
2167
  return diff.join("\n");
2162
2168
  }
2163
- createNestedSelect(req, schema, item, routeData, userRole, sqlParams) {
2169
+ createNestedSelect(req, schema, item, routeData, sqlParams) {
2164
2170
  if (!item.subquery) return "";
2165
2171
  if (!ObjectUtils4.isArrayWithData(
2166
2172
  item.subquery.properties.filter((nestedItem) => {
2167
- return this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
2168
- ...routeData.joins,
2169
- ...item.subquery.joins
2170
- ]);
2173
+ return this.canRequesterAccessColumn(
2174
+ req.requesterDetails.role,
2175
+ req.requesterDetails.scopes,
2176
+ schema,
2177
+ nestedItem,
2178
+ [...routeData.joins, ...item.subquery.joins]
2179
+ );
2171
2180
  })
2172
2181
  )) {
2173
2182
  return "'[]'";
2174
2183
  }
2175
2184
  return `COALESCE((SELECT JSON_AGG(JSON_BUILD_OBJECT(
2176
2185
  ${item.subquery.properties.map((nestedItem) => {
2177
- if (!this.doesRoleHavePermissionToColumn(req.requesterDetails.role, schema, nestedItem, [
2178
- ...routeData.joins,
2179
- ...item.subquery.joins
2180
- ])) {
2186
+ if (!this.canRequesterAccessColumn(
2187
+ req.requesterDetails.role,
2188
+ req.requesterDetails.scopes,
2189
+ schema,
2190
+ nestedItem,
2191
+ [...routeData.joins, ...item.subquery.joins]
2192
+ )) {
2181
2193
  return;
2182
2194
  }
2183
2195
  if (nestedItem.subquery) {
@@ -2187,7 +2199,6 @@ var PsqlEngine = class extends SqlEngine {
2187
2199
  schema,
2188
2200
  nestedItem,
2189
2201
  routeData,
2190
- userRole,
2191
2202
  sqlParams
2192
2203
  )}`;
2193
2204
  }
@@ -2196,7 +2207,7 @@ var PsqlEngine = class extends SqlEngine {
2196
2207
  ))
2197
2208
  FROM
2198
2209
  "${item.subquery.table}"
2199
- ${this.generateJoinStatements(req, item.subquery.joins, item.subquery.table, routeData, schema, userRole, sqlParams)}
2210
+ ${this.generateJoinStatements(req, item.subquery.joins, item.subquery.table, routeData, schema, sqlParams)}
2200
2211
  ${this.generateWhereClause(req, item.subquery.where, routeData, sqlParams)}
2201
2212
  ), '[]')`;
2202
2213
  }
@@ -2206,7 +2217,7 @@ var PsqlEngine = class extends SqlEngine {
2206
2217
  (routeData.assignments || []).forEach((assignment) => {
2207
2218
  parameterObj[assignment.name] = this.replaceParamKeywords(assignment.value, routeData, req, sqlParams);
2208
2219
  });
2209
- const query = insertObjectQuery(routeData.table, __spreadValues(__spreadValues({}, req.data), parameterObj));
2220
+ const query = insertObjectQuery(routeData.table, { ...req.data, ...parameterObj });
2210
2221
  const createdItem = await this.psqlConnectionPool.queryOne(
2211
2222
  query,
2212
2223
  sqlParams,
@@ -2221,24 +2232,29 @@ var PsqlEngine = class extends SqlEngine {
2221
2232
  };
2222
2233
  const whereData = [whereId];
2223
2234
  req.data = { id: insertId };
2224
- return this.executeGetRequest(req, __spreadProps(__spreadValues({}, routeData), { where: whereData }), schema);
2235
+ return this.executeGetRequest(req, { ...routeData, where: whereData }, schema);
2225
2236
  }
2226
2237
  async executeGetRequest(req, routeData, schema) {
2227
2238
  const DEFAULT_PAGED_PAGE_NUMBER = 0;
2228
2239
  const DEFAULT_PAGED_PER_PAGE_NUMBER = 25;
2229
2240
  const sqlParams = [];
2230
- const userRole = req.requesterDetails.role;
2231
2241
  let sqlStatement = "";
2232
2242
  const selectColumns = [];
2233
2243
  routeData.response.forEach((item) => {
2234
- if (item.subquery || this.doesRoleHavePermissionToColumn(userRole, schema, item, routeData.joins))
2244
+ if (item.subquery || this.canRequesterAccessColumn(
2245
+ req.requesterDetails.role,
2246
+ req.requesterDetails.scopes,
2247
+ schema,
2248
+ item,
2249
+ routeData.joins
2250
+ ))
2235
2251
  selectColumns.push(item);
2236
2252
  });
2237
2253
  if (!selectColumns.length) throw new RsError("FORBIDDEN", `You do not have permission to access this data.`);
2238
2254
  let selectStatement = "SELECT \n";
2239
2255
  selectStatement += ` ${selectColumns.map((item) => {
2240
2256
  if (item.subquery) {
2241
- return `${this.createNestedSelect(req, schema, item, routeData, userRole, sqlParams)} AS ${escapeColumnName(
2257
+ return `${this.createNestedSelect(req, schema, item, routeData, sqlParams)} AS ${escapeColumnName(
2242
2258
  item.name
2243
2259
  )}`;
2244
2260
  }
@@ -2253,7 +2269,6 @@ var PsqlEngine = class extends SqlEngine {
2253
2269
  routeData.table,
2254
2270
  routeData,
2255
2271
  schema,
2256
- userRole,
2257
2272
  sqlParams
2258
2273
  );
2259
2274
  sqlStatement += this.generateWhereClause(req, routeData.where, routeData, sqlParams);
@@ -2297,7 +2312,7 @@ var PsqlEngine = class extends SqlEngine {
2297
2312
  }
2298
2313
  async executeUpdateRequest(req, routeData, schema) {
2299
2314
  const sqlParams = [];
2300
- const _a2 = req.body, { id } = _a2, bodyNoId = __objRest(_a2, ["id"]);
2315
+ const { id, ...bodyNoId } = req.body;
2301
2316
  const table = schema.database.find((item) => {
2302
2317
  return item.name === routeData.table;
2303
2318
  });
@@ -2326,7 +2341,6 @@ var PsqlEngine = class extends SqlEngine {
2326
2341
  routeData.table,
2327
2342
  routeData,
2328
2343
  schema,
2329
- req.requesterDetails.role,
2330
2344
  sqlParams
2331
2345
  );
2332
2346
  const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
@@ -2338,10 +2352,15 @@ DELETE FROM "${routeData.table}" ${joinStatement} ${whereClause}`;
2338
2352
  await this.psqlConnectionPool.runQuery(deleteStatement, sqlParams, req.requesterDetails);
2339
2353
  return true;
2340
2354
  }
2341
- generateJoinStatements(req, joins, baseTable, routeData, schema, userRole, sqlParams) {
2355
+ generateJoinStatements(req, joins, baseTable, routeData, schema, sqlParams) {
2342
2356
  let joinStatements = "";
2343
2357
  joins.forEach((item) => {
2344
- if (!this.doesRoleHavePermissionToTable(userRole, schema, item.table))
2358
+ if (!this.canRequesterAccessTable(
2359
+ req.requesterDetails.role,
2360
+ req.requesterDetails.scopes,
2361
+ schema,
2362
+ item.table
2363
+ ))
2345
2364
  throw new RsError("FORBIDDEN", "You do not have permission to access this table");
2346
2365
  if (item.custom) {
2347
2366
  const customReplaced = this.replaceParamKeywords(item.custom, routeData, req, sqlParams);
@@ -2396,41 +2415,36 @@ DELETE FROM "${routeData.table}" ${joinStatement} ${whereClause}`;
2396
2415
  `Invalid where clause in route ${routeData.name}, missing required fields if not custom`
2397
2416
  );
2398
2417
  let operator = item.operator;
2399
- let value = item.value;
2400
2418
  if (operator === "LIKE") {
2401
- value = `'%${value}%'`;
2402
- } else if (operator === "NOT LIKE") {
2403
- value = `'%${value}%'`;
2419
+ item.value = `'%${item.value}%'`;
2404
2420
  } else if (operator === "STARTS WITH") {
2405
2421
  operator = "LIKE";
2406
- value = `'${value}%'`;
2422
+ item.value = `'${item.value}%'`;
2407
2423
  } else if (operator === "ENDS WITH") {
2408
2424
  operator = "LIKE";
2409
- value = `'%${value}'`;
2425
+ item.value = `'%${item.value}'`;
2410
2426
  }
2411
- const replacedValue = this.replaceParamKeywords(value, routeData, req, sqlParams);
2427
+ const replacedValue = this.replaceParamKeywords(item.value, routeData, req, sqlParams);
2412
2428
  whereClause += ` ${item.conjunction || ""} "${item.tableName}"."${item.columnName}" ${operator.replace("LIKE", "ILIKE")} ${["IN", "NOT IN"].includes(operator) ? `(${replacedValue})` : replacedValue}
2413
2429
  `;
2414
2430
  });
2415
2431
  const data = req.data;
2416
- if (routeData.type === "PAGED" && !!(data == null ? void 0 : data.filter)) {
2432
+ if (routeData.type === "PAGED" && !!data?.filter) {
2417
2433
  let statement = data.filter.replace(/\$[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
2418
- var _a2;
2419
2434
  const requestParam = routeData.request.find((item) => {
2420
2435
  return item.name === value.replace("$", "");
2421
2436
  });
2422
2437
  if (!requestParam)
2423
2438
  throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
2424
- return ((_a2 = data[requestParam.name]) == null ? void 0 : _a2.toString()) || "";
2439
+ return data[requestParam.name]?.toString() || "";
2425
2440
  });
2426
2441
  statement = statement.replace(/#[a-zA-Z][a-zA-Z0-9_]+/g, (value) => {
2427
- var _a2;
2428
2442
  const requestParam = routeData.request.find((item) => {
2429
2443
  return item.name === value.replace("#", "");
2430
2444
  });
2431
2445
  if (!requestParam)
2432
2446
  throw new RsError("SCHEMA_ERROR", `Invalid route keyword in route ${routeData.name}`);
2433
- return ((_a2 = data[requestParam.name]) == null ? void 0 : _a2.toString()) || "";
2447
+ return data[requestParam.name]?.toString() || "";
2434
2448
  });
2435
2449
  statement = filterPsqlParser_default.parse(statement);
2436
2450
  if (whereClause.startsWith("WHERE")) {
@@ -2671,8 +2685,9 @@ import { FileUtils as FileUtils2 } from "@restura/internal";
2671
2685
  import Bluebird3 from "bluebird";
2672
2686
  import * as os2 from "os";
2673
2687
  var TempCache = class {
2688
+ location;
2689
+ maxDurationDays = 7;
2674
2690
  constructor(location) {
2675
- this.maxDurationDays = 7;
2676
2691
  this.location = location || os2.tmpdir();
2677
2692
  FileUtils2.ensureDir(this.location).catch((e) => {
2678
2693
  throw e;
@@ -2697,15 +2712,24 @@ var TempCache = class {
2697
2712
 
2698
2713
  // src/restura/restura.ts
2699
2714
  var ResturaEngine = class {
2700
- constructor() {
2701
- this.publicEndpoints = {
2702
- GET: [],
2703
- POST: [],
2704
- PUT: [],
2705
- PATCH: [],
2706
- DELETE: []
2707
- };
2708
- }
2715
+ // Make public so other modules can access without re-parsing the config
2716
+ resturaConfig;
2717
+ multerCommonUpload;
2718
+ resturaRouter;
2719
+ publicEndpoints = {
2720
+ GET: [],
2721
+ POST: [],
2722
+ PUT: [],
2723
+ PATCH: [],
2724
+ DELETE: []
2725
+ };
2726
+ expressApp;
2727
+ schema;
2728
+ responseValidator;
2729
+ authenticationHandler;
2730
+ customTypeValidation;
2731
+ psqlConnectionPool;
2732
+ psqlEngine;
2709
2733
  /**
2710
2734
  * Initializes the Restura engine with the provided Express application.
2711
2735
  *
@@ -2713,11 +2737,11 @@ var ResturaEngine = class {
2713
2737
  * @returns A promise that resolves when the initialization is complete.
2714
2738
  */
2715
2739
  async init(app, authenticationHandler, psqlConnectionPool) {
2716
- this.resturaConfig = config2.validate("restura", resturaConfigSchema);
2740
+ this.resturaConfig = await config2.validate("restura", resturaConfigSchema);
2717
2741
  this.multerCommonUpload = getMulterUpload(this.resturaConfig.fileTempCachePath);
2718
2742
  new TempCache(this.resturaConfig.fileTempCachePath);
2719
2743
  this.psqlConnectionPool = psqlConnectionPool;
2720
- this.psqlEngine = new PsqlEngine(this.psqlConnectionPool, true);
2744
+ this.psqlEngine = new PsqlEngine(this.psqlConnectionPool, true, this.resturaConfig.scratchDatabaseSuffix);
2721
2745
  await customApiFactory_default.loadApiFiles(this.resturaConfig.customApiFolderPath);
2722
2746
  this.authenticationHandler = authenticationHandler;
2723
2747
  app.use(compression());
@@ -2726,7 +2750,7 @@ var ResturaEngine = class {
2726
2750
  app.use(cookieParser());
2727
2751
  app.disable("x-powered-by");
2728
2752
  app.use("/", addApiResponseFunctions);
2729
- app.use("/api/", authenticateUser(this.authenticationHandler));
2753
+ app.use("/api/", authenticateRequester(this.authenticationHandler));
2730
2754
  app.use("/restura", this.resturaAuthentication);
2731
2755
  app.put(
2732
2756
  "/restura/v1/schema",
@@ -2747,7 +2771,7 @@ var ResturaEngine = class {
2747
2771
  }
2748
2772
  /**
2749
2773
  * Determines if a given endpoint is public based on the HTTP method and full URL. This
2750
- * is determined on whether the endpoint in the schema has no roles assigned to it.
2774
+ * is determined on whether the endpoint in the schema has no roles or scopes assigned to it.
2751
2775
  *
2752
2776
  * @param method - The HTTP method (e.g., 'GET', 'POST', 'PUT', 'PATCH', 'DELETE').
2753
2777
  * @param fullUrl - The full URL of the endpoint.
@@ -2836,7 +2860,8 @@ var ResturaEngine = class {
2836
2860
  route.path = route.path.startsWith("/") ? route.path : `/${route.path}`;
2837
2861
  route.path = route.path.endsWith("/") ? route.path.slice(0, -1) : route.path;
2838
2862
  const fullUrl = `${baseUrl}${route.path}`;
2839
- if (route.roles.length === 0) this.publicEndpoints[route.method].push(fullUrl);
2863
+ if (route.roles.length === 0 && route.scopes.length === 0)
2864
+ this.publicEndpoints[route.method].push(fullUrl);
2840
2865
  this.resturaRouter[route.method.toLowerCase()](
2841
2866
  route.path,
2842
2867
  // <-- Notice we only use path here since the baseUrl is already added to the router.
@@ -2886,10 +2911,10 @@ var ResturaEngine = class {
2886
2911
  );
2887
2912
  this.generateResturaGlobalTypes(path4.join(this.resturaConfig.generatedTypesPath, "restura.d.ts"));
2888
2913
  }
2889
- async getSchema(req, res) {
2914
+ async getSchema(_req, res) {
2890
2915
  res.send({ data: this.schema });
2891
2916
  }
2892
- async getSchemaAndTypes(req, res) {
2917
+ async getSchemaAndTypes(_req, res) {
2893
2918
  try {
2894
2919
  const schema = await this.getLatestFileSystemSchema();
2895
2920
  const apiText = await apiGenerator(schema);
@@ -2900,8 +2925,7 @@ var ResturaEngine = class {
2900
2925
  }
2901
2926
  }
2902
2927
  async getMulterFilesIfAny(req, res, routeData) {
2903
- var _a2;
2904
- if (!((_a2 = req.header("content-type")) == null ? void 0 : _a2.includes("multipart/form-data"))) return;
2928
+ if (!req.header("content-type")?.includes("multipart/form-data")) return;
2905
2929
  if (!this.isCustomRoute(routeData)) return;
2906
2930
  if (!routeData.fileUploadType) {
2907
2931
  throw new RsError("BAD_REQUEST", "File upload type not defined for route");
@@ -2964,16 +2988,17 @@ var ResturaEngine = class {
2964
2988
  await customFunction(req, res, routeData);
2965
2989
  }
2966
2990
  async storeFileSystemSchema() {
2967
- const schemaPrettyStr = await prettier3.format(JSON.stringify(this.schema), __spreadValues({
2968
- parser: "json"
2969
- }, {
2970
- trailingComma: "none",
2971
- tabWidth: 4,
2972
- useTabs: true,
2973
- endOfLine: "lf",
2974
- printWidth: 120,
2975
- singleQuote: true
2976
- }));
2991
+ const schemaPrettyStr = await prettier3.format(JSON.stringify(this.schema), {
2992
+ parser: "json",
2993
+ ...{
2994
+ trailingComma: "none",
2995
+ tabWidth: 4,
2996
+ useTabs: true,
2997
+ endOfLine: "lf",
2998
+ printWidth: 120,
2999
+ singleQuote: true
3000
+ }
3001
+ });
2977
3002
  fs4.writeFileSync(this.resturaConfig.schemaFilePath, schemaPrettyStr);
2978
3003
  }
2979
3004
  resetPublicEndpoints() {
@@ -2986,9 +3011,13 @@ var ResturaEngine = class {
2986
3011
  };
2987
3012
  }
2988
3013
  validateAuthorization(req, routeData) {
2989
- const role = req.requesterDetails.role;
2990
- if (routeData.roles.length === 0 || !role) return;
2991
- if (!routeData.roles.includes(role)) throw new RsError("FORBIDDEN", "Not authorized to access this endpoint");
3014
+ const requesterRole = req.requesterDetails.role;
3015
+ const requesterScopes = req.requesterDetails.scopes;
3016
+ if (routeData.roles.length === 0 && routeData.scopes.length === 0) return;
3017
+ if (requesterRole && routeData.roles.length > 0 && !routeData.roles.includes(requesterRole))
3018
+ throw new RsError("FORBIDDEN", "Not authorized to access this endpoint");
3019
+ if (requesterScopes.length > 0 && routeData.scopes.length > 0 && !routeData.scopes.some((scope) => requesterScopes.includes(scope)))
3020
+ throw new RsError("FORBIDDEN", "Not authorized to access this endpoint");
2992
3021
  }
2993
3022
  getRouteData(method, baseUrl, path5) {
2994
3023
  const endpoint = this.schema.endpoints.find((item) => {
@@ -3042,6 +3071,9 @@ var PsqlTransaction = class extends PsqlConnection {
3042
3071
  this.connectPromise = this.client.connect();
3043
3072
  this.beginTransactionPromise = this.beginTransaction();
3044
3073
  }
3074
+ client;
3075
+ beginTransactionPromise;
3076
+ connectPromise;
3045
3077
  async close() {
3046
3078
  if (this.client) {
3047
3079
  await this.client.end();