@happyvertical/smrt-app-runtime 0.60.10 → 0.60.12

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.
@@ -1 +1 @@
1
- {"version":3,"file":"sveltekit.js","names":["name"],"sources":["../src/sveltekit/handlers.ts","../src/sveltekit/owner-setup.ts","../src/sveltekit/sign-out.ts"],"sourcesContent":["/**\n * Mountable runtime routes: liveness health, authorized diagnostics, and the\n * server-owned session summary for the root layout.\n */\n\nimport {\n getRequestScopedDatabase,\n RoleCollection,\n} from '@happyvertical/smrt-users';\nimport { json, type RequestHandler } from '@sveltejs/kit';\nimport type { RuntimeDiagnostics } from '../runtime-diagnostics.js';\nimport type {\n ApplicationRuntimeDiagnosticsOptions,\n SmrtSvelteKitRuntime,\n} from './runtime.js';\n\n/** Explicit permission that grants diagnostics read without the owner role. */\nexport const RUNTIME_DIAGNOSTICS_READ_PERMISSION = 'runtime_diagnostics.read';\n\n/**\n * Mount as `src/routes/api/_runtime/health/+server.ts`:\n * `export const GET = createRuntimeHealthHandler(runtime);`\n *\n * Local responses add the application ID, managed process instance, and the\n * secret-free configuration fingerprint that process managers compare.\n * Deployed responses expose only schema version, status, and profile.\n */\nexport function createRuntimeHealthHandler(\n runtime: Pick<SmrtSvelteKitRuntime, 'health'>,\n): RequestHandler {\n return async () => json(await runtime.health());\n}\n\n/** The locals the diagnostics route authorizes against. */\nexport interface DiagnosticsPrincipalLocals {\n readonly user?: { readonly id?: unknown } | null;\n readonly membership?: {\n readonly userId?: unknown;\n readonly tenantId?: unknown;\n readonly roleId?: unknown;\n isActive?: () => boolean;\n } | null;\n readonly permissions?: readonly string[];\n readonly tenantId?: unknown;\n readonly sessionId?: unknown;\n}\n\ntype DiagnosticsInputValue<T> = T | (() => T);\n\nexport interface RuntimeDiagnosticsHandlerOptions {\n /** Supplies default diagnostics reading and owner-role lookup. */\n readonly runtime?: Pick<\n SmrtSvelteKitRuntime,\n 'readDiagnostics' | 'classOptions'\n >;\n /** Replace the runtime projection (must already be the public allowlist). */\n readonly readDiagnostics?: () => Promise<RuntimeDiagnostics>;\n /** Replace the owner-role lookup. Thrown errors deny. */\n readonly resolveRoleSlug?: (roleId: string) => Promise<string | null>;\n /** Override where the principal comes from. Defaults to `event.locals`. */\n readonly resolveLocals?: (event: {\n readonly locals: unknown;\n }) => DiagnosticsPrincipalLocals;\n /** Public tool inventory (sorted/capped by the projector). */\n readonly toolNames?: DiagnosticsInputValue<readonly string[]>;\n readonly schemaStatus?: DiagnosticsInputValue<\n ApplicationRuntimeDiagnosticsOptions['schemaStatus']\n >;\n readonly migrationStatus?: DiagnosticsInputValue<\n ApplicationRuntimeDiagnosticsOptions['migrationStatus']\n >;\n readonly workerHeartbeatAt?: () => Date | string | null | undefined;\n readonly recentErrors?: () => ApplicationRuntimeDiagnosticsOptions['recentErrors'];\n /** Testable clock. */\n readonly now?: () => Date;\n}\n\nfunction resolveInputValue<T>(\n value: DiagnosticsInputValue<T> | undefined,\n): T | undefined {\n return typeof value === 'function' ? (value as () => T)() : value;\n}\n\n/**\n * Mount as `src/routes/api/_runtime/diagnostics/+server.ts`:\n * `export const GET = createRuntimeDiagnosticsHandler({ runtime });`\n *\n * Authentication and authorization always complete before the private runtime\n * is read. Authorized callers hold `runtime_diagnostics.read` or the `owner`\n * role of their active, session-matching membership. Every failure is a\n * stable `{ schemaVersion: 1, error: { code } }` body.\n */\nexport function createRuntimeDiagnosticsHandler(\n options: RuntimeDiagnosticsHandlerOptions,\n): RequestHandler {\n const runtime = options.runtime;\n const readDiagnostics =\n options.readDiagnostics ??\n (runtime\n ? () =>\n runtime.readDiagnostics({\n toolNames: resolveInputValue(options.toolNames) ?? [],\n observedAt: options.now?.() ?? new Date(),\n schemaStatus: resolveInputValue(options.schemaStatus) ?? 'unknown',\n migrationStatus:\n resolveInputValue(options.migrationStatus) ?? 'unknown',\n workerHeartbeatAt: options.workerHeartbeatAt?.() ?? null,\n recentErrors: options.recentErrors?.() ?? [],\n })\n : undefined);\n const resolveRoleSlug =\n options.resolveRoleSlug ??\n (runtime\n ? async (roleId: string) => {\n const config = runtime.classOptions('Role');\n const requestDb = getRequestScopedDatabase();\n const roles = await RoleCollection.create(\n requestDb\n ? { ...config, db: requestDb as typeof config.db }\n : config,\n );\n return (await roles.findById(roleId))?.slug ?? null;\n }\n : undefined);\n if (!readDiagnostics || !resolveRoleSlug) {\n throw new TypeError(\n 'createRuntimeDiagnosticsHandler requires `runtime`, or both `readDiagnostics` and `resolveRoleSlug`.',\n );\n }\n const resolveLocals =\n options.resolveLocals ??\n ((event: { readonly locals: unknown }) =>\n event.locals as DiagnosticsPrincipalLocals);\n\n return async (event) => {\n const locals = resolveLocals(event);\n const principal = authenticatedPrincipal(locals);\n if (!principal) return stableError(401, 'authentication_required');\n\n let authorized =\n locals.permissions?.includes(RUNTIME_DIAGNOSTICS_READ_PERMISSION) ===\n true;\n if (!authorized) {\n try {\n authorized = (await resolveRoleSlug(principal.roleId)) === 'owner';\n } catch {\n authorized = false;\n }\n }\n if (!authorized) return stableError(403, 'authorization_denied');\n\n try {\n return json(await readDiagnostics());\n } catch {\n return stableError(503, 'diagnostics_unavailable');\n }\n };\n}\n\nfunction authenticatedPrincipal(\n locals: DiagnosticsPrincipalLocals | null | undefined,\n): { roleId: string } | null {\n const userId = locals?.user?.id;\n const tenantId = locals?.tenantId;\n const sessionId = locals?.sessionId;\n const membership = locals?.membership;\n if (\n typeof userId !== 'string' ||\n !userId ||\n typeof tenantId !== 'string' ||\n !tenantId ||\n typeof sessionId !== 'string' ||\n !sessionId ||\n !membership ||\n membership.userId !== userId ||\n membership.tenantId !== tenantId ||\n typeof membership.roleId !== 'string' ||\n !membership.roleId\n ) {\n return null;\n }\n try {\n if (membership.isActive?.() !== true) return null;\n } catch {\n return null;\n }\n return { roleId: membership.roleId };\n}\n\nfunction stableError(status: number, code: string): Response {\n return json({ schemaVersion: 1, error: { code } }, { status });\n}\n\n/** Server-owned session summary for the root layout. */\nexport interface SessionLayoutData {\n readonly session: {\n readonly authenticated: boolean;\n /** Tenant authorized by the active session. */\n readonly activeTenantId: string | null;\n /** URL-selected tenant candidate; display only, never authorization. */\n readonly selectedTenantSlug: string | null;\n /** Display identity of the signed-in user (id, email, name only). */\n readonly user: SessionLayoutUser | null;\n };\n}\n\n/**\n * The only user fields the layout data ever carries. `name` appears only when\n * `locals.user` carries one (the `User` model itself has no name; it lives on\n * the linked profile), so consumers fall back to `email`.\n */\nexport interface SessionLayoutUser {\n readonly id: string;\n readonly email: string;\n readonly name?: string;\n}\n\nfunction layoutUser(user: unknown): SessionLayoutUser | null {\n if (!user || typeof user !== 'object') return null;\n const { id, email, name } = user as Record<string, unknown>;\n if (typeof id !== 'string' || typeof email !== 'string') return null;\n return {\n id,\n email,\n ...(typeof name === 'string' && name ? { name } : {}),\n };\n}\n\n/**\n * Mount as `src/routes/+layout.server.ts`:\n * `export const load = createSessionLayoutLoad();`\n */\nexport function createSessionLayoutLoad(): (event: {\n readonly locals: unknown;\n}) => SessionLayoutData {\n return ({ locals }) => {\n const value = locals as {\n user?: unknown;\n tenantId?: string | null;\n selectedTenantSlug?: string | null;\n };\n return {\n session: {\n authenticated: Boolean(value.user),\n activeTenantId: value.tenantId ?? null,\n selectedTenantSlug: value.selectedTenantSlug ?? null,\n user: layoutUser(value.user),\n },\n };\n };\n}\n","/**\n * Server half of the local owner-setup page (`src/routes/setup/+page.server.ts`).\n *\n * Owner bootstrap is local-profile only, loopback-only, and single-use. The\n * runtime stores only an HMAC of the short-lived token; this module never\n * stores, logs, or returns claim errors, and it re-checks loopback custody on\n * every request instead of trusting the bind host alone.\n */\n\nimport {\n type LocalOwnerClaimResult,\n LocalRuntimeError,\n removeOnboardingHandoff,\n} from '@happyvertical/smrt-app-runtime';\nimport type { Cookies } from '@sveltejs/kit';\nimport { type ActionFailure, fail, redirect } from '@sveltejs/kit';\nimport type { SmrtSvelteKitRuntime } from './runtime.js';\n\n/** Data returned by the setup page `load`. */\nexport interface OwnerSetupPageData {\n /** False when setup is claimed, disabled (deployed), or not loopback. */\n readonly available: boolean;\n /** One-time token from `?token=` (empty unless available). */\n readonly token: string;\n /**\n * State of the setup link, so the page can explain a dead link instead of\n * offering a form that cannot succeed. `ready`: a token is present and the\n * invitation has not expired (the token itself is verified on submit);\n * `missing`: no `?token=`; `expired`: the invitation lapsed; `unavailable`:\n * claimed, disabled, or not a loopback request.\n */\n readonly link: OwnerSetupLinkState;\n}\n\nexport type OwnerSetupLinkState =\n | 'ready'\n | 'missing'\n | 'expired'\n | 'unavailable';\n\n/** Stable failure codes returned by the setup action. */\nexport type OwnerSetupErrorCode =\n /** Not the local profile (HTTP 404). */\n | 'setup_disabled'\n /** Request is not from a loopback client on a loopback host (HTTP 403). */\n | 'setup_unavailable'\n /** Missing token, blank name, or email without `@` (HTTP 400). */\n | 'setup_invalid_input'\n /** Token wrong, expired, or already used; owner already claimed (HTTP 400). */\n | 'setup_invalid'\n /** The bootstrap tenant slug already exists (HTTP 409). */\n | 'setup_conflict';\n\n/** `form` data returned with `fail(status, …)`. */\nexport interface OwnerSetupFailure {\n readonly code: OwnerSetupErrorCode;\n /** Fixed, secret-free message; never claim error text. */\n readonly message: string;\n}\n\n/** Form field names posted to the setup action. */\nexport const OWNER_SETUP_FIELDS = Object.freeze({\n token: 'token',\n name: 'name',\n email: 'email',\n password: 'password',\n tenantName: 'tenantName',\n} as const);\n\nexport const DEFAULT_OWNER_SETUP_MESSAGES: Readonly<\n Record<OwnerSetupErrorCode, string>\n> = Object.freeze({\n setup_disabled: 'Local owner setup is disabled.',\n setup_unavailable: 'Local owner setup is available only from this device.',\n setup_invalid_input: 'Name, email, and a valid setup token are required.',\n setup_invalid:\n 'The setup invitation is invalid, expired, or already used. Run pnpm app:stop, pnpm app:recover, pnpm app:start, then pnpm app:open.',\n setup_conflict:\n 'A workspace with the configured owner tenant slug already exists. Configure ownerTenant with another slug or adoptExisting.',\n});\n\n/** The request fields the setup page reads. */\nexport interface OwnerSetupEvent {\n readonly url: URL;\n readonly locals: unknown;\n getClientAddress(): string;\n}\n\nexport interface OwnerSetupActionEvent extends OwnerSetupEvent {\n readonly request: Request;\n readonly cookies: Pick<Cookies, 'set'>;\n}\n\nexport interface OwnerSetupPageOptions {\n /** Require an initial owner password. Default false for existing bootstrap consumers. */\n readonly requirePassword?: boolean;\n /** Where a signed-in visitor or a successful claim is sent. Default `/`. */\n readonly redirectTo?: string;\n /** Replace fixed failure messages (for example app-specific recovery steps). */\n readonly messages?: Partial<Record<OwnerSetupErrorCode, string>>;\n /** Override where the signed-in user comes from. Defaults to `locals.user`. */\n readonly resolveUser?: (event: OwnerSetupEvent) => unknown;\n /**\n * Set false to keep the `smrt app` onboarding hand-off files after a claim.\n * By default the runtime removes `onboarding.json` and\n * `onboarding-launch.html` from the private state root once the claim has\n * committed, so `pnpm app:open` stops offering the spent invitation.\n */\n readonly removeOnboardingHandoff?: boolean;\n /**\n * Best-effort extra cleanup after a successful claim (the hand-off files are\n * already removed by default). Errors are ignored: the owner and session are\n * already authoritative and a stale token now fails closed.\n */\n readonly onOwnerClaimed?: (\n result: LocalOwnerClaimResult,\n event: OwnerSetupActionEvent,\n ) => void | Promise<void>;\n}\n\nexport interface OwnerSetupPage {\n readonly load: (event: OwnerSetupEvent) => Promise<OwnerSetupPageData>;\n readonly actions: {\n readonly default: (\n event: OwnerSetupActionEvent,\n ) => Promise<ActionFailure<OwnerSetupFailure>>;\n };\n}\n\n/**\n * Mount as `src/routes/setup/+page.server.ts`:\n *\n * ```ts\n * import { createOwnerSetupPage } from '@happyvertical/smrt-app-runtime/sveltekit';\n * import { runtime } from '../../hooks.server';\n *\n * export const { load, actions } = createOwnerSetupPage(runtime);\n * ```\n *\n * The form posts to the `default` action with fields `token` (hidden),\n * `name`, `email`, and optional `password` and `tenantName`. Set\n * `requirePassword: true` for password-based applications. Success sets the session cookie\n * and redirects 303; failures return `fail(status, { code, message })`.\n */\nexport function createOwnerSetupPage(\n runtime: Pick<\n SmrtSvelteKitRuntime,\n 'resolvedRuntime' | 'localRuntime' | 'sessionCookie'\n > &\n Partial<Pick<SmrtSvelteKitRuntime, 'applicationStateRoot'>>,\n options: OwnerSetupPageOptions = {},\n): OwnerSetupPage {\n const redirectTo = options.redirectTo ?? '/';\n const messages = { ...DEFAULT_OWNER_SETUP_MESSAGES, ...options.messages };\n const failure = (status: number, code: OwnerSetupErrorCode) =>\n fail(status, { code, message: messages[code] } as OwnerSetupFailure);\n const resolveUser =\n options.resolveUser ??\n ((event: OwnerSetupEvent) => (event.locals as { user?: unknown }).user);\n\n const load = async (event: OwnerSetupEvent): Promise<OwnerSetupPageData> => {\n if (resolveUser(event)) redirect(303, redirectTo);\n const resolved = await runtime.resolvedRuntime();\n if (resolved.profile !== 'local' || !isLoopbackRequest(event)) {\n return { available: false, token: '', link: 'unavailable' };\n }\n const local = await runtime.localRuntime();\n const diagnostics = await local.diagnostics();\n const status = diagnostics.bootstrap.status;\n if (status === 'claimed') {\n return { available: false, token: '', link: 'unavailable' };\n }\n const token = event.url.searchParams.get(OWNER_SETUP_FIELDS.token) || '';\n if (status === 'expired')\n return { available: true, token: '', link: 'expired' };\n if (!token) return { available: true, token: '', link: 'missing' };\n return { available: true, token, link: 'ready' };\n };\n\n const claim = async (\n event: OwnerSetupActionEvent,\n ): Promise<ActionFailure<OwnerSetupFailure>> => {\n const resolved = await runtime.resolvedRuntime();\n if (resolved.profile !== 'local') return failure(404, 'setup_disabled');\n if (!isLoopbackRequest(event)) return failure(403, 'setup_unavailable');\n\n let form: FormData;\n try {\n form = await event.request.formData();\n } catch {\n return failure(400, 'setup_invalid_input');\n }\n const field = (name: string) => {\n const value = form.get(name);\n return typeof value === 'string' ? value : '';\n };\n const token = field(OWNER_SETUP_FIELDS.token);\n const name = field(OWNER_SETUP_FIELDS.name);\n const email = field(OWNER_SETUP_FIELDS.email);\n const password = field(OWNER_SETUP_FIELDS.password);\n const tenantName = field(OWNER_SETUP_FIELDS.tenantName).trim();\n if (\n !token ||\n !name.trim() ||\n !email.includes('@') ||\n (options.requirePassword && !password)\n ) {\n return failure(400, 'setup_invalid_input');\n }\n\n const local = await runtime.localRuntime();\n let result: LocalOwnerClaimResult;\n try {\n result = await local.claimOwner({\n token,\n name,\n email,\n password,\n ...(tenantName ? { tenantName } : {}),\n userAgent: event.request.headers.get('user-agent') || undefined,\n ipAddress: event.getClientAddress(),\n });\n } catch (error) {\n if (\n error instanceof LocalRuntimeError &&\n (error.code === 'owner_tenant_conflict' ||\n error.code === 'owner_identity_conflict')\n ) {\n return failure(409, 'setup_conflict');\n }\n return failure(400, 'setup_invalid');\n }\n\n // claimOwner resolved, so the claim transaction has committed. Clean up\n // before cookie serialization, which can throw on a malformed config.\n if (\n options.removeOnboardingHandoff !== false &&\n runtime.applicationStateRoot\n ) {\n try {\n removeOnboardingHandoff(runtime.applicationStateRoot());\n } catch {\n // Best effort: the spent token already fails closed.\n }\n }\n const cookie = runtime.sessionCookie;\n event.cookies.set(cookie.name, result.sessionId, {\n path: cookie.path,\n ...(cookie.domain ? { domain: cookie.domain } : {}),\n httpOnly: true,\n secure: cookie.secure ?? event.url.protocol === 'https:',\n sameSite: cookie.sameSite,\n maxAge: cookie.maxAgeSeconds,\n });\n try {\n await options.onOwnerClaimed?.(result, event);\n } catch {\n // Best effort only; see OwnerSetupPageOptions.onOwnerClaimed.\n }\n redirect(303, redirectTo);\n };\n\n return Object.freeze({\n load,\n actions: Object.freeze({ default: claim }),\n });\n}\n\n/**\n * True only when both the peer address and the addressed host are loopback.\n * The host check rejects tunnels and DNS-rebinding hosts that forward to a\n * loopback listener; a throwing `getClientAddress` is treated as remote.\n */\nexport function isLoopbackRequest(event: {\n readonly url: URL;\n getClientAddress(): string;\n}): boolean {\n let address: string;\n try {\n address = event.getClientAddress();\n } catch {\n return false;\n }\n return isLoopbackAddress(address) && isLoopbackHostname(event.url.hostname);\n}\n\n/** Loopback IP literal: `127.0.0.0/8`, `::1`, or IPv4-mapped `127/8`. */\nexport function isLoopbackAddress(value: string | null | undefined): boolean {\n if (typeof value !== 'string') return false;\n let host = value\n .trim()\n .toLowerCase()\n .replace(/^\\[|\\]$/g, '');\n if (host === '::1' || host === '0:0:0:0:0:0:0:1') return true;\n if (host.startsWith('::ffff:')) host = host.slice('::ffff:'.length);\n const octets = host.split('.');\n return (\n octets.length === 4 &&\n octets.every((part) => /^\\d{1,3}$/.test(part) && Number(part) <= 255) &&\n Number(octets[0]) === 127\n );\n}\n\n/** Loopback host name as SvelteKit reports it in `url.hostname`. */\nexport function isLoopbackHostname(hostname: string): boolean {\n const host = hostname.trim().toLowerCase().replace(/\\.$/, '');\n return host === 'localhost' || isLoopbackAddress(host);\n}\n","/**\n * Sign-out route handler: destroys the current session and clears its cookie\n * with the same cookie attributes the owner-setup action used to set it.\n */\n\nimport { destroySessionCookie } from '@happyvertical/smrt-users/sveltekit';\nimport { type RequestHandler, redirect } from '@sveltejs/kit';\nimport type { SmrtSvelteKitRuntime } from './runtime.js';\n\n/** True when the browser says the request came from this origin. */\nfunction isSameOriginRequest(request: Request, url: URL): boolean {\n const site = request.headers.get('sec-fetch-site');\n if (site !== null) return site === 'same-origin';\n const origin = request.headers.get('origin');\n return origin !== null && origin === url.origin;\n}\n\n/**\n * Mount as `src/routes/sign-out/+server.ts`:\n * `export const POST = createSignOutHandler(runtime);`\n *\n * `POST` only (other methods answer 405) and same-origin only (403 otherwise).\n * The session row is destroyed best-effort and the cookie is cleared even when\n * the row is already gone. Answers 204, or a 303 to `/` when the request\n * accepts HTML so a no-JS form post lands on the home page.\n */\nexport function createSignOutHandler(\n runtime: Pick<SmrtSvelteKitRuntime, 'sessionCookie' | 'classOptions'>,\n): RequestHandler {\n return async (event) => {\n if (event.request.method !== 'POST') {\n return new Response('Method not allowed', {\n status: 405,\n headers: { allow: 'POST' },\n });\n }\n if (!isSameOriginRequest(event.request, event.url)) {\n return new Response('Forbidden', { status: 403 });\n }\n const cookie = runtime.sessionCookie;\n await destroySessionCookie(\n event as unknown as Parameters<typeof destroySessionCookie>[0],\n {\n ...runtime.classOptions('Session'),\n cookieName: cookie.name,\n cookiePath: cookie.path,\n ...(cookie.domain ? { cookieDomain: cookie.domain } : {}),\n },\n );\n // `delete` omits the Secure flag the setup action used; re-issue the\n // expiry with the same attributes so browsers match and drop the cookie.\n event.cookies.set(cookie.name, '', {\n path: cookie.path,\n ...(cookie.domain ? { domain: cookie.domain } : {}),\n httpOnly: true,\n secure: cookie.secure ?? event.url.protocol === 'https:',\n sameSite: cookie.sameSite,\n maxAge: 0,\n });\n if (event.request.headers.get('accept')?.includes('text/html')) {\n redirect(303, '/');\n }\n return new Response(null, { status: 204 });\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sveltekit.js","names":["name"],"sources":["../src/sveltekit/handlers.ts","../src/sveltekit/owner-setup.ts","../src/sveltekit/local-mcp-credentials.ts","../src/sveltekit/sign-out.ts"],"sourcesContent":["/**\n * Mountable runtime routes: liveness health, authorized diagnostics, and the\n * server-owned session summary for the root layout.\n */\n\nimport {\n getRequestScopedDatabase,\n RoleCollection,\n} from '@happyvertical/smrt-users';\nimport { json, type RequestHandler } from '@sveltejs/kit';\nimport type { RuntimeDiagnostics } from '../runtime-diagnostics.js';\nimport type {\n ApplicationRuntimeDiagnosticsOptions,\n SmrtSvelteKitRuntime,\n} from './runtime.js';\n\n/** Explicit permission that grants diagnostics read without the owner role. */\nexport const RUNTIME_DIAGNOSTICS_READ_PERMISSION = 'runtime_diagnostics.read';\n\n/**\n * Mount as `src/routes/api/_runtime/health/+server.ts`:\n * `export const GET = createRuntimeHealthHandler(runtime);`\n *\n * Local responses add the application ID, managed process instance, and the\n * secret-free configuration fingerprint that process managers compare.\n * Deployed responses expose only schema version, status, and profile.\n */\nexport function createRuntimeHealthHandler(\n runtime: Pick<SmrtSvelteKitRuntime, 'health'>,\n): RequestHandler {\n return async () => json(await runtime.health());\n}\n\n/** The locals the diagnostics route authorizes against. */\nexport interface DiagnosticsPrincipalLocals {\n readonly user?: { readonly id?: unknown } | null;\n readonly membership?: {\n readonly userId?: unknown;\n readonly tenantId?: unknown;\n readonly roleId?: unknown;\n isActive?: () => boolean;\n } | null;\n readonly permissions?: readonly string[];\n readonly tenantId?: unknown;\n readonly sessionId?: unknown;\n}\n\ntype DiagnosticsInputValue<T> = T | (() => T);\n\nexport interface RuntimeDiagnosticsHandlerOptions {\n /** Supplies default diagnostics reading and owner-role lookup. */\n readonly runtime?: Pick<\n SmrtSvelteKitRuntime,\n 'readDiagnostics' | 'classOptions'\n >;\n /** Replace the runtime projection (must already be the public allowlist). */\n readonly readDiagnostics?: () => Promise<RuntimeDiagnostics>;\n /** Replace the owner-role lookup. Thrown errors deny. */\n readonly resolveRoleSlug?: (roleId: string) => Promise<string | null>;\n /** Override where the principal comes from. Defaults to `event.locals`. */\n readonly resolveLocals?: (event: {\n readonly locals: unknown;\n }) => DiagnosticsPrincipalLocals;\n /** Public tool inventory (sorted/capped by the projector). */\n readonly toolNames?: DiagnosticsInputValue<readonly string[]>;\n readonly schemaStatus?: DiagnosticsInputValue<\n ApplicationRuntimeDiagnosticsOptions['schemaStatus']\n >;\n readonly migrationStatus?: DiagnosticsInputValue<\n ApplicationRuntimeDiagnosticsOptions['migrationStatus']\n >;\n readonly workerHeartbeatAt?: () => Date | string | null | undefined;\n readonly recentErrors?: () => ApplicationRuntimeDiagnosticsOptions['recentErrors'];\n /** Testable clock. */\n readonly now?: () => Date;\n}\n\nfunction resolveInputValue<T>(\n value: DiagnosticsInputValue<T> | undefined,\n): T | undefined {\n return typeof value === 'function' ? (value as () => T)() : value;\n}\n\n/**\n * Mount as `src/routes/api/_runtime/diagnostics/+server.ts`:\n * `export const GET = createRuntimeDiagnosticsHandler({ runtime });`\n *\n * Authentication and authorization always complete before the private runtime\n * is read. Authorized callers hold `runtime_diagnostics.read` or the `owner`\n * role of their active, session-matching membership. Every failure is a\n * stable `{ schemaVersion: 1, error: { code } }` body.\n */\nexport function createRuntimeDiagnosticsHandler(\n options: RuntimeDiagnosticsHandlerOptions,\n): RequestHandler {\n const runtime = options.runtime;\n const readDiagnostics =\n options.readDiagnostics ??\n (runtime\n ? () =>\n runtime.readDiagnostics({\n toolNames: resolveInputValue(options.toolNames) ?? [],\n observedAt: options.now?.() ?? new Date(),\n schemaStatus: resolveInputValue(options.schemaStatus) ?? 'unknown',\n migrationStatus:\n resolveInputValue(options.migrationStatus) ?? 'unknown',\n workerHeartbeatAt: options.workerHeartbeatAt?.() ?? null,\n recentErrors: options.recentErrors?.() ?? [],\n })\n : undefined);\n const resolveRoleSlug =\n options.resolveRoleSlug ??\n (runtime\n ? async (roleId: string) => {\n const config = runtime.classOptions('Role');\n const requestDb = getRequestScopedDatabase();\n const roles = await RoleCollection.create(\n requestDb\n ? { ...config, db: requestDb as typeof config.db }\n : config,\n );\n return (await roles.findById(roleId))?.slug ?? null;\n }\n : undefined);\n if (!readDiagnostics || !resolveRoleSlug) {\n throw new TypeError(\n 'createRuntimeDiagnosticsHandler requires `runtime`, or both `readDiagnostics` and `resolveRoleSlug`.',\n );\n }\n const resolveLocals =\n options.resolveLocals ??\n ((event: { readonly locals: unknown }) =>\n event.locals as DiagnosticsPrincipalLocals);\n\n return async (event) => {\n const locals = resolveLocals(event);\n const principal = authenticatedPrincipal(locals);\n if (!principal) return stableError(401, 'authentication_required');\n\n let authorized =\n locals.permissions?.includes(RUNTIME_DIAGNOSTICS_READ_PERMISSION) ===\n true;\n if (!authorized) {\n try {\n authorized = (await resolveRoleSlug(principal.roleId)) === 'owner';\n } catch {\n authorized = false;\n }\n }\n if (!authorized) return stableError(403, 'authorization_denied');\n\n try {\n return json(await readDiagnostics());\n } catch {\n return stableError(503, 'diagnostics_unavailable');\n }\n };\n}\n\nfunction authenticatedPrincipal(\n locals: DiagnosticsPrincipalLocals | null | undefined,\n): { roleId: string } | null {\n const userId = locals?.user?.id;\n const tenantId = locals?.tenantId;\n const sessionId = locals?.sessionId;\n const membership = locals?.membership;\n if (\n typeof userId !== 'string' ||\n !userId ||\n typeof tenantId !== 'string' ||\n !tenantId ||\n typeof sessionId !== 'string' ||\n !sessionId ||\n !membership ||\n membership.userId !== userId ||\n membership.tenantId !== tenantId ||\n typeof membership.roleId !== 'string' ||\n !membership.roleId\n ) {\n return null;\n }\n try {\n if (membership.isActive?.() !== true) return null;\n } catch {\n return null;\n }\n return { roleId: membership.roleId };\n}\n\nfunction stableError(status: number, code: string): Response {\n return json({ schemaVersion: 1, error: { code } }, { status });\n}\n\n/** Server-owned session summary for the root layout. */\nexport interface SessionLayoutData {\n readonly session: {\n readonly authenticated: boolean;\n /** Tenant authorized by the active session. */\n readonly activeTenantId: string | null;\n /** URL-selected tenant candidate; display only, never authorization. */\n readonly selectedTenantSlug: string | null;\n /** Display identity of the signed-in user (id, email, name only). */\n readonly user: SessionLayoutUser | null;\n };\n}\n\n/**\n * The only user fields the layout data ever carries. `name` appears only when\n * `locals.user` carries one (the `User` model itself has no name; it lives on\n * the linked profile), so consumers fall back to `email`.\n */\nexport interface SessionLayoutUser {\n readonly id: string;\n readonly email: string;\n readonly name?: string;\n}\n\nfunction layoutUser(user: unknown): SessionLayoutUser | null {\n if (!user || typeof user !== 'object') return null;\n const { id, email, name } = user as Record<string, unknown>;\n if (typeof id !== 'string' || typeof email !== 'string') return null;\n return {\n id,\n email,\n ...(typeof name === 'string' && name ? { name } : {}),\n };\n}\n\n/**\n * Mount as `src/routes/+layout.server.ts`:\n * `export const load = createSessionLayoutLoad();`\n */\nexport function createSessionLayoutLoad(): (event: {\n readonly locals: unknown;\n}) => SessionLayoutData {\n return ({ locals }) => {\n const value = locals as {\n user?: unknown;\n tenantId?: string | null;\n selectedTenantSlug?: string | null;\n };\n return {\n session: {\n authenticated: Boolean(value.user),\n activeTenantId: value.tenantId ?? null,\n selectedTenantSlug: value.selectedTenantSlug ?? null,\n user: layoutUser(value.user),\n },\n };\n };\n}\n","/**\n * Server half of the local owner-setup page (`src/routes/setup/+page.server.ts`).\n *\n * Owner bootstrap is local-profile only, loopback-only, and single-use. The\n * runtime stores only an HMAC of the short-lived token; this module never\n * stores, logs, or returns claim errors, and it re-checks loopback custody on\n * every request instead of trusting the bind host alone.\n */\n\nimport {\n type LocalOwnerClaimResult,\n LocalRuntimeError,\n removeOnboardingHandoff,\n} from '@happyvertical/smrt-app-runtime';\nimport type { Cookies } from '@sveltejs/kit';\nimport { type ActionFailure, fail, redirect } from '@sveltejs/kit';\nimport type { SmrtSvelteKitRuntime } from './runtime.js';\n\n/** Data returned by the setup page `load`. */\nexport interface OwnerSetupPageData {\n /** False when setup is claimed, disabled (deployed), or not loopback. */\n readonly available: boolean;\n /** One-time token from `?token=` (empty unless available). */\n readonly token: string;\n /**\n * State of the setup link, so the page can explain a dead link instead of\n * offering a form that cannot succeed. `ready`: a token is present and the\n * invitation has not expired (the token itself is verified on submit);\n * `missing`: no `?token=`; `expired`: the invitation lapsed; `unavailable`:\n * claimed, disabled, or not a loopback request.\n */\n readonly link: OwnerSetupLinkState;\n}\n\nexport type OwnerSetupLinkState =\n | 'ready'\n | 'missing'\n | 'expired'\n | 'unavailable';\n\n/** Stable failure codes returned by the setup action. */\nexport type OwnerSetupErrorCode =\n /** Not the local profile (HTTP 404). */\n | 'setup_disabled'\n /** Request is not from a loopback client on a loopback host (HTTP 403). */\n | 'setup_unavailable'\n /** Missing token, blank name, or email without `@` (HTTP 400). */\n | 'setup_invalid_input'\n /** Token wrong, expired, or already used; owner already claimed (HTTP 400). */\n | 'setup_invalid'\n /** The bootstrap tenant slug already exists (HTTP 409). */\n | 'setup_conflict';\n\n/** `form` data returned with `fail(status, …)`. */\nexport interface OwnerSetupFailure {\n readonly code: OwnerSetupErrorCode;\n /** Fixed, secret-free message; never claim error text. */\n readonly message: string;\n}\n\n/** Form field names posted to the setup action. */\nexport const OWNER_SETUP_FIELDS = Object.freeze({\n token: 'token',\n name: 'name',\n email: 'email',\n password: 'password',\n tenantName: 'tenantName',\n} as const);\n\nexport const DEFAULT_OWNER_SETUP_MESSAGES: Readonly<\n Record<OwnerSetupErrorCode, string>\n> = Object.freeze({\n setup_disabled: 'Local owner setup is disabled.',\n setup_unavailable: 'Local owner setup is available only from this device.',\n setup_invalid_input: 'Name, email, and a valid setup token are required.',\n setup_invalid:\n 'The setup invitation is invalid, expired, or already used. Run pnpm app:stop, pnpm app:recover, pnpm app:start, then pnpm app:open.',\n setup_conflict:\n 'A workspace with the configured owner tenant slug already exists. Configure ownerTenant with another slug or adoptExisting.',\n});\n\n/** The request fields the setup page reads. */\nexport interface OwnerSetupEvent {\n readonly url: URL;\n readonly locals: unknown;\n getClientAddress(): string;\n}\n\nexport interface OwnerSetupActionEvent extends OwnerSetupEvent {\n readonly request: Request;\n readonly cookies: Pick<Cookies, 'set'>;\n}\n\nexport interface OwnerSetupPageOptions {\n /** Require an initial owner password. Default false for existing bootstrap consumers. */\n readonly requirePassword?: boolean;\n /** Where a signed-in visitor or a successful claim is sent. Default `/`. */\n readonly redirectTo?: string;\n /** Replace fixed failure messages (for example app-specific recovery steps). */\n readonly messages?: Partial<Record<OwnerSetupErrorCode, string>>;\n /** Override where the signed-in user comes from. Defaults to `locals.user`. */\n readonly resolveUser?: (event: OwnerSetupEvent) => unknown;\n /**\n * Set false to keep the `smrt app` onboarding hand-off files after a claim.\n * By default the runtime removes `onboarding.json` and\n * `onboarding-launch.html` from the private state root once the claim has\n * committed, so `pnpm app:open` stops offering the spent invitation.\n */\n readonly removeOnboardingHandoff?: boolean;\n /**\n * Best-effort extra cleanup after a successful claim (the hand-off files are\n * already removed by default). Errors are ignored: the owner and session are\n * already authoritative and a stale token now fails closed.\n */\n readonly onOwnerClaimed?: (\n result: LocalOwnerClaimResult,\n event: OwnerSetupActionEvent,\n ) => void | Promise<void>;\n}\n\nexport interface OwnerSetupPage {\n readonly load: (event: OwnerSetupEvent) => Promise<OwnerSetupPageData>;\n readonly actions: {\n readonly default: (\n event: OwnerSetupActionEvent,\n ) => Promise<ActionFailure<OwnerSetupFailure>>;\n };\n}\n\n/**\n * Mount as `src/routes/setup/+page.server.ts`:\n *\n * ```ts\n * import { createOwnerSetupPage } from '@happyvertical/smrt-app-runtime/sveltekit';\n * import { runtime } from '../../hooks.server';\n *\n * export const { load, actions } = createOwnerSetupPage(runtime);\n * ```\n *\n * The form posts to the `default` action with fields `token` (hidden),\n * `name`, `email`, and optional `password` and `tenantName`. Set\n * `requirePassword: true` for password-based applications. Success sets the session cookie\n * and redirects 303; failures return `fail(status, { code, message })`.\n */\nexport function createOwnerSetupPage(\n runtime: Pick<\n SmrtSvelteKitRuntime,\n 'resolvedRuntime' | 'localRuntime' | 'sessionCookie'\n > &\n Partial<Pick<SmrtSvelteKitRuntime, 'applicationStateRoot'>>,\n options: OwnerSetupPageOptions = {},\n): OwnerSetupPage {\n const redirectTo = options.redirectTo ?? '/';\n const messages = { ...DEFAULT_OWNER_SETUP_MESSAGES, ...options.messages };\n const failure = (status: number, code: OwnerSetupErrorCode) =>\n fail(status, { code, message: messages[code] } as OwnerSetupFailure);\n const resolveUser =\n options.resolveUser ??\n ((event: OwnerSetupEvent) => (event.locals as { user?: unknown }).user);\n\n const load = async (event: OwnerSetupEvent): Promise<OwnerSetupPageData> => {\n if (resolveUser(event)) redirect(303, redirectTo);\n const resolved = await runtime.resolvedRuntime();\n if (resolved.profile !== 'local' || !isLoopbackRequest(event)) {\n return { available: false, token: '', link: 'unavailable' };\n }\n const local = await runtime.localRuntime();\n const diagnostics = await local.diagnostics();\n const status = diagnostics.bootstrap.status;\n if (status === 'claimed') {\n return { available: false, token: '', link: 'unavailable' };\n }\n const token = event.url.searchParams.get(OWNER_SETUP_FIELDS.token) || '';\n if (status === 'expired')\n return { available: true, token: '', link: 'expired' };\n if (!token) return { available: true, token: '', link: 'missing' };\n return { available: true, token, link: 'ready' };\n };\n\n const claim = async (\n event: OwnerSetupActionEvent,\n ): Promise<ActionFailure<OwnerSetupFailure>> => {\n const resolved = await runtime.resolvedRuntime();\n if (resolved.profile !== 'local') return failure(404, 'setup_disabled');\n if (!isLoopbackRequest(event)) return failure(403, 'setup_unavailable');\n\n let form: FormData;\n try {\n form = await event.request.formData();\n } catch {\n return failure(400, 'setup_invalid_input');\n }\n const field = (name: string) => {\n const value = form.get(name);\n return typeof value === 'string' ? value : '';\n };\n const token = field(OWNER_SETUP_FIELDS.token);\n const name = field(OWNER_SETUP_FIELDS.name);\n const email = field(OWNER_SETUP_FIELDS.email);\n const password = field(OWNER_SETUP_FIELDS.password);\n const tenantName = field(OWNER_SETUP_FIELDS.tenantName).trim();\n if (\n !token ||\n !name.trim() ||\n !email.includes('@') ||\n (options.requirePassword && !password)\n ) {\n return failure(400, 'setup_invalid_input');\n }\n\n const local = await runtime.localRuntime();\n let result: LocalOwnerClaimResult;\n try {\n result = await local.claimOwner({\n token,\n name,\n email,\n password,\n ...(tenantName ? { tenantName } : {}),\n userAgent: event.request.headers.get('user-agent') || undefined,\n ipAddress: event.getClientAddress(),\n });\n } catch (error) {\n if (\n error instanceof LocalRuntimeError &&\n (error.code === 'owner_tenant_conflict' ||\n error.code === 'owner_identity_conflict')\n ) {\n return failure(409, 'setup_conflict');\n }\n return failure(400, 'setup_invalid');\n }\n\n // claimOwner resolved, so the claim transaction has committed. Clean up\n // before cookie serialization, which can throw on a malformed config.\n if (\n options.removeOnboardingHandoff !== false &&\n runtime.applicationStateRoot\n ) {\n try {\n removeOnboardingHandoff(runtime.applicationStateRoot());\n } catch {\n // Best effort: the spent token already fails closed.\n }\n }\n const cookie = runtime.sessionCookie;\n event.cookies.set(cookie.name, result.sessionId, {\n path: cookie.path,\n ...(cookie.domain ? { domain: cookie.domain } : {}),\n httpOnly: true,\n secure: cookie.secure ?? event.url.protocol === 'https:',\n sameSite: cookie.sameSite,\n maxAge: cookie.maxAgeSeconds,\n });\n try {\n await options.onOwnerClaimed?.(result, event);\n } catch {\n // Best effort only; see OwnerSetupPageOptions.onOwnerClaimed.\n }\n redirect(303, redirectTo);\n };\n\n return Object.freeze({\n load,\n actions: Object.freeze({ default: claim }),\n });\n}\n\n/**\n * True only when both the peer address and the addressed host are loopback.\n * The host check rejects tunnels and DNS-rebinding hosts that forward to a\n * loopback listener; a throwing `getClientAddress` is treated as remote.\n */\nexport function isLoopbackRequest(event: {\n readonly url: URL;\n getClientAddress(): string;\n}): boolean {\n let address: string;\n try {\n address = event.getClientAddress();\n } catch {\n return false;\n }\n return isLoopbackAddress(address) && isLoopbackHostname(event.url.hostname);\n}\n\n/** Loopback IP literal: `127.0.0.0/8`, `::1`, or IPv4-mapped `127/8`. */\nexport function isLoopbackAddress(value: string | null | undefined): boolean {\n if (typeof value !== 'string') return false;\n let host = value\n .trim()\n .toLowerCase()\n .replace(/^\\[|\\]$/g, '');\n if (host === '::1' || host === '0:0:0:0:0:0:0:1') return true;\n if (host.startsWith('::ffff:')) host = host.slice('::ffff:'.length);\n const octets = host.split('.');\n return (\n octets.length === 4 &&\n octets.every((part) => /^\\d{1,3}$/.test(part) && Number(part) <= 255) &&\n Number(octets[0]) === 127\n );\n}\n\n/** Loopback host name as SvelteKit reports it in `url.hostname`. */\nexport function isLoopbackHostname(hostname: string): boolean {\n const host = hostname.trim().toLowerCase().replace(/\\.$/, '');\n return host === 'localhost' || isLoopbackAddress(host);\n}\n","import { LocalRuntimeError } from '@happyvertical/smrt-app-runtime';\nimport type { SmrtSvelteKitRuntime } from '@happyvertical/smrt-app-runtime/sveltekit-runtime';\nimport type { RequestEvent } from '@sveltejs/kit';\nimport { isLoopbackRequest } from './owner-setup.js';\n\nconst MAX_REQUEST_BYTES = 12 * 1024;\n\ntype CredentialEvent = Pick<\n RequestEvent,\n 'request' | 'url' | 'getClientAddress' | 'cookies'\n>;\n\nfunction reply(status: number, body: object): Response {\n return Response.json(body, {\n status,\n headers: {\n 'Cache-Control': 'no-store',\n 'X-Content-Type-Options': 'nosniff',\n 'Referrer-Policy': 'no-referrer',\n ...(status === 405 ? { Allow: 'POST' } : {}),\n },\n });\n}\n\nasync function readPayload(\n request: Request,\n): Promise<Record<string, unknown> | null> {\n const reader = request.body?.getReader();\n if (!reader) return null;\n const decoder = new TextDecoder('utf-8', { fatal: true });\n let bytes = 0;\n let json = '';\n try {\n for (;;) {\n const chunk = await reader.read();\n if (chunk.done) break;\n bytes += chunk.value.byteLength;\n if (bytes > MAX_REQUEST_BYTES) {\n void reader.cancel().catch(() => {});\n return null;\n }\n json += decoder.decode(chunk.value, { stream: true });\n }\n const data: unknown = JSON.parse(json + decoder.decode());\n if (data === null || typeof data !== 'object' || Array.isArray(data)) {\n return null;\n }\n return data as Record<string, unknown>;\n } catch {\n return null;\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Opt-in POST endpoint for the local desktop owner. Mount only on a direct\n * loopback listener, with no proxy-derived client address or origin headers.\n */\nexport function createLocalMcpCredentialHandler(\n runtime: Pick<\n SmrtSvelteKitRuntime,\n 'resolvedRuntime' | 'localRuntime' | 'sessionCookie'\n >,\n): (event: CredentialEvent) => Promise<Response> {\n return async (event) => {\n try {\n const { request, url } = event;\n if (request.method !== 'POST') {\n return reply(405, { code: 'method_not_allowed' });\n }\n const fetchSite = request.headers.get('sec-fetch-site');\n if (\n !isLoopbackRequest(event) ||\n (url.protocol !== 'http:' && url.protocol !== 'https:') ||\n request.headers.get('host') !== url.host ||\n request.headers.get('origin') !== url.origin ||\n (fetchSite !== null && fetchSite !== 'same-origin')\n ) {\n return reply(403, { code: 'forbidden' });\n }\n if ((await runtime.resolvedRuntime()).profile !== 'local') {\n return reply(404, { code: 'unavailable' });\n }\n const sessionId = event.cookies.get(runtime.sessionCookie.name);\n if (!sessionId) return reply(403, { code: 'forbidden' });\n const local = await runtime.localRuntime();\n const store = local.mcpTokens;\n if (!store?.manageDesktopCredential) {\n return reply(404, { code: 'unavailable' });\n }\n\n // Resolve the cookie through the normal session service. Request locals,\n // bearer credentials and payload claims cannot supply an actor.\n const session = await local.restoreSession(sessionId);\n if (\n !session?.user.id ||\n !session.tenantId ||\n !session.membership?.isActive() ||\n session.parent ||\n session.permissionCeiling !== null ||\n !['local-bootstrap', 'password', 'passkey'].includes(\n session.authMethod ?? '',\n )\n ) {\n return reply(403, { code: 'forbidden' });\n }\n\n const mediaType = request.headers.get('content-type')?.split(';', 1)[0];\n if (mediaType?.trim().toLowerCase() !== 'application/json') {\n return reply(415, { code: 'unsupported_media_type' });\n }\n const data = await readPayload(request);\n if (!data || typeof data.id !== 'string') {\n return reply(400, { code: 'invalid_request' });\n }\n const actor = {\n userId: session.user.id,\n tenantId: session.tenantId,\n permissions: session.permissions,\n };\n if (data.action === 'revoke' && Object.keys(data).length === 2) {\n const result = await store.manageDesktopCredential(actor, {\n action: 'revoke',\n id: data.id,\n });\n return result === 'revoked'\n ? reply(200, { status: 'revoked' })\n : reply(409, { code: 'slot_unavailable' });\n }\n if (\n data.action !== 'put' ||\n Object.keys(data).length !== 4 ||\n !Array.isArray(data.scopes) ||\n typeof data.expiresInSeconds !== 'number'\n ) {\n return reply(400, { code: 'invalid_request' });\n }\n const issued = await store.manageDesktopCredential(actor, {\n action: 'put',\n id: data.id,\n scopes: data.scopes,\n expiresInSeconds: data.expiresInSeconds,\n });\n if (typeof issued === 'string') {\n return reply(409, { code: 'slot_unavailable' });\n }\n return reply(200, {\n id: data.id,\n token: issued.token,\n scopes: issued.scopes,\n expiresAt: issued.expiresAt,\n });\n } catch (error) {\n // Do not log or echo upstream exceptions, request bodies, or credentials.\n if (error instanceof LocalRuntimeError) {\n if (error.code === 'owner_unavailable') {\n return reply(403, { code: 'forbidden' });\n }\n if (error.code === 'invalid_scope') {\n return reply(400, { code: 'invalid_scope' });\n }\n if (error.code === 'invalid_configuration') {\n return reply(400, { code: 'invalid_request' });\n }\n }\n return reply(500, { code: 'credential_operation_failed' });\n }\n };\n}\n","/**\n * Sign-out route handler: destroys the current session and clears its cookie\n * with the same cookie attributes the owner-setup action used to set it.\n */\n\nimport { destroySessionCookie } from '@happyvertical/smrt-users/sveltekit';\nimport { type RequestHandler, redirect } from '@sveltejs/kit';\nimport type { SmrtSvelteKitRuntime } from './runtime.js';\n\n/** True when the browser says the request came from this origin. */\nfunction isSameOriginRequest(request: Request, url: URL): boolean {\n const site = request.headers.get('sec-fetch-site');\n if (site !== null) return site === 'same-origin';\n const origin = request.headers.get('origin');\n return origin !== null && origin === url.origin;\n}\n\n/**\n * Mount as `src/routes/sign-out/+server.ts`:\n * `export const POST = createSignOutHandler(runtime);`\n *\n * `POST` only (other methods answer 405) and same-origin only (403 otherwise).\n * The session row is destroyed best-effort and the cookie is cleared even when\n * the row is already gone. Answers 204, or a 303 to `/` when the request\n * accepts HTML so a no-JS form post lands on the home page.\n */\nexport function createSignOutHandler(\n runtime: Pick<SmrtSvelteKitRuntime, 'sessionCookie' | 'classOptions'>,\n): RequestHandler {\n return async (event) => {\n if (event.request.method !== 'POST') {\n return new Response('Method not allowed', {\n status: 405,\n headers: { allow: 'POST' },\n });\n }\n if (!isSameOriginRequest(event.request, event.url)) {\n return new Response('Forbidden', { status: 403 });\n }\n const cookie = runtime.sessionCookie;\n await destroySessionCookie(\n event as unknown as Parameters<typeof destroySessionCookie>[0],\n {\n ...runtime.classOptions('Session'),\n cookieName: cookie.name,\n cookiePath: cookie.path,\n ...(cookie.domain ? { cookieDomain: cookie.domain } : {}),\n },\n );\n // `delete` omits the Secure flag the setup action used; re-issue the\n // expiry with the same attributes so browsers match and drop the cookie.\n event.cookies.set(cookie.name, '', {\n path: cookie.path,\n ...(cookie.domain ? { domain: cookie.domain } : {}),\n httpOnly: true,\n secure: cookie.secure ?? event.url.protocol === 'https:',\n sameSite: cookie.sameSite,\n maxAge: 0,\n });\n if (event.request.headers.get('accept')?.includes('text/html')) {\n redirect(303, '/');\n }\n return new Response(null, { status: 204 });\n };\n}\n"],"mappings":";;;;;;;AAiBO,IAAM,sCAAsC;AAU5C,SAAS,2BACd,SACgB;CAChB,OAAO,YAAY,KAAK,MAAM,QAAQ,OAAO,CAAC;AAChD;AA8CA,SAAS,kBACP,OACe;CACf,OAAO,OAAO,UAAU,aAAc,MAAkB,IAAI;AAC9D;AAWO,SAAS,gCACd,SACgB;CAChB,MAAM,UAAU,QAAQ;CACxB,MAAM,kBACJ,QAAQ,oBACP,gBAEK,QAAQ,gBAAgB;EACtB,WAAW,kBAAkB,QAAQ,SAAS,KAAK,CAAC;EACpD,YAAY,QAAQ,MAAM,qBAAK,IAAI,KAAK;EACxC,cAAc,kBAAkB,QAAQ,YAAY,KAAK;EACzD,iBACE,kBAAkB,QAAQ,eAAe,KAAK;EAChD,mBAAmB,QAAQ,oBAAoB,KAAK;EACpD,cAAc,QAAQ,eAAe,KAAK,CAAC;CAC7C,CAAC,IACH,KAAA;CACN,MAAM,kBACJ,QAAQ,oBACP,UACG,OAAO,WAAmB;EACxB,MAAM,SAAS,QAAQ,aAAa,MAAM;EAC1C,MAAM,YAAY,yBAAyB;EAM3C,QAAQ,OAAM,MALM,eAAe,OACjC,YACI;GAAE,GAAG;GAAQ,IAAI;EAA8B,IAC/C,MACN,EAAA,CACoB,SAAS,MAAM,EAAA,EAAI,QAAQ;CACjD,IACA,KAAA;CACN,IAAI,CAAC,mBAAmB,CAAC,iBACvB,MAAM,IAAI,UACR,sGACF;CAEF,MAAM,gBACJ,QAAQ,mBACN,UACA,MAAM;CAEV,OAAO,OAAO,UAAU;EACtB,MAAM,SAAS,cAAc,KAAK;EAClC,MAAM,YAAY,uBAAuB,MAAM;EAC/C,IAAI,CAAC,WAAW,OAAO,YAAY,KAAK,yBAAyB;EAEjE,IAAI,aACF,OAAO,aAAa,SAAS,mCAAmC,MAChE;EACF,IAAI,CAAC,YACH,IAAI;GACF,aAAc,MAAM,gBAAgB,UAAU,MAAM,MAAO;EAC7D,QAAQ;GACN,aAAa;EACf;EAEF,IAAI,CAAC,YAAY,OAAO,YAAY,KAAK,sBAAsB;EAE/D,IAAI;GACF,OAAO,KAAK,MAAM,gBAAgB,CAAC;EACrC,QAAQ;GACN,OAAO,YAAY,KAAK,yBAAyB;EACnD;CACF;AACF;AAEA,SAAS,uBACP,QAC2B;CAC3B,MAAM,SAAS,QAAQ,MAAM;CAC7B,MAAM,WAAW,QAAQ;CACzB,MAAM,YAAY,QAAQ;CAC1B,MAAM,aAAa,QAAQ;CAC3B,IACE,OAAO,WAAW,YAClB,CAAC,UACD,OAAO,aAAa,YACpB,CAAC,YACD,OAAO,cAAc,YACrB,CAAC,aACD,CAAC,cACD,WAAW,WAAW,UACtB,WAAW,aAAa,YACxB,OAAO,WAAW,WAAW,YAC7B,CAAC,WAAW,QAEZ,OAAO;CAET,IAAI;EACF,IAAI,WAAW,WAAW,MAAM,MAAM,OAAO;CAC/C,QAAQ;EACN,OAAO;CACT;CACA,OAAO,EAAE,QAAQ,WAAW,OAAO;AACrC;AAEA,SAAS,YAAY,QAAgB,MAAwB;CAC3D,OAAO,KAAK;EAAE,eAAe;EAAG,OAAO,EAAE,KAAK;CAAE,GAAG,EAAE,OAAO,CAAC;AAC/D;AA0BA,SAAS,WAAW,MAAyC;CAC3D,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO;CAC9C,MAAM,EAAE,IAAI,OAAO,SAAS;CAC5B,IAAI,OAAO,OAAO,YAAY,OAAO,UAAU,UAAU,OAAO;CAChE,OAAO;EACL;EACA;EACA,GAAI,OAAO,SAAS,YAAY,OAAO,EAAE,KAAK,IAAI,CAAC;CACrD;AACF;AAMO,SAAS,0BAEQ;CACtB,QAAQ,EAAE,aAAa;EACrB,MAAM,QAAQ;EAKd,OAAO,EACL,SAAS;GACP,eAAe,QAAQ,MAAM,IAAI;GACjC,gBAAgB,MAAM,YAAY;GAClC,oBAAoB,MAAM,sBAAsB;GAChD,MAAM,WAAW,MAAM,IAAI;EAC7B,EACF;CACF;AACF;;;AC7LO,IAAM,qBAAqB,OAAO,OAAO;CAC9C,OAAO;CACP,MAAM;CACN,OAAO;CACP,UAAU;CACV,YAAY;AACd,CAAU;AAEH,IAAM,+BAET,OAAO,OAAO;CAChB,gBAAgB;CAChB,mBAAmB;CACnB,qBAAqB;CACrB,eACE;CACF,gBACE;AACJ,CAAC;AAiEM,SAAS,qBACd,SAKA,UAAiC,CAAC,GAClB;CAChB,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,WAAW;EAAE,GAAG;EAA8B,GAAG,QAAQ;CAAS;CACxE,MAAM,WAAW,QAAgB,SAC/B,KAAK,QAAQ;EAAE;EAAM,SAAS,SAAS;CAAM,CAAsB;CACrE,MAAM,cACJ,QAAQ,iBACN,UAA4B,MAAM,OAA8B;CAEpE,MAAM,OAAO,OAAO,UAAwD;EAC1E,IAAI,YAAY,KAAK,GAAG,SAAS,KAAK,UAAU;EAEhD,KAAI,MADmB,QAAQ,gBAAgB,EAAA,CAClC,YAAY,WAAW,CAAC,kBAAkB,KAAK,GAC1D,OAAO;GAAE,WAAW;GAAO,OAAO;GAAI,MAAM;EAAc;EAI5D,MAAM,UAAS,OADW,MADN,QAAQ,aAAa,EAAA,CACT,YAAY,EAAA,CACjB,UAAU;EACrC,IAAI,WAAW,WACb,OAAO;GAAE,WAAW;GAAO,OAAO;GAAI,MAAM;EAAc;EAE5D,MAAM,QAAQ,MAAM,IAAI,aAAa,IAAI,mBAAmB,KAAK,KAAK;EACtE,IAAI,WAAW,WACb,OAAO;GAAE,WAAW;GAAM,OAAO;GAAI,MAAM;EAAU;EACvD,IAAI,CAAC,OAAO,OAAO;GAAE,WAAW;GAAM,OAAO;GAAI,MAAM;EAAU;EACjE,OAAO;GAAE,WAAW;GAAM;GAAO,MAAM;EAAQ;CACjD;CAEA,MAAM,QAAQ,OACZ,UAC8C;EAE9C,KAAI,MADmB,QAAQ,gBAAgB,EAAA,CAClC,YAAY,SAAS,OAAO,QAAQ,KAAK,gBAAgB;EACtE,IAAI,CAAC,kBAAkB,KAAK,GAAG,OAAO,QAAQ,KAAK,mBAAmB;EAEtE,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,MAAM,QAAQ,SAAS;EACtC,QAAQ;GACN,OAAO,QAAQ,KAAK,qBAAqB;EAC3C;EACA,MAAM,SAASA,UAAiB;GAC9B,MAAM,QAAQ,KAAK,IAAIA,KAAI;GAC3B,OAAO,OAAO,UAAU,WAAW,QAAQ;EAC7C;EACA,MAAM,QAAQ,MAAM,mBAAmB,KAAK;EAC5C,MAAM,OAAO,MAAM,mBAAmB,IAAI;EAC1C,MAAM,QAAQ,MAAM,mBAAmB,KAAK;EAC5C,MAAM,WAAW,MAAM,mBAAmB,QAAQ;EAClD,MAAM,aAAa,MAAM,mBAAmB,UAAU,CAAA,CAAE,KAAK;EAC7D,IACE,CAAC,SACD,CAAC,KAAK,KAAK,KACX,CAAC,MAAM,SAAS,GAAG,KAClB,QAAQ,mBAAmB,CAAC,UAE7B,OAAO,QAAQ,KAAK,qBAAqB;EAG3C,MAAM,QAAQ,MAAM,QAAQ,aAAa;EACzC,IAAI;EACJ,IAAI;GACF,SAAS,MAAM,MAAM,WAAW;IAC9B;IACA;IACA;IACA;IACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;IACnC,WAAW,MAAM,QAAQ,QAAQ,IAAI,YAAY,KAAK,KAAA;IACtD,WAAW,MAAM,iBAAiB;GACpC,CAAC;EACH,SAAS,OAAO;GACd,IACE,iBAAiB,sBAChB,MAAM,SAAS,2BACd,MAAM,SAAS,4BAEjB,OAAO,QAAQ,KAAK,gBAAgB;GAEtC,OAAO,QAAQ,KAAK,eAAe;EACrC;EAIA,IACE,QAAQ,4BAA4B,SACpC,QAAQ,sBAER,IAAI;GACF,wBAAwB,QAAQ,qBAAqB,CAAC;EACxD,QAAQ,CAER;EAEF,MAAM,SAAS,QAAQ;EACvB,MAAM,QAAQ,IAAI,OAAO,MAAM,OAAO,WAAW;GAC/C,MAAM,OAAO;GACb,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;GACjD,UAAU;GACV,QAAQ,OAAO,UAAU,MAAM,IAAI,aAAa;GAChD,UAAU,OAAO;GACjB,QAAQ,OAAO;EACjB,CAAC;EACD,IAAI;GACF,MAAM,QAAQ,iBAAiB,QAAQ,KAAK;EAC9C,QAAQ,CAER;EACA,SAAS,KAAK,UAAU;CAC1B;CAEA,OAAO,OAAO,OAAO;EACnB;EACA,SAAS,OAAO,OAAO,EAAE,SAAS,MAAM,CAAC;CAC3C,CAAC;AACH;AAOO,SAAS,kBAAkB,OAGtB;CACV,IAAI;CACJ,IAAI;EACF,UAAU,MAAM,iBAAiB;CACnC,QAAQ;EACN,OAAO;CACT;CACA,OAAO,kBAAkB,OAAO,KAAK,mBAAmB,MAAM,IAAI,QAAQ;AAC5E;AAGO,SAAS,kBAAkB,OAA2C;CAC3E,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI,OAAO,MACR,KAAK,CAAA,CACL,YAAY,CAAA,CACZ,QAAQ,YAAY,EAAE;CACzB,IAAI,SAAS,SAAS,SAAS,mBAAmB,OAAO;CACzD,IAAI,KAAK,WAAW,SAAS,GAAG,OAAO,KAAK,MAAM,CAAgB;CAClE,MAAM,SAAS,KAAK,MAAM,GAAG;CAC7B,OACE,OAAO,WAAW,KAClB,OAAO,OAAO,SAAS,YAAY,KAAK,IAAI,KAAK,OAAO,IAAI,KAAK,GAAG,KACpE,OAAO,OAAO,EAAE,MAAM;AAE1B;AAGO,SAAS,mBAAmB,UAA2B;CAC5D,MAAM,OAAO,SAAS,KAAK,CAAA,CAAE,YAAY,CAAA,CAAE,QAAQ,OAAO,EAAE;CAC5D,OAAO,SAAS,eAAe,kBAAkB,IAAI;AACvD;;;AC9SA,IAAM,oBAAoB,KAAK;AAO/B,SAAS,MAAM,QAAgB,MAAwB;CACrD,OAAO,SAAS,KAAK,MAAM;EACzB;EACA,SAAS;GACP,iBAAiB;GACjB,0BAA0B;GAC1B,mBAAmB;GACnB,GAAI,WAAW,MAAM,EAAE,OAAO,OAAO,IAAI,CAAC;EAC5C;CACF,CAAC;AACH;AAEA,eAAe,YACb,SACyC;CACzC,MAAM,SAAS,QAAQ,MAAM,UAAU;CACvC,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,UAAU,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC;CACxD,IAAI,QAAQ;CACZ,IAAI,OAAO;CACX,IAAI;EACF,SAAS;GACP,MAAM,QAAQ,MAAM,OAAO,KAAK;GAChC,IAAI,MAAM,MAAM;GAChB,SAAS,MAAM,MAAM;GACrB,IAAI,QAAQ,mBAAmB;IAC7B,OAAY,OAAO,CAAA,CAAE,YAAY,CAAC,CAAC;IACnC,OAAO;GACT;GACA,QAAQ,QAAQ,OAAO,MAAM,OAAO,EAAE,QAAQ,KAAK,CAAC;EACtD;EACA,MAAM,OAAgB,KAAK,MAAM,OAAO,QAAQ,OAAO,CAAC;EACxD,IAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GACjE,OAAO;EAET,OAAO;CACT,QAAQ;EACN,OAAO;CACT,UAAE;EACA,OAAO,YAAY;CACrB;AACF;AAMO,SAAS,gCACd,SAI+C;CAC/C,OAAO,OAAO,UAAU;EACtB,IAAI;GACF,MAAM,EAAE,SAAS,QAAQ;GACzB,IAAI,QAAQ,WAAW,QACrB,OAAO,MAAM,KAAK,EAAE,MAAM,qBAAqB,CAAC;GAElD,MAAM,YAAY,QAAQ,QAAQ,IAAI,gBAAgB;GACtD,IACE,CAAC,kBAAkB,KAAK,KACvB,IAAI,aAAa,WAAW,IAAI,aAAa,YAC9C,QAAQ,QAAQ,IAAI,MAAM,MAAM,IAAI,QACpC,QAAQ,QAAQ,IAAI,QAAQ,MAAM,IAAI,UACrC,cAAc,QAAQ,cAAc,eAErC,OAAO,MAAM,KAAK,EAAE,MAAM,YAAY,CAAC;GAEzC,KAAK,MAAM,QAAQ,gBAAgB,EAAA,CAAG,YAAY,SAChD,OAAO,MAAM,KAAK,EAAE,MAAM,cAAc,CAAC;GAE3C,MAAM,YAAY,MAAM,QAAQ,IAAI,QAAQ,cAAc,IAAI;GAC9D,IAAI,CAAC,WAAW,OAAO,MAAM,KAAK,EAAE,MAAM,YAAY,CAAC;GACvD,MAAM,QAAQ,MAAM,QAAQ,aAAa;GACzC,MAAM,QAAQ,MAAM;GACpB,IAAI,CAAC,OAAO,yBACV,OAAO,MAAM,KAAK,EAAE,MAAM,cAAc,CAAC;GAK3C,MAAM,UAAU,MAAM,MAAM,eAAe,SAAS;GACpD,IACE,CAAC,SAAS,KAAK,MACf,CAAC,QAAQ,YACT,CAAC,QAAQ,YAAY,SAAS,KAC9B,QAAQ,UACR,QAAQ,sBAAsB,QAC9B,CAAC;IAAC;IAAmB;IAAY;GAAS,CAAA,CAAE,SAC1C,QAAQ,cAAc,EACxB,GAEA,OAAO,MAAM,KAAK,EAAE,MAAM,YAAY,CAAC;GAIzC,KADkB,QAAQ,QAAQ,IAAI,cAAc,CAAA,EAAG,MAAM,KAAK,CAAC,CAAA,CAAE,GAAA,EACtD,KAAK,CAAA,CAAE,YAAY,MAAM,oBACtC,OAAO,MAAM,KAAK,EAAE,MAAM,yBAAyB,CAAC;GAEtD,MAAM,OAAO,MAAM,YAAY,OAAO;GACtC,IAAI,CAAC,QAAQ,OAAO,KAAK,OAAO,UAC9B,OAAO,MAAM,KAAK,EAAE,MAAM,kBAAkB,CAAC;GAE/C,MAAM,QAAQ;IACZ,QAAQ,QAAQ,KAAK;IACrB,UAAU,QAAQ;IAClB,aAAa,QAAQ;GACvB;GACA,IAAI,KAAK,WAAW,YAAY,OAAO,KAAK,IAAI,CAAA,CAAE,WAAW,GAK3D,OAAO,MAJc,MAAM,wBAAwB,OAAO;IACxD,QAAQ;IACR,IAAI,KAAK;GACX,CAAC,MACiB,YACd,MAAM,KAAK,EAAE,QAAQ,UAAU,CAAC,IAChC,MAAM,KAAK,EAAE,MAAM,mBAAmB,CAAC;GAE7C,IACE,KAAK,WAAW,SAChB,OAAO,KAAK,IAAI,CAAA,CAAE,WAAW,KAC7B,CAAC,MAAM,QAAQ,KAAK,MAAM,KAC1B,OAAO,KAAK,qBAAqB,UAEjC,OAAO,MAAM,KAAK,EAAE,MAAM,kBAAkB,CAAC;GAE/C,MAAM,SAAS,MAAM,MAAM,wBAAwB,OAAO;IACxD,QAAQ;IACR,IAAI,KAAK;IACT,QAAQ,KAAK;IACb,kBAAkB,KAAK;GACzB,CAAC;GACD,IAAI,OAAO,WAAW,UACpB,OAAO,MAAM,KAAK,EAAE,MAAM,mBAAmB,CAAC;GAEhD,OAAO,MAAM,KAAK;IAChB,IAAI,KAAK;IACT,OAAO,OAAO;IACd,QAAQ,OAAO;IACf,WAAW,OAAO;GACpB,CAAC;EACH,SAAS,OAAO;GAEd,IAAI,iBAAiB,mBAAmB;IACtC,IAAI,MAAM,SAAS,qBACjB,OAAO,MAAM,KAAK,EAAE,MAAM,YAAY,CAAC;IAEzC,IAAI,MAAM,SAAS,iBACjB,OAAO,MAAM,KAAK,EAAE,MAAM,gBAAgB,CAAC;IAE7C,IAAI,MAAM,SAAS,yBACjB,OAAO,MAAM,KAAK,EAAE,MAAM,kBAAkB,CAAC;GAEjD;GACA,OAAO,MAAM,KAAK,EAAE,MAAM,8BAA8B,CAAC;EAC3D;CACF;AACF;;;AC/JA,SAAS,oBAAoB,SAAkB,KAAmB;CAChE,MAAM,OAAO,QAAQ,QAAQ,IAAI,gBAAgB;CACjD,IAAI,SAAS,MAAM,OAAO,SAAS;CACnC,MAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ;CAC3C,OAAO,WAAW,QAAQ,WAAW,IAAI;AAC3C;AAWO,SAAS,qBACd,SACgB;CAChB,OAAO,OAAO,UAAU;EACtB,IAAI,MAAM,QAAQ,WAAW,QAC3B,OAAO,IAAI,SAAS,sBAAsB;GACxC,QAAQ;GACR,SAAS,EAAE,OAAO,OAAO;EAC3B,CAAC;EAEH,IAAI,CAAC,oBAAoB,MAAM,SAAS,MAAM,GAAG,GAC/C,OAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,IAAI,CAAC;EAElD,MAAM,SAAS,QAAQ;EACvB,MAAM,qBACJ,OACA;GACE,GAAG,QAAQ,aAAa,SAAS;GACjC,YAAY,OAAO;GACnB,YAAY,OAAO;GACnB,GAAI,OAAO,SAAS,EAAE,cAAc,OAAO,OAAO,IAAI,CAAC;EACzD,CACF;EAGA,MAAM,QAAQ,IAAI,OAAO,MAAM,IAAI;GACjC,MAAM,OAAO;GACb,GAAI,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;GACjD,UAAU;GACV,QAAQ,OAAO,UAAU,MAAM,IAAI,aAAa;GAChD,UAAU,OAAO;GACjB,QAAQ;EACV,CAAC;EACD,IAAI,MAAM,QAAQ,QAAQ,IAAI,QAAQ,CAAA,EAAG,SAAS,WAAW,GAC3D,SAAS,KAAK,GAAG;EAEnB,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;CAC3C;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-app-runtime",
3
- "version": "0.60.10",
3
+ "version": "0.60.12",
4
4
  "description": "Validated local, self-hosted, and cloud runtime composition for s-m-r-t applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,16 +33,16 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
- "@happyvertical/smrt-config": "0.60.10",
37
- "@happyvertical/smrt-core": "0.60.10",
38
- "@happyvertical/smrt-jobs": "0.60.10",
39
- "@happyvertical/smrt-profiles": "0.60.10",
40
- "@happyvertical/smrt-tenancy": "0.60.10",
41
- "@happyvertical/smrt-users": "0.60.10",
36
+ "@happyvertical/smrt-config": "0.60.12",
37
+ "@happyvertical/smrt-core": "0.60.12",
38
+ "@happyvertical/smrt-jobs": "0.60.12",
39
+ "@happyvertical/smrt-profiles": "0.60.12",
40
+ "@happyvertical/smrt-tenancy": "0.60.12",
41
+ "@happyvertical/smrt-users": "0.60.12",
42
42
  "@happyvertical/sql": "^0.111.5"
43
43
  },
44
44
  "devDependencies": {
45
- "@happyvertical/smrt-vitest": "0.60.10",
45
+ "@happyvertical/smrt-vitest": "0.60.12",
46
46
  "@sveltejs/kit": "2.70.3",
47
47
  "@types/node": "24.13.6",
48
48
  "tsx": "^4.23.13",