@getstrata/bootstrap 0.2.50 → 0.2.52

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @getstrata/bootstrap changelog
2
2
 
3
+ ## 0.2.52
4
+
5
+ - View provider registers `configureWebErrorView` so `errors/*.eta` render through the app Eta layout. `EtaViewEngine` receives `Request` from the layout-data resolver.
6
+ - `createWebServer` unknown routes and null handlers return styled HTML 404s and run inside request ALS so `configureWebLayoutData` can read the session cookie.
7
+ - `wrapSecuredRouteModelByKey` is documented as the wrong default for public HTML show pages; a missing model is a styled 404 and HTML/composite GET ETags stay off.
8
+
9
+ ## 0.2.51
10
+
11
+ - `createCookieSessionAuthManager` returns `CookieSessionAuthManager` (`signIn`, `signOut`, `signInRedirect`, `signOutRedirect`). Controllers no longer need a local cookie helper.
12
+ - Pass `loadSessionUser` to replace the default `users.learn_subscriber` / `users.is_admin` session query. Keep `mapUser` in the app.
13
+
3
14
  ## 0.2.50
4
15
 
5
16
  - `createCookieSessionAuthManager` / `CookieSessionGuard` turn `CookieSessionStore` into an `AuthGuard` / `AuthManager` for `CORE_AUTH_TOKEN`. Supply a `mapUser` mapper; WorkHub abilities are not baked in. Omit `sql` to read the client from `bindDatabaseConnection()` on every call.
package/README.md CHANGED
@@ -30,7 +30,7 @@ import { createHttpKernel, createAppContext, coreProviders } from "@getstrata/bo
30
30
 
31
31
  `createAppContext()` does not call `assertProductionSecrets`. WorkHub calls that from `App.serve()` / `queue:work`. Sibling HTMX apps can call it in production without WorkHub API tokens when feature flags are off — see [SIBLING-HTMX.md](../../docs/SIBLING-HTMX.md).
32
32
 
33
- Sibling HTMX apps should bind `createCookieSessionAuthManager` from `@getstrata/bootstrap/web/session` instead of HMAC `SessionGuard`. `CookieSessionStore` reads optional `is_admin` from the user row for `wrapWebGlobalAdmin`. Pass `mapUser` to map `is_admin` / `learn_subscriber` onto `AuthUser.role`.
33
+ Sibling HTMX apps should bind `createCookieSessionAuthManager` from `@getstrata/bootstrap/web/session` instead of HMAC `SessionGuard`. Use `signIn` / `signOut` (or the redirect helpers) instead of calling `CookieSessionStore` from controllers. Pass `mapUser` to map roles in the app. Pass `loadSessionUser` when the default `learn_subscriber` / `is_admin` SELECT does not match your schema.
34
34
 
35
35
  `wrapWeb` applies the web group (CSRF + flash) and `withErrorHandling`, so CSRF `ForbiddenError` becomes an HTML 403 in `FRONTEND_MODE=server-htmx`. `wrapWebLogin` / `wrapWebRegister` include that web group plus throttle — do not wrap them with `wrapWeb` again.
36
36
 
@@ -27,4 +27,4 @@ export { coreProviders } from "./providers/index.ts";
27
27
  export { registerDefaultJobs } from "./queue/defaultJobs.ts";
28
28
  export { RouteRegistry, routeRegistry } from "./routeRegistry.ts";
29
29
  export { assertProductionSecrets } from "./secretsGuard.ts";
30
- export { CookieSessionGuard, CookieSessionStore, createCookieSessionAuthManager, createCsrfProtection, createRouteKernel, createWebServer, type MapSessionUser, type ParsedForm, parseFormBody, routeParams, type SessionUser, slugify, toRouteRequest, type WebServerOptions, wrapSecuredRouteModelByKey, wrapWebLogin, wrapWebRegister, } from "./web/index.ts";
30
+ export { CookieSessionAuthManager, CookieSessionGuard, CookieSessionStore, createCookieSessionAuthManager, createCsrfProtection, createRouteKernel, createWebServer, type LoadSessionUser, type MapSessionUser, type ParsedForm, parseFormBody, routeParams, type SessionUser, slugify, toRouteRequest, type WebServerOptions, wrapSecuredRouteModelByKey, wrapWebLogin, wrapWebRegister, } from "./web/index.ts";
@@ -4,5 +4,5 @@
4
4
  export { createCsrfProtection, type ParsedForm, parseFormBody } from "./forms.ts";
5
5
  export { createRouteKernel, routeParams, toRouteRequest, wrapSecuredRouteModelByKey, wrapWebLogin, wrapWebRegister, } from "./routing.ts";
6
6
  export { convertAppRoutesToBunRoutes, createWebServer, type WebServerOptions } from "./server.ts";
7
- export { CookieSessionGuard, CookieSessionStore, createCookieSessionAuthManager, type MapSessionUser, type SessionUser, } from "./session.ts";
7
+ export { CookieSessionAuthManager, CookieSessionGuard, CookieSessionStore, createCookieSessionAuthManager, type LoadSessionUser, type MapSessionUser, type SessionUser, } from "./session.ts";
8
8
  export { slugify } from "./slug.ts";
@@ -9,10 +9,16 @@ export declare function routeParams(request: Request): Record<string, string>;
9
9
  * Mutates the request in place so instanceof Request and Bun internals stay valid.
10
10
  */
11
11
  export declare function toRouteRequest<TParams extends Record<string, string>>(request: Request): RouteRequest<TParams>;
12
- /** Laravel-style secured route-model binding for string keys (slugs, UUIDs). */
12
+ /**
13
+ * Laravel-style secured route-model binding for string keys (slugs, UUIDs).
14
+ * Public HTML show pages should look up the model in the controller instead.
15
+ * If this wrapper is still used on HTML, a missing model becomes a styled 404
16
+ * and GET ETags are skipped for HTML / composite objects unless `etag: true`.
17
+ */
13
18
  export declare function wrapSecuredRouteModelByKey<TParams extends Record<string, string>, TModel, TParam extends keyof TParams & string>(param: TParam, resolver: (key: string, request: RouteRequest<TParams>) => Promise<TModel>, authorization: {
14
19
  resource: string;
15
20
  action: "view" | "create" | "update" | "delete";
21
+ etag?: boolean;
16
22
  }, handler: (request: RouteRequest<TParams>, model: TModel) => Response | Promise<Response>): RouteHandler;
17
23
  /** Web group (CSRF + flash + HTML errors) plus login throttle. Do not wrap with wrapWeb again. */
18
24
  export declare function wrapWebLogin(kernel: HttpKernel, handler: RouteHandler, onThrottled: (request: Request) => Response | Promise<Response>): RouteHandler;
@@ -11,6 +11,7 @@ type SqlClient = {
11
11
  unsafe<T>(query: string, params?: readonly unknown[]): Promise<T[]>;
12
12
  };
13
13
  type SqlSource = SqlClient | (() => SqlClient);
14
+ export type LoadSessionUser = (sql: SqlClient, sessionId: string) => Promise<SessionUser | null>;
14
15
  declare function defaultSessionSql(): SqlClient;
15
16
  export type MapSessionUser = (user: SessionUser) => AuthUser;
16
17
  declare function defaultMapSessionUser(user: SessionUser): AuthUser;
