@getstrata/core 0.3.8 → 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.
- package/dist/core/auth/policy.d.ts +2 -2
- package/dist/core/database/bindConnection.d.ts +3 -0
- package/dist/core/database/boundConnection.d.ts +5 -0
- package/dist/core/database/query.d.ts +2 -1
- package/dist/core/database/repositoryConnection.d.ts +4 -0
- package/dist/framework/public-api.d.ts +1 -0
- package/dist/index.js +120 -70
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ declare class PolicyGate {
|
|
|
9
9
|
constructor();
|
|
10
10
|
private readonly policies;
|
|
11
11
|
register(resource: string, policy: Policy): void;
|
|
12
|
-
allows(resource: string, action:
|
|
13
|
-
authorize(resource: string, action:
|
|
12
|
+
allows(resource: string, action: string, user?: unknown, model?: unknown): boolean;
|
|
13
|
+
authorize(resource: string, action: string, user?: unknown, model?: unknown): void;
|
|
14
14
|
}
|
|
15
15
|
export { Policy, PolicyGate };
|
|
@@ -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, };
|
|
@@ -12,6 +12,7 @@ export { default as CacheRepository } from "../core/cache/repository";
|
|
|
12
12
|
export { CACHE_TAGS } from "../core/cache/tags";
|
|
13
13
|
export type { DatabaseConnection } from "../core/database/baseRepository";
|
|
14
14
|
export { default as BaseRepository } from "../core/database/baseRepository";
|
|
15
|
+
export { bindDatabaseConnection } from "../core/database/bindConnection";
|
|
15
16
|
export { freshDatabase, getMigrationStatus, loadMigrationsFromDirectory, migrateDatabase, rollbackDatabase, } from "../core/database/migrations/runner";
|
|
16
17
|
export type { Migration, MigrationDatabase, MigrationStatus, } from "../core/database/migrations/types";
|
|
17
18
|
export { Model, registerModelRepository } from "../core/database/model";
|
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,73 +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
|
-
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
297
|
-
function resolveDatabase() {
|
|
298
|
-
return getActiveDatabaseConnection(getDatabase());
|
|
299
|
-
}
|
|
300
|
-
function resolveDatabaseForProperty(property) {
|
|
301
|
-
if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
|
|
302
|
-
return getDatabase();
|
|
303
|
-
}
|
|
304
|
-
return resolveDatabase();
|
|
305
|
-
}
|
|
306
|
-
var db = new Proxy(function database() {}, {
|
|
307
|
-
apply(_target, _thisArg, args) {
|
|
308
|
-
return resolveDatabase()(...args);
|
|
309
|
-
},
|
|
310
|
-
get(_target, property) {
|
|
311
|
-
const connection = resolveDatabaseForProperty(property);
|
|
312
|
-
const value = connection[property];
|
|
313
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
var connection_default = db;
|
|
317
|
-
|
|
318
263
|
// ../../src/core/events/eventBus.ts
|
|
319
264
|
class EventBus {
|
|
320
265
|
constructor() {}
|
|
@@ -614,6 +559,7 @@ function buildCountQuery(table, where = {}, options = {}) {
|
|
|
614
559
|
};
|
|
615
560
|
}
|
|
616
561
|
function buildProjectionQuery(table, expression, alias, options = {}) {
|
|
562
|
+
assertSafeProjectionExpression(expression);
|
|
617
563
|
const { clause, params } = buildQueryWhereClause(table, options);
|
|
618
564
|
const orderBy = buildOrderByClause(table.name, options.orderBy);
|
|
619
565
|
const limit = buildLimitClause(options.limit);
|
|
@@ -622,6 +568,12 @@ function buildProjectionQuery(table, expression, alias, options = {}) {
|
|
|
622
568
|
params
|
|
623
569
|
};
|
|
624
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
|
+
}
|
|
625
577
|
function buildGroupedCountQuery(table, column, where = {}, options = {}) {
|
|
626
578
|
const qualifiedColumn = qualifyColumn(table.name, column);
|
|
627
579
|
const { clause, params } = buildQueryWhereClause(table, {
|
|
@@ -742,6 +694,96 @@ function indexBelongsToRelation(children, parents, relation) {
|
|
|
742
694
|
return result;
|
|
743
695
|
}
|
|
744
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
|
+
|
|
745
787
|
// ../../src/core/database/repositoryQuery.ts
|
|
746
788
|
class RepositoryQuery {
|
|
747
789
|
repository;
|
|
@@ -826,7 +868,7 @@ class RepositoryQuery {
|
|
|
826
868
|
class BaseRepository {
|
|
827
869
|
table;
|
|
828
870
|
connection;
|
|
829
|
-
constructor(table, connection =
|
|
871
|
+
constructor(table, connection = repositoryConnection) {
|
|
830
872
|
this.table = table;
|
|
831
873
|
this.connection = connection;
|
|
832
874
|
}
|
|
@@ -1024,6 +1066,10 @@ class BaseRepository {
|
|
|
1024
1066
|
}
|
|
1025
1067
|
}
|
|
1026
1068
|
var baseRepository_default = BaseRepository;
|
|
1069
|
+
// ../../src/core/database/bindConnection.ts
|
|
1070
|
+
function bindDatabaseConnection2(connection) {
|
|
1071
|
+
bindDatabaseConnection(connection);
|
|
1072
|
+
}
|
|
1027
1073
|
// ../../src/core/database/migrations/runner.ts
|
|
1028
1074
|
import { readdir } from "fs/promises";
|
|
1029
1075
|
import { join } from "path";
|
|
@@ -1048,7 +1094,7 @@ async function getAppliedMigrations(db2) {
|
|
|
1048
1094
|
}
|
|
1049
1095
|
async function loadMigrationsFromDirectory(directory) {
|
|
1050
1096
|
const entries = await readdir(directory);
|
|
1051
|
-
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();
|
|
1052
1098
|
const loadedMigrations = await Promise.all(migrationFiles.map(async (fileName) => {
|
|
1053
1099
|
const moduleUrl = pathToFileURL(join(directory, fileName)).href;
|
|
1054
1100
|
const module = await import(moduleUrl);
|
|
@@ -1073,7 +1119,10 @@ async function migrateDatabase(db2, migrations, options = {}) {
|
|
|
1073
1119
|
for (const migration of pendingMigrations) {
|
|
1074
1120
|
options.onMigration?.(migration.name);
|
|
1075
1121
|
await migration.up(db2);
|
|
1076
|
-
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
|
+
}
|
|
1077
1126
|
}
|
|
1078
1127
|
return pendingMigrations.length;
|
|
1079
1128
|
}
|
|
@@ -2525,6 +2574,7 @@ export {
|
|
|
2525
2574
|
createMetricsMiddleware,
|
|
2526
2575
|
config,
|
|
2527
2576
|
cache,
|
|
2577
|
+
bindDatabaseConnection2 as bindDatabaseConnection,
|
|
2528
2578
|
belongsTo,
|
|
2529
2579
|
auth,
|
|
2530
2580
|
assertIfMatch,
|