@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
@@ -36,9 +36,6 @@ class Logger {
36
36
  var appLogger = new Logger("app");
37
37
 
38
38
  // ../../src/bootstrap/config.ts
39
- var CORE_QUEUE_TOKEN = "core.queue";
40
- var CORE_POLICY_GATE_TOKEN = "core.policyGate";
41
- var CORE_AUTH_TOKEN = "core.auth";
42
39
  var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
43
40
  var DEFAULT_QUEUE_DRIVER = "sync";
44
41
 
@@ -113,15 +110,9 @@ function getRequiredDependency(dependencies, key) {
113
110
  }
114
111
  return dependency;
115
112
  }
116
- function resolveService(dependencies, token) {
117
- return dependencies.container.resolve(token);
118
- }
119
113
 
120
114
  // ../../src/bootstrap/applicationRegistry.ts
121
115
  var activeContext;
122
- function setActiveApplicationContext(context) {
123
- activeContext = context;
124
- }
125
116
  function requireActiveApplicationContext() {
126
117
  if (!activeContext) {
127
118
  throw new Error("The application context has not been bootstrapped.");
@@ -131,24 +122,6 @@ function requireActiveApplicationContext() {
131
122
  function resolveApplicationCache() {
132
123
  return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
133
124
  }
134
- function resolveApplicationQueue() {
135
- return requireActiveApplicationContext().container.resolve(CORE_QUEUE_TOKEN);
136
- }
137
- function resolveApplicationAuth() {
138
- return requireActiveApplicationContext().container.resolve(CORE_AUTH_TOKEN);
139
- }
140
- function resolveApplicationPolicyGate() {
141
- return requireActiveApplicationContext().container.resolve(CORE_POLICY_GATE_TOKEN);
142
- }
143
- function resolveApplicationConfig() {
144
- return requireActiveApplicationContext().config;
145
- }
146
- function resolveApplicationLogger() {
147
- return appLogger;
148
- }
149
- function resolveApplicationDependencies() {
150
- return requireActiveApplicationContext().dependencies;
151
- }
152
125
 
153
126
  // ../../src/core/jobs/dispatchWebhookJob.ts
154
127
  import { createHmac } from "crypto";
@@ -162,75 +135,77 @@ var appConfig = {
162
135
  apiPrefix: process.env.API_PREFIX ?? "/api/v1"
163
136
  };
164
137
 
165
- // ../../src/config/database.ts
166
- function readInteger(name, fallback) {
167
- const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
168
- return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
169
- }
170
- var databaseConfig = {
171
- url: process.env.DATABASE_URL ?? "",
172
- poolMax: readInteger("DB_POOL_MAX", 10),
173
- idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
174
- maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
175
- connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
138
+ // ../../src/core/database/boundConnection.ts
139
+ var boundConnectionHolder = {
140
+ connection: null
176
141
  };
142
+ function getBoundDatabaseConnection() {
143
+ return boundConnectionHolder.connection;
144
+ }
177
145
 
178
146
  // ../../src/core/database/connectionContext.ts
179
147
  import { AsyncLocalStorage } from "async_hooks";
180
148
  var activeConnection = new AsyncLocalStorage;
181
- function runWithDatabaseConnection(connection, callback) {
182
- return activeConnection.run(connection, callback);
183
- }
184
149
  function getActiveDatabaseConnection(fallback) {
185
150
  return activeConnection.getStore() ?? fallback;
186
151
  }
187
152
 
188
- // ../../src/db/connection/createConnection.ts
189
- var {SQL } = globalThis.Bun;
190
- function createDatabaseConnection(config) {
191
- if (!config.url) {
192
- throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
193
- }
194
- return new SQL({
195
- url: config.url,
196
- max: config.poolMax,
197
- idleTimeout: config.idleTimeoutSeconds,
198
- maxLifetime: config.maxLifetimeSeconds,
199
- connectionTimeout: config.connectionTimeoutSeconds
153
+ // ../../src/core/database/queryProxy.ts
154
+ var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
155
+ function createDatabaseQueryProxy(pool) {
156
+ function resolveDatabase() {
157
+ return getActiveDatabaseConnection(pool);
158
+ }
159
+ function resolveDatabaseForProperty(property) {
160
+ if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
161
+ return pool;
162
+ }
163
+ return resolveDatabase();
164
+ }
165
+ return new Proxy(function database() {}, {
166
+ apply(_target, _thisArg, args) {
167
+ return resolveDatabase()(...args);
168
+ },
169
+ get(_target, property) {
170
+ const connection = resolveDatabaseForProperty(property);
171
+ const value = connection[property];
172
+ return typeof value === "function" ? value.bind(connection) : value;
173
+ }
200
174
  });
201
175
  }
202
176
 
203
- // ../../src/db/connection/index.ts
204
- var connectionHolder = {
177
+ // ../../src/core/database/defaultConnection.ts
178
+ var defaultPool = {
205
179
  connection: null
206
180
  };
207
- function getDatabase() {
208
- if (!connectionHolder.connection) {
209
- connectionHolder.connection = createDatabaseConnection(databaseConfig);
210
- }
211
- return connectionHolder.connection;
212
- }
213
- var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
214
- function resolveDatabase() {
215
- return getActiveDatabaseConnection(getDatabase());
181
+ var defaultQuery = {
182
+ connection: null
183
+ };
184
+ function registerDefaultDatabasePool(connection) {
185
+ defaultPool.connection = connection;
186
+ defaultQuery.connection = createDatabaseQueryProxy(connection);
216
187
  }
217
- function resolveDatabaseForProperty(property) {
218
- if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
219
- return getDatabase();
188
+ function getDefaultDatabaseQuery() {
189
+ if (!defaultQuery.connection) {
190
+ throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
220
191
  }
221
- return resolveDatabase();
192
+ return defaultQuery.connection;
222
193
  }
223
- var db = new Proxy(function database() {}, {
194
+
195
+ // ../../src/core/database/repositoryConnection.ts
196
+ function resolveRepositoryConnection() {
197
+ return getBoundDatabaseConnection() ?? getDefaultDatabaseQuery();
198
+ }
199
+ var repositoryConnection = new Proxy(function repositoryConnection2() {}, {
224
200
  apply(_target, _thisArg, args) {
225
- return resolveDatabase()(...args);
201
+ return resolveRepositoryConnection()(...args);
226
202
  },
227
203
  get(_target, property) {
228
- const connection = resolveDatabaseForProperty(property);
204
+ const connection = resolveRepositoryConnection();
229
205
  const value = connection[property];
230
206
  return typeof value === "function" ? value.bind(connection) : value;
231
207
  }
232
208
  });
233
- var connection_default = db;
234
209
 
235
210
  // ../../src/core/queue/index.ts
236
211
  class Job {
@@ -239,56 +214,8 @@ class Job {
239
214
  priority;
240
215
  }
241
216
 
242
- class SyncQueue {
243
- async dispatch(job, payload) {
244
- await job.handle(payload);
245
- }
246
- }
247
-
248
- class AsyncQueue {
249
- async dispatch(job, payload) {
250
- setTimeout(() => {
251
- job.handle(payload).catch((error) => {
252
- console.error("[AsyncQueue] Job failed:", error);
253
- });
254
- }, 0);
255
- }
256
- }
257
- function createQueue(driver) {
258
- return driver === "async" ? new AsyncQueue : new SyncQueue;
259
- }
260
-
261
- // ../../src/core/security/safeFetch.ts
262
- var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
263
- async function safeFetch(input, init = {}, options = {}) {
264
- const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
265
- const maxRedirects = options.maxRedirects ?? 0;
266
- const controller = new AbortController;
267
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
268
- try {
269
- let currentUrl = input;
270
- let redirectCount = 0;
271
- while (true) {
272
- const response = await fetch(currentUrl, {
273
- ...init,
274
- signal: controller.signal,
275
- redirect: "manual"
276
- });
277
- if (response.status >= 300 && response.status < 400) {
278
- const location = response.headers.get("location");
279
- if (!location || redirectCount >= maxRedirects) {
280
- return response;
281
- }
282
- currentUrl = new URL(location, currentUrl).toString();
283
- redirectCount += 1;
284
- continue;
285
- }
286
- return response;
287
- }
288
- } finally {
289
- clearTimeout(timeout);
290
- }
291
- }
217
+ // ../../src/core/security/safeUrl.ts
218
+ import { lookup as dnsLookupImpl } from "dns/promises";
292
219
 
293
220
  // ../../src/core/errors/http.ts
294
221
  class HttpError extends Error {
@@ -357,6 +284,7 @@ class PreconditionFailedError extends HttpError {
357
284
  }
358
285
 
359
286
  // ../../src/core/security/safeUrl.ts
287
+ var dnsLookup = dnsLookupImpl;
360
288
  var BLOCKED_HOSTNAMES = new Set([
361
289
  "localhost",
362
290
  "127.0.0.1",
@@ -428,16 +356,68 @@ function assertSafeOutboundUrl(rawUrl, options = {}) {
428
356
  }
429
357
  return parsed;
430
358
  }
359
+ function isBlockedIpAddress(address) {
360
+ return isBlockedHostname(address.trim().toLowerCase());
361
+ }
362
+ async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
363
+ const parsed = assertSafeOutboundUrl(rawUrl, options);
364
+ if (options.resolveDns === false) {
365
+ return parsed;
366
+ }
367
+ const hostname = parsed.hostname.trim().toLowerCase();
368
+ const results = await dnsLookup(hostname, { all: true, verbatim: true });
369
+ if (results.some((result) => isBlockedIpAddress(result.address))) {
370
+ throw new BadRequestError("Webhook URL targets a blocked host.");
371
+ }
372
+ return parsed;
373
+ }
374
+ function setDnsLookupForTests(lookupFn) {
375
+ dnsLookup = lookupFn;
376
+ }
377
+ function resetDnsLookupForTests() {
378
+ dnsLookup = dnsLookupImpl;
379
+ }
380
+
381
+ // ../../src/core/security/safeFetch.ts
382
+ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
383
+ async function safeFetch(input, init = {}, options = {}) {
384
+ const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
385
+ const maxRedirects = options.maxRedirects ?? 0;
386
+ const resolveDns = options.resolveDns ?? appConfig.env === "production";
387
+ const urlOptions = { allowHttp: options.allowHttp, resolveDns };
388
+ const controller = new AbortController;
389
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
390
+ try {
391
+ let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
392
+ let redirectCount = 0;
393
+ while (true) {
394
+ const response = await fetch(currentUrl, {
395
+ ...init,
396
+ signal: controller.signal,
397
+ redirect: "manual"
398
+ });
399
+ if (response.status >= 300 && response.status < 400) {
400
+ const location = response.headers.get("location");
401
+ if (!location || redirectCount >= maxRedirects) {
402
+ return response;
403
+ }
404
+ currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
405
+ redirectCount += 1;
406
+ continue;
407
+ }
408
+ return response;
409
+ }
410
+ } finally {
411
+ clearTimeout(timeout);
412
+ }
413
+ }
431
414
 
432
415
  // ../../src/core/jobs/dispatchWebhookJob.ts
433
416
  class DispatchWebhookJob extends Job {
434
- constructor() {
435
- super();
436
- }
437
417
  maxAttempts = 3;
438
418
  backoffMs = 2000;
439
419
  async handle(payload) {
440
- const rows = await connection_default`
420
+ const rows = await repositoryConnection`
441
421
  SELECT id, url, secret
442
422
  FROM webhook
443
423
  WHERE id = ${payload.webhookId} AND active = TRUE
@@ -460,14 +440,14 @@ class DispatchWebhookJob extends Job {
460
440
  "x-workhub-signature": signature
461
441
  },
462
442
  body
463
- });
443
+ }, { allowHttp: appConfig.env !== "production" });
464
444
  responseStatus = response.status;
465
445
  if (!response.ok) {
466
446
  throw new Error(`Webhook delivery failed with status ${response.status}.`);
467
447
  }
468
448
  } catch (error) {
469
449
  errorMessage = error instanceof Error ? error.message : String(error);
470
- await connection_default`
450
+ await repositoryConnection`
471
451
  INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
472
452
  VALUES (
473
453
  ${webhook.id},
@@ -479,7 +459,7 @@ class DispatchWebhookJob extends Job {
479
459
  `;
480
460
  throw error instanceof Error ? error : new Error(errorMessage);
481
461
  }
482
- await connection_default`
462
+ await repositoryConnection`
483
463
  INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
484
464
  VALUES (
485
465
  ${webhook.id},
@@ -738,14 +718,6 @@ function appendWhereParts(tableName, where, params) {
738
718
  }
739
719
  return clauses.join(" AND ");
740
720
  }
741
- function buildWhereClause(tableName, where = {}) {
742
- const params = [];
743
- const body = appendWhereParts(tableName, where, params);
744
- return {
745
- clause: body.length > 0 ? ` WHERE ${body}` : "",
746
- params
747
- };
748
- }
749
721
  function buildWhereNodeClause(tableName, node, params) {
750
722
  if ("where" in node) {
751
723
  return appendWhereParts(tableName, node.where, params);
@@ -1048,30 +1020,6 @@ function buildDeleteByIdQuery(table, id) {
1048
1020
  }
1049
1021
 
1050
1022
  // ../../src/core/database/relationships.ts
1051
- function hasMany(definition) {
1052
- return {
1053
- type: "hasMany",
1054
- ...definition
1055
- };
1056
- }
1057
- function hasOne(definition) {
1058
- return {
1059
- type: "hasOne",
1060
- ...definition
1061
- };
1062
- }
1063
- function belongsTo(definition) {
1064
- return {
1065
- type: "belongsTo",
1066
- ...definition
1067
- };
1068
- }
1069
- function belongsToMany(definition) {
1070
- return {
1071
- type: "belongsToMany",
1072
- ...definition
1073
- };
1074
- }
1075
1023
  function indexHasManyRelation(parents, children, relation) {
1076
1024
  const groups = new Map;
1077
1025
  for (const parent of parents) {
@@ -1087,15 +1035,6 @@ function indexHasManyRelation(parents, children, relation) {
1087
1035
  }
1088
1036
  return groups;
1089
1037
  }
1090
- function indexHasOneRelation(parents, children, relation) {
1091
- const grouped = indexHasManyRelation(parents, children, relation);
1092
- const result = new Map;
1093
- for (const parent of parents) {
1094
- const matches = grouped.get(parent[relation.localKey]) ?? [];
1095
- result.set(parent[relation.localKey], matches[0]);
1096
- }
1097
- return result;
1098
- }
1099
1038
  function indexBelongsToRelation(children, parents, relation) {
1100
1039
  const parentsById = new Map;
1101
1040
  for (const parent of parents) {
@@ -1111,45 +1050,6 @@ function indexBelongsToRelation(children, parents, relation) {
1111
1050
  }
1112
1051
  return result;
1113
1052
  }
1114
- function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
1115
- const relatedById = new Map;
1116
- for (const related of relatedRows) {
1117
- relatedById.set(related[relation.relatedKey], related);
1118
- }
1119
- const groups = new Map;
1120
- for (const parent of parents) {
1121
- groups.set(parent[relation.parentKey], []);
1122
- }
1123
- for (const pivot of pivotRows) {
1124
- const parentId = pivot[relation.foreignPivotKey];
1125
- const relatedId = pivot[relation.relatedPivotKey];
1126
- const group = groups.get(parentId);
1127
- const related = relatedById.get(relatedId);
1128
- if (!group || !related) {
1129
- continue;
1130
- }
1131
- group.push(related);
1132
- }
1133
- return groups;
1134
- }
1135
- function morphMany(definition) {
1136
- return {
1137
- type: "morphMany",
1138
- ...definition
1139
- };
1140
- }
1141
- function morphOne(definition) {
1142
- return {
1143
- type: "morphOne",
1144
- ...definition
1145
- };
1146
- }
1147
- function morphTo(definition) {
1148
- return {
1149
- type: "morphTo",
1150
- ...definition
1151
- };
1152
- }
1153
1053
  function indexMorphManyRelation(parents, children, relation) {
1154
1054
  const groups = new Map;
1155
1055
  for (const parent of parents) {
@@ -1168,15 +1068,6 @@ function indexMorphManyRelation(parents, children, relation) {
1168
1068
  }
1169
1069
  return groups;
1170
1070
  }
1171
- function indexMorphOneRelation(parents, children, relation) {
1172
- const grouped = indexMorphManyRelation(parents, children, relation);
1173
- const result = new Map;
1174
- for (const parent of parents) {
1175
- const matches = grouped.get(parent[relation.localKey]) ?? [];
1176
- result.set(parent[relation.localKey], matches[0]);
1177
- }
1178
- return result;
1179
- }
1180
1071
  function indexMorphToRelation(children, parentsByType, relation) {
1181
1072
  const result = new Map;
1182
1073
  for (const child of children) {
@@ -1193,29 +1084,6 @@ function indexMorphToRelation(children, parentsByType, relation) {
1193
1084
  return result;
1194
1085
  }
1195
1086
 
1196
- // ../../src/core/database/boundConnection.ts
1197
- var boundConnectionHolder = {
1198
- connection: null
1199
- };
1200
- function bindDatabaseConnection(connection) {
1201
- boundConnectionHolder.connection = connection;
1202
- }
1203
- function getBoundDatabaseConnection() {
1204
- return boundConnectionHolder.connection;
1205
- }
1206
-
1207
- // ../../src/core/database/repositoryConnection.ts
1208
- function resolveRepositoryConnection() {
1209
- return getBoundDatabaseConnection() ?? connection_default;
1210
- }
1211
- var repositoryConnection = new Proxy({}, {
1212
- get(_target, property) {
1213
- const connection = resolveRepositoryConnection();
1214
- const value = connection[property];
1215
- return typeof value === "function" ? value.bind(connection) : value;
1216
- }
1217
- });
1218
-
1219
1087
  // ../../src/core/database/whereBuilder.ts
1220
1088
  class WhereBuilder {
1221
1089
  nodes = [];
@@ -1771,327 +1639,10 @@ class BaseRepository {
1771
1639
  }
1772
1640
  }
1773
1641
  var baseRepository_default = BaseRepository;
1774
- // ../../src/core/database/connection.ts
1775
- function createDatabaseConnection2(source) {
1776
- return {
1777
- async unsafe(query, params = []) {
1778
- return await source.unsafe(query, params);
1779
- }
1780
- };
1781
- }
1782
1642
  // ../../src/core/database/model.ts
1783
1643
  var modelRepositories = new WeakMap;
1784
1644
  var modelGlobalScopes = new WeakMap;
1785
1645
  var modelBooted = new WeakSet;
1786
- function resolveModelRepository(model) {
1787
- const repository = modelRepositories.get(model);
1788
- if (!repository) {
1789
- throw new Error(`${model.name}.repository() is not implemented.`);
1790
- }
1791
- return repository;
1792
- }
1793
- function modelStatics(model) {
1794
- return model;
1795
- }
1796
- function ensureBooted(model) {
1797
- if (modelBooted.has(model)) {
1798
- return;
1799
- }
1800
- modelBooted.add(model);
1801
- const boot = model.boot;
1802
- if (typeof boot === "function") {
1803
- boot.call(model);
1804
- }
1805
- }
1806
- function getGlobalScopes(model) {
1807
- return modelGlobalScopes.get(model) ?? [];
1808
- }
1809
- function hydrateValue(value, cast) {
1810
- if (value === null || value === undefined) {
1811
- return value;
1812
- }
1813
- switch (cast) {
1814
- case "date":
1815
- case "datetime":
1816
- return value instanceof Date ? value : new Date(String(value));
1817
- case "json":
1818
- return typeof value === "string" ? JSON.parse(value) : value;
1819
- case "bool":
1820
- case "boolean":
1821
- return value === true || value === 1 || value === "1" || value === "true";
1822
- default:
1823
- return value;
1824
- }
1825
- }
1826
- function dehydrateValue(value, cast) {
1827
- if (value === null || value === undefined) {
1828
- return value;
1829
- }
1830
- switch (cast) {
1831
- case "date":
1832
- case "datetime":
1833
- return value instanceof Date ? value : new Date(String(value));
1834
- case "json":
1835
- return typeof value === "string" ? value : JSON.stringify(value);
1836
- case "bool":
1837
- case "boolean":
1838
- return Boolean(value);
1839
- default:
1840
- return value;
1841
- }
1842
- }
1843
- function filterMassAssignable(fillable, guarded, input) {
1844
- const resolvedGuarded = guarded ?? true;
1845
- if (fillable && fillable.length > 0) {
1846
- const allowed = new Set(fillable);
1847
- return Object.fromEntries(Object.entries(input).filter(([key]) => allowed.has(key)));
1848
- }
1849
- if (resolvedGuarded === true || resolvedGuarded.includes("*")) {
1850
- return {};
1851
- }
1852
- const blocked = new Set(resolvedGuarded);
1853
- return Object.fromEntries(Object.entries(input).filter(([key]) => !blocked.has(key)));
1854
- }
1855
- function applyCasts(values, casts, direction) {
1856
- if (Object.keys(casts).length === 0) {
1857
- return values;
1858
- }
1859
- const result = { ...values };
1860
- const castFn = direction === "hydrate" ? hydrateValue : dehydrateValue;
1861
- for (const [key, cast] of Object.entries(casts)) {
1862
- if (key in result && cast) {
1863
- result[key] = castFn(result[key], cast);
1864
- }
1865
- }
1866
- return result;
1867
- }
1868
- function applyTimestampsOnCreate(columns, values, enabled) {
1869
- if (!enabled) {
1870
- return values;
1871
- }
1872
- const now = new Date;
1873
- const result = { ...values };
1874
- if (columns.includes("created_at")) {
1875
- result.created_at = now;
1876
- }
1877
- if (columns.includes("updated_at")) {
1878
- result.updated_at = now;
1879
- }
1880
- return result;
1881
- }
1882
- function applyTimestampsOnUpdate(columns, values, enabled) {
1883
- if (!enabled) {
1884
- return values;
1885
- }
1886
- const result = { ...values };
1887
- if (columns.includes("updated_at")) {
1888
- result.updated_at = new Date;
1889
- }
1890
- return result;
1891
- }
1892
-
1893
- class Model {
1894
- attributes;
1895
- repository;
1896
- static $fillable;
1897
- static $guarded;
1898
- static $casts = {};
1899
- static $timestamps = true;
1900
- _exists;
1901
- constructor(attributes, repository, exists = true) {
1902
- this.attributes = attributes;
1903
- this.repository = repository;
1904
- this._exists = exists;
1905
- }
1906
- get $exists() {
1907
- return this._exists;
1908
- }
1909
- get(key) {
1910
- return this.attributes[key];
1911
- }
1912
- get id() {
1913
- return this.attributes[this.primaryKey()];
1914
- }
1915
- toObject() {
1916
- return { ...this.attributes };
1917
- }
1918
- primaryKey() {
1919
- throw new Error(`${this.constructor.name}.primaryKey() is not implemented.`);
1920
- }
1921
- static primaryKeyField() {
1922
- return resolveModelRepository(this).getTable().primaryKey;
1923
- }
1924
- static hydrateAttributes(attributes) {
1925
- const casts = modelStatics(this).$casts ?? {};
1926
- return applyCasts(attributes, casts, "hydrate");
1927
- }
1928
- static dehydrateAttributes(attributes) {
1929
- const casts = modelStatics(this).$casts ?? {};
1930
- return applyCasts(attributes, casts, "dehydrate");
1931
- }
1932
- static fromRecord(record, repository, exists = true) {
1933
- const statics = modelStatics(this);
1934
- const hydrated = statics.hydrateAttributes(record);
1935
- return new statics(hydrated, repository, exists);
1936
- }
1937
- static boot() {}
1938
- static addGlobalScope(_name, scope) {
1939
- ensureBooted(this);
1940
- const existing = modelGlobalScopes.get(this) ?? [];
1941
- modelGlobalScopes.set(this, [
1942
- ...existing,
1943
- scope
1944
- ]);
1945
- }
1946
- static repository() {
1947
- return resolveModelRepository(this);
1948
- }
1949
- static query() {
1950
- ensureBooted(this);
1951
- const repository = resolveModelRepository(this);
1952
- let query = repository.query();
1953
- for (const scope of getGlobalScopes(this)) {
1954
- query = scope(query);
1955
- }
1956
- return query;
1957
- }
1958
- static async create(attributes) {
1959
- const statics = modelStatics(this);
1960
- ensureBooted(this);
1961
- const repository = resolveModelRepository(this);
1962
- const table = repository.getTable();
1963
- const timestamps = statics.$timestamps ?? true;
1964
- const assignable = filterMassAssignable(statics.$fillable, statics.$guarded, attributes);
1965
- const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
1966
- const payload = statics.dehydrateAttributes(withTimestamps);
1967
- const record = await repository.create(payload);
1968
- return statics.fromRecord(record, repository, true);
1969
- }
1970
- static async find(id) {
1971
- const statics = modelStatics(this);
1972
- const repository = resolveModelRepository(this);
1973
- const primaryKey = repository.getTable().primaryKey;
1974
- const record = await Model.query.call(this).where({ [primaryKey]: id }).first();
1975
- return record ? statics.fromRecord(record, repository, true) : null;
1976
- }
1977
- static async findOrFail(id, errorFactory) {
1978
- const model = await Model.find.call(this, id);
1979
- if (model) {
1980
- return model;
1981
- }
1982
- throw errorFactory?.(id) ?? new NotFoundError(`${this.name} ${String(id)} not found.`);
1983
- }
1984
- static async all(options = {}) {
1985
- const statics = modelStatics(this);
1986
- const repository = resolveModelRepository(this);
1987
- let query = Model.query.call(this);
1988
- if (options.orderBy) {
1989
- query = query.orderBy(options.orderBy);
1990
- }
1991
- if (options.limit !== undefined) {
1992
- query = query.limit(options.limit);
1993
- }
1994
- const rows = await query.get();
1995
- return rows.map((row) => statics.fromRecord(row, repository, true));
1996
- }
1997
- static async firstWhere(where, options = {}) {
1998
- const statics = modelStatics(this);
1999
- const repository = resolveModelRepository(this);
2000
- let query = Model.query.call(this).where(where);
2001
- if (options.orderBy) {
2002
- query = query.orderBy(options.orderBy);
2003
- }
2004
- const record = await query.first();
2005
- return record ? statics.fromRecord(record, repository, true) : null;
2006
- }
2007
- async save() {
2008
- const ModelClass = modelStatics(this.constructor);
2009
- const timestamps = ModelClass.$timestamps ?? true;
2010
- const casts = ModelClass.$casts ?? {};
2011
- const table = this.repository.getTable();
2012
- if (this.$exists) {
2013
- const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
2014
- const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
2015
- this.attributes = ModelClass.hydrateAttributes(record2);
2016
- return this;
2017
- }
2018
- const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
2019
- const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
2020
- const payload = ModelClass.dehydrateAttributes(withTimestamps);
2021
- const record = await this.repository.create(payload);
2022
- this.attributes = ModelClass.hydrateAttributes(record);
2023
- this._exists = true;
2024
- return this;
2025
- }
2026
- async update(changes) {
2027
- const ModelClass = modelStatics(this.constructor);
2028
- const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, changes);
2029
- Object.assign(this.attributes, assignable);
2030
- return await this.save();
2031
- }
2032
- async delete() {
2033
- if (resolveSoftDeleteColumn(this.repository.getTable())) {
2034
- return await this.repository.deleteById(this.id);
2035
- }
2036
- return await this.repository.forceDeleteById(this.id);
2037
- }
2038
- async forceDelete() {
2039
- return await this.repository.forceDeleteById(this.id);
2040
- }
2041
- async restore() {
2042
- const ModelClass = modelStatics(this.constructor);
2043
- const record = await this.repository.restoreById(this.id);
2044
- if (!record) {
2045
- return null;
2046
- }
2047
- this.attributes = ModelClass.hydrateAttributes(record);
2048
- return this;
2049
- }
2050
- async loadHasMany(as, relation, childRepository, options = {}) {
2051
- const grouped = await childRepository.withConnection(this.repository.getConnection()).loadHasManyForParents([this.attributes], relation, options);
2052
- const loaded = grouped.get(this.attributes[relation.localKey]) ?? [];
2053
- return Object.assign(this, { [as]: loaded });
2054
- }
2055
- async loadHasOne(as, relation, childRepository, options = {}) {
2056
- const loaded = await this.loadHasMany(as, relation, childRepository, { ...options, limit: 1 });
2057
- const value = loaded[as]?.[0];
2058
- return Object.assign(this, { [as]: value });
2059
- }
2060
- async loadBelongsTo(as, relation, parentRepository, options = {}) {
2061
- const grouped = await this.repository.loadBelongsToForParents([this.attributes], relation, parentRepository, options);
2062
- const loaded = grouped.get(this.attributes[relation.foreignKey]);
2063
- return Object.assign(this, { [as]: loaded });
2064
- }
2065
- async loadBelongsToMany(as, relation, relatedRepository, options = {}) {
2066
- const connection = this.repository.getConnection();
2067
- const parentId = this.attributes[relation.parentKey];
2068
- const pivotRows = await connection.unsafe(`SELECT * FROM ${relation.pivotTable} WHERE ${String(relation.foreignPivotKey)} = $1`, [parentId]);
2069
- if (pivotRows.length === 0) {
2070
- return Object.assign(this, { [as]: [] });
2071
- }
2072
- const relatedIds = [
2073
- ...new Set(pivotRows.map((row) => row[relation.relatedPivotKey]))
2074
- ];
2075
- const relatedRows = await relatedRepository.withConnection(connection).findAll({
2076
- ...options,
2077
- where: {
2078
- [relation.relatedKey]: relatedIds
2079
- }
2080
- });
2081
- const grouped = indexBelongsToManyRelation([this.attributes], pivotRows, relatedRows, relation);
2082
- const loaded = grouped.get(parentId) ?? [];
2083
- return Object.assign(this, { [as]: loaded });
2084
- }
2085
- mergeAttributes(patch) {
2086
- Object.assign(this.attributes, patch);
2087
- return this;
2088
- }
2089
- }
2090
- function registerModelRepository(model, repository) {
2091
- modelRepositories.set(model, repository);
2092
- ensureBooted(model);
2093
- return model;
2094
- }
2095
1646
  // ../../src/core/database/schema/columnDefinition.ts
2096
1647
  class ColumnDefinition {
2097
1648
  name;
@@ -2313,45 +1864,6 @@ class Blueprint {
2313
1864
  });
2314
1865
  }
2315
1866
  }
2316
- // ../../src/core/database/schema/driver.ts
2317
- function normalizeConnectionName(connection) {
2318
- const normalized = connection.trim().toLowerCase();
2319
- if (normalized === "pgsql" || normalized === "postgres" || normalized === "postgresql") {
2320
- return "pgsql";
2321
- }
2322
- if (normalized === "mysql" || normalized === "mariadb") {
2323
- return "mysql";
2324
- }
2325
- if (normalized === "sqlite") {
2326
- return "sqlite";
2327
- }
2328
- throw new Error(`Unsupported DB_CONNECTION: ${connection}`);
2329
- }
2330
- function resolveDriverFromUrl(url) {
2331
- const normalized = url.trim().toLowerCase();
2332
- if (normalized.startsWith("postgres://") || normalized.startsWith("postgresql://") || normalized.startsWith("postgres:")) {
2333
- return "pgsql";
2334
- }
2335
- if (normalized.startsWith("mysql://") || normalized.startsWith("mysql:")) {
2336
- return "mysql";
2337
- }
2338
- if (normalized.startsWith("sqlite:")) {
2339
- return "sqlite";
2340
- }
2341
- return null;
2342
- }
2343
- function resolveDatabaseDriver(options = {}) {
2344
- const connection = options.connection ?? process.env.DB_CONNECTION;
2345
- if (connection) {
2346
- return normalizeConnectionName(connection);
2347
- }
2348
- const url = options.url ?? process.env.DATABASE_URL ?? "";
2349
- const fromUrl = resolveDriverFromUrl(url);
2350
- if (fromUrl) {
2351
- return fromUrl;
2352
- }
2353
- return "pgsql";
2354
- }
2355
1867
  // ../../src/core/database/schema/errors.ts
2356
1868
  class UnsupportedSchemaFeatureError extends Error {
2357
1869
  constructor(feature, driver) {
@@ -2690,48 +2202,16 @@ class SchemaBuilder {
2690
2202
  toSql() {
2691
2203
  return [...this.#statements];
2692
2204
  }
2693
- async execute(db2) {
2205
+ async execute(db) {
2694
2206
  for (const statement of this.#statements) {
2695
- await db2.unsafe(statement);
2207
+ await db.unsafe(statement);
2696
2208
  }
2697
2209
  }
2698
2210
  }
2699
-
2700
- class Schema {
2701
- static builder(driver) {
2702
- return new SchemaBuilder(driver ?? resolveDatabaseDriver());
2703
- }
2704
- static async run(db2, driver, callback) {
2705
- const schema = Schema.builder(driver);
2706
- await callback(schema);
2707
- await schema.execute(db2);
2708
- }
2709
- }
2710
- function createSchemaBuilder(db2, driver) {
2711
- const builder = Schema.builder(driver);
2712
- return Object.assign(builder, {
2713
- async commit() {
2714
- await builder.execute(db2);
2715
- }
2716
- });
2717
- }
2718
2211
  // ../../src/core/database/table.ts
2719
2212
  function defineTable(definition) {
2720
2213
  return definition;
2721
2214
  }
2722
- // ../../src/core/database/transaction.ts
2723
- function supportsTransactions(connection) {
2724
- return typeof connection.begin === "function";
2725
- }
2726
- async function runInTransaction(operation) {
2727
- const pool = resolveRepositoryConnection();
2728
- if (!supportsTransactions(pool)) {
2729
- throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
2730
- }
2731
- return await pool.begin(async (transaction) => {
2732
- return await operation(createDatabaseConnection2(transaction));
2733
- });
2734
- }
2735
2215
  // ../../src/core/queue/failedJobTable.ts
2736
2216
  var failedJobTable = defineTable({
2737
2217
  name: "failed_job",