@getstrata/bootstrap 0.2.26 → 0.2.29

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.
Files changed (36) hide show
  1. package/dist/bootstrap/http/securedRouteModelBinding.d.ts +2 -2
  2. package/dist/bootstrap/listeners/invalidateCacheOnModelWrite.d.ts +1 -1
  3. package/dist/bootstrap/schedule.d.ts +2 -1
  4. package/dist/bootstrap/web/forms.d.ts +1 -1
  5. package/dist/entries/applicationRegistry.js +2 -0
  6. package/dist/entries/buildModuleRoutes.js +5 -0
  7. package/dist/entries/buildWebModuleRoutes.js +5 -0
  8. package/dist/entries/cache/modelCacheTags.js +2 -0
  9. package/dist/entries/config.js +2 -0
  10. package/dist/entries/context.js +99 -3156
  11. package/dist/entries/contracts.js +2 -0
  12. package/dist/entries/createRoutes.js +1468 -0
  13. package/dist/entries/createSpaRoutes.js +64 -0
  14. package/dist/entries/createWebRoutes.js +5 -0
  15. package/dist/entries/dependencies.js +99 -3156
  16. package/dist/entries/discoverModules.js +2 -0
  17. package/dist/entries/health.js +215 -0
  18. package/dist/entries/http/securedRouteModelBinding.js +6 -293
  19. package/dist/entries/httpKernel.js +5 -0
  20. package/dist/entries/listeners/invalidateCacheOnModelWrite.js +120 -0
  21. package/dist/entries/membershipService.js +2 -0
  22. package/dist/entries/metricsRoutes.js +18 -0
  23. package/dist/entries/providers/view.js +8 -603
  24. package/dist/entries/providers.js +94 -3158
  25. package/dist/entries/queue/defaultJobs.js +7 -465
  26. package/dist/entries/routeRegistry.js +2 -0
  27. package/dist/entries/schedule.js +49 -0
  28. package/dist/entries/secretsGuard.js +9 -0
  29. package/dist/entries/web/forms.js +4 -18
  30. package/dist/entries/web/routing.js +21 -304
  31. package/dist/entries/web/server.js +2 -0
  32. package/dist/entries/web/session.js +3 -26
  33. package/dist/entries/web/slug.js +2 -0
  34. package/dist/index-sfreg6q3.js +0 -0
  35. package/dist/index.js +176 -3543
  36. package/package.json +32 -2
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __jsonParse = (a) => JSON.parse(a);
3
+
2
4
  // ../../src/bootstrap/discoverModules.ts
3
5
  import { readdirSync } from "fs";
4
6
  import { join } from "path";
