@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.
@@ -13,6 +13,9 @@ function readFrontendMode() {
13
13
  function isViewsEnabled() {
14
14
  return readFrontendMode() === "server-htmx";
15
15
  }
16
+ function isSpaEnabled() {
17
+ return readFrontendMode() === "spa-react";
18
+ }
16
19
 
17
20
  // ../../src/core/runtime/asyncContextStore.ts
18
21
  import { AsyncLocalStorage } from "async_hooks";
@@ -213,6 +216,25 @@ function getDb() {
213
216
  getDatabase();
214
217
  return getDefaultDatabaseQuery();
215
218
  }
219
+ async function pingDatabase(connection = getDatabase()) {
220
+ try {
221
+ await connection`SELECT 1`;
222
+ return true;
223
+ } catch {
224
+ return false;
225
+ }
226
+ }
227
+ async function ensureDatabaseConnection() {
228
+ if (await pingDatabase()) {
229
+ return getDatabase();
230
+ }
231
+ await getDatabase().close().catch(() => {
232
+ return;
233
+ });
234
+ connectionHolder.connection = createDatabaseConnection(databaseConfig);
235
+ registerDefaultDatabasePool(connectionHolder.connection);
236
+ return getDatabase();
237
+ }
216
238
  var db = new Proxy(function database() {}, {
217
239
  apply(_target, _thisArg, args) {
218
240
  return getDb()(...args);
@@ -201,6 +201,25 @@ function getDb() {
201
201
  getDatabase();
202
202
  return getDefaultDatabaseQuery();
203
203
  }
204
+ async function pingDatabase(connection = getDatabase()) {
205
+ try {
206
+ await connection`SELECT 1`;
207
+ return true;
208
+ } catch {
209
+ return false;
210
+ }
211
+ }
212
+ async function ensureDatabaseConnection() {
213
+ if (await pingDatabase()) {
214
+ return getDatabase();
215
+ }
216
+ await getDatabase().close().catch(() => {
217
+ return;
218
+ });
219
+ connectionHolder.connection = createDatabaseConnection(databaseConfig);
220
+ registerDefaultDatabasePool(connectionHolder.connection);
221
+ return getDatabase();
222
+ }
204
223
  var db = new Proxy(function database() {}, {
205
224
  apply(_target, _thisArg, args) {
206
225
  return getDb()(...args);
@@ -1310,25 +1329,190 @@ function discoverListeners() {
1310
1329
  return appListeners;
1311
1330
  }
1312
1331
 
1313
- // ../../src/core/queue/index.ts
1314
- class Job {
1315
- maxAttempts;
1316
- backoffMs;
1317
- priority;
1332
+ // ../../src/bootstrap/listeners/invalidateCacheOnModelWrite.ts
1333
+ import { eventBus as eventBus2, modelEventName as modelEventName2 } from "@getstrata/core/events";
1334
+ import InvalidateCacheTagsJob from "@getstrata/core/jobs/invalidateCacheTagsJob";
1335
+ import { createTrackedJob } from "@getstrata/core/queue/createAppQueue";
1336
+
1337
+ // ../../src/bootstrap/applicationRegistry.ts
1338
+ import {
1339
+ resolveApplicationAuth,
1340
+ resolveApplicationCache,
1341
+ resolveApplicationConfig,
1342
+ resolveApplicationDependencies,
1343
+ resolveApplicationEventBus,
1344
+ resolveApplicationLogger,
1345
+ resolveApplicationPolicyGate,
1346
+ resolveApplicationQueue,
1347
+ setActiveApplicationContext
1348
+ } from "@getstrata/core/runtime/applicationRegistry";
1349
+
1350
+ // ../../src/bootstrap/discoverModules.ts
1351
+ import { readdirSync as readdirSync2 } from "fs";
1352
+ import { join as join2 } from "path";
1353
+ import { pathToFileURL as pathToFileURL2 } from "url";
1354
+ var DISCOVER_MODULES_STATE_KEY = Symbol.for("@getstrata/discoverModulesState");
1355
+ function readDiscoverModulesState() {
1356
+ const existing = globalThis[DISCOVER_MODULES_STATE_KEY];
1357
+ if (existing) {
1358
+ return existing;
1359
+ }
1360
+ const state = { appModules: [] };
1361
+ globalThis[DISCOVER_MODULES_STATE_KEY] = state;
1362
+ return state;
1363
+ }
1364
+ function configureModulesDirectory(modulesDir) {
1365
+ readDiscoverModulesState().configuredModulesDir = modulesDir;
1366
+ }
1367
+ function resolveModulesDirectory(options) {
1368
+ const state = readDiscoverModulesState();
1369
+ if (options?.modulesDir) {
1370
+ return options.modulesDir;
1371
+ }
1372
+ if (state.configuredModulesDir) {
1373
+ return state.configuredModulesDir;
1374
+ }
1375
+ return join2(import.meta.dir, "../modules");
1376
+ }
1377
+ async function loadDiscoveredModules(options) {
1378
+ const modulesDirectory = resolveModulesDirectory(options);
1379
+ let moduleNames;
1380
+ try {
1381
+ moduleNames = readdirSync2(modulesDirectory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
1382
+ } catch (error) {
1383
+ if (error.code === "ENOENT") {
1384
+ return [];
1385
+ }
1386
+ throw error;
1387
+ }
1388
+ const modules = await Promise.all(moduleNames.map(async (moduleName) => {
1389
+ const moduleUrl = pathToFileURL2(join2(modulesDirectory, moduleName, "index.ts")).href;
1390
+ const loaded = await import(moduleUrl);
1391
+ return loaded.default;
1392
+ }));
1393
+ return modules.filter((module) => module?.name !== undefined).sort((left, right) => (left.order ?? 100) - (right.order ?? 100));
1394
+ }
1395
+ async function ensureModulesLoaded(options) {
1396
+ const state = readDiscoverModulesState();
1397
+ if (state.appModules.length > 0) {
1398
+ return state.appModules;
1399
+ }
1400
+ state.modulesReady ??= loadDiscoveredModules(options).then((modules) => {
1401
+ state.appModules.splice(0, state.appModules.length, ...modules);
1402
+ return state.appModules;
1403
+ });
1404
+ return state.modulesReady;
1405
+ }
1406
+ function discoverModules() {
1407
+ return readDiscoverModulesState().appModules;
1318
1408
  }
1319
1409
 
1320
- // ../../src/core/jobs/invalidateCacheTagsJob.ts
1321
- class InvalidateCacheTagsJob extends Job {
1322
- cache;
1323
- constructor(cache) {
1324
- super();
1325
- this.cache = cache;
1410
+ // ../../src/bootstrap/cache/modelCacheTags.ts
1411
+ function cacheTagsForModelWrite(tableName, action) {
1412
+ const module = discoverModules().find((entry) => entry.tableName === tableName);
1413
+ const baseTags = module?.cacheTags ?? [`${tableName}s`];
1414
+ const isDelete = action === "deleted" || action === "force-deleted";
1415
+ const extraTags = isDelete ? module?.cacheDeleteExtraTags ?? [] : [];
1416
+ return [...new Set([...baseTags, ...extraTags])];
1417
+ }
1418
+ function discoverModelTableNames() {
1419
+ return discoverModules().map((module) => module.tableName).filter((tableName) => tableName !== undefined);
1420
+ }
1421
+
1422
+ // ../../src/bootstrap/listeners/invalidateCacheOnModelWrite.ts
1423
+ var MODEL_WRITE_ACTIONS = ["created", "updated", "deleted", "restored", "force-deleted"];
1424
+ function registerInvalidateCacheOnModelWriteListeners(bus = eventBus2) {
1425
+ for (const tableName of discoverModelTableNames()) {
1426
+ for (const action of MODEL_WRITE_ACTIONS) {
1427
+ bus.listen(modelEventName2(tableName, action), async () => {
1428
+ const tags = cacheTagsForModelWrite(tableName, action);
1429
+ if (tags.length === 0) {
1430
+ return;
1431
+ }
1432
+ let cache;
1433
+ let queue;
1434
+ try {
1435
+ cache = resolveApplicationCache();
1436
+ queue = resolveApplicationQueue();
1437
+ } catch {
1438
+ return;
1439
+ }
1440
+ const job = createTrackedJob("cache.invalidate-tags", new InvalidateCacheTagsJob(cache));
1441
+ await queue.dispatch(job, { tags });
1442
+ });
1443
+ }
1326
1444
  }
1327
- async handle(payload) {
1328
- await this.cache.tags(...payload.tags).flush();
1445
+ }
1446
+
1447
+ // ../../src/bootstrap/providers/listeners.ts
1448
+ var registeredListenerGroups = new Set;
1449
+ function registerListenerGroup(name, register) {
1450
+ if (registeredListenerGroups.has(name)) {
1451
+ return;
1452
+ }
1453
+ registeredListenerGroups.add(name);
1454
+ register();
1455
+ }
1456
+ var listenersProvider = {
1457
+ name: "core.listeners",
1458
+ boot() {
1459
+ registerListenerGroup("cache.invalidate-on-model-write", () => {
1460
+ registerInvalidateCacheOnModelWriteListeners();
1461
+ });
1462
+ for (const [index, registerListener] of discoverListeners().entries()) {
1463
+ registerListenerGroup(`app.listener.${index}`, registerListener);
1464
+ }
1465
+ }
1466
+ };
1467
+ var listeners_default = listenersProvider;
1468
+
1469
+ // ../../src/core/auth/policy.ts
1470
+ var BLOCKED_POLICY_ACTIONS = new Set([
1471
+ "constructor",
1472
+ "toString",
1473
+ "valueOf",
1474
+ "hasOwnProperty",
1475
+ "isPrototypeOf",
1476
+ "propertyIsEnumerable",
1477
+ "__proto__"
1478
+ ]);
1479
+
1480
+ class PolicyGate {
1481
+ constructor() {}
1482
+ policies = new Map;
1483
+ register(resource, policy) {
1484
+ this.policies.set(resource, policy);
1485
+ }
1486
+ allows(resource, action, user, model) {
1487
+ const policy = this.policies.get(resource);
1488
+ if (!policy) {
1489
+ return false;
1490
+ }
1491
+ if (BLOCKED_POLICY_ACTIONS.has(action) || !(action in policy)) {
1492
+ return false;
1493
+ }
1494
+ const handler = policy[action];
1495
+ if (typeof handler !== "function") {
1496
+ return false;
1497
+ }
1498
+ const resolvedUser = user === undefined ? currentAuthUser() : user;
1499
+ return model === undefined ? handler.call(policy, resolvedUser) : handler.call(policy, resolvedUser, model);
1500
+ }
1501
+ authorize(resource, action, user, model) {
1502
+ if (!this.allows(resource, action, user, model)) {
1503
+ throw new ForbiddenError2;
1504
+ }
1329
1505
  }
1330
1506
  }
1331
- var invalidateCacheTagsJob_default = InvalidateCacheTagsJob;
1507
+
1508
+ // ../../src/bootstrap/providers/policy.ts
1509
+ var policyProvider = {
1510
+ name: "core.policy",
1511
+ register({ container }) {
1512
+ container.set(CORE_POLICY_GATE_TOKEN, new PolicyGate);
1513
+ }
1514
+ };
1515
+ var policy_default = policyProvider;
1332
1516
 
1333
1517
  // ../../src/core/pagination/index.ts
1334
1518
  function buildPaginationMeta(input) {
@@ -1620,10 +1804,10 @@ function buildHavingClause(tableName, having, params) {
1620
1804
  return body.length > 0 ? ` HAVING ${body}` : "";
1621
1805
  }
1622
1806
  function buildJoinClause(joins = []) {
1623
- return joins.map((join2) => {
1624
- const joinType = join2.type === "left" ? "LEFT JOIN" : "INNER JOIN";
1625
- const onClause = join2.on.map(({ left, right }) => `${qualifyColumn(left.table, left.column)} = ${qualifyColumn(right.table, right.column)}`).join(" AND ");
1626
- return ` ${joinType} ${quoteIdentifier(join2.table)} ON ${onClause}`;
1807
+ return joins.map((join3) => {
1808
+ const joinType = join3.type === "left" ? "LEFT JOIN" : "INNER JOIN";
1809
+ const onClause = join3.on.map(({ left, right }) => `${qualifyColumn(left.table, left.column)} = ${qualifyColumn(right.table, right.column)}`).join(" AND ");
1810
+ return ` ${joinType} ${quoteIdentifier(join3.table)} ON ${onClause}`;
1627
1811
  }).join("");
1628
1812
  }
1629
1813
  function buildLimitClause(limit) {
@@ -2059,7 +2243,7 @@ class RepositoryQuery {
2059
2243
  const rightRef = parseQualifiedColumn(right);
2060
2244
  const table = type === "inner" ? rightRef.table : rightRef.table;
2061
2245
  const joins = this.queryOptions.joins ?? [];
2062
- const existing = joins.find((join2) => join2.table === table && join2.type === type);
2246
+ const existing = joins.find((join3) => join3.table === table && join3.type === type);
2063
2247
  if (existing) {
2064
2248
  existing.on.push({ left: leftRef, right: rightRef });
2065
2249
  return this;
@@ -3216,9 +3400,6 @@ class ResilientQueue {
3216
3400
  function createFailedJobService() {
3217
3401
  return new failedJobService_default(new failedJobRepository_default);
3218
3402
  }
3219
- function createTrackedJob(name, job) {
3220
- return jobRegistry.track(name, job);
3221
- }
3222
3403
  function createProductionQueue(driver, options = {}) {
3223
3404
  options.registerJobs?.();
3224
3405
  const failedJobs = options.failedJobs ?? createFailedJobService();
@@ -3241,189 +3422,16 @@ function createAppQueue(driver, redisUrl, failedJobs = createFailedJobService(),
3241
3422
  });
3242
3423
  }
3243
3424
 
3244
- // ../../src/bootstrap/applicationRegistry.ts
3245
- import {
3246
- resolveApplicationAuth,
3247
- resolveApplicationCache,
3248
- resolveApplicationConfig,
3249
- resolveApplicationDependencies,
3250
- resolveApplicationEventBus,
3251
- resolveApplicationLogger,
3252
- resolveApplicationPolicyGate,
3253
- resolveApplicationQueue,
3254
- setActiveApplicationContext
3255
- } from "@getstrata/core/runtime/applicationRegistry";
3256
-
3257
- // ../../src/bootstrap/discoverModules.ts
3258
- import { readdirSync as readdirSync2 } from "fs";
3259
- import { join as join2 } from "path";
3260
- import { pathToFileURL as pathToFileURL2 } from "url";
3261
- var DISCOVER_MODULES_STATE_KEY = Symbol.for("@getstrata/discoverModulesState");
3262
- function readDiscoverModulesState() {
3263
- const existing = globalThis[DISCOVER_MODULES_STATE_KEY];
3264
- if (existing) {
3265
- return existing;
3266
- }
3267
- const state = { appModules: [] };
3268
- globalThis[DISCOVER_MODULES_STATE_KEY] = state;
3269
- return state;
3270
- }
3271
- function configureModulesDirectory(modulesDir) {
3272
- readDiscoverModulesState().configuredModulesDir = modulesDir;
3273
- }
3274
- function resolveModulesDirectory(options) {
3275
- const state = readDiscoverModulesState();
3276
- if (options?.modulesDir) {
3277
- return options.modulesDir;
3278
- }
3279
- if (state.configuredModulesDir) {
3280
- return state.configuredModulesDir;
3281
- }
3282
- return join2(import.meta.dir, "../modules");
3283
- }
3284
- async function loadDiscoveredModules(options) {
3285
- const modulesDirectory = resolveModulesDirectory(options);
3286
- let moduleNames;
3287
- try {
3288
- moduleNames = readdirSync2(modulesDirectory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
3289
- } catch (error) {
3290
- if (error.code === "ENOENT") {
3291
- return [];
3292
- }
3293
- throw error;
3294
- }
3295
- const modules = await Promise.all(moduleNames.map(async (moduleName) => {
3296
- const moduleUrl = pathToFileURL2(join2(modulesDirectory, moduleName, "index.ts")).href;
3297
- const loaded = await import(moduleUrl);
3298
- return loaded.default;
3299
- }));
3300
- return modules.filter((module) => module?.name !== undefined).sort((left, right) => (left.order ?? 100) - (right.order ?? 100));
3301
- }
3302
- async function ensureModulesLoaded(options) {
3303
- const state = readDiscoverModulesState();
3304
- if (state.appModules.length > 0) {
3305
- return state.appModules;
3306
- }
3307
- state.modulesReady ??= loadDiscoveredModules(options).then((modules) => {
3308
- state.appModules.splice(0, state.appModules.length, ...modules);
3309
- return state.appModules;
3310
- });
3311
- return state.modulesReady;
3312
- }
3313
- function discoverModules() {
3314
- return readDiscoverModulesState().appModules;
3315
- }
3316
-
3317
- // ../../src/bootstrap/cache/modelCacheTags.ts
3318
- function cacheTagsForModelWrite(tableName, action) {
3319
- const module = discoverModules().find((entry) => entry.tableName === tableName);
3320
- const baseTags = module?.cacheTags ?? [`${tableName}s`];
3321
- const isDelete = action === "deleted" || action === "force-deleted";
3322
- const extraTags = isDelete ? module?.cacheDeleteExtraTags ?? [] : [];
3323
- return [...new Set([...baseTags, ...extraTags])];
3324
- }
3325
- function discoverModelTableNames() {
3326
- return discoverModules().map((module) => module.tableName).filter((tableName) => tableName !== undefined);
3327
- }
3328
-
3329
- // ../../src/bootstrap/listeners/invalidateCacheOnModelWrite.ts
3330
- var MODEL_WRITE_ACTIONS = ["created", "updated", "deleted", "restored", "force-deleted"];
3331
- function registerInvalidateCacheOnModelWriteListeners(bus = eventBus) {
3332
- for (const tableName of discoverModelTableNames()) {
3333
- for (const action of MODEL_WRITE_ACTIONS) {
3334
- bus.listen(modelEventName(tableName, action), async () => {
3335
- const tags = cacheTagsForModelWrite(tableName, action);
3336
- if (tags.length === 0) {
3337
- return;
3338
- }
3339
- let cache;
3340
- let queue;
3341
- try {
3342
- cache = resolveApplicationCache();
3343
- queue = resolveApplicationQueue();
3344
- } catch {
3345
- return;
3346
- }
3347
- const job = createTrackedJob("cache.invalidate-tags", new invalidateCacheTagsJob_default(cache));
3348
- await queue.dispatch(job, { tags });
3349
- });
3350
- }
3351
- }
3352
- }
3353
-
3354
- // ../../src/bootstrap/providers/listeners.ts
3355
- var registeredListenerGroups = new Set;
3356
- function registerListenerGroup(name, register) {
3357
- if (registeredListenerGroups.has(name)) {
3358
- return;
3359
- }
3360
- registeredListenerGroups.add(name);
3361
- register();
3362
- }
3363
- var listenersProvider = {
3364
- name: "core.listeners",
3365
- boot() {
3366
- registerListenerGroup("cache.invalidate-on-model-write", () => {
3367
- registerInvalidateCacheOnModelWriteListeners();
3368
- });
3369
- for (const [index, registerListener] of discoverListeners().entries()) {
3370
- registerListenerGroup(`app.listener.${index}`, registerListener);
3371
- }
3372
- }
3373
- };
3374
- var listeners_default = listenersProvider;
3375
-
3376
- // ../../src/core/auth/policy.ts
3377
- var BLOCKED_POLICY_ACTIONS = new Set([
3378
- "constructor",
3379
- "toString",
3380
- "valueOf",
3381
- "hasOwnProperty",
3382
- "isPrototypeOf",
3383
- "propertyIsEnumerable",
3384
- "__proto__"
3385
- ]);
3386
-
3387
- class PolicyGate {
3388
- constructor() {}
3389
- policies = new Map;
3390
- register(resource, policy) {
3391
- this.policies.set(resource, policy);
3392
- }
3393
- allows(resource, action, user, model) {
3394
- const policy = this.policies.get(resource);
3395
- if (!policy) {
3396
- return false;
3397
- }
3398
- if (BLOCKED_POLICY_ACTIONS.has(action) || !(action in policy)) {
3399
- return false;
3400
- }
3401
- const handler = policy[action];
3402
- if (typeof handler !== "function") {
3403
- return false;
3404
- }
3405
- const resolvedUser = user === undefined ? currentAuthUser() : user;
3406
- return model === undefined ? handler.call(policy, resolvedUser) : handler.call(policy, resolvedUser, model);
3407
- }
3408
- authorize(resource, action, user, model) {
3409
- if (!this.allows(resource, action, user, model)) {
3410
- throw new ForbiddenError2;
3411
- }
3412
- }
3413
- }
3414
-
3415
- // ../../src/bootstrap/providers/policy.ts
3416
- var policyProvider = {
3417
- name: "core.policy",
3418
- register({ container }) {
3419
- container.set(CORE_POLICY_GATE_TOKEN, new PolicyGate);
3420
- }
3421
- };
3422
- var policy_default = policyProvider;
3423
-
3424
3425
  // ../../src/core/jobs/dispatchWebhookJob.ts
3425
3426
  import { createHmac as createHmac2 } from "crypto";
3426
3427
 
3428
+ // ../../src/core/queue/index.ts
3429
+ class Job {
3430
+ maxAttempts;
3431
+ backoffMs;
3432
+ priority;
3433
+ }
3434
+
3427
3435
  // ../../src/core/security/safeUrl.ts
3428
3436
  import { lookup as dnsLookupImpl } from "dns/promises";
3429
3437
  var dnsLookup = dnsLookupImpl;
@@ -3608,6 +3616,19 @@ class DispatchWebhookJob extends Job {
3608
3616
  }
3609
3617
  var dispatchWebhookJob_default = DispatchWebhookJob;
3610
3618
 
3619
+ // ../../src/core/jobs/invalidateCacheTagsJob.ts
3620
+ class InvalidateCacheTagsJob2 extends Job {
3621
+ cache;
3622
+ constructor(cache) {
3623
+ super();
3624
+ this.cache = cache;
3625
+ }
3626
+ async handle(payload) {
3627
+ await this.cache.tags(...payload.tags).flush();
3628
+ }
3629
+ }
3630
+ var invalidateCacheTagsJob_default = InvalidateCacheTagsJob2;
3631
+
3611
3632
  // ../../src/core/contracts/di.ts
3612
3633
  function getRequiredDependency(dependencies, key) {
3613
3634
  const dependency = dependencies[key];
@@ -3845,6 +3866,9 @@ function readFrontendMode() {
3845
3866
  function isViewsEnabled() {
3846
3867
  return readFrontendMode() === "server-htmx";
3847
3868
  }
3869
+ function isSpaEnabled() {
3870
+ return readFrontendMode() === "spa-react";
3871
+ }
3848
3872
 
3849
3873
  // ../../src/core/http/requestMetaContext.ts
3850
3874
  var requestMetaContext = createAsyncContextStore("@getstrata/requestMetaContext");
@@ -336,6 +336,9 @@ function readFrontendMode() {
336
336
  function isViewsEnabled() {
337
337
  return readFrontendMode() === "server-htmx";
338
338
  }
339
+ function isSpaEnabled() {
340
+ return readFrontendMode() === "spa-react";
341
+ }
339
342
 
340
343
  // ../../src/config/rateLimit.ts
341
344
  var LOCAL_LOGIN_RATE_LIMIT = {