@notionx/core 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/rate-limit.js.map +1 -1
- package/dist/auth/routes/google-callback.js.map +1 -1
- package/dist/auth/routes/google.js.map +1 -1
- package/dist/auth/routes/index.js.map +1 -1
- package/dist/auth/routes/verify-email.js.map +1 -1
- package/dist/auth/routes/viewer.js.map +1 -1
- package/dist/auth/turnstile.js.map +1 -1
- package/dist/auth/user-session.d.ts +1 -1
- package/dist/auth/user-session.js.map +1 -1
- package/dist/auth/users.js.map +1 -1
- package/dist/content/index.d.ts +2 -2
- package/dist/content/index.js +8 -60
- package/dist/content/index.js.map +1 -1
- package/dist/content/revalidate.d.ts +2 -1
- package/dist/content/revalidate.js +5 -28
- package/dist/content/revalidate.js.map +1 -1
- package/dist/content/search-index.d.ts +1 -1
- package/dist/content/search-index.js.map +1 -1
- package/dist/content/search.d.ts +2 -5
- package/dist/content/search.js +3 -32
- package/dist/content/search.js.map +1 -1
- package/dist/email/index.js.map +1 -1
- package/dist/{env-C5qu-0R-.d.ts → env-hoez1e-n.d.ts} +0 -4
- package/dist/i18n/index.d.ts +18 -24
- package/dist/i18n/index.js +29 -54
- package/dist/i18n/index.js.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/admin/index.js.map +1 -1
- package/dist/media/index.js +3 -2
- package/dist/media/index.js.map +1 -1
- package/dist/media/routes/index.js +0 -1
- package/dist/media/routes/index.js.map +1 -1
- package/dist/media/routes/notion-media.js +0 -1
- package/dist/media/routes/notion-media.js.map +1 -1
- package/dist/notion/config.d.ts +1 -4
- package/dist/notion/config.js +1 -23
- package/dist/notion/config.js.map +1 -1
- package/dist/notion/content-cache.d.ts +1 -1
- package/dist/notion/generic-source.js +0 -1
- package/dist/notion/generic-source.js.map +1 -1
- package/dist/notion/index.d.ts +3 -3
- package/dist/notion/index.js +1 -23
- package/dist/notion/index.js.map +1 -1
- package/dist/notion/media.d.ts +1 -1
- package/dist/notion/media.js +1 -1
- package/dist/notion/media.js.map +1 -1
- package/dist/notion/routes/index.d.ts +1 -1
- package/dist/notion/routes/index.js +0 -1
- package/dist/notion/routes/index.js.map +1 -1
- package/dist/notion/routes/webhook.d.ts +1 -1
- package/dist/notion/routes/webhook.js +0 -1
- package/dist/notion/routes/webhook.js.map +1 -1
- package/dist/notion/types.d.ts +1 -73
- package/dist/notion/webhook.d.ts +1 -1
- package/dist/notion/webhook.js +0 -1
- package/dist/notion/webhook.js.map +1 -1
- package/dist/pages/index.js +0 -1
- package/dist/pages/index.js.map +1 -1
- package/dist/platform/current.d.ts +1 -1
- package/dist/platform/current.js.map +1 -1
- package/dist/platform/index.d.ts +1 -1
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/runtime.d.ts +1 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/routes/cdn.js.map +1 -1
- package/dist/storage/routes/files.js.map +1 -1
- package/dist/storage/routes/index.js.map +1 -1
- package/dist/util/index.d.ts +1 -1
- package/dist/util/index.js +1 -2
- package/dist/util/index.js.map +1 -1
- package/dist/worker/index.js +0 -1
- package/dist/worker/index.js.map +1 -1
- package/dist/worker/routes/content-revalidate.d.ts +1 -1
- package/dist/worker/routes/health.js.map +1 -1
- package/dist/worker/routes/index.d.ts +1 -1
- package/dist/worker/routes/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/content/models.ts","../src/middleware.ts","../src/storage/routes/files.ts","../src/util/env.ts","../src/platform/runtime.ts","../src/platform/cloudflare-runtime.ts","../src/platform/selection.ts","../src/platform/current.ts","../src/storage/routes/cdn.ts","../src/media/routes/notion-media.ts","../src/cache/cache-keys.ts","../src/notion/client.ts","../src/notion/config.ts","../src/notion/media.ts","../src/worker/routes/health.ts","../src/internal/admin/schema-guard.ts","../src/worker/bootstrap.ts","../src/platform/capabilities.ts","../src/doctor/doctor.ts","../src/doctor/index.ts"],"sourcesContent":["// packages/nextion/src/content/models.ts\n//\n// Canonical content-source shape and a module-level registry.\n//\n// The starter's `defineContentModel` returns the same value passed in.\n// The foundation's `defineContentSource` adds a side effect: it stores\n// the value in a process-wide registry so other packages (admin pages,\n// search index, revalidation) can discover content sources without\n// reaching back into the starter.\n//\n// Existing values from the starter pass through unchanged: `ContentSource`\n// is a structural alias of the prior `ContentModelDefinition<TFields>` so\n// source field-maps stay narrowly typed through the boundary.\n\nimport type {\n NotionFieldMap,\n NotionSort,\n NotionSortDirection,\n} from \"../notion/types\";\n\nexport type { NotionFieldMap, NotionSort, NotionSortDirection };\n\n/**\n * Canonical content-source shape. The shape mirrors the starter's\n * prior `ContentModelDefinition` exactly so that registered sources\n * remain type-compatible across the package boundary.\n */\nexport type ContentModelDefinition<\n TFields extends NotionFieldMap = NotionFieldMap,\n> = {\n id: string;\n kind: \"article\" | \"catalog\" | \"directory\";\n visibility: {\n public: boolean;\n admin: boolean;\n };\n source: {\n type: \"notion\";\n tokenEnv: \"NOTION_TOKEN\";\n dataSourceEnv: string;\n defaultDataSourceId?: string;\n fields: TFields;\n query: {\n pageSize: number;\n sorts?: readonly NotionSort[];\n filterProperties?: readonly string[];\n };\n };\n routes: {\n listPath: string;\n detailPath: string;\n detailParam: string;\n publicApiPath?: string;\n };\n ui: {\n name: string;\n pluralName: string;\n navLabel: string;\n listTitle: string;\n listDescription: string;\n emptyState: string;\n };\n capabilities: {\n richBlocks: boolean;\n coverImages: boolean;\n gatedAssets: boolean;\n };\n};\n\n/**\n * Public alias for `ContentModelDefinition`. External consumers import\n * this name from `@notionx/core/content`; the internal\n * `ContentModelDefinition` name remains available for the starter's\n * `model.ts`.\n */\nexport type ContentSource<\n TFields extends NotionFieldMap = NotionFieldMap,\n> = ContentModelDefinition<TFields>;\n\nconst registry: ContentSource[] = [];\n\n/**\n * Register a content source. Returns the value unchanged. Re-registering\n * the same `id` replaces the prior value (idempotent on the id, useful\n * for HMR + tests).\n */\nexport function defineContentSource<const TFields extends NotionFieldMap>(\n model: ContentModelDefinition<TFields>\n): ContentModelDefinition<TFields> {\n const existing = registry.findIndex((s) => s.id === model.id);\n if (existing >= 0) registry[existing] = model;\n else registry.push(model);\n return model;\n}\n\nexport function getRegisteredSources(): readonly ContentSource[] {\n return registry;\n}\n\nexport function getRegisteredSource(id: string): ContentSource | undefined {\n return registry.find((s) => s.id === id);\n}\n\n/**\n * Test-only escape hatch: empties the registry so vitest cases do not\n * leak state between files. Not for production use.\n */\nexport function clearRegistryForTests(): void {\n registry.length = 0;\n}\n","// Foundation request-scoped middleware.\n//\n// Responsibilities:\n// 1. Read the session cookie named in `authConfig.sessionCookie.name`.\n// 2. Look up the session row in D1 (via the optional `sessionLookup`\n// injection; falls back to a no-op when the consumer has not\n// provided one).\n// 3. Attach a viewer to a request-scoped context (a WeakMap keyed\n// by the Request instance) so downstream handlers can read it\n// without reaching into globals.\n// 4. For protected admin patterns (`/api/admin/*` for any method,\n// and non-GET requests to `/admin*`), short-circuit with a 401\n// when there is no viewer.\n//\n// This module is intentionally tier-7 (top-level). It can reach into\n// the auth tier because the eslint tier rules reserve that direction\n// for higher-level glue code.\n\nimport type { AuthConfig } from \"./types\";\n\n/**\n * Minimal viewer shape exposed by the foundation middleware. Phase 3\n * replaces this placeholder with the richer `AuthViewer` once\n * `createAuth` ships its real implementation.\n */\nexport interface FoundationViewer {\n userId: string | number;\n role: string | null;\n email: string | null;\n}\n\n/**\n * Resolves a session id to the user/role pair backing the viewer.\n * Callers can inject a custom lookup so they can read from their own\n * sessions table layout (the foundation only knows the table name).\n */\nexport type SessionLookup = (\n sessionId: string,\n env: unknown\n) => Promise<{ userId: string | number; role: string | null; email?: string | null } | null>;\n\n/**\n * Caller-supplied options. `WorkerOptions.siteConfig` etc. are passed\n * through the bootstrap; the middleware itself only needs the auth\n * block and an optional viewer lookup.\n */\nexport interface NextionMiddlewareOptions {\n authConfig: AuthConfig;\n /**\n * Optional session resolver. When provided, the middleware uses\n * this to look up the session row. When omitted, the middleware\n * still runs (to enforce the admin gate) but always reports no\n * viewer — useful for the bootstrap's test environment and for\n * consumers that have not yet wired their D1 session table.\n */\n sessionLookup?: SessionLookup;\n /**\n * Override which paths are gated. The default is nextion's\n * own contract: `/api/admin/*` for any method, and non-GET\n * requests to `/admin*` (GET `/admin*` is left for the app\n * router so the admin shell can render a login redirect).\n */\n isProtectedPath?: (request: Request) => boolean;\n}\n\nexport interface NextionMiddlewareRequestContext {\n viewer: FoundationViewer | null;\n sessionId: string | null;\n}\n\nconst requestContext = new WeakMap<Request, NextionMiddlewareRequestContext>();\n\n/**\n * Default admin gate. Returns `true` for paths that require a viewer\n * before any handler runs. Excludes the GET form of `/admin*` so the\n * app router can render the admin shell and decide whether to\n * redirect to `/login` (matching the existing behavior).\n */\nexport function defaultIsProtectedPath(request: Request): boolean {\n const url = new URL(request.url);\n if (url.pathname.startsWith(\"/api/admin/\")) return true;\n if (url.pathname.startsWith(\"/admin\")) {\n return request.method !== \"GET\" && request.method !== \"HEAD\";\n }\n return false;\n}\n\nfunction readSessionCookie(request: Request, name: string): string | null {\n // The Cookie header may be absent in pure unit tests. Guard so the\n // middleware never throws.\n const header = request.headers.get(\"cookie\");\n if (!header) return null;\n\n for (const part of header.split(\";\")) {\n const [rawKey, ...rest] = part.split(\"=\");\n if (!rawKey) continue;\n if (rawKey.trim() !== name) continue;\n return rest.join(\"=\").trim() || null;\n }\n return null;\n}\n\n/**\n * Build the request-scoped context for `request`. Returns a viewer\n * (or `null` when no session is present / no lookup is configured).\n */\nexport async function resolveFoundationViewer(\n request: Request,\n options: NextionMiddlewareOptions\n): Promise<NextionMiddlewareRequestContext> {\n const sessionId = readSessionCookie(\n request,\n options.authConfig.sessionCookie.name\n );\n\n if (!sessionId || !options.sessionLookup) {\n return { viewer: null, sessionId: sessionId ?? null };\n }\n\n const env = (request as Request & { env?: unknown }).env ?? null;\n const lookup = await options.sessionLookup(sessionId, env);\n if (!lookup) {\n return { viewer: null, sessionId };\n }\n\n return {\n viewer: {\n userId: lookup.userId,\n role: lookup.role,\n email: lookup.email ?? null,\n },\n sessionId,\n };\n}\n\n/**\n * Read the cached context for a request. Returns `undefined` when\n * `resolveFoundationViewer` was never run for this Request — i.e.\n * the request did not pass through `nextionMiddleware`.\n */\nexport function getFoundationContext(\n request: Request\n): NextionMiddlewareRequestContext | undefined {\n return requestContext.get(request);\n}\n\n/**\n * Worker-style middleware. Returns a 401 `Response` for protected\n * paths that lack a viewer; otherwise `null` to let the request\n * continue through the route table.\n *\n * Side effect: attaches a `NextionMiddlewareRequestContext` to\n * the request via a `WeakMap` so downstream route handlers can\n * resolve the viewer without a global.\n */\nexport async function nextionMiddleware(\n request: Request,\n env: unknown,\n options: NextionMiddlewareOptions\n): Promise<Response | null> {\n const context = await resolveFoundationViewer(request, options);\n requestContext.set(request, context);\n\n const isProtected = options.isProtectedPath ?? defaultIsProtectedPath;\n if (!isProtected(request)) return null;\n if (context.viewer) return null;\n\n return new Response(\n JSON.stringify({ ok: false, error: \"Unauthorized\" }),\n {\n status: 401,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"no-store\",\n \"X-Foundation-Gate\": \"admin\",\n },\n }\n );\n}\n\n/**\n * Build a `(request, env) => Promise<Response | null>` middleware\n * function with the options pre-bound. Used by the bootstrap and\n * the starter's Next.js middleware so they share one code path.\n */\nexport function createNextionMiddleware(\n options: NextionMiddlewareOptions\n): (request: Request, env: unknown) => Promise<Response | null> {\n return (request, env) => nextionMiddleware(request, env, options);\n}\n\n/**\n * Test-only: clear the request-scoped context cache. Production\n * code should not need this — the WeakMap is keyed by Request\n * instances and entries die with the request. The helper exists so\n * the unit tests can stay deterministic when they reuse a single\n * Request across assertions.\n */\nexport function _clearFoundationContextForTests(): void {\n // WeakMap has no `clear`, but we can replace the reference.\n // The function is intentionally a no-op when no entries exist.\n void requestContext;\n}\n","// storage/routes/files.ts\n//\n// GET /api/files/[...key] - 从对象存储取文件并代理返回\n// 不公开 bucket/container,避免任何人都能列文件\n//\n// The package exports a route object with two surfaces:\n// - `GET` is the Next.js handler signature (request, { params }).\n// - `handle` is a single `(request) => Response` form used by the\n// Cloudflare Workers bootstrap in `@notionx/core/worker`.\n//\n// The bodies are identical; only the way the object key is read differs.\n\nimport { NextResponse } from \"next/server\";\nimport { getRuntimePlatform } from \"../../platform/current\";\n\nexport const dynamic = \"force-dynamic\";\n\ntype Props = {\n params: Promise<{ key: string[] }>;\n};\n\nexport const filesRoute = {\n /**\n * Next.js handler for `app/api/files/[...key]/route.ts`. Receives the\n * catch-all key from the route params.\n */\n async GET(_request: Request, props: Props) {\n const { key } = await props.params;\n return filesRoute.handle(new Request(buildInternalUrl(_request, key)));\n },\n /**\n * Worker-friendly handler. Extracts the catch-all key from the URL\n * pathname (`/api/files/<key>`).\n */\n async handle(request: Request): Promise<Response> {\n const key = readKeyFromUrl(request.url);\n if (!key) {\n return NextResponse.json({ error: \"Invalid key\" }, { status: 400 });\n }\n\n if (key.includes(\"..\") || key.startsWith(\"/\")) {\n return NextResponse.json({ error: \"Invalid key\" }, { status: 400 });\n }\n\n const storage = getRuntimePlatform().objectStorage;\n if (!storage) {\n return NextResponse.json(\n { error: \"Object storage not configured\" },\n { status: 503 }\n );\n }\n\n const object = await storage.get(key);\n if (!object) {\n return NextResponse.json({ error: \"Not found\" }, { status: 404 });\n }\n\n const headers = new Headers();\n if (object.contentType) {\n headers.set(\"Content-Type\", object.contentType);\n }\n headers.set(\"Cache-Control\", \"public, max-age=31536000, immutable\");\n if (object.etag) headers.set(\"ETag\", object.etag);\n headers.set(\"Content-Length\", String(object.size));\n\n return new Response(object.body, { headers });\n },\n};\n\n/**\n * Worker-friendly single-arg handler. Used by the Cloudflare Workers\n * bootstrap in `@notionx/core/worker`. Equivalent to\n * `filesRoute.handle`.\n */\nexport async function filesRouteHandle(request: Request): Promise<Response> {\n return filesRoute.handle(request);\n}\n\nfunction buildInternalUrl(request: Request, keyParts: string[]) {\n const url = new URL(request.url);\n url.pathname = `/api/files/${keyParts.map(encodeURIComponent).join(\"/\")}`;\n return url.toString();\n}\n\nfunction readKeyFromUrl(rawUrl: string): string | null {\n const url = new URL(rawUrl);\n const prefix = \"/api/files/\";\n if (!url.pathname.startsWith(prefix)) return null;\n const encoded = url.pathname.slice(prefix.length);\n if (!encoded) return null;\n return decodeURIComponent(encoded);\n}\n\n// Re-export as top-level function aliases for callers that prefer a\n// flat signature (e.g. the Next.js app/api/.../route.ts delegates).\nexport const GET = filesRoute.GET;\n","// lib/env.ts - 集中获取 Cloudflare bindings\n// 用 cloudflare:workers 模块(workerd 内置),作为平台 adapter 的绑定入口\n\n/// <reference types=\"@cloudflare/workers-types\" />\nimport { env } from \"cloudflare:workers\";\n\nexport type AppEnv = {\n DB: D1Database;\n ASSETS: Fetcher;\n IMAGES: ImagesBinding;\n ASSETS_BUCKET?: R2Bucket;\n CONTENT_CACHE?: KVNamespace;\n ADMIN_PASSWORD: string;\n ADMIN_EMAIL?: string;\n SITE_URL?: string;\n RESEND_API_KEY?: string;\n RESEND_FROM?: string;\n // Google OAuth 仍然兼容 Cloudflare Secret 作为兜底。\n // 实际生效值以 app_settings.google_client_id / google_client_secret 为准。\n GOOGLE_CLIENT_ID?: string;\n GOOGLE_CLIENT_SECRET?: string;\n /** Turnstile site key fallback when not stored in app_settings */\n TURNSTILE_SITE_KEY?: string;\n /** Turnstile secret — set via `wrangler secret put TURNSTILE_SECRET_KEY` */\n TURNSTILE_SECRET_KEY?: string;\n /** Notion integration token for the blog data source */\n NOTION_TOKEN?: string;\n /** Notion data source ID used by dataSources.query */\n NOTION_DATA_SOURCE_ID?: string;\n /** Notion data source ID for the public movie catalog */\n NOTION_MOVIES_DATA_SOURCE_ID?: string;\n /** Notion data source ID for localized movie copy */\n NOTION_MOVIE_TRANSLATIONS_DATA_SOURCE_ID?: string;\n /** Optional Notion API base URL for tests or proxies */\n NOTION_API_BASE_URL?: string;\n /** Optional Notion edit URL for admin handoff screens */\n NOTION_EDIT_BASE_URL?: string;\n /** Optional webhook verification token for Notion invalidation */\n NOTION_WEBHOOK_VERIFICATION_TOKEN?: string;\n};\n\n// 强制类型:vinext 把 env 类型放在 env.d.ts(interface VinextEnv extends Env),\n// 但 TS server 经常解析不到。运行时一定有 DB,类型断言保证编译通过。\nexport const workerEnv = env as unknown as AppEnv;\n","import type { AppEnv } from \"../util/env\";\n\nexport type PlatformBindingEnv = Pick<\n AppEnv,\n \"ASSETS_BUCKET\" | \"CONTENT_CACHE\" | \"DB\" | \"IMAGES\"\n>;\n\nexport type StoredObject = {\n body: ReadableStream;\n size: number;\n etag?: string;\n contentType?: string;\n};\n\nexport type ObjectStoragePutOptions = {\n contentType?: string;\n cacheControl?: string;\n metadata?: Record<string, string>;\n};\n\nexport type ObjectStorageListItem = {\n key: string;\n size: number;\n uploaded: Date;\n};\n\nexport type ObjectStorageAdapter = {\n kind: \"r2\";\n get(key: string): Promise<StoredObject | null>;\n put(\n key: string,\n value: ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob,\n options?: ObjectStoragePutOptions\n ): Promise<void>;\n delete(key: string): Promise<void>;\n list(\n options?: { prefix?: string; limit?: number }\n ): Promise<ObjectStorageListItem[]>;\n};\n\nexport type ImageTransformOptions = {\n width?: number;\n format: \"image/avif\" | \"image/webp\";\n quality: number;\n};\n\nexport type ImageTransformResult = {\n body: ReadableStream;\n contentType: string;\n response(): Response;\n};\n\nexport type ImageTransformerAdapter = {\n kind: \"cloudflare-images\" | \"external\";\n transform(\n body: ReadableStream,\n options: ImageTransformOptions\n ): Promise<ImageTransformResult>;\n};\n\nexport type PublicCacheAdapter = {\n kind: \"cloudflare-cache\" | \"noop\" | \"external\";\n match(key: string): Promise<Response | null>;\n put(key: string, response: Response): Promise<void>;\n delete(key: string): Promise<boolean>;\n};\n\nexport type KeyValueCacheGetOptions = {\n cacheTtl?: number;\n};\n\nexport type KeyValueCachePutOptions = {\n expirationTtl?: number;\n metadata?: Record<string, string | number | boolean | null>;\n};\n\nexport type KeyValueCacheListOptions = {\n prefix?: string;\n limit?: number;\n cursor?: string;\n};\n\nexport type KeyValueCacheListResult = {\n keys: Array<{ name: string }>;\n cursor?: string;\n listComplete: boolean;\n};\n\nexport type KeyValueCacheAdapter = {\n kind: \"workers-kv\" | \"noop\" | \"external\";\n get<T = unknown>(\n key: string,\n options?: KeyValueCacheGetOptions\n ): Promise<T | null>;\n put<T = unknown>(\n key: string,\n value: T,\n options?: KeyValueCachePutOptions\n ): Promise<void>;\n delete(key: string): Promise<void>;\n list(options?: KeyValueCacheListOptions): Promise<KeyValueCacheListResult>;\n};\n\nexport type SqlValue = string | number | boolean | null;\n\nexport type SqlResult<T = Record<string, unknown>> = {\n results?: T[];\n success?: boolean;\n meta?: {\n changes?: number;\n duration?: number;\n last_row_id?: number;\n rows_read?: number;\n rows_written?: number;\n [key: string]: unknown;\n };\n};\n\nexport type SqlPreparedStatement = {\n bind(...values: SqlValue[]): SqlPreparedStatement;\n all<T = Record<string, unknown>>(): Promise<SqlResult<T>>;\n first<T = Record<string, unknown>>(columnName?: string): Promise<T | null>;\n run<T = Record<string, unknown>>(): Promise<SqlResult<T>>;\n};\n\nexport type SqlDatabaseAdapter = {\n kind: \"d1\";\n prepare(query: string): SqlPreparedStatement;\n batch<T = Record<string, unknown>>(\n statements: SqlPreparedStatement[]\n ): Promise<SqlResult<T>[]>;\n};\n\nexport type RuntimePlatform = {\n id: \"cloudflare-workers\";\n database: SqlDatabaseAdapter | null;\n objectStorage: ObjectStorageAdapter | null;\n imageTransformer: ImageTransformerAdapter | null;\n publicCache: PublicCacheAdapter | null;\n keyValueCache: KeyValueCacheAdapter | null;\n};\n\ntype CloudflareCacheLike = Pick<Cache, \"match\" | \"put\" | \"delete\">;\ntype CloudflareKvLike = Pick<KVNamespace, \"get\" | \"put\" | \"delete\" | \"list\">;\n\nfunction cacheRequestForKey(key: string) {\n return new Request(key, { method: \"GET\" });\n}\n\nexport function createCloudflarePublicCacheAdapter(\n cache: CloudflareCacheLike\n): PublicCacheAdapter {\n return {\n kind: \"cloudflare-cache\",\n async match(key) {\n return (await cache.match(cacheRequestForKey(key))) ?? null;\n },\n put(key, response) {\n return cache.put(cacheRequestForKey(key), response);\n },\n delete(key) {\n return cache.delete(cacheRequestForKey(key));\n },\n };\n}\n\nexport function createNoopPublicCacheAdapter(kind: \"noop\" = \"noop\"): PublicCacheAdapter {\n return {\n kind,\n async match() {\n return null;\n },\n async put() {},\n async delete() {\n return false;\n },\n };\n}\n\nexport function createCloudflareKeyValueCacheAdapter(\n namespace: CloudflareKvLike\n): KeyValueCacheAdapter {\n return {\n kind: \"workers-kv\",\n async get<T = unknown>(\n key: string,\n options?: KeyValueCacheGetOptions\n ): Promise<T | null> {\n return (await namespace.get(key, {\n type: \"json\",\n cacheTtl: options?.cacheTtl,\n })) as T | null;\n },\n async put(key, value, options) {\n await namespace.put(key, JSON.stringify(value), {\n expirationTtl: options?.expirationTtl,\n metadata: options?.metadata,\n });\n },\n delete(key) {\n return namespace.delete(key);\n },\n async list(options) {\n const result = await namespace.list({\n prefix: options?.prefix,\n limit: options?.limit,\n cursor: options?.cursor,\n });\n return {\n keys: result.keys.map((key) => ({ name: key.name })),\n cursor: result.list_complete ? undefined : result.cursor,\n listComplete: result.list_complete,\n };\n },\n };\n}\n\nexport function createNoopKeyValueCacheAdapter(\n kind: \"noop\" = \"noop\"\n): KeyValueCacheAdapter {\n return {\n kind,\n async get() {\n return null;\n },\n async put() {},\n async delete() {},\n async list() {\n return { keys: [], listComplete: true };\n },\n };\n}\n\nfunction r2ObjectToStoredObject(object: R2ObjectBody): StoredObject {\n return {\n body: object.body,\n size: object.size,\n etag: object.etag,\n contentType: object.httpMetadata?.contentType,\n };\n}\n\nexport function createCloudflareRuntimePlatform(\n env: PlatformBindingEnv,\n options?: { publicCache?: CloudflareCacheLike | null }\n): RuntimePlatform {\n const database: SqlDatabaseAdapter | null = env.DB\n ? ({\n kind: \"d1\",\n prepare(query: string) {\n return env.DB.prepare(query) as unknown as SqlPreparedStatement;\n },\n async batch(statements: SqlPreparedStatement[]) {\n return (await env.DB.batch(\n statements as unknown as D1PreparedStatement[]\n )) as unknown as SqlResult<Record<string, unknown>>[];\n },\n } as unknown as SqlDatabaseAdapter)\n : null;\n\n const objectStorage: ObjectStorageAdapter | null = env.ASSETS_BUCKET\n ? {\n kind: \"r2\",\n async get(key) {\n const object = await env.ASSETS_BUCKET?.get(key);\n return object ? r2ObjectToStoredObject(object) : null;\n },\n async put(key, value, options) {\n await env.ASSETS_BUCKET?.put(key, value, {\n httpMetadata: {\n contentType: options?.contentType,\n cacheControl: options?.cacheControl,\n },\n customMetadata: options?.metadata,\n });\n },\n async delete(key) {\n await env.ASSETS_BUCKET?.delete(key);\n },\n async list(options) {\n const listed = await env.ASSETS_BUCKET?.list({\n prefix: options?.prefix,\n limit: options?.limit,\n });\n return (\n listed?.objects.map((object) => ({\n key: object.key,\n size: object.size,\n uploaded: object.uploaded,\n })) ?? []\n );\n },\n }\n : null;\n\n const imageTransformer: ImageTransformerAdapter | null = env.IMAGES\n ? {\n kind: \"cloudflare-images\",\n async transform(body, options) {\n const result = await env.IMAGES.input(body)\n .transform(options.width ? { width: options.width } : {})\n .output({\n format: options.format,\n quality: options.quality,\n });\n return {\n body: result.image(),\n contentType: result.contentType(),\n response: () => result.response(),\n };\n },\n }\n : null;\n\n const keyValueCache: KeyValueCacheAdapter | null = env.CONTENT_CACHE\n ? createCloudflareKeyValueCacheAdapter(env.CONTENT_CACHE)\n : null;\n\n return {\n id: \"cloudflare-workers\",\n database,\n objectStorage,\n imageTransformer,\n keyValueCache,\n publicCache: options?.publicCache\n ? createCloudflarePublicCacheAdapter(options.publicCache)\n : null,\n };\n}\n","import { workerEnv } from \"../util/env\";\nimport {\n createCloudflarePublicCacheAdapter,\n createCloudflareRuntimePlatform,\n} from \"./runtime\";\n\nfunction getDefaultCloudflareCache() {\n const globalWithCaches = globalThis as typeof globalThis & {\n caches?: CacheStorage & { default?: Cache };\n };\n return globalWithCaches.caches?.default ?? null;\n}\n\nexport function getRuntimePlatform() {\n return createCloudflareRuntimePlatform(workerEnv, {\n publicCache: getDefaultCloudflareCache(),\n });\n}\n\nexport function getDatabase() {\n const database = getRuntimePlatform().database;\n if (!database) {\n throw new Error(\"SQL database binding not configured\");\n }\n return database;\n}\n\nexport function getPublicCache() {\n const cache = getDefaultCloudflareCache();\n if (!cache) {\n throw new Error(\"Cloudflare cache binding not configured\");\n }\n return createCloudflarePublicCacheAdapter(cache);\n}\n","export type RuntimeId = \"cloudflare-workers\";\n\nexport type RuntimeKind = \"cloudflare\";\n\nexport interface RuntimeSelection {\n kind: RuntimeKind;\n runtimeId: RuntimeId;\n}\n\nfunction hasCloudflareBindings(env: unknown): boolean {\n if (!env || typeof env !== \"object\") return false;\n const record = env as Record<string, unknown>;\n return (\n \"DB\" in record ||\n \"ASSETS_BUCKET\" in record ||\n \"R2\" in record ||\n \"IMAGES\" in record ||\n \"CONTENT_CACHE\" in record\n );\n}\n\nexport function selectRuntime(env: unknown): RuntimeSelection {\n if (hasCloudflareBindings(env)) {\n return { kind: \"cloudflare\", runtimeId: \"cloudflare-workers\" };\n }\n throw new Error(\n \"No supported runtime detected. Expected Cloudflare Workers bindings \" +\n \"(DB, ASSETS_BUCKET, R2, IMAGES, or CONTENT_CACHE).\",\n );\n}\n\nexport function currentRuntimeId(): RuntimeId {\n return \"cloudflare-workers\";\n}\n","import {\n getPublicCache as getCloudflarePublicCache,\n getRuntimePlatform as getCloudflareRuntimePlatform,\n} from \"./cloudflare-runtime\";\nimport { currentRuntimeId } from \"./selection\";\n\nexport function getRuntimePlatform() {\n return getCloudflareRuntimePlatform();\n}\n\nexport function getDatabase() {\n const platform = getRuntimePlatform();\n const database = platform.database;\n if (!database) {\n throw new Error(`SQL database adapter not configured for ${platform.id}`);\n }\n return database;\n}\n\nexport function getPublicCache() {\n return getCloudflarePublicCache();\n}\n\nexport function getKeyValueCache() {\n return getRuntimePlatform().keyValueCache;\n}\n\nexport const runtimeSelection = {\n currentRuntimeId,\n};\n","// storage/routes/cdn.ts\n//\n// GET /api/cdn/[...key] - 从对象存储取原图,调用运行时图片转换转 WebP/AVIF 后返回\n//\n// 用 catch-all 参数兼容两种 URL:\n// 1. /api/cdn/uploads/2026-06-06/file.jpg\n// 2. /api/cdn/uploads%2F2026-06-06%2Ffile.jpg\n//\n// Like the other storage route, the package exports a route object that\n// exposes both a Next.js handler and a worker-friendly single-arg\n// handler. The body is otherwise identical to the original starter\n// implementation.\n\nimport { NextResponse } from \"next/server\";\nimport {\n type StoredObject,\n} from \"../../platform/runtime\";\nimport { getRuntimePlatform } from \"../../platform/current\";\n\nexport const dynamic = \"force-dynamic\";\n\nconst DEFAULT_WIDTH = 1200;\nconst MAX_WIDTH = 2400;\nconst DEFAULT_QUALITY = 75;\nconst MIN_QUALITY = 40;\nconst MAX_QUALITY = 85;\n\ntype Props = {\n params: Promise<{ key: string[] }>;\n};\n\nexport const cdnRoute = {\n async GET(request: Request, props: Props) {\n const { key } = await props.params;\n return cdnRoute.handle(new Request(buildInternalUrl(request, key)));\n },\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const key = readKeyFromPathname(url.pathname);\n if (!key) {\n return NextResponse.json({ error: \"Invalid key\" }, { status: 400 });\n }\n\n if (key.includes(\"..\") || key.startsWith(\"/\")) {\n return NextResponse.json({ error: \"Invalid key\" }, { status: 400 });\n }\n\n const platform = getRuntimePlatform();\n const storage = platform.objectStorage;\n if (!storage) {\n return NextResponse.json(\n { error: \"Object storage not configured\" },\n { status: 503 }\n );\n }\n\n const object = await storage.get(key);\n if (!object) {\n return NextResponse.json({ error: \"Not found\" }, { status: 404 });\n }\n\n const accept = request.headers.get(\"accept\") ?? \"\";\n const isImage = object.contentType?.startsWith(\"image/\") ?? false;\n\n if (!isImage) {\n return streamObject(object, {\n \"X-Debug-Cdn-Branch\": \"non-image\",\n \"X-Debug-Cdn-Key\": key,\n });\n }\n\n let outputFormat: \"image/avif\" | \"image/webp\" | null = null;\n let outputQuality: number | undefined = undefined;\n\n if (accept.includes(\"image/avif\")) {\n outputFormat = \"image/avif\";\n outputQuality = 60;\n } else if (accept.includes(\"image/webp\")) {\n outputFormat = \"image/webp\";\n outputQuality = 75;\n }\n\n const isSvg = object.contentType === \"image/svg+xml\";\n if (!outputFormat || isSvg || !platform.imageTransformer) {\n return streamObject(object, {\n \"X-Debug-Cdn-Branch\": isSvg\n ? \"svg-bypass\"\n : !platform.imageTransformer\n ? \"transformer-bypass\"\n : \"format-bypass\",\n \"X-Debug-Cdn-Accept\": accept.includes(\"image/avif\")\n ? \"avif\"\n : accept.includes(\"image/webp\")\n ? \"webp\"\n : \"other\",\n \"X-Debug-Cdn-Key\": key,\n });\n }\n\n try {\n const width = clampInt(\n url.searchParams.get(\"w\"),\n 64,\n MAX_WIDTH,\n DEFAULT_WIDTH\n );\n const quality = clampInt(\n url.searchParams.get(\"q\"),\n MIN_QUALITY,\n MAX_QUALITY,\n outputQuality ?? DEFAULT_QUALITY\n );\n\n const result = await platform.imageTransformer.transform(object.body, {\n width,\n format: outputFormat,\n quality,\n });\n\n return new Response(result.body, {\n headers: {\n \"Content-Type\": result.contentType,\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n Vary: \"Accept\",\n \"X-Debug-Cdn-Branch\": \"transformed\",\n \"X-Debug-Cdn-Key\": key,\n \"X-Optimized-Width\": String(width),\n \"X-Optimized-Quality\": String(quality),\n \"X-Original-Format\": object.contentType ?? \"unknown\",\n \"X-Optimized-Format\": outputFormat,\n },\n });\n } catch (e) {\n return streamObject(object, {\n \"X-Debug-Cdn-Branch\": \"transform-error-fallback\",\n \"X-Debug-Cdn-Key\": key,\n \"X-Debug-Cdn-Error\": e instanceof Error ? e.name : \"unknown\",\n });\n }\n },\n};\n\nfunction buildInternalUrl(request: Request, keyParts: string[]) {\n const url = new URL(request.url);\n url.pathname = `/api/cdn/${keyParts.map(encodeURIComponent).join(\"/\")}`;\n return url.toString();\n}\n\nfunction readKeyFromPathname(pathname: string): string | null {\n const prefix = \"/api/cdn/\";\n if (!pathname.startsWith(prefix)) return null;\n const encoded = pathname.slice(prefix.length);\n if (!encoded) return null;\n return decodeURIComponent(encoded);\n}\n\nfunction clampInt(\n value: string | null,\n min: number,\n max: number,\n fallback: number\n) {\n const parsed = Number.parseInt(value ?? \"\", 10);\n if (!Number.isFinite(parsed)) {\n return fallback;\n }\n\n return Math.max(min, Math.min(max, parsed));\n}\n\nfunction streamObject(\n object: StoredObject,\n extraHeaders?: Record<string, string>\n): Response {\n const headers = new Headers();\n if (object.contentType) {\n headers.set(\"Content-Type\", object.contentType);\n }\n headers.set(\"Cache-Control\", \"public, max-age=31536000, immutable\");\n headers.set(\"Content-Length\", String(object.size));\n if (object.etag) headers.set(\"ETag\", object.etag);\n for (const [key, value] of Object.entries(extraHeaders ?? {})) {\n headers.set(key, value);\n }\n return new Response(object.body, { headers });\n}\n\n// Top-level aliases for callers that prefer a flat signature (e.g. the\n// Next.js `app/api/.../route.ts` delegates).\nexport const GET = cdnRoute.GET;\n\n/**\n * Worker-friendly single-arg handler. Used by the Cloudflare Workers\n * bootstrap in `@notionx/core/worker`. Equivalent to\n * `cdnRoute.handle`.\n */\nexport async function cdnRouteHandle(request: Request): Promise<Response> {\n return cdnRoute.handle(request);\n}\n","// media/routes/notion-media.ts\n//\n// GET /api/notion/media/[...ref] - Notion-hosted media proxy with\n// edge cache, R2 fanout, and image transformation.\n//\n// Originally lives at `apps/moviebluebook/app/api/notion/media/[...ref]/route.ts`.\n// The package exports a route object that exposes both a Next.js\n// handler (the `GET` field) and a worker-friendly `handle` function.\n//\n// The `vinext/shims/request-context` module is treated as a runtime\n// shim by the starter and the Cloudflare Workers runtime; it is\n// listed in the package's tsup `external` config so it is resolved by\n// the consumer at runtime.\n\nimport { NextResponse } from \"next/server\";\nimport { getRequestExecutionContext } from \"vinext/shims/request-context\";\nimport {\n notionMediaR2KeyForUrl,\n publicMediaCacheKeyForUrl,\n publicMediaVariantForAccept,\n type PublicMediaVariant,\n} from \"../../cache\";\nimport { createNotionClient } from \"../../notion/client\";\nimport { getNotionClientConfig } from \"../../notion/config\";\nimport {\n fileObjectForMediaBlock,\n normalizeNotionFileSource,\n pickFirstFilesPropertyValue,\n} from \"../../notion/media\";\nimport type { NotionBlock, NotionPageLike } from \"../../notion/types\";\nimport {\n getPublicCache,\n getRuntimePlatform,\n} from \"../../platform/current\";\n\nexport const dynamic = \"force-dynamic\";\n\nconst DEFAULT_WIDTH = 1200;\nconst MAX_WIDTH = 2400;\nconst DEFAULT_QUALITY = 75;\nconst MIN_QUALITY = 40;\nconst MAX_QUALITY = 85;\nconst CACHEABLE_STATUS = new Set([200]);\n\ntype Props = {\n params: Promise<{ ref: string[] }>;\n};\n\nexport const notionMediaRoute = {\n async GET(_request: Request, props: Props) {\n const { ref } = await props.params;\n if (ref.some((part) => part === \"..\" || part.includes(\"/\"))) {\n return badRequest();\n }\n const url = new URL(_request.url);\n url.pathname = buildNotionMediaPath(ref);\n return notionMediaRoute.handle(new Request(url.toString(), _request));\n },\n async handle(request: Request): Promise<Response> {\n const variant = publicMediaVariantForAccept(\n request.headers.get(\"accept\") ?? \"\"\n );\n return withEdgeMediaCache(request, variant, () => loadMedia(request));\n },\n};\n\nfunction buildNotionMediaPath(ref: string[]) {\n return `/api/notion/media/${ref.map(encodeURIComponent).join(\"/\")}`;\n}\n\nfunction clampInt(\n value: string | null,\n min: number,\n max: number,\n fallback: number\n) {\n const parsed = Number.parseInt(value ?? \"\", 10);\n if (!Number.isFinite(parsed)) return fallback;\n return Math.max(min, Math.min(max, parsed));\n}\n\nfunction cacheControl(request: Request) {\n const url = new URL(request.url);\n if (url.searchParams.has(\"v\")) {\n return \"public, max-age=31536000, immutable\";\n }\n\n return \"public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400\";\n}\n\nfunction canUseMediaCache(request: Request) {\n if (request.method !== \"GET\") return false;\n return !request.headers.has(\"range\");\n}\n\nfunction mediaCacheHeaders(\n response: Response,\n request: Request,\n state: \"HIT\" | \"MISS\" | \"BYPASS\",\n r2State?: \"HIT\" | \"MISS\" | \"BYPASS\"\n) {\n const headers = new Headers(response.headers);\n headers.set(\"Cache-Control\", cacheControl(request));\n headers.set(\"X-Notion-Media-Cache\", state);\n if (r2State) headers.set(\"X-Notion-Media-R2\", r2State);\n return headers;\n}\n\nasync function responseFromR2Cache(\n request: Request,\n variant: PublicMediaVariant\n) {\n const url = new URL(request.url);\n const r2Key = notionMediaR2KeyForUrl(url, variant);\n const storage = getRuntimePlatform().objectStorage;\n if (!r2Key || !storage) return null;\n\n const object = await storage.get(r2Key);\n if (!object?.body) return null;\n\n const contentType =\n object.contentType ?? (variant === \"avif\" ? \"image/avif\" : \"image/webp\");\n const headers = new Headers();\n headers.set(\"Content-Type\", contentType);\n headers.set(\"Cache-Control\", cacheControl(request));\n headers.set(\"Vary\", \"Accept\");\n headers.set(\"X-Notion-Media-Branch\", \"r2\");\n headers.set(\"X-Notion-Media-R2\", \"HIT\");\n if (object.etag) headers.set(\"ETag\", object.etag);\n\n return new Response(object.body, { headers });\n}\n\nasync function withEdgeMediaCache(\n request: Request,\n variant: PublicMediaVariant,\n load: () => Promise<Response>\n) {\n if (!canUseMediaCache(request)) {\n const response = await load();\n return new Response(response.body, {\n status: response.status,\n headers: mediaCacheHeaders(response, request, \"BYPASS\"),\n });\n }\n\n const url = new URL(request.url);\n const cache = getPublicCache();\n const cacheKey = publicMediaCacheKeyForUrl(url, variant);\n const cached = await cache.match(cacheKey);\n if (cached) {\n return new Response(cached.body, {\n status: cached.status,\n headers: mediaCacheHeaders(cached, request, \"HIT\"),\n });\n }\n\n const r2Response = await responseFromR2Cache(request, variant);\n const response = r2Response ?? (await load());\n const headers = mediaCacheHeaders(response, request, \"MISS\");\n const output = new Response(response.body, {\n status: response.status,\n headers,\n });\n\n if (CACHEABLE_STATUS.has(response.status)) {\n const toCache = output.clone();\n getRequestExecutionContext()?.waitUntil(cache.put(cacheKey, toCache));\n }\n\n return output;\n}\n\nfunction mediaRedirect(url: string) {\n const response = NextResponse.redirect(url, 302);\n response.headers.set(\n \"Cache-Control\",\n \"public, max-age=300, s-maxage=300, stale-while-revalidate=300\"\n );\n return response;\n}\n\nfunction notFound() {\n return NextResponse.json({ error: \"Not found\" }, { status: 404 });\n}\n\nfunction badRequest() {\n return NextResponse.json({ error: \"Invalid media ref\" }, { status: 400 });\n}\n\nfunction forbidden() {\n return NextResponse.json({ error: \"Forbidden\" }, { status: 403 });\n}\n\nasync function serveFileObject(\n input: unknown,\n request: Request,\n options?: { redirectNotionHosted?: boolean }\n) {\n const source = normalizeNotionFileSource(input);\n if (!source) return notFound();\n if (options?.redirectNotionHosted) return mediaRedirect(source.url);\n return proxyNotionHostedFile(source.url, request);\n}\n\nasync function proxyNotionHostedFile(url: string, request: Request) {\n const range = request.headers.get(\"range\");\n const ifRange = request.headers.get(\"if-range\");\n const upstreamHeaders = new Headers({\n Accept: request.headers.get(\"accept\") ?? \"*/*\",\n });\n if (range) upstreamHeaders.set(\"Range\", range);\n if (ifRange) upstreamHeaders.set(\"If-Range\", ifRange);\n\n const upstream = await fetch(url, {\n headers: upstreamHeaders,\n });\n if ((!upstream.ok && upstream.status !== 416) || !upstream.body) {\n return NextResponse.json(\n { error: \"Unable to fetch Notion media\" },\n { status: upstream.status || 502 }\n );\n }\n\n const contentType = upstream.headers.get(\"content-type\") ?? \"\";\n const isImage = contentType.startsWith(\"image/\");\n const accept = request.headers.get(\"accept\") ?? \"\";\n const variant = publicMediaVariantForAccept(accept);\n const urlObj = new URL(request.url);\n const width = clampInt(\n urlObj.searchParams.get(\"w\"),\n 64,\n MAX_WIDTH,\n DEFAULT_WIDTH\n );\n const quality = clampInt(\n urlObj.searchParams.get(\"q\"),\n MIN_QUALITY,\n MAX_QUALITY,\n DEFAULT_QUALITY\n );\n\n let outputFormat: \"image/avif\" | \"image/webp\" | null = null;\n if (variant === \"avif\") {\n outputFormat = \"image/avif\";\n } else if (variant === \"webp\") {\n outputFormat = \"image/webp\";\n }\n\n const platform = getRuntimePlatform();\n const imageTransformer = platform.imageTransformer;\n if (isImage && !range && outputFormat && imageTransformer) {\n const r2Key = notionMediaR2KeyForUrl(urlObj, variant);\n\n try {\n const result = await imageTransformer.transform(upstream.body, {\n width,\n format: outputFormat,\n quality,\n });\n const transformed = result.response();\n const headers = new Headers(transformed.headers);\n headers.set(\"Content-Type\", result.contentType);\n headers.set(\"Cache-Control\", cacheControl(request));\n headers.set(\"Vary\", \"Accept\");\n headers.set(\"X-Notion-Media-Branch\", \"transformed\");\n headers.set(\"X-Notion-Media-R2\", r2Key ? \"MISS\" : \"BYPASS\");\n headers.set(\"X-Optimized-Width\", String(width));\n headers.set(\"X-Optimized-Quality\", String(quality));\n\n if (transformed.body && r2Key && platform.objectStorage) {\n const [clientBody, r2Body] = transformed.body.tee();\n getRequestExecutionContext()?.waitUntil(\n platform.objectStorage.put(r2Key, r2Body, {\n contentType: result.contentType,\n cacheControl: \"public, max-age=31536000, immutable\",\n metadata: {\n source: \"notion\",\n cachedAt: new Date().toISOString(),\n width: String(width),\n quality: String(quality),\n },\n })\n );\n\n return new Response(clientBody, { headers });\n }\n\n return new Response(transformed.body, { headers });\n } catch {\n // Fall through to the original file when the image binding cannot transform.\n }\n }\n\n const headers = new Headers();\n for (const header of [\n \"accept-ranges\",\n \"content-disposition\",\n \"content-encoding\",\n \"content-length\",\n \"content-range\",\n \"content-type\",\n \"etag\",\n \"last-modified\",\n ]) {\n const value = upstream.headers.get(header);\n if (value) headers.set(header, value);\n }\n if (contentType && !headers.has(\"Content-Type\")) {\n headers.set(\"Content-Type\", contentType);\n }\n headers.set(\"Cache-Control\", cacheControl(request));\n headers.set(\"X-Notion-Media-Branch\", \"proxied\");\n return new Response(upstream.body, { status: upstream.status, headers });\n}\n\nasync function loadMedia(request: Request) {\n const url = new URL(request.url);\n const ref = readRefFromPathname(url.pathname);\n if (!ref) return badRequest();\n const client = createNotionClient(await getNotionClientConfig());\n\n if (ref[0] === \"page\" && ref[1] && ref[2] === \"cover\") {\n const page = (await client.pages.retrieve({\n page_id: ref[1],\n })) as NotionPageLike;\n return serveFileObject(page.cover, request);\n }\n\n if (ref[0] === \"page\" && ref[1] && ref[2] === \"property\" && ref[3]) {\n const page = (await client.pages.retrieve({\n page_id: ref[1],\n })) as NotionPageLike;\n const propertyName = decodeURIComponent(ref.slice(3).join(\"/\"));\n return serveFileObject(\n pickFirstFilesPropertyValue(page.properties?.[propertyName]),\n request\n );\n }\n\n if (ref[0] === \"block\" && ref[1]) {\n const block = (await client.blocks.retrieve({\n block_id: ref[1],\n })) as NotionBlock;\n if (block.type === \"video\") {\n return forbidden();\n }\n return serveFileObject(fileObjectForMediaBlock(block), request, {\n redirectNotionHosted:\n block.type === \"audio\" ||\n block.type === \"pdf\" ||\n block.type === \"file\",\n });\n }\n\n return badRequest();\n}\n\nfunction readRefFromPathname(pathname: string): string[] | null {\n const prefix = \"/api/notion/media/\";\n if (!pathname.startsWith(prefix)) return null;\n const encoded = pathname.slice(prefix.length);\n if (!encoded) return null;\n return encoded.split(\"/\").map((part) => decodeURIComponent(part));\n}\n\n// Top-level alias for callers that prefer a flat signature (e.g. the\n// Next.js `app/api/.../route.ts` delegates).\nexport const GET = notionMediaRoute.GET;\n\n/**\n * Worker-friendly single-arg handler. Used by the Cloudflare Workers\n * bootstrap in `@notionx/core/worker`. Equivalent to\n * `notionMediaRoute.handle`.\n */\nexport async function notionMediaRouteHandle(\n request: Request\n): Promise<Response> {\n return notionMediaRoute.handle(request);\n}\n","// Edge cache keys for transformed Notion media responses.\n//\n// Page and API route caching is handled by vinext's CDN cache adapter\n// (vite.config.ts). These helpers remain for the media proxy route.\n\nconst CACHE_ORIGIN = \"https://cache.local\";\nconst CACHE_NAMESPACE = \"/__public-cache/v20260609a\";\nconst NOTION_MEDIA_R2_PREFIX = \"notion-media/v1\";\n\nexport type PublicMediaVariant = \"avif\" | \"webp\" | \"source\";\n\nfunction normalizePath(pathname: string) {\n if (pathname === \"/\") return \"/\";\n return pathname.endsWith(\"/\") ? pathname.slice(0, -1) : pathname;\n}\n\nexport function publicMediaVariantForAccept(accept: string): PublicMediaVariant {\n if (accept.includes(\"image/avif\")) return \"avif\";\n if (accept.includes(\"image/webp\")) return \"webp\";\n return \"source\";\n}\n\nexport function publicMediaCacheKeyForUrl(\n input: URL,\n variant: PublicMediaVariant\n) {\n const url = new URL(\n `${CACHE_NAMESPACE}${normalizePath(input.pathname)}${input.search}`,\n CACHE_ORIGIN\n );\n url.searchParams.set(\"__variant\", variant);\n url.searchParams.sort();\n return url.toString();\n}\n\nfunction keySegment(value: string) {\n return encodeURIComponent(value || \"none\");\n}\n\nexport function notionMediaR2KeyForUrl(\n input: URL,\n variant: PublicMediaVariant\n) {\n if (variant === \"source\") return null;\n\n const version = input.searchParams.get(\"v\");\n if (!version) return null;\n\n const path = normalizePath(input.pathname)\n .split(\"/\")\n .filter(Boolean)\n .map(keySegment)\n .join(\"/\");\n const width = input.searchParams.get(\"w\") ?? \"source\";\n const quality = input.searchParams.get(\"q\") ?? \"source\";\n\n return [\n NOTION_MEDIA_R2_PREFIX,\n variant,\n path,\n `v-${keySegment(version)}`,\n `w-${keySegment(width)}`,\n `q-${keySegment(quality)}.${variant}`,\n ].join(\"/\");\n}\n","import { Client } from \"@notionhq/client\";\nimport type { NotionClientConfig } from \"./config\";\n\nexport function createNotionClient(config: NotionClientConfig) {\n return new Client({\n auth: config.token,\n baseUrl: config.apiBaseUrl,\n notionVersion: \"2026-03-11\",\n });\n}\n","import type { NotionContentModelLike } from \"./types\";\n\ntype NotionEnv = {\n NOTION_TOKEN?: string;\n NOTION_DATA_SOURCE_ID?: string;\n NOTION_MOVIES_DATA_SOURCE_ID?: string;\n NOTION_API_BASE_URL?: string;\n NOTION_EDIT_BASE_URL?: string;\n NOTION_WEBHOOK_VERIFICATION_TOKEN?: string;\n [key: string]: string | undefined;\n};\n\nexport const DEFAULT_NOTION_MOVIES_DATA_SOURCE_ID =\n \"371dc62d-0738-8015-a601-000bc3944fcb\";\n\nexport type NotionClientConfig = {\n token: string;\n apiBaseUrl?: string;\n};\n\nexport type NotionConfig = {\n token: string;\n dataSourceId: string;\n apiBaseUrl?: string;\n editBaseUrl?: string;\n webhookVerificationToken?: string;\n};\n\nfunction readProcessEnv(): NotionEnv {\n const env: NotionEnv = {\n NOTION_TOKEN: process.env.NOTION_TOKEN,\n NOTION_DATA_SOURCE_ID: process.env.NOTION_DATA_SOURCE_ID,\n NOTION_MOVIES_DATA_SOURCE_ID: process.env.NOTION_MOVIES_DATA_SOURCE_ID,\n NOTION_API_BASE_URL: process.env.NOTION_API_BASE_URL,\n NOTION_EDIT_BASE_URL: process.env.NOTION_EDIT_BASE_URL,\n NOTION_WEBHOOK_VERIFICATION_TOKEN:\n process.env.NOTION_WEBHOOK_VERIFICATION_TOKEN,\n };\n\n for (const [key, value] of Object.entries(process.env)) {\n if (key.startsWith(\"NOTION_\") && typeof value === \"string\") {\n env[key] = value;\n }\n }\n\n return env;\n}\n\nasync function readWorkerEnv(): Promise<NotionEnv> {\n try {\n const mod = (await import(\n /* webpackIgnore: true */ \"cloudflare:workers\"\n )) as unknown as { env?: Record<string, unknown> };\n const env: NotionEnv = {};\n for (const [key, value] of Object.entries(mod.env ?? {})) {\n if (key.startsWith(\"NOTION_\") && typeof value === \"string\") {\n env[key] = value;\n }\n }\n return env;\n } catch {\n return {};\n }\n}\n\nfunction readString(source: NotionEnv, name: string): string | undefined {\n const value = String(source[name] ?? \"\").trim();\n return value || undefined;\n}\n\nfunction mergeEnv(...sources: NotionEnv[]): NotionEnv {\n const merged: NotionEnv = {};\n\n for (const source of sources) {\n for (const name of Object.keys(source)) {\n if (!name.startsWith(\"NOTION_\")) continue;\n const value = readString(source, name);\n if (value) merged[name] = value;\n }\n }\n\n return merged;\n}\n\nasync function readEnv(): Promise<NotionEnv> {\n const processEnv = readProcessEnv();\n return mergeEnv(await readWorkerEnv(), processEnv);\n}\n\nfunction readRequired(\n source: NotionEnv,\n name: string\n): string {\n const value = readString(source, name);\n if (!value) {\n throw new Error(`Missing required Notion env: ${name}`);\n }\n return value;\n}\n\nexport function getNotionEditBaseUrl(): string {\n return readString(readProcessEnv(), \"NOTION_EDIT_BASE_URL\") ?? \"https://www.notion.so\";\n}\n\nexport async function hasNotionConfig(): Promise<boolean> {\n const env = await readEnv();\n return Boolean(\n readString(env, \"NOTION_TOKEN\") && readString(env, \"NOTION_DATA_SOURCE_ID\")\n );\n}\n\nexport async function hasNotionMovieConfig(): Promise<boolean> {\n const env = await readEnv();\n return Boolean(readString(env, \"NOTION_TOKEN\"));\n}\n\nexport async function hasNotionModelConfig(\n model: NotionContentModelLike\n): Promise<boolean> {\n const env = await readEnv();\n return Boolean(\n readString(env, \"NOTION_TOKEN\") &&\n (readString(env, model.source.dataSourceEnv) ||\n model.source.defaultDataSourceId)\n );\n}\n\nexport async function getNotionClientConfig(): Promise<NotionClientConfig> {\n const env = await readEnv();\n return {\n token: readRequired(env, \"NOTION_TOKEN\"),\n apiBaseUrl: readString(env, \"NOTION_API_BASE_URL\"),\n };\n}\n\nexport async function getNotionConfig(): Promise<NotionConfig> {\n const env = await readEnv();\n return {\n token: readRequired(env, \"NOTION_TOKEN\"),\n dataSourceId: readRequired(env, \"NOTION_DATA_SOURCE_ID\"),\n apiBaseUrl: readString(env, \"NOTION_API_BASE_URL\"),\n editBaseUrl: readString(env, \"NOTION_EDIT_BASE_URL\"),\n webhookVerificationToken: readString(\n env,\n \"NOTION_WEBHOOK_VERIFICATION_TOKEN\"\n ),\n };\n}\n\nexport async function getNotionWebhookVerificationToken(): Promise<\n string | undefined\n> {\n const env = await readEnv();\n return readString(env, \"NOTION_WEBHOOK_VERIFICATION_TOKEN\");\n}\n\nexport async function getNotionMovieConfig(): Promise<NotionConfig> {\n const env = await readEnv();\n return {\n token: readRequired(env, \"NOTION_TOKEN\"),\n dataSourceId:\n readString(env, \"NOTION_MOVIES_DATA_SOURCE_ID\") ??\n DEFAULT_NOTION_MOVIES_DATA_SOURCE_ID,\n apiBaseUrl: readString(env, \"NOTION_API_BASE_URL\"),\n editBaseUrl: readString(env, \"NOTION_EDIT_BASE_URL\"),\n webhookVerificationToken: readString(\n env,\n \"NOTION_WEBHOOK_VERIFICATION_TOKEN\"\n ),\n };\n}\n\nexport async function getNotionConfigForModel(\n model: NotionContentModelLike\n): Promise<NotionConfig> {\n const env = await readEnv();\n const dataSourceId =\n readString(env, model.source.dataSourceEnv) ??\n model.source.defaultDataSourceId;\n if (!dataSourceId) {\n throw new Error(`Missing required Notion env: ${model.source.dataSourceEnv}`);\n }\n\n return {\n token: readRequired(env, model.source.tokenEnv),\n dataSourceId,\n apiBaseUrl: readString(env, \"NOTION_API_BASE_URL\"),\n editBaseUrl: readString(env, \"NOTION_EDIT_BASE_URL\"),\n webhookVerificationToken: readString(\n env,\n \"NOTION_WEBHOOK_VERIFICATION_TOKEN\"\n ),\n };\n}\n","import type { NotionBlock, NotionFileSource, NotionPageLike } from \"./types\";\n\ntype FileLike = {\n type?: string;\n external?: { url?: string };\n file?: { url?: string; expiry_time?: string };\n name?: string;\n};\n\nfunction stripLeadingSlash(value: string) {\n return value.startsWith(\"/\") ? value.slice(1) : value;\n}\n\nfunction encodePathPart(value: string) {\n return encodeURIComponent(stripLeadingSlash(value));\n}\n\nfunction appendVersion(path: string, version?: string) {\n const value = String(version ?? \"\").trim();\n if (!value) return path;\n return `${path}?${new URLSearchParams({ v: value })}`;\n}\n\nfunction blockVersion(block: NotionBlock): string | undefined {\n return typeof block.last_edited_time === \"string\"\n ? block.last_edited_time\n : undefined;\n}\n\nexport function notionPageCoverMediaPath(pageId: string): string {\n return `/api/notion/media/page/${encodePathPart(pageId)}/cover`;\n}\n\nexport function notionPagePropertyMediaPath(\n pageId: string,\n propertyName: string\n): string {\n return `/api/notion/media/page/${encodePathPart(pageId)}/property/${encodePathPart(propertyName)}`;\n}\n\nexport function notionBlockMediaPath(blockId: string): string {\n return `/api/notion/media/block/${encodePathPart(blockId)}`;\n}\n\nexport function normalizeNotionFileSource(input: unknown): NotionFileSource | null {\n const file = input as FileLike | null | undefined;\n if (!file || typeof file !== \"object\") return null;\n\n if (file.type === \"external\") {\n const url = String(file.external?.url ?? \"\").trim();\n return url ? { type: \"external\", url } : null;\n }\n\n if (file.type === \"file\") {\n const url = String(file.file?.url ?? \"\").trim();\n if (!url) return null;\n return {\n type: \"file\",\n url,\n expiryTime: String(file.file?.expiry_time ?? \"\").trim() || null,\n };\n }\n\n return null;\n}\n\nexport function resolveNotionFileUrl(input: unknown): string | null {\n return normalizeNotionFileSource(input)?.url ?? null;\n}\n\nexport function isNotionHostedFile(input: unknown): boolean {\n return normalizeNotionFileSource(input)?.type === \"file\";\n}\n\nexport function pickFirstFilesPropertyValue(property: unknown): unknown | null {\n const value = property as { type?: string; files?: unknown[] } | null | undefined;\n if (!value || value.type !== \"files\" || !Array.isArray(value.files)) {\n return null;\n }\n return value.files[0] ?? null;\n}\n\nexport function pickPageCoverFile(page: NotionPageLike): unknown | null {\n return page.cover ?? null;\n}\n\nexport function coverImageUrlForPage(\n page: NotionPageLike,\n coverPropertyName = \"Cover\"\n): string | null {\n const propertyFile = pickFirstFilesPropertyValue(\n page.properties?.[coverPropertyName]\n );\n const propertySource = normalizeNotionFileSource(propertyFile);\n if (propertySource) {\n return appendVersion(\n notionPagePropertyMediaPath(page.id, coverPropertyName),\n page.last_edited_time\n );\n }\n\n const coverSource = normalizeNotionFileSource(pickPageCoverFile(page));\n if (coverSource) {\n return appendVersion(notionPageCoverMediaPath(page.id), page.last_edited_time);\n }\n\n return null;\n}\n\nexport function fileObjectForMediaBlock(block: NotionBlock): unknown | null {\n const typed = block[block.type] as Record<string, unknown> | undefined;\n if (!typed || typeof typed !== \"object\") return null;\n\n if (\n block.type === \"image\" ||\n block.type === \"video\" ||\n block.type === \"file\" ||\n block.type === \"pdf\" ||\n block.type === \"audio\"\n ) {\n return typed;\n }\n\n return null;\n}\n\nexport function mediaUrlForBlock(block: NotionBlock): string | null {\n const source = normalizeNotionFileSource(fileObjectForMediaBlock(block));\n if (!source) return null;\n if (source.type === \"external\" && block.type !== \"image\") {\n return source.url;\n }\n return appendVersion(notionBlockMediaPath(block.id), blockVersion(block));\n}\n\nexport function firstImageUrlFromBlocks(blocks: NotionBlock[]): string | null {\n for (const block of blocks) {\n if (block.type === \"image\") {\n const imageUrl = mediaUrlForBlock(block);\n if (imageUrl) return imageUrl;\n }\n\n const nested = block.children?.length\n ? firstImageUrlFromBlocks(block.children)\n : null;\n if (nested) return nested;\n }\n\n return null;\n}\n\nexport function publicMediaBlockForApi(block: NotionBlock): NotionBlock {\n const value = block[block.type];\n const source = normalizeNotionFileSource(value);\n const children = block.children?.map(publicMediaBlockForApi);\n\n if (!source) {\n return children ? { ...block, children } : { ...block };\n }\n\n const path = appendVersion(notionBlockMediaPath(block.id), blockVersion(block));\n const publicValue =\n source.type === \"external\"\n ? block.type === \"image\"\n ? {\n ...(value as Record<string, unknown>),\n external: { url: path },\n }\n : value\n : {\n ...(value as Record<string, unknown>),\n file: {\n url: path,\n expiry_time: null,\n },\n };\n\n return {\n ...block,\n [block.type]: publicValue,\n ...(children ? { children } : {}),\n };\n}\n\nexport function gatedMediaBlockForApi(\n block: NotionBlock,\n options?: { movieId?: string }\n): NotionBlock {\n const value = block[block.type];\n const source = normalizeNotionFileSource(value);\n const children = block.children?.map((child) =>\n gatedMediaBlockForApi(child, options)\n );\n\n if (block.type !== \"video\" || !source) {\n const publicBlock = publicMediaBlockForApi(block);\n return children ? { ...publicBlock, children } : publicBlock;\n }\n\n const gatedValue: Record<string, unknown> = {\n ...(value as Record<string, unknown>),\n gated: true,\n access_url: options?.movieId\n ? `/api/movies/${encodePathPart(options.movieId)}/video/${encodePathPart(block.id)}`\n : null,\n };\n\n if (source.type === \"external\") {\n gatedValue.external = { url: null };\n } else {\n gatedValue.file = {\n url: null,\n expiry_time: null,\n };\n }\n\n return {\n ...block,\n video: gatedValue,\n ...(children ? { children } : {}),\n };\n}\n\nexport function isDirectVideoUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return /\\.(mp4|webm|mov|m4v)(?:$|\\?)/i.test(parsed.pathname);\n } catch {\n return false;\n }\n}\n\nexport function videoEmbedUrl(url: string): string | null {\n try {\n const parsed = new URL(url);\n const host = parsed.hostname.replace(/^www\\./, \"\");\n\n if (host === \"youtube.com\" || host === \"m.youtube.com\") {\n const id = parsed.searchParams.get(\"v\");\n return id ? `https://www.youtube.com/embed/${encodeURIComponent(id)}` : null;\n }\n\n if (host === \"youtu.be\") {\n const id = parsed.pathname.split(\"/\").filter(Boolean)[0];\n return id ? `https://www.youtube.com/embed/${encodeURIComponent(id)}` : null;\n }\n\n if (host === \"youtube-nocookie.com\") {\n return parsed.toString();\n }\n\n if (host === \"vimeo.com\") {\n const id = parsed.pathname.split(\"/\").filter(Boolean)[0];\n return id ? `https://player.vimeo.com/video/${encodeURIComponent(id)}` : null;\n }\n\n if (host === \"player.vimeo.com\") {\n return parsed.toString();\n }\n } catch {\n return null;\n }\n\n return null;\n}\n","// worker/routes/health.ts\n//\n// GET /api/health - 健康检查端点\n// 用于监控、容器编排、load balancer 健康探测\n// 顺便验证 SQL database adapter 连接是否正常\n//\n// The package exports a Next.js handler and a worker-friendly single-arg\n// handler. The two are equivalent; only the calling convention differs.\n//\n// The database check is domain-agnostic: it issues a `SELECT 1` to\n// confirm the SQL adapter is reachable, then runs the foundation\n// schema guard to ensure the auth tables are present.\n\nimport { NextResponse } from \"next/server\";\nimport { getDatabase } from \"../../platform/current\";\nimport { runSchemaHealthChecks } from \"../../internal/admin/schema-guard\";\n\nexport const dynamic = \"force-dynamic\";\n\nasync function probeDatabase(): Promise<{\n ok: boolean;\n error: string | null;\n postsTableExists: boolean;\n}> {\n const database = getDatabase();\n try {\n const result = await database\n .prepare(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name='posts' LIMIT 1\"\n )\n .first<{ name?: string }>();\n return {\n ok: true,\n error: null,\n postsTableExists: Boolean(result?.name),\n };\n } catch (e) {\n return {\n ok: false,\n error: e instanceof Error ? e.message : String(e),\n postsTableExists: false,\n };\n }\n}\n\nexport const healthRoute = {\n async GET() {\n return healthRoute.handle(new Request(\"https://health.local/api/health\"));\n },\n async handle(_request: Request): Promise<Response> {\n const start = Date.now();\n const probe = await probeDatabase();\n const d1Ok = probe.ok;\n const d1Error = probe.error;\n\n let schemaOk = false;\n let schemaError: string | null = null;\n let schemaMissing: string[] = [];\n try {\n const schema = await runSchemaHealthChecks(getDatabase());\n schemaOk = schema.ok;\n schemaMissing = schema.missing;\n if (schema.errors.length > 0) {\n schemaError = schema.errors.join(\"; \");\n } else if (schema.missing.length > 0) {\n schemaError = `missing required schema: ${schema.missing.join(\", \")}`;\n }\n } catch (e) {\n schemaError = e instanceof Error ? e.message : String(e);\n }\n\n const allHealthy = d1Ok && schemaOk;\n\n return NextResponse.json(\n {\n status: allHealthy ? \"ok\" : \"degraded\",\n timestamp: new Date().toISOString(),\n uptime_ms: Date.now() - start,\n checks: {\n d1: d1Ok ? \"ok\" : \"error\",\n d1_error: d1Error,\n schema: schemaOk ? \"ok\" : \"error\",\n schema_error: schemaError,\n schema_missing: schemaMissing,\n },\n version: \"1.0.0\",\n },\n {\n status: allHealthy ? 200 : 503,\n headers: {\n \"Cache-Control\": \"no-store\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n }\n );\n },\n};\n\n// Top-level aliases for callers that prefer a flat signature.\nexport const GET = healthRoute.GET;\n\n/**\n * Worker-friendly single-arg handler. Used by the Cloudflare Workers\n * bootstrap in `@notionx/core/worker`. Equivalent to\n * `healthRoute.handle`.\n */\nexport async function healthRouteHandle(request: Request): Promise<Response> {\n return healthRoute.handle(request);\n}\n","export const REQUIRED_SCHEMA_CHECKS = [\n {\n key: \"app_settings.turnstile_enabled\",\n sql: \"SELECT turnstile_enabled FROM app_settings LIMIT 1\",\n },\n {\n key: \"users.session_rev\",\n sql: \"SELECT session_rev FROM users LIMIT 1\",\n },\n {\n key: \"auth_rate_limits\",\n sql: \"SELECT 1 FROM auth_rate_limits LIMIT 1\",\n },\n];\n\nexport const DEFAULT_TURNSTILE_PUBLIC_CONFIG = {\n enabled: false,\n siteKey: null,\n secretConfigured: false,\n};\n\nexport function isSchemaDriftError(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error ?? \"\");\n return (\n message.includes(\"no such column\") || message.includes(\"no such table\")\n );\n}\n\nexport function buildTurnstilePublicConfig(\n settings: { turnstile_enabled: number; turnstile_site_key: string | null },\n envLike: { TURNSTILE_SITE_KEY?: string; TURNSTILE_SECRET_KEY?: string }\n): { enabled: boolean; siteKey: string | null; secretConfigured: boolean } {\n const envSiteKey = envLike.TURNSTILE_SITE_KEY?.trim() || null;\n const siteKey = settings.turnstile_site_key?.trim() || envSiteKey || null;\n const secretConfigured = Boolean(envLike.TURNSTILE_SECRET_KEY?.trim());\n const enabled =\n (settings.turnstile_enabled === 1 || Boolean(envSiteKey)) &&\n Boolean(siteKey) &&\n secretConfigured;\n\n return {\n enabled,\n siteKey,\n secretConfigured,\n };\n}\n\nexport async function runSchemaHealthChecks(db: {\n prepare: (sql: string) => { first: () => Promise<unknown> };\n}): Promise<{ ok: boolean; missing: string[]; errors: string[] }> {\n const missing: string[] = [];\n const errors: string[] = [];\n\n for (const check of REQUIRED_SCHEMA_CHECKS) {\n try {\n await db.prepare(check.sql).first();\n } catch (error) {\n if (isSchemaDriftError(error)) {\n missing.push(check.key);\n } else {\n const message = error instanceof Error ? error.message : String(error);\n errors.push(`${check.key}: ${message}`);\n }\n }\n }\n\n return {\n ok: missing.length === 0 && errors.length === 0,\n missing,\n errors,\n };\n}\n","// Foundation Cloudflare Worker bootstrap.\n//\n// Wires the package's first-party worker routes (health, storage,\n// notion media) plus any project-injected `extraRoutes` into a single\n// `fetch` handler. The handler returns `Response | null` so the\n// starter wrapper can fall through to vinext for any request\n// foundation does not own.\n//\n// Contract:\n// - `fetch(request, env, ctx)` returns a `Response` when a route\n// matched (or when the admin gate fires), and `null` to signal\n// \"not handled\" so the caller can delegate to the underlying\n// app router / vinext handler.\n//\n// Tier restriction: this file lives in `src/worker/`, so it CANNOT\n// import from `src/auth/` or `src/admin/`. The session/admin gate\n// lives in the tier-7 `middleware.ts` module which the bootstrap\n// delegates to.\n\nimport { nextionMiddleware, type NextionMiddlewareOptions } from \"../middleware\";\nimport type {\n AdminNavItem,\n AuthConfig,\n ContentSource,\n} from \"../types\";\nimport { filesRoute } from \"../storage/routes/files\";\nimport { cdnRoute } from \"../storage/routes/cdn\";\nimport { notionMediaRoute } from \"../media/routes/notion-media\";\nimport { healthRoute, healthRouteHandle } from \"./routes/health\";\n\n/**\n * Public surface for the bootstrap options. Mirrors the placeholder\n * `WorkerOptions` type in `src/types.ts`; Phase 6 normalizes the\n * shape once the content abstraction lands.\n */\nexport interface FoundationWorkerOptions {\n sources: ContentSource[];\n adminNav: AdminNavItem[];\n authConfig: AuthConfig;\n /**\n * Placeholder for the future site config. Kept on the options\n * surface so consumers can wire it today; the bootstrap itself\n * does not yet consume it.\n */\n siteConfig: {\n name: string;\n description: string;\n defaultLocale: string;\n locales: string[];\n navigation: unknown[];\n };\n /**\n * Optional session resolver passed straight through to\n * `nextionMiddleware`. When omitted the middleware still runs\n * the admin gate but always reports no viewer.\n */\n sessionLookup?: NextionMiddlewareOptions[\"sessionLookup\"];\n /**\n * Project-injected routes. Each entry maps a pathname to a\n * dynamic loader. The loader returns a module whose `default`\n * export is a `(request, options) => Promise<Response>` handler.\n *\n * Loaders are invoked lazily on the first matching request so\n * the route module can pull in heavy dependencies (e.g. the\n * starter's content models) only when needed.\n */\n extraRoutes?: Record<\n string,\n () => Promise<{ default: FoundationExtraRouteHandler }>\n >;\n}\n\nexport type FoundationExtraRouteHandler = (\n request: Request,\n options: FoundationWorkerOptions,\n sources: ContentSource[],\n auth: { databaseBinding: string }\n) => Promise<Response>;\n\nexport interface FoundationWorker {\n /**\n * Worker-style fetch. Returns `Response` when foundation handled\n * the request, `null` to let the caller fall through to vinext.\n */\n fetch: (\n request: Request,\n env: unknown,\n ctx: unknown\n ) => Promise<Response | null>;\n}\n\ninterface RouteEntry {\n match: (request: Request) => boolean;\n handle: (request: Request) => Promise<Response>;\n}\n\nfunction pathMatches(pathname: string, match: string): boolean {\n if (match.endsWith(\"/\")) return pathname.startsWith(match);\n return pathname === match || pathname.startsWith(`${match}/`);\n}\n\nfunction buildStaticRoutes(): RouteEntry[] {\n return [\n {\n match: (req) => new URL(req.url).pathname === \"/api/health\",\n handle: healthRouteHandle,\n },\n {\n match: (req) =>\n pathMatches(new URL(req.url).pathname, \"/api/notion/media/\"),\n handle: notionMediaRoute.handle,\n },\n {\n match: (req) =>\n pathMatches(new URL(req.url).pathname, \"/api/files/\"),\n handle: filesRoute.handle,\n },\n {\n match: (req) =>\n pathMatches(new URL(req.url).pathname, \"/api/cdn/\"),\n handle: cdnRoute.handle,\n },\n ];\n}\n\n/**\n * Build a foundation Cloudflare Worker.\n *\n * The returned object exposes a `fetch` method that:\n * 1. Runs `nextionMiddleware` (admin gate + viewer attachment).\n * 2. Tries the first-party route table.\n * 3. Tries any project-injected `extraRoutes`.\n * 4. Returns `null` if nothing matched, so the starter wrapper can\n * delegate to vinext.\n */\nexport function createNextionWorker(\n options: FoundationWorkerOptions\n): FoundationWorker {\n const sources: ContentSource[] = options.sources;\n const auth = { databaseBinding: options.authConfig.databaseBinding };\n\n const routes: RouteEntry[] = buildStaticRoutes();\n\n if (options.extraRoutes) {\n for (const [path, load] of Object.entries(options.extraRoutes)) {\n const modPromise = load();\n routes.push({\n match: (req) => new URL(req.url).pathname === path,\n handle: async (req) => {\n const mod = await modPromise;\n return mod.default(req, options, sources, auth);\n },\n });\n }\n }\n\n const middlewareOptions: NextionMiddlewareOptions = {\n authConfig: options.authConfig,\n sessionLookup: options.sessionLookup,\n };\n\n return {\n async fetch(request, env, ctx) {\n void ctx;\n\n // 1. Admin gate. A 401 short-circuits the route table.\n const gateResponse = await nextionMiddleware(\n request,\n env,\n middlewareOptions\n );\n if (gateResponse) return gateResponse;\n\n // 2. First-party + extra routes.\n for (const route of routes) {\n if (route.match(request)) return route.handle(request);\n }\n\n // 3. Not handled. The caller falls through to vinext.\n return null;\n },\n };\n}\n\n// Re-export the health route shape so consumers can build their own\n// worker if they need finer control than the bootstrap offers.\nexport { healthRoute, healthRouteHandle };\n","import type { RuntimeId } from \"./selection\";\n\nexport type RuntimeCapability =\n | \"server-rendering\"\n | \"edge-cache\"\n | \"relational-storage\"\n | \"object-storage\"\n | \"image-optimization\"\n | \"secrets\"\n | \"observability\";\n\nexport type RuntimeAdapterDefinition = {\n id: RuntimeId;\n label: string;\n status: \"active\" | \"partial\" | \"planned\";\n services: {\n compute: string;\n relationalStorage: string;\n objectStorage: string;\n imageOptimization: string;\n cache: string;\n authStorage: string;\n };\n capabilities: readonly RuntimeCapability[];\n};\n\nexport type RuntimeServiceStatus = {\n database: boolean;\n objectStorage: boolean;\n imageTransformer: boolean;\n publicCache: boolean;\n};\n\nexport const cloudflareWorkersAdapter: RuntimeAdapterDefinition = {\n id: \"cloudflare-workers\",\n label: \"Cloudflare Workers + D1\",\n status: \"active\",\n services: {\n compute: \"Cloudflare Workers via vinext\",\n relationalStorage: \"D1 through the runtime SQL adapter\",\n objectStorage: \"R2\",\n imageOptimization: \"Cloudflare Images\",\n cache: \"vinext CDN/data adapters and caches.default for media\",\n authStorage: \"D1 users and signed cookies\",\n },\n capabilities: [\n \"server-rendering\",\n \"edge-cache\",\n \"relational-storage\",\n \"object-storage\",\n \"image-optimization\",\n \"secrets\",\n \"observability\",\n ],\n};\n\nexport const runtimeAdapters = [cloudflareWorkersAdapter] as const;\n\nexport function getRuntimeAdapter(id: RuntimeAdapterDefinition[\"id\"]) {\n return runtimeAdapters.find((adapter) => adapter.id === id);\n}\n\nexport function runtimeServiceStatus(\n platform: {\n database: unknown;\n objectStorage: unknown;\n imageTransformer: unknown;\n publicCache: unknown;\n }\n): RuntimeServiceStatus {\n return {\n database: Boolean(platform.database),\n objectStorage: Boolean(platform.objectStorage),\n imageTransformer: Boolean(platform.imageTransformer),\n publicCache: Boolean(platform.publicCache),\n };\n}\n","import type { ContentModelDefinition, NotionFieldMap } from \"./model\";\nimport {\n getRuntimeAdapter,\n type RuntimeAdapterDefinition,\n} from \"../platform/capabilities\";\nimport { currentRuntimeId, type RuntimeId } from \"../platform/selection\";\n\nexport type NextionDoctorStatus = \"ok\" | \"warn\" | \"missing\";\n\nexport type EnvLike = Record<string, string | undefined>;\n\nexport type WranglerConfigLike = {\n vars?: EnvLike;\n d1_databases?: Array<{ binding?: string }>;\n r2_buckets?: Array<{ binding?: string }>;\n images?: { binding?: string } | Array<{ binding?: string }>;\n observability?: { enabled?: boolean };\n};\n\nexport type NextionDoctorCheck = {\n id: string;\n label: string;\n status: NextionDoctorStatus;\n required: boolean;\n detail: string;\n action?: string;\n};\n\nexport type NextionDoctorModel = {\n id: string;\n public: boolean;\n admin: boolean;\n listPath: string;\n detailPath: string;\n publicApiPath?: string;\n dataSourceEnv: string;\n dataSourceStatus: NextionDoctorStatus;\n dataSourceSource: \"env\" | \"default\" | \"missing\";\n};\n\nexport type NextionDoctorReport = {\n overall: {\n status: NextionDoctorStatus;\n summary: string;\n };\n runtime: {\n id: RuntimeId;\n label: string;\n adapterStatus: RuntimeAdapterDefinition[\"status\"];\n };\n checks: NextionDoctorCheck[];\n models: NextionDoctorModel[];\n nextSteps: string[];\n};\n\ntype BuildNextionDoctorReportOptions = {\n env?: EnvLike;\n runtimeId?: RuntimeId;\n wranglerConfig?: WranglerConfigLike | null;\n models?: readonly ContentModelDefinition<NotionFieldMap>[];\n};\n\nfunction envValue(env: EnvLike, name: string) {\n const value = String(env[name] ?? \"\").trim();\n return value || undefined;\n}\n\nfunction hasEnv(env: EnvLike, name: string) {\n return Boolean(envValue(env, name));\n}\n\nfunction hasD1Binding(config: WranglerConfigLike | null | undefined, binding: string) {\n return Boolean(config?.d1_databases?.some((item) => item.binding === binding));\n}\n\nfunction hasR2Binding(config: WranglerConfigLike | null | undefined, binding: string) {\n return Boolean(config?.r2_buckets?.some((item) => item.binding === binding));\n}\n\nfunction hasImagesBinding(\n config: WranglerConfigLike | null | undefined,\n binding: string\n) {\n const images = config?.images;\n if (Array.isArray(images)) {\n return images.some((item) => item.binding === binding);\n }\n return images?.binding === binding;\n}\n\nfunction statusSummary(status: NextionDoctorStatus) {\n if (status === \"ok\") return \"ready\";\n if (status === \"warn\") return \"usable with warnings\";\n return \"missing required configuration\";\n}\n\nfunction overallStatus(\n checks: readonly NextionDoctorCheck[],\n models: readonly NextionDoctorModel[]\n): NextionDoctorStatus {\n if (\n checks.some((check) => check.status === \"missing\") ||\n models.some((model) => model.dataSourceStatus === \"missing\")\n ) {\n return \"missing\";\n }\n\n if (\n checks.some((check) => check.status === \"warn\") ||\n models.some((model) => model.dataSourceStatus === \"warn\")\n ) {\n return \"warn\";\n }\n\n return \"ok\";\n}\n\nfunction cloudflareChecks(\n config: WranglerConfigLike | null | undefined\n): NextionDoctorCheck[] {\n return [\n {\n id: \"runtime.database\",\n label: \"SQL database\",\n status: hasD1Binding(config, \"DB\") ? \"ok\" : \"missing\",\n required: true,\n detail: hasD1Binding(config, \"DB\")\n ? \"wrangler D1 binding DB is declared\"\n : \"wrangler D1 binding DB is missing\",\n action: \"Add a DB binding under d1_databases in wrangler.jsonc.\",\n },\n {\n id: \"runtime.objectStorage\",\n label: \"Object storage\",\n status: hasR2Binding(config, \"ASSETS_BUCKET\") ? \"ok\" : \"warn\",\n required: false,\n detail: hasR2Binding(config, \"ASSETS_BUCKET\")\n ? \"wrangler R2 binding ASSETS_BUCKET is declared\"\n : \"uploads and persistent media cache need ASSETS_BUCKET\",\n action: \"Add an ASSETS_BUCKET R2 binding for uploads and media cache.\",\n },\n {\n id: \"runtime.imageTransformer\",\n label: \"Image transformation\",\n status: hasImagesBinding(config, \"IMAGES\") ? \"ok\" : \"warn\",\n required: false,\n detail: hasImagesBinding(config, \"IMAGES\")\n ? \"wrangler Images binding IMAGES is declared\"\n : \"Notion media optimization will fall back without IMAGES\",\n action: \"Add a Cloudflare Images binding named IMAGES.\",\n },\n {\n id: \"runtime.publicCache\",\n label: \"Public cache\",\n status: \"ok\",\n required: true,\n detail: \"vinext CDN adapter handles page cache; caches.default remains for media\",\n },\n {\n id: \"runtime.observability\",\n label: \"Observability\",\n status: config?.observability?.enabled ? \"ok\" : \"warn\",\n required: false,\n detail: config?.observability?.enabled\n ? \"wrangler observability is enabled\"\n : \"wrangler observability is not enabled\",\n action: \"Enable observability in wrangler.jsonc for production debugging.\",\n },\n ];\n}\n\nfunction notionChecks(env: EnvLike): NextionDoctorCheck[] {\n return [\n {\n id: \"notion.token\",\n label: \"Notion token\",\n status: hasEnv(env, \"NOTION_TOKEN\") ? \"ok\" : \"missing\",\n required: true,\n detail: hasEnv(env, \"NOTION_TOKEN\")\n ? \"NOTION_TOKEN is configured\"\n : \"NOTION_TOKEN is missing\",\n action: \"Set NOTION_TOKEN to an internal integration token.\",\n },\n {\n id: \"notion.webhook\",\n label: \"Notion webhook verification\",\n status: hasEnv(env, \"NOTION_WEBHOOK_VERIFICATION_TOKEN\") ? \"ok\" : \"warn\",\n required: false,\n detail: hasEnv(env, \"NOTION_WEBHOOK_VERIFICATION_TOKEN\")\n ? \"NOTION_WEBHOOK_VERIFICATION_TOKEN is configured\"\n : \"instant content invalidation needs NOTION_WEBHOOK_VERIFICATION_TOKEN\",\n action:\n \"Set NOTION_WEBHOOK_VERIFICATION_TOKEN after creating the Notion webhook.\",\n },\n ];\n}\n\nfunction modelDoctorStatus(\n env: EnvLike,\n model: ContentModelDefinition<NotionFieldMap>\n): Pick<NextionDoctorModel, \"dataSourceStatus\" | \"dataSourceSource\"> {\n const hasConfiguredEnv = hasEnv(env, model.source.dataSourceEnv);\n const hasDefault = Boolean(model.source.defaultDataSourceId);\n const dataSourceSource = hasConfiguredEnv\n ? \"env\"\n : hasDefault\n ? \"default\"\n : \"missing\";\n const dataSourceStatus = dataSourceSource === \"missing\" ? \"missing\" : \"ok\";\n\n return {\n dataSourceStatus,\n dataSourceSource,\n };\n}\n\nfunction modelChecks(\n env: EnvLike,\n models: readonly ContentModelDefinition<NotionFieldMap>[]\n): NextionDoctorModel[] {\n return models.map((model) => ({\n id: model.id,\n public: model.visibility.public,\n admin: model.visibility.admin,\n listPath: model.routes.listPath,\n detailPath: model.routes.detailPath,\n publicApiPath: model.routes.publicApiPath,\n dataSourceEnv: model.source.dataSourceEnv,\n ...modelDoctorStatus(env, model),\n }));\n}\n\nfunction uniqueActions(checks: readonly NextionDoctorCheck[]) {\n return Array.from(\n new Set(\n checks\n .filter((check) => check.status !== \"ok\" && check.action)\n .map((check) => check.action as string)\n )\n );\n}\n\nfunction omitResolvedActions(check: NextionDoctorCheck): NextionDoctorCheck {\n if (check.status !== \"ok\") return check;\n return {\n ...check,\n action: undefined,\n };\n}\n\nexport function buildNextionDoctorReport(\n options: BuildNextionDoctorReportOptions = {}\n): NextionDoctorReport {\n const env = options.env ?? process.env;\n const runtimeId = options.runtimeId ?? currentRuntimeId();\n const adapter = getRuntimeAdapter(runtimeId);\n const checks = [\n ...cloudflareChecks(options.wranglerConfig),\n ...notionChecks(env),\n ].map(omitResolvedActions);\n const models = modelChecks(env, options.models ?? []);\n const status = overallStatus(checks, models);\n const modelActions = models\n .filter((model) => model.dataSourceStatus === \"missing\")\n .map(\n (model) =>\n `Set ${model.dataSourceEnv} for the ${model.id} content model or add a model defaultDataSourceId.`\n );\n\n return {\n overall: {\n status,\n summary: statusSummary(status),\n },\n runtime: {\n id: runtimeId,\n label: adapter?.label ?? runtimeId,\n adapterStatus: adapter?.status ?? \"planned\",\n },\n checks,\n models,\n nextSteps: [...uniqueActions(checks), ...modelActions],\n };\n}\n\nfunction formatCheck(check: NextionDoctorCheck) {\n const required = check.required ? \"required\" : \"optional\";\n return ` [${check.status}] ${check.label} (${required}) - ${check.detail}`;\n}\n\nfunction formatModel(model: NextionDoctorModel) {\n const visibility = [\n model.public ? \"public\" : \"\",\n model.admin ? \"admin\" : \"\",\n ].filter(Boolean);\n const source =\n model.dataSourceSource === \"env\"\n ? model.dataSourceEnv\n : model.dataSourceSource === \"default\"\n ? `${model.dataSourceEnv} or model default`\n : model.dataSourceEnv;\n\n return [\n ` [${model.dataSourceStatus}] ${model.id} (${visibility.join(\", \") || \"private\"})`,\n ` routes: ${model.listPath}, ${model.detailPath}${\n model.publicApiPath ? `, ${model.publicApiPath}` : \"\"\n }`,\n ` notion: ${source}`,\n ].join(\"\\n\");\n}\n\nexport function formatNextionDoctorReport(report: NextionDoctorReport) {\n const lines = [\n \"vinext nextion doctor\",\n \"\",\n `Overall: [${report.overall.status}] ${report.overall.summary}`,\n `Runtime: ${report.runtime.label} (${report.runtime.id}, ${report.runtime.adapterStatus})`,\n \"\",\n \"Checks:\",\n ...report.checks.map(formatCheck),\n \"\",\n \"Content models:\",\n ...report.models.map(formatModel),\n ];\n\n if (report.nextSteps.length > 0) {\n lines.push(\"\", \"Next steps:\");\n for (const step of report.nextSteps) {\n lines.push(` - ${step}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n","// Re-export the detailed report builder used by the CLI and starter tests.\nexport {\n buildNextionDoctorReport,\n formatNextionDoctorReport,\n} from \"./doctor\";\nexport type {\n NextionDoctorCheck,\n NextionDoctorModel,\n NextionDoctorReport,\n NextionDoctorStatus,\n EnvLike,\n WranglerConfigLike,\n} from \"./doctor\";\nexport type { ContentModelDefinition, NotionFieldMap } from \"./model\";\n\nimport {\n buildNextionDoctorReport,\n formatNextionDoctorReport,\n type EnvLike,\n type NextionDoctorReport,\n type WranglerConfigLike,\n} from \"./doctor\";\n\n/**\n * Public, runtime-driven doctor API. Consumers (the worker bootstrap,\n * scripts, CI) pass a runtime that exposes binding lookups; the doctor\n * derives a synthetic wrangler config from it and runs the full report.\n *\n * `sources` is a list of project content sources. For now, the doctor\n * ignores their detailed shape; future phases will surface per-source\n * data-source status as findings.\n */\nexport type DoctorFinding = {\n code: string;\n message: string;\n severity: \"info\" | \"warning\" | \"error\";\n};\n\nexport type RuntimeLike = {\n getBinding(name: string): unknown;\n};\n\nexport type RunNextionDoctorOptions = {\n env: EnvLike;\n runtime: RuntimeLike;\n sources: readonly unknown[];\n wranglerConfig?: WranglerConfigLike | null;\n};\n\nexport type NextionDoctorFindingsReport = NextionDoctorReport & {\n findings: DoctorFinding[];\n};\n\nfunction deriveWranglerConfig(runtime: RuntimeLike): WranglerConfigLike {\n return {\n d1_databases: runtime.getBinding(\"DB\") ? [{ binding: \"DB\" }] : [],\n r2_buckets: runtime.getBinding(\"ASSETS_BUCKET\")\n ? [{ binding: \"ASSETS_BUCKET\" }]\n : [],\n images: runtime.getBinding(\"IMAGES\") ? { binding: \"IMAGES\" } : undefined,\n };\n}\n\nfunction checkToFindingCode(checkId: string): string {\n if (checkId === \"runtime.database\") return \"missing-db-binding\";\n if (checkId === \"runtime.objectStorage\") return \"missing-r2-binding\";\n if (checkId === \"runtime.imageTransformer\") return \"missing-images-binding\";\n if (checkId === \"runtime.observability\") return \"observability-disabled\";\n if (checkId === \"notion.token\") return \"missing-notion-token\";\n if (checkId === \"notion.webhook\") return \"missing-notion-webhook\";\n return checkId;\n}\n\nfunction checkToFinding(check: import(\"./doctor\").NextionDoctorCheck): DoctorFinding {\n const severity: DoctorFinding[\"severity\"] =\n check.status === \"missing\" ? \"error\" : check.status === \"warn\" ? \"warning\" : \"info\";\n return {\n code: checkToFindingCode(check.id),\n message: check.action ?? check.detail,\n severity,\n };\n}\n\nfunction modelToFinding(\n model: import(\"./doctor\").NextionDoctorModel\n): DoctorFinding | null {\n if (model.dataSourceStatus !== \"missing\") return null;\n return {\n code: `missing-data-source:${model.id}`,\n message: `Set ${model.dataSourceEnv} for the ${model.id} content model or add a model defaultDataSourceId.`,\n severity: \"error\",\n };\n}\n\nexport function runNextionDoctor(\n options: RunNextionDoctorOptions\n): NextionDoctorFindingsReport {\n const wranglerConfig = options.wranglerConfig ?? deriveWranglerConfig(options.runtime);\n const report = buildNextionDoctorReport({\n env: options.env,\n wranglerConfig,\n // Phase 6 will map `sources` (ContentSource[]) to the detailed model\n // shape used by `buildNextionDoctorReport`. For now, treat the\n // empty array as the default and rely on binding/env checks.\n models: [],\n });\n const findings: DoctorFinding[] = [\n ...report.checks\n .filter((check) => check.status !== \"ok\")\n .map(checkToFinding),\n ...report.models\n .map(modelToFinding)\n .filter((finding): finding is DoctorFinding => finding !== null),\n ];\n return { ...report, findings };\n}\n\nexport { formatNextionDoctorReport as formatDoctorReport };\n"],"mappings":"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","names":["env","env","env","options","getRuntimePlatform","getPublicCache","getRuntimePlatform","NextResponse","buildInternalUrl","NextResponse","getRuntimePlatform","GET","NextResponse","env","env","DEFAULT_WIDTH","MAX_WIDTH","DEFAULT_QUALITY","MIN_QUALITY","MAX_QUALITY","clampInt","getRuntimePlatform","response","getPublicCache","NextResponse","headers","GET","NextResponse","NextResponse","GET","env","env"]}
|
|
1
|
+
{"version":3,"sources":["../src/content/models.ts","../src/middleware.ts","../src/storage/routes/files.ts","../src/util/env.ts","../src/platform/runtime.ts","../src/platform/cloudflare-runtime.ts","../src/platform/selection.ts","../src/platform/current.ts","../src/storage/routes/cdn.ts","../src/media/routes/notion-media.ts","../src/cache/cache-keys.ts","../src/notion/client.ts","../src/notion/config.ts","../src/notion/media.ts","../src/worker/routes/health.ts","../src/internal/admin/schema-guard.ts","../src/worker/bootstrap.ts","../src/platform/capabilities.ts","../src/doctor/doctor.ts","../src/doctor/index.ts"],"sourcesContent":["// packages/nextion/src/content/models.ts\n//\n// Canonical content-source shape and a module-level registry.\n//\n// The starter's `defineContentModel` returns the same value passed in.\n// The foundation's `defineContentSource` adds a side effect: it stores\n// the value in a process-wide registry so other packages (admin pages,\n// search index, revalidation) can discover content sources without\n// reaching back into the starter.\n//\n// Existing values from the starter pass through unchanged: `ContentSource`\n// is a structural alias of the prior `ContentModelDefinition<TFields>` so\n// source field-maps stay narrowly typed through the boundary.\n\nimport type {\n NotionFieldMap,\n NotionSort,\n NotionSortDirection,\n} from \"../notion/types\";\n\nexport type { NotionFieldMap, NotionSort, NotionSortDirection };\n\n/**\n * Canonical content-source shape. The shape mirrors the starter's\n * prior `ContentModelDefinition` exactly so that registered sources\n * remain type-compatible across the package boundary.\n */\nexport type ContentModelDefinition<\n TFields extends NotionFieldMap = NotionFieldMap,\n> = {\n id: string;\n kind: \"article\" | \"catalog\" | \"directory\";\n visibility: {\n public: boolean;\n admin: boolean;\n };\n source: {\n type: \"notion\";\n tokenEnv: \"NOTION_TOKEN\";\n dataSourceEnv: string;\n defaultDataSourceId?: string;\n fields: TFields;\n query: {\n pageSize: number;\n sorts?: readonly NotionSort[];\n filterProperties?: readonly string[];\n };\n };\n routes: {\n listPath: string;\n detailPath: string;\n detailParam: string;\n publicApiPath?: string;\n };\n ui: {\n name: string;\n pluralName: string;\n navLabel: string;\n listTitle: string;\n listDescription: string;\n emptyState: string;\n };\n capabilities: {\n richBlocks: boolean;\n coverImages: boolean;\n gatedAssets: boolean;\n };\n};\n\n/**\n * Public alias for `ContentModelDefinition`. External consumers import\n * this name from `@notionx/core/content`; the internal\n * `ContentModelDefinition` name remains available for the starter's\n * `model.ts`.\n */\nexport type ContentSource<\n TFields extends NotionFieldMap = NotionFieldMap,\n> = ContentModelDefinition<TFields>;\n\nconst registry: ContentSource[] = [];\n\n/**\n * Register a content source. Returns the value unchanged. Re-registering\n * the same `id` replaces the prior value (idempotent on the id, useful\n * for HMR + tests).\n */\nexport function defineContentSource<const TFields extends NotionFieldMap>(\n model: ContentModelDefinition<TFields>\n): ContentModelDefinition<TFields> {\n const existing = registry.findIndex((s) => s.id === model.id);\n if (existing >= 0) registry[existing] = model;\n else registry.push(model);\n return model;\n}\n\nexport function getRegisteredSources(): readonly ContentSource[] {\n return registry;\n}\n\nexport function getRegisteredSource(id: string): ContentSource | undefined {\n return registry.find((s) => s.id === id);\n}\n\n/**\n * Test-only escape hatch: empties the registry so vitest cases do not\n * leak state between files. Not for production use.\n */\nexport function clearRegistryForTests(): void {\n registry.length = 0;\n}\n","// Foundation request-scoped middleware.\n//\n// Responsibilities:\n// 1. Read the session cookie named in `authConfig.sessionCookie.name`.\n// 2. Look up the session row in D1 (via the optional `sessionLookup`\n// injection; falls back to a no-op when the consumer has not\n// provided one).\n// 3. Attach a viewer to a request-scoped context (a WeakMap keyed\n// by the Request instance) so downstream handlers can read it\n// without reaching into globals.\n// 4. For protected admin patterns (`/api/admin/*` for any method,\n// and non-GET requests to `/admin*`), short-circuit with a 401\n// when there is no viewer.\n//\n// This module is intentionally tier-7 (top-level). It can reach into\n// the auth tier because the eslint tier rules reserve that direction\n// for higher-level glue code.\n\nimport type { AuthConfig } from \"./types\";\n\n/**\n * Minimal viewer shape exposed by the foundation middleware. Phase 3\n * replaces this placeholder with the richer `AuthViewer` once\n * `createAuth` ships its real implementation.\n */\nexport interface FoundationViewer {\n userId: string | number;\n role: string | null;\n email: string | null;\n}\n\n/**\n * Resolves a session id to the user/role pair backing the viewer.\n * Callers can inject a custom lookup so they can read from their own\n * sessions table layout (the foundation only knows the table name).\n */\nexport type SessionLookup = (\n sessionId: string,\n env: unknown\n) => Promise<{ userId: string | number; role: string | null; email?: string | null } | null>;\n\n/**\n * Caller-supplied options. `WorkerOptions.siteConfig` etc. are passed\n * through the bootstrap; the middleware itself only needs the auth\n * block and an optional viewer lookup.\n */\nexport interface NextionMiddlewareOptions {\n authConfig: AuthConfig;\n /**\n * Optional session resolver. When provided, the middleware uses\n * this to look up the session row. When omitted, the middleware\n * still runs (to enforce the admin gate) but always reports no\n * viewer — useful for the bootstrap's test environment and for\n * consumers that have not yet wired their D1 session table.\n */\n sessionLookup?: SessionLookup;\n /**\n * Override which paths are gated. The default is nextion's\n * own contract: `/api/admin/*` for any method, and non-GET\n * requests to `/admin*` (GET `/admin*` is left for the app\n * router so the admin shell can render a login redirect).\n */\n isProtectedPath?: (request: Request) => boolean;\n}\n\nexport interface NextionMiddlewareRequestContext {\n viewer: FoundationViewer | null;\n sessionId: string | null;\n}\n\nconst requestContext = new WeakMap<Request, NextionMiddlewareRequestContext>();\n\n/**\n * Default admin gate. Returns `true` for paths that require a viewer\n * before any handler runs. Excludes the GET form of `/admin*` so the\n * app router can render the admin shell and decide whether to\n * redirect to `/login` (matching the existing behavior).\n */\nexport function defaultIsProtectedPath(request: Request): boolean {\n const url = new URL(request.url);\n if (url.pathname.startsWith(\"/api/admin/\")) return true;\n if (url.pathname.startsWith(\"/admin\")) {\n return request.method !== \"GET\" && request.method !== \"HEAD\";\n }\n return false;\n}\n\nfunction readSessionCookie(request: Request, name: string): string | null {\n // The Cookie header may be absent in pure unit tests. Guard so the\n // middleware never throws.\n const header = request.headers.get(\"cookie\");\n if (!header) return null;\n\n for (const part of header.split(\";\")) {\n const [rawKey, ...rest] = part.split(\"=\");\n if (!rawKey) continue;\n if (rawKey.trim() !== name) continue;\n return rest.join(\"=\").trim() || null;\n }\n return null;\n}\n\n/**\n * Build the request-scoped context for `request`. Returns a viewer\n * (or `null` when no session is present / no lookup is configured).\n */\nexport async function resolveFoundationViewer(\n request: Request,\n options: NextionMiddlewareOptions\n): Promise<NextionMiddlewareRequestContext> {\n const sessionId = readSessionCookie(\n request,\n options.authConfig.sessionCookie.name\n );\n\n if (!sessionId || !options.sessionLookup) {\n return { viewer: null, sessionId: sessionId ?? null };\n }\n\n const env = (request as Request & { env?: unknown }).env ?? null;\n const lookup = await options.sessionLookup(sessionId, env);\n if (!lookup) {\n return { viewer: null, sessionId };\n }\n\n return {\n viewer: {\n userId: lookup.userId,\n role: lookup.role,\n email: lookup.email ?? null,\n },\n sessionId,\n };\n}\n\n/**\n * Read the cached context for a request. Returns `undefined` when\n * `resolveFoundationViewer` was never run for this Request — i.e.\n * the request did not pass through `nextionMiddleware`.\n */\nexport function getFoundationContext(\n request: Request\n): NextionMiddlewareRequestContext | undefined {\n return requestContext.get(request);\n}\n\n/**\n * Worker-style middleware. Returns a 401 `Response` for protected\n * paths that lack a viewer; otherwise `null` to let the request\n * continue through the route table.\n *\n * Side effect: attaches a `NextionMiddlewareRequestContext` to\n * the request via a `WeakMap` so downstream route handlers can\n * resolve the viewer without a global.\n */\nexport async function nextionMiddleware(\n request: Request,\n env: unknown,\n options: NextionMiddlewareOptions\n): Promise<Response | null> {\n const context = await resolveFoundationViewer(request, options);\n requestContext.set(request, context);\n\n const isProtected = options.isProtectedPath ?? defaultIsProtectedPath;\n if (!isProtected(request)) return null;\n if (context.viewer) return null;\n\n return new Response(\n JSON.stringify({ ok: false, error: \"Unauthorized\" }),\n {\n status: 401,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"no-store\",\n \"X-Foundation-Gate\": \"admin\",\n },\n }\n );\n}\n\n/**\n * Build a `(request, env) => Promise<Response | null>` middleware\n * function with the options pre-bound. Used by the bootstrap and\n * the starter's Next.js middleware so they share one code path.\n */\nexport function createNextionMiddleware(\n options: NextionMiddlewareOptions\n): (request: Request, env: unknown) => Promise<Response | null> {\n return (request, env) => nextionMiddleware(request, env, options);\n}\n\n/**\n * Test-only: clear the request-scoped context cache. Production\n * code should not need this — the WeakMap is keyed by Request\n * instances and entries die with the request. The helper exists so\n * the unit tests can stay deterministic when they reuse a single\n * Request across assertions.\n */\nexport function _clearFoundationContextForTests(): void {\n // WeakMap has no `clear`, but we can replace the reference.\n // The function is intentionally a no-op when no entries exist.\n void requestContext;\n}\n","// storage/routes/files.ts\n//\n// GET /api/files/[...key] - 从对象存储取文件并代理返回\n// 不公开 bucket/container,避免任何人都能列文件\n//\n// The package exports a route object with two surfaces:\n// - `GET` is the Next.js handler signature (request, { params }).\n// - `handle` is a single `(request) => Response` form used by the\n// Cloudflare Workers bootstrap in `@notionx/core/worker`.\n//\n// The bodies are identical; only the way the object key is read differs.\n\nimport { NextResponse } from \"next/server\";\nimport { getRuntimePlatform } from \"../../platform/current\";\n\nexport const dynamic = \"force-dynamic\";\n\ntype Props = {\n params: Promise<{ key: string[] }>;\n};\n\nexport const filesRoute = {\n /**\n * Next.js handler for `app/api/files/[...key]/route.ts`. Receives the\n * catch-all key from the route params.\n */\n async GET(_request: Request, props: Props) {\n const { key } = await props.params;\n return filesRoute.handle(new Request(buildInternalUrl(_request, key)));\n },\n /**\n * Worker-friendly handler. Extracts the catch-all key from the URL\n * pathname (`/api/files/<key>`).\n */\n async handle(request: Request): Promise<Response> {\n const key = readKeyFromUrl(request.url);\n if (!key) {\n return NextResponse.json({ error: \"Invalid key\" }, { status: 400 });\n }\n\n if (key.includes(\"..\") || key.startsWith(\"/\")) {\n return NextResponse.json({ error: \"Invalid key\" }, { status: 400 });\n }\n\n const storage = getRuntimePlatform().objectStorage;\n if (!storage) {\n return NextResponse.json(\n { error: \"Object storage not configured\" },\n { status: 503 }\n );\n }\n\n const object = await storage.get(key);\n if (!object) {\n return NextResponse.json({ error: \"Not found\" }, { status: 404 });\n }\n\n const headers = new Headers();\n if (object.contentType) {\n headers.set(\"Content-Type\", object.contentType);\n }\n headers.set(\"Cache-Control\", \"public, max-age=31536000, immutable\");\n if (object.etag) headers.set(\"ETag\", object.etag);\n headers.set(\"Content-Length\", String(object.size));\n\n return new Response(object.body, { headers });\n },\n};\n\n/**\n * Worker-friendly single-arg handler. Used by the Cloudflare Workers\n * bootstrap in `@notionx/core/worker`. Equivalent to\n * `filesRoute.handle`.\n */\nexport async function filesRouteHandle(request: Request): Promise<Response> {\n return filesRoute.handle(request);\n}\n\nfunction buildInternalUrl(request: Request, keyParts: string[]) {\n const url = new URL(request.url);\n url.pathname = `/api/files/${keyParts.map(encodeURIComponent).join(\"/\")}`;\n return url.toString();\n}\n\nfunction readKeyFromUrl(rawUrl: string): string | null {\n const url = new URL(rawUrl);\n const prefix = \"/api/files/\";\n if (!url.pathname.startsWith(prefix)) return null;\n const encoded = url.pathname.slice(prefix.length);\n if (!encoded) return null;\n return decodeURIComponent(encoded);\n}\n\n// Re-export as top-level function aliases for callers that prefer a\n// flat signature (e.g. the Next.js app/api/.../route.ts delegates).\nexport const GET = filesRoute.GET;\n","// lib/env.ts - 集中获取 Cloudflare bindings\n// 用 cloudflare:workers 模块(workerd 内置),作为平台 adapter 的绑定入口\n\n/// <reference types=\"@cloudflare/workers-types\" />\nimport { env } from \"cloudflare:workers\";\n\nexport type AppEnv = {\n DB: D1Database;\n ASSETS: Fetcher;\n IMAGES: ImagesBinding;\n ASSETS_BUCKET?: R2Bucket;\n CONTENT_CACHE?: KVNamespace;\n ADMIN_PASSWORD: string;\n ADMIN_EMAIL?: string;\n SITE_URL?: string;\n RESEND_API_KEY?: string;\n RESEND_FROM?: string;\n // Google OAuth 仍然兼容 Cloudflare Secret 作为兜底。\n // 实际生效值以 app_settings.google_client_id / google_client_secret 为准。\n GOOGLE_CLIENT_ID?: string;\n GOOGLE_CLIENT_SECRET?: string;\n /** Turnstile site key fallback when not stored in app_settings */\n TURNSTILE_SITE_KEY?: string;\n /** Turnstile secret — set via `wrangler secret put TURNSTILE_SECRET_KEY` */\n TURNSTILE_SECRET_KEY?: string;\n /** Notion integration token for the blog data source */\n NOTION_TOKEN?: string;\n /** Notion data source ID used by dataSources.query */\n NOTION_DATA_SOURCE_ID?: string;\n /** Optional Notion API base URL for tests or proxies */\n NOTION_API_BASE_URL?: string;\n /** Optional Notion edit URL for admin handoff screens */\n NOTION_EDIT_BASE_URL?: string;\n /** Optional webhook verification token for Notion invalidation */\n NOTION_WEBHOOK_VERIFICATION_TOKEN?: string;\n};\n\n// 强制类型:vinext 把 env 类型放在 env.d.ts(interface VinextEnv extends Env),\n// 但 TS server 经常解析不到。运行时一定有 DB,类型断言保证编译通过。\nexport const workerEnv = env as unknown as AppEnv;\n","import type { AppEnv } from \"../util/env\";\n\nexport type PlatformBindingEnv = Pick<\n AppEnv,\n \"ASSETS_BUCKET\" | \"CONTENT_CACHE\" | \"DB\" | \"IMAGES\"\n>;\n\nexport type StoredObject = {\n body: ReadableStream;\n size: number;\n etag?: string;\n contentType?: string;\n};\n\nexport type ObjectStoragePutOptions = {\n contentType?: string;\n cacheControl?: string;\n metadata?: Record<string, string>;\n};\n\nexport type ObjectStorageListItem = {\n key: string;\n size: number;\n uploaded: Date;\n};\n\nexport type ObjectStorageAdapter = {\n kind: \"r2\";\n get(key: string): Promise<StoredObject | null>;\n put(\n key: string,\n value: ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob,\n options?: ObjectStoragePutOptions\n ): Promise<void>;\n delete(key: string): Promise<void>;\n list(\n options?: { prefix?: string; limit?: number }\n ): Promise<ObjectStorageListItem[]>;\n};\n\nexport type ImageTransformOptions = {\n width?: number;\n format: \"image/avif\" | \"image/webp\";\n quality: number;\n};\n\nexport type ImageTransformResult = {\n body: ReadableStream;\n contentType: string;\n response(): Response;\n};\n\nexport type ImageTransformerAdapter = {\n kind: \"cloudflare-images\" | \"external\";\n transform(\n body: ReadableStream,\n options: ImageTransformOptions\n ): Promise<ImageTransformResult>;\n};\n\nexport type PublicCacheAdapter = {\n kind: \"cloudflare-cache\" | \"noop\" | \"external\";\n match(key: string): Promise<Response | null>;\n put(key: string, response: Response): Promise<void>;\n delete(key: string): Promise<boolean>;\n};\n\nexport type KeyValueCacheGetOptions = {\n cacheTtl?: number;\n};\n\nexport type KeyValueCachePutOptions = {\n expirationTtl?: number;\n metadata?: Record<string, string | number | boolean | null>;\n};\n\nexport type KeyValueCacheListOptions = {\n prefix?: string;\n limit?: number;\n cursor?: string;\n};\n\nexport type KeyValueCacheListResult = {\n keys: Array<{ name: string }>;\n cursor?: string;\n listComplete: boolean;\n};\n\nexport type KeyValueCacheAdapter = {\n kind: \"workers-kv\" | \"noop\" | \"external\";\n get<T = unknown>(\n key: string,\n options?: KeyValueCacheGetOptions\n ): Promise<T | null>;\n put<T = unknown>(\n key: string,\n value: T,\n options?: KeyValueCachePutOptions\n ): Promise<void>;\n delete(key: string): Promise<void>;\n list(options?: KeyValueCacheListOptions): Promise<KeyValueCacheListResult>;\n};\n\nexport type SqlValue = string | number | boolean | null;\n\nexport type SqlResult<T = Record<string, unknown>> = {\n results?: T[];\n success?: boolean;\n meta?: {\n changes?: number;\n duration?: number;\n last_row_id?: number;\n rows_read?: number;\n rows_written?: number;\n [key: string]: unknown;\n };\n};\n\nexport type SqlPreparedStatement = {\n bind(...values: SqlValue[]): SqlPreparedStatement;\n all<T = Record<string, unknown>>(): Promise<SqlResult<T>>;\n first<T = Record<string, unknown>>(columnName?: string): Promise<T | null>;\n run<T = Record<string, unknown>>(): Promise<SqlResult<T>>;\n};\n\nexport type SqlDatabaseAdapter = {\n kind: \"d1\";\n prepare(query: string): SqlPreparedStatement;\n batch<T = Record<string, unknown>>(\n statements: SqlPreparedStatement[]\n ): Promise<SqlResult<T>[]>;\n};\n\nexport type RuntimePlatform = {\n id: \"cloudflare-workers\";\n database: SqlDatabaseAdapter | null;\n objectStorage: ObjectStorageAdapter | null;\n imageTransformer: ImageTransformerAdapter | null;\n publicCache: PublicCacheAdapter | null;\n keyValueCache: KeyValueCacheAdapter | null;\n};\n\ntype CloudflareCacheLike = Pick<Cache, \"match\" | \"put\" | \"delete\">;\ntype CloudflareKvLike = Pick<KVNamespace, \"get\" | \"put\" | \"delete\" | \"list\">;\n\nfunction cacheRequestForKey(key: string) {\n return new Request(key, { method: \"GET\" });\n}\n\nexport function createCloudflarePublicCacheAdapter(\n cache: CloudflareCacheLike\n): PublicCacheAdapter {\n return {\n kind: \"cloudflare-cache\",\n async match(key) {\n return (await cache.match(cacheRequestForKey(key))) ?? null;\n },\n put(key, response) {\n return cache.put(cacheRequestForKey(key), response);\n },\n delete(key) {\n return cache.delete(cacheRequestForKey(key));\n },\n };\n}\n\nexport function createNoopPublicCacheAdapter(kind: \"noop\" = \"noop\"): PublicCacheAdapter {\n return {\n kind,\n async match() {\n return null;\n },\n async put() {},\n async delete() {\n return false;\n },\n };\n}\n\nexport function createCloudflareKeyValueCacheAdapter(\n namespace: CloudflareKvLike\n): KeyValueCacheAdapter {\n return {\n kind: \"workers-kv\",\n async get<T = unknown>(\n key: string,\n options?: KeyValueCacheGetOptions\n ): Promise<T | null> {\n return (await namespace.get(key, {\n type: \"json\",\n cacheTtl: options?.cacheTtl,\n })) as T | null;\n },\n async put(key, value, options) {\n await namespace.put(key, JSON.stringify(value), {\n expirationTtl: options?.expirationTtl,\n metadata: options?.metadata,\n });\n },\n delete(key) {\n return namespace.delete(key);\n },\n async list(options) {\n const result = await namespace.list({\n prefix: options?.prefix,\n limit: options?.limit,\n cursor: options?.cursor,\n });\n return {\n keys: result.keys.map((key) => ({ name: key.name })),\n cursor: result.list_complete ? undefined : result.cursor,\n listComplete: result.list_complete,\n };\n },\n };\n}\n\nexport function createNoopKeyValueCacheAdapter(\n kind: \"noop\" = \"noop\"\n): KeyValueCacheAdapter {\n return {\n kind,\n async get() {\n return null;\n },\n async put() {},\n async delete() {},\n async list() {\n return { keys: [], listComplete: true };\n },\n };\n}\n\nfunction r2ObjectToStoredObject(object: R2ObjectBody): StoredObject {\n return {\n body: object.body,\n size: object.size,\n etag: object.etag,\n contentType: object.httpMetadata?.contentType,\n };\n}\n\nexport function createCloudflareRuntimePlatform(\n env: PlatformBindingEnv,\n options?: { publicCache?: CloudflareCacheLike | null }\n): RuntimePlatform {\n const database: SqlDatabaseAdapter | null = env.DB\n ? ({\n kind: \"d1\",\n prepare(query: string) {\n return env.DB.prepare(query) as unknown as SqlPreparedStatement;\n },\n async batch(statements: SqlPreparedStatement[]) {\n return (await env.DB.batch(\n statements as unknown as D1PreparedStatement[]\n )) as unknown as SqlResult<Record<string, unknown>>[];\n },\n } as unknown as SqlDatabaseAdapter)\n : null;\n\n const objectStorage: ObjectStorageAdapter | null = env.ASSETS_BUCKET\n ? {\n kind: \"r2\",\n async get(key) {\n const object = await env.ASSETS_BUCKET?.get(key);\n return object ? r2ObjectToStoredObject(object) : null;\n },\n async put(key, value, options) {\n await env.ASSETS_BUCKET?.put(key, value, {\n httpMetadata: {\n contentType: options?.contentType,\n cacheControl: options?.cacheControl,\n },\n customMetadata: options?.metadata,\n });\n },\n async delete(key) {\n await env.ASSETS_BUCKET?.delete(key);\n },\n async list(options) {\n const listed = await env.ASSETS_BUCKET?.list({\n prefix: options?.prefix,\n limit: options?.limit,\n });\n return (\n listed?.objects.map((object) => ({\n key: object.key,\n size: object.size,\n uploaded: object.uploaded,\n })) ?? []\n );\n },\n }\n : null;\n\n const imageTransformer: ImageTransformerAdapter | null = env.IMAGES\n ? {\n kind: \"cloudflare-images\",\n async transform(body, options) {\n const result = await env.IMAGES.input(body)\n .transform(options.width ? { width: options.width } : {})\n .output({\n format: options.format,\n quality: options.quality,\n });\n return {\n body: result.image(),\n contentType: result.contentType(),\n response: () => result.response(),\n };\n },\n }\n : null;\n\n const keyValueCache: KeyValueCacheAdapter | null = env.CONTENT_CACHE\n ? createCloudflareKeyValueCacheAdapter(env.CONTENT_CACHE)\n : null;\n\n return {\n id: \"cloudflare-workers\",\n database,\n objectStorage,\n imageTransformer,\n keyValueCache,\n publicCache: options?.publicCache\n ? createCloudflarePublicCacheAdapter(options.publicCache)\n : null,\n };\n}\n","import { workerEnv } from \"../util/env\";\nimport {\n createCloudflarePublicCacheAdapter,\n createCloudflareRuntimePlatform,\n} from \"./runtime\";\n\nfunction getDefaultCloudflareCache() {\n const globalWithCaches = globalThis as typeof globalThis & {\n caches?: CacheStorage & { default?: Cache };\n };\n return globalWithCaches.caches?.default ?? null;\n}\n\nexport function getRuntimePlatform() {\n return createCloudflareRuntimePlatform(workerEnv, {\n publicCache: getDefaultCloudflareCache(),\n });\n}\n\nexport function getDatabase() {\n const database = getRuntimePlatform().database;\n if (!database) {\n throw new Error(\"SQL database binding not configured\");\n }\n return database;\n}\n\nexport function getPublicCache() {\n const cache = getDefaultCloudflareCache();\n if (!cache) {\n throw new Error(\"Cloudflare cache binding not configured\");\n }\n return createCloudflarePublicCacheAdapter(cache);\n}\n","export type RuntimeId = \"cloudflare-workers\";\n\nexport type RuntimeKind = \"cloudflare\";\n\nexport interface RuntimeSelection {\n kind: RuntimeKind;\n runtimeId: RuntimeId;\n}\n\nfunction hasCloudflareBindings(env: unknown): boolean {\n if (!env || typeof env !== \"object\") return false;\n const record = env as Record<string, unknown>;\n return (\n \"DB\" in record ||\n \"ASSETS_BUCKET\" in record ||\n \"R2\" in record ||\n \"IMAGES\" in record ||\n \"CONTENT_CACHE\" in record\n );\n}\n\nexport function selectRuntime(env: unknown): RuntimeSelection {\n if (hasCloudflareBindings(env)) {\n return { kind: \"cloudflare\", runtimeId: \"cloudflare-workers\" };\n }\n throw new Error(\n \"No supported runtime detected. Expected Cloudflare Workers bindings \" +\n \"(DB, ASSETS_BUCKET, R2, IMAGES, or CONTENT_CACHE).\",\n );\n}\n\nexport function currentRuntimeId(): RuntimeId {\n return \"cloudflare-workers\";\n}\n","import {\n getPublicCache as getCloudflarePublicCache,\n getRuntimePlatform as getCloudflareRuntimePlatform,\n} from \"./cloudflare-runtime\";\nimport { currentRuntimeId } from \"./selection\";\n\nexport function getRuntimePlatform() {\n return getCloudflareRuntimePlatform();\n}\n\nexport function getDatabase() {\n const platform = getRuntimePlatform();\n const database = platform.database;\n if (!database) {\n throw new Error(`SQL database adapter not configured for ${platform.id}`);\n }\n return database;\n}\n\nexport function getPublicCache() {\n return getCloudflarePublicCache();\n}\n\nexport function getKeyValueCache() {\n return getRuntimePlatform().keyValueCache;\n}\n\nexport const runtimeSelection = {\n currentRuntimeId,\n};\n","// storage/routes/cdn.ts\n//\n// GET /api/cdn/[...key] - 从对象存储取原图,调用运行时图片转换转 WebP/AVIF 后返回\n//\n// 用 catch-all 参数兼容两种 URL:\n// 1. /api/cdn/uploads/2026-06-06/file.jpg\n// 2. /api/cdn/uploads%2F2026-06-06%2Ffile.jpg\n//\n// Like the other storage route, the package exports a route object that\n// exposes both a Next.js handler and a worker-friendly single-arg\n// handler. The body is otherwise identical to the original starter\n// implementation.\n\nimport { NextResponse } from \"next/server\";\nimport {\n type StoredObject,\n} from \"../../platform/runtime\";\nimport { getRuntimePlatform } from \"../../platform/current\";\n\nexport const dynamic = \"force-dynamic\";\n\nconst DEFAULT_WIDTH = 1200;\nconst MAX_WIDTH = 2400;\nconst DEFAULT_QUALITY = 75;\nconst MIN_QUALITY = 40;\nconst MAX_QUALITY = 85;\n\ntype Props = {\n params: Promise<{ key: string[] }>;\n};\n\nexport const cdnRoute = {\n async GET(request: Request, props: Props) {\n const { key } = await props.params;\n return cdnRoute.handle(new Request(buildInternalUrl(request, key)));\n },\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const key = readKeyFromPathname(url.pathname);\n if (!key) {\n return NextResponse.json({ error: \"Invalid key\" }, { status: 400 });\n }\n\n if (key.includes(\"..\") || key.startsWith(\"/\")) {\n return NextResponse.json({ error: \"Invalid key\" }, { status: 400 });\n }\n\n const platform = getRuntimePlatform();\n const storage = platform.objectStorage;\n if (!storage) {\n return NextResponse.json(\n { error: \"Object storage not configured\" },\n { status: 503 }\n );\n }\n\n const object = await storage.get(key);\n if (!object) {\n return NextResponse.json({ error: \"Not found\" }, { status: 404 });\n }\n\n const accept = request.headers.get(\"accept\") ?? \"\";\n const isImage = object.contentType?.startsWith(\"image/\") ?? false;\n\n if (!isImage) {\n return streamObject(object, {\n \"X-Debug-Cdn-Branch\": \"non-image\",\n \"X-Debug-Cdn-Key\": key,\n });\n }\n\n let outputFormat: \"image/avif\" | \"image/webp\" | null = null;\n let outputQuality: number | undefined = undefined;\n\n if (accept.includes(\"image/avif\")) {\n outputFormat = \"image/avif\";\n outputQuality = 60;\n } else if (accept.includes(\"image/webp\")) {\n outputFormat = \"image/webp\";\n outputQuality = 75;\n }\n\n const isSvg = object.contentType === \"image/svg+xml\";\n if (!outputFormat || isSvg || !platform.imageTransformer) {\n return streamObject(object, {\n \"X-Debug-Cdn-Branch\": isSvg\n ? \"svg-bypass\"\n : !platform.imageTransformer\n ? \"transformer-bypass\"\n : \"format-bypass\",\n \"X-Debug-Cdn-Accept\": accept.includes(\"image/avif\")\n ? \"avif\"\n : accept.includes(\"image/webp\")\n ? \"webp\"\n : \"other\",\n \"X-Debug-Cdn-Key\": key,\n });\n }\n\n try {\n const width = clampInt(\n url.searchParams.get(\"w\"),\n 64,\n MAX_WIDTH,\n DEFAULT_WIDTH\n );\n const quality = clampInt(\n url.searchParams.get(\"q\"),\n MIN_QUALITY,\n MAX_QUALITY,\n outputQuality ?? DEFAULT_QUALITY\n );\n\n const result = await platform.imageTransformer.transform(object.body, {\n width,\n format: outputFormat,\n quality,\n });\n\n return new Response(result.body, {\n headers: {\n \"Content-Type\": result.contentType,\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n Vary: \"Accept\",\n \"X-Debug-Cdn-Branch\": \"transformed\",\n \"X-Debug-Cdn-Key\": key,\n \"X-Optimized-Width\": String(width),\n \"X-Optimized-Quality\": String(quality),\n \"X-Original-Format\": object.contentType ?? \"unknown\",\n \"X-Optimized-Format\": outputFormat,\n },\n });\n } catch (e) {\n return streamObject(object, {\n \"X-Debug-Cdn-Branch\": \"transform-error-fallback\",\n \"X-Debug-Cdn-Key\": key,\n \"X-Debug-Cdn-Error\": e instanceof Error ? e.name : \"unknown\",\n });\n }\n },\n};\n\nfunction buildInternalUrl(request: Request, keyParts: string[]) {\n const url = new URL(request.url);\n url.pathname = `/api/cdn/${keyParts.map(encodeURIComponent).join(\"/\")}`;\n return url.toString();\n}\n\nfunction readKeyFromPathname(pathname: string): string | null {\n const prefix = \"/api/cdn/\";\n if (!pathname.startsWith(prefix)) return null;\n const encoded = pathname.slice(prefix.length);\n if (!encoded) return null;\n return decodeURIComponent(encoded);\n}\n\nfunction clampInt(\n value: string | null,\n min: number,\n max: number,\n fallback: number\n) {\n const parsed = Number.parseInt(value ?? \"\", 10);\n if (!Number.isFinite(parsed)) {\n return fallback;\n }\n\n return Math.max(min, Math.min(max, parsed));\n}\n\nfunction streamObject(\n object: StoredObject,\n extraHeaders?: Record<string, string>\n): Response {\n const headers = new Headers();\n if (object.contentType) {\n headers.set(\"Content-Type\", object.contentType);\n }\n headers.set(\"Cache-Control\", \"public, max-age=31536000, immutable\");\n headers.set(\"Content-Length\", String(object.size));\n if (object.etag) headers.set(\"ETag\", object.etag);\n for (const [key, value] of Object.entries(extraHeaders ?? {})) {\n headers.set(key, value);\n }\n return new Response(object.body, { headers });\n}\n\n// Top-level aliases for callers that prefer a flat signature (e.g. the\n// Next.js `app/api/.../route.ts` delegates).\nexport const GET = cdnRoute.GET;\n\n/**\n * Worker-friendly single-arg handler. Used by the Cloudflare Workers\n * bootstrap in `@notionx/core/worker`. Equivalent to\n * `cdnRoute.handle`.\n */\nexport async function cdnRouteHandle(request: Request): Promise<Response> {\n return cdnRoute.handle(request);\n}\n","// media/routes/notion-media.ts\n//\n// GET /api/notion/media/[...ref] - Notion-hosted media proxy with\n// edge cache, R2 fanout, and image transformation.\n//\n// The package exports a route object that exposes both a Next.js\n// handler (the `GET` field) and a worker-friendly `handle` function.\n//\n// The `vinext/shims/request-context` module is treated as a runtime\n// shim by the starter and the Cloudflare Workers runtime; it is\n// listed in the package's tsup `external` config so it is resolved by\n// the consumer at runtime.\n\nimport { NextResponse } from \"next/server\";\nimport { getRequestExecutionContext } from \"vinext/shims/request-context\";\nimport {\n notionMediaR2KeyForUrl,\n publicMediaCacheKeyForUrl,\n publicMediaVariantForAccept,\n type PublicMediaVariant,\n} from \"../../cache\";\nimport { createNotionClient } from \"../../notion/client\";\nimport { getNotionClientConfig } from \"../../notion/config\";\nimport {\n fileObjectForMediaBlock,\n normalizeNotionFileSource,\n pickFirstFilesPropertyValue,\n} from \"../../notion/media\";\nimport type { NotionBlock, NotionPageLike } from \"../../notion/types\";\nimport {\n getPublicCache,\n getRuntimePlatform,\n} from \"../../platform/current\";\n\nexport const dynamic = \"force-dynamic\";\n\nconst DEFAULT_WIDTH = 1200;\nconst MAX_WIDTH = 2400;\nconst DEFAULT_QUALITY = 75;\nconst MIN_QUALITY = 40;\nconst MAX_QUALITY = 85;\nconst CACHEABLE_STATUS = new Set([200]);\n\ntype Props = {\n params: Promise<{ ref: string[] }>;\n};\n\nexport const notionMediaRoute = {\n async GET(_request: Request, props: Props) {\n const { ref } = await props.params;\n if (ref.some((part) => part === \"..\" || part.includes(\"/\"))) {\n return badRequest();\n }\n const url = new URL(_request.url);\n url.pathname = buildNotionMediaPath(ref);\n return notionMediaRoute.handle(new Request(url.toString(), _request));\n },\n async handle(request: Request): Promise<Response> {\n const variant = publicMediaVariantForAccept(\n request.headers.get(\"accept\") ?? \"\"\n );\n return withEdgeMediaCache(request, variant, () => loadMedia(request));\n },\n};\n\nfunction buildNotionMediaPath(ref: string[]) {\n return `/api/notion/media/${ref.map(encodeURIComponent).join(\"/\")}`;\n}\n\nfunction clampInt(\n value: string | null,\n min: number,\n max: number,\n fallback: number\n) {\n const parsed = Number.parseInt(value ?? \"\", 10);\n if (!Number.isFinite(parsed)) return fallback;\n return Math.max(min, Math.min(max, parsed));\n}\n\nfunction cacheControl(request: Request) {\n const url = new URL(request.url);\n if (url.searchParams.has(\"v\")) {\n return \"public, max-age=31536000, immutable\";\n }\n\n return \"public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400\";\n}\n\nfunction canUseMediaCache(request: Request) {\n if (request.method !== \"GET\") return false;\n return !request.headers.has(\"range\");\n}\n\nfunction mediaCacheHeaders(\n response: Response,\n request: Request,\n state: \"HIT\" | \"MISS\" | \"BYPASS\",\n r2State?: \"HIT\" | \"MISS\" | \"BYPASS\"\n) {\n const headers = new Headers(response.headers);\n headers.set(\"Cache-Control\", cacheControl(request));\n headers.set(\"X-Notion-Media-Cache\", state);\n if (r2State) headers.set(\"X-Notion-Media-R2\", r2State);\n return headers;\n}\n\nasync function responseFromR2Cache(\n request: Request,\n variant: PublicMediaVariant\n) {\n const url = new URL(request.url);\n const r2Key = notionMediaR2KeyForUrl(url, variant);\n const storage = getRuntimePlatform().objectStorage;\n if (!r2Key || !storage) return null;\n\n const object = await storage.get(r2Key);\n if (!object?.body) return null;\n\n const contentType =\n object.contentType ?? (variant === \"avif\" ? \"image/avif\" : \"image/webp\");\n const headers = new Headers();\n headers.set(\"Content-Type\", contentType);\n headers.set(\"Cache-Control\", cacheControl(request));\n headers.set(\"Vary\", \"Accept\");\n headers.set(\"X-Notion-Media-Branch\", \"r2\");\n headers.set(\"X-Notion-Media-R2\", \"HIT\");\n if (object.etag) headers.set(\"ETag\", object.etag);\n\n return new Response(object.body, { headers });\n}\n\nasync function withEdgeMediaCache(\n request: Request,\n variant: PublicMediaVariant,\n load: () => Promise<Response>\n) {\n if (!canUseMediaCache(request)) {\n const response = await load();\n return new Response(response.body, {\n status: response.status,\n headers: mediaCacheHeaders(response, request, \"BYPASS\"),\n });\n }\n\n const url = new URL(request.url);\n const cache = getPublicCache();\n const cacheKey = publicMediaCacheKeyForUrl(url, variant);\n const cached = await cache.match(cacheKey);\n if (cached) {\n return new Response(cached.body, {\n status: cached.status,\n headers: mediaCacheHeaders(cached, request, \"HIT\"),\n });\n }\n\n const r2Response = await responseFromR2Cache(request, variant);\n const response = r2Response ?? (await load());\n const headers = mediaCacheHeaders(response, request, \"MISS\");\n const output = new Response(response.body, {\n status: response.status,\n headers,\n });\n\n if (CACHEABLE_STATUS.has(response.status)) {\n const toCache = output.clone();\n getRequestExecutionContext()?.waitUntil(cache.put(cacheKey, toCache));\n }\n\n return output;\n}\n\nfunction mediaRedirect(url: string) {\n const response = NextResponse.redirect(url, 302);\n response.headers.set(\n \"Cache-Control\",\n \"public, max-age=300, s-maxage=300, stale-while-revalidate=300\"\n );\n return response;\n}\n\nfunction notFound() {\n return NextResponse.json({ error: \"Not found\" }, { status: 404 });\n}\n\nfunction badRequest() {\n return NextResponse.json({ error: \"Invalid media ref\" }, { status: 400 });\n}\n\nfunction forbidden() {\n return NextResponse.json({ error: \"Forbidden\" }, { status: 403 });\n}\n\nasync function serveFileObject(\n input: unknown,\n request: Request,\n options?: { redirectNotionHosted?: boolean }\n) {\n const source = normalizeNotionFileSource(input);\n if (!source) return notFound();\n if (options?.redirectNotionHosted) return mediaRedirect(source.url);\n return proxyNotionHostedFile(source.url, request);\n}\n\nasync function proxyNotionHostedFile(url: string, request: Request) {\n const range = request.headers.get(\"range\");\n const ifRange = request.headers.get(\"if-range\");\n const upstreamHeaders = new Headers({\n Accept: request.headers.get(\"accept\") ?? \"*/*\",\n });\n if (range) upstreamHeaders.set(\"Range\", range);\n if (ifRange) upstreamHeaders.set(\"If-Range\", ifRange);\n\n const upstream = await fetch(url, {\n headers: upstreamHeaders,\n });\n if ((!upstream.ok && upstream.status !== 416) || !upstream.body) {\n return NextResponse.json(\n { error: \"Unable to fetch Notion media\" },\n { status: upstream.status || 502 }\n );\n }\n\n const contentType = upstream.headers.get(\"content-type\") ?? \"\";\n const isImage = contentType.startsWith(\"image/\");\n const accept = request.headers.get(\"accept\") ?? \"\";\n const variant = publicMediaVariantForAccept(accept);\n const urlObj = new URL(request.url);\n const width = clampInt(\n urlObj.searchParams.get(\"w\"),\n 64,\n MAX_WIDTH,\n DEFAULT_WIDTH\n );\n const quality = clampInt(\n urlObj.searchParams.get(\"q\"),\n MIN_QUALITY,\n MAX_QUALITY,\n DEFAULT_QUALITY\n );\n\n let outputFormat: \"image/avif\" | \"image/webp\" | null = null;\n if (variant === \"avif\") {\n outputFormat = \"image/avif\";\n } else if (variant === \"webp\") {\n outputFormat = \"image/webp\";\n }\n\n const platform = getRuntimePlatform();\n const imageTransformer = platform.imageTransformer;\n if (isImage && !range && outputFormat && imageTransformer) {\n const r2Key = notionMediaR2KeyForUrl(urlObj, variant);\n\n try {\n const result = await imageTransformer.transform(upstream.body, {\n width,\n format: outputFormat,\n quality,\n });\n const transformed = result.response();\n const headers = new Headers(transformed.headers);\n headers.set(\"Content-Type\", result.contentType);\n headers.set(\"Cache-Control\", cacheControl(request));\n headers.set(\"Vary\", \"Accept\");\n headers.set(\"X-Notion-Media-Branch\", \"transformed\");\n headers.set(\"X-Notion-Media-R2\", r2Key ? \"MISS\" : \"BYPASS\");\n headers.set(\"X-Optimized-Width\", String(width));\n headers.set(\"X-Optimized-Quality\", String(quality));\n\n if (transformed.body && r2Key && platform.objectStorage) {\n const [clientBody, r2Body] = transformed.body.tee();\n getRequestExecutionContext()?.waitUntil(\n platform.objectStorage.put(r2Key, r2Body, {\n contentType: result.contentType,\n cacheControl: \"public, max-age=31536000, immutable\",\n metadata: {\n source: \"notion\",\n cachedAt: new Date().toISOString(),\n width: String(width),\n quality: String(quality),\n },\n })\n );\n\n return new Response(clientBody, { headers });\n }\n\n return new Response(transformed.body, { headers });\n } catch {\n // Fall through to the original file when the image binding cannot transform.\n }\n }\n\n const headers = new Headers();\n for (const header of [\n \"accept-ranges\",\n \"content-disposition\",\n \"content-encoding\",\n \"content-length\",\n \"content-range\",\n \"content-type\",\n \"etag\",\n \"last-modified\",\n ]) {\n const value = upstream.headers.get(header);\n if (value) headers.set(header, value);\n }\n if (contentType && !headers.has(\"Content-Type\")) {\n headers.set(\"Content-Type\", contentType);\n }\n headers.set(\"Cache-Control\", cacheControl(request));\n headers.set(\"X-Notion-Media-Branch\", \"proxied\");\n return new Response(upstream.body, { status: upstream.status, headers });\n}\n\nasync function loadMedia(request: Request) {\n const url = new URL(request.url);\n const ref = readRefFromPathname(url.pathname);\n if (!ref) return badRequest();\n const client = createNotionClient(await getNotionClientConfig());\n\n if (ref[0] === \"page\" && ref[1] && ref[2] === \"cover\") {\n const page = (await client.pages.retrieve({\n page_id: ref[1],\n })) as NotionPageLike;\n return serveFileObject(page.cover, request);\n }\n\n if (ref[0] === \"page\" && ref[1] && ref[2] === \"property\" && ref[3]) {\n const page = (await client.pages.retrieve({\n page_id: ref[1],\n })) as NotionPageLike;\n const propertyName = decodeURIComponent(ref.slice(3).join(\"/\"));\n return serveFileObject(\n pickFirstFilesPropertyValue(page.properties?.[propertyName]),\n request\n );\n }\n\n if (ref[0] === \"block\" && ref[1]) {\n const block = (await client.blocks.retrieve({\n block_id: ref[1],\n })) as NotionBlock;\n if (block.type === \"video\") {\n return forbidden();\n }\n return serveFileObject(fileObjectForMediaBlock(block), request, {\n redirectNotionHosted:\n block.type === \"audio\" ||\n block.type === \"pdf\" ||\n block.type === \"file\",\n });\n }\n\n return badRequest();\n}\n\nfunction readRefFromPathname(pathname: string): string[] | null {\n const prefix = \"/api/notion/media/\";\n if (!pathname.startsWith(prefix)) return null;\n const encoded = pathname.slice(prefix.length);\n if (!encoded) return null;\n return encoded.split(\"/\").map((part) => decodeURIComponent(part));\n}\n\n// Top-level alias for callers that prefer a flat signature (e.g. the\n// Next.js `app/api/.../route.ts` delegates).\nexport const GET = notionMediaRoute.GET;\n\n/**\n * Worker-friendly single-arg handler. Used by the Cloudflare Workers\n * bootstrap in `@notionx/core/worker`. Equivalent to\n * `notionMediaRoute.handle`.\n */\nexport async function notionMediaRouteHandle(\n request: Request\n): Promise<Response> {\n return notionMediaRoute.handle(request);\n}\n","// Edge cache keys for transformed Notion media responses.\n//\n// Page and API route caching is handled by vinext's CDN cache adapter\n// (vite.config.ts). These helpers remain for the media proxy route.\n\nconst CACHE_ORIGIN = \"https://cache.local\";\nconst CACHE_NAMESPACE = \"/__public-cache/v20260609a\";\nconst NOTION_MEDIA_R2_PREFIX = \"notion-media/v1\";\n\nexport type PublicMediaVariant = \"avif\" | \"webp\" | \"source\";\n\nfunction normalizePath(pathname: string) {\n if (pathname === \"/\") return \"/\";\n return pathname.endsWith(\"/\") ? pathname.slice(0, -1) : pathname;\n}\n\nexport function publicMediaVariantForAccept(accept: string): PublicMediaVariant {\n if (accept.includes(\"image/avif\")) return \"avif\";\n if (accept.includes(\"image/webp\")) return \"webp\";\n return \"source\";\n}\n\nexport function publicMediaCacheKeyForUrl(\n input: URL,\n variant: PublicMediaVariant\n) {\n const url = new URL(\n `${CACHE_NAMESPACE}${normalizePath(input.pathname)}${input.search}`,\n CACHE_ORIGIN\n );\n url.searchParams.set(\"__variant\", variant);\n url.searchParams.sort();\n return url.toString();\n}\n\nfunction keySegment(value: string) {\n return encodeURIComponent(value || \"none\");\n}\n\nexport function notionMediaR2KeyForUrl(\n input: URL,\n variant: PublicMediaVariant\n) {\n if (variant === \"source\") return null;\n\n const version = input.searchParams.get(\"v\");\n if (!version) return null;\n\n const path = normalizePath(input.pathname)\n .split(\"/\")\n .filter(Boolean)\n .map(keySegment)\n .join(\"/\");\n const width = input.searchParams.get(\"w\") ?? \"source\";\n const quality = input.searchParams.get(\"q\") ?? \"source\";\n\n return [\n NOTION_MEDIA_R2_PREFIX,\n variant,\n path,\n `v-${keySegment(version)}`,\n `w-${keySegment(width)}`,\n `q-${keySegment(quality)}.${variant}`,\n ].join(\"/\");\n}\n","import { Client } from \"@notionhq/client\";\nimport type { NotionClientConfig } from \"./config\";\n\nexport function createNotionClient(config: NotionClientConfig) {\n return new Client({\n auth: config.token,\n baseUrl: config.apiBaseUrl,\n notionVersion: \"2026-03-11\",\n });\n}\n","import type { NotionContentModelLike } from \"./types\";\n\ntype NotionEnv = {\n NOTION_TOKEN?: string;\n NOTION_DATA_SOURCE_ID?: string;\n NOTION_API_BASE_URL?: string;\n NOTION_EDIT_BASE_URL?: string;\n NOTION_WEBHOOK_VERIFICATION_TOKEN?: string;\n [key: string]: string | undefined;\n};\n\nexport type NotionClientConfig = {\n token: string;\n apiBaseUrl?: string;\n};\n\nexport type NotionConfig = {\n token: string;\n dataSourceId: string;\n apiBaseUrl?: string;\n editBaseUrl?: string;\n webhookVerificationToken?: string;\n};\n\nfunction readProcessEnv(): NotionEnv {\n const env: NotionEnv = {\n NOTION_TOKEN: process.env.NOTION_TOKEN,\n NOTION_DATA_SOURCE_ID: process.env.NOTION_DATA_SOURCE_ID,\n NOTION_API_BASE_URL: process.env.NOTION_API_BASE_URL,\n NOTION_EDIT_BASE_URL: process.env.NOTION_EDIT_BASE_URL,\n NOTION_WEBHOOK_VERIFICATION_TOKEN:\n process.env.NOTION_WEBHOOK_VERIFICATION_TOKEN,\n };\n\n for (const [key, value] of Object.entries(process.env)) {\n if (key.startsWith(\"NOTION_\") && typeof value === \"string\") {\n env[key] = value;\n }\n }\n\n return env;\n}\n\nasync function readWorkerEnv(): Promise<NotionEnv> {\n try {\n const mod = (await import(\n /* webpackIgnore: true */ \"cloudflare:workers\"\n )) as unknown as { env?: Record<string, unknown> };\n const env: NotionEnv = {};\n for (const [key, value] of Object.entries(mod.env ?? {})) {\n if (key.startsWith(\"NOTION_\") && typeof value === \"string\") {\n env[key] = value;\n }\n }\n return env;\n } catch {\n return {};\n }\n}\n\nfunction readString(source: NotionEnv, name: string): string | undefined {\n const value = String(source[name] ?? \"\").trim();\n return value || undefined;\n}\n\nfunction mergeEnv(...sources: NotionEnv[]): NotionEnv {\n const merged: NotionEnv = {};\n\n for (const source of sources) {\n for (const name of Object.keys(source)) {\n if (!name.startsWith(\"NOTION_\")) continue;\n const value = readString(source, name);\n if (value) merged[name] = value;\n }\n }\n\n return merged;\n}\n\nasync function readEnv(): Promise<NotionEnv> {\n const processEnv = readProcessEnv();\n return mergeEnv(await readWorkerEnv(), processEnv);\n}\n\nfunction readRequired(\n source: NotionEnv,\n name: string\n): string {\n const value = readString(source, name);\n if (!value) {\n throw new Error(`Missing required Notion env: ${name}`);\n }\n return value;\n}\n\nexport function getNotionEditBaseUrl(): string {\n return readString(readProcessEnv(), \"NOTION_EDIT_BASE_URL\") ?? \"https://www.notion.so\";\n}\n\nexport async function hasNotionConfig(): Promise<boolean> {\n const env = await readEnv();\n return Boolean(\n readString(env, \"NOTION_TOKEN\") && readString(env, \"NOTION_DATA_SOURCE_ID\")\n );\n}\n\nexport async function hasNotionModelConfig(\n model: NotionContentModelLike\n): Promise<boolean> {\n const env = await readEnv();\n return Boolean(\n readString(env, \"NOTION_TOKEN\") &&\n (readString(env, model.source.dataSourceEnv) ||\n model.source.defaultDataSourceId)\n );\n}\n\nexport async function getNotionClientConfig(): Promise<NotionClientConfig> {\n const env = await readEnv();\n return {\n token: readRequired(env, \"NOTION_TOKEN\"),\n apiBaseUrl: readString(env, \"NOTION_API_BASE_URL\"),\n };\n}\n\nexport async function getNotionConfig(): Promise<NotionConfig> {\n const env = await readEnv();\n return {\n token: readRequired(env, \"NOTION_TOKEN\"),\n dataSourceId: readRequired(env, \"NOTION_DATA_SOURCE_ID\"),\n apiBaseUrl: readString(env, \"NOTION_API_BASE_URL\"),\n editBaseUrl: readString(env, \"NOTION_EDIT_BASE_URL\"),\n webhookVerificationToken: readString(\n env,\n \"NOTION_WEBHOOK_VERIFICATION_TOKEN\"\n ),\n };\n}\n\nexport async function getNotionWebhookVerificationToken(): Promise<\n string | undefined\n> {\n const env = await readEnv();\n return readString(env, \"NOTION_WEBHOOK_VERIFICATION_TOKEN\");\n}\n\nexport async function getNotionConfigForModel(\n model: NotionContentModelLike\n): Promise<NotionConfig> {\n const env = await readEnv();\n const dataSourceId =\n readString(env, model.source.dataSourceEnv) ??\n model.source.defaultDataSourceId;\n if (!dataSourceId) {\n throw new Error(`Missing required Notion env: ${model.source.dataSourceEnv}`);\n }\n\n return {\n token: readRequired(env, model.source.tokenEnv),\n dataSourceId,\n apiBaseUrl: readString(env, \"NOTION_API_BASE_URL\"),\n editBaseUrl: readString(env, \"NOTION_EDIT_BASE_URL\"),\n webhookVerificationToken: readString(\n env,\n \"NOTION_WEBHOOK_VERIFICATION_TOKEN\"\n ),\n };\n}\n","import type { NotionBlock, NotionFileSource, NotionPageLike } from \"./types\";\n\ntype FileLike = {\n type?: string;\n external?: { url?: string };\n file?: { url?: string; expiry_time?: string };\n name?: string;\n};\n\nfunction stripLeadingSlash(value: string) {\n return value.startsWith(\"/\") ? value.slice(1) : value;\n}\n\nfunction encodePathPart(value: string) {\n return encodeURIComponent(stripLeadingSlash(value));\n}\n\nfunction appendVersion(path: string, version?: string) {\n const value = String(version ?? \"\").trim();\n if (!value) return path;\n return `${path}?${new URLSearchParams({ v: value })}`;\n}\n\nfunction blockVersion(block: NotionBlock): string | undefined {\n return typeof block.last_edited_time === \"string\"\n ? block.last_edited_time\n : undefined;\n}\n\nexport function notionPageCoverMediaPath(pageId: string): string {\n return `/api/notion/media/page/${encodePathPart(pageId)}/cover`;\n}\n\nexport function notionPagePropertyMediaPath(\n pageId: string,\n propertyName: string\n): string {\n return `/api/notion/media/page/${encodePathPart(pageId)}/property/${encodePathPart(propertyName)}`;\n}\n\nexport function notionBlockMediaPath(blockId: string): string {\n return `/api/notion/media/block/${encodePathPart(blockId)}`;\n}\n\nexport function normalizeNotionFileSource(input: unknown): NotionFileSource | null {\n const file = input as FileLike | null | undefined;\n if (!file || typeof file !== \"object\") return null;\n\n if (file.type === \"external\") {\n const url = String(file.external?.url ?? \"\").trim();\n return url ? { type: \"external\", url } : null;\n }\n\n if (file.type === \"file\") {\n const url = String(file.file?.url ?? \"\").trim();\n if (!url) return null;\n return {\n type: \"file\",\n url,\n expiryTime: String(file.file?.expiry_time ?? \"\").trim() || null,\n };\n }\n\n return null;\n}\n\nexport function resolveNotionFileUrl(input: unknown): string | null {\n return normalizeNotionFileSource(input)?.url ?? null;\n}\n\nexport function isNotionHostedFile(input: unknown): boolean {\n return normalizeNotionFileSource(input)?.type === \"file\";\n}\n\nexport function pickFirstFilesPropertyValue(property: unknown): unknown | null {\n const value = property as { type?: string; files?: unknown[] } | null | undefined;\n if (!value || value.type !== \"files\" || !Array.isArray(value.files)) {\n return null;\n }\n return value.files[0] ?? null;\n}\n\nexport function pickPageCoverFile(page: NotionPageLike): unknown | null {\n return page.cover ?? null;\n}\n\nexport function coverImageUrlForPage(\n page: NotionPageLike,\n coverPropertyName = \"Cover\"\n): string | null {\n const propertyFile = pickFirstFilesPropertyValue(\n page.properties?.[coverPropertyName]\n );\n const propertySource = normalizeNotionFileSource(propertyFile);\n if (propertySource) {\n return appendVersion(\n notionPagePropertyMediaPath(page.id, coverPropertyName),\n page.last_edited_time\n );\n }\n\n const coverSource = normalizeNotionFileSource(pickPageCoverFile(page));\n if (coverSource) {\n return appendVersion(notionPageCoverMediaPath(page.id), page.last_edited_time);\n }\n\n return null;\n}\n\nexport function fileObjectForMediaBlock(block: NotionBlock): unknown | null {\n const typed = block[block.type] as Record<string, unknown> | undefined;\n if (!typed || typeof typed !== \"object\") return null;\n\n if (\n block.type === \"image\" ||\n block.type === \"video\" ||\n block.type === \"file\" ||\n block.type === \"pdf\" ||\n block.type === \"audio\"\n ) {\n return typed;\n }\n\n return null;\n}\n\nexport function mediaUrlForBlock(block: NotionBlock): string | null {\n const source = normalizeNotionFileSource(fileObjectForMediaBlock(block));\n if (!source) return null;\n if (source.type === \"external\" && block.type !== \"image\") {\n return source.url;\n }\n return appendVersion(notionBlockMediaPath(block.id), blockVersion(block));\n}\n\nexport function firstImageUrlFromBlocks(blocks: NotionBlock[]): string | null {\n for (const block of blocks) {\n if (block.type === \"image\") {\n const imageUrl = mediaUrlForBlock(block);\n if (imageUrl) return imageUrl;\n }\n\n const nested = block.children?.length\n ? firstImageUrlFromBlocks(block.children)\n : null;\n if (nested) return nested;\n }\n\n return null;\n}\n\nexport function publicMediaBlockForApi(block: NotionBlock): NotionBlock {\n const value = block[block.type];\n const source = normalizeNotionFileSource(value);\n const children = block.children?.map(publicMediaBlockForApi);\n\n if (!source) {\n return children ? { ...block, children } : { ...block };\n }\n\n const path = appendVersion(notionBlockMediaPath(block.id), blockVersion(block));\n const publicValue =\n source.type === \"external\"\n ? block.type === \"image\"\n ? {\n ...(value as Record<string, unknown>),\n external: { url: path },\n }\n : value\n : {\n ...(value as Record<string, unknown>),\n file: {\n url: path,\n expiry_time: null,\n },\n };\n\n return {\n ...block,\n [block.type]: publicValue,\n ...(children ? { children } : {}),\n };\n}\n\nexport function gatedMediaBlockForApi(\n block: NotionBlock,\n options?: { accessUrlForBlock?: (block: NotionBlock) => string | null }\n): NotionBlock {\n const value = block[block.type];\n const source = normalizeNotionFileSource(value);\n const children = block.children?.map((child) =>\n gatedMediaBlockForApi(child, options)\n );\n\n if (block.type !== \"video\" || !source) {\n const publicBlock = publicMediaBlockForApi(block);\n return children ? { ...publicBlock, children } : publicBlock;\n }\n\n const gatedValue: Record<string, unknown> = {\n ...(value as Record<string, unknown>),\n gated: true,\n access_url: options?.accessUrlForBlock?.(block) ?? null,\n };\n\n if (source.type === \"external\") {\n gatedValue.external = { url: null };\n } else {\n gatedValue.file = {\n url: null,\n expiry_time: null,\n };\n }\n\n return {\n ...block,\n video: gatedValue,\n ...(children ? { children } : {}),\n };\n}\n\nexport function isDirectVideoUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return /\\.(mp4|webm|mov|m4v)(?:$|\\?)/i.test(parsed.pathname);\n } catch {\n return false;\n }\n}\n\nexport function videoEmbedUrl(url: string): string | null {\n try {\n const parsed = new URL(url);\n const host = parsed.hostname.replace(/^www\\./, \"\");\n\n if (host === \"youtube.com\" || host === \"m.youtube.com\") {\n const id = parsed.searchParams.get(\"v\");\n return id ? `https://www.youtube.com/embed/${encodeURIComponent(id)}` : null;\n }\n\n if (host === \"youtu.be\") {\n const id = parsed.pathname.split(\"/\").filter(Boolean)[0];\n return id ? `https://www.youtube.com/embed/${encodeURIComponent(id)}` : null;\n }\n\n if (host === \"youtube-nocookie.com\") {\n return parsed.toString();\n }\n\n if (host === \"vimeo.com\") {\n const id = parsed.pathname.split(\"/\").filter(Boolean)[0];\n return id ? `https://player.vimeo.com/video/${encodeURIComponent(id)}` : null;\n }\n\n if (host === \"player.vimeo.com\") {\n return parsed.toString();\n }\n } catch {\n return null;\n }\n\n return null;\n}\n","// worker/routes/health.ts\n//\n// GET /api/health - 健康检查端点\n// 用于监控、容器编排、load balancer 健康探测\n// 顺便验证 SQL database adapter 连接是否正常\n//\n// The package exports a Next.js handler and a worker-friendly single-arg\n// handler. The two are equivalent; only the calling convention differs.\n//\n// The database check is domain-agnostic: it issues a `SELECT 1` to\n// confirm the SQL adapter is reachable, then runs the foundation\n// schema guard to ensure the auth tables are present.\n\nimport { NextResponse } from \"next/server\";\nimport { getDatabase } from \"../../platform/current\";\nimport { runSchemaHealthChecks } from \"../../internal/admin/schema-guard\";\n\nexport const dynamic = \"force-dynamic\";\n\nasync function probeDatabase(): Promise<{\n ok: boolean;\n error: string | null;\n postsTableExists: boolean;\n}> {\n const database = getDatabase();\n try {\n const result = await database\n .prepare(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name='posts' LIMIT 1\"\n )\n .first<{ name?: string }>();\n return {\n ok: true,\n error: null,\n postsTableExists: Boolean(result?.name),\n };\n } catch (e) {\n return {\n ok: false,\n error: e instanceof Error ? e.message : String(e),\n postsTableExists: false,\n };\n }\n}\n\nexport const healthRoute = {\n async GET() {\n return healthRoute.handle(new Request(\"https://health.local/api/health\"));\n },\n async handle(_request: Request): Promise<Response> {\n const start = Date.now();\n const probe = await probeDatabase();\n const d1Ok = probe.ok;\n const d1Error = probe.error;\n\n let schemaOk = false;\n let schemaError: string | null = null;\n let schemaMissing: string[] = [];\n try {\n const schema = await runSchemaHealthChecks(getDatabase());\n schemaOk = schema.ok;\n schemaMissing = schema.missing;\n if (schema.errors.length > 0) {\n schemaError = schema.errors.join(\"; \");\n } else if (schema.missing.length > 0) {\n schemaError = `missing required schema: ${schema.missing.join(\", \")}`;\n }\n } catch (e) {\n schemaError = e instanceof Error ? e.message : String(e);\n }\n\n const allHealthy = d1Ok && schemaOk;\n\n return NextResponse.json(\n {\n status: allHealthy ? \"ok\" : \"degraded\",\n timestamp: new Date().toISOString(),\n uptime_ms: Date.now() - start,\n checks: {\n d1: d1Ok ? \"ok\" : \"error\",\n d1_error: d1Error,\n schema: schemaOk ? \"ok\" : \"error\",\n schema_error: schemaError,\n schema_missing: schemaMissing,\n },\n version: \"1.0.0\",\n },\n {\n status: allHealthy ? 200 : 503,\n headers: {\n \"Cache-Control\": \"no-store\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n }\n );\n },\n};\n\n// Top-level aliases for callers that prefer a flat signature.\nexport const GET = healthRoute.GET;\n\n/**\n * Worker-friendly single-arg handler. Used by the Cloudflare Workers\n * bootstrap in `@notionx/core/worker`. Equivalent to\n * `healthRoute.handle`.\n */\nexport async function healthRouteHandle(request: Request): Promise<Response> {\n return healthRoute.handle(request);\n}\n","export const REQUIRED_SCHEMA_CHECKS = [\n {\n key: \"app_settings.turnstile_enabled\",\n sql: \"SELECT turnstile_enabled FROM app_settings LIMIT 1\",\n },\n {\n key: \"users.session_rev\",\n sql: \"SELECT session_rev FROM users LIMIT 1\",\n },\n {\n key: \"auth_rate_limits\",\n sql: \"SELECT 1 FROM auth_rate_limits LIMIT 1\",\n },\n];\n\nexport const DEFAULT_TURNSTILE_PUBLIC_CONFIG = {\n enabled: false,\n siteKey: null,\n secretConfigured: false,\n};\n\nexport function isSchemaDriftError(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error ?? \"\");\n return (\n message.includes(\"no such column\") || message.includes(\"no such table\")\n );\n}\n\nexport function buildTurnstilePublicConfig(\n settings: { turnstile_enabled: number; turnstile_site_key: string | null },\n envLike: { TURNSTILE_SITE_KEY?: string; TURNSTILE_SECRET_KEY?: string }\n): { enabled: boolean; siteKey: string | null; secretConfigured: boolean } {\n const envSiteKey = envLike.TURNSTILE_SITE_KEY?.trim() || null;\n const siteKey = settings.turnstile_site_key?.trim() || envSiteKey || null;\n const secretConfigured = Boolean(envLike.TURNSTILE_SECRET_KEY?.trim());\n const enabled =\n (settings.turnstile_enabled === 1 || Boolean(envSiteKey)) &&\n Boolean(siteKey) &&\n secretConfigured;\n\n return {\n enabled,\n siteKey,\n secretConfigured,\n };\n}\n\nexport async function runSchemaHealthChecks(db: {\n prepare: (sql: string) => { first: () => Promise<unknown> };\n}): Promise<{ ok: boolean; missing: string[]; errors: string[] }> {\n const missing: string[] = [];\n const errors: string[] = [];\n\n for (const check of REQUIRED_SCHEMA_CHECKS) {\n try {\n await db.prepare(check.sql).first();\n } catch (error) {\n if (isSchemaDriftError(error)) {\n missing.push(check.key);\n } else {\n const message = error instanceof Error ? error.message : String(error);\n errors.push(`${check.key}: ${message}`);\n }\n }\n }\n\n return {\n ok: missing.length === 0 && errors.length === 0,\n missing,\n errors,\n };\n}\n","// Foundation Cloudflare Worker bootstrap.\n//\n// Wires the package's first-party worker routes (health, storage,\n// notion media) plus any project-injected `extraRoutes` into a single\n// `fetch` handler. The handler returns `Response | null` so the\n// starter wrapper can fall through to vinext for any request\n// foundation does not own.\n//\n// Contract:\n// - `fetch(request, env, ctx)` returns a `Response` when a route\n// matched (or when the admin gate fires), and `null` to signal\n// \"not handled\" so the caller can delegate to the underlying\n// app router / vinext handler.\n//\n// Tier restriction: this file lives in `src/worker/`, so it CANNOT\n// import from `src/auth/` or `src/admin/`. The session/admin gate\n// lives in the tier-7 `middleware.ts` module which the bootstrap\n// delegates to.\n\nimport { nextionMiddleware, type NextionMiddlewareOptions } from \"../middleware\";\nimport type {\n AdminNavItem,\n AuthConfig,\n ContentSource,\n} from \"../types\";\nimport { filesRoute } from \"../storage/routes/files\";\nimport { cdnRoute } from \"../storage/routes/cdn\";\nimport { notionMediaRoute } from \"../media/routes/notion-media\";\nimport { healthRoute, healthRouteHandle } from \"./routes/health\";\n\n/**\n * Public surface for the bootstrap options. Mirrors the placeholder\n * `WorkerOptions` type in `src/types.ts`; Phase 6 normalizes the\n * shape once the content abstraction lands.\n */\nexport interface FoundationWorkerOptions {\n sources: ContentSource[];\n adminNav: AdminNavItem[];\n authConfig: AuthConfig;\n /**\n * Placeholder for the future site config. Kept on the options\n * surface so consumers can wire it today; the bootstrap itself\n * does not yet consume it.\n */\n siteConfig: {\n name: string;\n description: string;\n defaultLocale: string;\n locales: string[];\n navigation: unknown[];\n };\n /**\n * Optional session resolver passed straight through to\n * `nextionMiddleware`. When omitted the middleware still runs\n * the admin gate but always reports no viewer.\n */\n sessionLookup?: NextionMiddlewareOptions[\"sessionLookup\"];\n /**\n * Project-injected routes. Each entry maps a pathname to a\n * dynamic loader. The loader returns a module whose `default`\n * export is a `(request, options) => Promise<Response>` handler.\n *\n * Loaders are invoked lazily on the first matching request so\n * the route module can pull in heavy dependencies (e.g. the\n * starter's content models) only when needed.\n */\n extraRoutes?: Record<\n string,\n () => Promise<{ default: FoundationExtraRouteHandler }>\n >;\n}\n\nexport type FoundationExtraRouteHandler = (\n request: Request,\n options: FoundationWorkerOptions,\n sources: ContentSource[],\n auth: { databaseBinding: string }\n) => Promise<Response>;\n\nexport interface FoundationWorker {\n /**\n * Worker-style fetch. Returns `Response` when foundation handled\n * the request, `null` to let the caller fall through to vinext.\n */\n fetch: (\n request: Request,\n env: unknown,\n ctx: unknown\n ) => Promise<Response | null>;\n}\n\ninterface RouteEntry {\n match: (request: Request) => boolean;\n handle: (request: Request) => Promise<Response>;\n}\n\nfunction pathMatches(pathname: string, match: string): boolean {\n if (match.endsWith(\"/\")) return pathname.startsWith(match);\n return pathname === match || pathname.startsWith(`${match}/`);\n}\n\nfunction buildStaticRoutes(): RouteEntry[] {\n return [\n {\n match: (req) => new URL(req.url).pathname === \"/api/health\",\n handle: healthRouteHandle,\n },\n {\n match: (req) =>\n pathMatches(new URL(req.url).pathname, \"/api/notion/media/\"),\n handle: notionMediaRoute.handle,\n },\n {\n match: (req) =>\n pathMatches(new URL(req.url).pathname, \"/api/files/\"),\n handle: filesRoute.handle,\n },\n {\n match: (req) =>\n pathMatches(new URL(req.url).pathname, \"/api/cdn/\"),\n handle: cdnRoute.handle,\n },\n ];\n}\n\n/**\n * Build a foundation Cloudflare Worker.\n *\n * The returned object exposes a `fetch` method that:\n * 1. Runs `nextionMiddleware` (admin gate + viewer attachment).\n * 2. Tries the first-party route table.\n * 3. Tries any project-injected `extraRoutes`.\n * 4. Returns `null` if nothing matched, so the starter wrapper can\n * delegate to vinext.\n */\nexport function createNextionWorker(\n options: FoundationWorkerOptions\n): FoundationWorker {\n const sources: ContentSource[] = options.sources;\n const auth = { databaseBinding: options.authConfig.databaseBinding };\n\n const routes: RouteEntry[] = buildStaticRoutes();\n\n if (options.extraRoutes) {\n for (const [path, load] of Object.entries(options.extraRoutes)) {\n const modPromise = load();\n routes.push({\n match: (req) => new URL(req.url).pathname === path,\n handle: async (req) => {\n const mod = await modPromise;\n return mod.default(req, options, sources, auth);\n },\n });\n }\n }\n\n const middlewareOptions: NextionMiddlewareOptions = {\n authConfig: options.authConfig,\n sessionLookup: options.sessionLookup,\n };\n\n return {\n async fetch(request, env, ctx) {\n void ctx;\n\n // 1. Admin gate. A 401 short-circuits the route table.\n const gateResponse = await nextionMiddleware(\n request,\n env,\n middlewareOptions\n );\n if (gateResponse) return gateResponse;\n\n // 2. First-party + extra routes.\n for (const route of routes) {\n if (route.match(request)) return route.handle(request);\n }\n\n // 3. Not handled. The caller falls through to vinext.\n return null;\n },\n };\n}\n\n// Re-export the health route shape so consumers can build their own\n// worker if they need finer control than the bootstrap offers.\nexport { healthRoute, healthRouteHandle };\n","import type { RuntimeId } from \"./selection\";\n\nexport type RuntimeCapability =\n | \"server-rendering\"\n | \"edge-cache\"\n | \"relational-storage\"\n | \"object-storage\"\n | \"image-optimization\"\n | \"secrets\"\n | \"observability\";\n\nexport type RuntimeAdapterDefinition = {\n id: RuntimeId;\n label: string;\n status: \"active\" | \"partial\" | \"planned\";\n services: {\n compute: string;\n relationalStorage: string;\n objectStorage: string;\n imageOptimization: string;\n cache: string;\n authStorage: string;\n };\n capabilities: readonly RuntimeCapability[];\n};\n\nexport type RuntimeServiceStatus = {\n database: boolean;\n objectStorage: boolean;\n imageTransformer: boolean;\n publicCache: boolean;\n};\n\nexport const cloudflareWorkersAdapter: RuntimeAdapterDefinition = {\n id: \"cloudflare-workers\",\n label: \"Cloudflare Workers + D1\",\n status: \"active\",\n services: {\n compute: \"Cloudflare Workers via vinext\",\n relationalStorage: \"D1 through the runtime SQL adapter\",\n objectStorage: \"R2\",\n imageOptimization: \"Cloudflare Images\",\n cache: \"vinext CDN/data adapters and caches.default for media\",\n authStorage: \"D1 users and signed cookies\",\n },\n capabilities: [\n \"server-rendering\",\n \"edge-cache\",\n \"relational-storage\",\n \"object-storage\",\n \"image-optimization\",\n \"secrets\",\n \"observability\",\n ],\n};\n\nexport const runtimeAdapters = [cloudflareWorkersAdapter] as const;\n\nexport function getRuntimeAdapter(id: RuntimeAdapterDefinition[\"id\"]) {\n return runtimeAdapters.find((adapter) => adapter.id === id);\n}\n\nexport function runtimeServiceStatus(\n platform: {\n database: unknown;\n objectStorage: unknown;\n imageTransformer: unknown;\n publicCache: unknown;\n }\n): RuntimeServiceStatus {\n return {\n database: Boolean(platform.database),\n objectStorage: Boolean(platform.objectStorage),\n imageTransformer: Boolean(platform.imageTransformer),\n publicCache: Boolean(platform.publicCache),\n };\n}\n","import type { ContentModelDefinition, NotionFieldMap } from \"./model\";\nimport {\n getRuntimeAdapter,\n type RuntimeAdapterDefinition,\n} from \"../platform/capabilities\";\nimport { currentRuntimeId, type RuntimeId } from \"../platform/selection\";\n\nexport type NextionDoctorStatus = \"ok\" | \"warn\" | \"missing\";\n\nexport type EnvLike = Record<string, string | undefined>;\n\nexport type WranglerConfigLike = {\n vars?: EnvLike;\n d1_databases?: Array<{ binding?: string }>;\n r2_buckets?: Array<{ binding?: string }>;\n images?: { binding?: string } | Array<{ binding?: string }>;\n observability?: { enabled?: boolean };\n};\n\nexport type NextionDoctorCheck = {\n id: string;\n label: string;\n status: NextionDoctorStatus;\n required: boolean;\n detail: string;\n action?: string;\n};\n\nexport type NextionDoctorModel = {\n id: string;\n public: boolean;\n admin: boolean;\n listPath: string;\n detailPath: string;\n publicApiPath?: string;\n dataSourceEnv: string;\n dataSourceStatus: NextionDoctorStatus;\n dataSourceSource: \"env\" | \"default\" | \"missing\";\n};\n\nexport type NextionDoctorReport = {\n overall: {\n status: NextionDoctorStatus;\n summary: string;\n };\n runtime: {\n id: RuntimeId;\n label: string;\n adapterStatus: RuntimeAdapterDefinition[\"status\"];\n };\n checks: NextionDoctorCheck[];\n models: NextionDoctorModel[];\n nextSteps: string[];\n};\n\ntype BuildNextionDoctorReportOptions = {\n env?: EnvLike;\n runtimeId?: RuntimeId;\n wranglerConfig?: WranglerConfigLike | null;\n models?: readonly ContentModelDefinition<NotionFieldMap>[];\n};\n\nfunction envValue(env: EnvLike, name: string) {\n const value = String(env[name] ?? \"\").trim();\n return value || undefined;\n}\n\nfunction hasEnv(env: EnvLike, name: string) {\n return Boolean(envValue(env, name));\n}\n\nfunction hasD1Binding(config: WranglerConfigLike | null | undefined, binding: string) {\n return Boolean(config?.d1_databases?.some((item) => item.binding === binding));\n}\n\nfunction hasR2Binding(config: WranglerConfigLike | null | undefined, binding: string) {\n return Boolean(config?.r2_buckets?.some((item) => item.binding === binding));\n}\n\nfunction hasImagesBinding(\n config: WranglerConfigLike | null | undefined,\n binding: string\n) {\n const images = config?.images;\n if (Array.isArray(images)) {\n return images.some((item) => item.binding === binding);\n }\n return images?.binding === binding;\n}\n\nfunction statusSummary(status: NextionDoctorStatus) {\n if (status === \"ok\") return \"ready\";\n if (status === \"warn\") return \"usable with warnings\";\n return \"missing required configuration\";\n}\n\nfunction overallStatus(\n checks: readonly NextionDoctorCheck[],\n models: readonly NextionDoctorModel[]\n): NextionDoctorStatus {\n if (\n checks.some((check) => check.status === \"missing\") ||\n models.some((model) => model.dataSourceStatus === \"missing\")\n ) {\n return \"missing\";\n }\n\n if (\n checks.some((check) => check.status === \"warn\") ||\n models.some((model) => model.dataSourceStatus === \"warn\")\n ) {\n return \"warn\";\n }\n\n return \"ok\";\n}\n\nfunction cloudflareChecks(\n config: WranglerConfigLike | null | undefined\n): NextionDoctorCheck[] {\n return [\n {\n id: \"runtime.database\",\n label: \"SQL database\",\n status: hasD1Binding(config, \"DB\") ? \"ok\" : \"missing\",\n required: true,\n detail: hasD1Binding(config, \"DB\")\n ? \"wrangler D1 binding DB is declared\"\n : \"wrangler D1 binding DB is missing\",\n action: \"Add a DB binding under d1_databases in wrangler.jsonc.\",\n },\n {\n id: \"runtime.objectStorage\",\n label: \"Object storage\",\n status: hasR2Binding(config, \"ASSETS_BUCKET\") ? \"ok\" : \"warn\",\n required: false,\n detail: hasR2Binding(config, \"ASSETS_BUCKET\")\n ? \"wrangler R2 binding ASSETS_BUCKET is declared\"\n : \"uploads and persistent media cache need ASSETS_BUCKET\",\n action: \"Add an ASSETS_BUCKET R2 binding for uploads and media cache.\",\n },\n {\n id: \"runtime.imageTransformer\",\n label: \"Image transformation\",\n status: hasImagesBinding(config, \"IMAGES\") ? \"ok\" : \"warn\",\n required: false,\n detail: hasImagesBinding(config, \"IMAGES\")\n ? \"wrangler Images binding IMAGES is declared\"\n : \"Notion media optimization will fall back without IMAGES\",\n action: \"Add a Cloudflare Images binding named IMAGES.\",\n },\n {\n id: \"runtime.publicCache\",\n label: \"Public cache\",\n status: \"ok\",\n required: true,\n detail: \"vinext CDN adapter handles page cache; caches.default remains for media\",\n },\n {\n id: \"runtime.observability\",\n label: \"Observability\",\n status: config?.observability?.enabled ? \"ok\" : \"warn\",\n required: false,\n detail: config?.observability?.enabled\n ? \"wrangler observability is enabled\"\n : \"wrangler observability is not enabled\",\n action: \"Enable observability in wrangler.jsonc for production debugging.\",\n },\n ];\n}\n\nfunction notionChecks(env: EnvLike): NextionDoctorCheck[] {\n return [\n {\n id: \"notion.token\",\n label: \"Notion token\",\n status: hasEnv(env, \"NOTION_TOKEN\") ? \"ok\" : \"missing\",\n required: true,\n detail: hasEnv(env, \"NOTION_TOKEN\")\n ? \"NOTION_TOKEN is configured\"\n : \"NOTION_TOKEN is missing\",\n action: \"Set NOTION_TOKEN to an internal integration token.\",\n },\n {\n id: \"notion.webhook\",\n label: \"Notion webhook verification\",\n status: hasEnv(env, \"NOTION_WEBHOOK_VERIFICATION_TOKEN\") ? \"ok\" : \"warn\",\n required: false,\n detail: hasEnv(env, \"NOTION_WEBHOOK_VERIFICATION_TOKEN\")\n ? \"NOTION_WEBHOOK_VERIFICATION_TOKEN is configured\"\n : \"instant content invalidation needs NOTION_WEBHOOK_VERIFICATION_TOKEN\",\n action:\n \"Set NOTION_WEBHOOK_VERIFICATION_TOKEN after creating the Notion webhook.\",\n },\n ];\n}\n\nfunction modelDoctorStatus(\n env: EnvLike,\n model: ContentModelDefinition<NotionFieldMap>\n): Pick<NextionDoctorModel, \"dataSourceStatus\" | \"dataSourceSource\"> {\n const hasConfiguredEnv = hasEnv(env, model.source.dataSourceEnv);\n const hasDefault = Boolean(model.source.defaultDataSourceId);\n const dataSourceSource = hasConfiguredEnv\n ? \"env\"\n : hasDefault\n ? \"default\"\n : \"missing\";\n const dataSourceStatus = dataSourceSource === \"missing\" ? \"missing\" : \"ok\";\n\n return {\n dataSourceStatus,\n dataSourceSource,\n };\n}\n\nfunction modelChecks(\n env: EnvLike,\n models: readonly ContentModelDefinition<NotionFieldMap>[]\n): NextionDoctorModel[] {\n return models.map((model) => ({\n id: model.id,\n public: model.visibility.public,\n admin: model.visibility.admin,\n listPath: model.routes.listPath,\n detailPath: model.routes.detailPath,\n publicApiPath: model.routes.publicApiPath,\n dataSourceEnv: model.source.dataSourceEnv,\n ...modelDoctorStatus(env, model),\n }));\n}\n\nfunction uniqueActions(checks: readonly NextionDoctorCheck[]) {\n return Array.from(\n new Set(\n checks\n .filter((check) => check.status !== \"ok\" && check.action)\n .map((check) => check.action as string)\n )\n );\n}\n\nfunction omitResolvedActions(check: NextionDoctorCheck): NextionDoctorCheck {\n if (check.status !== \"ok\") return check;\n return {\n ...check,\n action: undefined,\n };\n}\n\nexport function buildNextionDoctorReport(\n options: BuildNextionDoctorReportOptions = {}\n): NextionDoctorReport {\n const env = options.env ?? process.env;\n const runtimeId = options.runtimeId ?? currentRuntimeId();\n const adapter = getRuntimeAdapter(runtimeId);\n const checks = [\n ...cloudflareChecks(options.wranglerConfig),\n ...notionChecks(env),\n ].map(omitResolvedActions);\n const models = modelChecks(env, options.models ?? []);\n const status = overallStatus(checks, models);\n const modelActions = models\n .filter((model) => model.dataSourceStatus === \"missing\")\n .map(\n (model) =>\n `Set ${model.dataSourceEnv} for the ${model.id} content model or add a model defaultDataSourceId.`\n );\n\n return {\n overall: {\n status,\n summary: statusSummary(status),\n },\n runtime: {\n id: runtimeId,\n label: adapter?.label ?? runtimeId,\n adapterStatus: adapter?.status ?? \"planned\",\n },\n checks,\n models,\n nextSteps: [...uniqueActions(checks), ...modelActions],\n };\n}\n\nfunction formatCheck(check: NextionDoctorCheck) {\n const required = check.required ? \"required\" : \"optional\";\n return ` [${check.status}] ${check.label} (${required}) - ${check.detail}`;\n}\n\nfunction formatModel(model: NextionDoctorModel) {\n const visibility = [\n model.public ? \"public\" : \"\",\n model.admin ? \"admin\" : \"\",\n ].filter(Boolean);\n const source =\n model.dataSourceSource === \"env\"\n ? model.dataSourceEnv\n : model.dataSourceSource === \"default\"\n ? `${model.dataSourceEnv} or model default`\n : model.dataSourceEnv;\n\n return [\n ` [${model.dataSourceStatus}] ${model.id} (${visibility.join(\", \") || \"private\"})`,\n ` routes: ${model.listPath}, ${model.detailPath}${\n model.publicApiPath ? `, ${model.publicApiPath}` : \"\"\n }`,\n ` notion: ${source}`,\n ].join(\"\\n\");\n}\n\nexport function formatNextionDoctorReport(report: NextionDoctorReport) {\n const lines = [\n \"vinext nextion doctor\",\n \"\",\n `Overall: [${report.overall.status}] ${report.overall.summary}`,\n `Runtime: ${report.runtime.label} (${report.runtime.id}, ${report.runtime.adapterStatus})`,\n \"\",\n \"Checks:\",\n ...report.checks.map(formatCheck),\n \"\",\n \"Content models:\",\n ...report.models.map(formatModel),\n ];\n\n if (report.nextSteps.length > 0) {\n lines.push(\"\", \"Next steps:\");\n for (const step of report.nextSteps) {\n lines.push(` - ${step}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n","// Re-export the detailed report builder used by the CLI and starter tests.\nexport {\n buildNextionDoctorReport,\n formatNextionDoctorReport,\n} from \"./doctor\";\nexport type {\n NextionDoctorCheck,\n NextionDoctorModel,\n NextionDoctorReport,\n NextionDoctorStatus,\n EnvLike,\n WranglerConfigLike,\n} from \"./doctor\";\nexport type { ContentModelDefinition, NotionFieldMap } from \"./model\";\n\nimport {\n buildNextionDoctorReport,\n formatNextionDoctorReport,\n type EnvLike,\n type NextionDoctorReport,\n type WranglerConfigLike,\n} from \"./doctor\";\n\n/**\n * Public, runtime-driven doctor API. Consumers (the worker bootstrap,\n * scripts, CI) pass a runtime that exposes binding lookups; the doctor\n * derives a synthetic wrangler config from it and runs the full report.\n *\n * `sources` is a list of project content sources. For now, the doctor\n * ignores their detailed shape; future phases will surface per-source\n * data-source status as findings.\n */\nexport type DoctorFinding = {\n code: string;\n message: string;\n severity: \"info\" | \"warning\" | \"error\";\n};\n\nexport type RuntimeLike = {\n getBinding(name: string): unknown;\n};\n\nexport type RunNextionDoctorOptions = {\n env: EnvLike;\n runtime: RuntimeLike;\n sources: readonly unknown[];\n wranglerConfig?: WranglerConfigLike | null;\n};\n\nexport type NextionDoctorFindingsReport = NextionDoctorReport & {\n findings: DoctorFinding[];\n};\n\nfunction deriveWranglerConfig(runtime: RuntimeLike): WranglerConfigLike {\n return {\n d1_databases: runtime.getBinding(\"DB\") ? [{ binding: \"DB\" }] : [],\n r2_buckets: runtime.getBinding(\"ASSETS_BUCKET\")\n ? [{ binding: \"ASSETS_BUCKET\" }]\n : [],\n images: runtime.getBinding(\"IMAGES\") ? { binding: \"IMAGES\" } : undefined,\n };\n}\n\nfunction checkToFindingCode(checkId: string): string {\n if (checkId === \"runtime.database\") return \"missing-db-binding\";\n if (checkId === \"runtime.objectStorage\") return \"missing-r2-binding\";\n if (checkId === \"runtime.imageTransformer\") return \"missing-images-binding\";\n if (checkId === \"runtime.observability\") return \"observability-disabled\";\n if (checkId === \"notion.token\") return \"missing-notion-token\";\n if (checkId === \"notion.webhook\") return \"missing-notion-webhook\";\n return checkId;\n}\n\nfunction checkToFinding(check: import(\"./doctor\").NextionDoctorCheck): DoctorFinding {\n const severity: DoctorFinding[\"severity\"] =\n check.status === \"missing\" ? \"error\" : check.status === \"warn\" ? \"warning\" : \"info\";\n return {\n code: checkToFindingCode(check.id),\n message: check.action ?? check.detail,\n severity,\n };\n}\n\nfunction modelToFinding(\n model: import(\"./doctor\").NextionDoctorModel\n): DoctorFinding | null {\n if (model.dataSourceStatus !== \"missing\") return null;\n return {\n code: `missing-data-source:${model.id}`,\n message: `Set ${model.dataSourceEnv} for the ${model.id} content model or add a model defaultDataSourceId.`,\n severity: \"error\",\n };\n}\n\nexport function runNextionDoctor(\n options: RunNextionDoctorOptions\n): NextionDoctorFindingsReport {\n const wranglerConfig = options.wranglerConfig ?? deriveWranglerConfig(options.runtime);\n const report = buildNextionDoctorReport({\n env: options.env,\n wranglerConfig,\n // Phase 6 will map `sources` (ContentSource[]) to the detailed model\n // shape used by `buildNextionDoctorReport`. For now, treat the\n // empty array as the default and rely on binding/env checks.\n models: [],\n });\n const findings: DoctorFinding[] = [\n ...report.checks\n .filter((check) => check.status !== \"ok\")\n .map(checkToFinding),\n ...report.models\n .map(modelToFinding)\n .filter((finding): finding is DoctorFinding => finding !== null),\n ];\n return { ...report, findings };\n}\n\nexport { formatNextionDoctorReport as formatDoctorReport };\n"],"mappings":";AA+EA,IAAM,WAA4B,CAAC;AAO5B,SAAS,oBACd,OACiC;AACjC,QAAM,WAAW,SAAS,UAAU,CAAC,MAAM,EAAE,OAAO,MAAM,EAAE;AAC5D,MAAI,YAAY,EAAG,UAAS,QAAQ,IAAI;AAAA,MACnC,UAAS,KAAK,KAAK;AACxB,SAAO;AACT;;;ACvBA,IAAM,iBAAiB,oBAAI,QAAkD;AAQtE,SAAS,uBAAuB,SAA2B;AAChE,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,MAAI,IAAI,SAAS,WAAW,aAAa,EAAG,QAAO;AACnD,MAAI,IAAI,SAAS,WAAW,QAAQ,GAAG;AACrC,WAAO,QAAQ,WAAW,SAAS,QAAQ,WAAW;AAAA,EACxD;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,SAAkB,MAA6B;AAGxE,QAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ;AAC3C,MAAI,CAAC,OAAQ,QAAO;AAEpB,aAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,UAAM,CAAC,QAAQ,GAAG,IAAI,IAAI,KAAK,MAAM,GAAG;AACxC,QAAI,CAAC,OAAQ;AACb,QAAI,OAAO,KAAK,MAAM,KAAM;AAC5B,WAAO,KAAK,KAAK,GAAG,EAAE,KAAK,KAAK;AAAA,EAClC;AACA,SAAO;AACT;AAMA,eAAsB,wBACpB,SACA,SAC0C;AAC1C,QAAM,YAAY;AAAA,IAChB;AAAA,IACA,QAAQ,WAAW,cAAc;AAAA,EACnC;AAEA,MAAI,CAAC,aAAa,CAAC,QAAQ,eAAe;AACxC,WAAO,EAAE,QAAQ,MAAM,WAAW,aAAa,KAAK;AAAA,EACtD;AAEA,QAAMA,OAAO,QAAwC,OAAO;AAC5D,QAAM,SAAS,MAAM,QAAQ,cAAc,WAAWA,IAAG;AACzD,MAAI,CAAC,QAAQ;AACX,WAAO,EAAE,QAAQ,MAAM,UAAU;AAAA,EACnC;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,MACN,QAAQ,OAAO;AAAA,MACf,MAAM,OAAO;AAAA,MACb,OAAO,OAAO,SAAS;AAAA,IACzB;AAAA,IACA;AAAA,EACF;AACF;AAsBA,eAAsB,kBACpB,SACAC,MACA,SAC0B;AAC1B,QAAM,UAAU,MAAM,wBAAwB,SAAS,OAAO;AAC9D,iBAAe,IAAI,SAAS,OAAO;AAEnC,QAAM,cAAc,QAAQ,mBAAmB;AAC/C,MAAI,CAAC,YAAY,OAAO,EAAG,QAAO;AAClC,MAAI,QAAQ,OAAQ,QAAO;AAE3B,SAAO,IAAI;AAAA,IACT,KAAK,UAAU,EAAE,IAAI,OAAO,OAAO,eAAe,CAAC;AAAA,IACnD;AAAA,MACE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,qBAAqB;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AACF;;;ACtKA,SAAS,oBAAoB;;;ACR7B,SAAS,WAAW;AAmCb,IAAM,YAAY;;;AC0GzB,SAAS,mBAAmB,KAAa;AACvC,SAAO,IAAI,QAAQ,KAAK,EAAE,QAAQ,MAAM,CAAC;AAC3C;AAEO,SAAS,mCACd,OACoB;AACpB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,MAAM,KAAK;AACf,aAAQ,MAAM,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAM;AAAA,IACzD;AAAA,IACA,IAAI,KAAK,UAAU;AACjB,aAAO,MAAM,IAAI,mBAAmB,GAAG,GAAG,QAAQ;AAAA,IACpD;AAAA,IACA,OAAO,KAAK;AACV,aAAO,MAAM,OAAO,mBAAmB,GAAG,CAAC;AAAA,IAC7C;AAAA,EACF;AACF;AAeO,SAAS,qCACd,WACsB;AACtB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,IACJ,KACA,SACmB;AACnB,aAAQ,MAAM,UAAU,IAAI,KAAK;AAAA,QAC/B,MAAM;AAAA,QACN,UAAU,SAAS;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,IACA,MAAM,IAAI,KAAK,OAAO,SAAS;AAC7B,YAAM,UAAU,IAAI,KAAK,KAAK,UAAU,KAAK,GAAG;AAAA,QAC9C,eAAe,SAAS;AAAA,QACxB,UAAU,SAAS;AAAA,MACrB,CAAC;AAAA,IACH;AAAA,IACA,OAAO,KAAK;AACV,aAAO,UAAU,OAAO,GAAG;AAAA,IAC7B;AAAA,IACA,MAAM,KAAK,SAAS;AAClB,YAAM,SAAS,MAAM,UAAU,KAAK;AAAA,QAClC,QAAQ,SAAS;AAAA,QACjB,OAAO,SAAS;AAAA,QAChB,QAAQ,SAAS;AAAA,MACnB,CAAC;AACD,aAAO;AAAA,QACL,MAAM,OAAO,KAAK,IAAI,CAAC,SAAS,EAAE,MAAM,IAAI,KAAK,EAAE;AAAA,QACnD,QAAQ,OAAO,gBAAgB,SAAY,OAAO;AAAA,QAClD,cAAc,OAAO;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AACF;AAkBA,SAAS,uBAAuB,QAAoC;AAClE,SAAO;AAAA,IACL,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,IACb,aAAa,OAAO,cAAc;AAAA,EACpC;AACF;AAEO,SAAS,gCACdC,MACA,SACiB;AACjB,QAAM,WAAsCA,KAAI,KAC3C;AAAA,IACC,MAAM;AAAA,IACN,QAAQ,OAAe;AACrB,aAAOA,KAAI,GAAG,QAAQ,KAAK;AAAA,IAC7B;AAAA,IACA,MAAM,MAAM,YAAoC;AAC9C,aAAQ,MAAMA,KAAI,GAAG;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAAA,EACF,IACA;AAEJ,QAAM,gBAA6CA,KAAI,gBACnD;AAAA,IACE,MAAM;AAAA,IACN,MAAM,IAAI,KAAK;AACb,YAAM,SAAS,MAAMA,KAAI,eAAe,IAAI,GAAG;AAC/C,aAAO,SAAS,uBAAuB,MAAM,IAAI;AAAA,IACnD;AAAA,IACA,MAAM,IAAI,KAAK,OAAOC,UAAS;AAC7B,YAAMD,KAAI,eAAe,IAAI,KAAK,OAAO;AAAA,QACvC,cAAc;AAAA,UACZ,aAAaC,UAAS;AAAA,UACtB,cAAcA,UAAS;AAAA,QACzB;AAAA,QACA,gBAAgBA,UAAS;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,IACA,MAAM,OAAO,KAAK;AAChB,YAAMD,KAAI,eAAe,OAAO,GAAG;AAAA,IACrC;AAAA,IACA,MAAM,KAAKC,UAAS;AAClB,YAAM,SAAS,MAAMD,KAAI,eAAe,KAAK;AAAA,QAC3C,QAAQC,UAAS;AAAA,QACjB,OAAOA,UAAS;AAAA,MAClB,CAAC;AACD,aACE,QAAQ,QAAQ,IAAI,CAAC,YAAY;AAAA,QAC/B,KAAK,OAAO;AAAA,QACZ,MAAM,OAAO;AAAA,QACb,UAAU,OAAO;AAAA,MACnB,EAAE,KAAK,CAAC;AAAA,IAEZ;AAAA,EACF,IACA;AAEJ,QAAM,mBAAmDD,KAAI,SACzD;AAAA,IACE,MAAM;AAAA,IACN,MAAM,UAAU,MAAMC,UAAS;AAC7B,YAAM,SAAS,MAAMD,KAAI,OAAO,MAAM,IAAI,EACvC,UAAUC,SAAQ,QAAQ,EAAE,OAAOA,SAAQ,MAAM,IAAI,CAAC,CAAC,EACvD,OAAO;AAAA,QACN,QAAQA,SAAQ;AAAA,QAChB,SAASA,SAAQ;AAAA,MACnB,CAAC;AACH,aAAO;AAAA,QACL,MAAM,OAAO,MAAM;AAAA,QACnB,aAAa,OAAO,YAAY;AAAA,QAChC,UAAU,MAAM,OAAO,SAAS;AAAA,MAClC;AAAA,IACF;AAAA,EACF,IACA;AAEJ,QAAM,gBAA6CD,KAAI,gBACnD,qCAAqCA,KAAI,aAAa,IACtD;AAEJ,SAAO;AAAA,IACL,IAAI;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,SAAS,cAClB,mCAAmC,QAAQ,WAAW,IACtD;AAAA,EACN;AACF;;;AClUA,SAAS,4BAA4B;AACnC,QAAM,mBAAmB;AAGzB,SAAO,iBAAiB,QAAQ,WAAW;AAC7C;AAEO,SAAS,qBAAqB;AACnC,SAAO,gCAAgC,WAAW;AAAA,IAChD,aAAa,0BAA0B;AAAA,EACzC,CAAC;AACH;AAUO,SAAS,iBAAiB;AAC/B,QAAM,QAAQ,0BAA0B;AACxC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AACA,SAAO,mCAAmC,KAAK;AACjD;;;ACFO,SAAS,mBAA8B;AAC5C,SAAO;AACT;;;AC3BO,SAASE,sBAAqB;AACnC,SAAO,mBAA6B;AACtC;AAEO,SAAS,cAAc;AAC5B,QAAM,WAAWA,oBAAmB;AACpC,QAAM,WAAW,SAAS;AAC1B,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,2CAA2C,SAAS,EAAE,EAAE;AAAA,EAC1E;AACA,SAAO;AACT;AAEO,SAASC,kBAAiB;AAC/B,SAAO,eAAyB;AAClC;;;ALAO,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA,EAKxB,MAAM,IAAI,UAAmB,OAAc;AACzC,UAAM,EAAE,IAAI,IAAI,MAAM,MAAM;AAC5B,WAAO,WAAW,OAAO,IAAI,QAAQ,iBAAiB,UAAU,GAAG,CAAC,CAAC;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,SAAqC;AAChD,UAAM,MAAM,eAAe,QAAQ,GAAG;AACtC,QAAI,CAAC,KAAK;AACR,aAAO,aAAa,KAAK,EAAE,OAAO,cAAc,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpE;AAEA,QAAI,IAAI,SAAS,IAAI,KAAK,IAAI,WAAW,GAAG,GAAG;AAC7C,aAAO,aAAa,KAAK,EAAE,OAAO,cAAc,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpE;AAEA,UAAM,UAAUC,oBAAmB,EAAE;AACrC,QAAI,CAAC,SAAS;AACZ,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,gCAAgC;AAAA,QACzC,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,QAAQ,IAAI,GAAG;AACpC,QAAI,CAAC,QAAQ;AACX,aAAO,aAAa,KAAK,EAAE,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAClE;AAEA,UAAM,UAAU,IAAI,QAAQ;AAC5B,QAAI,OAAO,aAAa;AACtB,cAAQ,IAAI,gBAAgB,OAAO,WAAW;AAAA,IAChD;AACA,YAAQ,IAAI,iBAAiB,qCAAqC;AAClE,QAAI,OAAO,KAAM,SAAQ,IAAI,QAAQ,OAAO,IAAI;AAChD,YAAQ,IAAI,kBAAkB,OAAO,OAAO,IAAI,CAAC;AAEjD,WAAO,IAAI,SAAS,OAAO,MAAM,EAAE,QAAQ,CAAC;AAAA,EAC9C;AACF;AAWA,SAAS,iBAAiB,SAAkB,UAAoB;AAC9D,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,MAAI,WAAW,cAAc,SAAS,IAAI,kBAAkB,EAAE,KAAK,GAAG,CAAC;AACvE,SAAO,IAAI,SAAS;AACtB;AAEA,SAAS,eAAe,QAA+B;AACrD,QAAM,MAAM,IAAI,IAAI,MAAM;AAC1B,QAAM,SAAS;AACf,MAAI,CAAC,IAAI,SAAS,WAAW,MAAM,EAAG,QAAO;AAC7C,QAAM,UAAU,IAAI,SAAS,MAAM,OAAO,MAAM;AAChD,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,mBAAmB,OAAO;AACnC;AAIO,IAAM,MAAM,WAAW;;;AMlF9B,SAAS,gBAAAC,qBAAoB;AAQ7B,IAAM,gBAAgB;AACtB,IAAM,YAAY;AAClB,IAAM,kBAAkB;AACxB,IAAM,cAAc;AACpB,IAAM,cAAc;AAMb,IAAM,WAAW;AAAA,EACtB,MAAM,IAAI,SAAkB,OAAc;AACxC,UAAM,EAAE,IAAI,IAAI,MAAM,MAAM;AAC5B,WAAO,SAAS,OAAO,IAAI,QAAQC,kBAAiB,SAAS,GAAG,CAAC,CAAC;AAAA,EACpE;AAAA,EACA,MAAM,OAAO,SAAqC;AAChD,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,MAAM,oBAAoB,IAAI,QAAQ;AAC5C,QAAI,CAAC,KAAK;AACR,aAAOC,cAAa,KAAK,EAAE,OAAO,cAAc,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpE;AAEA,QAAI,IAAI,SAAS,IAAI,KAAK,IAAI,WAAW,GAAG,GAAG;AAC7C,aAAOA,cAAa,KAAK,EAAE,OAAO,cAAc,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpE;AAEA,UAAM,WAAWC,oBAAmB;AACpC,UAAM,UAAU,SAAS;AACzB,QAAI,CAAC,SAAS;AACZ,aAAOD,cAAa;AAAA,QAClB,EAAE,OAAO,gCAAgC;AAAA,QACzC,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,SAAS,MAAM,QAAQ,IAAI,GAAG;AACpC,QAAI,CAAC,QAAQ;AACX,aAAOA,cAAa,KAAK,EAAE,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAClE;AAEA,UAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AAChD,UAAM,UAAU,OAAO,aAAa,WAAW,QAAQ,KAAK;AAE5D,QAAI,CAAC,SAAS;AACZ,aAAO,aAAa,QAAQ;AAAA,QAC1B,sBAAsB;AAAA,QACtB,mBAAmB;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,QAAI,eAAmD;AACvD,QAAI,gBAAoC;AAExC,QAAI,OAAO,SAAS,YAAY,GAAG;AACjC,qBAAe;AACf,sBAAgB;AAAA,IAClB,WAAW,OAAO,SAAS,YAAY,GAAG;AACxC,qBAAe;AACf,sBAAgB;AAAA,IAClB;AAEA,UAAM,QAAQ,OAAO,gBAAgB;AACrC,QAAI,CAAC,gBAAgB,SAAS,CAAC,SAAS,kBAAkB;AACxD,aAAO,aAAa,QAAQ;AAAA,QAC1B,sBAAsB,QAClB,eACA,CAAC,SAAS,mBACR,uBACA;AAAA,QACN,sBAAsB,OAAO,SAAS,YAAY,IAC9C,SACA,OAAO,SAAS,YAAY,IAC1B,SACA;AAAA,QACN,mBAAmB;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,QAAI;AACF,YAAM,QAAQ;AAAA,QACZ,IAAI,aAAa,IAAI,GAAG;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,YAAM,UAAU;AAAA,QACd,IAAI,aAAa,IAAI,GAAG;AAAA,QACxB;AAAA,QACA;AAAA,QACA,iBAAiB;AAAA,MACnB;AAEA,YAAM,SAAS,MAAM,SAAS,iBAAiB,UAAU,OAAO,MAAM;AAAA,QACpE;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AAED,aAAO,IAAI,SAAS,OAAO,MAAM;AAAA,QAC/B,SAAS;AAAA,UACP,gBAAgB,OAAO;AAAA,UACvB,iBAAiB;AAAA,UACjB,MAAM;AAAA,UACN,sBAAsB;AAAA,UACtB,mBAAmB;AAAA,UACnB,qBAAqB,OAAO,KAAK;AAAA,UACjC,uBAAuB,OAAO,OAAO;AAAA,UACrC,qBAAqB,OAAO,eAAe;AAAA,UAC3C,sBAAsB;AAAA,QACxB;AAAA,MACF,CAAC;AAAA,IACH,SAAS,GAAG;AACV,aAAO,aAAa,QAAQ;AAAA,QAC1B,sBAAsB;AAAA,QACtB,mBAAmB;AAAA,QACnB,qBAAqB,aAAa,QAAQ,EAAE,OAAO;AAAA,MACrD,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,SAASD,kBAAiB,SAAkB,UAAoB;AAC9D,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,MAAI,WAAW,YAAY,SAAS,IAAI,kBAAkB,EAAE,KAAK,GAAG,CAAC;AACrE,SAAO,IAAI,SAAS;AACtB;AAEA,SAAS,oBAAoB,UAAiC;AAC5D,QAAM,SAAS;AACf,MAAI,CAAC,SAAS,WAAW,MAAM,EAAG,QAAO;AACzC,QAAM,UAAU,SAAS,MAAM,OAAO,MAAM;AAC5C,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,mBAAmB,OAAO;AACnC;AAEA,SAAS,SACP,OACA,KACA,KACA,UACA;AACA,QAAM,SAAS,OAAO,SAAS,SAAS,IAAI,EAAE;AAC9C,MAAI,CAAC,OAAO,SAAS,MAAM,GAAG;AAC5B,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,MAAM,CAAC;AAC5C;AAEA,SAAS,aACP,QACA,cACU;AACV,QAAM,UAAU,IAAI,QAAQ;AAC5B,MAAI,OAAO,aAAa;AACtB,YAAQ,IAAI,gBAAgB,OAAO,WAAW;AAAA,EAChD;AACA,UAAQ,IAAI,iBAAiB,qCAAqC;AAClE,UAAQ,IAAI,kBAAkB,OAAO,OAAO,IAAI,CAAC;AACjD,MAAI,OAAO,KAAM,SAAQ,IAAI,QAAQ,OAAO,IAAI;AAChD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,gBAAgB,CAAC,CAAC,GAAG;AAC7D,YAAQ,IAAI,KAAK,KAAK;AAAA,EACxB;AACA,SAAO,IAAI,SAAS,OAAO,MAAM,EAAE,QAAQ,CAAC;AAC9C;AAIO,IAAMG,OAAM,SAAS;;;AChL5B,SAAS,gBAAAC,qBAAoB;AAC7B,SAAS,kCAAkC;;;ACT3C,IAAM,eAAe;AACrB,IAAM,kBAAkB;AACxB,IAAM,yBAAyB;AAI/B,SAAS,cAAc,UAAkB;AACvC,MAAI,aAAa,IAAK,QAAO;AAC7B,SAAO,SAAS,SAAS,GAAG,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI;AAC1D;AAEO,SAAS,4BAA4B,QAAoC;AAC9E,MAAI,OAAO,SAAS,YAAY,EAAG,QAAO;AAC1C,MAAI,OAAO,SAAS,YAAY,EAAG,QAAO;AAC1C,SAAO;AACT;AAEO,SAAS,0BACd,OACA,SACA;AACA,QAAM,MAAM,IAAI;AAAA,IACd,GAAG,eAAe,GAAG,cAAc,MAAM,QAAQ,CAAC,GAAG,MAAM,MAAM;AAAA,IACjE;AAAA,EACF;AACA,MAAI,aAAa,IAAI,aAAa,OAAO;AACzC,MAAI,aAAa,KAAK;AACtB,SAAO,IAAI,SAAS;AACtB;AAEA,SAAS,WAAW,OAAe;AACjC,SAAO,mBAAmB,SAAS,MAAM;AAC3C;AAEO,SAAS,uBACd,OACA,SACA;AACA,MAAI,YAAY,SAAU,QAAO;AAEjC,QAAM,UAAU,MAAM,aAAa,IAAI,GAAG;AAC1C,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,OAAO,cAAc,MAAM,QAAQ,EACtC,MAAM,GAAG,EACT,OAAO,OAAO,EACd,IAAI,UAAU,EACd,KAAK,GAAG;AACX,QAAM,QAAQ,MAAM,aAAa,IAAI,GAAG,KAAK;AAC7C,QAAM,UAAU,MAAM,aAAa,IAAI,GAAG,KAAK;AAE/C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK,WAAW,OAAO,CAAC;AAAA,IACxB,KAAK,WAAW,KAAK,CAAC;AAAA,IACtB,KAAK,WAAW,OAAO,CAAC,IAAI,OAAO;AAAA,EACrC,EAAE,KAAK,GAAG;AACZ;;;AChEA,SAAS,cAAc;AAGhB,SAAS,mBAAmB,QAA4B;AAC7D,SAAO,IAAI,OAAO;AAAA,IAChB,MAAM,OAAO;AAAA,IACb,SAAS,OAAO;AAAA,IAChB,eAAe;AAAA,EACjB,CAAC;AACH;;;ACeA,SAAS,iBAA4B;AACnC,QAAMC,OAAiB;AAAA,IACrB,cAAc,QAAQ,IAAI;AAAA,IAC1B,uBAAuB,QAAQ,IAAI;AAAA,IACnC,qBAAqB,QAAQ,IAAI;AAAA,IACjC,sBAAsB,QAAQ,IAAI;AAAA,IAClC,mCACE,QAAQ,IAAI;AAAA,EAChB;AAEA,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG,GAAG;AACtD,QAAI,IAAI,WAAW,SAAS,KAAK,OAAO,UAAU,UAAU;AAC1D,MAAAA,KAAI,GAAG,IAAI;AAAA,IACb;AAAA,EACF;AAEA,SAAOA;AACT;AAEA,eAAe,gBAAoC;AACjD,MAAI;AACF,UAAM,MAAO,MAAM;AAAA;AAAA,MACS;AAAA,IAC5B;AACA,UAAMA,OAAiB,CAAC;AACxB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,CAAC,CAAC,GAAG;AACxD,UAAI,IAAI,WAAW,SAAS,KAAK,OAAO,UAAU,UAAU;AAC1D,QAAAA,KAAI,GAAG,IAAI;AAAA,MACb;AAAA,IACF;AACA,WAAOA;AAAA,EACT,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,WAAW,QAAmB,MAAkC;AACvE,QAAM,QAAQ,OAAO,OAAO,IAAI,KAAK,EAAE,EAAE,KAAK;AAC9C,SAAO,SAAS;AAClB;AAEA,SAAS,YAAY,SAAiC;AACpD,QAAM,SAAoB,CAAC;AAE3B,aAAW,UAAU,SAAS;AAC5B,eAAW,QAAQ,OAAO,KAAK,MAAM,GAAG;AACtC,UAAI,CAAC,KAAK,WAAW,SAAS,EAAG;AACjC,YAAM,QAAQ,WAAW,QAAQ,IAAI;AACrC,UAAI,MAAO,QAAO,IAAI,IAAI;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,UAA8B;AAC3C,QAAM,aAAa,eAAe;AAClC,SAAO,SAAS,MAAM,cAAc,GAAG,UAAU;AACnD;AAEA,SAAS,aACP,QACA,MACQ;AACR,QAAM,QAAQ,WAAW,QAAQ,IAAI;AACrC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,gCAAgC,IAAI,EAAE;AAAA,EACxD;AACA,SAAO;AACT;AAwBA,eAAsB,wBAAqD;AACzE,QAAMC,OAAM,MAAM,QAAQ;AAC1B,SAAO;AAAA,IACL,OAAO,aAAaA,MAAK,cAAc;AAAA,IACvC,YAAY,WAAWA,MAAK,qBAAqB;AAAA,EACnD;AACF;;;AC/EO,SAAS,0BAA0B,OAAyC;AACjF,QAAM,OAAO;AACb,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAE9C,MAAI,KAAK,SAAS,YAAY;AAC5B,UAAM,MAAM,OAAO,KAAK,UAAU,OAAO,EAAE,EAAE,KAAK;AAClD,WAAO,MAAM,EAAE,MAAM,YAAY,IAAI,IAAI;AAAA,EAC3C;AAEA,MAAI,KAAK,SAAS,QAAQ;AACxB,UAAM,MAAM,OAAO,KAAK,MAAM,OAAO,EAAE,EAAE,KAAK;AAC9C,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA,YAAY,OAAO,KAAK,MAAM,eAAe,EAAE,EAAE,KAAK,KAAK;AAAA,IAC7D;AAAA,EACF;AAEA,SAAO;AACT;AAUO,SAAS,4BAA4B,UAAmC;AAC7E,QAAM,QAAQ;AACd,MAAI,CAAC,SAAS,MAAM,SAAS,WAAW,CAAC,MAAM,QAAQ,MAAM,KAAK,GAAG;AACnE,WAAO;AAAA,EACT;AACA,SAAO,MAAM,MAAM,CAAC,KAAK;AAC3B;AA6BO,SAAS,wBAAwB,OAAoC;AAC1E,QAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,MACE,MAAM,SAAS,WACf,MAAM,SAAS,WACf,MAAM,SAAS,UACf,MAAM,SAAS,SACf,MAAM,SAAS,SACf;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;;;AJxFA,IAAMC,iBAAgB;AACtB,IAAMC,aAAY;AAClB,IAAMC,mBAAkB;AACxB,IAAMC,eAAc;AACpB,IAAMC,eAAc;AACpB,IAAM,mBAAmB,oBAAI,IAAI,CAAC,GAAG,CAAC;AAM/B,IAAM,mBAAmB;AAAA,EAC9B,MAAM,IAAI,UAAmB,OAAc;AACzC,UAAM,EAAE,IAAI,IAAI,MAAM,MAAM;AAC5B,QAAI,IAAI,KAAK,CAAC,SAAS,SAAS,QAAQ,KAAK,SAAS,GAAG,CAAC,GAAG;AAC3D,aAAO,WAAW;AAAA,IACpB;AACA,UAAM,MAAM,IAAI,IAAI,SAAS,GAAG;AAChC,QAAI,WAAW,qBAAqB,GAAG;AACvC,WAAO,iBAAiB,OAAO,IAAI,QAAQ,IAAI,SAAS,GAAG,QAAQ,CAAC;AAAA,EACtE;AAAA,EACA,MAAM,OAAO,SAAqC;AAChD,UAAM,UAAU;AAAA,MACd,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AAAA,IACnC;AACA,WAAO,mBAAmB,SAAS,SAAS,MAAM,UAAU,OAAO,CAAC;AAAA,EACtE;AACF;AAEA,SAAS,qBAAqB,KAAe;AAC3C,SAAO,qBAAqB,IAAI,IAAI,kBAAkB,EAAE,KAAK,GAAG,CAAC;AACnE;AAEA,SAASC,UACP,OACA,KACA,KACA,UACA;AACA,QAAM,SAAS,OAAO,SAAS,SAAS,IAAI,EAAE;AAC9C,MAAI,CAAC,OAAO,SAAS,MAAM,EAAG,QAAO;AACrC,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,MAAM,CAAC;AAC5C;AAEA,SAAS,aAAa,SAAkB;AACtC,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,MAAI,IAAI,aAAa,IAAI,GAAG,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,iBAAiB,SAAkB;AAC1C,MAAI,QAAQ,WAAW,MAAO,QAAO;AACrC,SAAO,CAAC,QAAQ,QAAQ,IAAI,OAAO;AACrC;AAEA,SAAS,kBACP,UACA,SACA,OACA,SACA;AACA,QAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;AAC5C,UAAQ,IAAI,iBAAiB,aAAa,OAAO,CAAC;AAClD,UAAQ,IAAI,wBAAwB,KAAK;AACzC,MAAI,QAAS,SAAQ,IAAI,qBAAqB,OAAO;AACrD,SAAO;AACT;AAEA,eAAe,oBACb,SACA,SACA;AACA,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,QAAM,QAAQ,uBAAuB,KAAK,OAAO;AACjD,QAAM,UAAUC,oBAAmB,EAAE;AACrC,MAAI,CAAC,SAAS,CAAC,QAAS,QAAO;AAE/B,QAAM,SAAS,MAAM,QAAQ,IAAI,KAAK;AACtC,MAAI,CAAC,QAAQ,KAAM,QAAO;AAE1B,QAAM,cACJ,OAAO,gBAAgB,YAAY,SAAS,eAAe;AAC7D,QAAM,UAAU,IAAI,QAAQ;AAC5B,UAAQ,IAAI,gBAAgB,WAAW;AACvC,UAAQ,IAAI,iBAAiB,aAAa,OAAO,CAAC;AAClD,UAAQ,IAAI,QAAQ,QAAQ;AAC5B,UAAQ,IAAI,yBAAyB,IAAI;AACzC,UAAQ,IAAI,qBAAqB,KAAK;AACtC,MAAI,OAAO,KAAM,SAAQ,IAAI,QAAQ,OAAO,IAAI;AAEhD,SAAO,IAAI,SAAS,OAAO,MAAM,EAAE,QAAQ,CAAC;AAC9C;AAEA,eAAe,mBACb,SACA,SACA,MACA;AACA,MAAI,CAAC,iBAAiB,OAAO,GAAG;AAC9B,UAAMC,YAAW,MAAM,KAAK;AAC5B,WAAO,IAAI,SAASA,UAAS,MAAM;AAAA,MACjC,QAAQA,UAAS;AAAA,MACjB,SAAS,kBAAkBA,WAAU,SAAS,QAAQ;AAAA,IACxD,CAAC;AAAA,EACH;AAEA,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,QAAM,QAAQC,gBAAe;AAC7B,QAAM,WAAW,0BAA0B,KAAK,OAAO;AACvD,QAAM,SAAS,MAAM,MAAM,MAAM,QAAQ;AACzC,MAAI,QAAQ;AACV,WAAO,IAAI,SAAS,OAAO,MAAM;AAAA,MAC/B,QAAQ,OAAO;AAAA,MACf,SAAS,kBAAkB,QAAQ,SAAS,KAAK;AAAA,IACnD,CAAC;AAAA,EACH;AAEA,QAAM,aAAa,MAAM,oBAAoB,SAAS,OAAO;AAC7D,QAAM,WAAW,cAAe,MAAM,KAAK;AAC3C,QAAM,UAAU,kBAAkB,UAAU,SAAS,MAAM;AAC3D,QAAM,SAAS,IAAI,SAAS,SAAS,MAAM;AAAA,IACzC,QAAQ,SAAS;AAAA,IACjB;AAAA,EACF,CAAC;AAED,MAAI,iBAAiB,IAAI,SAAS,MAAM,GAAG;AACzC,UAAM,UAAU,OAAO,MAAM;AAC7B,+BAA2B,GAAG,UAAU,MAAM,IAAI,UAAU,OAAO,CAAC;AAAA,EACtE;AAEA,SAAO;AACT;AAEA,SAAS,cAAc,KAAa;AAClC,QAAM,WAAWC,cAAa,SAAS,KAAK,GAAG;AAC/C,WAAS,QAAQ;AAAA,IACf;AAAA,IACA;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,WAAW;AAClB,SAAOA,cAAa,KAAK,EAAE,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClE;AAEA,SAAS,aAAa;AACpB,SAAOA,cAAa,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC1E;AAEA,SAAS,YAAY;AACnB,SAAOA,cAAa,KAAK,EAAE,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClE;AAEA,eAAe,gBACb,OACA,SACA,SACA;AACA,QAAM,SAAS,0BAA0B,KAAK;AAC9C,MAAI,CAAC,OAAQ,QAAO,SAAS;AAC7B,MAAI,SAAS,qBAAsB,QAAO,cAAc,OAAO,GAAG;AAClE,SAAO,sBAAsB,OAAO,KAAK,OAAO;AAClD;AAEA,eAAe,sBAAsB,KAAa,SAAkB;AAClE,QAAM,QAAQ,QAAQ,QAAQ,IAAI,OAAO;AACzC,QAAM,UAAU,QAAQ,QAAQ,IAAI,UAAU;AAC9C,QAAM,kBAAkB,IAAI,QAAQ;AAAA,IAClC,QAAQ,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AAAA,EAC3C,CAAC;AACD,MAAI,MAAO,iBAAgB,IAAI,SAAS,KAAK;AAC7C,MAAI,QAAS,iBAAgB,IAAI,YAAY,OAAO;AAEpD,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,SAAS;AAAA,EACX,CAAC;AACD,MAAK,CAAC,SAAS,MAAM,SAAS,WAAW,OAAQ,CAAC,SAAS,MAAM;AAC/D,WAAOA,cAAa;AAAA,MAClB,EAAE,OAAO,+BAA+B;AAAA,MACxC,EAAE,QAAQ,SAAS,UAAU,IAAI;AAAA,IACnC;AAAA,EACF;AAEA,QAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC5D,QAAM,UAAU,YAAY,WAAW,QAAQ;AAC/C,QAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AAChD,QAAM,UAAU,4BAA4B,MAAM;AAClD,QAAM,SAAS,IAAI,IAAI,QAAQ,GAAG;AAClC,QAAM,QAAQJ;AAAA,IACZ,OAAO,aAAa,IAAI,GAAG;AAAA,IAC3B;AAAA,IACAJ;AAAA,IACAD;AAAA,EACF;AACA,QAAM,UAAUK;AAAA,IACd,OAAO,aAAa,IAAI,GAAG;AAAA,IAC3BF;AAAA,IACAC;AAAA,IACAF;AAAA,EACF;AAEA,MAAI,eAAmD;AACvD,MAAI,YAAY,QAAQ;AACtB,mBAAe;AAAA,EACjB,WAAW,YAAY,QAAQ;AAC7B,mBAAe;AAAA,EACjB;AAEA,QAAM,WAAWI,oBAAmB;AACpC,QAAM,mBAAmB,SAAS;AAClC,MAAI,WAAW,CAAC,SAAS,gBAAgB,kBAAkB;AACzD,UAAM,QAAQ,uBAAuB,QAAQ,OAAO;AAEpD,QAAI;AACF,YAAM,SAAS,MAAM,iBAAiB,UAAU,SAAS,MAAM;AAAA,QAC7D;AAAA,QACA,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AACD,YAAM,cAAc,OAAO,SAAS;AACpC,YAAMI,WAAU,IAAI,QAAQ,YAAY,OAAO;AAC/C,MAAAA,SAAQ,IAAI,gBAAgB,OAAO,WAAW;AAC9C,MAAAA,SAAQ,IAAI,iBAAiB,aAAa,OAAO,CAAC;AAClD,MAAAA,SAAQ,IAAI,QAAQ,QAAQ;AAC5B,MAAAA,SAAQ,IAAI,yBAAyB,aAAa;AAClD,MAAAA,SAAQ,IAAI,qBAAqB,QAAQ,SAAS,QAAQ;AAC1D,MAAAA,SAAQ,IAAI,qBAAqB,OAAO,KAAK,CAAC;AAC9C,MAAAA,SAAQ,IAAI,uBAAuB,OAAO,OAAO,CAAC;AAElD,UAAI,YAAY,QAAQ,SAAS,SAAS,eAAe;AACvD,cAAM,CAAC,YAAY,MAAM,IAAI,YAAY,KAAK,IAAI;AAClD,mCAA2B,GAAG;AAAA,UAC5B,SAAS,cAAc,IAAI,OAAO,QAAQ;AAAA,YACxC,aAAa,OAAO;AAAA,YACpB,cAAc;AAAA,YACd,UAAU;AAAA,cACR,QAAQ;AAAA,cACR,WAAU,oBAAI,KAAK,GAAE,YAAY;AAAA,cACjC,OAAO,OAAO,KAAK;AAAA,cACnB,SAAS,OAAO,OAAO;AAAA,YACzB;AAAA,UACF,CAAC;AAAA,QACH;AAEA,eAAO,IAAI,SAAS,YAAY,EAAE,SAAAA,SAAQ,CAAC;AAAA,MAC7C;AAEA,aAAO,IAAI,SAAS,YAAY,MAAM,EAAE,SAAAA,SAAQ,CAAC;AAAA,IACnD,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,UAAU,IAAI,QAAQ;AAC5B,aAAW,UAAU;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAAG;AACD,UAAM,QAAQ,SAAS,QAAQ,IAAI,MAAM;AACzC,QAAI,MAAO,SAAQ,IAAI,QAAQ,KAAK;AAAA,EACtC;AACA,MAAI,eAAe,CAAC,QAAQ,IAAI,cAAc,GAAG;AAC/C,YAAQ,IAAI,gBAAgB,WAAW;AAAA,EACzC;AACA,UAAQ,IAAI,iBAAiB,aAAa,OAAO,CAAC;AAClD,UAAQ,IAAI,yBAAyB,SAAS;AAC9C,SAAO,IAAI,SAAS,SAAS,MAAM,EAAE,QAAQ,SAAS,QAAQ,QAAQ,CAAC;AACzE;AAEA,eAAe,UAAU,SAAkB;AACzC,QAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,QAAM,MAAM,oBAAoB,IAAI,QAAQ;AAC5C,MAAI,CAAC,IAAK,QAAO,WAAW;AAC5B,QAAM,SAAS,mBAAmB,MAAM,sBAAsB,CAAC;AAE/D,MAAI,IAAI,CAAC,MAAM,UAAU,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,SAAS;AACrD,UAAM,OAAQ,MAAM,OAAO,MAAM,SAAS;AAAA,MACxC,SAAS,IAAI,CAAC;AAAA,IAChB,CAAC;AACD,WAAO,gBAAgB,KAAK,OAAO,OAAO;AAAA,EAC5C;AAEA,MAAI,IAAI,CAAC,MAAM,UAAU,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,cAAc,IAAI,CAAC,GAAG;AAClE,UAAM,OAAQ,MAAM,OAAO,MAAM,SAAS;AAAA,MACxC,SAAS,IAAI,CAAC;AAAA,IAChB,CAAC;AACD,UAAM,eAAe,mBAAmB,IAAI,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC;AAC9D,WAAO;AAAA,MACL,4BAA4B,KAAK,aAAa,YAAY,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AAEA,MAAI,IAAI,CAAC,MAAM,WAAW,IAAI,CAAC,GAAG;AAChC,UAAM,QAAS,MAAM,OAAO,OAAO,SAAS;AAAA,MAC1C,UAAU,IAAI,CAAC;AAAA,IACjB,CAAC;AACD,QAAI,MAAM,SAAS,SAAS;AAC1B,aAAO,UAAU;AAAA,IACnB;AACA,WAAO,gBAAgB,wBAAwB,KAAK,GAAG,SAAS;AAAA,MAC9D,sBACE,MAAM,SAAS,WACf,MAAM,SAAS,SACf,MAAM,SAAS;AAAA,IACnB,CAAC;AAAA,EACH;AAEA,SAAO,WAAW;AACpB;AAEA,SAAS,oBAAoB,UAAmC;AAC9D,QAAM,SAAS;AACf,MAAI,CAAC,SAAS,WAAW,MAAM,EAAG,QAAO;AACzC,QAAM,UAAU,SAAS,MAAM,OAAO,MAAM;AAC5C,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,QAAQ,MAAM,GAAG,EAAE,IAAI,CAAC,SAAS,mBAAmB,IAAI,CAAC;AAClE;AAIO,IAAMC,OAAM,iBAAiB;;;AKlWpC,SAAS,gBAAAC,qBAAoB;;;ACbtB,IAAM,yBAAyB;AAAA,EACpC;AAAA,IACE,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACF;AAQO,SAAS,mBAAmB,OAAyB;AAC1D,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,SAAS,EAAE;AAC3E,SACE,QAAQ,SAAS,gBAAgB,KAAK,QAAQ,SAAS,eAAe;AAE1E;AAqBA,eAAsB,sBAAsB,IAEsB;AAChE,QAAM,UAAoB,CAAC;AAC3B,QAAM,SAAmB,CAAC;AAE1B,aAAW,SAAS,wBAAwB;AAC1C,QAAI;AACF,YAAM,GAAG,QAAQ,MAAM,GAAG,EAAE,MAAM;AAAA,IACpC,SAAS,OAAO;AACd,UAAI,mBAAmB,KAAK,GAAG;AAC7B,gBAAQ,KAAK,MAAM,GAAG;AAAA,MACxB,OAAO;AACL,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,eAAO,KAAK,GAAG,MAAM,GAAG,KAAK,OAAO,EAAE;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,QAAQ,WAAW,KAAK,OAAO,WAAW;AAAA,IAC9C;AAAA,IACA;AAAA,EACF;AACF;;;ADpDA,eAAe,gBAIZ;AACD,QAAM,WAAW,YAAY;AAC7B,MAAI;AACF,UAAM,SAAS,MAAM,SAClB;AAAA,MACC;AAAA,IACF,EACC,MAAyB;AAC5B,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,kBAAkB,QAAQ,QAAQ,IAAI;AAAA,IACxC;AAAA,EACF,SAAS,GAAG;AACV,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,MAChD,kBAAkB;AAAA,IACpB;AAAA,EACF;AACF;AAEO,IAAM,cAAc;AAAA,EACzB,MAAM,MAAM;AACV,WAAO,YAAY,OAAO,IAAI,QAAQ,iCAAiC,CAAC;AAAA,EAC1E;AAAA,EACA,MAAM,OAAO,UAAsC;AACjD,UAAM,QAAQ,KAAK,IAAI;AACvB,UAAM,QAAQ,MAAM,cAAc;AAClC,UAAM,OAAO,MAAM;AACnB,UAAM,UAAU,MAAM;AAEtB,QAAI,WAAW;AACf,QAAI,cAA6B;AACjC,QAAI,gBAA0B,CAAC;AAC/B,QAAI;AACF,YAAM,SAAS,MAAM,sBAAsB,YAAY,CAAC;AACxD,iBAAW,OAAO;AAClB,sBAAgB,OAAO;AACvB,UAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,sBAAc,OAAO,OAAO,KAAK,IAAI;AAAA,MACvC,WAAW,OAAO,QAAQ,SAAS,GAAG;AACpC,sBAAc,4BAA4B,OAAO,QAAQ,KAAK,IAAI,CAAC;AAAA,MACrE;AAAA,IACF,SAAS,GAAG;AACV,oBAAc,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,IACzD;AAEA,UAAM,aAAa,QAAQ;AAE3B,WAAOC,cAAa;AAAA,MAClB;AAAA,QACE,QAAQ,aAAa,OAAO;AAAA,QAC5B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,WAAW,KAAK,IAAI,IAAI;AAAA,QACxB,QAAQ;AAAA,UACN,IAAI,OAAO,OAAO;AAAA,UAClB,UAAU;AAAA,UACV,QAAQ,WAAW,OAAO;AAAA,UAC1B,cAAc;AAAA,UACd,gBAAgB;AAAA,QAClB;AAAA,QACA,SAAS;AAAA,MACX;AAAA,MACA;AAAA,QACE,QAAQ,aAAa,MAAM;AAAA,QAC3B,SAAS;AAAA,UACP,iBAAiB;AAAA,UACjB,+BAA+B;AAAA,QACjC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAMC,OAAM,YAAY;AAO/B,eAAsB,kBAAkB,SAAqC;AAC3E,SAAO,YAAY,OAAO,OAAO;AACnC;;;AEZA,SAAS,YAAY,UAAkB,OAAwB;AAC7D,MAAI,MAAM,SAAS,GAAG,EAAG,QAAO,SAAS,WAAW,KAAK;AACzD,SAAO,aAAa,SAAS,SAAS,WAAW,GAAG,KAAK,GAAG;AAC9D;AAEA,SAAS,oBAAkC;AACzC,SAAO;AAAA,IACL;AAAA,MACE,OAAO,CAAC,QAAQ,IAAI,IAAI,IAAI,GAAG,EAAE,aAAa;AAAA,MAC9C,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,OAAO,CAAC,QACN,YAAY,IAAI,IAAI,IAAI,GAAG,EAAE,UAAU,oBAAoB;AAAA,MAC7D,QAAQ,iBAAiB;AAAA,IAC3B;AAAA,IACA;AAAA,MACE,OAAO,CAAC,QACN,YAAY,IAAI,IAAI,IAAI,GAAG,EAAE,UAAU,aAAa;AAAA,MACtD,QAAQ,WAAW;AAAA,IACrB;AAAA,IACA;AAAA,MACE,OAAO,CAAC,QACN,YAAY,IAAI,IAAI,IAAI,GAAG,EAAE,UAAU,WAAW;AAAA,MACpD,QAAQ,SAAS;AAAA,IACnB;AAAA,EACF;AACF;AAYO,SAAS,oBACd,SACkB;AAClB,QAAM,UAA2B,QAAQ;AACzC,QAAM,OAAO,EAAE,iBAAiB,QAAQ,WAAW,gBAAgB;AAEnE,QAAM,SAAuB,kBAAkB;AAE/C,MAAI,QAAQ,aAAa;AACvB,eAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,QAAQ,WAAW,GAAG;AAC9D,YAAM,aAAa,KAAK;AACxB,aAAO,KAAK;AAAA,QACV,OAAO,CAAC,QAAQ,IAAI,IAAI,IAAI,GAAG,EAAE,aAAa;AAAA,QAC9C,QAAQ,OAAO,QAAQ;AACrB,gBAAM,MAAM,MAAM;AAClB,iBAAO,IAAI,QAAQ,KAAK,SAAS,SAAS,IAAI;AAAA,QAChD;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,oBAA8C;AAAA,IAClD,YAAY,QAAQ;AAAA,IACpB,eAAe,QAAQ;AAAA,EACzB;AAEA,SAAO;AAAA,IACL,MAAM,MAAM,SAASC,MAAK,KAAK;AAC7B,WAAK;AAGL,YAAM,eAAe,MAAM;AAAA,QACzB;AAAA,QACAA;AAAA,QACA;AAAA,MACF;AACA,UAAI,aAAc,QAAO;AAGzB,iBAAW,SAAS,QAAQ;AAC1B,YAAI,MAAM,MAAM,OAAO,EAAG,QAAO,MAAM,OAAO,OAAO;AAAA,MACvD;AAGA,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ACrJO,IAAM,2BAAqD;AAAA,EAChE,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,UAAU;AAAA,IACR,SAAS;AAAA,IACT,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,mBAAmB;AAAA,IACnB,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,kBAAkB,CAAC,wBAAwB;AAEjD,SAAS,kBAAkB,IAAoC;AACpE,SAAO,gBAAgB,KAAK,CAAC,YAAY,QAAQ,OAAO,EAAE;AAC5D;;;ACEA,SAAS,SAASC,MAAc,MAAc;AAC5C,QAAM,QAAQ,OAAOA,KAAI,IAAI,KAAK,EAAE,EAAE,KAAK;AAC3C,SAAO,SAAS;AAClB;AAEA,SAAS,OAAOA,MAAc,MAAc;AAC1C,SAAO,QAAQ,SAASA,MAAK,IAAI,CAAC;AACpC;AAEA,SAAS,aAAa,QAA+C,SAAiB;AACpF,SAAO,QAAQ,QAAQ,cAAc,KAAK,CAAC,SAAS,KAAK,YAAY,OAAO,CAAC;AAC/E;AAEA,SAAS,aAAa,QAA+C,SAAiB;AACpF,SAAO,QAAQ,QAAQ,YAAY,KAAK,CAAC,SAAS,KAAK,YAAY,OAAO,CAAC;AAC7E;AAEA,SAAS,iBACP,QACA,SACA;AACA,QAAM,SAAS,QAAQ;AACvB,MAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,WAAO,OAAO,KAAK,CAAC,SAAS,KAAK,YAAY,OAAO;AAAA,EACvD;AACA,SAAO,QAAQ,YAAY;AAC7B;AAEA,SAAS,cAAc,QAA6B;AAClD,MAAI,WAAW,KAAM,QAAO;AAC5B,MAAI,WAAW,OAAQ,QAAO;AAC9B,SAAO;AACT;AAEA,SAAS,cACP,QACA,QACqB;AACrB,MACE,OAAO,KAAK,CAAC,UAAU,MAAM,WAAW,SAAS,KACjD,OAAO,KAAK,CAAC,UAAU,MAAM,qBAAqB,SAAS,GAC3D;AACA,WAAO;AAAA,EACT;AAEA,MACE,OAAO,KAAK,CAAC,UAAU,MAAM,WAAW,MAAM,KAC9C,OAAO,KAAK,CAAC,UAAU,MAAM,qBAAqB,MAAM,GACxD;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,iBACP,QACsB;AACtB,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,aAAa,QAAQ,IAAI,IAAI,OAAO;AAAA,MAC5C,UAAU;AAAA,MACV,QAAQ,aAAa,QAAQ,IAAI,IAC7B,uCACA;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,aAAa,QAAQ,eAAe,IAAI,OAAO;AAAA,MACvD,UAAU;AAAA,MACV,QAAQ,aAAa,QAAQ,eAAe,IACxC,kDACA;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,OAAO;AAAA,MACpD,UAAU;AAAA,MACV,QAAQ,iBAAiB,QAAQ,QAAQ,IACrC,+CACA;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,QAAQ,eAAe,UAAU,OAAO;AAAA,MAChD,UAAU;AAAA,MACV,QAAQ,QAAQ,eAAe,UAC3B,sCACA;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAEA,SAAS,aAAaA,MAAoC;AACxD,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,OAAOA,MAAK,cAAc,IAAI,OAAO;AAAA,MAC7C,UAAU;AAAA,MACV,QAAQ,OAAOA,MAAK,cAAc,IAC9B,+BACA;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,OAAOA,MAAK,mCAAmC,IAAI,OAAO;AAAA,MAClE,UAAU;AAAA,MACV,QAAQ,OAAOA,MAAK,mCAAmC,IACnD,oDACA;AAAA,MACJ,QACE;AAAA,IACJ;AAAA,EACF;AACF;AAEA,SAAS,kBACPA,MACA,OACmE;AACnE,QAAM,mBAAmB,OAAOA,MAAK,MAAM,OAAO,aAAa;AAC/D,QAAM,aAAa,QAAQ,MAAM,OAAO,mBAAmB;AAC3D,QAAM,mBAAmB,mBACrB,QACA,aACE,YACA;AACN,QAAM,mBAAmB,qBAAqB,YAAY,YAAY;AAEtE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,YACPA,MACA,QACsB;AACtB,SAAO,OAAO,IAAI,CAAC,WAAW;AAAA,IAC5B,IAAI,MAAM;AAAA,IACV,QAAQ,MAAM,WAAW;AAAA,IACzB,OAAO,MAAM,WAAW;AAAA,IACxB,UAAU,MAAM,OAAO;AAAA,IACvB,YAAY,MAAM,OAAO;AAAA,IACzB,eAAe,MAAM,OAAO;AAAA,IAC5B,eAAe,MAAM,OAAO;AAAA,IAC5B,GAAG,kBAAkBA,MAAK,KAAK;AAAA,EACjC,EAAE;AACJ;AAEA,SAAS,cAAc,QAAuC;AAC5D,SAAO,MAAM;AAAA,IACX,IAAI;AAAA,MACF,OACG,OAAO,CAAC,UAAU,MAAM,WAAW,QAAQ,MAAM,MAAM,EACvD,IAAI,CAAC,UAAU,MAAM,MAAgB;AAAA,IAC1C;AAAA,EACF;AACF;AAEA,SAAS,oBAAoB,OAA+C;AAC1E,MAAI,MAAM,WAAW,KAAM,QAAO;AAClC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ;AAAA,EACV;AACF;AAEO,SAAS,yBACd,UAA2C,CAAC,GACvB;AACrB,QAAMA,OAAM,QAAQ,OAAO,QAAQ;AACnC,QAAM,YAAY,QAAQ,aAAa,iBAAiB;AACxD,QAAM,UAAU,kBAAkB,SAAS;AAC3C,QAAM,SAAS;AAAA,IACb,GAAG,iBAAiB,QAAQ,cAAc;AAAA,IAC1C,GAAG,aAAaA,IAAG;AAAA,EACrB,EAAE,IAAI,mBAAmB;AACzB,QAAM,SAAS,YAAYA,MAAK,QAAQ,UAAU,CAAC,CAAC;AACpD,QAAM,SAAS,cAAc,QAAQ,MAAM;AAC3C,QAAM,eAAe,OAClB,OAAO,CAAC,UAAU,MAAM,qBAAqB,SAAS,EACtD;AAAA,IACC,CAAC,UACC,OAAO,MAAM,aAAa,YAAY,MAAM,EAAE;AAAA,EAClD;AAEF,SAAO;AAAA,IACL,SAAS;AAAA,MACP;AAAA,MACA,SAAS,cAAc,MAAM;AAAA,IAC/B;AAAA,IACA,SAAS;AAAA,MACP,IAAI;AAAA,MACJ,OAAO,SAAS,SAAS;AAAA,MACzB,eAAe,SAAS,UAAU;AAAA,IACpC;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,CAAC,GAAG,cAAc,MAAM,GAAG,GAAG,YAAY;AAAA,EACvD;AACF;;;ACtOA,SAAS,qBAAqB,SAA0C;AACtE,SAAO;AAAA,IACL,cAAc,QAAQ,WAAW,IAAI,IAAI,CAAC,EAAE,SAAS,KAAK,CAAC,IAAI,CAAC;AAAA,IAChE,YAAY,QAAQ,WAAW,eAAe,IAC1C,CAAC,EAAE,SAAS,gBAAgB,CAAC,IAC7B,CAAC;AAAA,IACL,QAAQ,QAAQ,WAAW,QAAQ,IAAI,EAAE,SAAS,SAAS,IAAI;AAAA,EACjE;AACF;AAEA,SAAS,mBAAmB,SAAyB;AACnD,MAAI,YAAY,mBAAoB,QAAO;AAC3C,MAAI,YAAY,wBAAyB,QAAO;AAChD,MAAI,YAAY,2BAA4B,QAAO;AACnD,MAAI,YAAY,wBAAyB,QAAO;AAChD,MAAI,YAAY,eAAgB,QAAO;AACvC,MAAI,YAAY,iBAAkB,QAAO;AACzC,SAAO;AACT;AAEA,SAAS,eAAe,OAA6D;AACnF,QAAM,WACJ,MAAM,WAAW,YAAY,UAAU,MAAM,WAAW,SAAS,YAAY;AAC/E,SAAO;AAAA,IACL,MAAM,mBAAmB,MAAM,EAAE;AAAA,IACjC,SAAS,MAAM,UAAU,MAAM;AAAA,IAC/B;AAAA,EACF;AACF;AAEA,SAAS,eACP,OACsB;AACtB,MAAI,MAAM,qBAAqB,UAAW,QAAO;AACjD,SAAO;AAAA,IACL,MAAM,uBAAuB,MAAM,EAAE;AAAA,IACrC,SAAS,OAAO,MAAM,aAAa,YAAY,MAAM,EAAE;AAAA,IACvD,UAAU;AAAA,EACZ;AACF;AAEO,SAAS,iBACd,SAC6B;AAC7B,QAAM,iBAAiB,QAAQ,kBAAkB,qBAAqB,QAAQ,OAAO;AACrF,QAAM,SAAS,yBAAyB;AAAA,IACtC,KAAK,QAAQ;AAAA,IACb;AAAA;AAAA;AAAA;AAAA,IAIA,QAAQ,CAAC;AAAA,EACX,CAAC;AACD,QAAM,WAA4B;AAAA,IAChC,GAAG,OAAO,OACP,OAAO,CAAC,UAAU,MAAM,WAAW,IAAI,EACvC,IAAI,cAAc;AAAA,IACrB,GAAG,OAAO,OACP,IAAI,cAAc,EAClB,OAAO,CAAC,YAAsC,YAAY,IAAI;AAAA,EACnE;AACA,SAAO,EAAE,GAAG,QAAQ,SAAS;AAC/B;","names":["env","env","env","options","getRuntimePlatform","getPublicCache","getRuntimePlatform","NextResponse","buildInternalUrl","NextResponse","getRuntimePlatform","GET","NextResponse","env","env","DEFAULT_WIDTH","MAX_WIDTH","DEFAULT_QUALITY","MIN_QUALITY","MAX_QUALITY","clampInt","getRuntimePlatform","response","getPublicCache","NextResponse","headers","GET","NextResponse","NextResponse","GET","env","env"]}
|