@@ -0,0 +1,215 @@
1
+ // @bun
2
+ var __jsonParse = (a) => JSON.parse(a);
3
+
4
+ // ../../src/bootstrap/health.ts
5
+ import { jsonResponse } from "@getstrata/core/http";
6
+ var {RedisClient } = globalThis.Bun;
7
+
8
+ // ../../src/config/database.ts
9
+ function readInteger(name, fallback) {
10
+ const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
11
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
12
+ }
13
+ var databaseConfig = {
14
+ url: process.env.DATABASE_URL ?? "",
15
+ poolMax: readInteger("DB_POOL_MAX", 10),
16
+ idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
17
+ maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
18
+ connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
19
+ };
20
+
21
+ // ../../src/core/runtime/asyncContextStore.ts
22
+ import { AsyncLocalStorage } from "async_hooks";
23
+ function createAsyncContextStore(key) {
24
+ const symbol = Symbol.for(key);
25
+ const globalRecord = globalThis;
26
+ const existing = globalRecord[symbol];
27
+ if (existing) {
28
+ return existing;
29
+ }
30
+ const store = new AsyncLocalStorage;
31
+ globalRecord[symbol] = store;
32
+ return store;
33
+ }
34
+
35
+ // ../../src/core/database/connectionContext.ts
36
+ var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
37
+ function getActiveDatabaseConnection(fallback) {
38
+ return activeConnection.getStore() ?? fallback;
39
+ }
40
+
41
+ // ../../src/core/database/queryProxy.ts
42
+ var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
43
+ function createDatabaseQueryProxy(pool) {
44
+ function resolveDatabase() {
45
+ return getActiveDatabaseConnection(pool);
46
+ }
47
+ function resolveDatabaseForProperty(property) {
48
+ if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
49
+ return pool;
50
+ }
51
+ return resolveDatabase();
52
+ }
53
+ return new Proxy(function database() {}, {
54
+ apply(_target, _thisArg, args) {
55
+ return resolveDatabase()(...args);
56
+ },
57
+ get(_target, property) {
58
+ const connection = resolveDatabaseForProperty(property);
59
+ const value = connection[property];
60
+ return typeof value === "function" ? value.bind(connection) : value;
61
+ }
62
+ });
63
+ }
64
+
65
+ // ../../src/core/database/defaultConnection.ts
66
+ var defaultPool = {
67
+ connection: null
68
+ };
69
+ var defaultQuery = {
70
+ connection: null
71
+ };
72
+ function registerDefaultDatabasePool(connection) {
73
+ defaultPool.connection = connection;
74
+ defaultQuery.connection = createDatabaseQueryProxy(connection);
75
+ }
76
+ function getDefaultDatabaseQuery() {
77
+ if (!defaultQuery.connection) {
78
+ throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
79
+ }
80
+ return defaultQuery.connection;
81
+ }
82
+
83
+ // ../../src/db/connection/createConnection.ts
84
+ var {SQL } = globalThis.Bun;
85
+ function createDatabaseConnection(config) {
86
+ if (!config.url) {
87
+ throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
88
+ }
89
+ return new SQL({
90
+ url: config.url,
91
+ max: config.poolMax,
92
+ idleTimeout: config.idleTimeoutSeconds,
93
+ maxLifetime: config.maxLifetimeSeconds,
94
+ connectionTimeout: config.connectionTimeoutSeconds
95
+ });
96
+ }
97
+
98
+ // ../../src/db/connection/index.ts
99
+ var connectionHolder = {
100
+ connection: null
101
+ };
102
+ function getDatabase() {
103
+ if (!connectionHolder.connection) {
104
+ connectionHolder.connection = createDatabaseConnection(databaseConfig);
105
+ registerDefaultDatabasePool(connectionHolder.connection);
106
+ }
107
+ return connectionHolder.connection;
108
+ }
109
+ function getDb() {
110
+ getDatabase();
111
+ return getDefaultDatabaseQuery();
112
+ }
113
+ async function pingDatabase(connection = getDatabase()) {
114
+ try {
115
+ await connection`SELECT 1`;
116
+ return true;
117
+ } catch {
118
+ return false;
119
+ }
120
+ }
121
+ async function ensureDatabaseConnection() {
122
+ if (await pingDatabase()) {
123
+ return getDatabase();
124
+ }
125
+ await getDatabase().close().catch(() => {
126
+ return;
127
+ });
128
+ connectionHolder.connection = createDatabaseConnection(databaseConfig);
129
+ registerDefaultDatabasePool(connectionHolder.connection);
130
+ return getDatabase();
131
+ }
132
+ var db = new Proxy(function database() {}, {
133
+ apply(_target, _thisArg, args) {
134
+ return getDb()(...args);
135
+ },
136
+ get(_target, property) {
137
+ const connection = getDb();
138
+ const value = connection[property];
139
+ return typeof value === "function" ? value.bind(connection) : value;
140
+ }
141
+ });
142
+ var connection_default = db;
143
+
144
+ // ../../src/bootstrap/config.ts
145
+ import {
146
+ CORE_AUTH_TOKEN,
147
+ CORE_CACHE_TOKEN,
148
+ CORE_CONFIG_TOKEN,
149
+ CORE_EVENT_BUS_TOKEN,
150
+ CORE_POLICY_GATE_TOKEN,
151
+ CORE_QUEUE_TOKEN,
152
+ CORE_TOKEN_SERVICE_TOKEN
153
+ } from "@getstrata/core/contracts/serviceTokens";
154
+ var APP_PORT_CONFIG_KEY = "app.port";
155
+ var CACHE_TTL_MS_CONFIG_KEY = "cache.ttlMs";
156
+ var CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
157
+ var CACHE_DRIVER_CONFIG_KEY = "cache.driver";
158
+ var REDIS_URL_CONFIG_KEY = "cache.redisUrl";
159
+ var DATABASE_URL_CONFIG_KEY = "database.url";
160
+ var AUTH_DEV_HEADERS_CONFIG_KEY = "auth.allowDevHeaders";
161
+ var DEFAULT_APP_PORT = 3000;
162
+ var DEFAULT_CACHE_TTL_MS = 3600000;
163
+ var DEFAULT_CACHE_MAX_ENTRIES = 100;
164
+ var DEFAULT_CACHE_DRIVER = "array";
165
+ var DEFAULT_API_TOKEN = "";
166
+ var DEFAULT_QUEUE_DRIVER = "sync";
167
+
168
+ // ../../src/bootstrap/health.ts
169
+ function resolveRedisUrl(dependencies) {
170
+ if (!dependencies.container.has(CORE_CONFIG_TOKEN)) {
171
+ return process.env.REDIS_URL?.trim() || undefined;
172
+ }
173
+ const config = dependencies.container.resolve(CORE_CONFIG_TOKEN);
174
+ const redisUrl = config.get(REDIS_URL_CONFIG_KEY)?.trim();
175
+ return redisUrl || undefined;
176
+ }
177
+ async function checkDatabase() {
178
+ await ensureDatabaseConnection();
179
+ return await pingDatabase();
180
+ }
181
+ async function checkRedis(redisUrl) {
182
+ try {
183
+ const client = new RedisClient(redisUrl);
184
+ const response = await client.ping();
185
+ return response === "PONG";
186
+ } catch {
187
+ return false;
188
+ }
189
+ }
190
+ function createHealthRoutes(dependencies) {
191
+ return {
192
+ "/health": async () => jsonResponse({ status: "ok" }),
193
+ "/ready": async () => {
194
+ const checks = {
195
+ database: await checkDatabase() ? "ok" : "error"
196
+ };
197
+ const redisUrl = resolveRedisUrl(dependencies);
198
+ if (redisUrl) {
199
+ checks.redis = await checkRedis(redisUrl) ? "ok" : "error";
200
+ } else {
201
+ checks.redis = "skipped";
202
+ }
203
+ const ready = checks.database === "ok" && (checks.redis === "ok" || checks.redis === "skipped");
204
+ return jsonResponse({
205
+ status: ready ? "ready" : "not_ready",
206
+ checks
207
+ }, { status: ready ? 200 : 503 });
208
+ }
209
+ };
210
+ }
211
+ export {
212
+ createHealthRoutes,
213
+ checkRedis,
214
+ checkDatabase
215
+ };
@@ -1,298 +1,11 @@
1
1
  // @bun
