@getstrata/core 0.5.42 → 0.5.44
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/entries/admin/formatValue.js +32 -0
- package/dist/entries/admin/registry.js +32 -0
- package/dist/entries/audit/siemFormatter.js +37 -0
- package/dist/entries/auth/abilityChecker.js +1 -0
- package/dist/entries/auth/membershipMiddleware.js +298 -0
- package/dist/entries/auth/scimAuthMiddleware.js +251 -0
- package/dist/entries/auth/sessionGuard.js +72 -0
- package/dist/entries/database/migrations/types.js +1 -0
- package/dist/entries/database/migrations.js +127 -0
- package/dist/entries/database/schema.js +1054 -0
- package/dist/entries/database/seeders/types.js +1 -0
- package/dist/entries/http/conditionalResponse.js +192 -0
- package/dist/entries/http/corsMiddleware.js +54 -0
- package/dist/entries/http/csrfMiddleware.js +236 -0
- package/dist/entries/http/csrfToken.js +3 -0
- package/dist/entries/http/flashMiddleware.js +143 -0
- package/dist/entries/http/formRequest.js +152 -0
- package/dist/entries/http/loginThrottleMiddleware.js +46 -0
- package/dist/entries/http/memoryThrottleMiddleware.js +30 -0
- package/dist/entries/http/requireAbilityMiddleware.js +110 -0
- package/dist/entries/http/requireAuthMiddleware.js +80 -0
- package/dist/entries/http/requireGlobalAdminMiddleware.js +147 -0
- package/dist/entries/http/requireWebAuthMiddleware.js +107 -0
- package/dist/entries/http/route.js +8 -0
- package/dist/entries/http/routeMiddleware.js +32 -0
- package/dist/entries/http/routeModelBinding.js +141 -0
- package/dist/entries/http/scimThrottleMiddleware.js +23 -0
- package/dist/entries/http/securedRouteModelBinding.js +10 -0
- package/dist/entries/http/securityHeadersMiddleware.js +77 -0
- package/dist/entries/http/throttleMiddleware.js +87 -0
- package/dist/entries/http/webErrorResponse.js +72 -0
- package/dist/entries/http/webFormRequest.js +10 -0
- package/dist/entries/logging/requestLoggingMiddleware.js +89 -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 +58 -0
- package/dist/entries/queue/failedJobRepository.js +58 -0
- package/dist/entries/queue/publicQueue.js +58 -0
- package/dist/entries/queue/queueMetrics.js +58 -0
- package/dist/entries/runtime/asyncContextStore.js +17 -0
- package/dist/entries/security/safeFetch.js +211 -0
- package/dist/entries/security/timingSafeCompare.js +14 -0
- package/dist/entries/tenant/databaseTenantContext.js +116 -0
- package/dist/entries/tenant/tenantDatabaseScope.js +10 -0
- package/dist/entries/tracing/tracingMiddleware.js +103 -0
- package/dist/entries/view.js +72 -0
- package/package.json +202 -7
|
@@ -2131,6 +2131,45 @@ class Blueprint {
|
|
|
2131
2131
|
});
|
|
2132
2132
|
}
|
|
2133
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
|
+
}
|
|
2134
2173
|
// ../../src/core/database/schema/errors.ts
|
|
2135
2174
|
class UnsupportedSchemaFeatureError extends Error {
|
|
2136
2175
|
constructor(feature, driver) {
|
|
@@ -2475,6 +2514,25 @@ class SchemaBuilder {
|
|
|
2475
2514
|
}
|
|
2476
2515
|
}
|
|
2477
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
|
+
}
|
|
2478
2536
|
// ../../src/core/database/table.ts
|
|
2479
2537
|
function defineTable(definition) {
|
|
2480
2538
|
return definition;
|
|
@@ -2545,6 +2603,7 @@ var db = new Proxy(function database() {}, {
|
|
|
2545
2603
|
return typeof value === "function" ? value.bind(connection) : value;
|
|
2546
2604
|
}
|
|
2547
2605
|
});
|
|
2606
|
+
var connection_default = db;
|
|
2548
2607
|
|
|
2549
2608
|
// ../../src/modules/user/apiTokenTable.ts
|
|
2550
2609
|
var apiTokenTable = defineTable({
|
|
@@ -2686,6 +2745,9 @@ function currentAuthUser() {
|
|
|
2686
2745
|
|
|
2687
2746
|
// ../../src/core/http/requestMetaContext.ts
|
|
2688
2747
|
var requestMetaContext = createAsyncContextStore("@getstrata/requestMetaContext");
|
|
2748
|
+
function runWithRequestMeta(meta, callback) {
|
|
2749
|
+
return requestMetaContext.run(meta, callback);
|
|
2750
|
+
}
|
|
2689
2751
|
function currentRequestMeta() {
|
|
2690
2752
|
return requestMetaContext.getStore() ?? {
|
|
2691
2753
|
ipAddress: null,
|
|
@@ -2779,6 +2841,16 @@ function currentTenant() {
|
|
|
2779
2841
|
function currentTenantId() {
|
|
2780
2842
|
return currentTenant()?.id ?? 1;
|
|
2781
2843
|
}
|
|
2844
|
+
function rateLimitMultiplierForPlan(plan) {
|
|
2845
|
+
switch (plan) {
|
|
2846
|
+
case "enterprise":
|
|
2847
|
+
return 4;
|
|
2848
|
+
case "pro":
|
|
2849
|
+
return 2;
|
|
2850
|
+
default:
|
|
2851
|
+
return 1;
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2782
2854
|
|
|
2783
2855
|
// ../../src/modules/user/authService.ts
|
|
2784
2856
|
class AuthService {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// @bun
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// ../../src/core/database/migrations/runner.ts
|
|
3
|
+
import { readdir } from "fs/promises";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { pathToFileURL } from "url";
|
|
6
|
+
|
|
7
|
+
// ../../src/core/database/migrations/advisoryLock.ts
|
|
8
|
+
var MIGRATION_LOCK_KEY = 42424242;
|
|
9
|
+
async function withMigrationLock(db, callback, lockKey = MIGRATION_LOCK_KEY) {
|
|
10
|
+
await db.unsafe("SELECT pg_advisory_lock($1)", [lockKey]);
|
|
11
|
+
try {
|
|
12
|
+
return await callback();
|
|
13
|
+
} finally {
|
|
14
|
+
await db.unsafe("SELECT pg_advisory_unlock($1)", [lockKey]);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ../../src/core/database/migrations/runner.ts
|
|
19
|
+
var MIGRATIONS_TABLE = "framework_migrations";
|
|
20
|
+
async function ensureMigrationsTable(db) {
|
|
21
|
+
await db.unsafe(`
|
|
22
|
+
CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
|
|
23
|
+
name TEXT PRIMARY KEY,
|
|
24
|
+
batch INTEGER NOT NULL,
|
|
25
|
+
run_on TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
26
|
+
)
|
|
27
|
+
`);
|
|
28
|
+
}
|
|
29
|
+
async function getAppliedMigrations(db) {
|
|
30
|
+
await ensureMigrationsTable(db);
|
|
31
|
+
return await db.unsafe(`
|
|
32
|
+
SELECT name, batch
|
|
33
|
+
FROM ${MIGRATIONS_TABLE}
|
|
34
|
+
ORDER BY batch ASC, name ASC
|
|
35
|
+
`);
|
|
36
|
+
}
|
|
37
|
+
async function loadMigrationsFromDirectory(directory) {
|
|
38
|
+
const entries = await readdir(directory);
|
|
39
|
+
const migrationFiles = entries.filter((entry) => (entry.endsWith(".ts") || entry.endsWith(".js")) && entry !== "types.ts" && entry !== "index.ts" && entry !== "runner.ts").sort();
|
|
40
|
+
const loadedMigrations = await Promise.all(migrationFiles.map(async (fileName) => {
|
|
41
|
+
const moduleUrl = pathToFileURL(join(directory, fileName)).href;
|
|
42
|
+
const module = await import(moduleUrl);
|
|
43
|
+
return module.default;
|
|
44
|
+
}));
|
|
45
|
+
return loadedMigrations.filter((migration) => migration?.name !== undefined);
|
|
46
|
+
}
|
|
47
|
+
async function getMigrationStatus(db, migrations) {
|
|
48
|
+
const applied = await getAppliedMigrations(db);
|
|
49
|
+
const appliedByName = new Map(applied.map(({ name, batch }) => [name, Number(batch)]));
|
|
50
|
+
return migrations.map(({ name }) => ({
|
|
51
|
+
name,
|
|
52
|
+
status: appliedByName.has(name) ? "up" : "pending",
|
|
53
|
+
batch: appliedByName.get(name) ?? null
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
async function runPendingMigrations(db, migrations, options = {}) {
|
|
57
|
+
const applied = await getAppliedMigrations(db);
|
|
58
|
+
const appliedNames = new Set(applied.map(({ name }) => name));
|
|
59
|
+
const nextBatch = applied.reduce((currentMax, { batch }) => Math.max(currentMax, Number(batch)), 0) + 1;
|
|
60
|
+
const pendingMigrations = migrations.filter(({ name }) => !appliedNames.has(name));
|
|
61
|
+
for (const migration of pendingMigrations) {
|
|
62
|
+
options.onMigration?.(migration.name);
|
|
63
|
+
await migration.up(db);
|
|
64
|
+
const inserted = await db.unsafe(`INSERT INTO ${MIGRATIONS_TABLE} (name, batch) VALUES ($1, $2) ON CONFLICT (name) DO NOTHING RETURNING name`, [migration.name, nextBatch]);
|
|
65
|
+
if (inserted.length === 0) {
|
|
66
|
+
throw new Error(`Migration ${migration.name} was applied but not recorded.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return pendingMigrations.length;
|
|
70
|
+
}
|
|
71
|
+
async function migrateDatabase(db, migrations, options = {}) {
|
|
72
|
+
const { advisoryLock = false, onMigration } = options;
|
|
73
|
+
if (advisoryLock) {
|
|
74
|
+
return withMigrationLock(db, () => runPendingMigrations(db, migrations, { onMigration }));
|
|
75
|
+
}
|
|
76
|
+
return runPendingMigrations(db, migrations, { onMigration });
|
|
77
|
+
}
|
|
78
|
+
async function rollbackDatabase(db, migrations, options = {}) {
|
|
79
|
+
const applied = await getAppliedMigrations(db);
|
|
80
|
+
if (applied.length === 0) {
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
const lastBatch = applied.reduce((currentMax, { batch }) => Math.max(currentMax, Number(batch)), 0);
|
|
84
|
+
const migrationsToRollback = new Set(applied.filter(({ batch }) => Number(batch) === lastBatch).map(({ name }) => name));
|
|
85
|
+
let rolledBack = 0;
|
|
86
|
+
for (const migration of [...migrations].reverse()) {
|
|
87
|
+
if (!migrationsToRollback.has(migration.name)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
options.onMigration?.(migration.name);
|
|
91
|
+
await migration.down(db);
|
|
92
|
+
await db.unsafe(`DELETE FROM ${MIGRATIONS_TABLE} WHERE name = $1`, [migration.name]);
|
|
93
|
+
rolledBack += 1;
|
|
94
|
+
}
|
|
95
|
+
return rolledBack;
|
|
96
|
+
}
|
|
97
|
+
async function freshDatabase(db, migrations, options = {}) {
|
|
98
|
+
const runFresh = async () => {
|
|
99
|
+
const applied = await getAppliedMigrations(db);
|
|
100
|
+
const appliedNames = new Set(applied.map(({ name }) => name));
|
|
101
|
+
const appliedMigrations = migrations.filter(({ name }) => appliedNames.has(name));
|
|
102
|
+
for (const migration of [...appliedMigrations].reverse()) {
|
|
103
|
+
options.onMigration?.(migration.name);
|
|
104
|
+
await migration.down(db);
|
|
105
|
+
}
|
|
106
|
+
if (appliedMigrations.length > 0) {
|
|
107
|
+
await db.unsafe(`DELETE FROM ${MIGRATIONS_TABLE}`);
|
|
108
|
+
}
|
|
109
|
+
await runPendingMigrations(db, migrations, options);
|
|
110
|
+
};
|
|
111
|
+
if (options.advisoryLock) {
|
|
112
|
+
await withMigrationLock(db, runFresh);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await runFresh();
|
|
116
|
+
}
|
|
117
|
+
export {
|
|
118
|
+
withMigrationLock,
|
|
119
|
+
runPendingMigrations,
|
|
120
|
+
rollbackDatabase,
|
|
121
|
+
migrateDatabase,
|
|
122
|
+
loadMigrationsFromDirectory,
|
|
123
|
+
getMigrationStatus,
|
|
124
|
+
getAppliedMigrations,
|
|
125
|
+
freshDatabase,
|
|
126
|
+
ensureMigrationsTable
|
|
127
|
+
};
|