@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, };
@@ -0,0 +1,4 @@
1
+ import type { DatabaseConnection } from "./baseRepository";
2
+ declare function resolveRepositoryConnection(): DatabaseConnection;
3
+ declare const repositoryConnection: DatabaseConnection;
4
+ export { repositoryConnection, resolveRepositoryConnection };
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 = connection_default) {
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 bindDatabaseConnection(connection) {
1032
- resetDatabaseConnectionForTests(connection);
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "0.3.9",
3
+ "version": "0.4.0",
4
4
  "description": "Strata — Laravel-inspired Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",