@@ -19,9 +20,11 @@ export declare class CookieSessionStore {
19
20
  private readonly secret;
20
21
  private readonly cookieName;
21
22
  private readonly maxAgeSeconds;
22
- constructor(sqlSource: SqlSource, secret: string, cookieName?: string, maxAgeSeconds?: number);
23
+ private readonly loadSessionUser;
24
+ constructor(sqlSource: SqlSource, secret: string, cookieName?: string, maxAgeSeconds?: number, loadSessionUser?: LoadSessionUser);
23
25
  cookieHeader(_user: SessionUser, sessionId: string): string;
24
26
  clearCookieHeader(): string;
27
+ sessionIdFromRequest(request: Request): string | null;
25
28
  private withSecureFlag;
26
29
  private sql;
27
30
  create(user: SessionUser): Promise<string>;
@@ -35,6 +38,19 @@ export declare class CookieSessionGuard implements AuthGuard {
35
38
  constructor(store: CookieSessionStore, mapUser?: MapSessionUser);
36
39
  resolve(request: Request): Promise<AuthUser | null>;
37
40
  }
41
+ export declare class CookieSessionAuthManager extends AuthManager {
42
+ readonly store: CookieSessionStore;
43
+ constructor(store: CookieSessionStore, mapUser?: MapSessionUser);
44
+ signIn(user: SessionUser): Promise<{
45
+ sessionId: string;
46
+ setCookie: string;
47
+ }>;
48
+ signOut(request: Request): Promise<{
49
+ setCookie: string;
50
+ }>;
51
+ signInRedirect(user: SessionUser, location: string, status?: number): Promise<Response>;
52
+ signOutRedirect(request: Request, location: string, status?: number): Promise<Response>;
53
+ }
38
54
  export interface CreateCookieSessionAuthManagerOptions {
39
55
  store?: CookieSessionStore;
40
56
  sql?: SqlSource;
@@ -42,6 +58,7 @@ export interface CreateCookieSessionAuthManagerOptions {
42
58
  cookieName?: string;
43
59
  maxAgeSeconds?: number;
44
60
  mapUser?: MapSessionUser;
61
+ loadSessionUser?: LoadSessionUser;
45
62
  }
46
- declare function createCookieSessionAuthManager(options?: CreateCookieSessionAuthManagerOptions): AuthManager;
63
+ declare function createCookieSessionAuthManager(options?: CreateCookieSessionAuthManagerOptions): CookieSessionAuthManager;
47
64
  export { createCookieSessionAuthManager, defaultMapSessionUser, defaultSessionSql };
@@ -460,7 +460,13 @@ var storage_default = storageProvider;
460
460
 
461
461
  // ../../src/bootstrap/providers/view.ts
462
462
  import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
463
- import { DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, resolveWebLayoutData } from "@getstrata/core/view";
463
+ import {
464
+ configureWebErrorView,
465
+ DEFAULT_VIEWS_DIRECTORY,
466
+ EtaViewEngine,
467
+ errorTemplateName,
468
+ resolveWebLayoutData
469
+ } from "@getstrata/core/view";
464
470
 
465
471
  // ../../src/config/frontend.ts
466
472
  function readFrontendMode() {
@@ -491,7 +497,15 @@ var viewProvider = {
491
497
  }
492
498
  const viewsDirectory = process.env.VIEW_DIRECTORY?.trim() || DEFAULT_VIEWS_DIRECTORY;
493
499
  config.set(VIEW_DIRECTORY_CONFIG_KEY, viewsDirectory);
494
- container.set(CORE_VIEW_TOKEN, new EtaViewEngine(viewsDirectory, () => resolveWebLayoutData(container, currentRequestMeta().request)));
500
+ const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
501
+ container.set(CORE_VIEW_TOKEN, engine);
502
+ configureWebErrorView({
503
+ render: async (input) => engine.render(errorTemplateName(input.status), {
504
+ title: input.title,
505
+ message: input.message,
506
+ errors: input.errors
507
+ })
508
+ });
495
509
  }
496
510
  };
497
511
 
@@ -4,7 +4,7 @@ var __jsonParse = (a) => JSON.parse(a);
4
4
  // ../../src/bootstrap/createRoutes.ts
5
5
  import { applyMiddlewareToRoutes as applyMiddlewareToRoutes3 } from "@getstrata/core/http/middleware";
6
6
  import { jsonResponse as jsonResponse4 } from "@getstrata/core/http/response";
7
- import { htmlResponse as htmlResponse2 } from "@getstrata/core/view";
7
+ import { notFoundHtmlResponse as notFoundHtmlResponse2 } from "@getstrata/core/view";
8
8
 
9
9
  // ../../index.html
10
10
  var strata_default = __jsonParse("{\"index\":\"_.._/_.._/index.html\",\"files\":[{\"input\":\"../../index.html\",\"path\":\"./index-sfreg6q3.js\",\"loader\":\"js\",\"isEntry\":true,\"headers\":{\"etag\":\"Oc_YLpCmV6M\",\"content-type\":\"text/javascript;charset=utf-8\"}},{\"input\":\"../../index.html\",\"path\":\"_.._/_.._/index.html\",\"loader\":\"html\",\"isEntry\":true,\"headers\":{\"etag\":\"rWY90LWIcx8\",\"content-type\":\"text/html;charset=utf-8\"}}]}");
@@ -508,7 +508,7 @@ function mergeSpaRoutes(dependencies, routes) {
508
508
 
509
509
  // ../../src/bootstrap/createWebRoutes.ts
510
510
  import { join as join3 } from "path";
511
- import { htmlResponse } from "@getstrata/core/view";
511
+ import { notFoundHtmlResponse } from "@getstrata/core/view";
512
512
 
513
513
  // ../../src/bootstrap/buildWebModuleRoutes.ts
514
514
  import { applyMiddlewareToRoutes as applyMiddlewareToRoutes2 } from "@getstrata/core/http/middleware";
@@ -551,7 +551,7 @@ function createWebRoutes(dependencies) {
551
551
  const relativePath = pathname.replace(/^\//, "");
552
552
  const file = Bun.file(join3(process.cwd(), "public", relativePath));
553
553
  if (!await file.exists()) {
554
- return htmlResponse("Not Found", { status: 404 });
554
+ return notFoundHtmlResponse();
555
555
  }
556
556
  return new Response(file);
557
557
  };
@@ -1428,7 +1428,7 @@ function createRoutes(dependencies) {
1428
1428
  "/*": async () => {
1429
1429
  registerRoute2("GET", "/*", []);
1430
1430
  if (isViewsEnabled()) {
1431
- return htmlResponse2("Not Found", { status: 404 });
1431
+ return notFoundHtmlResponse2();
1432
1432
  }
1433
1433
  if (isSpaEnabled()) {
1434
1434
  return jsonResponse4({ error: "Not Found" }, { status: 404 });
@@ -3,7 +3,7 @@ var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/createWebRoutes.ts
5
5
  import { join as join2 } from "path";
6
- import { htmlResponse } from "@getstrata/core/view";
6
+ import { notFoundHtmlResponse } from "@getstrata/core/view";
7
7
 
8
8
  // ../../src/config/frontend.ts
9
9
  function readFrontendMode() {
@@ -478,7 +478,7 @@ function createWebRoutes(dependencies) {
478
478
  const relativePath = pathname.replace(/^\//, "");
479
479
  const file = Bun.file(join2(process.cwd(), "public", relativePath));
480
480
  if (!await file.exists()) {
481
- return htmlResponse("Not Found", { status: 404 });
481
+ return notFoundHtmlResponse();
482
482
  }
483
483
  return new Response(file);
484
484
  };
@@ -460,7 +460,13 @@ var storage_default = storageProvider;
460
460
 
461
461
  // ../../src/bootstrap/providers/view.ts
462
462
  import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
463
- import { DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, resolveWebLayoutData } from "@getstrata/core/view";
463
+ import {
464
+ configureWebErrorView,
465
+ DEFAULT_VIEWS_DIRECTORY,
466
+ EtaViewEngine,
467
+ errorTemplateName,
468
+ resolveWebLayoutData
469
+ } from "@getstrata/core/view";
464
470
 
465
471
  // ../../src/config/frontend.ts
466
472
  function readFrontendMode() {
@@ -491,7 +497,15 @@ var viewProvider = {
491
497
  }
492
498
  const viewsDirectory = process.env.VIEW_DIRECTORY?.trim() || DEFAULT_VIEWS_DIRECTORY;
493
499
  config.set(VIEW_DIRECTORY_CONFIG_KEY, viewsDirectory);
494
- container.set(CORE_VIEW_TOKEN, new EtaViewEngine(viewsDirectory, () => resolveWebLayoutData(container, currentRequestMeta().request)));
500
+ const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
501
+ container.set(CORE_VIEW_TOKEN, engine);
502
+ configureWebErrorView({
503
+ render: async (input) => engine.render(errorTemplateName(input.status), {
504
+ title: input.title,
505
+ message: input.message,
506
+ errors: input.errors
507
+ })
508
+ });
495
509
  }
496
510
  };
497
511
 
@@ -3,7 +3,13 @@ var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/providers/view.ts
5
5
  import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
6
- import { DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, resolveWebLayoutData } from "@getstrata/core/view";
6
+ import {
7
+ configureWebErrorView,
8
+ DEFAULT_VIEWS_DIRECTORY,
9
+ EtaViewEngine,
10
+ errorTemplateName,
11
+ resolveWebLayoutData
12
+ } from "@getstrata/core/view";
7
13
 
8
14
  // ../../src/config/frontend.ts
9
15
  function readFrontendMode() {
@@ -34,7 +40,15 @@ var viewProvider = {
34
40
  }
35
41
  const viewsDirectory = process.env.VIEW_DIRECTORY?.trim() || DEFAULT_VIEWS_DIRECTORY;
36
42
  config.set(VIEW_DIRECTORY_CONFIG_KEY, viewsDirectory);
37
- container.set(CORE_VIEW_TOKEN, new EtaViewEngine(viewsDirectory, () => resolveWebLayoutData(container, currentRequestMeta().request)));
43
+ const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
44
+ container.set(CORE_VIEW_TOKEN, engine);
45
+ configureWebErrorView({
46
+ render: async (input) => engine.render(errorTemplateName(input.status), {
47
+ title: input.title,
48
+ message: input.message,
49
+ errors: input.errors
50
+ })
51
+ });
38
52
  }
39
53
  };
40
54
  export {
@@ -450,7 +450,13 @@ var storage_default = storageProvider;
450
450
 
451
451
  // ../../src/bootstrap/providers/view.ts
452
452
  import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
453
- import { DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, resolveWebLayoutData } from "@getstrata/core/view";
453
+ import {
454
+ configureWebErrorView,
455
+ DEFAULT_VIEWS_DIRECTORY,
456
+ EtaViewEngine,
457
+ errorTemplateName,
458
+ resolveWebLayoutData
459
+ } from "@getstrata/core/view";
454
460
 
455
461
  // ../../src/config/frontend.ts
456
462
  function readFrontendMode() {
@@ -481,7 +487,15 @@ var viewProvider = {
481
487
  }
482
488
  const viewsDirectory = process.env.VIEW_DIRECTORY?.trim() || DEFAULT_VIEWS_DIRECTORY;
483
489
  config.set(VIEW_DIRECTORY_CONFIG_KEY, viewsDirectory);
484
- container.set(CORE_VIEW_TOKEN, new EtaViewEngine(viewsDirectory, () => resolveWebLayoutData(container, currentRequestMeta().request)));
490
+ const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
491
+ container.set(CORE_VIEW_TOKEN, engine);
492
+ configureWebErrorView({
493
+ render: async (input) => engine.render(errorTemplateName(input.status), {
494
+ title: input.title,
495
+ message: input.message,
496
+ errors: input.errors
497
+ })
498
+ });
485
499
  }
486
500
  };
487
501
 
@@ -2,10 +2,21 @@
2
2
  var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/web/server.ts
5
+ import { currentRequestMeta, runWithRequestMeta } from "@getstrata/core/http/requestMetaContext";
6
+ import { notFoundHtmlResponse } from "@getstrata/core/view";
7
+ async function missingHtmlResponse() {
8
+ return notFoundHtmlResponse();
9
+ }
5
10
  function wrapRouteHandler(handler) {
6
11
  return async (request) => {
7
- const response = await handler(request);
8
- return response ?? new Response("Not Found", { status: 404 });
12
+ return await runWithRequestMeta({
13
+ ...currentRequestMeta(),
14
+ request,
15
+ userAgent: request.headers.get("user-agent")
16
+ }, async () => {
17
+ const response = await handler(request);
18
+ return response ?? await missingHtmlResponse();
19
+ });
9
20
  };
10
21
  }
11
22
  function convertAppRoutesToBunRoutes(routes) {
@@ -37,19 +48,25 @@ function createWebServer(options) {
37
48
  port: options.port,
38
49
  ...bunRoutes ? { routes: bunRoutes } : {},
39
50
  async fetch(request) {
40
- await options.onRequest?.(request);
41
- const url = new URL(request.url);
42
- if (url.pathname.startsWith("/assets/")) {
43
- const file = Bun.file(`${publicDir}${url.pathname}`);
44
- if (await file.exists()) {
45
- return new Response(file);
51
+ return await runWithRequestMeta({
52
+ ...currentRequestMeta(),
53
+ request,
54
+ userAgent: request.headers.get("user-agent")
55
+ }, async () => {
56
+ await options.onRequest?.(request);
57
+ const url = new URL(request.url);
58
+ if (url.pathname.startsWith("/assets/")) {
59
+ const file = Bun.file(`${publicDir}${url.pathname}`);
60
+ if (await file.exists()) {
61
+ return new Response(file);
62
+ }
46
63
  }
47
- }
48
- if (options.handle) {
49
- const response = await options.handle(request);
50
- return response ?? new Response("Not Found", { status: 404 });
51
- }
52
- return new Response("Not Found", { status: 404 });
64
+ if (options.handle) {
65
+ const response = await options.handle(request);
66
+ return response ?? await missingHtmlResponse();
67
+ }
68
+ return await missingHtmlResponse();
69
+ });
53
70
  }
54
71
  });
55
72
  }
@@ -29,17 +29,45 @@ function defaultMapSessionUser(user) {
29
29
  role: user.is_admin ? "admin" : "member"
30
30
  };
31
31
  }
32
+ async function defaultLoadSessionUser(sql, sessionId) {
33
+ const rows = await sql.unsafe(`SELECT s.id, s.user_id, s.expires_at, u.name, u.email, u.learn_subscriber,
34
+ COALESCE(u.is_admin, false) AS is_admin
35
+ FROM sessions s
36
+ INNER JOIN users u ON u.id = s.user_id
37
+ WHERE s.id = $1 AND s.expires_at > NOW()`, [sessionId]);
38
+ const row = rows[0];
39
+ if (!row)
40
+ return null;
41
+ return {
42
+ id: row.user_id,
43
+ name: row.name,
44
+ email: row.email,
45
+ learn_subscriber: row.learn_subscriber,
46
+ is_admin: row.is_admin
47
+ };
48
+ }
49
+ function redirectWithCookie(location, setCookie, status) {
50
+ return new Response(null, {
51
+ status,
52
+ headers: {
53
+ Location: location,
54
+ "Set-Cookie": setCookie
55
+ }
56
+ });
57
+ }
32
58
 
33
59
  class CookieSessionStore {
34
60
  sqlSource;
35
61
  secret;
36
62
  cookieName;
37
63
  maxAgeSeconds;
38
- constructor(sqlSource, secret, cookieName = "strata_session", maxAgeSeconds = 60 * 60 * 24 * 14) {
64
+ loadSessionUser;
65
+ constructor(sqlSource, secret, cookieName = "strata_session", maxAgeSeconds = 60 * 60 * 24 * 14, loadSessionUser = defaultLoadSessionUser) {
39
66
  this.sqlSource = sqlSource;
40
67
  this.secret = secret;
41
68
  this.cookieName = cookieName;
42
69
  this.maxAgeSeconds = maxAgeSeconds;
70
+ this.loadSessionUser = loadSessionUser;
43
71
  }
44
72
  cookieHeader(_user, sessionId) {
45
73
  const payload = `${sessionId}.${this.sign(sessionId)}`;
@@ -48,6 +76,17 @@ class CookieSessionStore {
48
76
  clearCookieHeader() {
49
77
  return this.withSecureFlag(`${this.cookieName}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);
50
78
  }
79
+ sessionIdFromRequest(request) {
80
+ const cookie = readRequestCookie(request, this.cookieName);
81
+ const raw = cookie ?? null;
82
+ if (!raw)
83
+ return null;
84
+ const [sessionId, signature] = raw.split(".");
85
+ if (!sessionId || !signature || signature !== this.sign(sessionId)) {
86
+ return null;
87
+ }
88
+ return sessionId;
89
+ }
51
90
  withSecureFlag(header) {
52
91
  if (true) {
53
92
  return header;
@@ -71,29 +110,10 @@ class CookieSessionStore {
71
110
  await this.sql().unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
72
111
  }
73
112
  async read(request) {
74
- const cookie = readRequestCookie(request, this.cookieName);
75
- const raw = cookie ?? null;
76
- if (!raw)
113
+ const sessionId = this.sessionIdFromRequest(request);
114
+ if (!sessionId)
77
115
  return null;
78
- const [sessionId, signature] = raw.split(".");
79
- if (!sessionId || !signature || signature !== this.sign(sessionId)) {
80
- return null;
81
- }
82
- const rows = await this.sql().unsafe(`SELECT s.id, s.user_id, s.expires_at, u.name, u.email, u.learn_subscriber,
83
- COALESCE(u.is_admin, false) AS is_admin
84
- FROM sessions s
85
- INNER JOIN users u ON u.id = s.user_id
86
- WHERE s.id = $1 AND s.expires_at > NOW()`, [sessionId]);
87
- const row = rows[0];
88
- if (!row)
89
- return null;
90
- return {
91
- id: row.user_id,
92
- name: row.name,
93
- email: row.email,
94
- learn_subscriber: row.learn_subscriber,
95
- is_admin: row.is_admin
96
- };
116
+ return this.loadSessionUser(this.sql(), sessionId);
97
117
  }
98
118
  sign(value) {
99
119
  return createHash("sha256").update(`${value}.${this.secret}`).digest("hex").slice(0, 32);
@@ -115,11 +135,39 @@ class CookieSessionGuard {
115
135
  return this.mapUser(user);
116
136
  }
117
137
  }
138
+
139
+ class CookieSessionAuthManager extends AuthManager {
140
+ store;
141
+ constructor(store, mapUser = defaultMapSessionUser) {
142
+ super(new CookieSessionGuard(store, mapUser));
143
+ this.store = store;
144
+ }
145
+ async signIn(user) {
146
+ const sessionId = await this.store.create(user);
147
+ return { sessionId, setCookie: this.store.cookieHeader(user, sessionId) };
148
+ }
149
+ async signOut(request) {
150
+ const sessionId = this.store.sessionIdFromRequest(request);
151
+ if (sessionId) {
152
+ await this.store.destroy(sessionId);
153
+ }
154
+ return { setCookie: this.store.clearCookieHeader() };
155
+ }
156
+ async signInRedirect(user, location, status = 302) {
157
+ const { setCookie } = await this.signIn(user);
158
+ return redirectWithCookie(location, setCookie, status);
159
+ }
160
+ async signOutRedirect(request, location, status = 302) {
161
+ const { setCookie } = await this.signOut(request);
162
+ return redirectWithCookie(location, setCookie, status);
163
+ }
164
+ }
118
165
  function createCookieSessionAuthManager(options = {}) {
119
- const store = options.store ?? new CookieSessionStore(options.sql ?? defaultSessionSql, options.secret ?? process.env.SESSION_SECRET?.trim() ?? "", options.cookieName, options.maxAgeSeconds);
120
- return new AuthManager(new CookieSessionGuard(store, options.mapUser ?? defaultMapSessionUser));
166
+ const store = options.store ?? new CookieSessionStore(options.sql ?? defaultSessionSql, options.secret ?? process.env.SESSION_SECRET?.trim() ?? "", options.cookieName, options.maxAgeSeconds, options.loadSessionUser);
167
+ return new CookieSessionAuthManager(store, options.mapUser ?? defaultMapSessionUser);
121
168
  }
122
169
  export {
170
+ CookieSessionAuthManager,
123
171
  CookieSessionGuard,
124
172
  CookieSessionStore,
125
173
  createCookieSessionAuthManager,
package/dist/index.js CHANGED
@@ -806,7 +806,13 @@ var storage_default = storageProvider;
806
806
 
807
807
  // ../../src/bootstrap/providers/view.ts
808
808
  import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
809
- import { DEFAULT_VIEWS_DIRECTORY, EtaViewEngine, resolveWebLayoutData } from "@getstrata/core/view";
809
+ import {
810
+ configureWebErrorView,
811
+ DEFAULT_VIEWS_DIRECTORY,
812
+ EtaViewEngine,
813
+ errorTemplateName,
814
+ resolveWebLayoutData
815
+ } from "@getstrata/core/view";
810
816
  var CORE_VIEW_TOKEN = "core.view";
811
817
  var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
812
818
  var viewProvider = {
@@ -817,7 +823,15 @@ var viewProvider = {
817
823
  }
818
824
  const viewsDirectory = process.env.VIEW_DIRECTORY?.trim() || DEFAULT_VIEWS_DIRECTORY;
819
825
  config.set(VIEW_DIRECTORY_CONFIG_KEY, viewsDirectory);
820
- container.set(CORE_VIEW_TOKEN, new EtaViewEngine(viewsDirectory, () => resolveWebLayoutData(container, currentRequestMeta().request)));
826
+ const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
827
+ container.set(CORE_VIEW_TOKEN, engine);
828
+ configureWebErrorView({
829
+ render: async (input) => engine.render(errorTemplateName(input.status), {
830
+ title: input.title,
831
+ message: input.message,
832
+ errors: input.errors
833
+ })
834
+ });
821
835
  }
822
836
  };
823
837
 
@@ -868,7 +882,7 @@ function createAppContext() {
868
882
  }
869
883
  // ../../src/bootstrap/createWebRoutes.ts
870
884
  import { join as join3 } from "path";
871
- import { htmlResponse } from "@getstrata/core/view";
885
+ import { notFoundHtmlResponse } from "@getstrata/core/view";
872
886
  function registerRoute(method, path, middleware) {
873
887
  routeRegistry.register({ method, path, middleware });
874
888
  }
@@ -886,7 +900,7 @@ function createWebRoutes(dependencies) {
886
900
  const relativePath = pathname.replace(/^\//, "");
887
901
  const file = Bun.file(join3(process.cwd(), "public", relativePath));
888
902
  if (!await file.exists()) {
889
- return htmlResponse("Not Found", { status: 404 });
903
+ return notFoundHtmlResponse();
890
904
  }
891
905
  return new Response(file);
892
906
  };
@@ -1201,10 +1215,21 @@ function createRouteKernel(dependencies) {
1201
1215
  return createHttpKernel(dependencies);
1202
1216
  }
1203
1217
  // ../../src/bootstrap/web/server.ts
1218
+ import { currentRequestMeta as currentRequestMeta2, runWithRequestMeta } from "@getstrata/core/http/requestMetaContext";
1219
+ import { notFoundHtmlResponse as notFoundHtmlResponse2 } from "@getstrata/core/view";
1220
+ async function missingHtmlResponse() {
1221
+ return notFoundHtmlResponse2();
1222
+ }
1204
1223
  function wrapRouteHandler(handler) {
1205
1224
  return async (request) => {
1206
- const response = await handler(request);
1207
- return response ?? new Response("Not Found", { status: 404 });
1225
+ return await runWithRequestMeta({
1226
+ ...currentRequestMeta2(),
1227
+ request,
1228
+ userAgent: request.headers.get("user-agent")
1229
+ }, async () => {
1230
+ const response = await handler(request);
1231
+ return response ?? await missingHtmlResponse();
1232
+ });
1208
1233
  };
1209
1234
  }
1210
1235
  function convertAppRoutesToBunRoutes(routes) {
@@ -1236,19 +1261,25 @@ function createWebServer(options) {
1236
1261
  port: options.port,
1237
1262
  ...bunRoutes ? { routes: bunRoutes } : {},
1238
1263
  async fetch(request) {
1239
- await options.onRequest?.(request);
1240
- const url = new URL(request.url);
1241
- if (url.pathname.startsWith("/assets/")) {
1242
- const file = Bun.file(`${publicDir}${url.pathname}`);
1243
- if (await file.exists()) {
1244
- return new Response(file);
1264
+ return await runWithRequestMeta({
1265
+ ...currentRequestMeta2(),
1266
+ request,
1267
+ userAgent: request.headers.get("user-agent")
1268
+ }, async () => {
1269
+ await options.onRequest?.(request);
1270
+ const url = new URL(request.url);
1271
+ if (url.pathname.startsWith("/assets/")) {
1272
+ const file = Bun.file(`${publicDir}${url.pathname}`);
1273
+ if (await file.exists()) {
1274
+ return new Response(file);
1275
+ }
1245
1276
  }
1246
- }
1247
- if (options.handle) {
1248
- const response = await options.handle(request);
1249
- return response ?? new Response("Not Found", { status: 404 });
1250
- }
1251
- return new Response("Not Found", { status: 404 });
1277
+ if (options.handle) {
1278
+ const response = await options.handle(request);
1279
+ return response ?? await missingHtmlResponse();
1280
+ }
1281
+ return await missingHtmlResponse();
1282
+ });
1252
1283
  }
1253
1284
  });
1254
1285
  }
@@ -1280,17 +1311,45 @@ function defaultMapSessionUser(user) {
1280
1311
  role: user.is_admin ? "admin" : "member"
1281
1312
  };
1282
1313
  }
1314
+ async function defaultLoadSessionUser(sql, sessionId) {
1315
+ const rows = await sql.unsafe(`SELECT s.id, s.user_id, s.expires_at, u.name, u.email, u.learn_subscriber,
1316
+ COALESCE(u.is_admin, false) AS is_admin
1317
+ FROM sessions s
1318
+ INNER JOIN users u ON u.id = s.user_id
1319
+ WHERE s.id = $1 AND s.expires_at > NOW()`, [sessionId]);
1320
+ const row = rows[0];
1321
+ if (!row)
1322
+ return null;
1323
+ return {
1324
+ id: row.user_id,
1325
+ name: row.name,
1326
+ email: row.email,
1327
+ learn_subscriber: row.learn_subscriber,
1328
+ is_admin: row.is_admin
1329
+ };
1330
+ }
1331
+ function redirectWithCookie(location, setCookie, status) {
1332
+ return new Response(null, {
1333
+ status,
1334
+ headers: {
1335
+ Location: location,
1336
+ "Set-Cookie": setCookie
1337
+ }
1338
+ });
1339
+ }
1283
1340
 
1284
1341
  class CookieSessionStore {
1285
1342
  sqlSource;
1286
1343
  secret;
1287
1344
  cookieName;
1288
1345
  maxAgeSeconds;
1289
- constructor(sqlSource, secret, cookieName = "strata_session", maxAgeSeconds = 60 * 60 * 24 * 14) {
1346
+ loadSessionUser;
1347
+ constructor(sqlSource, secret, cookieName = "strata_session", maxAgeSeconds = 60 * 60 * 24 * 14, loadSessionUser = defaultLoadSessionUser) {
1290
1348
  this.sqlSource = sqlSource;
1291
1349
  this.secret = secret;
1292
1350
  this.cookieName = cookieName;
1293
1351
  this.maxAgeSeconds = maxAgeSeconds;
1352
+ this.loadSessionUser = loadSessionUser;
1294
1353
  }
1295
1354
  cookieHeader(_user, sessionId) {
1296
1355
  const payload = `${sessionId}.${this.sign(sessionId)}`;
@@ -1299,6 +1358,17 @@ class CookieSessionStore {
1299
1358
  clearCookieHeader() {
1300
1359
  return this.withSecureFlag(`${this.cookieName}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);
1301
1360
  }
1361
+ sessionIdFromRequest(request) {
1362
+ const cookie = readRequestCookie(request, this.cookieName);
1363
+ const raw = cookie ?? null;
1364
+ if (!raw)
1365
+ return null;
1366
+ const [sessionId, signature] = raw.split(".");
1367
+ if (!sessionId || !signature || signature !== this.sign(sessionId)) {
1368
+ return null;
1369
+ }
1370
+ return sessionId;
1371
+ }
1302
1372
  withSecureFlag(header) {
1303
1373
  if (true) {
1304
1374
  return header;
@@ -1322,29 +1392,10 @@ class CookieSessionStore {
1322
1392
  await this.sql().unsafe(`DELETE FROM sessions WHERE id = $1`, [sessionId]);
1323
1393
  }
1324
1394
  async read(request) {
1325
- const cookie = readRequestCookie(request, this.cookieName);
1326
- const raw = cookie ?? null;
1327
- if (!raw)
1395
+ const sessionId = this.sessionIdFromRequest(request);
1396
+ if (!sessionId)
1328
1397
  return null;
1329
- const [sessionId, signature] = raw.split(".");
1330
- if (!sessionId || !signature || signature !== this.sign(sessionId)) {
1331
- return null;
1332
- }
1333
- const rows = await this.sql().unsafe(`SELECT s.id, s.user_id, s.expires_at, u.name, u.email, u.learn_subscriber,
1334
- COALESCE(u.is_admin, false) AS is_admin
1335
- FROM sessions s
1336
- INNER JOIN users u ON u.id = s.user_id
1337
- WHERE s.id = $1 AND s.expires_at > NOW()`, [sessionId]);
1338
- const row = rows[0];
1339
- if (!row)
1340
- return null;
1341
- return {
1342
- id: row.user_id,
1343
- name: row.name,
1344
- email: row.email,
1345
- learn_subscriber: row.learn_subscriber,
1346
- is_admin: row.is_admin
1347
- };
1398
+ return this.loadSessionUser(this.sql(), sessionId);
1348
1399
  }
1349
1400
  sign(value) {
1350
1401
  return createHash("sha256").update(`${value}.${this.secret}`).digest("hex").slice(0, 32);
@@ -1366,9 +1417,36 @@ class CookieSessionGuard {
1366
1417
  return this.mapUser(user);
1367
1418
  }
1368
1419
  }
1420
+
1421
+ class CookieSessionAuthManager extends AuthManager2 {
1422
+ store;
1423
+ constructor(store, mapUser = defaultMapSessionUser) {
1424
+ super(new CookieSessionGuard(store, mapUser));
1425
+ this.store = store;
1426
+ }
1427
+ async signIn(user) {
1428
+ const sessionId = await this.store.create(user);
1429
+ return { sessionId, setCookie: this.store.cookieHeader(user, sessionId) };
1430
+ }
1431
+ async signOut(request) {
1432
+ const sessionId = this.store.sessionIdFromRequest(request);
1433
+ if (sessionId) {
1434
+ await this.store.destroy(sessionId);
1435
+ }
1436
+ return { setCookie: this.store.clearCookieHeader() };
1437
+ }
1438
+ async signInRedirect(user, location, status = 302) {
1439
+ const { setCookie } = await this.signIn(user);
1440
+ return redirectWithCookie(location, setCookie, status);
1441
+ }
1442
+ async signOutRedirect(request, location, status = 302) {
1443
+ const { setCookie } = await this.signOut(request);
1444
+ return redirectWithCookie(location, setCookie, status);
1445
+ }
1446
+ }
1369
1447
  function createCookieSessionAuthManager(options = {}) {
1370
- const store = options.store ?? new CookieSessionStore(options.sql ?? defaultSessionSql, options.secret ?? process.env.SESSION_SECRET?.trim() ?? "", options.cookieName, options.maxAgeSeconds);
1371
- return new AuthManager2(new CookieSessionGuard(store, options.mapUser ?? defaultMapSessionUser));
1448
+ const store = options.store ?? new CookieSessionStore(options.sql ?? defaultSessionSql, options.secret ?? process.env.SESSION_SECRET?.trim() ?? "", options.cookieName, options.maxAgeSeconds, options.loadSessionUser);
1449
+ return new CookieSessionAuthManager(store, options.mapUser ?? defaultMapSessionUser);
1372
1450
  }
1373
1451
  // ../../src/bootstrap/web/slug.ts
1374
1452
  function slugify(value) {
@@ -1385,6 +1463,7 @@ export {
1385
1463
  CORE_POLICY_GATE_TOKEN,
1386
1464
  CORE_QUEUE_TOKEN,
1387
1465
  CORE_TOKEN_SERVICE_TOKEN,
1466
+ CookieSessionAuthManager,
1388
1467
  CookieSessionGuard,
1389
1468
  CookieSessionStore,
1390
1469
  DATABASE_URL_CONFIG_KEY,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/bootstrap",
3
- "version": "0.2.50",
3
+ "version": "0.2.52",
4
4
  "description": "Strata application bootstrap — HttpKernel, DI, web session helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -173,14 +173,14 @@
173
173
  "build:bundle": "bun build index.ts --outdir dist --target bun --external bun --external eta --external @getstrata/core",
174
174
  "build:types": "tsc -p tsconfig.types.json && bun ../../scripts/prune-bootstrap-dist-types.ts",
175
175
  "prepublishOnly": "bun run build",
176
- "build:subpaths": "bun build entries/applicationRegistry.ts entries/buildModuleRoutes.ts entries/buildWebModuleRoutes.ts entries/cache/modelCacheTags.ts entries/config.ts entries/context.ts entries/contracts.ts entries/createWebRoutes.ts entries/createRoutes.ts entries/createSpaRoutes.ts entries/dependencies.ts entries/discoverModules.ts entries/health.ts entries/http/securedRouteModelBinding.ts entries/httpKernel.ts entries/listeners/invalidateCacheOnModelWrite.ts entries/membershipService.ts entries/metricsRoutes.ts entries/queue/defaultJobs.ts entries/providers.ts entries/providers/view.ts entries/routeRegistry.ts entries/schedule.ts entries/secretsGuard.ts entries/web/forms.ts entries/web/routing.ts entries/web/server.ts entries/web/session.ts entries/web/slug.ts --outdir dist --root . --target bun --external bun --external eta --external @getstrata/core --external @getstrata/bootstrap --external @getstrata/bootstrap/applicationRegistry --external @getstrata/bootstrap/buildModuleRoutes --external @getstrata/bootstrap/buildWebModuleRoutes --external @getstrata/bootstrap/cache/modelCacheTags --external @getstrata/bootstrap/config --external @getstrata/bootstrap/context --external @getstrata/bootstrap/contracts --external @getstrata/bootstrap/createWebRoutes --external @getstrata/bootstrap/createRoutes --external @getstrata/bootstrap/createSpaRoutes --external @getstrata/bootstrap/dependencies --external @getstrata/bootstrap/discoverModules --external @getstrata/bootstrap/health --external @getstrata/bootstrap/http/securedRouteModelBinding --external @getstrata/bootstrap/httpKernel --external @getstrata/bootstrap/listeners/invalidateCacheOnModelWrite --external @getstrata/bootstrap/membershipService --external @getstrata/bootstrap/metricsRoutes --external @getstrata/bootstrap/queue/defaultJobs --external @getstrata/bootstrap/providers --external @getstrata/bootstrap/providers/view --external @getstrata/bootstrap/routeRegistry --external @getstrata/bootstrap/schedule --external @getstrata/bootstrap/secretsGuard --external @getstrata/bootstrap/web/forms --external @getstrata/bootstrap/web/routing --external @getstrata/bootstrap/web/server --external @getstrata/bootstrap/web/session --external @getstrata/bootstrap/web/slug --external @getstrata/core/auth/accessControl --external @getstrata/core/auth/abilityChecker --external @getstrata/core/auth/authContext --external @getstrata/core/auth/guard --external @getstrata/core/auth/membershipContext --external @getstrata/core/auth/membershipMiddleware --external @getstrata/core/auth/membershipScope --external @getstrata/core/auth/membershipService --external @getstrata/core/auth/oauth/oidcProvider --external @getstrata/core/auth/oauth/providers --external @getstrata/core/auth/oauth/samlProvider --external @getstrata/core/auth/oauth/types --external @getstrata/core/auth/password --external @getstrata/core/auth/policy --external @getstrata/core/auth/scimAuthMiddleware --external @getstrata/core/auth/sessionCookie --external @getstrata/core/auth/sessionGuard --external @getstrata/core/auth/tokenHash --external @getstrata/core/audit/exportAuditLogs --external @getstrata/core/audit/siemFormatter --external @getstrata/core/admin/formatValue --external @getstrata/core/admin/registry --external @getstrata/core/admin/types --external @getstrata/core/cache/tags --external @getstrata/core/cache/createCacheStore --external @getstrata/core/cache/repository --external @getstrata/core/cache/simpleCache --external @getstrata/core/cache/simpleCacheStore --external @getstrata/core/config/envSchema --external @getstrata/core/contracts/serviceTokens --external @getstrata/core/contracts/container --external @getstrata/core/contracts/di --external @getstrata/core/crypto/fieldEncryption --external @getstrata/core/crypto/mfaSecret --external @getstrata/core/database --external @getstrata/core/database/baseRepository --external @getstrata/core/database/bindConnection --external @getstrata/core/database/boundConnection --external @getstrata/core/database/connection --external @getstrata/core/database/defaultConnection --external @getstrata/core/database/errors --external @getstrata/core/database/factory --external @getstrata/core/database/migrations --external @getstrata/core/database/migrations/types --external @getstrata/core/database/model --external @getstrata/core/database/query --external @getstrata/core/database/relationships --external @getstrata/core/database/repositoryConnection --external @getstrata/core/database/repositoryQuery --external @getstrata/core/database/seeders --external @getstrata/core/database/seeders/types --external @getstrata/core/database/schema --external @getstrata/core/database/table --external @getstrata/core/database/transaction --external @getstrata/core/database/types --external @getstrata/core/database/whereBuilder --external @getstrata/core/errors/http --external @getstrata/core/events --external @getstrata/core/facades --external @getstrata/core/http --external @getstrata/core/http/authMiddleware --external @getstrata/core/http/authorizeMiddleware --external @getstrata/core/http/bodySizeLimitMiddleware --external @getstrata/core/http/clientIp --external @getstrata/core/http/cookies --external @getstrata/core/http/contentNegotiation --external @getstrata/core/http/conditionalResponse --external @getstrata/core/http/corsMiddleware --external @getstrata/core/http/csrfMiddleware --external @getstrata/core/http/csrfProtection --external @getstrata/core/http/csrfToken --external @getstrata/core/http/etag --external @getstrata/core/http/flashSession --external @getstrata/core/http/flashMiddleware --external @getstrata/core/http/formRequest --external @getstrata/core/http/middleware --external @getstrata/core/http/metricsMiddleware --external @getstrata/core/http/loginThrottleMiddleware --external @getstrata/core/http/memoryThrottleMiddleware --external @getstrata/core/http/pagination --external @getstrata/core/http/parseFormBody --external @getstrata/core/http/parseMultipartUpload --external @getstrata/core/http/requireAbilityMiddleware --external @getstrata/core/http/requireAuthMiddleware --external @getstrata/core/http/requireGlobalAdminMiddleware --external @getstrata/core/http/requireWebAuthMiddleware --external @getstrata/core/http/resources --external @getstrata/core/http/response --external @getstrata/core/http/route --external @getstrata/core/http/routeMiddleware --external @getstrata/core/http/routeModelBinding --external @getstrata/core/http/scimThrottleMiddleware --external @getstrata/core/http/securityHeadersMiddleware --external @getstrata/core/http/securedRouteModelBinding --external @getstrata/core/http/requestMetaContext --external @getstrata/core/http/webErrorResponse --external @getstrata/core/http/webFormRequest --external @getstrata/core/http/throttleMiddleware --external @getstrata/core/http/validation --external @getstrata/core/jobs/dispatchWebhookJob --external @getstrata/core/jobs/invalidateCacheTagsJob --external @getstrata/core/lifecycle/gracefulShutdown --external @getstrata/core/logging/logger --external @getstrata/core/logging/requestLoggingMiddleware --external @getstrata/core/mail/mailer --external @getstrata/core/mail/markdownMail --external @getstrata/core/mail/markdownMailable --external @getstrata/core/media/imageTransform --external @getstrata/core/metrics/prometheus --external @getstrata/core/notifications --external @getstrata/core/openapi/generator --external @getstrata/core/openapi/registeredRoute --external @getstrata/core/openapi/validate --external @getstrata/core/pagination --external @getstrata/core/queue --external @getstrata/core/queue/createAppQueue --external @getstrata/core/queue/failedJobRepository --external @getstrata/core/queue/failedJobService --external @getstrata/core/queue/jobRegistry --external @getstrata/core/queue/jobRunner --external @getstrata/core/queue/queueMetrics --external @getstrata/core/queue/publicQueue --external @getstrata/core/queue/redisQueue --external @getstrata/core/queue/types --external @getstrata/core/runtime/applicationRegistry --external @getstrata/core/runtime/asyncContextStore --external @getstrata/core/scheduler/schedule --external @getstrata/core/scheduler/osCron --external @getstrata/core/security/oauthState --external @getstrata/core/security/publicReads --external @getstrata/core/security/safeFetch --external @getstrata/core/security/safeUrl --external @getstrata/core/security/scimTenantTokens --external @getstrata/core/security/securityEvents --external @getstrata/core/security/stripeWebhook --external @getstrata/core/security/timingSafeCompare --external @getstrata/core/security/tokenExpiry --external @getstrata/core/security/totp --external @getstrata/core/storage/storage --external @getstrata/core/tenant/tenancyConfig --external @getstrata/core/tenant/tenantContext --external @getstrata/core/tenant/tenantDatabaseScope --external @getstrata/core/tenant/databaseTenantContext --external @getstrata/core/tenant/tenantMiddleware --external @getstrata/core/terminal/runShell --external @getstrata/core/tracing/traceContext --external @getstrata/core/tracing/tracingMiddleware --external @getstrata/core/validation/rules --external @getstrata/core/view",
176
+ "build:subpaths": "bun build entries/applicationRegistry.ts entries/buildModuleRoutes.ts entries/buildWebModuleRoutes.ts entries/cache/modelCacheTags.ts entries/config.ts entries/context.ts entries/contracts.ts entries/createWebRoutes.ts entries/createRoutes.ts entries/createSpaRoutes.ts entries/dependencies.ts entries/discoverModules.ts entries/health.ts entries/http/securedRouteModelBinding.ts entries/httpKernel.ts entries/listeners/invalidateCacheOnModelWrite.ts entries/membershipService.ts entries/metricsRoutes.ts entries/queue/defaultJobs.ts entries/providers.ts entries/providers/view.ts entries/routeRegistry.ts entries/schedule.ts entries/secretsGuard.ts entries/web/forms.ts entries/web/routing.ts entries/web/server.ts entries/web/session.ts entries/web/slug.ts --outdir dist --root . --target bun --external bun --external eta --external @getstrata/core --external @getstrata/bootstrap --external @getstrata/bootstrap/applicationRegistry --external @getstrata/bootstrap/buildModuleRoutes --external @getstrata/bootstrap/buildWebModuleRoutes --external @getstrata/bootstrap/cache/modelCacheTags --external @getstrata/bootstrap/config --external @getstrata/bootstrap/context --external @getstrata/bootstrap/contracts --external @getstrata/bootstrap/createWebRoutes --external @getstrata/bootstrap/createRoutes --external @getstrata/bootstrap/createSpaRoutes --external @getstrata/bootstrap/dependencies --external @getstrata/bootstrap/discoverModules --external @getstrata/bootstrap/health --external @getstrata/bootstrap/http/securedRouteModelBinding --external @getstrata/bootstrap/httpKernel --external @getstrata/bootstrap/listeners/invalidateCacheOnModelWrite --external @getstrata/bootstrap/membershipService --external @getstrata/bootstrap/metricsRoutes --external @getstrata/bootstrap/queue/defaultJobs --external @getstrata/bootstrap/providers --external @getstrata/bootstrap/providers/view --external @getstrata/bootstrap/routeRegistry --external @getstrata/bootstrap/schedule --external @getstrata/bootstrap/secretsGuard --external @getstrata/bootstrap/web/forms --external @getstrata/bootstrap/web/routing --external @getstrata/bootstrap/web/server --external @getstrata/bootstrap/web/session --external @getstrata/bootstrap/web/slug --external @getstrata/core/auth/accessControl --external @getstrata/core/auth/abilityChecker --external @getstrata/core/auth/authContext --external @getstrata/core/auth/guard --external @getstrata/core/auth/membershipContext --external @getstrata/core/auth/membershipMiddleware --external @getstrata/core/auth/membershipScope --external @getstrata/core/auth/membershipService --external @getstrata/core/auth/oauth/oidcProvider --external @getstrata/core/auth/oauth/providers --external @getstrata/core/auth/oauth/samlProvider --external @getstrata/core/auth/oauth/types --external @getstrata/core/auth/password --external @getstrata/core/auth/policy --external @getstrata/core/auth/scimAuthMiddleware --external @getstrata/core/auth/sessionCookie --external @getstrata/core/auth/sessionGuard --external @getstrata/core/auth/tokenHash --external @getstrata/core/audit/exportAuditLogs --external @getstrata/core/audit/siemFormatter --external @getstrata/core/admin/formatValue --external @getstrata/core/admin/registry --external @getstrata/core/admin/types --external @getstrata/core/cache/tags --external @getstrata/core/cache/createCacheStore --external @getstrata/core/cache/repository --external @getstrata/core/cache/simpleCache --external @getstrata/core/cache/simpleCacheStore --external @getstrata/core/config/envSchema --external @getstrata/core/contracts/serviceTokens --external @getstrata/core/contracts/container --external @getstrata/core/contracts/di --external @getstrata/core/crypto/fieldEncryption --external @getstrata/core/crypto/mfaSecret --external @getstrata/core/database --external @getstrata/core/database/baseRepository --external @getstrata/core/database/bindConnection --external @getstrata/core/database/boundConnection --external @getstrata/core/database/bunSql --external @getstrata/core/database/connection --external @getstrata/core/database/defaultConnection --external @getstrata/core/database/errors --external @getstrata/core/database/factory --external @getstrata/core/database/migrations --external @getstrata/core/database/migrations/types --external @getstrata/core/database/model --external @getstrata/core/database/query --external @getstrata/core/database/relationships --external @getstrata/core/database/repositoryConnection --external @getstrata/core/database/repositoryQuery --external @getstrata/core/database/seeders --external @getstrata/core/database/seeders/types --external @getstrata/core/database/schema --external @getstrata/core/database/table --external @getstrata/core/database/transaction --external @getstrata/core/database/types --external @getstrata/core/database/whereBuilder --external @getstrata/core/errors/http --external @getstrata/core/events --external @getstrata/core/facades --external @getstrata/core/http --external @getstrata/core/http/authMiddleware --external @getstrata/core/http/authorizeMiddleware --external @getstrata/core/http/bodySizeLimitMiddleware --external @getstrata/core/http/clientIp --external @getstrata/core/http/cookies --external @getstrata/core/http/contentNegotiation --external @getstrata/core/http/contentSecurityPolicy --external @getstrata/core/http/conditionalResponse --external @getstrata/core/http/corsMiddleware --external @getstrata/core/http/csrfMiddleware --external @getstrata/core/http/csrfProtection --external @getstrata/core/http/csrfToken --external @getstrata/core/http/etag --external @getstrata/core/http/flashSession --external @getstrata/core/http/flashMiddleware --external @getstrata/core/http/formRequest --external @getstrata/core/http/middleware --external @getstrata/core/http/metricsMiddleware --external @getstrata/core/http/loginThrottleMiddleware --external @getstrata/core/http/memoryThrottleMiddleware --external @getstrata/core/http/pagination --external @getstrata/core/http/parseFormBody --external @getstrata/core/http/parseMultipartUpload --external @getstrata/core/http/requireAbilityMiddleware --external @getstrata/core/http/requireAuthMiddleware --external @getstrata/core/http/requireGlobalAdminMiddleware --external @getstrata/core/http/requireWebAuthMiddleware --external @getstrata/core/http/resources --external @getstrata/core/http/response --external @getstrata/core/http/route --external @getstrata/core/http/routeMiddleware --external @getstrata/core/http/routeModelBinding --external @getstrata/core/http/safeInternalPath --external @getstrata/core/http/scimThrottleMiddleware --external @getstrata/core/http/securityHeadersMiddleware --external @getstrata/core/http/securedRouteModelBinding --external @getstrata/core/http/requestMetaContext --external @getstrata/core/http/webErrorResponse --external @getstrata/core/http/webFormRequest --external @getstrata/core/http/throttleMiddleware --external @getstrata/core/http/validation --external @getstrata/core/jobs/dispatchWebhookJob --external @getstrata/core/jobs/invalidateCacheTagsJob --external @getstrata/core/lifecycle/gracefulShutdown --external @getstrata/core/logging/logger --external @getstrata/core/logging/requestLoggingMiddleware --external @getstrata/core/mail/mailer --external @getstrata/core/mail/markdownMail --external @getstrata/core/mail/markdownMailable --external @getstrata/core/media/imageTransform --external @getstrata/core/metrics/prometheus --external @getstrata/core/notifications --external @getstrata/core/openapi/generator --external @getstrata/core/openapi/registeredRoute --external @getstrata/core/openapi/validate --external @getstrata/core/pagination --external @getstrata/core/queue --external @getstrata/core/queue/createAppQueue --external @getstrata/core/queue/failedJobRepository --external @getstrata/core/queue/failedJobService --external @getstrata/core/queue/jobRegistry --external @getstrata/core/queue/jobRunner --external @getstrata/core/queue/queueMetrics --external @getstrata/core/queue/publicQueue --external @getstrata/core/queue/redisQueue --external @getstrata/core/queue/types --external @getstrata/core/runtime/applicationRegistry --external @getstrata/core/runtime/asyncContextStore --external @getstrata/core/scheduler/schedule --external @getstrata/core/scheduler/osCron --external @getstrata/core/security/oauthState --external @getstrata/core/security/publicReads --external @getstrata/core/security/safeFetch --external @getstrata/core/security/safeUrl --external @getstrata/core/security/scimTenantTokens --external @getstrata/core/security/securityEvents --external @getstrata/core/security/stripeWebhook --external @getstrata/core/security/timingSafeCompare --external @getstrata/core/security/tokenExpiry --external @getstrata/core/security/totp --external @getstrata/core/storage/storage --external @getstrata/core/tenant/tenancyConfig --external @getstrata/core/tenant/tenantContext --external @getstrata/core/tenant/tenantDatabaseScope --external @getstrata/core/tenant/databaseTenantContext --external @getstrata/core/tenant/tenantMiddleware --external @getstrata/core/terminal/runShell --external @getstrata/core/tracing/traceContext --external @getstrata/core/tracing/tracingMiddleware --external @getstrata/core/validation/rules --external @getstrata/core/view",
177
177
  "build:shims": "true"
178
178
  },
179
179
  "publishConfig": {
180
180
  "access": "public"
181
181
  },
182
182
  "peerDependencies": {
183
- "@getstrata/core": "^0.5.59",
183
+ "@getstrata/core": "^0.5.61",
184
184
  "typescript": "^5.9.0"
185
185
  }
186
186
  }