@getstrata/core 0.5.13 → 0.5.15

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.
Files changed (46) hide show
  1. package/README.md +16 -6
  2. package/dist/bootstrap/contracts.d.ts +2 -0
  3. package/dist/bootstrap/httpKernel.d.ts +1 -2
  4. package/dist/core/admin/registry.d.ts +1 -0
  5. package/dist/core/auth/membershipService.d.ts +5 -1
  6. package/dist/core/database/baseRepository.d.ts +6 -1
  7. package/dist/core/database/connectionContext.d.ts +2 -1
  8. package/dist/core/database/defaultConnection.d.ts +6 -0
  9. package/dist/core/database/queryProxy.d.ts +3 -0
  10. package/dist/core/database/repositoryConnection.d.ts +3 -3
  11. package/dist/core/jobs/dispatchWebhookJob.d.ts +0 -1
  12. package/dist/core/security/safeFetch.d.ts +2 -0
  13. package/dist/core/security/safeUrl.d.ts +16 -1
  14. package/dist/core/storage/storage.d.ts +6 -3
  15. package/dist/core/tenant/tenantDatabaseScope.d.ts +2 -1
  16. package/dist/db/connection/index.d.ts +1 -1
  17. package/dist/entries/auth/accessControl.js +1 -113
  18. package/dist/entries/auth/authContext.js +1 -15
  19. package/dist/entries/auth/guard.js +1 -3044
  20. package/dist/entries/auth/membershipContext.js +1 -276
  21. package/dist/entries/auth/membershipScope.js +1 -390
  22. package/dist/entries/auth/membershipService.js +1 -480
  23. package/dist/entries/auth/policy.js +1 -134
  24. package/dist/entries/database.js +1 -2398
  25. package/dist/entries/http/csrfToken.js +0 -6
  26. package/dist/entries/http/middleware.js +1 -68
  27. package/dist/entries/http/requestMetaContext.js +1 -18
  28. package/dist/entries/http/webErrorResponse.js +94 -563
  29. package/dist/entries/http/webFormRequest.js +0 -131
  30. package/dist/entries/http.js +1 -4091
  31. package/dist/entries/jobs/dispatchWebhookJob.js +109 -102
  32. package/dist/entries/queue/createAppQueue.js +111 -631
  33. package/dist/entries/queue/jobRunner.js +0 -3
  34. package/dist/entries/queue/publicQueue.js +45 -546
  35. package/dist/entries/queue/queueMetrics.js +111 -631
  36. package/dist/entries/security/safeUrl.js +29 -0
  37. package/dist/entries/security/securityEvents.js +1 -41
  38. package/dist/entries/storage/storage.js +14 -4
  39. package/dist/entries/tenant/tenantContext.js +1 -30
  40. package/dist/entries/tenant/tenantMiddleware.js +1 -312
  41. package/dist/entries/tracing/traceContext.js +1 -15
  42. package/dist/entries/view.js +94 -563
  43. package/dist/framework/public-api.d.ts +36 -4
  44. package/dist/index.js +2270 -1666
  45. package/dist/modules/user/repository.d.ts +1 -0
  46. package/package.json +6 -5
@@ -3,9 +3,6 @@
3
3
  var {RedisClient: RedisClient2 } = globalThis.Bun;
4
4
 
5
5
  // ../../src/bootstrap/config.ts
6
- var CORE_QUEUE_TOKEN = "core.queue";
7
- var CORE_POLICY_GATE_TOKEN = "core.policyGate";
8
- var CORE_AUTH_TOKEN = "core.auth";
9
6
  var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
10
7
  var DEFAULT_QUEUE_DRIVER = "sync";
11
8
 
@@ -123,15 +120,9 @@ function getRequiredDependency(dependencies, key) {
123
120
  }
124
121
  return dependency;
125
122
  }
126
- function resolveService(dependencies, token) {
127
- return dependencies.container.resolve(token);
128
- }
129
123
 
130
124
  // ../../src/bootstrap/applicationRegistry.ts
131
125
  var activeContext;
