@carbonorm/carbonnode 4.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -507
- package/dist/api/executors/SqlExecutor.d.ts +6 -0
- package/dist/api/handlers/ExpressHandler.d.ts +2 -1
- package/dist/api/orm/builders/ConditionBuilder.d.ts +2 -0
- package/dist/api/types/ormInterfaces.d.ts +12 -0
- package/dist/api/utils/sqlAllowList.d.ts +2 -0
- package/dist/index.cjs.js +279 -20
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +278 -21
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/scripts/assets/handlebars/C6.test.ts.handlebars +578 -32
- package/scripts/generateRestBindings.cjs +5 -5
- package/scripts/generateRestBindings.ts +5 -5
- package/src/__tests__/fixtures/createTestServer.ts +11 -3
- package/src/__tests__/fixtures/sqlResponses/actor.get.json +13 -0
- package/src/__tests__/fixtures/sqlResponses/sqlAllowList.blocked.json +3 -0
- package/src/__tests__/fixtures/sqlResponses/sqlAllowList.json +3 -0
- package/src/__tests__/sakila-db/C6.js +1 -1
- package/src/__tests__/sakila-db/C6.mysql.cnf +6 -0
- package/src/__tests__/sakila-db/C6.mysqldump.json +1 -0
- package/src/__tests__/sakila-db/C6.mysqldump.sql +720 -0
- package/src/__tests__/sakila-db/C6.sqlAllowList.json +94 -0
- package/src/__tests__/sakila-db/C6.test.ts +578 -32
- package/src/__tests__/sakila-db/C6.ts +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.get.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.join.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.json +12 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.latest.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.lookup.json +16 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.seed.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.fk.current.json +358 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.fk.referenced.json +158 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.get.json +22 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.join.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.post.json +16 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.post.latest.json +22 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.put.lookup.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.address.seed.json +22 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.get.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.join.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.post.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.post.latest.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.put.lookup.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.category.seed.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.fk.current.json +158 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.fk.referenced.json +133 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.get.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.join.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.post.json +12 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.post.latest.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.put.lookup.json +16 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.city.seed.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.get.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.join.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.post.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.post.latest.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.put.lookup.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.country.seed.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.fk.current.json +283 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.fk.referenced.json +358 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.get.json +19 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.join.json +29 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.latest.json +19 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.lookup.json +21 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.seed.json +19 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.fk.current.json +383 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.fk.referenced.json +38 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.get.json +23 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.join.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.post.json +20 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.post.latest.json +23 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.put.lookup.json +25 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.film.seed.json +23 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.fk.current.json +158 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.fk.referenced.json +20 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.get.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.join.json +25 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.json +12 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.latest.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.lookup.json +16 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.seed.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.get.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.join.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.post.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.post.latest.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.put.lookup.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.language.seed.json +13 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.fk.current.json +233 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.fk.referenced.json +233 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.get.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.join.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.latest.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.lookup.json +19 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.seed.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.delete.json +10 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.delete.lookup.json +9 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.fk.current.json +233 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.fk.referenced.json +34 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.get.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.join.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.json +15 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.latest.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.json +11 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.lookup.json +19 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.seed.json +17 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.staff.fk.current.json +34 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.staff.fk.referenced.json +20 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.staff.get.json +21 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.staff.join.json +31 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.staff.seed.json +21 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.store.fk.current.json +20 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.store.fk.referenced.json +34 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.store.get.json +14 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.store.join.json +24 -0
- package/src/__tests__/sakila-db/sqlResponses/C6.store.seed.json +14 -0
- package/src/__tests__/sakila.generated.test.ts +31 -0
- package/src/__tests__/sqlAllowList.test.ts +135 -0
- package/src/__tests__/sqlBuilders.test.ts +17 -0
- package/src/api/executors/SqlExecutor.ts +156 -0
- package/src/api/handlers/ExpressHandler.ts +10 -1
- package/src/api/orm/builders/ConditionBuilder.ts +27 -7
- package/src/api/types/ormInterfaces.ts +15 -0
- package/src/api/utils/sqlAllowList.ts +54 -0
- package/src/index.ts +1 -0
|
@@ -7,7 +7,15 @@ import {iC6Object, iRestMethods} from "../types/ormInterfaces";
|
|
|
7
7
|
|
|
8
8
|
// TODO - WE MUST make this a generic - optional, but helpful
|
|
9
9
|
// note sure how it would help anyone actually...
|
|
10
|
-
export function ExpressHandler({
|
|
10
|
+
export function ExpressHandler({
|
|
11
|
+
C6,
|
|
12
|
+
mysqlPool,
|
|
13
|
+
sqlAllowListPath,
|
|
14
|
+
}: {
|
|
15
|
+
C6: iC6Object;
|
|
16
|
+
mysqlPool: Pool;
|
|
17
|
+
sqlAllowListPath?: string;
|
|
18
|
+
}) {
|
|
11
19
|
|
|
12
20
|
return async (req: Request, res: Response, next: NextFunction) => {
|
|
13
21
|
try {
|
|
@@ -92,6 +100,7 @@ export function ExpressHandler({C6, mysqlPool}: { C6: iC6Object, mysqlPool: Pool
|
|
|
92
100
|
const response = await restRequest({
|
|
93
101
|
C6,
|
|
94
102
|
mysqlPool,
|
|
103
|
+
sqlAllowListPath,
|
|
95
104
|
requestMethod: method,
|
|
96
105
|
restModel: C6.TABLES[table]
|
|
97
106
|
})(payload);
|
|
@@ -378,19 +378,19 @@ export abstract class ConditionBuilder<
|
|
|
378
378
|
throw new Error('Unsupported operand type in SQL expression.');
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
-
private isPlainArrayLiteral(value: any): boolean {
|
|
381
|
+
private isPlainArrayLiteral(value: any, allowColumnRefs = false): boolean {
|
|
382
382
|
if (!Array.isArray(value)) return false;
|
|
383
383
|
return value.every(item => {
|
|
384
384
|
if (item === null) return true;
|
|
385
385
|
const type = typeof item;
|
|
386
386
|
if (type === 'string' || type === 'number' || type === 'boolean') return true;
|
|
387
|
-
if (Array.isArray(item)) return this.isPlainArrayLiteral(item);
|
|
388
|
-
if (item && typeof item === 'object') return this.isPlainObjectLiteral(item);
|
|
387
|
+
if (Array.isArray(item)) return this.isPlainArrayLiteral(item, allowColumnRefs);
|
|
388
|
+
if (item && typeof item === 'object') return this.isPlainObjectLiteral(item, allowColumnRefs);
|
|
389
389
|
return false;
|
|
390
390
|
});
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
private isPlainObjectLiteral(value: any): boolean {
|
|
393
|
+
private isPlainObjectLiteral(value: any, allowColumnRefs = false): boolean {
|
|
394
394
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
395
395
|
if (value instanceof Date) return false;
|
|
396
396
|
if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(value)) return false;
|
|
@@ -405,13 +405,30 @@ export abstract class ConditionBuilder<
|
|
|
405
405
|
return false;
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
-
if (
|
|
409
|
-
|
|
408
|
+
if (!allowColumnRefs) {
|
|
409
|
+
if (entries.some(([key]) => typeof key === 'string' && (this.isColumnRef(key) || key.includes('.')))) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
410
412
|
}
|
|
411
413
|
|
|
412
414
|
return true;
|
|
413
415
|
}
|
|
414
416
|
|
|
417
|
+
private resolveColumnDefinition(column?: string): any | undefined {
|
|
418
|
+
if (!column || typeof column !== 'string' || !column.includes('.')) return undefined;
|
|
419
|
+
const [prefix, colName] = column.split('.', 2);
|
|
420
|
+
const tableName = this.aliasMap[prefix] ?? prefix;
|
|
421
|
+
const table = this.config.C6?.TABLES?.[tableName];
|
|
422
|
+
if (!table?.TYPE_VALIDATION) return undefined;
|
|
423
|
+
return table.TYPE_VALIDATION[colName] ?? table.TYPE_VALIDATION[`${tableName}.${colName}`];
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private isJsonColumn(column?: string): boolean {
|
|
427
|
+
const columnDef = this.resolveColumnDefinition(column);
|
|
428
|
+
const mysqlType = columnDef?.MYSQL_TYPE;
|
|
429
|
+
return typeof mysqlType === 'string' && mysqlType.toLowerCase().includes('json');
|
|
430
|
+
}
|
|
431
|
+
|
|
415
432
|
protected serializeUpdateValue(
|
|
416
433
|
value: any,
|
|
417
434
|
params: any[] | Record<string, any>,
|
|
@@ -419,7 +436,10 @@ export abstract class ConditionBuilder<
|
|
|
419
436
|
): string {
|
|
420
437
|
const normalized = value instanceof Map ? Object.fromEntries(value) : value;
|
|
421
438
|
|
|
422
|
-
|
|
439
|
+
const allowColumnRefs = this.isJsonColumn(contextColumn);
|
|
440
|
+
|
|
441
|
+
if (this.isPlainArrayLiteral(normalized, allowColumnRefs)
|
|
442
|
+
|| this.isPlainObjectLiteral(normalized, allowColumnRefs)) {
|
|
423
443
|
return this.addParam(params, contextColumn ?? '', JSON.stringify(normalized));
|
|
424
444
|
}
|
|
425
445
|
|
|
@@ -162,6 +162,19 @@ export type DetermineResponseDataType<
|
|
|
162
162
|
? iDeleteC6RestResponse<RestTableInterface>
|
|
163
163
|
: never);
|
|
164
164
|
|
|
165
|
+
|
|
166
|
+
export type iRestWebsocketPayload = {
|
|
167
|
+
REST: {
|
|
168
|
+
TABLE_NAME: string;
|
|
169
|
+
TABLE_PREFIX: string;
|
|
170
|
+
METHOD: iRestMethods;
|
|
171
|
+
REQUEST: Record<string, any>;
|
|
172
|
+
REQUEST_PRIMARY_KEY: Record<string, any> | null;
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export type tWebsocketBroadcast = (payload: iRestWebsocketPayload) => void | Promise<void>;
|
|
177
|
+
|
|
165
178
|
export interface iRest<
|
|
166
179
|
RestShortTableName extends string = any,
|
|
167
180
|
RestTableInterface extends Record<string, any> = any,
|
|
@@ -177,7 +190,9 @@ export interface iRest<
|
|
|
177
190
|
requestMethod: iRestMethods;
|
|
178
191
|
clearCache?: () => void;
|
|
179
192
|
skipPrimaryCheck?: boolean;
|
|
193
|
+
websocketBroadcast?: tWebsocketBroadcast;
|
|
180
194
|
verbose?: boolean;
|
|
195
|
+
sqlAllowListPath?: string;
|
|
181
196
|
}
|
|
182
197
|
|
|
183
198
|
export interface iConstraint {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import isNode from "../../variables/isNode";
|
|
2
|
+
|
|
3
|
+
const allowListCache = new Map<string, Set<string>>();
|
|
4
|
+
|
|
5
|
+
export const normalizeSql = (sql: string): string =>
|
|
6
|
+
sql.replace(/\s+/g, " ").trim();
|
|
7
|
+
|
|
8
|
+
const parseAllowList = (raw: string, sourcePath: string): string[] => {
|
|
9
|
+
let parsed: unknown;
|
|
10
|
+
try {
|
|
11
|
+
parsed = JSON.parse(raw);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new Error(`SQL allowlist at ${sourcePath} is not valid JSON.`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!Array.isArray(parsed)) {
|
|
17
|
+
throw new Error(`SQL allowlist at ${sourcePath} must be a JSON array of strings.`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const sqlEntries = parsed
|
|
21
|
+
.filter((entry): entry is string => typeof entry === "string")
|
|
22
|
+
.map(normalizeSql)
|
|
23
|
+
.filter((entry) => entry.length > 0);
|
|
24
|
+
|
|
25
|
+
if (sqlEntries.length !== parsed.length) {
|
|
26
|
+
throw new Error(`SQL allowlist at ${sourcePath} must contain only string entries.`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return sqlEntries;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const loadSqlAllowList = async (allowListPath: string): Promise<Set<string>> => {
|
|
33
|
+
if (allowListCache.has(allowListPath)) {
|
|
34
|
+
return allowListCache.get(allowListPath)!;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!isNode()) {
|
|
38
|
+
throw new Error("SQL allowlist validation requires a Node runtime.");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const {readFile} = await import("node:fs/promises");
|
|
42
|
+
|
|
43
|
+
let raw: string;
|
|
44
|
+
try {
|
|
45
|
+
raw = await readFile(allowListPath, "utf-8");
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new Error(`SQL allowlist file not found at ${allowListPath}.`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const sqlEntries = parseAllowList(raw, allowListPath);
|
|
51
|
+
const allowList = new Set(sqlEntries);
|
|
52
|
+
allowListCache.set(allowListPath, allowList);
|
|
53
|
+
return allowList;
|
|
54
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ export * from "./api/utils/determineRuntimeJsType";
|
|
|
37
37
|
export * from "./api/utils/logger";
|
|
38
38
|
export * from "./api/utils/normalizeSingularRequest";
|
|
39
39
|
export * from "./api/utils/sortAndSerializeQueryObject";
|
|
40
|
+
export * from "./api/utils/sqlAllowList";
|
|
40
41
|
export * from "./api/utils/testHelpers";
|
|
41
42
|
export * from "./api/utils/toastNotifier";
|
|
42
43
|
export * from "./variables/getEnvVar";
|