@objectstack/runtime 11.0.0 → 11.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -918,12 +918,16 @@ interface HttpDispatcherOptions {
918
918
  scopeManager?: EnvironmentScopeManager;
919
919
  }
920
920
  /**
921
- * @deprecated Use `createDispatcherPlugin()` from `@objectstack/runtime` instead.
922
- * This class will be removed in v2. Prefer the plugin-based approach:
923
- * ```ts
924
- * import { createDispatcherPlugin } from '@objectstack/runtime';
925
- * kernel.use(createDispatcherPlugin({ prefix: '/api/v1' }));
926
- * ```
921
+ * The HTTP dispatch engine — translates an inbound (method, path, body, ctx)
922
+ * request into a kernel response. Used directly by the framework's HTTP adapters
923
+ * (express / fastify / nextjs / nestjs / nuxt / sveltekit / hono) and plugin-msw,
924
+ * which need a *callable* dispatcher.
925
+ *
926
+ * NOTE: `createDispatcherPlugin()` is a different thing — a kernel plugin that
927
+ * registers routes on a kernel-hosted HTTP server. It is NOT a drop-in for
928
+ * adapters. Retiring this public class behind a `createHttpDispatcher()` factory
929
+ * is tracked in #2380 (a deliberate adapter-API change, not yet done) — so this
930
+ * is intentionally NOT marked `@deprecated` while no working replacement exists.
927
931
  */