2
- // ../../src/core/runtime/asyncContextStore.ts
3
- import { AsyncLocalStorage } from "async_hooks";
4
- function createAsyncContextStore(key) {
5
- const symbol = Symbol.for(key);
6
- const globalRecord = globalThis;
7
- const existing = globalRecord[symbol];
8
- if (existing) {
9
- return existing;
10
- }
11
- const store = new AsyncLocalStorage;
12
- globalRecord[symbol] = store;
13
- return store;
14
- }
2
+ var __jsonParse = (a) => JSON.parse(a);
15
3
 
16
- // ../../src/core/auth/authContext.ts
17
- var authContext = createAsyncContextStore("@getstrata/authContext");
18
- function currentAuthUser() {
19
- return authContext.getStore() ?? null;
20
- }
21
-
22
- // ../../src/core/errors/http.ts
23
- class HttpError extends Error {
24
- status;
25
- details;
26
- constructor(status, message, details) {
27
- super(message);
28
- this.name = new.target.name;
29
- this.status = status;
30
- this.details = details;
31
- }
32
- }
33
-
34
- class BadRequestError extends HttpError {
35
- constructor(message = "Bad Request", details) {
36
- super(400, message, details);
37
- }
38
- }
39
- class ConflictError extends HttpError {
40
- constructor(message = "Conflict", details) {
41
- super(409, message, details);
42
- }
43
- }
44
-
45
- class UnprocessableEntityError extends HttpError {
46
- constructor(message = "Unprocessable Entity", details) {
47
- super(422, message, details);
48
- }
49
- }
50
- class ForbiddenError extends HttpError {
51
- constructor(message = "Forbidden", details) {
52
- super(403, message, details);
53
- }
54
- }
55
-
56
- class UnauthorizedError extends HttpError {
57
- constructor(message = "Unauthorized", details) {
58
- super(401, message, details);
59
- }
60
- }
61
- class PreconditionFailedError extends HttpError {
62
- constructor(message = "Precondition Failed", details) {
63
- super(412, message, details);
64
- }
65
- }
66
-
67
- // ../../src/core/contracts/di.ts
68
- function getRequiredDependency(dependencies, key) {
69
- const dependency = dependencies[key];
70
- if (dependency === undefined) {
71
- throw new Error(`Required dependency "${key}" is not registered.`);
72
- }
73
- return dependency;
74
- }
75
-
76
- // ../../src/core/contracts/serviceTokens.ts
77
- var CORE_POLICY_GATE_TOKEN = "core.policyGate";
78
- var CORE_AUTH_TOKEN = "core.auth";
79
-
80
- // ../../src/core/logging/logger.ts
81
- class Logger {
82
- channel;
83
- constructor(channel = "app") {
84
- this.channel = channel;
85
- }
86
- write(level, message, context = {}) {
87
- const entry = {
88
- level,
89
- channel: this.channel,
90
- message,
91
- timestamp: new Date().toISOString(),
92
- ...context
93
- };
94
- const line = JSON.stringify(entry);
95
- if (level === "error") {
96
- console.error(line);
97
- return;
98
- }
99
- console.log(line);
100
- }
101
- debug(message, context) {
102
- this.write("debug", message, context);
103
- }
104
- info(message, context) {
105
- this.write("info", message, context);
106
- }
107
- warn(message, context) {
108
- this.write("warn", message, context);
109
- }
110
- error(message, context) {
111
- this.write("error", message, context);
112
- }
113
- }
114
- var appLogger = new Logger("app");
115
-
116
- // ../../src/core/runtime/applicationRegistry.ts
117
- var APPLICATION_CONTEXT_KEY = Symbol.for("@getstrata/applicationContext");
118
- var activeContext;
119
- function readStoredApplicationContext() {
120
- if (activeContext) {
121
- return activeContext;
122
- }
123
- const globalContext = globalThis[APPLICATION_CONTEXT_KEY];
124
- if (globalContext) {
125
- activeContext = globalContext;
126
- }
127
- return activeContext;
128
- }
129
- function requireActiveApplicationContext() {
130
- const context = readStoredApplicationContext();
131
- if (!context) {
132
- throw new Error("The application context has not been bootstrapped.");
133
- }
134
- return context;
135
- }
136
- function resolveApplicationCache() {
137
- return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
138
- }
139
- function resolveApplicationAuth() {
140
- return requireActiveApplicationContext().container.resolve(CORE_AUTH_TOKEN);
141
- }
142
- function resolveApplicationPolicyGate() {
143
- return requireActiveApplicationContext().container.resolve(CORE_POLICY_GATE_TOKEN);
144
- }
145
-
146
- // ../../src/core/crypto/nonCryptographicHash.ts
147
- function nonCryptographicDigest(input) {
148
- return Bun.hash(input).toString(16);
149
- }
150
-
151
- // ../../src/core/http/etag.ts
152
- function isEtagEnabled() {
153
- return (process.env.FEATURE_ETAG ?? "true") !== "false";
154
- }
155
- function formatWeakEtag(digest) {
156
- return `W/"${digest}"`;
157
- }
158
- function etagFromResource(resource) {
159
- const version = resource.updated_at ?? resource.created_at ?? "";
160
- const versionText = version instanceof Date ? version.toISOString() : version ? String(version) : "0";
161
- const digest = nonCryptographicDigest(`${String(resource.id ?? "0")}:${versionText}`).slice(0, 32);
162
- return formatWeakEtag(digest);
163
- }
164
- function normalizeEtag(value) {
165
- return value.trim();
166
- }
167
- function etagValuesMatch(left, right) {
168
- return normalizeEtag(left) === normalizeEtag(right);
169
- }
170
- function parseEtagList(header) {
171
- if (!header) {
172
- return [];
173
- }
174
- return header.split(",").map((value) => normalizeEtag(value)).filter(Boolean);
175
- }
176
- function ifNoneMatchSatisfied(request, etag) {
177
- const header = request.headers.get("if-none-match");
178
- if (!header) {
179
- return false;
180
- }
181
- if (header.trim() === "*") {
182
- return true;
183
- }
184
- return parseEtagList(header).some((candidate) => etagValuesMatch(candidate, etag));
185
- }
186
- function ifMatchSatisfied(request, etag) {
187
- const header = request.headers.get("if-match");
188
- if (!header) {
189
- return false;
190
- }
191
- if (header.trim() === "*") {
192
- return true;
193
- }
194
- return parseEtagList(header).some((candidate) => etagValuesMatch(candidate, etag));
195
- }
196
- function assertIfMatch(request, etag, options = {}) {
197
- const header = request.headers.get("if-match");
198
- if (!header) {
199
- if (options.required) {
200
- throw new PreconditionFailedError("If-Match header is required.");
201
- }
202
- return;
203
- }
204
- if (!ifMatchSatisfied(request, etag)) {
205
- throw new PreconditionFailedError("Resource ETag does not match If-Match.");
206
- }
207
- }
208
- function applyEtagHeaders(headers, etag) {
209
- const next = new Headers(headers);
210
- next.set("ETag", etag);
211
- next.set("Cache-Control", "private, must-revalidate");
212
- next.append("Vary", "Authorization");
213
- next.append("Vary", "X-Tenant-Id");
214
- return next;
215
- }
216
- function notModifiedResponse(etag) {
217
- return new Response(null, {
218
- status: 304,
219
- headers: applyEtagHeaders(new Headers, etag)
220
- });
221
- }
222
- function applyConditionalGet(request, response, etag) {
223
- if (!isEtagEnabled()) {
224
- return response;
225
- }
226
- if (ifNoneMatchSatisfied(request, etag)) {
227
- return notModifiedResponse(etag);
228
- }
229
- const headers = applyEtagHeaders(new Headers(response.headers), etag);
230
- return new Response(response.body, {
231
- status: response.status,
232
- statusText: response.statusText,
233
- headers
234
- });
235
- }
236
-
237
- // ../../src/core/tenant/tenantContext.ts
238
- var tenantContext = createAsyncContextStore("@getstrata/tenantContext");
239
-
240
- // ../../src/core/http/validation.ts
241
- function parsePositiveIntParam(value, name = "id") {
242
- const parsed = Number.parseInt(value, 10);
243
- if (!Number.isInteger(parsed) || parsed <= 0) {
244
- throw new BadRequestError(`Invalid ${name}. Expected a positive integer.`);
245
- }
246
- return parsed;
247
- }
248
-
249
- // ../../src/core/http/securedRouteModelBinding.ts
250
- function isMutatingPolicyAction(action) {
251
- return action === "update" || action === "delete";
252
- }
253
- function securedBindRouteModel(param, resolver, authorization, handler) {
254
- return async (request) => {
255
- const id = parsePositiveIntParam(String(request.params[param]), String(param));
256
- const model = await resolver(id, request);
257
- const gate = resolveApplicationPolicyGate();
258
- const auth = resolveApplicationAuth();
259
- const user = currentAuthUser() ?? await auth.resolve(request);
260
- gate.authorize(authorization.resource, authorization.action, user, model);
261
- if (isEtagEnabled() && isMutatingPolicyAction(authorization.action)) {
262
- assertIfMatch(request, etagFromResource(model), {
263
- required: authorization.requireIfMatch ?? true
264
- });
265
- }
266
- const response = await handler(request, model);
267
- if (isEtagEnabled() && authorization.action === "view") {
268
- return applyConditionalGet(request, response, etagFromResource(model));
269
- }
270
- return response;
271
- };
272
- }
273
- function securedBindRouteModelByKey(param, resolver, authorization, handler) {
274
- return async (request) => {
275
- const key = String(request.params[param] ?? "").trim();
276
- if (!key) {
277
- throw new BadRequestError(`Missing route parameter "${String(param)}".`);
278
- }
279
- const model = await resolver(key, request);
280
- const gate = resolveApplicationPolicyGate();
281
- const auth = resolveApplicationAuth();
282
- const user = currentAuthUser() ?? await auth.resolve(request);
283
- gate.authorize(authorization.resource, authorization.action, user, model);
284
- if (isEtagEnabled() && isMutatingPolicyAction(authorization.action)) {
285
- assertIfMatch(request, etagFromResource(model), {
286
- required: authorization.requireIfMatch ?? true
287
- });
288
- }
289
- const response = await handler(request, model);
290
- if (isEtagEnabled() && authorization.action === "view") {
291
- return applyConditionalGet(request, response, etagFromResource(model));
292
- }
293
- return response;
294
- };
295
- }
4
+ // ../../src/bootstrap/http/securedRouteModelBinding.ts
5
+ import {
6
+ securedBindRouteModel,
7
+ securedBindRouteModelByKey
8
+ } from "@getstrata/core/http/securedRouteModelBinding";
296
9
  export {
297
10
  securedBindRouteModelByKey,
298
11
  securedBindRouteModel
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __jsonParse = (a) => JSON.parse(a);
3
+
2
4
  // ../../src/bootstrap/httpKernel.ts
3
5
  import {
4
6
  createAuthMiddleware,
@@ -39,6 +41,9 @@ function readFrontendMode() {
39
41
  function isViewsEnabled() {
40
42
  return readFrontendMode() === "server-htmx";
41
43
  }
44
+ function isSpaEnabled() {
45
+ return readFrontendMode() === "spa-react";
46
+ }
42
47
 
43
48
  // ../../src/config/rateLimit.ts
44
49
  var LOCAL_LOGIN_RATE_LIMIT = {
@@ -0,0 +1,120 @@
1
+ // @bun
2
+ var __jsonParse = (a) => JSON.parse(a);
3
+
4
+ // ../../src/bootstrap/listeners/invalidateCacheOnModelWrite.ts
5
+ import { eventBus, modelEventName } from "@getstrata/core/events";
6
+ import InvalidateCacheTagsJob from "@getstrata/core/jobs/invalidateCacheTagsJob";
7
+ import { createTrackedJob } from "@getstrata/core/queue/createAppQueue";
8
+
9
+ // ../../src/bootstrap/applicationRegistry.ts
10
+ import {
11
+ resolveApplicationAuth,
12
+ resolveApplicationCache,
13
+ resolveApplicationConfig,
14
+ resolveApplicationDependencies,
15
+ resolveApplicationEventBus,
16
+ resolveApplicationLogger,
17
+ resolveApplicationPolicyGate,
18
+ resolveApplicationQueue,
19
+ setActiveApplicationContext
20
+ } from "@getstrata/core/runtime/applicationRegistry";
21
+
22
+ // ../../src/bootstrap/discoverModules.ts
23
+ import { readdirSync } from "fs";
24
+ import { join } from "path";
25
+ import { pathToFileURL } from "url";
26
+ var DISCOVER_MODULES_STATE_KEY = Symbol.for("@getstrata/discoverModulesState");
27
+ function readDiscoverModulesState() {
28
+ const existing = globalThis[DISCOVER_MODULES_STATE_KEY];
29
+ if (existing) {
30
+ return existing;
31
+ }
32
+ const state = { appModules: [] };
33
+ globalThis[DISCOVER_MODULES_STATE_KEY] = state;
34
+ return state;
35
+ }
36
+ function configureModulesDirectory(modulesDir) {
37
+ readDiscoverModulesState().configuredModulesDir = modulesDir;
38
+ }
39
+ function resolveModulesDirectory(options) {
40
+ const state = readDiscoverModulesState();
41
+ if (options?.modulesDir) {
42
+ return options.modulesDir;
43
+ }
44
+ if (state.configuredModulesDir) {
45
+ return state.configuredModulesDir;
46
+ }
47
+ return join(import.meta.dir, "../modules");
48
+ }
49
+ async function loadDiscoveredModules(options) {
50
+ const modulesDirectory = resolveModulesDirectory(options);
51
+ let moduleNames;
52
+ try {
53
+ moduleNames = readdirSync(modulesDirectory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
54
+ } catch (error) {
55
+ if (error.code === "ENOENT") {
56
+ return [];
57
+ }
58
+ throw error;
59
+ }
60
+ const modules = await Promise.all(moduleNames.map(async (moduleName) => {
61
+ const moduleUrl = pathToFileURL(join(modulesDirectory, moduleName, "index.ts")).href;
62
+ const loaded = await import(moduleUrl);
63
+ return loaded.default;
64
+ }));
65
+ return modules.filter((module) => module?.name !== undefined).sort((left, right) => (left.order ?? 100) - (right.order ?? 100));
66
+ }
67
+ async function ensureModulesLoaded(options) {
68
+ const state = readDiscoverModulesState();
69
+ if (state.appModules.length > 0) {
70
+ return state.appModules;
71
+ }
72
+ state.modulesReady ??= loadDiscoveredModules(options).then((modules) => {
73
+ state.appModules.splice(0, state.appModules.length, ...modules);
74
+ return state.appModules;
75
+ });
76
+ return state.modulesReady;
77
+ }
78
+ function discoverModules() {
79
+ return readDiscoverModulesState().appModules;
80
+ }
81
+
82
+ // ../../src/bootstrap/cache/modelCacheTags.ts
83
+ function cacheTagsForModelWrite(tableName, action) {
84
+ const module = discoverModules().find((entry) => entry.tableName === tableName);
85
+ const baseTags = module?.cacheTags ?? [`${tableName}s`];
86
+ const isDelete = action === "deleted" || action === "force-deleted";
87
+ const extraTags = isDelete ? module?.cacheDeleteExtraTags ?? [] : [];
88
+ return [...new Set([...baseTags, ...extraTags])];
89
+ }
90
+ function discoverModelTableNames() {
91
+ return discoverModules().map((module) => module.tableName).filter((tableName) => tableName !== undefined);
92
+ }
93
+
94
+ // ../../src/bootstrap/listeners/invalidateCacheOnModelWrite.ts
95
+ var MODEL_WRITE_ACTIONS = ["created", "updated", "deleted", "restored", "force-deleted"];
96
+ function registerInvalidateCacheOnModelWriteListeners(bus = eventBus) {
97
+ for (const tableName of discoverModelTableNames()) {
98
+ for (const action of MODEL_WRITE_ACTIONS) {
99
+ bus.listen(modelEventName(tableName, action), async () => {
100
+ const tags = cacheTagsForModelWrite(tableName, action);
101
+ if (tags.length === 0) {
102
+ return;
103
+ }
104
+ let cache;
105
+ let queue;
106
+ try {
107
+ cache = resolveApplicationCache();
108
+ queue = resolveApplicationQueue();
109
+ } catch {
110
+ return;
111
+ }
112
+ const job = createTrackedJob("cache.invalidate-tags", new InvalidateCacheTagsJob(cache));
113
+ await queue.dispatch(job, { tags });
114
+ });
115
+ }
116
+ }
117
+ }
118
+ export {
119
+ registerInvalidateCacheOnModelWriteListeners
120
+ };
@@ -1,4 +1,6 @@
1
1
  // @bun
2
+ var __jsonParse = (a) => JSON.parse(a);
3
+
2
4
  // ../../src/bootstrap/membershipService.ts
3
5
  import { resolveMembershipService } from "@getstrata/core/auth/membershipService";
4
6
  export {
@@ -0,0 +1,18 @@
1
+ // @bun
2
+ var __jsonParse = (a) => JSON.parse(a);
3
+
4
+ // ../../src/bootstrap/metricsRoutes.ts
5
+ import { prometheusRegistry } from "@getstrata/core/metrics/prometheus";
6
+ function createMetricsRoutes() {
7
+ return {
8
+ "/metrics": async () => new Response(prometheusRegistry.renderMetrics(), {
9
+ status: 200,
10
+ headers: {
11
+ "content-type": "text/plain; version=0.0.4; charset=utf-8"
12
+ }
13
+ })
14
+ };
15
+ }
16
+ export {
17
+ createMetricsRoutes
18
+ };