132
- function setActiveApplicationContext(context) {
133
- activeContext = context;
134
- }
135
126
  function requireActiveApplicationContext() {
136
127
  if (!activeContext) {
137
128
  throw new Error("The application context has not been bootstrapped.");
@@ -141,24 +132,6 @@ function requireActiveApplicationContext() {
141
132
  function resolveApplicationCache() {
142
133
  return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
143
134
  }
144
- function resolveApplicationQueue() {
145
- return requireActiveApplicationContext().container.resolve(CORE_QUEUE_TOKEN);
146
- }
147
- function resolveApplicationAuth() {
148
- return requireActiveApplicationContext().container.resolve(CORE_AUTH_TOKEN);
149
- }
150
- function resolveApplicationPolicyGate() {
151
- return requireActiveApplicationContext().container.resolve(CORE_POLICY_GATE_TOKEN);
152
- }
153
- function resolveApplicationConfig() {
154
- return requireActiveApplicationContext().config;
155
- }
156
- function resolveApplicationLogger() {
157
- return appLogger;
158
- }
159
- function resolveApplicationDependencies() {
160
- return requireActiveApplicationContext().dependencies;
161
- }
162
135
 
163
136
  // ../../src/core/jobs/dispatchWebhookJob.ts
164
137
  import { createHmac } from "crypto";
@@ -172,75 +145,77 @@ var appConfig = {
172
145
  apiPrefix: process.env.API_PREFIX ?? "/api/v1"
173
146
  };
174
147
 
175
- // ../../src/config/database.ts
176
- function readInteger(name, fallback) {
177
- const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
178
- return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
179
- }
180
- var databaseConfig = {
181
- url: process.env.DATABASE_URL ?? "",
182
- poolMax: readInteger("DB_POOL_MAX", 10),
183
- idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
184
- maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
185
- connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
148
+ // ../../src/core/database/boundConnection.ts
149
+ var boundConnectionHolder = {
150
+ connection: null
186
151
  };
152
+ function getBoundDatabaseConnection() {
153
+ return boundConnectionHolder.connection;
154
+ }
187
155
 
188
156
  // ../../src/core/database/connectionContext.ts
189
157
  import { AsyncLocalStorage } from "async_hooks";
190
158
  var activeConnection = new AsyncLocalStorage;
191
- function runWithDatabaseConnection(connection, callback) {
192
- return activeConnection.run(connection, callback);
193
- }
194
159
  function getActiveDatabaseConnection(fallback) {
195
160
  return activeConnection.getStore() ?? fallback;
196
161
  }
197
162
 
198
- // ../../src/db/connection/createConnection.ts
199
- var {SQL } = globalThis.Bun;
200
- function createDatabaseConnection(config) {
201
- if (!config.url) {
202
- throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
203
- }
204
- return new SQL({
205
- url: config.url,
206
- max: config.poolMax,
207
- idleTimeout: config.idleTimeoutSeconds,
208
- maxLifetime: config.maxLifetimeSeconds,
209
- connectionTimeout: config.connectionTimeoutSeconds
163
+ // ../../src/core/database/queryProxy.ts
164
+ var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
165
+ function createDatabaseQueryProxy(pool) {
166
+ function resolveDatabase() {
167
+ return getActiveDatabaseConnection(pool);
168
+ }
169
+ function resolveDatabaseForProperty(property) {
170
+ if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
171
+ return pool;
172
+ }
173
+ return resolveDatabase();
174
+ }
175
+ return new Proxy(function database() {}, {
176
+ apply(_target, _thisArg, args) {
177
+ return resolveDatabase()(...args);
178
+ },
179
+ get(_target, property) {
180
+ const connection = resolveDatabaseForProperty(property);
181
+ const value = connection[property];
182
+ return typeof value === "function" ? value.bind(connection) : value;
183
+ }
210
184
  });
211
185
  }
212
186
 
213
- // ../../src/db/connection/index.ts
214
- var connectionHolder = {
187
+ // ../../src/core/database/defaultConnection.ts
188
+ var defaultPool = {
215
189
  connection: null
216
190
  };
217
- function getDatabase() {
218
- if (!connectionHolder.connection) {
219
- connectionHolder.connection = createDatabaseConnection(databaseConfig);
220
- }
221
- return connectionHolder.connection;
222
- }
223
- var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
224
- function resolveDatabase() {
225
- return getActiveDatabaseConnection(getDatabase());
191
+ var defaultQuery = {
192
+ connection: null
193
+ };
194
+ function registerDefaultDatabasePool(connection) {
195
+ defaultPool.connection = connection;
196
+ defaultQuery.connection = createDatabaseQueryProxy(connection);
226
197
  }
227
- function resolveDatabaseForProperty(property) {
228
- if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
229
- return getDatabase();
198
+ function getDefaultDatabaseQuery() {
199
+ if (!defaultQuery.connection) {
200
+ throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
230
201
  }
231
- return resolveDatabase();
202
+ return defaultQuery.connection;
232
203
  }
233
- var db = new Proxy(function database() {}, {
204
+
205
+ // ../../src/core/database/repositoryConnection.ts
206
+ function resolveRepositoryConnection() {
207
+ return getBoundDatabaseConnection() ?? getDefaultDatabaseQuery();
208
+ }
209
+ var repositoryConnection = new Proxy(function repositoryConnection2() {}, {
234
210
  apply(_target, _thisArg, args) {
235
- return resolveDatabase()(...args);
211
+ return resolveRepositoryConnection()(...args);
236
212
  },
237
213
  get(_target, property) {
238
- const connection = resolveDatabaseForProperty(property);
214
+ const connection = resolveRepositoryConnection();
239
215
  const value = connection[property];
240
216
  return typeof value === "function" ? value.bind(connection) : value;
241
217
  }
242
218
  });
243
- var connection_default = db;
244
219
 
245
220
  // ../../src/core/queue/index.ts
246
221
  class Job {
@@ -249,56 +224,8 @@ class Job {
249
224
  priority;
250
225
  }
251
226
 
252
- class SyncQueue {
253
- async dispatch(job, payload) {
254
- await job.handle(payload);
255
- }
256
- }
257
-
258
- class AsyncQueue {
259
- async dispatch(job, payload) {
260
- setTimeout(() => {
261
- job.handle(payload).catch((error) => {
262
- console.error("[AsyncQueue] Job failed:", error);
263
- });
264
- }, 0);
265
- }
266
- }
267
- function createQueue(driver) {
268
- return driver === "async" ? new AsyncQueue : new SyncQueue;
269
- }
270
-
271
- // ../../src/core/security/safeFetch.ts
272
- var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
273
- async function safeFetch(input, init = {}, options = {}) {
274
- const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
275
- const maxRedirects = options.maxRedirects ?? 0;
276
- const controller = new AbortController;
277
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
278
- try {
279
- let currentUrl = input;
280
- let redirectCount = 0;
281
- while (true) {
282
- const response = await fetch(currentUrl, {
283
- ...init,
284
- signal: controller.signal,
285
- redirect: "manual"
286
- });
287
- if (response.status >= 300 && response.status < 400) {
288
- const location = response.headers.get("location");
289
- if (!location || redirectCount >= maxRedirects) {
290
- return response;
291
- }
292
- currentUrl = new URL(location, currentUrl).toString();
293
- redirectCount += 1;
294
- continue;
295
- }
296
- return response;
297
- }
298
- } finally {
299
- clearTimeout(timeout);
300
- }
301
- }
227
+ // ../../src/core/security/safeUrl.ts
228
+ import { lookup as dnsLookupImpl } from "dns/promises";
302
229
 
303
230
  // ../../src/core/errors/http.ts
304
231
  class HttpError extends Error {
@@ -367,6 +294,7 @@ class PreconditionFailedError extends HttpError {
367
294
  }
368
295
 
369
296
  // ../../src/core/security/safeUrl.ts
297
+ var dnsLookup = dnsLookupImpl;
370
298
  var BLOCKED_HOSTNAMES = new Set([
371
299
  "localhost",
372
300
  "127.0.0.1",
@@ -438,16 +366,68 @@ function assertSafeOutboundUrl(rawUrl, options = {}) {
438
366
  }
439
367
  return parsed;
440
368
  }
369
+ function isBlockedIpAddress(address) {
370
+ return isBlockedHostname(address.trim().toLowerCase());
371
+ }
372
+ async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
373
+ const parsed = assertSafeOutboundUrl(rawUrl, options);
374
+ if (options.resolveDns === false) {
375
+ return parsed;
376
+ }
377
+ const hostname = parsed.hostname.trim().toLowerCase();
378
+ const results = await dnsLookup(hostname, { all: true, verbatim: true });
379
+ if (results.some((result) => isBlockedIpAddress(result.address))) {
380
+ throw new BadRequestError("Webhook URL targets a blocked host.");
381
+ }
382
+ return parsed;
383
+ }
384
+ function setDnsLookupForTests(lookupFn) {
385
+ dnsLookup = lookupFn;
386
+ }
387
+ function resetDnsLookupForTests() {
388
+ dnsLookup = dnsLookupImpl;
389
+ }
390
+
391
+ // ../../src/core/security/safeFetch.ts
392
+ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
393
+ async function safeFetch(input, init = {}, options = {}) {
394
+ const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
395
+ const maxRedirects = options.maxRedirects ?? 0;
396
+ const resolveDns = options.resolveDns ?? appConfig.env === "production";
397
+ const urlOptions = { allowHttp: options.allowHttp, resolveDns };
398
+ const controller = new AbortController;
399
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
400
+ try {
401
+ let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
402
+ let redirectCount = 0;
403
+ while (true) {
404
+ const response = await fetch(currentUrl, {
405
+ ...init,
406
+ signal: controller.signal,
407
+ redirect: "manual"
408
+ });
409
+ if (response.status >= 300 && response.status < 400) {
410
+ const location = response.headers.get("location");
411
+ if (!location || redirectCount >= maxRedirects) {
412
+ return response;
413
+ }
414
+ currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
415
+ redirectCount += 1;
416
+ continue;
417
+ }
418
+ return response;
419
+ }
420
+ } finally {
421
+ clearTimeout(timeout);
422
+ }
423
+ }
441
424
 
442
425
  // ../../src/core/jobs/dispatchWebhookJob.ts
443
426
  class DispatchWebhookJob extends Job {
444
- constructor() {
445
- super();
446
- }
447
427
  maxAttempts = 3;
448
428
  backoffMs = 2000;
449
429
  async handle(payload) {
450
- const rows = await connection_default`
430
+ const rows = await repositoryConnection`
451
431
  SELECT id, url, secret
452
432
  FROM webhook
453
433
  WHERE id = ${payload.webhookId} AND active = TRUE
@@ -470,14 +450,14 @@ class DispatchWebhookJob extends Job {
470
450
  "x-workhub-signature": signature
471
451
  },
472
452
  body
473
- });
453
+ }, { allowHttp: appConfig.env !== "production" });
474
454
  responseStatus = response.status;
475
455
  if (!response.ok) {
476
456
  throw new Error(`Webhook delivery failed with status ${response.status}.`);
477
457
  }
478
458
  } catch (error) {
479
459
  errorMessage = error instanceof Error ? error.message : String(error);
480
- await connection_default`
460
+ await repositoryConnection`
481
461
  INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
482
462
  VALUES (
483
463
  ${webhook.id},
@@ -489,7 +469,7 @@ class DispatchWebhookJob extends Job {
489
469
  `;
490
470
  throw error instanceof Error ? error : new Error(errorMessage);
491
471
  }
492
- await connection_default`
472
+ await repositoryConnection`
493
473
  INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
494
474
  VALUES (
495
475
  ${webhook.id},
@@ -748,14 +728,6 @@ function appendWhereParts(tableName, where, params) {
748
728
  }
749
729
  return clauses.join(" AND ");
750
730
  }
751
- function buildWhereClause(tableName, where = {}) {
752
- const params = [];
753
- const body = appendWhereParts(tableName, where, params);
754
- return {
755
- clause: body.length > 0 ? ` WHERE ${body}` : "",
756
- params
757
- };
758
- }
759
731
  function buildWhereNodeClause(tableName, node, params) {
760
732
  if ("where" in node) {
761
733
  return appendWhereParts(tableName, node.where, params);
@@ -1058,30 +1030,6 @@ function buildDeleteByIdQuery(table, id) {
1058
1030
  }
1059
1031
 
1060
1032
  // ../../src/core/database/relationships.ts
1061
- function hasMany(definition) {
1062
- return {
1063
- type: "hasMany",
1064
- ...definition
1065
- };
1066
- }
1067
- function hasOne(definition) {
1068
- return {
1069
- type: "hasOne",
1070
- ...definition
1071
- };
1072
- }
1073
- function belongsTo(definition) {
1074
- return {
1075
- type: "belongsTo",
1076
- ...definition
1077
- };
1078
- }
1079
- function belongsToMany(definition) {
1080
- return {
1081
- type: "belongsToMany",
1082
- ...definition
1083
- };
1084
- }
1085
1033
  function indexHasManyRelation(parents, children, relation) {
1086
1034
  const groups = new Map;
1087
1035
  for (const parent of parents) {
@@ -1097,15 +1045,6 @@ function indexHasManyRelation(parents, children, relation) {
1097
1045
  }
1098
1046
  return groups;
1099
1047
  }
1100
- function indexHasOneRelation(parents, children, relation) {
1101
- const grouped = indexHasManyRelation(parents, children, relation);
1102
- const result = new Map;
1103
- for (const parent of parents) {
1104
- const matches = grouped.get(parent[relation.localKey]) ?? [];
1105
- result.set(parent[relation.localKey], matches[0]);
1106
- }
1107
- return result;
1108
- }
1109
1048
  function indexBelongsToRelation(children, parents, relation) {
1110
1049
  const parentsById = new Map;
1111
1050
  for (const parent of parents) {
@@ -1121,45 +1060,6 @@ function indexBelongsToRelation(children, parents, relation) {
1121
1060
  }
1122
1061
  return result;
1123
1062
  }
1124
- function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
1125
- const relatedById = new Map;
1126
- for (const related of relatedRows) {
1127
- relatedById.set(related[relation.relatedKey], related);
1128
- }
1129
- const groups = new Map;
1130
- for (const parent of parents) {
1131
- groups.set(parent[relation.parentKey], []);
1132
- }
1133
- for (const pivot of pivotRows) {
1134
- const parentId = pivot[relation.foreignPivotKey];
1135
- const relatedId = pivot[relation.relatedPivotKey];
1136
- const group = groups.get(parentId);
1137
- const related = relatedById.get(relatedId);
1138
- if (!group || !related) {
1139
- continue;
1140
- }
1141
- group.push(related);
1142
- }
1143
- return groups;
1144
- }
1145
- function morphMany(definition) {
1146
- return {
1147
- type: "morphMany",
1148
- ...definition
1149
- };
1150
- }
1151
- function morphOne(definition) {
1152
- return {
1153
- type: "morphOne",
1154
- ...definition
1155
- };
1156
- }
1157
- function morphTo(definition) {
1158
- return {
1159
- type: "morphTo",
1160
- ...definition
1161
- };
1162
- }
1163
1063
  function indexMorphManyRelation(parents, children, relation) {
1164
1064
  const groups = new Map;
1165
1065
  for (const parent of parents) {
@@ -1178,15 +1078,6 @@ function indexMorphManyRelation(parents, children, relation) {
1178
1078
  }
1179
1079
  return groups;
1180
1080
  }
1181
- function indexMorphOneRelation(parents, children, relation) {
1182
- const grouped = indexMorphManyRelation(parents, children, relation);
1183
- const result = new Map;
1184
- for (const parent of parents) {
1185
- const matches = grouped.get(parent[relation.localKey]) ?? [];
1186
- result.set(parent[relation.localKey], matches[0]);
1187
- }
1188
- return result;
1189
- }
1190
1081
  function indexMorphToRelation(children, parentsByType, relation) {
1191
1082
  const result = new Map;
1192
1083
  for (const child of children) {
@@ -1203,29 +1094,6 @@ function indexMorphToRelation(children, parentsByType, relation) {
1203
1094
  return result;
1204
1095
  }
1205
1096
 
1206
- // ../../src/core/database/boundConnection.ts
1207
- var boundConnectionHolder = {
1208
- connection: null
1209
- };
1210
- function bindDatabaseConnection(connection) {
1211
- boundConnectionHolder.connection = connection;
1212
- }
1213
- function getBoundDatabaseConnection() {
1214
- return boundConnectionHolder.connection;
1215
- }
1216
-
1217
- // ../../src/core/database/repositoryConnection.ts
1218
- function resolveRepositoryConnection() {
1219
- return getBoundDatabaseConnection() ?? connection_default;
1220
- }
1221
- var repositoryConnection = new Proxy({}, {
1222
- get(_target, property) {
1223
- const connection = resolveRepositoryConnection();
1224
- const value = connection[property];
1225
- return typeof value === "function" ? value.bind(connection) : value;
1226
- }
1227
- });
1228
-
1229
1097
  // ../../src/core/database/whereBuilder.ts
1230
1098
  class WhereBuilder {
1231
1099
  nodes = [];
@@ -1781,327 +1649,10 @@ class BaseRepository {
1781
1649
  }
1782
1650
  }
1783
1651
  var baseRepository_default = BaseRepository;
1784
- // ../../src/core/database/connection.ts
1785
- function createDatabaseConnection2(source) {
1786
- return {
1787
- async unsafe(query, params = []) {
1788
- return await source.unsafe(query, params);
1789
- }
1790
- };
1791
- }
1792
1652
  // ../../src/core/database/model.ts
1793
1653
  var modelRepositories = new WeakMap;
1794
1654
  var modelGlobalScopes = new WeakMap;
1795
1655
  var modelBooted = new WeakSet;
1796
- function resolveModelRepository(model) {
1797
- const repository = modelRepositories.get(model);
1798
- if (!repository) {
1799
- throw new Error(`${model.name}.repository() is not implemented.`);
1800
- }
1801
- return repository;
1802
- }
1803
- function modelStatics(model) {
1804
- return model;
1805
- }
1806
- function ensureBooted(model) {
1807
- if (modelBooted.has(model)) {
1808
- return;
1809
- }
1810
- modelBooted.add(model);
1811
- const boot = model.boot;
1812
- if (typeof boot === "function") {
1813
- boot.call(model);
1814
- }
1815
- }
1816
- function getGlobalScopes(model) {
1817
- return modelGlobalScopes.get(model) ?? [];
1818
- }
1819
- function hydrateValue(value, cast) {
1820
- if (value === null || value === undefined) {
1821
- return value;
1822
- }
1823
- switch (cast) {
1824
- case "date":
1825
- case "datetime":
1826
- return value instanceof Date ? value : new Date(String(value));
1827
- case "json":
1828
- return typeof value === "string" ? JSON.parse(value) : value;
1829
- case "bool":
1830
- case "boolean":
1831
- return value === true || value === 1 || value === "1" || value === "true";
1832
- default:
1833
- return value;
1834
- }
1835
- }
1836
- function dehydrateValue(value, cast) {
1837
- if (value === null || value === undefined) {
1838
- return value;
1839
- }
1840
- switch (cast) {
1841
- case "date":
1842
- case "datetime":
1843
- return value instanceof Date ? value : new Date(String(value));
1844
- case "json":
1845
- return typeof value === "string" ? value : JSON.stringify(value);
1846
- case "bool":
1847
- case "boolean":
1848
- return Boolean(value);
1849
- default:
1850
- return value;
1851
- }
1852
- }
1853
- function filterMassAssignable(fillable, guarded, input) {
1854
- const resolvedGuarded = guarded ?? true;
1855
- if (fillable && fillable.length > 0) {
1856
- const allowed = new Set(fillable);
1857
- return Object.fromEntries(Object.entries(input).filter(([key]) => allowed.has(key)));
1858
- }
1859
- if (resolvedGuarded === true || resolvedGuarded.includes("*")) {
1860
- return {};
1861
- }
1862
- const blocked = new Set(resolvedGuarded);
1863
- return Object.fromEntries(Object.entries(input).filter(([key]) => !blocked.has(key)));
1864
- }
1865
- function applyCasts(values, casts, direction) {
1866
- if (Object.keys(casts).length === 0) {
1867
- return values;
1868
- }
1869
- const result = { ...values };
1870
- const castFn = direction === "hydrate" ? hydrateValue : dehydrateValue;
1871
- for (const [key, cast] of Object.entries(casts)) {
1872
- if (key in result && cast) {
1873
- result[key] = castFn(result[key], cast);
1874
- }
1875
- }
1876
- return result;
1877
- }
1878
- function applyTimestampsOnCreate(columns, values, enabled) {
1879
- if (!enabled) {
1880
- return values;
1881
- }
1882
- const now = new Date;
1883
- const result = { ...values };
1884
- if (columns.includes("created_at")) {
1885
- result.created_at = now;
1886
- }
1887
- if (columns.includes("updated_at")) {
1888
- result.updated_at = now;
1889
- }
1890
- return result;
1891
- }
1892
- function applyTimestampsOnUpdate(columns, values, enabled) {
1893
- if (!enabled) {
1894
- return values;
1895
- }
1896
- const result = { ...values };
1897
- if (columns.includes("updated_at")) {
1898
- result.updated_at = new Date;
1899
- }
1900
- return result;
1901
- }
1902
-
1903
- class Model {
1904
- attributes;
1905
- repository;
1906
- static $fillable;
1907
- static $guarded;
1908
- static $casts = {};
1909
- static $timestamps = true;
1910
- _exists;
1911
- constructor(attributes, repository, exists = true) {
1912
- this.attributes = attributes;
1913
- this.repository = repository;
1914
- this._exists = exists;
1915
- }
1916
- get $exists() {
1917
- return this._exists;
1918
- }
1919
- get(key) {
1920
- return this.attributes[key];
1921
- }
1922
- get id() {
1923
- return this.attributes[this.primaryKey()];
1924
- }
1925
- toObject() {
1926
- return { ...this.attributes };
1927
- }
1928
- primaryKey() {
1929
- throw new Error(`${this.constructor.name}.primaryKey() is not implemented.`);
1930
- }
1931
- static primaryKeyField() {
1932
- return resolveModelRepository(this).getTable().primaryKey;
1933
- }
1934
- static hydrateAttributes(attributes) {
1935
- const casts = modelStatics(this).$casts ?? {};
1936
- return applyCasts(attributes, casts, "hydrate");
1937
- }
1938
- static dehydrateAttributes(attributes) {
1939
- const casts = modelStatics(this).$casts ?? {};
1940
- return applyCasts(attributes, casts, "dehydrate");
1941
- }
1942
- static fromRecord(record, repository, exists = true) {
1943
- const statics = modelStatics(this);
1944
- const hydrated = statics.hydrateAttributes(record);
1945
- return new statics(hydrated, repository, exists);
1946
- }
1947
- static boot() {}
1948
- static addGlobalScope(_name, scope) {
1949
- ensureBooted(this);
1950
- const existing = modelGlobalScopes.get(this) ?? [];
1951
- modelGlobalScopes.set(this, [
1952
- ...existing,
1953
- scope
1954
- ]);
1955
- }
1956
- static repository() {
1957
- return resolveModelRepository(this);
1958
- }
1959
- static query() {
1960
- ensureBooted(this);
1961
- const repository = resolveModelRepository(this);
1962
- let query = repository.query();
1963
- for (const scope of getGlobalScopes(this)) {
1964
- query = scope(query);
1965
- }
1966
- return query;
1967
- }
1968
- static async create(attributes) {
1969
- const statics = modelStatics(this);
1970
- ensureBooted(this);
1971
- const repository = resolveModelRepository(this);
1972
- const table = repository.getTable();
1973
- const timestamps = statics.$timestamps ?? true;
1974
- const assignable = filterMassAssignable(statics.$fillable, statics.$guarded, attributes);
1975
- const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
1976
- const payload = statics.dehydrateAttributes(withTimestamps);
1977
- const record = await repository.create(payload);
1978
- return statics.fromRecord(record, repository, true);
1979
- }
1980
- static async find(id) {
1981
- const statics = modelStatics(this);
1982
- const repository = resolveModelRepository(this);
1983
- const primaryKey = repository.getTable().primaryKey;
1984
- const record = await Model.query.call(this).where({ [primaryKey]: id }).first();
1985
- return record ? statics.fromRecord(record, repository, true) : null;
1986
- }
1987
- static async findOrFail(id, errorFactory) {
1988
- const model = await Model.find.call(this, id);
1989
- if (model) {
1990
- return model;
1991
- }
1992
- throw errorFactory?.(id) ?? new NotFoundError(`${this.name} ${String(id)} not found.`);
1993
- }
1994
- static async all(options = {}) {
1995
- const statics = modelStatics(this);
1996
- const repository = resolveModelRepository(this);
1997
- let query = Model.query.call(this);
1998
- if (options.orderBy) {
1999
- query = query.orderBy(options.orderBy);
2000
- }
2001
- if (options.limit !== undefined) {
2002
- query = query.limit(options.limit);
2003
- }
2004
- const rows = await query.get();
2005
- return rows.map((row) => statics.fromRecord(row, repository, true));
2006
- }
2007
- static async firstWhere(where, options = {}) {
2008
- const statics = modelStatics(this);
2009
- const repository = resolveModelRepository(this);
2010
- let query = Model.query.call(this).where(where);
2011
- if (options.orderBy) {
2012
- query = query.orderBy(options.orderBy);
2013
- }
2014
- const record = await query.first();
2015
- return record ? statics.fromRecord(record, repository, true) : null;
2016
- }
2017
- async save() {
2018
- const ModelClass = modelStatics(this.constructor);
2019
- const timestamps = ModelClass.$timestamps ?? true;
2020
- const casts = ModelClass.$casts ?? {};
2021
- const table = this.repository.getTable();
2022
- if (this.$exists) {
2023
- const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
2024
- const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
2025
- this.attributes = ModelClass.hydrateAttributes(record2);
2026
- return this;
2027
- }
2028
- const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
2029
- const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
2030
- const payload = ModelClass.dehydrateAttributes(withTimestamps);
2031
- const record = await this.repository.create(payload);
2032
- this.attributes = ModelClass.hydrateAttributes(record);
2033
- this._exists = true;
2034
- return this;
2035
- }
2036
- async update(changes) {
2037
- const ModelClass = modelStatics(this.constructor);
2038
- const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, changes);
2039
- Object.assign(this.attributes, assignable);
2040
- return await this.save();
2041
- }
2042
- async delete() {
2043
- if (resolveSoftDeleteColumn(this.repository.getTable())) {
2044
- return await this.repository.deleteById(this.id);
2045
- }
2046
- return await this.repository.forceDeleteById(this.id);
2047
- }
2048
- async forceDelete() {
2049
- return await this.repository.forceDeleteById(this.id);
2050
- }
2051
- async restore() {
2052
- const ModelClass = modelStatics(this.constructor);
2053
- const record = await this.repository.restoreById(this.id);
2054
- if (!record) {
2055
- return null;
2056
- }
2057
- this.attributes = ModelClass.hydrateAttributes(record);
2058
- return this;
2059
- }
2060
- async loadHasMany(as, relation, childRepository, options = {}) {
2061
- const grouped = await childRepository.withConnection(this.repository.getConnection()).loadHasManyForParents([this.attributes], relation, options);
2062
- const loaded = grouped.get(this.attributes[relation.localKey]) ?? [];
2063
- return Object.assign(this, { [as]: loaded });
2064
- }
2065
- async loadHasOne(as, relation, childRepository, options = {}) {
2066
- const loaded = await this.loadHasMany(as, relation, childRepository, { ...options, limit: 1 });
2067
- const value = loaded[as]?.[0];
2068
- return Object.assign(this, { [as]: value });
2069
- }
2070
- async loadBelongsTo(as, relation, parentRepository, options = {}) {
2071
- const grouped = await this.repository.loadBelongsToForParents([this.attributes], relation, parentRepository, options);
2072
- const loaded = grouped.get(this.attributes[relation.foreignKey]);
2073
- return Object.assign(this, { [as]: loaded });
2074
- }
2075
- async loadBelongsToMany(as, relation, relatedRepository, options = {}) {
2076
- const connection = this.repository.getConnection();
2077
- const parentId = this.attributes[relation.parentKey];
2078
- const pivotRows = await connection.unsafe(`SELECT * FROM ${relation.pivotTable} WHERE ${String(relation.foreignPivotKey)} = $1`, [parentId]);
2079
- if (pivotRows.length === 0) {
2080
- return Object.assign(this, { [as]: [] });
2081
- }
2082
- const relatedIds = [
2083
- ...new Set(pivotRows.map((row) => row[relation.relatedPivotKey]))
2084
- ];
2085
- const relatedRows = await relatedRepository.withConnection(connection).findAll({
2086
- ...options,
2087
- where: {
2088
- [relation.relatedKey]: relatedIds
2089
- }
2090
- });
2091
- const grouped = indexBelongsToManyRelation([this.attributes], pivotRows, relatedRows, relation);
2092
- const loaded = grouped.get(parentId) ?? [];
2093
- return Object.assign(this, { [as]: loaded });
2094
- }
2095
- mergeAttributes(patch) {
2096
- Object.assign(this.attributes, patch);
2097
- return this;
2098
- }
2099
- }
2100
- function registerModelRepository(model, repository) {
2101
- modelRepositories.set(model, repository);
2102
- ensureBooted(model);
2103
- return model;
2104
- }
2105
1656
  // ../../src/core/database/schema/columnDefinition.ts
2106
1657
  class ColumnDefinition {
2107
1658
  name;
@@ -2323,45 +1874,6 @@ class Blueprint {
2323
1874
  });
2324
1875
  }
2325
1876
  }
2326
- // ../../src/core/database/schema/driver.ts
2327
- function normalizeConnectionName(connection) {
2328
- const normalized = connection.trim().toLowerCase();
2329
- if (normalized === "pgsql" || normalized === "postgres" || normalized === "postgresql") {
2330
- return "pgsql";
2331
- }
2332
- if (normalized === "mysql" || normalized === "mariadb") {
2333
- return "mysql";
2334
- }
2335
- if (normalized === "sqlite") {
2336
- return "sqlite";
2337
- }
2338
- throw new Error(`Unsupported DB_CONNECTION: ${connection}`);
2339
- }
2340
- function resolveDriverFromUrl(url) {
2341
- const normalized = url.trim().toLowerCase();
2342
- if (normalized.startsWith("postgres://") || normalized.startsWith("postgresql://") || normalized.startsWith("postgres:")) {
2343
- return "pgsql";
2344
- }
2345
- if (normalized.startsWith("mysql://") || normalized.startsWith("mysql:")) {
2346
- return "mysql";
2347
- }
2348
- if (normalized.startsWith("sqlite:")) {
2349
- return "sqlite";
2350
- }
2351
- return null;
2352
- }
2353
- function resolveDatabaseDriver(options = {}) {
2354
- const connection = options.connection ?? process.env.DB_CONNECTION;
2355
- if (connection) {
2356
- return normalizeConnectionName(connection);
2357
- }
2358
- const url = options.url ?? process.env.DATABASE_URL ?? "";
2359
- const fromUrl = resolveDriverFromUrl(url);
2360
- if (fromUrl) {
2361
- return fromUrl;
2362
- }
2363
- return "pgsql";
2364
- }
2365
1877
  // ../../src/core/database/schema/errors.ts
2366
1878
  class UnsupportedSchemaFeatureError extends Error {
2367
1879
  constructor(feature, driver) {
@@ -2700,48 +2212,16 @@ class SchemaBuilder {
2700
2212
  toSql() {
2701
2213
  return [...this.#statements];
2702
2214
  }
2703
- async execute(db2) {
2215
+ async execute(db) {
2704
2216
  for (const statement of this.#statements) {
2705
- await db2.unsafe(statement);
2217
+ await db.unsafe(statement);
2706
2218
  }
2707
2219
  }
2708
2220
  }
2709
-
2710
- class Schema {
2711
- static builder(driver) {
2712
- return new SchemaBuilder(driver ?? resolveDatabaseDriver());
2713
- }
2714
- static async run(db2, driver, callback) {
2715
- const schema = Schema.builder(driver);
2716
- await callback(schema);
2717
- await schema.execute(db2);
2718
- }
2719
- }
2720
- function createSchemaBuilder(db2, driver) {
2721
- const builder = Schema.builder(driver);
2722
- return Object.assign(builder, {
2723
- async commit() {
2724
- await builder.execute(db2);
2725
- }
2726
- });
2727
- }
2728
2221
  // ../../src/core/database/table.ts
2729
2222
  function defineTable(definition) {
2730
2223
  return definition;
2731
2224
  }
2732
- // ../../src/core/database/transaction.ts
2733
- function supportsTransactions(connection) {
2734
- return typeof connection.begin === "function";
2735
- }
2736
- async function runInTransaction(operation) {
2737
- const pool = resolveRepositoryConnection();
2738
- if (!supportsTransactions(pool)) {
2739
- throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
2740
- }
2741
- return await pool.begin(async (transaction) => {
2742
- return await operation(createDatabaseConnection2(transaction));
2743
- });
2744
- }
2745
2225
  // ../../src/core/queue/failedJobTable.ts
2746
2226
  var failedJobTable = defineTable({
2747
2227
  name: "failed_job",