@getstrata/core 0.3.9 → 0.4.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.
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DatabaseConnection } from "./baseRepository";
|
|
2
|
+
declare function bindDatabaseConnection(connection: DatabaseConnection): void;
|
|
3
|
+
declare function getBoundDatabaseConnection(): DatabaseConnection | null;
|
|
4
|
+
declare function resetBoundDatabaseConnection(): void;
|
|
5
|
+
export { bindDatabaseConnection, getBoundDatabaseConnection, resetBoundDatabaseConnection };
|
|
@@ -24,6 +24,7 @@ declare function buildProjectionQuery<TEntity>(table: TableDefinition<TEntity>,
|
|
|
24
24
|
text: string;
|
|
25
25
|
params: unknown[];
|
|
26
26
|
};
|
|
27
|
+
declare function assertSafeProjectionExpression(expression: string): void;
|
|
27
28
|
declare function buildGroupedCountQuery<TEntity, K extends keyof TEntity & string>(table: TableDefinition<TEntity>, column: K, where?: QueryWhere<TEntity>, options?: Pick<QueryOptions<TEntity>, "withTrashed" | "onlyTrashed">): {
|
|
28
29
|
text: string;
|
|
29
30
|
params: unknown[];
|
|
@@ -48,4 +49,4 @@ declare function buildDeleteByIdQuery<TEntity, PrimaryKey extends keyof TEntity
|
|
|
48
49
|
text: string;
|
|
49
50
|
params: unknown[];
|
|
50
51
|
};
|
|
51
|
-
export { buildCountQuery, buildDeleteByIdQuery, buildGroupedCountQuery, buildInsertQuery, buildOrderByClause, buildProjectionQuery, buildQueryWhereClause, buildRestoreByIdQuery, buildSelectQuery, buildSoftDeleteByIdQuery, buildUpdateQuery, buildWhereClause, qualifyColumn, quoteIdentifier, resolveSoftDeleteColumn, };
|
|
52
|
+
export { assertSafeProjectionExpression, buildCountQuery, buildDeleteByIdQuery, buildGroupedCountQuery, buildInsertQuery, buildOrderByClause, buildProjectionQuery, buildQueryWhereClause, buildRestoreByIdQuery, buildSelectQuery, buildSoftDeleteByIdQuery, buildUpdateQuery, buildWhereClause, qualifyColumn, quoteIdentifier, resolveSoftDeleteColumn, };
|
package/dist/index.js
CHANGED
|
@@ -157,6 +157,15 @@ class Policy {
|
|
|
157
157
|
return false;
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
|
+
var BLOCKED_POLICY_ACTIONS = new Set([
|
|
161
|
+
"constructor",
|
|
162
|
+
"toString",
|
|
163
|
+
"valueOf",
|
|
164
|
+
"hasOwnProperty",
|
|
165
|
+
"isPrototypeOf",
|
|
166
|
+
"propertyIsEnumerable",
|
|
167
|
+
"__proto__"
|
|
168
|
+
]);
|
|
160
169
|
|
|
161
170
|
class PolicyGate {
|
|
162
171
|
constructor() {}
|
|
@@ -169,6 +178,9 @@ class PolicyGate {
|
|
|
169
178
|
if (!policy) {
|
|
170
179
|
return false;
|
|
171
180
|
}
|
|
181
|
+
if (BLOCKED_POLICY_ACTIONS.has(action) || !(action in policy)) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
172
184
|
const handler = policy[action];
|
|
173
185
|
if (typeof handler !== "function") {
|
|
174
186
|
return false;
|
|
@@ -248,76 +260,6 @@ var CACHE_TAGS = {
|
|
|
248
260
|
attachments: "attachments",
|
|
249
261
|
reports: "reports"
|
|
250
262
|
};
|
|
251
|
-
// ../../src/config/database.ts
|
|
252
|
-
function readInteger(name, fallback) {
|
|
253
|
-
const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
|
|
254
|
-
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
255
|
-
}
|
|
256
|
-
var databaseConfig = {
|
|
257
|
-
url: process.env.DATABASE_URL ?? "",
|
|
258
|
-
poolMax: readInteger("DB_POOL_MAX", 10),
|
|
259
|
-
idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
|
|
260
|
-
maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
|
|
261
|
-
connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
// ../../src/core/database/connectionContext.ts
|
|
265
|
-
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
266
|
-
var activeConnection = new AsyncLocalStorage2;
|
|
267
|
-
function getActiveDatabaseConnection(fallback) {
|
|
268
|
-
return activeConnection.getStore() ?? fallback;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// ../../src/db/connection/createConnection.ts
|
|
272
|
-
var {SQL } = globalThis.Bun;
|
|
273
|
-
function createDatabaseConnection(config) {
|
|
274
|
-
if (!config.url) {
|
|
275
|
-
throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
|
|
276
|
-
}
|
|
277
|
-
return new SQL({
|
|
278
|
-
url: config.url,
|
|
279
|
-
max: config.poolMax,
|
|
280
|
-
idleTimeout: config.idleTimeoutSeconds,
|
|
281
|
-
maxLifetime: config.maxLifetimeSeconds,
|
|
282
|
-
connectionTimeout: config.connectionTimeoutSeconds
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// ../../src/db/connection/index.ts
|
|
287
|
-
var connectionHolder = {
|
|
288
|
-
connection: null
|
|
289
|
-
};
|
|
290
|
-
function getDatabase() {
|
|
291
|
-
if (!connectionHolder.connection) {
|
|
292
|
-
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
293
|
-
}
|
|
294
|
-
return connectionHolder.connection;
|
|
295
|
-
}
|
|
296
|
-
function resetDatabaseConnectionForTests(connection) {
|
|
297
|
-
connectionHolder.connection = connection;
|
|
298
|
-
}
|
|
299
|
-
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
300
|
-
function resolveDatabase() {
|
|
301
|
-
return getActiveDatabaseConnection(getDatabase());
|
|
302
|
-
}
|
|
303
|
-
function resolveDatabaseForProperty(property) {
|
|
304
|
-
if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
|
|
305
|
-
return getDatabase();
|
|
306
|
-
}
|
|
307
|
-
return resolveDatabase();
|
|
308
|
-
}
|
|
309
|
-
var db = new Proxy(function database() {}, {
|
|
310
|
-
apply(_target, _thisArg, args) {
|
|
311
|
-
return resolveDatabase()(...args);
|
|
312
|
-
},
|
|
313
|
-
get(_target, property) {
|
|
314
|
-
const connection = resolveDatabaseForProperty(property);
|
|
315
|
-
const value = connection[property];
|
|
316
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
var connection_default = db;
|
|
320
|
-
|
|
321
263
|
// ../../src/core/events/eventBus.ts
|
|
322
264
|
class EventBus {
|
|
323
265
|
constructor() {}
|
|
@@ -617,6 +559,7 @@ function buildCountQuery(table, where = {}, options = {}) {
|
|
|
617
559
|
};
|
|
618
560
|
}
|
|
619
561
|
function buildProjectionQuery(table, expression, alias, options = {}) {
|
|
562
|
+
assertSafeProjectionExpression(expression);
|
|
620
563
|
const { clause, params } = buildQueryWhereClause(table, options);
|
|
621
564
|
const orderBy = buildOrderByClause(table.name, options.orderBy);
|
|
622
565
|
const limit = buildLimitClause(options.limit);
|
|
@@ -625,6 +568,12 @@ function buildProjectionQuery(table, expression, alias, options = {}) {
|
|
|
625
568
|
params
|
|
626
569
|
};
|
|
627
570
|
}
|
|
571
|
+
var SAFE_PROJECTION_EXPRESSION = /^(COUNT\(\*\)|(?:"[\w_]+"(?:\."[\w_]+")?)|(?:AVG|SUM|MIN|MAX)\((?:"[\w_]+"(?:\."[\w_]+")?)\))$/;
|
|
572
|
+
function assertSafeProjectionExpression(expression) {
|
|
573
|
+
if (!SAFE_PROJECTION_EXPRESSION.test(expression.trim())) {
|
|
574
|
+
throw new Error(`Unsafe projection expression: ${expression}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
628
577
|
function buildGroupedCountQuery(table, column, where = {}, options = {}) {
|
|
629
578
|
const qualifiedColumn = qualifyColumn(table.name, column);
|
|
630
579
|
const { clause, params } = buildQueryWhereClause(table, {
|
|
@@ -745,6 +694,96 @@ function indexBelongsToRelation(children, parents, relation) {
|
|
|
745
694
|
return result;
|
|
746
695
|
}
|
|
747
696
|
|
|
697
|
+
// ../../src/config/database.ts
|
|
698
|
+
function readInteger(name, fallback) {
|
|
699
|
+
const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
|
|
700
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
701
|
+
}
|
|
702
|
+
var databaseConfig = {
|
|
703
|
+
url: process.env.DATABASE_URL ?? "",
|
|
704
|
+
poolMax: readInteger("DB_POOL_MAX", 10),
|
|
705
|
+
idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
|
|
706
|
+
maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
|
|
707
|
+
connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
// ../../src/core/database/connectionContext.ts
|
|
711
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
712
|
+
var activeConnection = new AsyncLocalStorage2;
|
|
713
|
+
function getActiveDatabaseConnection(fallback) {
|
|
714
|
+
return activeConnection.getStore() ?? fallback;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// ../../src/db/connection/createConnection.ts
|
|
718
|
+
var {SQL } = globalThis.Bun;
|
|
719
|
+
function createDatabaseConnection(config) {
|
|
720
|
+
if (!config.url) {
|
|
721
|
+
throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
|
|
722
|
+
}
|
|
723
|
+
return new SQL({
|
|
724
|
+
url: config.url,
|
|
725
|
+
max: config.poolMax,
|
|
726
|
+
idleTimeout: config.idleTimeoutSeconds,
|
|
727
|
+
maxLifetime: config.maxLifetimeSeconds,
|
|
728
|
+
connectionTimeout: config.connectionTimeoutSeconds
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// ../../src/db/connection/index.ts
|
|
733
|
+
var connectionHolder = {
|
|
734
|
+
connection: null
|
|
735
|
+
};
|
|
736
|
+
function getDatabase() {
|
|
737
|
+
if (!connectionHolder.connection) {
|
|
738
|
+
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
739
|
+
}
|
|
740
|
+
return connectionHolder.connection;
|
|
741
|
+
}
|
|
742
|
+
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
743
|
+
function resolveDatabase() {
|
|
744
|
+
return getActiveDatabaseConnection(getDatabase());
|
|
745
|
+
}
|
|
746
|
+
function resolveDatabaseForProperty(property) {
|
|
747
|
+
if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
|
|
748
|
+
return getDatabase();
|
|
749
|
+
}
|
|
750
|
+
return resolveDatabase();
|
|
751
|
+
}
|
|
752
|
+
var db = new Proxy(function database() {}, {
|
|
753
|
+
apply(_target, _thisArg, args) {
|
|
754
|
+
return resolveDatabase()(...args);
|
|
755
|
+
},
|
|
756
|
+
get(_target, property) {
|
|
757
|
+
const connection = resolveDatabaseForProperty(property);
|
|
758
|
+
const value = connection[property];
|
|
759
|
+
return typeof value === "function" ? value.bind(connection) : value;
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
var connection_default = db;
|
|
763
|
+
|
|
764
|
+
// ../../src/core/database/boundConnection.ts
|
|
765
|
+
var boundConnectionHolder = {
|
|
766
|
+
connection: null
|
|
767
|
+
};
|
|
768
|
+
function bindDatabaseConnection(connection) {
|
|
769
|
+
boundConnectionHolder.connection = connection;
|
|
770
|
+
}
|
|
771
|
+
function getBoundDatabaseConnection() {
|
|
772
|
+
return boundConnectionHolder.connection;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// ../../src/core/database/repositoryConnection.ts
|
|
776
|
+
function resolveRepositoryConnection() {
|
|
777
|
+
return getBoundDatabaseConnection() ?? connection_default;
|
|
778
|
+
}
|
|
779
|
+
var repositoryConnection = new Proxy({}, {
|
|
780
|
+
get(_target, property) {
|
|
781
|
+
const connection = resolveRepositoryConnection();
|
|
782
|
+
const value = connection[property];
|
|
783
|
+
return typeof value === "function" ? value.bind(connection) : value;
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
|
|
748
787
|
// ../../src/core/database/repositoryQuery.ts
|
|
749
788
|
class RepositoryQuery {
|
|
750
789
|
repository;
|
|
@@ -829,7 +868,7 @@ class RepositoryQuery {
|
|
|
829
868
|
class BaseRepository {
|
|
830
869
|
table;
|
|
831
870
|
connection;
|
|
832
|
-
constructor(table, connection =
|
|
871
|
+
constructor(table, connection = repositoryConnection) {
|
|
833
872
|
this.table = table;
|
|
834
873
|
this.connection = connection;
|
|
835
874
|
}
|
|
@@ -1028,8 +1067,8 @@ class BaseRepository {
|
|
|
1028
1067
|
}
|
|
1029
1068
|
var baseRepository_default = BaseRepository;
|
|
1030
1069
|
// ../../src/core/database/bindConnection.ts
|
|
1031
|
-
function
|
|
1032
|
-
|
|
1070
|
+
function bindDatabaseConnection2(connection) {
|
|
1071
|
+
bindDatabaseConnection(connection);
|
|
1033
1072
|
}
|
|
1034
1073
|
// ../../src/core/database/migrations/runner.ts
|
|
1035
1074
|
import { readdir } from "fs/promises";
|
|
@@ -1055,7 +1094,7 @@ async function getAppliedMigrations(db2) {
|
|
|
1055
1094
|
}
|
|
1056
1095
|
async function loadMigrationsFromDirectory(directory) {
|
|
1057
1096
|
const entries = await readdir(directory);
|
|
1058
|
-
const migrationFiles = entries.filter((entry) => entry.endsWith(".ts") && entry !== "types.ts" && entry !== "index.ts" && entry !== "runner.ts").sort();
|
|
1097
|
+
const migrationFiles = entries.filter((entry) => (entry.endsWith(".ts") || entry.endsWith(".js")) && entry !== "types.ts" && entry !== "index.ts" && entry !== "runner.ts").sort();
|
|
1059
1098
|
const loadedMigrations = await Promise.all(migrationFiles.map(async (fileName) => {
|
|
1060
1099
|
const moduleUrl = pathToFileURL(join(directory, fileName)).href;
|
|
1061
1100
|
const module = await import(moduleUrl);
|
|
@@ -1080,7 +1119,10 @@ async function migrateDatabase(db2, migrations, options = {}) {
|
|
|
1080
1119
|
for (const migration of pendingMigrations) {
|
|
1081
1120
|
options.onMigration?.(migration.name);
|
|
1082
1121
|
await migration.up(db2);
|
|
1083
|
-
await db2.unsafe(`INSERT INTO ${MIGRATIONS_TABLE} (name, batch) VALUES ($1, $2) ON CONFLICT (name) DO NOTHING`, [migration.name, nextBatch]);
|
|
1122
|
+
const inserted = await db2.unsafe(`INSERT INTO ${MIGRATIONS_TABLE} (name, batch) VALUES ($1, $2) ON CONFLICT (name) DO NOTHING RETURNING name`, [migration.name, nextBatch]);
|
|
1123
|
+
if (inserted.length === 0) {
|
|
1124
|
+
throw new Error(`Migration ${migration.name} was applied but not recorded.`);
|
|
1125
|
+
}
|
|
1084
1126
|
}
|
|
1085
1127
|
return pendingMigrations.length;
|
|
1086
1128
|
}
|
|
@@ -2532,7 +2574,7 @@ export {
|
|
|
2532
2574
|
createMetricsMiddleware,
|
|
2533
2575
|
config,
|
|
2534
2576
|
cache,
|
|
2535
|
-
bindDatabaseConnection,
|
|
2577
|
+
bindDatabaseConnection2 as bindDatabaseConnection,
|
|
2536
2578
|
belongsTo,
|
|
2537
2579
|
auth,
|
|
2538
2580
|
assertIfMatch,
|