@getstrata/core 0.5.41 → 0.5.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/database/baseRepository.d.ts +1 -0
- package/dist/core/queue/failedJobRepository.d.ts +1 -0
- package/dist/entries/admin/formatValue.js +32 -0
- package/dist/entries/admin/registry.js +32 -0
- package/dist/entries/audit/exportAuditLogs.js +18 -0
- package/dist/entries/audit/siemFormatter.js +37 -0
- package/dist/entries/auth/scimAuthMiddleware.js +241 -0
- package/dist/entries/auth/sessionGuard.js +501 -0
- package/dist/entries/database/baseRepository.js +1388 -0
- package/dist/entries/database/bindConnection.js +22 -0
- package/dist/entries/database/boundConnection.js +19 -0
- package/dist/entries/database/connection.js +12 -0
- package/dist/entries/database/errors.js +128 -0
- package/dist/entries/database/model.js +948 -0
- package/dist/entries/database/query.js +436 -0
- package/dist/entries/database/relationships.js +162 -0
- package/dist/entries/database/schema.js +1054 -0
- package/dist/entries/database/table.js +8 -0
- package/dist/entries/database/transaction.js +129 -0
- package/dist/entries/http/authMiddleware.js +47 -0
- package/dist/entries/http/authorizeMiddleware.js +104 -0
- package/dist/entries/http/metricsMiddleware.js +91 -0
- package/dist/entries/http/parseMultipartUpload.js +144 -0
- package/dist/entries/http/securedRouteModelBinding.js +6 -0
- package/dist/entries/http/webErrorResponse.js +501 -0
- package/dist/entries/http/webFormRequest.js +6 -0
- package/dist/entries/jobs/dispatchWebhookJob.js +18 -0
- package/dist/entries/mail/mailer.js +208 -0
- package/dist/entries/mail/markdownMail.js +63 -0
- package/dist/entries/mail/markdownMailable.js +78 -0
- package/dist/entries/notifications.js +152 -0
- package/dist/entries/openapi/generator.js +178 -0
- package/dist/entries/openapi/validate.js +28 -0
- package/dist/entries/queue/createAppQueue.js +507 -0
- package/dist/entries/queue/failedJobRepository.js +2364 -0
- package/dist/entries/queue/publicQueue.js +507 -0
- package/dist/entries/queue/queueMetrics.js +507 -0
- package/dist/entries/queue/redisQueue.js +232 -0
- package/dist/entries/runtime/asyncContextStore.js +17 -0
- package/dist/entries/security/safeFetch.js +211 -0
- package/dist/entries/security/scimTenantTokens.js +51 -0
- package/dist/entries/security/timingSafeCompare.js +14 -0
- package/dist/entries/tenant/databaseTenantContext.js +116 -0
- package/dist/entries/tenant/tenantDatabaseScope.js +113 -0
- package/dist/entries/view.js +501 -0
- package/package.json +167 -2
|
@@ -498,6 +498,14 @@ function appendWhereParts(tableName, where, params) {
|
|
|
498
498
|
}
|
|
499
499
|
return clauses.join(" AND ");
|
|
500
500
|
}
|
|
501
|
+
function buildWhereClause(tableName, where = {}) {
|
|
502
|
+
const params = [];
|
|
503
|
+
const body = appendWhereParts(tableName, where, params);
|
|
504
|
+
return {
|
|
505
|
+
clause: body.length > 0 ? ` WHERE ${body}` : "",
|
|
506
|
+
params
|
|
507
|
+
};
|
|
508
|
+
}
|
|
501
509
|
function buildWhereNodeClause(tableName, node, params) {
|
|
502
510
|
if ("where" in node) {
|
|
503
511
|
return appendWhereParts(tableName, node.where, params);
|
|
@@ -800,6 +808,30 @@ function buildDeleteByIdQuery(table, id) {
|
|
|
800
808
|
}
|
|
801
809
|
|
|
802
810
|
// ../../src/core/database/relationships.ts
|
|
811
|
+
function hasMany(definition) {
|
|
812
|
+
return {
|
|
813
|
+
type: "hasMany",
|
|
814
|
+
...definition
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
function hasOne(definition) {
|
|
818
|
+
return {
|
|
819
|
+
type: "hasOne",
|
|
820
|
+
...definition
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
function belongsTo(definition) {
|
|
824
|
+
return {
|
|
825
|
+
type: "belongsTo",
|
|
826
|
+
...definition
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
function belongsToMany(definition) {
|
|
830
|
+
return {
|
|
831
|
+
type: "belongsToMany",
|
|
832
|
+
...definition
|
|
833
|
+
};
|
|
834
|
+
}
|
|
803
835
|
function indexHasManyRelation(parents, children, relation) {
|
|
804
836
|
const groups = new Map;
|
|
805
837
|
for (const parent of parents) {
|
|
@@ -815,6 +847,15 @@ function indexHasManyRelation(parents, children, relation) {
|
|
|
815
847
|
}
|
|
816
848
|
return groups;
|
|
817
849
|
}
|
|
850
|
+
function indexHasOneRelation(parents, children, relation) {
|
|
851
|
+
const grouped = indexHasManyRelation(parents, children, relation);
|
|
852
|
+
const result = new Map;
|
|
853
|
+
for (const parent of parents) {
|
|
854
|
+
const matches = grouped.get(parent[relation.localKey]) ?? [];
|
|
855
|
+
result.set(parent[relation.localKey], matches[0]);
|
|
856
|
+
}
|
|
857
|
+
return result;
|
|
858
|
+
}
|
|
818
859
|
function indexBelongsToRelation(children, parents, relation) {
|
|
819
860
|
const parentsById = new Map;
|
|
820
861
|
for (const parent of parents) {
|
|
@@ -830,6 +871,45 @@ function indexBelongsToRelation(children, parents, relation) {
|
|
|
830
871
|
}
|
|
831
872
|
return result;
|
|
832
873
|
}
|
|
874
|
+
function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
|
|
875
|
+
const relatedById = new Map;
|
|
876
|
+
for (const related of relatedRows) {
|
|
877
|
+
relatedById.set(related[relation.relatedKey], related);
|
|
878
|
+
}
|
|
879
|
+
const groups = new Map;
|
|
880
|
+
for (const parent of parents) {
|
|
881
|
+
groups.set(parent[relation.parentKey], []);
|
|
882
|
+
}
|
|
883
|
+
for (const pivot of pivotRows) {
|
|
884
|
+
const parentId = pivot[relation.foreignPivotKey];
|
|
885
|
+
const relatedId = pivot[relation.relatedPivotKey];
|
|
886
|
+
const group = groups.get(parentId);
|
|
887
|
+
const related = relatedById.get(relatedId);
|
|
888
|
+
if (!group || !related) {
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
group.push(related);
|
|
892
|
+
}
|
|
893
|
+
return groups;
|
|
894
|
+
}
|
|
895
|
+
function morphMany(definition) {
|
|
896
|
+
return {
|
|
897
|
+
type: "morphMany",
|
|
898
|
+
...definition
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
function morphOne(definition) {
|
|
902
|
+
return {
|
|
903
|
+
type: "morphOne",
|
|
904
|
+
...definition
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
function morphTo(definition) {
|
|
908
|
+
return {
|
|
909
|
+
type: "morphTo",
|
|
910
|
+
...definition
|
|
911
|
+
};
|
|
912
|
+
}
|
|
833
913
|
function indexMorphManyRelation(parents, children, relation) {
|
|
834
914
|
const groups = new Map;
|
|
835
915
|
for (const parent of parents) {
|
|
@@ -848,6 +928,15 @@ function indexMorphManyRelation(parents, children, relation) {
|
|
|
848
928
|
}
|
|
849
929
|
return groups;
|
|
850
930
|
}
|
|
931
|
+
function indexMorphOneRelation(parents, children, relation) {
|
|
932
|
+
const grouped = indexMorphManyRelation(parents, children, relation);
|
|
933
|
+
const result = new Map;
|
|
934
|
+
for (const parent of parents) {
|
|
935
|
+
const matches = grouped.get(parent[relation.localKey]) ?? [];
|
|
936
|
+
result.set(parent[relation.localKey], matches[0]);
|
|
937
|
+
}
|
|
938
|
+
return result;
|
|
939
|
+
}
|
|
851
940
|
function indexMorphToRelation(children, parentsByType, relation) {
|
|
852
941
|
const result = new Map;
|
|
853
942
|
for (const child of children) {
|
|
@@ -868,9 +957,15 @@ function indexMorphToRelation(children, parentsByType, relation) {
|
|
|
868
957
|
var boundConnectionHolder = {
|
|
869
958
|
connection: null
|
|
870
959
|
};
|
|
960
|
+
function bindDatabaseConnection(connection) {
|
|
961
|
+
boundConnectionHolder.connection = connection;
|
|
962
|
+
}
|
|
871
963
|
function getBoundDatabaseConnection() {
|
|
872
964
|
return boundConnectionHolder.connection;
|
|
873
965
|
}
|
|
966
|
+
function resetBoundDatabaseConnection() {
|
|
967
|
+
boundConnectionHolder.connection = null;
|
|
968
|
+
}
|
|
874
969
|
|
|
875
970
|
// ../../src/core/runtime/asyncContextStore.ts
|
|
876
971
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -888,9 +983,15 @@ function createAsyncContextStore(key) {
|
|
|
888
983
|
|
|
889
984
|
// ../../src/core/database/connectionContext.ts
|
|
890
985
|
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
986
|
+
function runWithDatabaseConnection(connection, callback) {
|
|
987
|
+
return activeConnection.run(connection, callback);
|
|
988
|
+
}
|
|
891
989
|
function getActiveDatabaseConnection(fallback) {
|
|
892
990
|
return activeConnection.getStore() ?? fallback;
|
|
893
991
|
}
|
|
992
|
+
function hasActiveDatabaseConnection() {
|
|
993
|
+
return activeConnection.getStore() !== undefined;
|
|
994
|
+
}
|
|
894
995
|
|
|
895
996
|
// ../../src/core/database/queryProxy.ts
|
|
896
997
|
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
@@ -927,6 +1028,12 @@ function registerDefaultDatabasePool(connection) {
|
|
|
927
1028
|
defaultPool.connection = connection;
|
|
928
1029
|
defaultQuery.connection = createDatabaseQueryProxy(connection);
|
|
929
1030
|
}
|
|
1031
|
+
function getDefaultDatabasePool() {
|
|
1032
|
+
if (!defaultPool.connection) {
|
|
1033
|
+
throw new Error("Default database pool is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
1034
|
+
}
|
|
1035
|
+
return defaultPool.connection;
|
|
1036
|
+
}
|
|
930
1037
|
function getDefaultDatabaseQuery() {
|
|
931
1038
|
if (!defaultQuery.connection) {
|
|
932
1039
|
throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
@@ -1504,10 +1611,327 @@ class BaseRepository {
|
|
|
1504
1611
|
}
|
|
1505
1612
|
}
|
|
1506
1613
|
var baseRepository_default = BaseRepository;
|
|
1614
|
+
// ../../src/core/database/connection.ts
|
|
1615
|
+
function createDatabaseConnection(source) {
|
|
1616
|
+
return {
|
|
1617
|
+
async unsafe(query, params = []) {
|
|
1618
|
+
return await source.unsafe(query, params);
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1507
1622
|
// ../../src/core/database/model.ts
|
|
1508
1623
|
var modelRepositories = new WeakMap;
|
|
1509
1624
|
var modelGlobalScopes = new WeakMap;
|
|
1510
1625
|
var modelBooted = new WeakSet;
|
|
1626
|
+
function resolveModelRepository(model) {
|
|
1627
|
+
const repository = modelRepositories.get(model);
|
|
1628
|
+
if (!repository) {
|
|
1629
|
+
throw new Error(`${model.name}.repository() is not implemented.`);
|
|
1630
|
+
}
|
|
1631
|
+
return repository;
|
|
1632
|
+
}
|
|
1633
|
+
function modelStatics(model) {
|
|
1634
|
+
return model;
|
|
1635
|
+
}
|
|
1636
|
+
function ensureBooted(model) {
|
|
1637
|
+
if (modelBooted.has(model)) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
modelBooted.add(model);
|
|
1641
|
+
const boot = model.boot;
|
|
1642
|
+
if (typeof boot === "function") {
|
|
1643
|
+
boot.call(model);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
function getGlobalScopes(model) {
|
|
1647
|
+
return modelGlobalScopes.get(model) ?? [];
|
|
1648
|
+
}
|
|
1649
|
+
function hydrateValue(value, cast) {
|
|
1650
|
+
if (value === null || value === undefined) {
|
|
1651
|
+
return value;
|
|
1652
|
+
}
|
|
1653
|
+
switch (cast) {
|
|
1654
|
+
case "date":
|
|
1655
|
+
case "datetime":
|
|
1656
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
1657
|
+
case "json":
|
|
1658
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
1659
|
+
case "bool":
|
|
1660
|
+
case "boolean":
|
|
1661
|
+
return value === true || value === 1 || value === "1" || value === "true";
|
|
1662
|
+
default:
|
|
1663
|
+
return value;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
function dehydrateValue(value, cast) {
|
|
1667
|
+
if (value === null || value === undefined) {
|
|
1668
|
+
return value;
|
|
1669
|
+
}
|
|
1670
|
+
switch (cast) {
|
|
1671
|
+
case "date":
|
|
1672
|
+
case "datetime":
|
|
1673
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
1674
|
+
case "json":
|
|
1675
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
1676
|
+
case "bool":
|
|
1677
|
+
case "boolean":
|
|
1678
|
+
return Boolean(value);
|
|
1679
|
+
default:
|
|
1680
|
+
return value;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
function filterMassAssignable(fillable, guarded, input) {
|
|
1684
|
+
const resolvedGuarded = guarded ?? true;
|
|
1685
|
+
if (fillable && fillable.length > 0) {
|
|
1686
|
+
const allowed = new Set(fillable);
|
|
1687
|
+
return Object.fromEntries(Object.entries(input).filter(([key]) => allowed.has(key)));
|
|
1688
|
+
}
|
|
1689
|
+
if (resolvedGuarded === true || resolvedGuarded.includes("*")) {
|
|
1690
|
+
return {};
|
|
1691
|
+
}
|
|
1692
|
+
const blocked = new Set(resolvedGuarded);
|
|
1693
|
+
return Object.fromEntries(Object.entries(input).filter(([key]) => !blocked.has(key)));
|
|
1694
|
+
}
|
|
1695
|
+
function applyCasts(values, casts, direction) {
|
|
1696
|
+
if (Object.keys(casts).length === 0) {
|
|
1697
|
+
return values;
|
|
1698
|
+
}
|
|
1699
|
+
const result = { ...values };
|
|
1700
|
+
const castFn = direction === "hydrate" ? hydrateValue : dehydrateValue;
|
|
1701
|
+
for (const [key, cast] of Object.entries(casts)) {
|
|
1702
|
+
if (key in result && cast) {
|
|
1703
|
+
result[key] = castFn(result[key], cast);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
return result;
|
|
1707
|
+
}
|
|
1708
|
+
function applyTimestampsOnCreate(columns, values, enabled) {
|
|
1709
|
+
if (!enabled) {
|
|
1710
|
+
return values;
|
|
1711
|
+
}
|
|
1712
|
+
const now = new Date;
|
|
1713
|
+
const result = { ...values };
|
|
1714
|
+
if (columns.includes("created_at")) {
|
|
1715
|
+
result.created_at = now;
|
|
1716
|
+
}
|
|
1717
|
+
if (columns.includes("updated_at")) {
|
|
1718
|
+
result.updated_at = now;
|
|
1719
|
+
}
|
|
1720
|
+
return result;
|
|
1721
|
+
}
|
|
1722
|
+
function applyTimestampsOnUpdate(columns, values, enabled) {
|
|
1723
|
+
if (!enabled) {
|
|
1724
|
+
return values;
|
|
1725
|
+
}
|
|
1726
|
+
const result = { ...values };
|
|
1727
|
+
if (columns.includes("updated_at")) {
|
|
1728
|
+
result.updated_at = new Date;
|
|
1729
|
+
}
|
|
1730
|
+
return result;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
class Model {
|
|
1734
|
+
attributes;
|
|
1735
|
+
repository;
|
|
1736
|
+
static $fillable;
|
|
1737
|
+
static $guarded;
|
|
1738
|
+
static $casts = {};
|
|
1739
|
+
static $timestamps = true;
|
|
1740
|
+
_exists;
|
|
1741
|
+
constructor(attributes, repository, exists = true) {
|
|
1742
|
+
this.attributes = attributes;
|
|
1743
|
+
this.repository = repository;
|
|
1744
|
+
this._exists = exists;
|
|
1745
|
+
}
|
|
1746
|
+
get $exists() {
|
|
1747
|
+
return this._exists;
|
|
1748
|
+
}
|
|
1749
|
+
get(key) {
|
|
1750
|
+
return this.attributes[key];
|
|
1751
|
+
}
|
|
1752
|
+
get id() {
|
|
1753
|
+
return this.attributes[this.primaryKey()];
|
|
1754
|
+
}
|
|
1755
|
+
toObject() {
|
|
1756
|
+
return { ...this.attributes };
|
|
1757
|
+
}
|
|
1758
|
+
primaryKey() {
|
|
1759
|
+
throw new Error(`${this.constructor.name}.primaryKey() is not implemented.`);
|
|
1760
|
+
}
|
|
1761
|
+
static primaryKeyField() {
|
|
1762
|
+
return resolveModelRepository(this).getTable().primaryKey;
|
|
1763
|
+
}
|
|
1764
|
+
static hydrateAttributes(attributes) {
|
|
1765
|
+
const casts = modelStatics(this).$casts ?? {};
|
|
1766
|
+
return applyCasts(attributes, casts, "hydrate");
|
|
1767
|
+
}
|
|
1768
|
+
static dehydrateAttributes(attributes) {
|
|
1769
|
+
const casts = modelStatics(this).$casts ?? {};
|
|
1770
|
+
return applyCasts(attributes, casts, "dehydrate");
|
|
1771
|
+
}
|
|
1772
|
+
static fromRecord(record, repository, exists = true) {
|
|
1773
|
+
const statics = modelStatics(this);
|
|
1774
|
+
const hydrated = statics.hydrateAttributes(record);
|
|
1775
|
+
return new statics(hydrated, repository, exists);
|
|
1776
|
+
}
|
|
1777
|
+
static boot() {}
|
|
1778
|
+
static addGlobalScope(_name, scope) {
|
|
1779
|
+
ensureBooted(this);
|
|
1780
|
+
const existing = modelGlobalScopes.get(this) ?? [];
|
|
1781
|
+
modelGlobalScopes.set(this, [
|
|
1782
|
+
...existing,
|
|
1783
|
+
scope
|
|
1784
|
+
]);
|
|
1785
|
+
}
|
|
1786
|
+
static repository() {
|
|
1787
|
+
return resolveModelRepository(this);
|
|
1788
|
+
}
|
|
1789
|
+
static query() {
|
|
1790
|
+
ensureBooted(this);
|
|
1791
|
+
const repository = resolveModelRepository(this);
|
|
1792
|
+
let query = repository.query();
|
|
1793
|
+
for (const scope of getGlobalScopes(this)) {
|
|
1794
|
+
query = scope(query);
|
|
1795
|
+
}
|
|
1796
|
+
return query;
|
|
1797
|
+
}
|
|
1798
|
+
static async create(attributes) {
|
|
1799
|
+
const statics = modelStatics(this);
|
|
1800
|
+
ensureBooted(this);
|
|
1801
|
+
const repository = resolveModelRepository(this);
|
|
1802
|
+
const table = repository.getTable();
|
|
1803
|
+
const timestamps = statics.$timestamps ?? true;
|
|
1804
|
+
const assignable = filterMassAssignable(statics.$fillable, statics.$guarded, attributes);
|
|
1805
|
+
const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
|
|
1806
|
+
const payload = statics.dehydrateAttributes(withTimestamps);
|
|
1807
|
+
const record = await repository.create(payload);
|
|
1808
|
+
return statics.fromRecord(record, repository, true);
|
|
1809
|
+
}
|
|
1810
|
+
static async find(id) {
|
|
1811
|
+
const statics = modelStatics(this);
|
|
1812
|
+
const repository = resolveModelRepository(this);
|
|
1813
|
+
const primaryKey = repository.getTable().primaryKey;
|
|
1814
|
+
const record = await Model.query.call(this).where({ [primaryKey]: id }).first();
|
|
1815
|
+
return record ? statics.fromRecord(record, repository, true) : null;
|
|
1816
|
+
}
|
|
1817
|
+
static async findOrFail(id, errorFactory) {
|
|
1818
|
+
const model = await Model.find.call(this, id);
|
|
1819
|
+
if (model) {
|
|
1820
|
+
return model;
|
|
1821
|
+
}
|
|
1822
|
+
throw errorFactory?.(id) ?? new NotFoundError(`${this.name} ${String(id)} not found.`);
|
|
1823
|
+
}
|
|
1824
|
+
static async all(options = {}) {
|
|
1825
|
+
const statics = modelStatics(this);
|
|
1826
|
+
const repository = resolveModelRepository(this);
|
|
1827
|
+
let query = Model.query.call(this);
|
|
1828
|
+
if (options.orderBy) {
|
|
1829
|
+
query = query.orderBy(options.orderBy);
|
|
1830
|
+
}
|
|
1831
|
+
if (options.limit !== undefined) {
|
|
1832
|
+
query = query.limit(options.limit);
|
|
1833
|
+
}
|
|
1834
|
+
const rows = await query.get();
|
|
1835
|
+
return rows.map((row) => statics.fromRecord(row, repository, true));
|
|
1836
|
+
}
|
|
1837
|
+
static async firstWhere(where, options = {}) {
|
|
1838
|
+
const statics = modelStatics(this);
|
|
1839
|
+
const repository = resolveModelRepository(this);
|
|
1840
|
+
let query = Model.query.call(this).where(where);
|
|
1841
|
+
if (options.orderBy) {
|
|
1842
|
+
query = query.orderBy(options.orderBy);
|
|
1843
|
+
}
|
|
1844
|
+
const record = await query.first();
|
|
1845
|
+
return record ? statics.fromRecord(record, repository, true) : null;
|
|
1846
|
+
}
|
|
1847
|
+
async save() {
|
|
1848
|
+
const ModelClass = modelStatics(this.constructor);
|
|
1849
|
+
const timestamps = ModelClass.$timestamps ?? true;
|
|
1850
|
+
const casts = ModelClass.$casts ?? {};
|
|
1851
|
+
const table = this.repository.getTable();
|
|
1852
|
+
if (this.$exists) {
|
|
1853
|
+
const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
|
|
1854
|
+
const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
|
|
1855
|
+
this.attributes = ModelClass.hydrateAttributes(record2);
|
|
1856
|
+
return this;
|
|
1857
|
+
}
|
|
1858
|
+
const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
|
|
1859
|
+
const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
|
|
1860
|
+
const payload = ModelClass.dehydrateAttributes(withTimestamps);
|
|
1861
|
+
const record = await this.repository.create(payload);
|
|
1862
|
+
this.attributes = ModelClass.hydrateAttributes(record);
|
|
1863
|
+
this._exists = true;
|
|
1864
|
+
return this;
|
|
1865
|
+
}
|
|
1866
|
+
async update(changes) {
|
|
1867
|
+
const ModelClass = modelStatics(this.constructor);
|
|
1868
|
+
const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, changes);
|
|
1869
|
+
Object.assign(this.attributes, assignable);
|
|
1870
|
+
return await this.save();
|
|
1871
|
+
}
|
|
1872
|
+
async delete() {
|
|
1873
|
+
if (resolveSoftDeleteColumn(this.repository.getTable())) {
|
|
1874
|
+
return await this.repository.deleteById(this.id);
|
|
1875
|
+
}
|
|
1876
|
+
return await this.repository.forceDeleteById(this.id);
|
|
1877
|
+
}
|
|
1878
|
+
async forceDelete() {
|
|
1879
|
+
return await this.repository.forceDeleteById(this.id);
|
|
1880
|
+
}
|
|
1881
|
+
async restore() {
|
|
1882
|
+
const ModelClass = modelStatics(this.constructor);
|
|
1883
|
+
const record = await this.repository.restoreById(this.id);
|
|
1884
|
+
if (!record) {
|
|
1885
|
+
return null;
|
|
1886
|
+
}
|
|
1887
|
+
this.attributes = ModelClass.hydrateAttributes(record);
|
|
1888
|
+
return this;
|
|
1889
|
+
}
|
|
1890
|
+
async loadHasMany(as, relation, childRepository, options = {}) {
|
|
1891
|
+
const grouped = await childRepository.withConnection(this.repository.getConnection()).loadHasManyForParents([this.attributes], relation, options);
|
|
1892
|
+
const loaded = grouped.get(this.attributes[relation.localKey]) ?? [];
|
|
1893
|
+
return Object.assign(this, { [as]: loaded });
|
|
1894
|
+
}
|
|
1895
|
+
async loadHasOne(as, relation, childRepository, options = {}) {
|
|
1896
|
+
const loaded = await this.loadHasMany(as, relation, childRepository, { ...options, limit: 1 });
|
|
1897
|
+
const value = loaded[as]?.[0];
|
|
1898
|
+
return Object.assign(this, { [as]: value });
|
|
1899
|
+
}
|
|
1900
|
+
async loadBelongsTo(as, relation, parentRepository, options = {}) {
|
|
1901
|
+
const grouped = await this.repository.loadBelongsToForParents([this.attributes], relation, parentRepository, options);
|
|
1902
|
+
const loaded = grouped.get(this.attributes[relation.foreignKey]);
|
|
1903
|
+
return Object.assign(this, { [as]: loaded });
|
|
1904
|
+
}
|
|
1905
|
+
async loadBelongsToMany(as, relation, relatedRepository, options = {}) {
|
|
1906
|
+
const connection = this.repository.getConnection();
|
|
1907
|
+
const parentId = this.attributes[relation.parentKey];
|
|
1908
|
+
const pivotRows = await connection.unsafe(`SELECT * FROM ${relation.pivotTable} WHERE ${String(relation.foreignPivotKey)} = $1`, [parentId]);
|
|
1909
|
+
if (pivotRows.length === 0) {
|
|
1910
|
+
return Object.assign(this, { [as]: [] });
|
|
1911
|
+
}
|
|
1912
|
+
const relatedIds = [
|
|
1913
|
+
...new Set(pivotRows.map((row) => row[relation.relatedPivotKey]))
|
|
1914
|
+
];
|
|
1915
|
+
const relatedRows = await relatedRepository.withConnection(connection).findAll({
|
|
1916
|
+
...options,
|
|
1917
|
+
where: {
|
|
1918
|
+
[relation.relatedKey]: relatedIds
|
|
1919
|
+
}
|
|
1920
|
+
});
|
|
1921
|
+
const grouped = indexBelongsToManyRelation([this.attributes], pivotRows, relatedRows, relation);
|
|
1922
|
+
const loaded = grouped.get(parentId) ?? [];
|
|
1923
|
+
return Object.assign(this, { [as]: loaded });
|
|
1924
|
+
}
|
|
1925
|
+
mergeAttributes(patch) {
|
|
1926
|
+
Object.assign(this.attributes, patch);
|
|
1927
|
+
return this;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
function registerModelRepository(model, repository) {
|
|
1931
|
+
modelRepositories.set(model, repository);
|
|
1932
|
+
ensureBooted(model);
|
|
1933
|
+
return model;
|
|
1934
|
+
}
|
|
1511
1935
|
// ../../src/core/database/schema/columnDefinition.ts
|
|
1512
1936
|
class ColumnDefinition {
|
|
1513
1937
|
name;
|
|
@@ -1729,6 +2153,45 @@ class Blueprint {
|
|
|
1729
2153
|
});
|
|
1730
2154
|
}
|
|
1731
2155
|
}
|
|
2156
|
+
// ../../src/core/database/schema/driver.ts
|
|
2157
|
+
function normalizeConnectionName(connection) {
|
|
2158
|
+
const normalized = connection.trim().toLowerCase();
|
|
2159
|
+
if (normalized === "pgsql" || normalized === "postgres" || normalized === "postgresql") {
|
|
2160
|
+
return "pgsql";
|
|
2161
|
+
}
|
|
2162
|
+
if (normalized === "mysql" || normalized === "mariadb") {
|
|
2163
|
+
return "mysql";
|
|
2164
|
+
}
|
|
2165
|
+
if (normalized === "sqlite") {
|
|
2166
|
+
return "sqlite";
|
|
2167
|
+
}
|
|
2168
|
+
throw new Error(`Unsupported DB_CONNECTION: ${connection}`);
|
|
2169
|
+
}
|
|
2170
|
+
function resolveDriverFromUrl(url) {
|
|
2171
|
+
const normalized = url.trim().toLowerCase();
|
|
2172
|
+
if (normalized.startsWith("postgres://") || normalized.startsWith("postgresql://") || normalized.startsWith("postgres:")) {
|
|
2173
|
+
return "pgsql";
|
|
2174
|
+
}
|
|
2175
|
+
if (normalized.startsWith("mysql://") || normalized.startsWith("mysql:")) {
|
|
2176
|
+
return "mysql";
|
|
2177
|
+
}
|
|
2178
|
+
if (normalized.startsWith("sqlite:")) {
|
|
2179
|
+
return "sqlite";
|
|
2180
|
+
}
|
|
2181
|
+
return null;
|
|
2182
|
+
}
|
|
2183
|
+
function resolveDatabaseDriver(options = {}) {
|
|
2184
|
+
const connection = options.connection ?? process.env.DB_CONNECTION;
|
|
2185
|
+
if (connection) {
|
|
2186
|
+
return normalizeConnectionName(connection);
|
|
2187
|
+
}
|
|
2188
|
+
const url = options.url ?? process.env.DATABASE_URL ?? "";
|
|
2189
|
+
const fromUrl = resolveDriverFromUrl(url);
|
|
2190
|
+
if (fromUrl) {
|
|
2191
|
+
return fromUrl;
|
|
2192
|
+
}
|
|
2193
|
+
return "pgsql";
|
|
2194
|
+
}
|
|
1732
2195
|
// ../../src/core/database/schema/errors.ts
|
|
1733
2196
|
class UnsupportedSchemaFeatureError extends Error {
|
|
1734
2197
|
constructor(feature, driver) {
|
|
@@ -2073,10 +2536,42 @@ class SchemaBuilder {
|
|
|
2073
2536
|
}
|
|
2074
2537
|
}
|
|
2075
2538
|
}
|
|
2539
|
+
|
|
2540
|
+
class Schema {
|
|
2541
|
+
static builder(driver) {
|
|
2542
|
+
return new SchemaBuilder(driver ?? resolveDatabaseDriver());
|
|
2543
|
+
}
|
|
2544
|
+
static async run(db, driver, callback) {
|
|
2545
|
+
const schema = Schema.builder(driver);
|
|
2546
|
+
await callback(schema);
|
|
2547
|
+
await schema.execute(db);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
function createSchemaBuilder(db, driver) {
|
|
2551
|
+
const builder = Schema.builder(driver);
|
|
2552
|
+
return Object.assign(builder, {
|
|
2553
|
+
async commit() {
|
|
2554
|
+
await builder.execute(db);
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2076
2558
|
// ../../src/core/database/table.ts
|
|
2077
2559
|
function defineTable(definition) {
|
|
2078
2560
|
return definition;
|
|
2079
2561
|
}
|
|
2562
|
+
// ../../src/core/database/transaction.ts
|
|
2563
|
+
function supportsTransactions(connection) {
|
|
2564
|
+
return typeof connection.begin === "function";
|
|
2565
|
+
}
|
|
2566
|
+
async function runInTransaction(operation) {
|
|
2567
|
+
const pool = resolveRepositoryConnection();
|
|
2568
|
+
if (!supportsTransactions(pool)) {
|
|
2569
|
+
throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
|
|
2570
|
+
}
|
|
2571
|
+
return await pool.begin(async (transaction) => {
|
|
2572
|
+
return await operation(createDatabaseConnection(transaction));
|
|
2573
|
+
});
|
|
2574
|
+
}
|
|
2080
2575
|
// ../../src/config/database.ts
|
|
2081
2576
|
function readInteger(name, fallback) {
|
|
2082
2577
|
const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
|
|
@@ -2262,6 +2757,9 @@ function revealMfaSecret(stored) {
|
|
|
2262
2757
|
|
|
2263
2758
|
// ../../src/core/auth/authContext.ts
|
|
2264
2759
|
var authContext = createAsyncContextStore("@getstrata/authContext");
|
|
2760
|
+
function runWithAuthUser(user, callback) {
|
|
2761
|
+
return authContext.run(user, callback);
|
|
2762
|
+
}
|
|
2265
2763
|
function currentAuthUser() {
|
|
2266
2764
|
return authContext.getStore() ?? null;
|
|
2267
2765
|
}
|
|
@@ -2352,6 +2850,9 @@ function verifyTotp(secret, token, window = 1) {
|
|
|
2352
2850
|
|
|
2353
2851
|
// ../../src/core/tenant/tenantContext.ts
|
|
2354
2852
|
var tenantContext = createAsyncContextStore("@getstrata/tenantContext");
|
|
2853
|
+
function runWithTenant(tenant, callback) {
|
|
2854
|
+
return tenantContext.run(tenant, callback);
|
|
2855
|
+
}
|
|
2355
2856
|
function currentTenant() {
|
|
2356
2857
|
return tenantContext.getStore() ?? null;
|
|
2357
2858
|
}
|
|
@@ -122,12 +122,18 @@ function createAsyncContextStore(key) {
|
|
|
122
122
|
|
|
123
123
|
// ../../src/core/auth/authContext.ts
|
|
124
124
|
var authContext = createAsyncContextStore("@getstrata/authContext");
|
|
125
|
+
function runWithAuthUser(user, callback) {
|
|
126
|
+
return authContext.run(user, callback);
|
|
127
|
+
}
|
|
125
128
|
function currentAuthUser() {
|
|
126
129
|
return authContext.getStore() ?? null;
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
// ../../src/core/tenant/tenantContext.ts
|
|
130
133
|
var tenantContext = createAsyncContextStore("@getstrata/tenantContext");
|
|
134
|
+
function runWithTenant(tenant, callback) {
|
|
135
|
+
return tenantContext.run(tenant, callback);
|
|
136
|
+
}
|
|
131
137
|
function currentTenant() {
|
|
132
138
|
return tenantContext.getStore() ?? null;
|
|
133
139
|
}
|
|
@@ -15,9 +15,15 @@ var appConfig = {
|
|
|
15
15
|
var boundConnectionHolder = {
|
|
16
16
|
connection: null
|
|
17
17
|
};
|
|
18
|
+
function bindDatabaseConnection(connection) {
|
|
19
|
+
boundConnectionHolder.connection = connection;
|
|
20
|
+
}
|
|
18
21
|
function getBoundDatabaseConnection() {
|
|
19
22
|
return boundConnectionHolder.connection;
|
|
20
23
|
}
|
|
24
|
+
function resetBoundDatabaseConnection() {
|
|
25
|
+
boundConnectionHolder.connection = null;
|
|
26
|
+
}
|
|
21
27
|
|
|
22
28
|
// ../../src/core/runtime/asyncContextStore.ts
|
|
23
29
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -35,9 +41,15 @@ function createAsyncContextStore(key) {
|
|
|
35
41
|
|
|
36
42
|
// ../../src/core/database/connectionContext.ts
|
|
37
43
|
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
44
|
+
function runWithDatabaseConnection(connection, callback) {
|
|
45
|
+
return activeConnection.run(connection, callback);
|
|
46
|
+
}
|
|
38
47
|
function getActiveDatabaseConnection(fallback) {
|
|
39
48
|
return activeConnection.getStore() ?? fallback;
|
|
40
49
|
}
|
|
50
|
+
function hasActiveDatabaseConnection() {
|
|
51
|
+
return activeConnection.getStore() !== undefined;
|
|
52
|
+
}
|
|
41
53
|
|
|
42
54
|
// ../../src/core/database/queryProxy.ts
|
|
43
55
|
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
@@ -74,6 +86,12 @@ function registerDefaultDatabasePool(connection) {
|
|
|
74
86
|
defaultPool.connection = connection;
|
|
75
87
|
defaultQuery.connection = createDatabaseQueryProxy(connection);
|
|
76
88
|
}
|
|
89
|
+
function getDefaultDatabasePool() {
|
|
90
|
+
if (!defaultPool.connection) {
|
|
91
|
+
throw new Error("Default database pool is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
92
|
+
}
|
|
93
|
+
return defaultPool.connection;
|
|
94
|
+
}
|
|
77
95
|
function getDefaultDatabaseQuery() {
|
|
78
96
|
if (!defaultQuery.connection) {
|
|
79
97
|
throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|