@happyvertical/smrt-core 0.53.4 → 0.53.6
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/AGENTS.md +4 -0
- package/README.md +11 -0
- package/agents/build-knowledge.md +7 -0
- package/agents/generators.md +24 -1
- package/dist/generated-collection-access.d.ts +43 -0
- package/dist/generated-collection-access.d.ts.map +1 -0
- package/dist/generated-collection-access.js +71 -0
- package/dist/generated-collection-access.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +3 -1
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/smrt-knowledge.json +9 -9
- package/dist/utils/qualified-names.d.ts +15 -0
- package/dist/utils/qualified-names.d.ts.map +1 -1
- package/dist/utils/qualified-names.js +31 -1
- package/dist/utils/qualified-names.js.map +1 -1
- package/dist/vite-plugin/changes-route.d.ts +4 -3
- package/dist/vite-plugin/changes-route.d.ts.map +1 -1
- package/dist/vite-plugin/changes-route.js +8 -7
- package/dist/vite-plugin/changes-route.js.map +1 -1
- package/dist/vite-plugin/dev-plane-route.d.ts.map +1 -1
- package/dist/vite-plugin/dev-plane-route.js +4 -4
- package/dist/vite-plugin/dev-plane-route.js.map +1 -1
- package/dist/vite-plugin/events-route.d.ts.map +1 -1
- package/dist/vite-plugin/events-route.js +4 -4
- package/dist/vite-plugin/events-route.js.map +1 -1
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +13 -1
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/resources-route.d.ts.map +1 -1
- package/dist/vite-plugin/resources-route.js +9 -4
- package/dist/vite-plugin/resources-route.js.map +1 -1
- package/dist/vite-plugin/sveltekit-config-import.d.ts +22 -0
- package/dist/vite-plugin/sveltekit-config-import.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-config-import.js +44 -2
- package/dist/vite-plugin/sveltekit-config-import.js.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
- package/dist/vite-plugin/sveltekit-generator.js +59 -9
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/sveltekit-register-injection.d.ts +26 -0
- package/dist/vite-plugin/sveltekit-register-injection.d.ts.map +1 -0
- package/dist/vite-plugin/sveltekit-register-injection.js +37 -0
- package/dist/vite-plugin/sveltekit-register-injection.js.map +1 -0
- package/dist/vite-plugin/sync-apply-route.d.ts +1 -1
- package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
- package/dist/vite-plugin/sync-apply-route.js +4 -4
- package/dist/vite-plugin/sync-apply-route.js.map +1 -1
- package/package.json +10 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-plane-route.js","names":[],"sources":["../../src/vite-plugin/dev-plane-route.ts"],"sourcesContent":["/**\n * Generated `_dev/[...tool]/+server.ts` route: the in-app runtime dev-plane\n * (#2782). Opt-in via `svelteKit: { devPlaneRoute: { enabled: true } }`.\n *\n * The route is dev-only twice over: it 404s unless SvelteKit's `dev` flag is\n * set, and it refuses to start without `SMRT_DEV_MCP_TOKEN`. Everything else\n * — loopback checks, bearer auth, the positive read-only catalog, the MCP\n * endpoint — lives in `@happyvertical/smrt-dev-mcp/dev-plane`, which the\n * generated file imports at consumer runtime. Like `_resources`, the route is\n * only emitted when that subpath resolves from the consumer.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { AUTO_GENERATED_ROUTE_HEADER } from './route-header.js';\nimport {
|
|
1
|
+
{"version":3,"file":"dev-plane-route.js","names":[],"sources":["../../src/vite-plugin/dev-plane-route.ts"],"sourcesContent":["/**\n * Generated `_dev/[...tool]/+server.ts` route: the in-app runtime dev-plane\n * (#2782). Opt-in via `svelteKit: { devPlaneRoute: { enabled: true } }`.\n *\n * The route is dev-only twice over: it 404s unless SvelteKit's `dev` flag is\n * set, and it refuses to start without `SMRT_DEV_MCP_TOKEN`. Everything else\n * — loopback checks, bearer auth, the positive read-only catalog, the MCP\n * endpoint — lives in `@happyvertical/smrt-dev-mcp/dev-plane`, which the\n * generated file imports at consumer runtime. Like `_resources`, the route is\n * only emitted when that subpath resolves from the consumer.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { AUTO_GENERATED_ROUTE_HEADER } from './route-header.js';\nimport { generateCollectionAccessImports } from './sveltekit-config-import.js';\nimport type { SvelteKitOptions } from './sveltekit-generator.js';\n\n/** Whether `@happyvertical/smrt-dev-mcp/dev-plane` resolves from the consumer. */\nexport function consumerHasSmrtDevMcp(projectRoot: string): boolean {\n try {\n const consumerRequire = createRequire(\n pathToFileURL(join(projectRoot, 'package.json')).href,\n );\n consumerRequire.resolve('@happyvertical/smrt-dev-mcp/dev-plane');\n return true;\n } catch {\n return false;\n }\n}\n\n/** Whether this invocation will own the `_dev/[...tool]` output file. */\nexport function willGenerateDevPlaneRoute(\n projectRoot: string,\n options: SvelteKitOptions,\n): boolean {\n return (\n options.devPlaneRoute?.enabled === true &&\n consumerHasSmrtDevMcp(projectRoot)\n );\n}\n\nexport function generateDevPlaneRoute(\n projectRoot: string,\n options: SvelteKitOptions,\n): boolean {\n if (options.devPlaneRoute?.enabled !== true) {\n return false;\n }\n if (!willGenerateDevPlaneRoute(projectRoot, options)) {\n console.log(\n '[smrt] Skipping _dev route - @happyvertical/smrt-dev-mcp/dev-plane is ' +\n 'not resolvable; install @happyvertical/smrt-dev-mcp as a devDependency',\n );\n return false;\n }\n const routeDir = join(projectRoot, options.routesDir, '_dev', '[...tool]');\n if (!existsSync(routeDir)) {\n mkdirSync(routeDir, { recursive: true });\n }\n const filePath = join(routeDir, '+server.ts');\n writeFileSync(\n filePath,\n generateDevPlaneRouteTemplate(\n generateCollectionAccessImports(projectRoot, routeDir, options, [\n 'getSmrtConfig',\n ]),\n ),\n 'utf-8',\n );\n console.log(`[smrt] Generated: ${filePath}`);\n return true;\n}\n\nfunction generateDevPlaneRouteTemplate(\n collectionAccessImports: string,\n): string {\n return `${AUTO_GENERATED_ROUTE_HEADER}\n// DO NOT EDIT - changes will be overwritten\n//\n// In-app runtime dev-plane (#2782). Dev mode only. Serves the read-only\n// runtime tools over JSON (GET|POST /_dev/<tool>, GET /_dev for the catalog)\n// and MCP (POST /_dev/mcp) against this app's live registry and database.\n// Requires SMRT_DEV_MCP_TOKEN; every request must present it as a bearer\n// token from loopback. Never mounted in production builds.\nimport { dev } from '$app/environment';\nimport { error } from '@sveltejs/kit';\nimport type { RequestHandler } from './$types';\nimport { createDevPlane, type DevPlane } from '@happyvertical/smrt-dev-mcp/dev-plane';\n${collectionAccessImports}\n\nlet plane: DevPlane | null = null;\n\nfunction getPlane(): DevPlane | null {\n if (!dev) return null;\n const token = process.env.SMRT_DEV_MCP_TOKEN?.trim();\n if (!token) return null;\n if (!plane) {\n // The default config's db is what the app itself opens; passing its\n // url/type makes the SDK connection cache hand the plane the same handle.\n const db = getSmrtConfig('__smrt_dev_plane__').db as\n | { url?: string; type?: string }\n | undefined;\n plane = createDevPlane({ token, projectRoot: process.cwd(), db });\n }\n return plane;\n}\n\nconst handle: RequestHandler = async ({ request, url }) => {\n if (!dev) throw error(404, 'Not found');\n const active = getPlane();\n if (!active) {\n throw error(503, 'SMRT_DEV_MCP_TOKEN is not set; the dev-plane is disabled');\n }\n const base = url.pathname.slice(0, url.pathname.indexOf('/_dev') + '/_dev'.length);\n return active.handleRequest(request, base);\n};\n\nexport const GET = handle;\nexport const POST = handle;\n`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,sBAAsB,aAA8B;CAClE,IAAI;EAIF,cAFE,cAAc,KAAK,aAAa,cAAc,CAAC,CAAC,CAAC,IAEnD,CAAA,CAAgB,QAAQ,uCAAuC;EAC/D,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,0BACd,aACA,SACS;CACT,OACE,QAAQ,eAAe,YAAY,QACnC,sBAAsB,WAAW;AAErC;AAEA,SAAgB,sBACd,aACA,SACS;CACT,IAAI,QAAQ,eAAe,YAAY,MACrC,OAAO;CAET,IAAI,CAAC,0BAA0B,aAAa,OAAO,GAAG;EACpD,QAAQ,IACN,8IAEF;EACA,OAAO;CACT;CACA,MAAM,WAAW,KAAK,aAAa,QAAQ,WAAW,QAAQ,WAAW;CACzE,IAAI,CAAC,WAAW,QAAQ,GACtB,UAAU,UAAU,EAAE,WAAW,KAAK,CAAC;CAEzC,MAAM,WAAW,KAAK,UAAU,YAAY;CAC5C,cACE,UACA,8BACE,gCAAgC,aAAa,UAAU,SAAS,CAC9D,eACF,CAAC,CACH,GACA,OACF;CACA,QAAQ,IAAI,qBAAqB,UAAU;CAC3C,OAAO;AACT;AAEA,SAAS,8BACP,yBACQ;CACR,OAAO,GAAG,4BAA4B;;;;;;;;;;;;EAYtC,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgC1B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events-route.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/events-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAO5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,gBAAgB,EACzB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,
|
|
1
|
+
{"version":3,"file":"events-route.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/events-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAO5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,gBAAgB,EACzB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAiCT"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AUTO_GENERATED_ROUTE_HEADER } from "./route-header.js";
|
|
2
|
-
import {
|
|
2
|
+
import { generateCollectionAccessImports } from "./sveltekit-config-import.js";
|
|
3
3
|
import { manifestHasTenantScopedObject, resolveAnchorClassName } from "./changes-route.js";
|
|
4
4
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -52,7 +52,7 @@ function generateEventsRoute(projectRoot, manifest, options, webManifestHash) {
|
|
|
52
52
|
return false;
|
|
53
53
|
}
|
|
54
54
|
const routeDir = join(projectRoot, options.routesDir, "_events");
|
|
55
|
-
const content = generateEventsRouteTemplate(anchorClassName, manifestHasTenantScopedObject(manifest), webManifestHash, options.eventsRoute?.maxSubscribers, normalizeAllowedOrigins(options.eventsRoute?.allowedOrigins), options.eventsRoute?.allowCredentials === true,
|
|
55
|
+
const content = generateEventsRouteTemplate(anchorClassName, manifestHasTenantScopedObject(manifest), webManifestHash, options.eventsRoute?.maxSubscribers, normalizeAllowedOrigins(options.eventsRoute?.allowedOrigins), options.eventsRoute?.allowCredentials === true, generateCollectionAccessImports(projectRoot, routeDir, options, ["getCollection"]));
|
|
56
56
|
if (!existsSync(routeDir)) mkdirSync(routeDir, { recursive: true });
|
|
57
57
|
const filePath = join(routeDir, "+server.ts");
|
|
58
58
|
writeFileSync(filePath, content, "utf-8");
|
|
@@ -131,7 +131,7 @@ export const OPTIONS: RequestHandler = async ({ request }) => {
|
|
|
131
131
|
`
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
|
-
function generateEventsRouteTemplate(anchorClassName, tenantScoped, manifestHash, maxSubscribers, allowedOrigins, allowCredentials,
|
|
134
|
+
function generateEventsRouteTemplate(anchorClassName, tenantScoped, manifestHash, maxSubscribers, allowedOrigins, allowCredentials, collectionAccessImports) {
|
|
135
135
|
const configuredMaxSubscribers = maxSubscribers !== void 0 && Number.isFinite(maxSubscribers) && maxSubscribers >= 0 ? Math.floor(maxSubscribers) : void 0;
|
|
136
136
|
const maxSubscribersArg = configuredMaxSubscribers === void 0 ? "" : `, ${JSON.stringify(configuredMaxSubscribers)}`;
|
|
137
137
|
const manifestHashLiteral = manifestHash === void 0 ? "undefined" : JSON.stringify(manifestHash);
|
|
@@ -174,8 +174,8 @@ import {
|
|
|
174
174
|
resolveDispatchTenantScope,
|
|
175
175
|
tryReserveChangeEventSubscriberSlot,
|
|
176
176
|
} from '@happyvertical/smrt-core';
|
|
177
|
-
import { getCollection } from '${configImport}';
|
|
178
177
|
import type { RequestHandler } from './$types';
|
|
178
|
+
${collectionAccessImports}
|
|
179
179
|
|
|
180
180
|
const MANIFEST_HASH = ${manifestHashLiteral};
|
|
181
181
|
${corsBlock.helpers}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events-route.js","names":[],"sources":["../../src/vite-plugin/events-route.ts"],"sourcesContent":["/**\n * SvelteKit `_events` route generation — live change signals (issue #1763,\n * SERVER half; parent PRD #1755).\n *\n * Emits `{routesDir}/_events/+server.ts` — an auth-guarded, tenant-scoped\n * Server-Sent-Events endpoint that streams coarse change signals (no row\n * payloads). It is the push companion to the generated `_changes` route\n * (#1758). Kept in its own module so `sveltekit-generator.ts` only carries a\n * one-line registration.\n *\n * The stream lifecycle (subscribe, catch-up, heartbeat, teardown) lives in\n * core's `buildChangeEventStream`, imported by the generated file — so the\n * emitted route stays THIN and the stream logic is written and tested once.\n *\n * Design notes (mirror `changes-route.ts`):\n * - **Fail-closed auth** (#1540 posture): the handler requires an authenticated\n * principal on `locals`. The signal stream spans every table, so per-model\n * `api: { public }` opt-outs deliberately do not apply.\n * - **Tenant scoping**: when the project has tenant-scoped objects, the route\n * establishes tenant context from `locals` exactly like the `_changes` route,\n * then captures the resolved scope ONCE at connection open and passes it to\n * the stream (delivery filters against that fixed value — it cannot\n * re-resolve per signal outside the request's ALS context).\n * - **Table/row authorization** (#3020): `locals` is forwarded to\n * `buildChangeEventStream`, which — if the consumer registered\n * `authorizeChangeFeed`/`isChangeFeedEntryVisible`\n * (`@happyvertical/smrt-core`'s `change-feed-authz.ts`) — resolves the\n * allowed tables ONCE at connection open (same reasoning as tenant scope)\n * and applies both hooks to catch-up replay AND every live signal before it\n * is enqueued. Unregistered, behavior is unchanged from pre-#3020.\n * - **Database resolution**: anchors on the project's first generated\n * collection (alphabetical) via `getCollection()`, exactly as `_changes`.\n * - Cleanup rides the generated-route sweep: the file starts with\n * {@link AUTO_GENERATED_ROUTE_HEADER}.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { SmartObjectManifest } from '../scanner/types';\nimport {\n manifestHasTenantScopedObject,\n resolveAnchorClassName,\n} from './changes-route.js';\nimport { AUTO_GENERATED_ROUTE_HEADER } from './route-header.js';\nimport { resolveSvelteKitConfigImport } from './sveltekit-config-import.js';\nimport type { SvelteKitOptions } from './sveltekit-generator.js';\n\n/**\n * Generate the `_events/+server.ts` route. Returns true when a route was\n * written. Disabled with `sveltekit: { eventsRoute: { enabled: false } }`;\n * skipped (with a log line) when the manifest has no objects to anchor on.\n */\nexport function generateEventsRoute(\n projectRoot: string,\n manifest: SmartObjectManifest,\n options: SvelteKitOptions,\n webManifestHash?: string,\n): boolean {\n if (options.eventsRoute?.enabled === false) {\n return false;\n }\n\n const anchorClassName = resolveAnchorClassName(manifest);\n if (!anchorClassName) {\n console.log(\n '[smrt] Skipping _events route - no SMRT objects to anchor the database on',\n );\n return false;\n }\n\n const routeDir = join(projectRoot, options.routesDir, '_events');\n const content = generateEventsRouteTemplate(\n anchorClassName,\n manifestHasTenantScopedObject(manifest),\n webManifestHash,\n options.eventsRoute?.maxSubscribers,\n normalizeAllowedOrigins(options.eventsRoute?.allowedOrigins),\n options.eventsRoute?.allowCredentials === true,\n resolveSvelteKitConfigImport(projectRoot, routeDir, options),\n );\n\n if (!existsSync(routeDir)) {\n mkdirSync(routeDir, { recursive: true });\n }\n const filePath = join(routeDir, '+server.ts');\n writeFileSync(filePath, content, 'utf-8');\n console.log(`[smrt] Generated: ${filePath}`);\n return true;\n}\n\n/**\n * Trim, de-duplicate, and drop empty entries from a configured origin\n * allowlist. An `undefined`/empty result means \"same-origin only\" — the\n * generated route emits no CORS surface at all (fail-closed default, #1861).\n */\nfunction normalizeAllowedOrigins(\n origins: string[] | undefined,\n): string[] | undefined {\n if (!Array.isArray(origins)) return undefined;\n const cleaned = [\n ...new Set(\n origins\n .filter((o): o is string => typeof o === 'string')\n .map((o) => o.trim())\n .filter((o) => o.length > 0),\n ),\n ];\n return cleaned.length > 0 ? cleaned : undefined;\n}\n\n/**\n * Build the CORS fragments injected into the generated `_events` route (#1861)\n * when an origin allowlist is configured. Mirrors the runtime REST generator's\n * posture: the request `Origin` is echoed only when it is on the allowlist\n * (never `*`), `Vary: Origin` is always set on a matched response, and\n * `Access-Control-Allow-Credentials: true` is added only when credentials are\n * opted in — so cookies flow to an allow-listed cross-origin `EventSource`\n * while an unmatched origin gets no CORS surface (fail-closed).\n */\nfunction buildEventsCorsBlock(\n allowedOrigins: string[],\n allowCredentials: boolean,\n): {\n helpers: string;\n streamHeadersSpread: string;\n capacityReturn: string;\n optionsHandler: string;\n} {\n const originsLiteral = JSON.stringify(allowedOrigins);\n const credentialsLine = allowCredentials\n ? \"\\n headers['Access-Control-Allow-Credentials'] = 'true';\"\n : '';\n const helpers = `\n// Credentialed cross-origin CORS (#1861). Opt-in via\n// \\`sveltekit.eventsRoute.allowedOrigins\\` (+ \\`allowCredentials\\`); an unmatched or\n// absent Origin gets no CORS surface, so this route stays same-origin only by\n// default. The Origin is echoed only when allow-listed (never \\`*\\`), which is\n// what makes credentialed CORS safe. CORS never authorizes — the fail-closed\n// \\`requireRouteAuth\\` guard below is unchanged; CORS only lets an allow-listed\n// browser's cookies reach it.\nconst CORS_ALLOWED_ORIGINS = new Set(${originsLiteral});\n\nfunction eventsCorsHeaders(request: Request): Record<string, string> {\n const origin = request.headers.get('origin');\n if (!origin || !CORS_ALLOWED_ORIGINS.has(origin)) return {};\n const headers: Record<string, string> = {\n 'Access-Control-Allow-Origin': origin,\n Vary: 'Origin',\n };${credentialsLine}\n return headers;\n}\n\nfunction applyEventsCors(response: Response, request: Request): Response {\n for (const [key, value] of Object.entries(eventsCorsHeaders(request))) {\n response.headers.set(key, value);\n }\n return response;\n}\n`;\n const streamHeadersSpread = '...eventsCorsHeaders(request),\\n ';\n const capacityReturn =\n ' return applyEventsCors(eventStreamCapacityExceededResponse(), request);';\n const optionsHandler = `\n// Preflight for a credentialed cross-origin subscription. \\`EventSource\\` GETs\n// are \"simple\" requests (no preflight), but a fetch-based SSE client may\n// preflight — answer it for allow-listed origins only.\nexport const OPTIONS: RequestHandler = async ({ request }) => {\n const headers = eventsCorsHeaders(request);\n if (!('Access-Control-Allow-Origin' in headers)) {\n return new Response(null, { status: 403 });\n }\n return new Response(null, {\n status: 204,\n headers: {\n ...headers,\n 'Access-Control-Allow-Methods': 'GET,OPTIONS',\n 'Access-Control-Allow-Headers': 'Authorization,Last-Event-ID,Content-Type',\n 'Access-Control-Max-Age': '86400',\n },\n });\n};\n`;\n return { helpers, streamHeadersSpread, capacityReturn, optionsHandler };\n}\n\nfunction generateEventsRouteTemplate(\n anchorClassName: string,\n tenantScoped: boolean,\n manifestHash: string | undefined,\n maxSubscribers: number | undefined,\n allowedOrigins: string[] | undefined,\n allowCredentials: boolean,\n configImport: string,\n): string {\n const configuredMaxSubscribers =\n maxSubscribers !== undefined &&\n Number.isFinite(maxSubscribers) &&\n maxSubscribers >= 0\n ? Math.floor(maxSubscribers)\n : undefined;\n const maxSubscribersArg =\n configuredMaxSubscribers === undefined\n ? ''\n : `, ${JSON.stringify(configuredMaxSubscribers)}`;\n const manifestHashLiteral =\n manifestHash === undefined ? 'undefined' : JSON.stringify(manifestHash);\n // CORS is emitted only when an explicit allowlist is configured (#1861).\n // Without one the generated route carries no CORS surface — same-origin only,\n // byte-for-byte the pre-#1861 output.\n const corsEnabled = allowedOrigins !== undefined;\n const corsBlock = corsEnabled\n ? buildEventsCorsBlock(allowedOrigins, allowCredentials)\n : {\n helpers: '',\n streamHeadersSpread: '',\n capacityReturn: ' return eventStreamCapacityExceededResponse();',\n optionsHandler: '',\n };\n const tenantHelper = tenantScoped\n ? `\nimport { enterTenantContext, hasTenantContext } from '@happyvertical/smrt-tenancy';\n\nfunction establishTenantContext(locals: unknown): void {\n if (hasTenantContext()) return;\n if (!locals || typeof locals !== 'object') return;\n const l = locals as Record<string, unknown>;\n const user = l.user as Record<string, unknown> | undefined;\n const session = l.session as Record<string, unknown> | undefined;\n const tenantId = l.tenantId ?? user?.tenantId ?? session?.tenantId;\n if (typeof tenantId === 'string' && tenantId) {\n enterTenantContext({ tenantId });\n }\n}\n`\n : '';\n const tenantCall = tenantScoped ? '\\n establishTenantContext(locals);' : '';\n\n return `${AUTO_GENERATED_ROUTE_HEADER}\n// DO NOT EDIT - changes will be overwritten\n//\n// GET /_events — Server-Sent-Events stream of coarse change signals (#1763).\n// Part of the client/mobile sync contract: each event names {table, operation,\n// rowId, tenantId} (NEVER a row payload — authorization stays on the read path)\n// and carries a seq cursor in the SSE id: field. A reconnecting EventSource\n// resends Last-Event-ID; the route replays missed changes from it, then streams\n// live. Same-origin only unless an origin allowlist is configured (#1861).\n\nimport { error } from '@sveltejs/kit';\nimport {\n buildChangeEventStream,\n eventStreamCapacityExceededResponse,\n resolveDispatchTenantScope,\n tryReserveChangeEventSubscriberSlot,\n} from '@happyvertical/smrt-core';\nimport { getCollection } from '${configImport}';\nimport type { RequestHandler } from './$types';\n\nconst MANIFEST_HASH = ${manifestHashLiteral};\n${corsBlock.helpers}\n// Fail-closed authorization (#1540): the signal stream spans every table, so\n// it is never public — an authenticated principal on \\`locals\\` is required.\nfunction hasAuthenticatedPrincipal(locals: unknown): boolean {\n if (!locals || typeof locals !== 'object') return false;\n const l = locals as Record<string, unknown>;\n const isResolvedPrincipal = (v: unknown) =>\n typeof v === 'object' && v !== null;\n return (\n isResolvedPrincipal(l.user) ||\n isResolvedPrincipal(l.session) ||\n l.smrtAuth === true\n );\n}\n\nfunction requireRouteAuth(locals: unknown): void {\n if (!hasAuthenticatedPrincipal(locals)) {\n throw error(401, 'Authentication required');\n }\n}\n\n// Last-Event-ID (auto-reconnect) takes precedence over ?since=; default is\n// live-forward only (no catch-up replay).\nfunction parseCursor(request: Request, url: URL): number | null {\n const lastEventId = request.headers.get('Last-Event-ID');\n if (lastEventId !== null && lastEventId.trim() !== '') {\n const n = Number(lastEventId);\n if (Number.isFinite(n) && n >= 0) return Math.floor(n);\n }\n const since = url.searchParams.get('since');\n if (since !== null && since.trim() !== '') {\n const n = Number(since);\n if (Number.isFinite(n) && n >= 0) return Math.floor(n);\n }\n return null;\n}\n${tenantHelper}\nexport const GET: RequestHandler = async ({ locals, url, request }) => {\n requireRouteAuth(locals);${tenantCall}\n\n const cursor = parseCursor(request, url);\n // Capture the tenant scope ONCE at connection open — signal delivery runs\n // outside this request's tenant context and must filter against a fixed\n // value, not a per-signal re-resolution. resolveDispatchTenantScope() reads\n // the context established above (fail-closed: tenancy on + no tenant → global\n // rows only; tenancy off → unenforced, all rows).\n const tenantScope = resolveDispatchTenantScope();\n\n // The feed lives in the project's database; anchor on the\n // ${anchorClassName} collection to reuse its configured connection.\n const collection = await getCollection('${anchorClassName}');\n const releaseSubscriberSlot = tryReserveChangeEventSubscriberSlot(collection.db${maxSubscribersArg});\n if (!releaseSubscriberSlot) {\n${corsBlock.capacityReturn}\n }\n return new Response(\n buildChangeEventStream(collection.db, {\n cursor,\n tenantScope,\n manifestHash: MANIFEST_HASH,\n releaseSubscriberSlot,\n locals,\n request,\n }),\n {\n status: 200,\n headers: {\n ${corsBlock.streamHeadersSpread}'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache, no-transform',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n },\n },\n );\n};\n${corsBlock.optionsHandler}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,oBACd,aACA,UACA,SACA,iBACS;CACT,IAAI,QAAQ,aAAa,YAAY,OACnC,OAAO;CAGT,MAAM,kBAAkB,uBAAuB,QAAQ;CACvD,IAAI,CAAC,iBAAiB;EACpB,QAAQ,IACN,2EACF;EACA,OAAO;CACT;CAEA,MAAM,WAAW,KAAK,aAAa,QAAQ,WAAW,SAAS;CAC/D,MAAM,UAAU,4BACd,iBACA,8BAA8B,QAAQ,GACtC,iBACA,QAAQ,aAAa,gBACrB,wBAAwB,QAAQ,aAAa,cAAc,GAC3D,QAAQ,aAAa,qBAAqB,MAC1C,6BAA6B,aAAa,UAAU,OAAO,CAC7D;CAEA,IAAI,CAAC,WAAW,QAAQ,GACtB,UAAU,UAAU,EAAE,WAAW,KAAK,CAAC;CAEzC,MAAM,WAAW,KAAK,UAAU,YAAY;CAC5C,cAAc,UAAU,SAAS,OAAO;CACxC,QAAQ,IAAI,qBAAqB,UAAU;CAC3C,OAAO;AACT;;;;;;AAOA,SAAS,wBACP,SACsB;CACtB,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO,KAAA;CACpC,MAAM,UAAU,CACd,GAAG,IAAI,IACL,QACG,QAAQ,MAAmB,OAAO,MAAM,QAAQ,CAAC,CACjD,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,CAAC,CAC/B,CACF;CACA,OAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;AACxC;;;;;;;;;;AAWA,SAAS,qBACP,gBACA,kBAMA;CAuDA,OAAO;EAAE,SAAA;;;;;;;;uCAtDc,KAAK,UAAU,cAYD,EAAe;;;;;;;;MAX5B,mBACpB,8DACA,GAiBgB;;;;;;;;;;;EAkCF,qBAAA;EAAqB,gBAAA;EAAgB,gBAAA;;;;;;;;;;;;;;;;;;;;CAAe;AACxE;AAEA,SAAS,4BACP,iBACA,cACA,cACA,gBACA,gBACA,kBACA,cACQ;CACR,MAAM,2BACJ,mBAAmB,KAAA,KACnB,OAAO,SAAS,cAAc,KAC9B,kBAAkB,IACd,KAAK,MAAM,cAAc,IACzB,KAAA;CACN,MAAM,oBACJ,6BAA6B,KAAA,IACzB,KACA,KAAK,KAAK,UAAU,wBAAwB;CAClD,MAAM,sBACJ,iBAAiB,KAAA,IAAY,cAAc,KAAK,UAAU,YAAY;CAKxE,MAAM,YADc,mBAAmB,KAAA,IAEnC,qBAAqB,gBAAgB,gBAAgB,IACrD;EACE,SAAS;EACT,qBAAqB;EACrB,gBAAgB;EAChB,gBAAgB;CAClB;CACJ,MAAM,eAAe,eACjB;;;;;;;;;;;;;;IAeA;CACJ,MAAM,aAAa,eAAe,wCAAwC;CAE1E,OAAO,GAAG,4BAA4B;;;;;;;;;;;;;;;;;iCAiBP,aAAa;;;wBAGtB,oBAAoB;EAC1C,UAAU,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoClB,aAAa;;6BAEc,WAAW;;;;;;;;;;;OAWjC,gBAAgB;4CACqB,gBAAgB;mFACuB,kBAAkB;;EAEnG,UAAU,eAAe;;;;;;;;;;;;;;UAcjB,UAAU,oBAAoB;;;;;;;;EAQtC,UAAU;AACZ"}
|
|
1
|
+
{"version":3,"file":"events-route.js","names":[],"sources":["../../src/vite-plugin/events-route.ts"],"sourcesContent":["/**\n * SvelteKit `_events` route generation — live change signals (issue #1763,\n * SERVER half; parent PRD #1755).\n *\n * Emits `{routesDir}/_events/+server.ts` — an auth-guarded, tenant-scoped\n * Server-Sent-Events endpoint that streams coarse change signals (no row\n * payloads). It is the push companion to the generated `_changes` route\n * (#1758). Kept in its own module so `sveltekit-generator.ts` only carries a\n * one-line registration.\n *\n * The stream lifecycle (subscribe, catch-up, heartbeat, teardown) lives in\n * core's `buildChangeEventStream`, imported by the generated file — so the\n * emitted route stays THIN and the stream logic is written and tested once.\n *\n * Design notes (mirror `changes-route.ts`):\n * - **Fail-closed auth** (#1540 posture): the handler requires an authenticated\n * principal on `locals`. The signal stream spans every table, so per-model\n * `api: { public }` opt-outs deliberately do not apply.\n * - **Tenant scoping**: when the project has tenant-scoped objects, the route\n * establishes tenant context from `locals` exactly like the `_changes` route,\n * then captures the resolved scope ONCE at connection open and passes it to\n * the stream (delivery filters against that fixed value — it cannot\n * re-resolve per signal outside the request's ALS context).\n * - **Table/row authorization** (#3020): `locals` is forwarded to\n * `buildChangeEventStream`, which — if the consumer registered\n * `authorizeChangeFeed`/`isChangeFeedEntryVisible`\n * (`@happyvertical/smrt-core`'s `change-feed-authz.ts`) — resolves the\n * allowed tables ONCE at connection open (same reasoning as tenant scope)\n * and applies both hooks to catch-up replay AND every live signal before it\n * is enqueued. Unregistered, behavior is unchanged from pre-#3020.\n * - **Database resolution**: anchors on the project's first generated\n * collection (alphabetical) via `getCollection()`, exactly as `_changes`.\n * - Cleanup rides the generated-route sweep: the file starts with\n * {@link AUTO_GENERATED_ROUTE_HEADER}.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { SmartObjectManifest } from '../scanner/types';\nimport {\n manifestHasTenantScopedObject,\n resolveAnchorClassName,\n} from './changes-route.js';\nimport { AUTO_GENERATED_ROUTE_HEADER } from './route-header.js';\nimport { generateCollectionAccessImports } from './sveltekit-config-import.js';\nimport type { SvelteKitOptions } from './sveltekit-generator.js';\n\n/**\n * Generate the `_events/+server.ts` route. Returns true when a route was\n * written. Disabled with `sveltekit: { eventsRoute: { enabled: false } }`;\n * skipped (with a log line) when the manifest has no objects to anchor on.\n */\nexport function generateEventsRoute(\n projectRoot: string,\n manifest: SmartObjectManifest,\n options: SvelteKitOptions,\n webManifestHash?: string,\n): boolean {\n if (options.eventsRoute?.enabled === false) {\n return false;\n }\n\n const anchorClassName = resolveAnchorClassName(manifest);\n if (!anchorClassName) {\n console.log(\n '[smrt] Skipping _events route - no SMRT objects to anchor the database on',\n );\n return false;\n }\n\n const routeDir = join(projectRoot, options.routesDir, '_events');\n const content = generateEventsRouteTemplate(\n anchorClassName,\n manifestHasTenantScopedObject(manifest),\n webManifestHash,\n options.eventsRoute?.maxSubscribers,\n normalizeAllowedOrigins(options.eventsRoute?.allowedOrigins),\n options.eventsRoute?.allowCredentials === true,\n generateCollectionAccessImports(projectRoot, routeDir, options, [\n 'getCollection',\n ]),\n );\n\n if (!existsSync(routeDir)) {\n mkdirSync(routeDir, { recursive: true });\n }\n const filePath = join(routeDir, '+server.ts');\n writeFileSync(filePath, content, 'utf-8');\n console.log(`[smrt] Generated: ${filePath}`);\n return true;\n}\n\n/**\n * Trim, de-duplicate, and drop empty entries from a configured origin\n * allowlist. An `undefined`/empty result means \"same-origin only\" — the\n * generated route emits no CORS surface at all (fail-closed default, #1861).\n */\nfunction normalizeAllowedOrigins(\n origins: string[] | undefined,\n): string[] | undefined {\n if (!Array.isArray(origins)) return undefined;\n const cleaned = [\n ...new Set(\n origins\n .filter((o): o is string => typeof o === 'string')\n .map((o) => o.trim())\n .filter((o) => o.length > 0),\n ),\n ];\n return cleaned.length > 0 ? cleaned : undefined;\n}\n\n/**\n * Build the CORS fragments injected into the generated `_events` route (#1861)\n * when an origin allowlist is configured. Mirrors the runtime REST generator's\n * posture: the request `Origin` is echoed only when it is on the allowlist\n * (never `*`), `Vary: Origin` is always set on a matched response, and\n * `Access-Control-Allow-Credentials: true` is added only when credentials are\n * opted in — so cookies flow to an allow-listed cross-origin `EventSource`\n * while an unmatched origin gets no CORS surface (fail-closed).\n */\nfunction buildEventsCorsBlock(\n allowedOrigins: string[],\n allowCredentials: boolean,\n): {\n helpers: string;\n streamHeadersSpread: string;\n capacityReturn: string;\n optionsHandler: string;\n} {\n const originsLiteral = JSON.stringify(allowedOrigins);\n const credentialsLine = allowCredentials\n ? \"\\n headers['Access-Control-Allow-Credentials'] = 'true';\"\n : '';\n const helpers = `\n// Credentialed cross-origin CORS (#1861). Opt-in via\n// \\`sveltekit.eventsRoute.allowedOrigins\\` (+ \\`allowCredentials\\`); an unmatched or\n// absent Origin gets no CORS surface, so this route stays same-origin only by\n// default. The Origin is echoed only when allow-listed (never \\`*\\`), which is\n// what makes credentialed CORS safe. CORS never authorizes — the fail-closed\n// \\`requireRouteAuth\\` guard below is unchanged; CORS only lets an allow-listed\n// browser's cookies reach it.\nconst CORS_ALLOWED_ORIGINS = new Set(${originsLiteral});\n\nfunction eventsCorsHeaders(request: Request): Record<string, string> {\n const origin = request.headers.get('origin');\n if (!origin || !CORS_ALLOWED_ORIGINS.has(origin)) return {};\n const headers: Record<string, string> = {\n 'Access-Control-Allow-Origin': origin,\n Vary: 'Origin',\n };${credentialsLine}\n return headers;\n}\n\nfunction applyEventsCors(response: Response, request: Request): Response {\n for (const [key, value] of Object.entries(eventsCorsHeaders(request))) {\n response.headers.set(key, value);\n }\n return response;\n}\n`;\n const streamHeadersSpread = '...eventsCorsHeaders(request),\\n ';\n const capacityReturn =\n ' return applyEventsCors(eventStreamCapacityExceededResponse(), request);';\n const optionsHandler = `\n// Preflight for a credentialed cross-origin subscription. \\`EventSource\\` GETs\n// are \"simple\" requests (no preflight), but a fetch-based SSE client may\n// preflight — answer it for allow-listed origins only.\nexport const OPTIONS: RequestHandler = async ({ request }) => {\n const headers = eventsCorsHeaders(request);\n if (!('Access-Control-Allow-Origin' in headers)) {\n return new Response(null, { status: 403 });\n }\n return new Response(null, {\n status: 204,\n headers: {\n ...headers,\n 'Access-Control-Allow-Methods': 'GET,OPTIONS',\n 'Access-Control-Allow-Headers': 'Authorization,Last-Event-ID,Content-Type',\n 'Access-Control-Max-Age': '86400',\n },\n });\n};\n`;\n return { helpers, streamHeadersSpread, capacityReturn, optionsHandler };\n}\n\nfunction generateEventsRouteTemplate(\n anchorClassName: string,\n tenantScoped: boolean,\n manifestHash: string | undefined,\n maxSubscribers: number | undefined,\n allowedOrigins: string[] | undefined,\n allowCredentials: boolean,\n collectionAccessImports: string,\n): string {\n const configuredMaxSubscribers =\n maxSubscribers !== undefined &&\n Number.isFinite(maxSubscribers) &&\n maxSubscribers >= 0\n ? Math.floor(maxSubscribers)\n : undefined;\n const maxSubscribersArg =\n configuredMaxSubscribers === undefined\n ? ''\n : `, ${JSON.stringify(configuredMaxSubscribers)}`;\n const manifestHashLiteral =\n manifestHash === undefined ? 'undefined' : JSON.stringify(manifestHash);\n // CORS is emitted only when an explicit allowlist is configured (#1861).\n // Without one the generated route carries no CORS surface — same-origin only,\n // byte-for-byte the pre-#1861 output.\n const corsEnabled = allowedOrigins !== undefined;\n const corsBlock = corsEnabled\n ? buildEventsCorsBlock(allowedOrigins, allowCredentials)\n : {\n helpers: '',\n streamHeadersSpread: '',\n capacityReturn: ' return eventStreamCapacityExceededResponse();',\n optionsHandler: '',\n };\n const tenantHelper = tenantScoped\n ? `\nimport { enterTenantContext, hasTenantContext } from '@happyvertical/smrt-tenancy';\n\nfunction establishTenantContext(locals: unknown): void {\n if (hasTenantContext()) return;\n if (!locals || typeof locals !== 'object') return;\n const l = locals as Record<string, unknown>;\n const user = l.user as Record<string, unknown> | undefined;\n const session = l.session as Record<string, unknown> | undefined;\n const tenantId = l.tenantId ?? user?.tenantId ?? session?.tenantId;\n if (typeof tenantId === 'string' && tenantId) {\n enterTenantContext({ tenantId });\n }\n}\n`\n : '';\n const tenantCall = tenantScoped ? '\\n establishTenantContext(locals);' : '';\n\n return `${AUTO_GENERATED_ROUTE_HEADER}\n// DO NOT EDIT - changes will be overwritten\n//\n// GET /_events — Server-Sent-Events stream of coarse change signals (#1763).\n// Part of the client/mobile sync contract: each event names {table, operation,\n// rowId, tenantId} (NEVER a row payload — authorization stays on the read path)\n// and carries a seq cursor in the SSE id: field. A reconnecting EventSource\n// resends Last-Event-ID; the route replays missed changes from it, then streams\n// live. Same-origin only unless an origin allowlist is configured (#1861).\n\nimport { error } from '@sveltejs/kit';\nimport {\n buildChangeEventStream,\n eventStreamCapacityExceededResponse,\n resolveDispatchTenantScope,\n tryReserveChangeEventSubscriberSlot,\n} from '@happyvertical/smrt-core';\nimport type { RequestHandler } from './$types';\n${collectionAccessImports}\n\nconst MANIFEST_HASH = ${manifestHashLiteral};\n${corsBlock.helpers}\n// Fail-closed authorization (#1540): the signal stream spans every table, so\n// it is never public — an authenticated principal on \\`locals\\` is required.\nfunction hasAuthenticatedPrincipal(locals: unknown): boolean {\n if (!locals || typeof locals !== 'object') return false;\n const l = locals as Record<string, unknown>;\n const isResolvedPrincipal = (v: unknown) =>\n typeof v === 'object' && v !== null;\n return (\n isResolvedPrincipal(l.user) ||\n isResolvedPrincipal(l.session) ||\n l.smrtAuth === true\n );\n}\n\nfunction requireRouteAuth(locals: unknown): void {\n if (!hasAuthenticatedPrincipal(locals)) {\n throw error(401, 'Authentication required');\n }\n}\n\n// Last-Event-ID (auto-reconnect) takes precedence over ?since=; default is\n// live-forward only (no catch-up replay).\nfunction parseCursor(request: Request, url: URL): number | null {\n const lastEventId = request.headers.get('Last-Event-ID');\n if (lastEventId !== null && lastEventId.trim() !== '') {\n const n = Number(lastEventId);\n if (Number.isFinite(n) && n >= 0) return Math.floor(n);\n }\n const since = url.searchParams.get('since');\n if (since !== null && since.trim() !== '') {\n const n = Number(since);\n if (Number.isFinite(n) && n >= 0) return Math.floor(n);\n }\n return null;\n}\n${tenantHelper}\nexport const GET: RequestHandler = async ({ locals, url, request }) => {\n requireRouteAuth(locals);${tenantCall}\n\n const cursor = parseCursor(request, url);\n // Capture the tenant scope ONCE at connection open — signal delivery runs\n // outside this request's tenant context and must filter against a fixed\n // value, not a per-signal re-resolution. resolveDispatchTenantScope() reads\n // the context established above (fail-closed: tenancy on + no tenant → global\n // rows only; tenancy off → unenforced, all rows).\n const tenantScope = resolveDispatchTenantScope();\n\n // The feed lives in the project's database; anchor on the\n // ${anchorClassName} collection to reuse its configured connection.\n const collection = await getCollection('${anchorClassName}');\n const releaseSubscriberSlot = tryReserveChangeEventSubscriberSlot(collection.db${maxSubscribersArg});\n if (!releaseSubscriberSlot) {\n${corsBlock.capacityReturn}\n }\n return new Response(\n buildChangeEventStream(collection.db, {\n cursor,\n tenantScope,\n manifestHash: MANIFEST_HASH,\n releaseSubscriberSlot,\n locals,\n request,\n }),\n {\n status: 200,\n headers: {\n ${corsBlock.streamHeadersSpread}'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache, no-transform',\n Connection: 'keep-alive',\n 'X-Accel-Buffering': 'no',\n },\n },\n );\n};\n${corsBlock.optionsHandler}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,oBACd,aACA,UACA,SACA,iBACS;CACT,IAAI,QAAQ,aAAa,YAAY,OACnC,OAAO;CAGT,MAAM,kBAAkB,uBAAuB,QAAQ;CACvD,IAAI,CAAC,iBAAiB;EACpB,QAAQ,IACN,2EACF;EACA,OAAO;CACT;CAEA,MAAM,WAAW,KAAK,aAAa,QAAQ,WAAW,SAAS;CAC/D,MAAM,UAAU,4BACd,iBACA,8BAA8B,QAAQ,GACtC,iBACA,QAAQ,aAAa,gBACrB,wBAAwB,QAAQ,aAAa,cAAc,GAC3D,QAAQ,aAAa,qBAAqB,MAC1C,gCAAgC,aAAa,UAAU,SAAS,CAC9D,eACF,CAAC,CACH;CAEA,IAAI,CAAC,WAAW,QAAQ,GACtB,UAAU,UAAU,EAAE,WAAW,KAAK,CAAC;CAEzC,MAAM,WAAW,KAAK,UAAU,YAAY;CAC5C,cAAc,UAAU,SAAS,OAAO;CACxC,QAAQ,IAAI,qBAAqB,UAAU;CAC3C,OAAO;AACT;;;;;;AAOA,SAAS,wBACP,SACsB;CACtB,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO,KAAA;CACpC,MAAM,UAAU,CACd,GAAG,IAAI,IACL,QACG,QAAQ,MAAmB,OAAO,MAAM,QAAQ,CAAC,CACjD,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,CACpB,QAAQ,MAAM,EAAE,SAAS,CAAC,CAC/B,CACF;CACA,OAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;AACxC;;;;;;;;;;AAWA,SAAS,qBACP,gBACA,kBAMA;CAuDA,OAAO;EAAE,SAAA;;;;;;;;uCAtDc,KAAK,UAAU,cAYD,EAAe;;;;;;;;MAX5B,mBACpB,8DACA,GAiBgB;;;;;;;;;;;EAkCF,qBAAA;EAAqB,gBAAA;EAAgB,gBAAA;;;;;;;;;;;;;;;;;;;;CAAe;AACxE;AAEA,SAAS,4BACP,iBACA,cACA,cACA,gBACA,gBACA,kBACA,yBACQ;CACR,MAAM,2BACJ,mBAAmB,KAAA,KACnB,OAAO,SAAS,cAAc,KAC9B,kBAAkB,IACd,KAAK,MAAM,cAAc,IACzB,KAAA;CACN,MAAM,oBACJ,6BAA6B,KAAA,IACzB,KACA,KAAK,KAAK,UAAU,wBAAwB;CAClD,MAAM,sBACJ,iBAAiB,KAAA,IAAY,cAAc,KAAK,UAAU,YAAY;CAKxE,MAAM,YADc,mBAAmB,KAAA,IAEnC,qBAAqB,gBAAgB,gBAAgB,IACrD;EACE,SAAS;EACT,qBAAqB;EACrB,gBAAgB;EAChB,gBAAgB;CAClB;CACJ,MAAM,eAAe,eACjB;;;;;;;;;;;;;;IAeA;CACJ,MAAM,aAAa,eAAe,wCAAwC;CAE1E,OAAO,GAAG,4BAA4B;;;;;;;;;;;;;;;;;;EAkBtC,wBAAwB;;wBAEF,oBAAoB;EAC1C,UAAU,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoClB,aAAa;;6BAEc,WAAW;;;;;;;;;;;OAWjC,gBAAgB;4CACqB,gBAAgB;mFACuB,kBAAkB;;EAEnG,UAAU,eAAe;;;;;;;;;;;;;;UAcjB,UAAU,oBAAoB;;;;;;;;EAQtC,UAAU;AACZ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EACV,2BAA2B,EAC3B,qBAAqB,EAEtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,MAAM,EAAiC,MAAM,MAAM,CAAC;AAElE,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,2BAA2B,CAAC;AAQnC,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACd,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite-plugin/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,7 @@ import { loadVerifiedSmrtGenerationSnapshot, serializeSmrtGenerationSnapshot, sh
|
|
|
11
11
|
import { renderApiClientCrudType, renderApiClientCustomMethodParameters, selectApiClientEntries } from "./api-client-entries.js";
|
|
12
12
|
import { GENERATED_CLIENT_BASE_METHODS, GENERATED_CLIENT_CRUD_METHODS, generateClientModule } from "./generated-client.js";
|
|
13
13
|
import { importBuildAwareModule } from "./import-build-aware.js";
|
|
14
|
+
import { injectSvelteKitRegistration } from "./sveltekit-register-injection.js";
|
|
14
15
|
import { assertSvelteKitRouteCoordinationComplete, contributeSvelteKitRoutes, expectedSvelteKitRouteOwners, markSvelteKitRouteParticipant } from "./sveltekit-route-coordinator.js";
|
|
15
16
|
import { buildWorkerRegistration } from "./worker-registration.js";
|
|
16
17
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -541,7 +542,18 @@ function smrtPlugin(options = {}) {
|
|
|
541
542
|
if (activeBuildStart === build) activeBuildStart = void 0;
|
|
542
543
|
}
|
|
543
544
|
},
|
|
544
|
-
transform(
|
|
545
|
+
transform(code, id) {
|
|
546
|
+
if (svelteKit.enabled && !config?.build?.lib) {
|
|
547
|
+
const injected = injectSvelteKitRegistration(code, id, {
|
|
548
|
+
projectRoot: configuredProjectRoot ?? projectRoot,
|
|
549
|
+
configPath: svelteKit.configPath,
|
|
550
|
+
configFileName: svelteKit.configFileName
|
|
551
|
+
});
|
|
552
|
+
return injected === null ? null : {
|
|
553
|
+
code: injected,
|
|
554
|
+
map: null
|
|
555
|
+
};
|
|
556
|
+
}
|
|
545
557
|
if (!config?.build?.lib || !manifest) return null;
|
|
546
558
|
if (!isRegisterShimModuleId(id)) return null;
|
|
547
559
|
return {
|