928
932
  declare class HttpDispatcher {
929
933
  private kernel;
@@ -1120,6 +1124,13 @@ declare class HttpDispatcher {
1120
1124
  * response object that callers should surface directly — no further
1121
1125
  * dispatch happens.
1122
1126
  */
1127
+ /**
1128
+ * ADR-0069 — returns a 403 response when the resolved session is blocked by
1129
+ * an auth-policy gate (expired password / required MFA) on a non-allow-listed
1130
+ * path, else null. Mirrors the REST `enforceAuth` seam so REST + dispatcher
1131
+ * (MCP, GraphQL) enforce consistently. Fails open on any lookup error.
1132
+ */
1133
+ private enforceAuthGate;
1123
1134
  private enforceProjectMembership;
1124
1135
  /**
1125
1136
  * Generates the discovery JSON response for the API root.
package/dist/index.d.ts CHANGED
@@ -918,12 +918,16 @@ interface HttpDispatcherOptions {
918
918
  scopeManager?: EnvironmentScopeManager;
919
919
  }
920
920
  /**
921
- * @deprecated Use `createDispatcherPlugin()` from `@objectstack/runtime` instead.
922
- * This class will be removed in v2. Prefer the plugin-based approach:
923
- * ```ts
924
- * import { createDispatcherPlugin } from '@objectstack/runtime';
925
- * kernel.use(createDispatcherPlugin({ prefix: '/api/v1' }));
926
- * ```
921
+ * The HTTP dispatch engine — translates an inbound (method, path, body, ctx)
922
+ * request into a kernel response. Used directly by the framework's HTTP adapters
923
+ * (express / fastify / nextjs / nestjs / nuxt / sveltekit / hono) and plugin-msw,
924
+ * which need a *callable* dispatcher.
925
+ *
926
+ * NOTE: `createDispatcherPlugin()` is a different thing — a kernel plugin that
927
+ * registers routes on a kernel-hosted HTTP server. It is NOT a drop-in for
928
+ * adapters. Retiring this public class behind a `createHttpDispatcher()` factory
929
+ * is tracked in #2380 (a deliberate adapter-API change, not yet done) — so this
930
+ * is intentionally NOT marked `@deprecated` while no working replacement exists.
927
931
  */
928
932
  declare class HttpDispatcher {
929
933
  private kernel;
@@ -1120,6 +1124,13 @@ declare class HttpDispatcher {
1120
1124
  * response object that callers should surface directly — no further
1121
1125
  * dispatch happens.
1122
1126
  */
1127
+ /**
1128
+ * ADR-0069 — returns a 403 response when the resolved session is blocked by
1129
+ * an auth-policy gate (expired password / required MFA) on a non-allow-listed
1130
+ * path, else null. Mirrors the REST `enforceAuth` seam so REST + dispatcher
1131
+ * (MCP, GraphQL) enforce consistently. Fails open on any lookup error.
1132
+ */
1133
+ private enforceAuthGate;
1123
1134
  private enforceProjectMembership;
1124
1135
  /**
1125
1136
  * Generates the discovery JSON response for the API root.
package/dist/index.js CHANGED
@@ -1556,7 +1556,7 @@ async function createStandaloneStack(config) {
1556
1556
  const environmentId = cfg.environmentId ?? process.env.OS_ENVIRONMENT_ID ?? "proj_local";
1557
1557
  const artifactPathInput = cfg.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? resolvePath2(cwd, "dist/objectstack.json");
1558
1558
  const artifactPath = isHttpUrl(artifactPathInput) ? artifactPathInput : artifactPathInput.startsWith("/") ? artifactPathInput : resolvePath2(cwd, artifactPathInput);
1559
- const dbUrl = cfg.databaseUrl ?? readEnvWithDeprecation("OS_DATABASE_URL", "DATABASE_URL")?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? (process.env.OS_HOME?.trim() ? `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}` : cfg.projectRoot ? `file:${resolvePath2(cfg.projectRoot, ".objectstack/data/standalone.db")}` : `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}`);
1559
+ const dbUrl = cfg.databaseUrl ?? readEnvWithDeprecation("OS_DATABASE_URL", "DATABASE_URL", { silent: true })?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? (process.env.OS_HOME?.trim() ? `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}` : cfg.projectRoot ? `file:${resolvePath2(cfg.projectRoot, ".objectstack/data/standalone.db")}` : `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}`);
1560
1560
  const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
1561
1561
  const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
1562
1562
  let driverPlugin;
@@ -1975,7 +1975,7 @@ function safeGet(ctx, name) {
1975
1975
 
1976
1976
  // src/http-dispatcher.ts
1977
1977
  init_package_state_store();
1978
- import { getEnv, resolveLocale } from "@objectstack/core";
1978
+ import { getEnv, resolveLocale, evaluateAuthGate, isAuthGateAllowlisted } from "@objectstack/core";
1979
1979
  import { CoreServiceName } from "@objectstack/spec/system";
1980
1980
  import { pluralToSingular, PLURAL_TO_SINGULAR } from "@objectstack/spec/shared";
1981
1981
 
@@ -2844,6 +2844,43 @@ var _HttpDispatcher = class _HttpDispatcher {
2844
2844
  * response object that callers should surface directly — no further
2845
2845
  * dispatch happens.
2846
2846
  */
2847
+ /**
2848
+ * ADR-0069 — returns a 403 response when the resolved session is blocked by
2849
+ * an auth-policy gate (expired password / required MFA) on a non-allow-listed
2850
+ * path, else null. Mirrors the REST `enforceAuth` seam so REST + dispatcher
2851
+ * (MCP, GraphQL) enforce consistently. Fails open on any lookup error.
2852
+ */
2853
+ async enforceAuthGate(context, cleanPath) {
2854
+ try {
2855
+ if (isAuthGateAllowlisted(cleanPath)) return null;
2856
+ const authService = await this.resolveService("auth", context.environmentId);
2857
+ if (!authService || typeof authService.isAuthGateActive !== "function" || !authService.isAuthGateActive()) {
2858
+ return null;
2859
+ }
2860
+ let api = authService.api;
2861
+ if (!api && typeof authService.getApi === "function") api = await authService.getApi();
2862
+ if (!api?.getSession) return null;
2863
+ const raw = context?.request?.headers;
2864
+ let headers;
2865
+ if (raw && typeof raw.get === "function") {
2866
+ headers = raw;
2867
+ } else if (raw && typeof raw === "object") {
2868
+ headers = new globalThis.Headers();
2869
+ for (const k of Object.keys(raw)) {
2870
+ const v = raw[k];
2871
+ if (v != null) headers.set(String(k), Array.isArray(v) ? v.join(",") : String(v));
2872
+ }
2873
+ } else {
2874
+ return null;
2875
+ }
2876
+ const session = await api.getSession({ headers }).catch(() => void 0);
2877
+ const gate = evaluateAuthGate(session?.user, cleanPath);
2878
+ if (!gate) return null;
2879
+ return this.error(gate.message, 403, { code: gate.code });
2880
+ } catch {
2881
+ return null;
2882
+ }
2883
+ }
2847
2884
  async enforceProjectMembership(context, path) {
2848
2885
  if (!this.enforceMembership) return null;
2849
2886
  const skipPaths = ["/auth", "/cloud", "/health", "/ready", "/discovery"];
@@ -4763,6 +4800,10 @@ var _HttpDispatcher = class _HttpDispatcher {
4763
4800
  });
4764
4801
  } catch {
4765
4802
  }
4803
+ const authGated = await this.enforceAuthGate(context, cleanPath);
4804
+ if (authGated) {
4805
+ return { handled: true, response: authGated };
4806
+ }
4766
4807
  const forbidden = await this.enforceProjectMembership(context, cleanPath);
4767
4808
  if (forbidden) {
4768
4809
  return { handled: true, response: forbidden };