@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.
@@ -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: keyof Policy, user?: unknown, model?: unknown): boolean;
13
- authorize(resource: string, action: keyof Policy, user?: unknown, model?: unknown): void;
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,3 @@
1
+ import type { DatabaseConnection } from "./baseRepository";
2
+ declare function bindDatabaseConnection(connection: DatabaseConnection): void;
3
+ export { bindDatabaseConnection };
@@ -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 };
@@ -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 = connection_default) {
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "0.3.8",
3
+ "version": "0.4.0",
4
4
  "description": "Strata — Laravel-inspired Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",