@getstrata/bootstrap 0.2.26 → 0.2.28
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/bootstrap/listeners/invalidateCacheOnModelWrite.d.ts +1 -1
- package/dist/entries/buildModuleRoutes.js +3 -0
- package/dist/entries/buildWebModuleRoutes.js +3 -0
- package/dist/entries/context.js +148 -124
- package/dist/entries/createSpaRoutes.js +62 -0
- package/dist/entries/createWebRoutes.js +3 -0
- package/dist/entries/dependencies.js +148 -124
- package/dist/entries/health.js +212 -0
- package/dist/entries/httpKernel.js +3 -0
- package/dist/entries/listeners/invalidateCacheOnModelWrite.js +118 -0
- package/dist/entries/metricsRoutes.js +16 -0
- package/dist/entries/providers/view.js +22 -0
- package/dist/entries/providers.js +226 -202
- package/dist/entries/web/routing.js +3 -0
- package/dist/index.js +441 -555
- package/package.json +22 -2
|
@@ -43,6 +43,9 @@ function readFrontendMode() {
|
|
|
43
43
|
function isViewsEnabled() {
|
|
44
44
|
return readFrontendMode() === "server-htmx";
|
|
45
45
|
}
|
|
46
|
+
function isSpaEnabled() {
|
|
47
|
+
return readFrontendMode() === "spa-react";
|
|
48
|
+
}
|
|
46
49
|
|
|
47
50
|
// ../../src/config/rateLimit.ts
|
|
48
51
|
var LOCAL_LOGIN_RATE_LIMIT = {
|
|
@@ -46,6 +46,9 @@ function readFrontendMode() {
|
|
|
46
46
|
function isViewsEnabled() {
|
|
47
47
|
return readFrontendMode() === "server-htmx";
|
|
48
48
|
}
|
|
49
|
+
function isSpaEnabled() {
|
|
50
|
+
return readFrontendMode() === "spa-react";
|
|
51
|
+
}
|
|
49
52
|
|
|
50
53
|
// ../../src/config/rateLimit.ts
|
|
51
54
|
var LOCAL_LOGIN_RATE_LIMIT = {
|
package/dist/entries/context.js
CHANGED
|
@@ -284,6 +284,25 @@ function getDb() {
|
|
|
284
284
|
getDatabase();
|
|
285
285
|
return getDefaultDatabaseQuery();
|
|
286
286
|
}
|
|
287
|
+
async function pingDatabase(connection = getDatabase()) {
|
|
288
|
+
try {
|
|
289
|
+
await connection`SELECT 1`;
|
|
290
|
+
return true;
|
|
291
|
+
} catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async function ensureDatabaseConnection() {
|
|
296
|
+
if (await pingDatabase()) {
|
|
297
|
+
return getDatabase();
|
|
298
|
+
}
|
|
299
|
+
await getDatabase().close().catch(() => {
|
|
300
|
+
return;
|
|
301
|
+
});
|
|
302
|
+
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
303
|
+
registerDefaultDatabasePool(connectionHolder.connection);
|
|
304
|
+
return getDatabase();
|
|
305
|
+
}
|
|
287
306
|
var db = new Proxy(function database() {}, {
|
|
288
307
|
apply(_target, _thisArg, args) {
|
|
289
308
|
return getDb()(...args);
|
|
@@ -1393,25 +1412,117 @@ function discoverListeners() {
|
|
|
1393
1412
|
return appListeners;
|
|
1394
1413
|
}
|
|
1395
1414
|
|
|
1396
|
-
// ../../src/
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1415
|
+
// ../../src/bootstrap/listeners/invalidateCacheOnModelWrite.ts
|
|
1416
|
+
import { eventBus as eventBus2, modelEventName as modelEventName2 } from "@getstrata/core/events";
|
|
1417
|
+
import InvalidateCacheTagsJob from "@getstrata/core/jobs/invalidateCacheTagsJob";
|
|
1418
|
+
import { createTrackedJob } from "@getstrata/core/queue/createAppQueue";
|
|
1419
|
+
|
|
1420
|
+
// ../../src/bootstrap/cache/modelCacheTags.ts
|
|
1421
|
+
function cacheTagsForModelWrite(tableName, action) {
|
|
1422
|
+
const module = discoverModules().find((entry) => entry.tableName === tableName);
|
|
1423
|
+
const baseTags = module?.cacheTags ?? [`${tableName}s`];
|
|
1424
|
+
const isDelete = action === "deleted" || action === "force-deleted";
|
|
1425
|
+
const extraTags = isDelete ? module?.cacheDeleteExtraTags ?? [] : [];
|
|
1426
|
+
return [...new Set([...baseTags, ...extraTags])];
|
|
1427
|
+
}
|
|
1428
|
+
function discoverModelTableNames() {
|
|
1429
|
+
return discoverModules().map((module) => module.tableName).filter((tableName) => tableName !== undefined);
|
|
1401
1430
|
}
|
|
1402
1431
|
|
|
1403
|
-
// ../../src/
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1432
|
+
// ../../src/bootstrap/listeners/invalidateCacheOnModelWrite.ts
|
|
1433
|
+
var MODEL_WRITE_ACTIONS = ["created", "updated", "deleted", "restored", "force-deleted"];
|
|
1434
|
+
function registerInvalidateCacheOnModelWriteListeners(bus = eventBus2) {
|
|
1435
|
+
for (const tableName of discoverModelTableNames()) {
|
|
1436
|
+
for (const action of MODEL_WRITE_ACTIONS) {
|
|
1437
|
+
bus.listen(modelEventName2(tableName, action), async () => {
|
|
1438
|
+
const tags = cacheTagsForModelWrite(tableName, action);
|
|
1439
|
+
if (tags.length === 0) {
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
let cache;
|
|
1443
|
+
let queue;
|
|
1444
|
+
try {
|
|
1445
|
+
cache = resolveApplicationCache();
|
|
1446
|
+
queue = resolveApplicationQueue();
|
|
1447
|
+
} catch {
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
const job = createTrackedJob("cache.invalidate-tags", new InvalidateCacheTagsJob(cache));
|
|
1451
|
+
await queue.dispatch(job, { tags });
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1409
1454
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
// ../../src/bootstrap/providers/listeners.ts
|
|
1458
|
+
var registeredListenerGroups = new Set;
|
|
1459
|
+
function registerListenerGroup(name, register) {
|
|
1460
|
+
if (registeredListenerGroups.has(name)) {
|
|
1461
|
+
return;
|
|
1412
1462
|
}
|
|
1463
|
+
registeredListenerGroups.add(name);
|
|
1464
|
+
register();
|
|
1413
1465
|
}
|
|
1414
|
-
var
|
|
1466
|
+
var listenersProvider = {
|
|
1467
|
+
name: "core.listeners",
|
|
1468
|
+
boot() {
|
|
1469
|
+
registerListenerGroup("cache.invalidate-on-model-write", () => {
|
|
1470
|
+
registerInvalidateCacheOnModelWriteListeners();
|
|
1471
|
+
});
|
|
1472
|
+
for (const [index, registerListener] of discoverListeners().entries()) {
|
|
1473
|
+
registerListenerGroup(`app.listener.${index}`, registerListener);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
var listeners_default = listenersProvider;
|
|
1478
|
+
|
|
1479
|
+
// ../../src/core/auth/policy.ts
|
|
1480
|
+
var BLOCKED_POLICY_ACTIONS = new Set([
|
|
1481
|
+
"constructor",
|
|
1482
|
+
"toString",
|
|
1483
|
+
"valueOf",
|
|
1484
|
+
"hasOwnProperty",
|
|
1485
|
+
"isPrototypeOf",
|
|
1486
|
+
"propertyIsEnumerable",
|
|
1487
|
+
"__proto__"
|
|
1488
|
+
]);
|
|
1489
|
+
|
|
1490
|
+
class PolicyGate {
|
|
1491
|
+
constructor() {}
|
|
1492
|
+
policies = new Map;
|
|
1493
|
+
register(resource, policy) {
|
|
1494
|
+
this.policies.set(resource, policy);
|
|
1495
|
+
}
|
|
1496
|
+
allows(resource, action, user, model) {
|
|
1497
|
+
const policy = this.policies.get(resource);
|
|
1498
|
+
if (!policy) {
|
|
1499
|
+
return false;
|
|
1500
|
+
}
|
|
1501
|
+
if (BLOCKED_POLICY_ACTIONS.has(action) || !(action in policy)) {
|
|
1502
|
+
return false;
|
|
1503
|
+
}
|
|
1504
|
+
const handler = policy[action];
|
|
1505
|
+
if (typeof handler !== "function") {
|
|
1506
|
+
return false;
|
|
1507
|
+
}
|
|
1508
|
+
const resolvedUser = user === undefined ? currentAuthUser() : user;
|
|
1509
|
+
return model === undefined ? handler.call(policy, resolvedUser) : handler.call(policy, resolvedUser, model);
|
|
1510
|
+
}
|
|
1511
|
+
authorize(resource, action, user, model) {
|
|
1512
|
+
if (!this.allows(resource, action, user, model)) {
|
|
1513
|
+
throw new ForbiddenError2;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// ../../src/bootstrap/providers/policy.ts
|
|
1519
|
+
var policyProvider = {
|
|
1520
|
+
name: "core.policy",
|
|
1521
|
+
register({ container }) {
|
|
1522
|
+
container.set(CORE_POLICY_GATE_TOKEN, new PolicyGate);
|
|
1523
|
+
}
|
|
1524
|
+
};
|
|
1525
|
+
var policy_default = policyProvider;
|
|
1415
1526
|
|
|
1416
1527
|
// ../../src/core/pagination/index.ts
|
|
1417
1528
|
function buildPaginationMeta(input) {
|
|
@@ -3299,9 +3410,6 @@ class ResilientQueue {
|
|
|
3299
3410
|
function createFailedJobService() {
|
|
3300
3411
|
return new failedJobService_default(new failedJobRepository_default);
|
|
3301
3412
|
}
|
|
3302
|
-
function createTrackedJob(name, job) {
|
|
3303
|
-
return jobRegistry.track(name, job);
|
|
3304
|
-
}
|
|
3305
3413
|
function createProductionQueue(driver, options = {}) {
|
|
3306
3414
|
options.registerJobs?.();
|
|
3307
3415
|
const failedJobs = options.failedJobs ?? createFailedJobService();
|
|
@@ -3324,116 +3432,16 @@ function createAppQueue(driver, redisUrl, failedJobs = createFailedJobService(),
|
|
|
3324
3432
|
});
|
|
3325
3433
|
}
|
|
3326
3434
|
|
|
3327
|
-
// ../../src/bootstrap/cache/modelCacheTags.ts
|
|
3328
|
-
function cacheTagsForModelWrite(tableName, action) {
|
|
3329
|
-
const module = discoverModules().find((entry) => entry.tableName === tableName);
|
|
3330
|
-
const baseTags = module?.cacheTags ?? [`${tableName}s`];
|
|
3331
|
-
const isDelete = action === "deleted" || action === "force-deleted";
|
|
3332
|
-
const extraTags = isDelete ? module?.cacheDeleteExtraTags ?? [] : [];
|
|
3333
|
-
return [...new Set([...baseTags, ...extraTags])];
|
|
3334
|
-
}
|
|
3335
|
-
function discoverModelTableNames() {
|
|
3336
|
-
return discoverModules().map((module) => module.tableName).filter((tableName) => tableName !== undefined);
|
|
3337
|
-
}
|
|
3338
|
-
|
|
3339
|
-
// ../../src/bootstrap/listeners/invalidateCacheOnModelWrite.ts
|
|
3340
|
-
var MODEL_WRITE_ACTIONS = ["created", "updated", "deleted", "restored", "force-deleted"];
|
|
3341
|
-
function registerInvalidateCacheOnModelWriteListeners(bus = eventBus) {
|
|
3342
|
-
for (const tableName of discoverModelTableNames()) {
|
|
3343
|
-
for (const action of MODEL_WRITE_ACTIONS) {
|
|
3344
|
-
bus.listen(modelEventName(tableName, action), async () => {
|
|
3345
|
-
const tags = cacheTagsForModelWrite(tableName, action);
|
|
3346
|
-
if (tags.length === 0) {
|
|
3347
|
-
return;
|
|
3348
|
-
}
|
|
3349
|
-
let cache;
|
|
3350
|
-
let queue;
|
|
3351
|
-
try {
|
|
3352
|
-
cache = resolveApplicationCache();
|
|
3353
|
-
queue = resolveApplicationQueue();
|
|
3354
|
-
} catch {
|
|
3355
|
-
return;
|
|
3356
|
-
}
|
|
3357
|
-
const job = createTrackedJob("cache.invalidate-tags", new invalidateCacheTagsJob_default(cache));
|
|
3358
|
-
await queue.dispatch(job, { tags });
|
|
3359
|
-
});
|
|
3360
|
-
}
|
|
3361
|
-
}
|
|
3362
|
-
}
|
|
3363
|
-
|
|
3364
|
-
// ../../src/bootstrap/providers/listeners.ts
|
|
3365
|
-
var registeredListenerGroups = new Set;
|
|
3366
|
-
function registerListenerGroup(name, register) {
|
|
3367
|
-
if (registeredListenerGroups.has(name)) {
|
|
3368
|
-
return;
|
|
3369
|
-
}
|
|
3370
|
-
registeredListenerGroups.add(name);
|
|
3371
|
-
register();
|
|
3372
|
-
}
|
|
3373
|
-
var listenersProvider = {
|
|
3374
|
-
name: "core.listeners",
|
|
3375
|
-
boot() {
|
|
3376
|
-
registerListenerGroup("cache.invalidate-on-model-write", () => {
|
|
3377
|
-
registerInvalidateCacheOnModelWriteListeners();
|
|
3378
|
-
});
|
|
3379
|
-
for (const [index, registerListener] of discoverListeners().entries()) {
|
|
3380
|
-
registerListenerGroup(`app.listener.${index}`, registerListener);
|
|
3381
|
-
}
|
|
3382
|
-
}
|
|
3383
|
-
};
|
|
3384
|
-
var listeners_default = listenersProvider;
|
|
3385
|
-
|
|
3386
|
-
// ../../src/core/auth/policy.ts
|
|
3387
|
-
var BLOCKED_POLICY_ACTIONS = new Set([
|
|
3388
|
-
"constructor",
|
|
3389
|
-
"toString",
|
|
3390
|
-
"valueOf",
|
|
3391
|
-
"hasOwnProperty",
|
|
3392
|
-
"isPrototypeOf",
|
|
3393
|
-
"propertyIsEnumerable",
|
|
3394
|
-
"__proto__"
|
|
3395
|
-
]);
|
|
3396
|
-
|
|
3397
|
-
class PolicyGate {
|
|
3398
|
-
constructor() {}
|
|
3399
|
-
policies = new Map;
|
|
3400
|
-
register(resource, policy) {
|
|
3401
|
-
this.policies.set(resource, policy);
|
|
3402
|
-
}
|
|
3403
|
-
allows(resource, action, user, model) {
|
|
3404
|
-
const policy = this.policies.get(resource);
|
|
3405
|
-
if (!policy) {
|
|
3406
|
-
return false;
|
|
3407
|
-
}
|
|
3408
|
-
if (BLOCKED_POLICY_ACTIONS.has(action) || !(action in policy)) {
|
|
3409
|
-
return false;
|
|
3410
|
-
}
|
|
3411
|
-
const handler = policy[action];
|
|
3412
|
-
if (typeof handler !== "function") {
|
|
3413
|
-
return false;
|
|
3414
|
-
}
|
|
3415
|
-
const resolvedUser = user === undefined ? currentAuthUser() : user;
|
|
3416
|
-
return model === undefined ? handler.call(policy, resolvedUser) : handler.call(policy, resolvedUser, model);
|
|
3417
|
-
}
|
|
3418
|
-
authorize(resource, action, user, model) {
|
|
3419
|
-
if (!this.allows(resource, action, user, model)) {
|
|
3420
|
-
throw new ForbiddenError2;
|
|
3421
|
-
}
|
|
3422
|
-
}
|
|
3423
|
-
}
|
|
3424
|
-
|
|
3425
|
-
// ../../src/bootstrap/providers/policy.ts
|
|
3426
|
-
var policyProvider = {
|
|
3427
|
-
name: "core.policy",
|
|
3428
|
-
register({ container }) {
|
|
3429
|
-
container.set(CORE_POLICY_GATE_TOKEN, new PolicyGate);
|
|
3430
|
-
}
|
|
3431
|
-
};
|
|
3432
|
-
var policy_default = policyProvider;
|
|
3433
|
-
|
|
3434
3435
|
// ../../src/core/jobs/dispatchWebhookJob.ts
|
|
3435
3436
|
import { createHmac as createHmac2 } from "crypto";
|
|
3436
3437
|
|
|
3438
|
+
// ../../src/core/queue/index.ts
|
|
3439
|
+
class Job {
|
|
3440
|
+
maxAttempts;
|
|
3441
|
+
backoffMs;
|
|
3442
|
+
priority;
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3437
3445
|
// ../../src/core/security/safeUrl.ts
|
|
3438
3446
|
import { lookup as dnsLookupImpl } from "dns/promises";
|
|
3439
3447
|
var dnsLookup = dnsLookupImpl;
|
|
@@ -3618,6 +3626,19 @@ class DispatchWebhookJob extends Job {
|
|
|
3618
3626
|
}
|
|
3619
3627
|
var dispatchWebhookJob_default = DispatchWebhookJob;
|
|
3620
3628
|
|
|
3629
|
+
// ../../src/core/jobs/invalidateCacheTagsJob.ts
|
|
3630
|
+
class InvalidateCacheTagsJob2 extends Job {
|
|
3631
|
+
cache;
|
|
3632
|
+
constructor(cache) {
|
|
3633
|
+
super();
|
|
3634
|
+
this.cache = cache;
|
|
3635
|
+
}
|
|
3636
|
+
async handle(payload) {
|
|
3637
|
+
await this.cache.tags(...payload.tags).flush();
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
var invalidateCacheTagsJob_default = InvalidateCacheTagsJob2;
|
|
3641
|
+
|
|
3621
3642
|
// ../../src/core/contracts/di.ts
|
|
3622
3643
|
function getRequiredDependency2(dependencies, key) {
|
|
3623
3644
|
const dependency = dependencies[key];
|
|
@@ -3855,6 +3876,9 @@ function readFrontendMode() {
|
|
|
3855
3876
|
function isViewsEnabled() {
|
|
3856
3877
|
return readFrontendMode() === "server-htmx";
|
|
3857
3878
|
}
|
|
3879
|
+
function isSpaEnabled() {
|
|
3880
|
+
return readFrontendMode() === "spa-react";
|
|
3881
|
+
}
|
|
3858
3882
|
|
|
3859
3883
|
// ../../src/core/http/requestMetaContext.ts
|
|
3860
3884
|
var requestMetaContext = createAsyncContextStore("@getstrata/requestMetaContext");
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// ../../src/bootstrap/createSpaRoutes.ts
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { jsonResponse } from "@getstrata/core/http";
|
|
5
|
+
|
|
6
|
+
// ../../src/config/frontend.ts
|
|
7
|
+
function readFrontendMode() {
|
|
8
|
+
const mode = (process.env.FRONTEND_MODE ?? "api").trim();
|
|
9
|
+
if (mode === "server-htmx") {
|
|
10
|
+
return "server-htmx";
|
|
11
|
+
}
|
|
12
|
+
if (mode === "spa-react") {
|
|
13
|
+
return "spa-react";
|
|
14
|
+
}
|
|
15
|
+
return "api";
|
|
16
|
+
}
|
|
17
|
+
function isViewsEnabled() {
|
|
18
|
+
return readFrontendMode() === "server-htmx";
|
|
19
|
+
}
|
|
20
|
+
function isSpaEnabled() {
|
|
21
|
+
return readFrontendMode() === "spa-react";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ../../src/bootstrap/createSpaRoutes.ts
|
|
25
|
+
var SPA_DIST_DIRECTORY = join(process.cwd(), "frontend/dist");
|
|
26
|
+
var SPA_INDEX_FILE = join(SPA_DIST_DIRECTORY, "index.html");
|
|
27
|
+
function createSpaRoutes(_dependencies) {
|
|
28
|
+
return {
|
|
29
|
+
"/app/*": async (request) => {
|
|
30
|
+
const pathname = new URL(request.url).pathname;
|
|
31
|
+
const relativePath = pathname.replace(/^\/app\//, "");
|
|
32
|
+
const assetFile = Bun.file(join(SPA_DIST_DIRECTORY, relativePath));
|
|
33
|
+
if (relativePath.length > 0 && await assetFile.exists()) {
|
|
34
|
+
return new Response(assetFile);
|
|
35
|
+
}
|
|
36
|
+
const indexFile = Bun.file(SPA_INDEX_FILE);
|
|
37
|
+
if (await indexFile.exists()) {
|
|
38
|
+
return new Response(indexFile, {
|
|
39
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return jsonResponse({
|
|
43
|
+
error: "SPA build not found. Run `cd frontend && bun install && bun run build`."
|
|
44
|
+
}, { status: 503 });
|
|
45
|
+
},
|
|
46
|
+
"/": async () => Response.redirect("/app/", 302)
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function mergeSpaRoutes(dependencies, routes) {
|
|
50
|
+
if (!isSpaEnabled()) {
|
|
51
|
+
return routes;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
...createSpaRoutes(dependencies),
|
|
55
|
+
...routes
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
mergeSpaRoutes,
|
|
60
|
+
createSpaRoutes,
|
|
61
|
+
SPA_DIST_DIRECTORY
|
|
62
|
+
};
|
|
@@ -17,6 +17,9 @@ function readFrontendMode() {
|
|
|
17
17
|
function isViewsEnabled() {
|
|
18
18
|
return readFrontendMode() === "server-htmx";
|
|
19
19
|
}
|
|
20
|
+
function isSpaEnabled() {
|
|
21
|
+
return readFrontendMode() === "spa-react";
|
|
22
|
+
}
|
|
20
23
|
|
|
21
24
|
// ../../src/bootstrap/buildWebModuleRoutes.ts
|
|
22
25
|
import { applyMiddlewareToRoutes as applyMiddlewareToRoutes2 } from "@getstrata/core/http/middleware";
|