@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
|
@@ -270,6 +270,14 @@ function appendWhereParts(tableName, where, params) {
|
|
|
270
270
|
}
|
|
271
271
|
return clauses.join(" AND ");
|
|
272
272
|
}
|
|
273
|
+
function buildWhereClause(tableName, where = {}) {
|
|
274
|
+
const params = [];
|
|
275
|
+
const body = appendWhereParts(tableName, where, params);
|
|
276
|
+
return {
|
|
277
|
+
clause: body.length > 0 ? ` WHERE ${body}` : "",
|
|
278
|
+
params
|
|
279
|
+
};
|
|
280
|
+
}
|
|
273
281
|
function buildWhereNodeClause(tableName, node, params) {
|
|
274
282
|
if ("where" in node) {
|
|
275
283
|
return appendWhereParts(tableName, node.where, params);
|
|
@@ -572,6 +580,30 @@ function buildDeleteByIdQuery(table, id) {
|
|
|
572
580
|
}
|
|
573
581
|
|
|
574
582
|
// ../../src/core/database/relationships.ts
|
|
583
|
+
function hasMany(definition) {
|
|
584
|
+
return {
|
|
585
|
+
type: "hasMany",
|
|
586
|
+
...definition
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function hasOne(definition) {
|
|
590
|
+
return {
|
|
591
|
+
type: "hasOne",
|
|
592
|
+
...definition
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
function belongsTo(definition) {
|
|
596
|
+
return {
|
|
597
|
+
type: "belongsTo",
|
|
598
|
+
...definition
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
function belongsToMany(definition) {
|
|
602
|
+
return {
|
|
603
|
+
type: "belongsToMany",
|
|
604
|
+
...definition
|
|
605
|
+
};
|
|
606
|
+
}
|
|
575
607
|
function indexHasManyRelation(parents, children, relation) {
|
|
576
608
|
const groups = new Map;
|
|
577
609
|
for (const parent of parents) {
|
|
@@ -587,6 +619,15 @@ function indexHasManyRelation(parents, children, relation) {
|
|
|
587
619
|
}
|
|
588
620
|
return groups;
|
|
589
621
|
}
|
|
622
|
+
function indexHasOneRelation(parents, children, relation) {
|
|
623
|
+
const grouped = indexHasManyRelation(parents, children, relation);
|
|
624
|
+
const result = new Map;
|
|
625
|
+
for (const parent of parents) {
|
|
626
|
+
const matches = grouped.get(parent[relation.localKey]) ?? [];
|
|
627
|
+
result.set(parent[relation.localKey], matches[0]);
|
|
628
|
+
}
|
|
629
|
+
return result;
|
|
630
|
+
}
|
|
590
631
|
function indexBelongsToRelation(children, parents, relation) {
|
|
591
632
|
const parentsById = new Map;
|
|
592
633
|
for (const parent of parents) {
|
|
@@ -602,6 +643,45 @@ function indexBelongsToRelation(children, parents, relation) {
|
|
|
602
643
|
}
|
|
603
644
|
return result;
|
|
604
645
|
}
|
|
646
|
+
function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
|
|
647
|
+
const relatedById = new Map;
|
|
648
|
+
for (const related of relatedRows) {
|
|
649
|
+
relatedById.set(related[relation.relatedKey], related);
|
|
650
|
+
}
|
|
651
|
+
const groups = new Map;
|
|
652
|
+
for (const parent of parents) {
|
|
653
|
+
groups.set(parent[relation.parentKey], []);
|
|
654
|
+
}
|
|
655
|
+
for (const pivot of pivotRows) {
|
|
656
|
+
const parentId = pivot[relation.foreignPivotKey];
|
|
657
|
+
const relatedId = pivot[relation.relatedPivotKey];
|
|
658
|
+
const group = groups.get(parentId);
|
|
659
|
+
const related = relatedById.get(relatedId);
|
|
660
|
+
if (!group || !related) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
group.push(related);
|
|
664
|
+
}
|
|
665
|
+
return groups;
|
|
666
|
+
}
|
|
667
|
+
function morphMany(definition) {
|
|
668
|
+
return {
|
|
669
|
+
type: "morphMany",
|
|
670
|
+
...definition
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function morphOne(definition) {
|
|
674
|
+
return {
|
|
675
|
+
type: "morphOne",
|
|
676
|
+
...definition
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
function morphTo(definition) {
|
|
680
|
+
return {
|
|
681
|
+
type: "morphTo",
|
|
682
|
+
...definition
|
|
683
|
+
};
|
|
684
|
+
}
|
|
605
685
|
function indexMorphManyRelation(parents, children, relation) {
|
|
606
686
|
const groups = new Map;
|
|
607
687
|
for (const parent of parents) {
|
|
@@ -620,6 +700,15 @@ function indexMorphManyRelation(parents, children, relation) {
|
|
|
620
700
|
}
|
|
621
701
|
return groups;
|
|
622
702
|
}
|
|
703
|
+
function indexMorphOneRelation(parents, children, relation) {
|
|
704
|
+
const grouped = indexMorphManyRelation(parents, children, relation);
|
|
705
|
+
const result = new Map;
|
|
706
|
+
for (const parent of parents) {
|
|
707
|
+
const matches = grouped.get(parent[relation.localKey]) ?? [];
|
|
708
|
+
result.set(parent[relation.localKey], matches[0]);
|
|
709
|
+
}
|
|
710
|
+
return result;
|
|
711
|
+
}
|
|
623
712
|
function indexMorphToRelation(children, parentsByType, relation) {
|
|
624
713
|
const result = new Map;
|
|
625
714
|
for (const child of children) {
|
|
@@ -640,9 +729,15 @@ function indexMorphToRelation(children, parentsByType, relation) {
|
|
|
640
729
|
var boundConnectionHolder = {
|
|
641
730
|
connection: null
|
|
642
731
|
};
|
|
732
|
+
function bindDatabaseConnection(connection) {
|
|
733
|
+
boundConnectionHolder.connection = connection;
|
|
734
|
+
}
|
|
643
735
|
function getBoundDatabaseConnection() {
|
|
644
736
|
return boundConnectionHolder.connection;
|
|
645
737
|
}
|
|
738
|
+
function resetBoundDatabaseConnection() {
|
|
739
|
+
boundConnectionHolder.connection = null;
|
|
740
|
+
}
|
|
646
741
|
|
|
647
742
|
// ../../src/core/runtime/asyncContextStore.ts
|
|
648
743
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -660,9 +755,15 @@ function createAsyncContextStore(key) {
|
|
|
660
755
|
|
|
661
756
|
// ../../src/core/database/connectionContext.ts
|
|
662
757
|
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
758
|
+
function runWithDatabaseConnection(connection, callback) {
|
|
759
|
+
return activeConnection.run(connection, callback);
|
|
760
|
+
}
|
|
663
761
|
function getActiveDatabaseConnection(fallback) {
|
|
664
762
|
return activeConnection.getStore() ?? fallback;
|
|
665
763
|
}
|
|
764
|
+
function hasActiveDatabaseConnection() {
|
|
765
|
+
return activeConnection.getStore() !== undefined;
|
|
766
|
+
}
|
|
666
767
|
|
|
667
768
|
// ../../src/core/database/queryProxy.ts
|
|
668
769
|
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
@@ -699,6 +800,12 @@ function registerDefaultDatabasePool(connection) {
|
|
|
699
800
|
defaultPool.connection = connection;
|
|
700
801
|
defaultQuery.connection = createDatabaseQueryProxy(connection);
|
|
701
802
|
}
|
|
803
|
+
function getDefaultDatabasePool() {
|
|
804
|
+
if (!defaultPool.connection) {
|
|
805
|
+
throw new Error("Default database pool is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
806
|
+
}
|
|
807
|
+
return defaultPool.connection;
|
|
808
|
+
}
|
|
702
809
|
function getDefaultDatabaseQuery() {
|
|
703
810
|
if (!defaultQuery.connection) {
|
|
704
811
|
throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
@@ -1276,10 +1383,327 @@ class BaseRepository {
|
|
|
1276
1383
|
}
|
|
1277
1384
|
}
|
|
1278
1385
|
var baseRepository_default = BaseRepository;
|
|
1386
|
+
// ../../src/core/database/connection.ts
|
|
1387
|
+
function createDatabaseConnection(source) {
|
|
1388
|
+
return {
|
|
1389
|
+
async unsafe(query, params = []) {
|
|
1390
|
+
return await source.unsafe(query, params);
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1279
1394
|
// ../../src/core/database/model.ts
|
|
1280
1395
|
var modelRepositories = new WeakMap;
|
|
1281
1396
|
var modelGlobalScopes = new WeakMap;
|
|
1282
1397
|
var modelBooted = new WeakSet;
|
|
1398
|
+
function resolveModelRepository(model) {
|
|
1399
|
+
const repository = modelRepositories.get(model);
|
|
1400
|
+
if (!repository) {
|
|
1401
|
+
throw new Error(`${model.name}.repository() is not implemented.`);
|
|
1402
|
+
}
|
|
1403
|
+
return repository;
|
|
1404
|
+
}
|
|
1405
|
+
function modelStatics(model) {
|
|
1406
|
+
return model;
|
|
1407
|
+
}
|
|
1408
|
+
function ensureBooted(model) {
|
|
1409
|
+
if (modelBooted.has(model)) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
modelBooted.add(model);
|
|
1413
|
+
const boot = model.boot;
|
|
1414
|
+
if (typeof boot === "function") {
|
|
1415
|
+
boot.call(model);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
function getGlobalScopes(model) {
|
|
1419
|
+
return modelGlobalScopes.get(model) ?? [];
|
|
1420
|
+
}
|
|
1421
|
+
function hydrateValue(value, cast) {
|
|
1422
|
+
if (value === null || value === undefined) {
|
|
1423
|
+
return value;
|
|
1424
|
+
}
|
|
1425
|
+
switch (cast) {
|
|
1426
|
+
case "date":
|
|
1427
|
+
case "datetime":
|
|
1428
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
1429
|
+
case "json":
|
|
1430
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
1431
|
+
case "bool":
|
|
1432
|
+
case "boolean":
|
|
1433
|
+
return value === true || value === 1 || value === "1" || value === "true";
|
|
1434
|
+
default:
|
|
1435
|
+
return value;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
function dehydrateValue(value, cast) {
|
|
1439
|
+
if (value === null || value === undefined) {
|
|
1440
|
+
return value;
|
|
1441
|
+
}
|
|
1442
|
+
switch (cast) {
|
|
1443
|
+
case "date":
|
|
1444
|
+
case "datetime":
|
|
1445
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
1446
|
+
case "json":
|
|
1447
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
1448
|
+
case "bool":
|
|
1449
|
+
case "boolean":
|
|
1450
|
+
return Boolean(value);
|
|
1451
|
+
default:
|
|
1452
|
+
return value;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
function filterMassAssignable(fillable, guarded, input) {
|
|
1456
|
+
const resolvedGuarded = guarded ?? true;
|
|
1457
|
+
if (fillable && fillable.length > 0) {
|
|
1458
|
+
const allowed = new Set(fillable);
|
|
1459
|
+
return Object.fromEntries(Object.entries(input).filter(([key]) => allowed.has(key)));
|
|
1460
|
+
}
|
|
1461
|
+
if (resolvedGuarded === true || resolvedGuarded.includes("*")) {
|
|
1462
|
+
return {};
|
|
1463
|
+
}
|
|
1464
|
+
const blocked = new Set(resolvedGuarded);
|
|
1465
|
+
return Object.fromEntries(Object.entries(input).filter(([key]) => !blocked.has(key)));
|
|
1466
|
+
}
|
|
1467
|
+
function applyCasts(values, casts, direction) {
|
|
1468
|
+
if (Object.keys(casts).length === 0) {
|
|
1469
|
+
return values;
|
|
1470
|
+
}
|
|
1471
|
+
const result = { ...values };
|
|
1472
|
+
const castFn = direction === "hydrate" ? hydrateValue : dehydrateValue;
|
|
1473
|
+
for (const [key, cast] of Object.entries(casts)) {
|
|
1474
|
+
if (key in result && cast) {
|
|
1475
|
+
result[key] = castFn(result[key], cast);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
return result;
|
|
1479
|
+
}
|
|
1480
|
+
function applyTimestampsOnCreate(columns, values, enabled) {
|
|
1481
|
+
if (!enabled) {
|
|
1482
|
+
return values;
|
|
1483
|
+
}
|
|
1484
|
+
const now = new Date;
|
|
1485
|
+
const result = { ...values };
|
|
1486
|
+
if (columns.includes("created_at")) {
|
|
1487
|
+
result.created_at = now;
|
|
1488
|
+
}
|
|
1489
|
+
if (columns.includes("updated_at")) {
|
|
1490
|
+
result.updated_at = now;
|
|
1491
|
+
}
|
|
1492
|
+
return result;
|
|
1493
|
+
}
|
|
1494
|
+
function applyTimestampsOnUpdate(columns, values, enabled) {
|
|
1495
|
+
if (!enabled) {
|
|
1496
|
+
return values;
|
|
1497
|
+
}
|
|
1498
|
+
const result = { ...values };
|
|
1499
|
+
if (columns.includes("updated_at")) {
|
|
1500
|
+
result.updated_at = new Date;
|
|
1501
|
+
}
|
|
1502
|
+
return result;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
class Model {
|
|
1506
|
+
attributes;
|
|
1507
|
+
repository;
|
|
1508
|
+
static $fillable;
|
|
1509
|
+
static $guarded;
|
|
1510
|
+
static $casts = {};
|
|
1511
|
+
static $timestamps = true;
|
|
1512
|
+
_exists;
|
|
1513
|
+
constructor(attributes, repository, exists = true) {
|
|
1514
|
+
this.attributes = attributes;
|
|
1515
|
+
this.repository = repository;
|
|
1516
|
+
this._exists = exists;
|
|
1517
|
+
}
|
|
1518
|
+
get $exists() {
|
|
1519
|
+
return this._exists;
|
|
1520
|
+
}
|
|
1521
|
+
get(key) {
|
|
1522
|
+
return this.attributes[key];
|
|
1523
|
+
}
|
|
1524
|
+
get id() {
|
|
1525
|
+
return this.attributes[this.primaryKey()];
|
|
1526
|
+
}
|
|
1527
|
+
toObject() {
|
|
1528
|
+
return { ...this.attributes };
|
|
1529
|
+
}
|
|
1530
|
+
primaryKey() {
|
|
1531
|
+
throw new Error(`${this.constructor.name}.primaryKey() is not implemented.`);
|
|
1532
|
+
}
|
|
1533
|
+
static primaryKeyField() {
|
|
1534
|
+
return resolveModelRepository(this).getTable().primaryKey;
|
|
1535
|
+
}
|
|
1536
|
+
static hydrateAttributes(attributes) {
|
|
1537
|
+
const casts = modelStatics(this).$casts ?? {};
|
|
1538
|
+
return applyCasts(attributes, casts, "hydrate");
|
|
1539
|
+
}
|
|
1540
|
+
static dehydrateAttributes(attributes) {
|
|
1541
|
+
const casts = modelStatics(this).$casts ?? {};
|
|
1542
|
+
return applyCasts(attributes, casts, "dehydrate");
|
|
1543
|
+
}
|
|
1544
|
+
static fromRecord(record, repository, exists = true) {
|
|
1545
|
+
const statics = modelStatics(this);
|
|
1546
|
+
const hydrated = statics.hydrateAttributes(record);
|
|
1547
|
+
return new statics(hydrated, repository, exists);
|
|
1548
|
+
}
|
|
1549
|
+
static boot() {}
|
|
1550
|
+
static addGlobalScope(_name, scope) {
|
|
1551
|
+
ensureBooted(this);
|
|
1552
|
+
const existing = modelGlobalScopes.get(this) ?? [];
|
|
1553
|
+
modelGlobalScopes.set(this, [
|
|
1554
|
+
...existing,
|
|
1555
|
+
scope
|
|
1556
|
+
]);
|
|
1557
|
+
}
|
|
1558
|
+
static repository() {
|
|
1559
|
+
return resolveModelRepository(this);
|
|
1560
|
+
}
|
|
1561
|
+
static query() {
|
|
1562
|
+
ensureBooted(this);
|
|
1563
|
+
const repository = resolveModelRepository(this);
|
|
1564
|
+
let query = repository.query();
|
|
1565
|
+
for (const scope of getGlobalScopes(this)) {
|
|
1566
|
+
query = scope(query);
|
|
1567
|
+
}
|
|
1568
|
+
return query;
|
|
1569
|
+
}
|
|
1570
|
+
static async create(attributes) {
|
|
1571
|
+
const statics = modelStatics(this);
|
|
1572
|
+
ensureBooted(this);
|
|
1573
|
+
const repository = resolveModelRepository(this);
|
|
1574
|
+
const table = repository.getTable();
|
|
1575
|
+
const timestamps = statics.$timestamps ?? true;
|
|
1576
|
+
const assignable = filterMassAssignable(statics.$fillable, statics.$guarded, attributes);
|
|
1577
|
+
const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
|
|
1578
|
+
const payload = statics.dehydrateAttributes(withTimestamps);
|
|
1579
|
+
const record = await repository.create(payload);
|
|
1580
|
+
return statics.fromRecord(record, repository, true);
|
|
1581
|
+
}
|
|
1582
|
+
static async find(id) {
|
|
1583
|
+
const statics = modelStatics(this);
|
|
1584
|
+
const repository = resolveModelRepository(this);
|
|
1585
|
+
const primaryKey = repository.getTable().primaryKey;
|
|
1586
|
+
const record = await Model.query.call(this).where({ [primaryKey]: id }).first();
|
|
1587
|
+
return record ? statics.fromRecord(record, repository, true) : null;
|
|
1588
|
+
}
|
|
1589
|
+
static async findOrFail(id, errorFactory) {
|
|
1590
|
+
const model = await Model.find.call(this, id);
|
|
1591
|
+
if (model) {
|
|
1592
|
+
return model;
|
|
1593
|
+
}
|
|
1594
|
+
throw errorFactory?.(id) ?? new NotFoundError(`${this.name} ${String(id)} not found.`);
|
|
1595
|
+
}
|
|
1596
|
+
static async all(options = {}) {
|
|
1597
|
+
const statics = modelStatics(this);
|
|
1598
|
+
const repository = resolveModelRepository(this);
|
|
1599
|
+
let query = Model.query.call(this);
|
|
1600
|
+
if (options.orderBy) {
|
|
1601
|
+
query = query.orderBy(options.orderBy);
|
|
1602
|
+
}
|
|
1603
|
+
if (options.limit !== undefined) {
|
|
1604
|
+
query = query.limit(options.limit);
|
|
1605
|
+
}
|
|
1606
|
+
const rows = await query.get();
|
|
1607
|
+
return rows.map((row) => statics.fromRecord(row, repository, true));
|
|
1608
|
+
}
|
|
1609
|
+
static async firstWhere(where, options = {}) {
|
|
1610
|
+
const statics = modelStatics(this);
|
|
1611
|
+
const repository = resolveModelRepository(this);
|
|
1612
|
+
let query = Model.query.call(this).where(where);
|
|
1613
|
+
if (options.orderBy) {
|
|
1614
|
+
query = query.orderBy(options.orderBy);
|
|
1615
|
+
}
|
|
1616
|
+
const record = await query.first();
|
|
1617
|
+
return record ? statics.fromRecord(record, repository, true) : null;
|
|
1618
|
+
}
|
|
1619
|
+
async save() {
|
|
1620
|
+
const ModelClass = modelStatics(this.constructor);
|
|
1621
|
+
const timestamps = ModelClass.$timestamps ?? true;
|
|
1622
|
+
const casts = ModelClass.$casts ?? {};
|
|
1623
|
+
const table = this.repository.getTable();
|
|
1624
|
+
if (this.$exists) {
|
|
1625
|
+
const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
|
|
1626
|
+
const record2 = await this.repository.updateByIdOrThrow(this.id, changes);
|
|
1627
|
+
this.attributes = ModelClass.hydrateAttributes(record2);
|
|
1628
|
+
return this;
|
|
1629
|
+
}
|
|
1630
|
+
const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
|
|
1631
|
+
const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
|
|
1632
|
+
const payload = ModelClass.dehydrateAttributes(withTimestamps);
|
|
1633
|
+
const record = await this.repository.create(payload);
|
|
1634
|
+
this.attributes = ModelClass.hydrateAttributes(record);
|
|
1635
|
+
this._exists = true;
|
|
1636
|
+
return this;
|
|
1637
|
+
}
|
|
1638
|
+
async update(changes) {
|
|
1639
|
+
const ModelClass = modelStatics(this.constructor);
|
|
1640
|
+
const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, changes);
|
|
1641
|
+
Object.assign(this.attributes, assignable);
|
|
1642
|
+
return await this.save();
|
|
1643
|
+
}
|
|
1644
|
+
async delete() {
|
|
1645
|
+
if (resolveSoftDeleteColumn(this.repository.getTable())) {
|
|
1646
|
+
return await this.repository.deleteById(this.id);
|
|
1647
|
+
}
|
|
1648
|
+
return await this.repository.forceDeleteById(this.id);
|
|
1649
|
+
}
|
|
1650
|
+
async forceDelete() {
|
|
1651
|
+
return await this.repository.forceDeleteById(this.id);
|
|
1652
|
+
}
|
|
1653
|
+
async restore() {
|
|
1654
|
+
const ModelClass = modelStatics(this.constructor);
|
|
1655
|
+
const record = await this.repository.restoreById(this.id);
|
|
1656
|
+
if (!record) {
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
this.attributes = ModelClass.hydrateAttributes(record);
|
|
1660
|
+
return this;
|
|
1661
|
+
}
|
|
1662
|
+
async loadHasMany(as, relation, childRepository, options = {}) {
|
|
1663
|
+
const grouped = await childRepository.withConnection(this.repository.getConnection()).loadHasManyForParents([this.attributes], relation, options);
|
|
1664
|
+
const loaded = grouped.get(this.attributes[relation.localKey]) ?? [];
|
|
1665
|
+
return Object.assign(this, { [as]: loaded });
|
|
1666
|
+
}
|
|
1667
|
+
async loadHasOne(as, relation, childRepository, options = {}) {
|
|
1668
|
+
const loaded = await this.loadHasMany(as, relation, childRepository, { ...options, limit: 1 });
|
|
1669
|
+
const value = loaded[as]?.[0];
|
|
1670
|
+
return Object.assign(this, { [as]: value });
|
|
1671
|
+
}
|
|
1672
|
+
async loadBelongsTo(as, relation, parentRepository, options = {}) {
|
|
1673
|
+
const grouped = await this.repository.loadBelongsToForParents([this.attributes], relation, parentRepository, options);
|
|
1674
|
+
const loaded = grouped.get(this.attributes[relation.foreignKey]);
|
|
1675
|
+
return Object.assign(this, { [as]: loaded });
|
|
1676
|
+
}
|
|
1677
|
+
async loadBelongsToMany(as, relation, relatedRepository, options = {}) {
|
|
1678
|
+
const connection = this.repository.getConnection();
|
|
1679
|
+
const parentId = this.attributes[relation.parentKey];
|
|
1680
|
+
const pivotRows = await connection.unsafe(`SELECT * FROM ${relation.pivotTable} WHERE ${String(relation.foreignPivotKey)} = $1`, [parentId]);
|
|
1681
|
+
if (pivotRows.length === 0) {
|
|
1682
|
+
return Object.assign(this, { [as]: [] });
|
|
1683
|
+
}
|
|
1684
|
+
const relatedIds = [
|
|
1685
|
+
...new Set(pivotRows.map((row) => row[relation.relatedPivotKey]))
|
|
1686
|
+
];
|
|
1687
|
+
const relatedRows = await relatedRepository.withConnection(connection).findAll({
|
|
1688
|
+
...options,
|
|
1689
|
+
where: {
|
|
1690
|
+
[relation.relatedKey]: relatedIds
|
|
1691
|
+
}
|
|
1692
|
+
});
|
|
1693
|
+
const grouped = indexBelongsToManyRelation([this.attributes], pivotRows, relatedRows, relation);
|
|
1694
|
+
const loaded = grouped.get(parentId) ?? [];
|
|
1695
|
+
return Object.assign(this, { [as]: loaded });
|
|
1696
|
+
}
|
|
1697
|
+
mergeAttributes(patch) {
|
|
1698
|
+
Object.assign(this.attributes, patch);
|
|
1699
|
+
return this;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
function registerModelRepository(model, repository) {
|
|
1703
|
+
modelRepositories.set(model, repository);
|
|
1704
|
+
ensureBooted(model);
|
|
1705
|
+
return model;
|
|
1706
|
+
}
|
|
1283
1707
|
// ../../src/core/database/schema/columnDefinition.ts
|
|
1284
1708
|
class ColumnDefinition {
|
|
1285
1709
|
name;
|
|
@@ -1501,6 +1925,45 @@ class Blueprint {
|
|
|
1501
1925
|
});
|
|
1502
1926
|
}
|
|
1503
1927
|
}
|
|
1928
|
+
// ../../src/core/database/schema/driver.ts
|
|
1929
|
+
function normalizeConnectionName(connection) {
|
|
1930
|
+
const normalized = connection.trim().toLowerCase();
|
|
1931
|
+
if (normalized === "pgsql" || normalized === "postgres" || normalized === "postgresql") {
|
|
1932
|
+
return "pgsql";
|
|
1933
|
+
}
|
|
1934
|
+
if (normalized === "mysql" || normalized === "mariadb") {
|
|
1935
|
+
return "mysql";
|
|
1936
|
+
}
|
|
1937
|
+
if (normalized === "sqlite") {
|
|
1938
|
+
return "sqlite";
|
|
1939
|
+
}
|
|
1940
|
+
throw new Error(`Unsupported DB_CONNECTION: ${connection}`);
|
|
1941
|
+
}
|
|
1942
|
+
function resolveDriverFromUrl(url) {
|
|
1943
|
+
const normalized = url.trim().toLowerCase();
|
|
1944
|
+
if (normalized.startsWith("postgres://") || normalized.startsWith("postgresql://") || normalized.startsWith("postgres:")) {
|
|
1945
|
+
return "pgsql";
|
|
1946
|
+
}
|
|
1947
|
+
if (normalized.startsWith("mysql://") || normalized.startsWith("mysql:")) {
|
|
1948
|
+
return "mysql";
|
|
1949
|
+
}
|
|
1950
|
+
if (normalized.startsWith("sqlite:")) {
|
|
1951
|
+
return "sqlite";
|
|
1952
|
+
}
|
|
1953
|
+
return null;
|
|
1954
|
+
}
|
|
1955
|
+
function resolveDatabaseDriver(options = {}) {
|
|
1956
|
+
const connection = options.connection ?? process.env.DB_CONNECTION;
|
|
1957
|
+
if (connection) {
|
|
1958
|
+
return normalizeConnectionName(connection);
|
|
1959
|
+
}
|
|
1960
|
+
const url = options.url ?? process.env.DATABASE_URL ?? "";
|
|
1961
|
+
const fromUrl = resolveDriverFromUrl(url);
|
|
1962
|
+
if (fromUrl) {
|
|
1963
|
+
return fromUrl;
|
|
1964
|
+
}
|
|
1965
|
+
return "pgsql";
|
|
1966
|
+
}
|
|
1504
1967
|
// ../../src/core/database/schema/errors.ts
|
|
1505
1968
|
class UnsupportedSchemaFeatureError extends Error {
|
|
1506
1969
|
constructor(feature, driver) {
|
|
@@ -1845,10 +2308,42 @@ class SchemaBuilder {
|
|
|
1845
2308
|
}
|
|
1846
2309
|
}
|
|
1847
2310
|
}
|
|
2311
|
+
|
|
2312
|
+
class Schema {
|
|
2313
|
+
static builder(driver) {
|
|
2314
|
+
return new SchemaBuilder(driver ?? resolveDatabaseDriver());
|
|
2315
|
+
}
|
|
2316
|
+
static async run(db, driver, callback) {
|
|
2317
|
+
const schema = Schema.builder(driver);
|
|
2318
|
+
await callback(schema);
|
|
2319
|
+
await schema.execute(db);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
function createSchemaBuilder(db, driver) {
|
|
2323
|
+
const builder = Schema.builder(driver);
|
|
2324
|
+
return Object.assign(builder, {
|
|
2325
|
+
async commit() {
|
|
2326
|
+
await builder.execute(db);
|
|
2327
|
+
}
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
1848
2330
|
// ../../src/core/database/table.ts
|
|
1849
2331
|
function defineTable(definition) {
|
|
1850
2332
|
return definition;
|
|
1851
2333
|
}
|
|
2334
|
+
// ../../src/core/database/transaction.ts
|
|
2335
|
+
function supportsTransactions(connection) {
|
|
2336
|
+
return typeof connection.begin === "function";
|
|
2337
|
+
}
|
|
2338
|
+
async function runInTransaction(operation) {
|
|
2339
|
+
const pool = resolveRepositoryConnection();
|
|
2340
|
+
if (!supportsTransactions(pool)) {
|
|
2341
|
+
throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
|
|
2342
|
+
}
|
|
2343
|
+
return await pool.begin(async (transaction) => {
|
|
2344
|
+
return await operation(createDatabaseConnection(transaction));
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
1852
2347
|
// ../../src/core/queue/failedJobTable.ts
|
|
1853
2348
|
var failedJobTable = defineTable({
|
|
1854
2349
|
name: "failed_job",
|
|
@@ -2116,6 +2611,18 @@ class QueueWorker {
|
|
|
2116
2611
|
this.client.close();
|
|
2117
2612
|
}
|
|
2118
2613
|
}
|
|
2614
|
+
async function countPendingQueueJobs(redisUrl) {
|
|
2615
|
+
const client = new RedisClient(redisUrl);
|
|
2616
|
+
try {
|
|
2617
|
+
let total = 0;
|
|
2618
|
+
for (const queueKey of QUEUE_KEYS) {
|
|
2619
|
+
total += await client.llen(queueKey);
|
|
2620
|
+
}
|
|
2621
|
+
return total;
|
|
2622
|
+
} finally {
|
|
2623
|
+
client.close();
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2119
2626
|
|
|
2120
2627
|
// ../../src/core/queue/resilientQueue.ts
|
|
2121
2628
|
class ResilientQueue {
|