@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
|
@@ -476,6 +476,14 @@ function appendWhereParts(tableName, where, params) {
|
|
|
476
476
|
}
|
|
477
477
|
return clauses.join(" AND ");
|
|
478
478
|
}
|
|
479
|
+
function buildWhereClause(tableName, where = {}) {
|
|
480
|
+
const params = [];
|
|
481
|
+
const body = appendWhereParts(tableName, where, params);
|
|
482
|
+
return {
|
|
483
|
+
clause: body.length > 0 ? ` WHERE ${body}` : "",
|
|
484
|
+
params
|
|
485
|
+
};
|
|
486
|
+
}
|
|
479
487
|
function buildWhereNodeClause(tableName, node, params) {
|
|
480
488
|
if ("where" in node) {
|
|
481
489
|
return appendWhereParts(tableName, node.where, params);
|
|
@@ -778,6 +786,30 @@ function buildDeleteByIdQuery(table, id) {
|
|
|
778
786
|
}
|
|
779
787
|
|
|
780
788
|
// ../../src/core/database/relationships.ts
|
|
789
|
+
function hasMany(definition) {
|
|
790
|
+
return {
|
|
791
|
+
type: "hasMany",
|
|
792
|
+
...definition
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function hasOne(definition) {
|
|
796
|
+
return {
|
|
797
|
+
type: "hasOne",
|
|
798
|
+
...definition
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function belongsTo(definition) {
|
|
802
|
+
return {
|
|
803
|
+
type: "belongsTo",
|
|
804
|
+
...definition
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
function belongsToMany(definition) {
|
|
808
|
+
return {
|
|
809
|
+
type: "belongsToMany",
|
|
810
|
+
...definition
|
|
811
|
+
};
|
|
812
|
+
}
|
|
781
813
|
function indexHasManyRelation(parents, children, relation) {
|
|
782
814
|
const groups = new Map;
|
|
783
815
|
for (const parent of parents) {
|
|
@@ -793,6 +825,15 @@ function indexHasManyRelation(parents, children, relation) {
|
|
|
793
825
|
}
|
|
794
826
|
return groups;
|
|
795
827
|
}
|
|
828
|
+
function indexHasOneRelation(parents, children, relation) {
|
|
829
|
+
const grouped = indexHasManyRelation(parents, children, relation);
|
|
830
|
+
const result = new Map;
|
|
831
|
+
for (const parent of parents) {
|
|
832
|
+
const matches = grouped.get(parent[relation.localKey]) ?? [];
|
|
833
|
+
result.set(parent[relation.localKey], matches[0]);
|
|
834
|
+
}
|
|
835
|
+
return result;
|
|
836
|
+
}
|
|
796
837
|
function indexBelongsToRelation(children, parents, relation) {
|
|
797
838
|
const parentsById = new Map;
|
|
798
839
|
for (const parent of parents) {
|
|
@@ -808,6 +849,45 @@ function indexBelongsToRelation(children, parents, relation) {
|
|
|
808
849
|
}
|
|
809
850
|
return result;
|
|
810
851
|
}
|
|
852
|
+
function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
|
|
853
|
+
const relatedById = new Map;
|
|
854
|
+
for (const related of relatedRows) {
|
|
855
|
+
relatedById.set(related[relation.relatedKey], related);
|
|
856
|
+
}
|
|
857
|
+
const groups = new Map;
|
|
858
|
+
for (const parent of parents) {
|
|
859
|
+
groups.set(parent[relation.parentKey], []);
|
|
860
|
+
}
|
|
861
|
+
for (const pivot of pivotRows) {
|
|
862
|
+
const parentId = pivot[relation.foreignPivotKey];
|
|
863
|
+
const relatedId = pivot[relation.relatedPivotKey];
|
|
864
|
+
const group = groups.get(parentId);
|
|
865
|
+
const related = relatedById.get(relatedId);
|
|
866
|
+
if (!group || !related) {
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
group.push(related);
|
|
870
|
+
}
|
|
871
|
+
return groups;
|
|
872
|
+
}
|
|
873
|
+
function morphMany(definition) {
|
|
874
|
+
return {
|
|
875
|
+
type: "morphMany",
|
|
876
|
+
...definition
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function morphOne(definition) {
|
|
880
|
+
return {
|
|
881
|
+
type: "morphOne",
|
|
882
|
+
...definition
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function morphTo(definition) {
|
|
886
|
+
return {
|
|
887
|
+
type: "morphTo",
|
|
888
|
+
...definition
|
|
889
|
+
};
|
|
890
|
+
}
|
|
811
891
|
function indexMorphManyRelation(parents, children, relation) {
|
|
812
892
|
const groups = new Map;
|
|
813
893
|
for (const parent of parents) {
|
|
@@ -826,6 +906,15 @@ function indexMorphManyRelation(parents, children, relation) {
|
|
|
826
906
|
}
|
|
827
907
|
return groups;
|
|
828
908
|
}
|
|
909
|
+
function indexMorphOneRelation(parents, children, relation) {
|
|
910
|
+
const grouped = indexMorphManyRelation(parents, children, relation);
|
|
911
|
+
const result = new Map;
|
|
912
|
+
for (const parent of parents) {
|
|
913
|
+
const matches = grouped.get(parent[relation.localKey]) ?? [];
|
|
914
|
+
result.set(parent[relation.localKey], matches[0]);
|
|
915
|
+
}
|
|
916
|
+
return result;
|
|
917
|
+
}
|
|
829
918
|
function indexMorphToRelation(children, parentsByType, relation) {
|
|
830
919
|
const result = new Map;
|
|
831
920
|
for (const child of children) {
|
|
@@ -846,9 +935,15 @@ function indexMorphToRelation(children, parentsByType, relation) {
|
|
|
846
935
|
var boundConnectionHolder = {
|
|
847
936
|
connection: null
|
|
848
937
|
};
|
|
938
|
+
function bindDatabaseConnection(connection) {
|
|
939
|
+
boundConnectionHolder.connection = connection;
|
|
940
|
+
}
|
|
849
941
|
function getBoundDatabaseConnection() {
|
|
850
942
|
return boundConnectionHolder.connection;
|
|
851
943
|
}
|
|
944
|
+
function resetBoundDatabaseConnection() {
|
|
945
|
+
boundConnectionHolder.connection = null;
|
|
946
|
+
}
|
|
852
947
|
|
|
853
948
|
// ../../src/core/runtime/asyncContextStore.ts
|
|
854
949
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -866,9 +961,15 @@ function createAsyncContextStore(key) {
|
|
|
866
961
|
|
|
867
962
|
// ../../src/core/database/connectionContext.ts
|
|
868
963
|
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
964
|
+
function runWithDatabaseConnection(connection, callback) {
|
|
965
|
+
return activeConnection.run(connection, callback);
|
|
966
|
+
}
|
|
869
967
|
function getActiveDatabaseConnection(fallback) {
|
|
870
968
|
return activeConnection.getStore() ?? fallback;
|
|
871
969
|
}
|
|
970
|
+
function hasActiveDatabaseConnection() {
|
|
971
|
+
return activeConnection.getStore() !== undefined;
|
|
972
|
+
}
|
|
872
973
|
|
|
873
974
|
// ../../src/core/database/queryProxy.ts
|
|
874
975
|
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
@@ -905,6 +1006,12 @@ function registerDefaultDatabasePool(connection) {
|
|
|
905
1006
|
defaultPool.connection = connection;
|
|
906
1007
|
defaultQuery.connection = createDatabaseQueryProxy(connection);
|
|
907
1008
|
}
|
|
1009
|
+
function getDefaultDatabasePool() {
|
|
1010
|
+
if (!defaultPool.connection) {
|
|
1011
|
+
throw new Error("Default database pool is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
1012
|
+
}
|
|
1013
|
+
return defaultPool.connection;
|
|
1014
|
+
}
|
|
908
1015
|
function getDefaultDatabaseQuery() {
|
|
909
1016
|
if (!defaultQuery.connection) {
|
|
910
1017
|
throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
@@ -1482,10 +1589,327 @@ class BaseRepository {
|
|
|
1482
1589
|
}
|
|
1483
1590
|
}
|
|
1484
1591
|
var baseRepository_default = BaseRepository;
|
|
1592
|
+
// ../../src/core/database/connection.ts
|
|
1593
|
+
function createDatabaseConnection(source) {
|
|
1594
|
+
return {
|
|
1595
|
+
async unsafe(query, params = []) {
|
|
1596
|
+
return await source.unsafe(query, params);
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1485
1600
|
// ../../src/core/database/model.ts
|
|
1486
1601
|
var modelRepositories = new WeakMap;
|
|
1487
1602
|
var modelGlobalScopes = new WeakMap;
|
|
1488
1603
|
var modelBooted = new WeakSet;
|
|
1604
|
+
function resolveModelRepository(model) {
|
|
1605
|
+
const repository = modelRepositories.get(model);
|
|
1606
|
+
if (!repository) {
|
|
1607
|
+
throw new Error(`${model.name}.repository() is not implemented.`);
|
|
1608
|
+
}
|
|
1609
|
+
return repository;
|
|
1610
|
+
}
|
|
1611
|
+
function modelStatics(model) {
|
|
1612
|
+
return model;
|
|
1613
|
+
}
|
|
1614
|
+
function ensureBooted(model) {
|
|
1615
|
+
if (modelBooted.has(model)) {
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
modelBooted.add(model);
|
|
1619
|
+
const boot = model.boot;
|
|
1620
|
+
if (typeof boot === "function") {
|
|
1621
|
+
boot.call(model);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
function getGlobalScopes(model) {
|
|
1625
|
+
return modelGlobalScopes.get(model) ?? [];
|
|
1626
|
+
}
|
|
1627
|
+
function hydrateValue(value, cast) {
|
|
1628
|
+
if (value === null || value === undefined) {
|
|
1629
|
+
return value;
|
|
1630
|
+
}
|
|
1631
|
+
switch (cast) {
|
|
1632
|
+
case "date":
|
|
1633
|
+
case "datetime":
|
|
1634
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
1635
|
+
case "json":
|
|
1636
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
1637
|
+
case "bool":
|
|
1638
|
+
case "boolean":
|
|
1639
|
+
return value === true || value === 1 || value === "1" || value === "true";
|
|
1640
|
+
default:
|
|
1641
|
+
return value;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
function dehydrateValue(value, cast) {
|
|
1645
|
+
if (value === null || value === undefined) {
|
|
1646
|
+
return value;
|
|
1647
|
+
}
|
|
1648
|
+
switch (cast) {
|
|
1649
|
+
case "date":
|
|
1650
|
+
case "datetime":
|
|
1651
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
1652
|
+
case "json":
|
|
1653
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
1654
|
+
case "bool":
|
|
1655
|
+
case "boolean":
|
|
1656
|
+
return Boolean(value);
|
|
1657
|
+
default:
|
|
1658
|
+
return value;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
function filterMassAssignable(fillable, guarded, input) {
|
|
1662
|
+
const resolvedGuarded = guarded ?? true;
|
|
1663
|
+
if (fillable && fillable.length > 0) {
|
|
1664
|
+
const allowed = new Set(fillable);
|
|
1665
|
+
return Object.fromEntries(Object.entries(input).filter(([key]) => allowed.has(key)));
|
|
1666
|
+
}
|
|
1667
|
+
if (resolvedGuarded === true || resolvedGuarded.includes("*")) {
|
|
1668
|
+
return {};
|
|
1669
|
+
}
|
|
1670
|
+
const blocked = new Set(resolvedGuarded);
|
|
1671
|
+
return Object.fromEntries(Object.entries(input).filter(([key]) => !blocked.has(key)));
|
|
1672
|
+
}
|
|
1673
|
+
function applyCasts(values, casts, direction) {
|
|
1674
|
+
if (Object.keys(casts).length === 0) {
|
|
1675
|
+
return values;
|
|
1676
|
+
}
|
|
1677
|
+
const result = { ...values };
|
|
1678
|
+
const castFn = direction === "hydrate" ? hydrateValue : dehydrateValue;
|
|
1679
|
+
for (const [key, cast] of Object.entries(casts)) {
|
|
1680
|
+
if (key in result && cast) {
|
|
1681
|
+
result[key] = castFn(result[key], cast);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
return result;
|
|
1685
|
+
}
|
|
1686
|
+
function applyTimestampsOnCreate(columns, values, enabled) {
|
|
1687
|
+
if (!enabled) {
|
|
1688
|
+
return values;
|
|
1689
|
+
}
|
|
1690
|
+
const now = new Date;
|
|
1691
|
+
const result = { ...values };
|
|
1692
|
+
if (columns.includes("created_at")) {
|
|
1693
|
+
result.created_at = now;
|
|
1694
|
+
}
|
|
1695
|
+
if (columns.includes("updated_at")) {
|
|
1696
|
+
result.updated_at = now;
|
|
1697
|
+
}
|
|
1698
|
+
return result;
|
|
1699
|
+
}
|
|
1700
|
+
function applyTimestampsOnUpdate(columns, values, enabled) {
|
|
1701
|
+
if (!enabled) {
|
|
1702
|
+
return values;
|
|
1703
|
+
}
|
|
1704
|
+
const result = { ...values };
|
|
1705
|
+
if (columns.includes("updated_at")) {
|
|
1706
|
+
result.updated_at = new Date;
|
|
1707
|
+
}
|
|
1708
|
+
return result;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
class Model {
|
|
1712
|
+
attributes;
|
|
1713
|
+
repository;
|
|
1714
|
+
static $fillable;
|
|
1715
|
+
static $guarded;
|
|
1716
|
+
static $casts = {};
|
|
1717
|
+
static $timestamps = true;
|
|
1718
|
+
_exists;
|
|
1719
|
+
constructor(attributes, repository, exists = true) {
|
|
1720
|
+
this.attributes = attributes;
|
|
1721
|
+
this.repository = repository;
|
|
1722
|
+
this._exists = exists;
|
|
1723
|
+
}
|
|
1724
|
+
get $exists() {
|
|
1725
|
+
return this._exists;
|
|
1726
|
+
}
|
|
1727
|
+
get(key) {
|
|
1728
|
+
return this.attributes[key];
|
|
1729
|
+
}
|
|
1730
|
+
get id() {
|
|
1731
|
+
return this.attributes[this.primaryKey()];
|
|
1732
|
+
}
|
|
1733
|
+
toObject() {
|
|
1734
|
+
return { ...this.attributes };
|
|
1735
|
+
}
|
|
1736
|
+
primaryKey() {
|
|
1737
|
+
throw new Error(`${this.constructor.name}.primaryKey() is not implemented.`);
|
|
1738
|
+
}
|
|
1739
|
+
static primaryKeyField() {
|
|
1740
|
+
return resolveModelRepository(this).getTable().primaryKey;
|
|
1741
|
+
}
|
|
1742
|
+
static hydrateAttributes(attributes) {
|
|
1743
|
+
const casts = modelStatics(this).$casts ?? {};
|
|
1744
|
+
return applyCasts(attributes, casts, "hydrate");
|
|
1745
|
+
}
|
|
1746
|
+
static dehydrateAttributes(attributes) {
|
|
1747
|
+
const casts = modelStatics(this).$casts ?? {};
|
|
1748
|
+
return applyCasts(attributes, casts, "dehydrate");
|
|
1749
|
+
}
|
|
1750
|
+
static fromRecord(record, repository, exists = true) {
|
|
1751
|
+
const statics = modelStatics(this);
|
|
1752
|
+
const hydrated = statics.hydrateAttributes(record);
|
|
1753
|
+
return new statics(hydrated, repository, exists);
|
|
1754
|
+
}
|
|
1755
|
+
static boot() {}
|
|
1756
|
+
static addGlobalScope(_name, scope) {
|
|
1757
|
+
ensureBooted(this);
|
|
1758
|
+
const existing = modelGlobalScopes.get(this) ?? [];
|
|
1759
|
+
modelGlobalScopes.set(this, [
|
|
1760
|
+
...existing,
|
|
1761
|
+
scope
|
|
1762
|
+
]);
|
|
1763
|
+
}
|
|
1764
|
+
static repository() {
|
|
1765
|
+
return resolveModelRepository(this);
|
|
1766
|
+
}
|
|
1767
|
+
static query() {
|
|
1768
|
+
ensureBooted(this);
|
|
1769
|
+
const repository = resolveModelRepository(this);
|
|
1770
|
+
let query = repository.query();
|
|
1771
|
+
for (const scope of getGlobalScopes(this)) {
|
|
1772
|
+
query = scope(query);
|
|
1773
|
+
}
|
|
1774
|
+
return query;
|
|
1775
|
+
}
|
|
1776
|
+
static async create(attributes) {
|
|
1777
|
+
const statics = modelStatics(this);
|
|
1778
|
+
ensureBooted(this);
|
|
1779
|
+
const repository = resolveModelRepository(this);
|
|
1780
|
+
const table = repository.getTable();
|
|
1781
|
+
const timestamps = statics.$timestamps ?? true;
|
|
1782
|
+
const assignable = filterMassAssignable(statics.$fillable, statics.$guarded, attributes);
|
|
1783
|
+
const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
|
|
1784
|
+
const payload = statics.dehydrateAttributes(withTimestamps);
|
|
1785
|
+
const record = await repository.create(payload);
|
|
1786
|
+
return statics.fromRecord(record, repository, true);
|
|
1787
|
+
}
|
|
1788
|
+
static async find(id) {
|
|
1789
|
+
const statics = modelStatics(this);
|
|
1790
|
+
const repository = resolveModelRepository(this);
|
|
1791
|
+
const primaryKey = repository.getTable().primaryKey;
|
|
1792
|
+
const record = await Model.query.call(this).where({ [primaryKey]: id }).first();
|
|
1793
|
+
return record ? statics.fromRecord(record, repository, true) : null;
|
|
1794
|
+
}
|
|
1795
|
+
static async findOrFail(id, errorFactory) {
|
|
1796
|
+
const model = await Model.find.call(this, id);
|
|
1797
|
+
if (model) {
|
|
1798
|
+
return model;
|
|
1799
|
+
}
|
|
1800
|
+
throw errorFactory?.(id) ?? new NotFoundError(`${this.name} ${String(id)} not found.`);
|
|
1801
|
+
}
|
|
1802
|
+
static async all(options = {}) {
|
|
1803
|
+
const statics = modelStatics(this);
|
|
1804
|
+
const repository = resolveModelRepository(this);
|
|
1805
|
+
let query = Model.query.call(this);
|
|
1806
|
+
if (options.orderBy) {
|
|
1807
|
+
query = query.orderBy(options.orderBy);
|
|
1808
|
+
}
|
|
1809
|
+
if (options.limit !== undefined) {
|
|
1810
|
+
query = query.limit(options.limit);
|
|
1811
|
+
}
|
|
1812
|
+
const rows = await query.get();
|
|
1813
|
+
return rows.map((row) => statics.fromRecord(row, repository, true));
|
|
1814
|
+
}
|
|
1815
|
+
static async firstWhere(where, options = {}) {
|
|
1816
|
+
const statics = modelStatics(this);
|
|
1817
|
+
const repository = resolveModelRepository(this);
|
|
1818
|
+
let query = Model.query.call(this).where(where);
|
|
1819
|
+
if (options.orderBy) {
|
|
1820
|
+
query = query.orderBy(options.orderBy);
|
|
1821
|
+
}
|
|
1822
|
+
const record = await query.first();
|
|
1823
|
+
return record ? statics.fromRecord(record, repository, true) : null;
|
|
1824
|
+
}
|
|
1825
|
+
async save() {
|
|
1826
|
+
const ModelClass = modelStatics(this.constructor);
|
|
1827
|
+
const timestamps = ModelClass.$timestamps ?? true;
|
|
1828
|
+
const casts = ModelClass.$casts ?? {};
|
|
1829
|
+
const table = this.repository.getTable();
|
|
1830
|
+
if (this.$exists) {
|
|
1831
|
+
const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
|
|
1832
|
+
const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
|
|
1833
|
+
this.attributes = ModelClass.hydrateAttributes(record2);
|
|
1834
|
+
return this;
|
|
1835
|
+
}
|
|
1836
|
+
const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
|
|
1837
|
+
const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
|
|
1838
|
+
const payload = ModelClass.dehydrateAttributes(withTimestamps);
|
|
1839
|
+
const record = await this.repository.create(payload);
|
|
1840
|
+
this.attributes = ModelClass.hydrateAttributes(record);
|
|
1841
|
+
this._exists = true;
|
|
1842
|
+
return this;
|
|
1843
|
+
}
|
|
1844
|
+
async update(changes) {
|
|
1845
|
+
const ModelClass = modelStatics(this.constructor);
|
|
1846
|
+
const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, changes);
|
|
1847
|
+
Object.assign(this.attributes, assignable);
|
|
1848
|
+
return await this.save();
|
|
1849
|
+
}
|
|
1850
|
+
async delete() {
|
|
1851
|
+
if (resolveSoftDeleteColumn(this.repository.getTable())) {
|
|
1852
|
+
return await this.repository.deleteById(this.id);
|
|
1853
|
+
}
|
|
1854
|
+
return await this.repository.forceDeleteById(this.id);
|
|
1855
|
+
}
|
|
1856
|
+
async forceDelete() {
|
|
1857
|
+
return await this.repository.forceDeleteById(this.id);
|
|
1858
|
+
}
|
|
1859
|
+
async restore() {
|
|
1860
|
+
const ModelClass = modelStatics(this.constructor);
|
|
1861
|
+
const record = await this.repository.restoreById(this.id);
|
|
1862
|
+
if (!record) {
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
this.attributes = ModelClass.hydrateAttributes(record);
|
|
1866
|
+
return this;
|
|
1867
|
+
}
|
|
1868
|
+
async loadHasMany(as, relation, childRepository, options = {}) {
|
|
1869
|
+
const grouped = await childRepository.withConnection(this.repository.getConnection()).loadHasManyForParents([this.attributes], relation, options);
|
|
1870
|
+
const loaded = grouped.get(this.attributes[relation.localKey]) ?? [];
|
|
1871
|
+
return Object.assign(this, { [as]: loaded });
|
|
1872
|
+
}
|
|
1873
|
+
async loadHasOne(as, relation, childRepository, options = {}) {
|
|
1874
|
+
const loaded = await this.loadHasMany(as, relation, childRepository, { ...options, limit: 1 });
|
|
1875
|
+
const value = loaded[as]?.[0];
|
|
1876
|
+
return Object.assign(this, { [as]: value });
|
|
1877
|
+
}
|
|
1878
|
+
async loadBelongsTo(as, relation, parentRepository, options = {}) {
|
|
1879
|
+
const grouped = await this.repository.loadBelongsToForParents([this.attributes], relation, parentRepository, options);
|
|
1880
|
+
const loaded = grouped.get(this.attributes[relation.foreignKey]);
|
|
1881
|
+
return Object.assign(this, { [as]: loaded });
|
|
1882
|
+
}
|
|
1883
|
+
async loadBelongsToMany(as, relation, relatedRepository, options = {}) {
|
|
1884
|
+
const connection = this.repository.getConnection();
|
|
1885
|
+
const parentId = this.attributes[relation.parentKey];
|
|
1886
|
+
const pivotRows = await connection.unsafe(`SELECT * FROM ${relation.pivotTable} WHERE ${String(relation.foreignPivotKey)} = $1`, [parentId]);
|
|
1887
|
+
if (pivotRows.length === 0) {
|
|
1888
|
+
return Object.assign(this, { [as]: [] });
|
|
1889
|
+
}
|
|
1890
|
+
const relatedIds = [
|
|
1891
|
+
...new Set(pivotRows.map((row) => row[relation.relatedPivotKey]))
|
|
1892
|
+
];
|
|
1893
|
+
const relatedRows = await relatedRepository.withConnection(connection).findAll({
|
|
1894
|
+
...options,
|
|
1895
|
+
where: {
|
|
1896
|
+
[relation.relatedKey]: relatedIds
|
|
1897
|
+
}
|
|
1898
|
+
});
|
|
1899
|
+
const grouped = indexBelongsToManyRelation([this.attributes], pivotRows, relatedRows, relation);
|
|
1900
|
+
const loaded = grouped.get(parentId) ?? [];
|
|
1901
|
+
return Object.assign(this, { [as]: loaded });
|
|
1902
|
+
}
|
|
1903
|
+
mergeAttributes(patch) {
|
|
1904
|
+
Object.assign(this.attributes, patch);
|
|
1905
|
+
return this;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
function registerModelRepository(model, repository) {
|
|
1909
|
+
modelRepositories.set(model, repository);
|
|
1910
|
+
ensureBooted(model);
|
|
1911
|
+
return model;
|
|
1912
|
+
}
|
|
1489
1913
|
// ../../src/core/database/schema/columnDefinition.ts
|
|
1490
1914
|
class ColumnDefinition {
|
|
1491
1915
|
name;
|
|
@@ -1707,6 +2131,45 @@ class Blueprint {
|
|
|
1707
2131
|
});
|
|
1708
2132
|
}
|
|
1709
2133
|
}
|
|
2134
|
+
// ../../src/core/database/schema/driver.ts
|
|
2135
|
+
function normalizeConnectionName(connection) {
|
|
2136
|
+
const normalized = connection.trim().toLowerCase();
|
|
2137
|
+
if (normalized === "pgsql" || normalized === "postgres" || normalized === "postgresql") {
|
|
2138
|
+
return "pgsql";
|
|
2139
|
+
}
|
|
2140
|
+
if (normalized === "mysql" || normalized === "mariadb") {
|
|
2141
|
+
return "mysql";
|
|
2142
|
+
}
|
|
2143
|
+
if (normalized === "sqlite") {
|
|
2144
|
+
return "sqlite";
|
|
2145
|
+
}
|
|
2146
|
+
throw new Error(`Unsupported DB_CONNECTION: ${connection}`);
|
|
2147
|
+
}
|
|
2148
|
+
function resolveDriverFromUrl(url) {
|
|
2149
|
+
const normalized = url.trim().toLowerCase();
|
|
2150
|
+
if (normalized.startsWith("postgres://") || normalized.startsWith("postgresql://") || normalized.startsWith("postgres:")) {
|
|
2151
|
+
return "pgsql";
|
|
2152
|
+
}
|
|
2153
|
+
if (normalized.startsWith("mysql://") || normalized.startsWith("mysql:")) {
|
|
2154
|
+
return "mysql";
|
|
2155
|
+
}
|
|
2156
|
+
if (normalized.startsWith("sqlite:")) {
|
|
2157
|
+
return "sqlite";
|
|
2158
|
+
}
|
|
2159
|
+
return null;
|
|
2160
|
+
}
|
|
2161
|
+
function resolveDatabaseDriver(options = {}) {
|
|
2162
|
+
const connection = options.connection ?? process.env.DB_CONNECTION;
|
|
2163
|
+
if (connection) {
|
|
2164
|
+
return normalizeConnectionName(connection);
|
|
2165
|
+
}
|
|
2166
|
+
const url = options.url ?? process.env.DATABASE_URL ?? "";
|
|
2167
|
+
const fromUrl = resolveDriverFromUrl(url);
|
|
2168
|
+
if (fromUrl) {
|
|
2169
|
+
return fromUrl;
|
|
2170
|
+
}
|
|
2171
|
+
return "pgsql";
|
|
2172
|
+
}
|
|
1710
2173
|
// ../../src/core/database/schema/errors.ts
|
|
1711
2174
|
class UnsupportedSchemaFeatureError extends Error {
|
|
1712
2175
|
constructor(feature, driver) {
|
|
@@ -2051,10 +2514,42 @@ class SchemaBuilder {
|
|
|
2051
2514
|
}
|
|
2052
2515
|
}
|
|
2053
2516
|
}
|
|
2517
|
+
|
|
2518
|
+
class Schema {
|
|
2519
|
+
static builder(driver) {
|
|
2520
|
+
return new SchemaBuilder(driver ?? resolveDatabaseDriver());
|
|
2521
|
+
}
|
|
2522
|
+
static async run(db, driver, callback) {
|
|
2523
|
+
const schema = Schema.builder(driver);
|
|
2524
|
+
await callback(schema);
|
|
2525
|
+
await schema.execute(db);
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
function createSchemaBuilder(db, driver) {
|
|
2529
|
+
const builder = Schema.builder(driver);
|
|
2530
|
+
return Object.assign(builder, {
|
|
2531
|
+
async commit() {
|
|
2532
|
+
await builder.execute(db);
|
|
2533
|
+
}
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
2054
2536
|
// ../../src/core/database/table.ts
|
|
2055
2537
|
function defineTable(definition) {
|
|
2056
2538
|
return definition;
|
|
2057
2539
|
}
|
|
2540
|
+
// ../../src/core/database/transaction.ts
|
|
2541
|
+
function supportsTransactions(connection) {
|
|
2542
|
+
return typeof connection.begin === "function";
|
|
2543
|
+
}
|
|
2544
|
+
async function runInTransaction(operation) {
|
|
2545
|
+
const pool = resolveRepositoryConnection();
|
|
2546
|
+
if (!supportsTransactions(pool)) {
|
|
2547
|
+
throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
|
|
2548
|
+
}
|
|
2549
|
+
return await pool.begin(async (transaction) => {
|
|
2550
|
+
return await operation(createDatabaseConnection(transaction));
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2058
2553
|
// ../../src/config/database.ts
|
|
2059
2554
|
function readInteger(name, fallback) {
|
|
2060
2555
|
const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
|
|
@@ -2240,6 +2735,9 @@ function revealMfaSecret(stored) {
|
|
|
2240
2735
|
|
|
2241
2736
|
// ../../src/core/auth/authContext.ts
|
|
2242
2737
|
var authContext = createAsyncContextStore("@getstrata/authContext");
|
|
2738
|
+
function runWithAuthUser(user, callback) {
|
|
2739
|
+
return authContext.run(user, callback);
|
|
2740
|
+
}
|
|
2243
2741
|
function currentAuthUser() {
|
|
2244
2742
|
return authContext.getStore() ?? null;
|
|
2245
2743
|
}
|
|
@@ -2330,6 +2828,9 @@ function verifyTotp(secret, token, window = 1) {
|
|
|
2330
2828
|
|
|
2331
2829
|
// ../../src/core/tenant/tenantContext.ts
|
|
2332
2830
|
var tenantContext = createAsyncContextStore("@getstrata/tenantContext");
|
|
2831
|
+
function runWithTenant(tenant, callback) {
|
|
2832
|
+
return tenantContext.run(tenant, callback);
|
|
2833
|
+
}
|
|
2333
2834
|
function currentTenant() {
|
|
2334
2835
|
return tenantContext.getStore() ?? null;
|
|
2335
2836
|
}
|