@notionx/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/index.d.ts +137 -0
- package/dist/admin/index.js +206 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/index.d.ts +324 -0
- package/dist/admin/pages/index.js +827 -0
- package/dist/admin/pages/index.js.map +1 -0
- package/dist/auth/auth-pages/forgot-password.d.ts +20 -0
- package/dist/auth/auth-pages/forgot-password.js +70 -0
- package/dist/auth/auth-pages/forgot-password.js.map +1 -0
- package/dist/auth/auth-pages/index.d.ts +6 -0
- package/dist/auth/auth-pages/index.js +342 -0
- package/dist/auth/auth-pages/index.js.map +1 -0
- package/dist/auth/auth-pages/login.d.ts +30 -0
- package/dist/auth/auth-pages/login.js +125 -0
- package/dist/auth/auth-pages/login.js.map +1 -0
- package/dist/auth/auth-pages/register.d.ts +17 -0
- package/dist/auth/auth-pages/register.js +81 -0
- package/dist/auth/auth-pages/register.js.map +1 -0
- package/dist/auth/auth-pages/reset-password.d.ts +18 -0
- package/dist/auth/auth-pages/reset-password.js +72 -0
- package/dist/auth/auth-pages/reset-password.js.map +1 -0
- package/dist/auth/index.d.ts +72 -0
- package/dist/auth/index.js +1011 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/passwords.d.ts +6 -0
- package/dist/auth/passwords.js +79 -0
- package/dist/auth/passwords.js.map +1 -0
- package/dist/auth/rate-limit.d.ts +28 -0
- package/dist/auth/rate-limit.js +245 -0
- package/dist/auth/rate-limit.js.map +1 -0
- package/dist/auth/routes/google-callback.d.ts +6 -0
- package/dist/auth/routes/google-callback.js +404 -0
- package/dist/auth/routes/google-callback.js.map +1 -0
- package/dist/auth/routes/google.d.ts +6 -0
- package/dist/auth/routes/google.js +250 -0
- package/dist/auth/routes/google.js.map +1 -0
- package/dist/auth/routes/index.d.ts +22 -0
- package/dist/auth/routes/index.js +619 -0
- package/dist/auth/routes/index.js.map +1 -0
- package/dist/auth/routes/verify-email.d.ts +6 -0
- package/dist/auth/routes/verify-email.js +317 -0
- package/dist/auth/routes/verify-email.js.map +1 -0
- package/dist/auth/routes/viewer.d.ts +6 -0
- package/dist/auth/routes/viewer.js +372 -0
- package/dist/auth/routes/viewer.js.map +1 -0
- package/dist/auth/session.d.ts +9 -0
- package/dist/auth/session.js +1 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/auth/turnstile.d.ts +20 -0
- package/dist/auth/turnstile.js +301 -0
- package/dist/auth/turnstile.js.map +1 -0
- package/dist/auth/user-session.d.ts +42 -0
- package/dist/auth/user-session.js +419 -0
- package/dist/auth/user-session.js.map +1 -0
- package/dist/auth/users.d.ts +112 -0
- package/dist/auth/users.js +558 -0
- package/dist/auth/users.js.map +1 -0
- package/dist/bootstrap-CN2g76M6.d.ts +67 -0
- package/dist/cache/index.d.ts +6 -0
- package/dist/cache/index.js +47 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/content/admin-summary.d.ts +24 -0
- package/dist/content/admin-summary.js +36 -0
- package/dist/content/admin-summary.js.map +1 -0
- package/dist/content/index.d.ts +9 -0
- package/dist/content/index.js +473 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/models.d.ts +69 -0
- package/dist/content/models.js +24 -0
- package/dist/content/models.js.map +1 -0
- package/dist/content/prewarm.d.ts +28 -0
- package/dist/content/prewarm.js +56 -0
- package/dist/content/prewarm.js.map +1 -0
- package/dist/content/revalidate.d.ts +37 -0
- package/dist/content/revalidate.js +170 -0
- package/dist/content/revalidate.js.map +1 -0
- package/dist/content/search-index.d.ts +54 -0
- package/dist/content/search-index.js +172 -0
- package/dist/content/search-index.js.map +1 -0
- package/dist/content/search.d.ts +8 -0
- package/dist/content/search.js +57 -0
- package/dist/content/search.js.map +1 -0
- package/dist/doctor/cli.d.ts +1 -0
- package/dist/doctor/cli.js +360 -0
- package/dist/doctor/cli.js.map +1 -0
- package/dist/doctor/index.d.ts +139 -0
- package/dist/doctor/index.js +289 -0
- package/dist/doctor/index.js.map +1 -0
- package/dist/email/index.d.ts +38 -0
- package/dist/email/index.js +126 -0
- package/dist/email/index.js.map +1 -0
- package/dist/env-C5qu-0R-.d.ts +35 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/i18n/index.d.ts +26 -0
- package/dist/i18n/index.js +73 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1281 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/admin/index.d.ts +75 -0
- package/dist/internal/admin/index.js +365 -0
- package/dist/internal/admin/index.js.map +1 -0
- package/dist/media/index.d.ts +24 -0
- package/dist/media/index.js +86 -0
- package/dist/media/index.js.map +1 -0
- package/dist/media/routes/index.d.ts +1 -0
- package/dist/media/routes/index.js +585 -0
- package/dist/media/routes/index.js.map +1 -0
- package/dist/media/routes/notion-media.d.ts +19 -0
- package/dist/media/routes/notion-media.js +588 -0
- package/dist/media/routes/notion-media.js.map +1 -0
- package/dist/middleware.d.ts +95 -0
- package/dist/middleware.js +79 -0
- package/dist/middleware.js.map +1 -0
- package/dist/notion/block-text.d.ts +5 -0
- package/dist/notion/block-text.js +37 -0
- package/dist/notion/block-text.js.map +1 -0
- package/dist/notion/blocks.d.ts +24 -0
- package/dist/notion/blocks.js +46 -0
- package/dist/notion/blocks.js.map +1 -0
- package/dist/notion/client.d.ts +7 -0
- package/dist/notion/client.js +13 -0
- package/dist/notion/client.js.map +1 -0
- package/dist/notion/config.d.ts +25 -0
- package/dist/notion/config.js +147 -0
- package/dist/notion/config.js.map +1 -0
- package/dist/notion/content-cache.d.ts +45 -0
- package/dist/notion/content-cache.js +166 -0
- package/dist/notion/content-cache.js.map +1 -0
- package/dist/notion/generic-source.d.ts +61 -0
- package/dist/notion/generic-source.js +408 -0
- package/dist/notion/generic-source.js.map +1 -0
- package/dist/notion/index.d.ts +13 -0
- package/dist/notion/index.js +1278 -0
- package/dist/notion/index.js.map +1 -0
- package/dist/notion/mappers.d.ts +1 -0
- package/dist/notion/mappers.js +152 -0
- package/dist/notion/mappers.js.map +1 -0
- package/dist/notion/media.d.ts +22 -0
- package/dist/notion/media.js +209 -0
- package/dist/notion/media.js.map +1 -0
- package/dist/notion/property-mappers.d.ts +24 -0
- package/dist/notion/property-mappers.js +152 -0
- package/dist/notion/property-mappers.js.map +1 -0
- package/dist/notion/routes/index.d.ts +8 -0
- package/dist/notion/routes/index.js +428 -0
- package/dist/notion/routes/index.js.map +1 -0
- package/dist/notion/routes/webhook.d.ts +98 -0
- package/dist/notion/routes/webhook.js +428 -0
- package/dist/notion/routes/webhook.js.map +1 -0
- package/dist/notion/types.d.ts +152 -0
- package/dist/notion/types.js +1 -0
- package/dist/notion/types.js.map +1 -0
- package/dist/notion/webhook.d.ts +83 -0
- package/dist/notion/webhook.js +490 -0
- package/dist/notion/webhook.js.map +1 -0
- package/dist/platform/capabilities.d.ts +34 -0
- package/dist/platform/capabilities.js +42 -0
- package/dist/platform/capabilities.js.map +1 -0
- package/dist/platform/current.d.ts +13 -0
- package/dist/platform/current.js +181 -0
- package/dist/platform/current.js.map +1 -0
- package/dist/platform/index.d.ts +5 -0
- package/dist/platform/index.js +269 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/runtime.d.ts +118 -0
- package/dist/platform/runtime.js +160 -0
- package/dist/platform/runtime.js.map +1 -0
- package/dist/platform/selection.d.ts +10 -0
- package/dist/platform/selection.js +22 -0
- package/dist/platform/selection.js.map +1 -0
- package/dist/storage/index.d.ts +17 -0
- package/dist/storage/index.js +218 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/routes/cdn.d.ts +19 -0
- package/dist/storage/routes/cdn.js +289 -0
- package/dist/storage/routes/cdn.js.map +1 -0
- package/dist/storage/routes/files.d.ts +27 -0
- package/dist/storage/routes/files.js +216 -0
- package/dist/storage/routes/files.js.map +1 -0
- package/dist/storage/routes/index.d.ts +2 -0
- package/dist/storage/routes/index.js +352 -0
- package/dist/storage/routes/index.js.map +1 -0
- package/dist/types-BsAcZSNX.d.ts +94 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/util/index.d.ts +18 -0
- package/dist/util/index.js +48 -0
- package/dist/util/index.js.map +1 -0
- package/dist/worker/index.d.ts +6 -0
- package/dist/worker/index.js +1026 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/routes/content-prewarm.d.ts +34 -0
- package/dist/worker/routes/content-prewarm.js +38 -0
- package/dist/worker/routes/content-prewarm.js.map +1 -0
- package/dist/worker/routes/content-revalidate.d.ts +81 -0
- package/dist/worker/routes/content-revalidate.js +64 -0
- package/dist/worker/routes/content-revalidate.js.map +1 -0
- package/dist/worker/routes/health.d.ts +14 -0
- package/dist/worker/routes/health.js +278 -0
- package/dist/worker/routes/health.js.map +1 -0
- package/dist/worker/routes/index.d.ts +6 -0
- package/dist/worker/routes/index.js +373 -0
- package/dist/worker/routes/index.js.map +1 -0
- package/package.json +124 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/content/models.ts","../../src/i18n/config.ts","../../src/content/revalidate.ts","../../src/content/search.ts","../../src/content/search-index.ts","../../src/content/prewarm.ts","../../src/content/admin-summary.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","export const supportedLocales = [\"zh-CN\", \"en-US\"] as const;\n\nexport type AppLocale = (typeof supportedLocales)[number];\n\nexport const defaultLocale: AppLocale = \"zh-CN\";\n\nexport function isAppLocale(value: string): value is AppLocale {\n return (supportedLocales as readonly string[]).includes(value);\n}\n\nexport function localizedMovieListPath(locale: AppLocale) {\n return `/${locale}/movies`;\n}\n\nexport function localizedMovieDetailPath(locale: AppLocale, slug: string) {\n return `/${locale}/movies/${slug}`;\n}\n\nexport function expandLocalizedMoviePaths(\n paths: readonly string[],\n locale?: string\n) {\n const locales =\n locale && isAppLocale(locale) ? [locale] : [...supportedLocales];\n const expanded: string[] = [];\n\n for (const path of paths) {\n if (path === \"/movies\" || path.startsWith(\"/movies/\")) {\n for (const currentLocale of locales) {\n expanded.push(`/${currentLocale}${path}`);\n }\n continue;\n }\n expanded.push(path);\n }\n\n return Array.from(new Set(expanded));\n}\n","// packages/nextion/src/content/revalidate.ts\n//\n// Generic content revalidation helpers. The starter's\n// `revalidateContentModel` is project-specific (it knows how to\n// resolve localized movie paths and which content models exist) and\n// lives in the starter. The helpers below are model-agnostic and are\n// re-exported by the starter's `lib/content/revalidate.ts`.\n\nimport { expandLocalizedMoviePaths } from \"../i18n/config\";\nimport type { ContentModelDefinition } from \"./models\";\nimport { getRegisteredSource } from \"./models\";\n\ntype RevalidatePathFn = (\n path: string,\n type?: \"page\" | \"layout\"\n) => void | Promise<void>;\n\nexport type { RevalidatePathFn };\n\nexport type InvalidationKind = \"publish\" | \"update\" | \"delete\";\n\nexport type ContentRevalidateRequest = {\n modelId: string;\n pageId?: string;\n routeId?: string;\n previousRouteId?: string;\n locale?: string;\n kind?: InvalidationKind;\n includeApi?: boolean;\n};\n\nfunction asObject(input: unknown): Record<string, unknown> | null {\n return input && typeof input === \"object\" && !Array.isArray(input)\n ? (input as Record<string, unknown>)\n : null;\n}\n\nfunction readString(input: Record<string, unknown>, key: string) {\n const value = input[key];\n return typeof value === \"string\" ? value.trim() : \"\";\n}\n\nfunction readKind(input: Record<string, unknown>): InvalidationKind {\n const value = readString(input, \"kind\");\n if (value === \"publish\" || value === \"delete\" || value === \"update\") {\n return value;\n }\n return \"update\";\n}\n\nfunction detailPathForRouteId(detailPath: string, routeId: string) {\n return detailPath.replace(/\\[[^\\]]+\\]/g, routeId);\n}\n\nfunction publicApiDetailPathForRouteId(publicApiPath: string, routeId: string) {\n return `${publicApiPath.replace(/\\/+$/, \"\")}/${routeId.replace(/^\\/+/, \"\")}`;\n}\n\nfunction bearerToken(request: Request) {\n const authorization = request.headers.get(\"authorization\") ?? \"\";\n const match = authorization.match(/^Bearer\\s+(.+)$/i);\n return match?.[1]?.trim() ?? \"\";\n}\n\nfunction timingSafeEqualString(a: string, b: string) {\n const encoder = new TextEncoder();\n const left = encoder.encode(a);\n const right = encoder.encode(b);\n if (left.byteLength !== right.byteLength) return false;\n\n let diff = 0;\n for (let index = 0; index < left.byteLength; index += 1) {\n diff |= (left[index] ?? 0) ^ (right[index] ?? 0);\n }\n return diff === 0;\n}\n\nfunction shouldLocalizeMoviePaths(modelId: string) {\n return modelId === \"movies\" || modelId === \"movie-translations\";\n}\n\nexport function buildContentRevalidationPaths(input: {\n model: Pick<ContentModelDefinition, \"id\" | \"routes\">;\n routeId?: string;\n previousRouteId?: string;\n locale?: string;\n includeApi?: boolean;\n localizedMovieDetailPaths?: readonly string[];\n}) {\n const pagePaths = [input.model.routes.listPath];\n const routePaths: string[] = [];\n\n if (input.routeId) {\n pagePaths.push(\n detailPathForRouteId(input.model.routes.detailPath, input.routeId)\n );\n }\n if (input.previousRouteId) {\n pagePaths.push(\n detailPathForRouteId(\n input.model.routes.detailPath,\n input.previousRouteId\n )\n );\n }\n if (input.localizedMovieDetailPaths?.length) {\n pagePaths.push(...input.localizedMovieDetailPaths);\n }\n if (input.includeApi !== false && input.model.routes.publicApiPath) {\n routePaths.push(input.model.routes.publicApiPath);\n if (input.routeId) {\n routePaths.push(\n publicApiDetailPathForRouteId(\n input.model.routes.publicApiPath,\n input.routeId\n )\n );\n }\n if (input.previousRouteId) {\n routePaths.push(\n publicApiDetailPathForRouteId(\n input.model.routes.publicApiPath,\n input.previousRouteId\n )\n );\n }\n }\n\n const localizedPagePaths = shouldLocalizeMoviePaths(input.model.id)\n ? expandLocalizedMoviePaths(pagePaths, input.locale)\n : pagePaths;\n\n return {\n pagePaths: Array.from(new Set(localizedPagePaths)),\n routePaths: Array.from(new Set(routePaths)),\n all: Array.from(new Set([...localizedPagePaths, ...routePaths])),\n };\n}\n\nexport function authorizeContentRevalidate(\n request: Request,\n token?: string | null\n) {\n const expected = String(token ?? \"\").trim();\n if (!expected) return false;\n const actual = bearerToken(request);\n return Boolean(actual && timingSafeEqualString(actual, expected));\n}\n\nexport async function readContentRevalidateRequest(\n request: Request\n): Promise<ContentRevalidateRequest | null> {\n const body = asObject(await request.json().catch(() => null));\n if (!body) return null;\n\n const modelId = readString(body, \"modelId\");\n const pageId = readString(body, \"pageId\");\n const routeId = readString(body, \"routeId\");\n const previousRouteId = readString(body, \"previousRouteId\");\n const locale = readString(body, \"locale\");\n\n return {\n modelId,\n pageId: pageId || undefined,\n routeId: routeId || undefined,\n previousRouteId: previousRouteId || undefined,\n locale: locale || undefined,\n kind: readKind(body),\n includeApi: body.includeApi !== false,\n };\n}\n\nexport function readContentRevalidateRequestFromUrl(\n url: URL\n): ContentRevalidateRequest | null {\n const modelId = url.searchParams.get(\"modelId\")?.trim() ?? \"\";\n if (!modelId) return null;\n\n const kind = url.searchParams.get(\"kind\")?.trim() ?? \"\";\n const includeApi = url.searchParams.get(\"includeApi\");\n return {\n modelId,\n pageId: url.searchParams.get(\"pageId\")?.trim() || undefined,\n routeId: url.searchParams.get(\"routeId\")?.trim() || undefined,\n previousRouteId:\n url.searchParams.get(\"previousRouteId\")?.trim() || undefined,\n locale: url.searchParams.get(\"locale\")?.trim() || undefined,\n kind:\n kind === \"publish\" || kind === \"delete\" || kind === \"update\"\n ? kind\n : \"update\",\n includeApi: includeApi !== \"false\",\n };\n}\n\nexport function previewContentModelInvalidation(input: ContentRevalidateRequest) {\n const model = getRegisteredSource(input.modelId);\n if (!model) {\n throw new Error(`Unknown content model: ${input.modelId}`);\n }\n\n return buildContentRevalidationPaths({\n model,\n routeId: input.routeId,\n previousRouteId: input.previousRouteId,\n includeApi: input.includeApi,\n });\n}\n","// packages/nextion/src/content/search.ts\n//\n// Generic text-search helpers for content sources.\n\nimport type {\n NotionMovieListItem,\n NotionPostListItem,\n} from \"../notion/types\";\n\nexport function normalizeSearchQuery(query: string | null | undefined) {\n return String(query ?? \"\")\n .normalize(\"NFKC\")\n .trim()\n .replace(/\\s+/g, \" \")\n .toLowerCase();\n}\n\nfunction searchTerms(query: string | null | undefined) {\n const normalized = normalizeSearchQuery(query);\n return normalized ? normalized.split(\" \") : [];\n}\n\nfunction searchableText(values: readonly unknown[]) {\n return values\n .flatMap((value) => (Array.isArray(value) ? value : [value]))\n .filter((value): value is string | number | boolean =>\n [\"string\", \"number\", \"boolean\"].includes(typeof value)\n )\n .map((value) => String(value))\n .join(\" \")\n .normalize(\"NFKC\")\n .toLowerCase();\n}\n\nexport function matchesSearchQuery(\n values: readonly unknown[],\n query: string | null | undefined\n) {\n const terms = searchTerms(query);\n if (terms.length === 0) return true;\n\n const haystack = searchableText(values);\n return terms.every((term) => haystack.includes(term));\n}\n\nexport function filterPostsBySearch<TPost extends NotionPostListItem>(\n posts: readonly TPost[],\n query: string | null | undefined\n) {\n return posts.filter((post) =>\n matchesSearchQuery(\n [\n post.title,\n post.description,\n post.author,\n post.tags,\n post.slug,\n post.date,\n ],\n query\n )\n );\n}\n\nexport function filterMoviesBySearch<TMovie extends NotionMovieListItem>(\n movies: readonly TMovie[],\n query: string | null | undefined\n) {\n return movies.filter((movie) =>\n matchesSearchQuery(\n [\n movie.title,\n movie.summary,\n movie.director,\n movie.actors,\n movie.genres,\n movie.releaseDate,\n movie.routeId,\n ],\n query\n )\n );\n}\n","// packages/nextion/src/content/search-index.ts\n//\n// Generic D1-backed content search index helpers.\n\nimport type { SqlDatabaseAdapter } from \"../platform/runtime\";\nimport { matchesSearchQuery, normalizeSearchQuery } from \"./search\";\n\nexport type SearchIndexedItem = {\n pageId?: string;\n slug?: string;\n routeId?: string;\n title: string;\n description?: string;\n date?: string;\n author?: string;\n tags?: readonly string[];\n releaseDate?: string;\n director?: string;\n actors?: string;\n summary?: string;\n genres?: readonly string[];\n};\n\nexport type SearchIndexDocument = {\n modelId: string;\n pageId: string;\n routeId: string;\n title: string;\n summary: string;\n bodyText: string;\n facets: readonly string[];\n sourceUpdatedAt?: string | null;\n};\n\ntype SearchIndexRow = {\n route_id: string;\n};\n\ntype MaybePromise<T> = T | Promise<T>;\n\nfunction indexValuesForItem(item: SearchIndexedItem) {\n return [\n item.title,\n item.description,\n item.author,\n item.tags,\n item.slug,\n item.date,\n item.summary,\n item.director,\n item.actors,\n item.genres,\n item.routeId,\n item.releaseDate,\n ];\n}\n\nfunction routeIdForItem(item: SearchIndexedItem) {\n return item.routeId ?? item.slug ?? \"\";\n}\n\nfunction routeOrder<T extends SearchIndexedItem>(items: readonly T[]) {\n const order = new Map<string, number>();\n items.forEach((item, index) => {\n const routeId = routeIdForItem(item);\n if (routeId) order.set(routeId, index);\n });\n return order;\n}\n\nfunction uniqueValues(values: readonly string[]) {\n return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)));\n}\n\nexport async function upsertSearchIndexDocument(\n db: SqlDatabaseAdapter,\n document: SearchIndexDocument\n) {\n const normalizedText = [\n document.title,\n document.summary,\n document.bodyText,\n ...document.facets,\n ]\n .join(\" \")\n .normalize(\"NFKC\")\n .replace(/\\s+/g, \" \")\n .toLowerCase();\n\n await db\n .prepare(\n `INSERT INTO content_search_index (\n model_id,\n page_id,\n route_id,\n title,\n summary,\n body_text,\n facets,\n normalized_text,\n source_updated_at,\n indexed_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))\n ON CONFLICT(model_id, route_id) DO UPDATE SET\n page_id = excluded.page_id,\n title = excluded.title,\n summary = excluded.summary,\n body_text = excluded.body_text,\n facets = excluded.facets,\n normalized_text = excluded.normalized_text,\n source_updated_at = excluded.source_updated_at,\n indexed_at = excluded.indexed_at`\n )\n .bind(\n document.modelId,\n document.pageId,\n document.routeId,\n document.title,\n document.summary,\n document.bodyText,\n JSON.stringify(uniqueValues([...document.facets])),\n normalizedText,\n document.sourceUpdatedAt ?? null\n )\n .run();\n}\n\nexport async function deleteSearchIndexDocument(\n db: SqlDatabaseAdapter,\n input: { modelId: string; routeId: string }\n) {\n await db\n .prepare(\n \"DELETE FROM content_search_index WHERE model_id = ? AND route_id = ?\"\n )\n .bind(input.modelId, input.routeId)\n .run();\n}\n\nexport async function deleteSearchIndexForModel(\n db: SqlDatabaseAdapter,\n input: { modelId: string }\n) {\n await db\n .prepare(\"DELETE FROM content_search_index WHERE model_id = ?\")\n .bind(input.modelId)\n .run();\n}\n\nexport async function getMissingSearchIndexRouteIds(\n db: SqlDatabaseAdapter,\n input: { modelId: string; routeIds: readonly string[] }\n) {\n const routeIds = uniqueValues([...input.routeIds]);\n if (routeIds.length === 0) return [];\n\n const placeholders = routeIds.map(() => \"?\").join(\", \");\n const result = await db\n .prepare(\n `SELECT route_id\n FROM content_search_index\n WHERE model_id = ? AND route_id IN (${placeholders})`\n )\n .bind(input.modelId, ...routeIds)\n .all<SearchIndexRow>();\n\n const present = new Set((result.results ?? []).map((row) => row.route_id));\n return routeIds.filter((routeId) => !present.has(routeId));\n}\n\nexport async function querySearchIndexRouteIds(\n db: SqlDatabaseAdapter,\n input: { modelId: string; query: string; limit?: number }\n) {\n const query = normalizeSearchQuery(input.query);\n if (!query) return [];\n\n const terms = query\n .split(\" \")\n .map((term) => `%${term}%`);\n const clauses = terms\n .map(\n () =>\n \"(normalized_text LIKE ? OR title LIKE ? OR summary LIKE ? OR facets LIKE ?)\"\n )\n .join(\" AND \");\n const values = terms.flatMap((term) => [term, term, term, term]);\n\n const result = await db\n .prepare(\n `SELECT route_id\n FROM content_search_index\n WHERE model_id = ? AND ${clauses}\n ORDER BY indexed_at DESC\n LIMIT ?`\n )\n .bind(input.modelId, ...values, input.limit ?? 200)\n .all<SearchIndexRow>();\n\n return (result.results ?? [])\n .map((row) => row.route_id)\n .filter((routeId): routeId is string => typeof routeId === \"string\");\n}\n\nexport async function filterItemsBySearchIndex<T extends SearchIndexedItem>(\n items: readonly T[],\n query: string | null | undefined,\n input: {\n modelId: string;\n filterFallback: (items: readonly T[], query: string | null | undefined) => T[];\n getDatabase?: () => MaybePromise<SqlDatabaseAdapter | null>;\n }\n) {\n const normalized = normalizeSearchQuery(query);\n if (!normalized) return [...items];\n if (!input.getDatabase) return input.filterFallback(items, normalized);\n\n try {\n const db = await input.getDatabase();\n if (!db) return input.filterFallback(items, normalized);\n const routeIds = await querySearchIndexRouteIds(db, {\n modelId: input.modelId,\n query: normalized,\n limit: Math.max(items.length, 200),\n });\n if (routeIds.length === 0) return input.filterFallback(items, normalized);\n\n const order = routeOrder(items);\n const matched = new Set(routeIds);\n return items\n .filter((item) => matched.has(routeIdForItem(item)))\n .sort(\n (a, b) =>\n (order.get(routeIdForItem(a)) ?? 0) -\n (order.get(routeIdForItem(b)) ?? 0)\n );\n } catch (error) {\n console.warn(\n JSON.stringify({\n tag: \"content_search_index_error\",\n modelId: input.modelId,\n message: error instanceof Error ? error.message : String(error),\n })\n );\n return input.filterFallback(items, normalized);\n }\n}\n\nexport function matchesIndexedItem(\n item: SearchIndexedItem,\n query: string | null | undefined\n) {\n return matchesSearchQuery(indexValuesForItem(item), query);\n}\n","// packages/nextion/src/content/prewarm.ts\n//\n// Generic content search index prewarming. The function takes a list\n// of (modelId, runner) targets and runs them. The starter wires the\n// project-specific runners in its `lib/content/prewarm.ts` shim.\n\nexport type ContentPrewarmModelResult = {\n modelId: string;\n ok: boolean;\n total: number;\n indexed: number;\n skipped: boolean;\n error?: string;\n};\n\nexport type ContentPrewarmResult = {\n ok: boolean;\n startedAt: string;\n finishedAt: string;\n durationMs: number;\n models: ContentPrewarmModelResult[];\n};\n\nexport type PrewarmTarget = {\n modelId: string;\n run: () => Promise<{ total: number; indexed: number; skipped: boolean }>;\n};\n\nfunction logContentPrewarm(fields: Record<string, unknown>) {\n try {\n console.log(JSON.stringify({ tag: \"content_prewarm\", ...fields }));\n } catch {\n // Ignore logging serialization errors.\n }\n}\n\nexport async function prewarmPublicContentSearchIndex(\n targets: readonly PrewarmTarget[],\n options?: { models?: readonly string[] }\n): Promise<ContentPrewarmResult> {\n const startedAtDate = new Date();\n const startedAt = startedAtDate.toISOString();\n const t0 = performance.now();\n const requestedModels = new Set(options?.models?.filter(Boolean));\n const selected =\n requestedModels.size > 0\n ? targets.filter((target) => requestedModels.has(target.modelId))\n : targets;\n\n const models: ContentPrewarmModelResult[] = [];\n for (const target of selected) {\n try {\n const result = await target.run();\n models.push({\n modelId: target.modelId,\n ok: true,\n total: result.total,\n indexed: result.indexed,\n skipped: result.skipped,\n });\n } catch (error) {\n models.push({\n modelId: target.modelId,\n ok: false,\n total: 0,\n indexed: 0,\n skipped: true,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n const finishedAt = new Date().toISOString();\n const output: ContentPrewarmResult = {\n ok: models.every((model) => model.ok),\n startedAt,\n finishedAt,\n durationMs: Math.round((performance.now() - t0) * 100) / 100,\n models,\n };\n\n logContentPrewarm({\n ok: output.ok,\n started_at: output.startedAt,\n finished_at: output.finishedAt,\n duration_ms: output.durationMs,\n models: output.models,\n });\n\n return output;\n}\n","// packages/nextion/src/content/admin-summary.ts\n//\n// Generic admin summary helpers for content sources.\n\nimport type {\n ContentModelDefinition,\n NotionFieldMap,\n} from \"./models\";\nimport { getRegisteredSources } from \"./models\";\n\nexport type ContentModelAdminSummary = {\n id: string;\n name: string;\n kind: ContentModelDefinition[\"kind\"];\n visibility: \"public\" | \"admin\" | \"public+admin\" | \"private\";\n listPath: string;\n detailPath: string;\n publicApiPath?: string;\n dataSourceEnv: string;\n hasDefaultDataSource: boolean;\n fieldCount: number;\n capabilities: {\n richBlocks: boolean;\n coverImages: boolean;\n gatedAssets: boolean;\n };\n};\n\nfunction visibilityFor(model: ContentModelDefinition<NotionFieldMap>) {\n if (model.visibility.public && model.visibility.admin) return \"public+admin\";\n if (model.visibility.public) return \"public\";\n if (model.visibility.admin) return \"admin\";\n return \"private\";\n}\n\nexport function summarizeContentModelForAdmin(\n model: ContentModelDefinition<NotionFieldMap>\n): ContentModelAdminSummary {\n return {\n id: model.id,\n name: model.ui.name,\n kind: model.kind,\n visibility: visibilityFor(model),\n listPath: model.routes.listPath,\n detailPath: model.routes.detailPath,\n publicApiPath: model.routes.publicApiPath,\n dataSourceEnv: model.source.dataSourceEnv,\n hasDefaultDataSource: Boolean(model.source.defaultDataSourceId),\n fieldCount: Object.keys(model.source.fields).length,\n capabilities: model.capabilities,\n };\n}\n\nexport function getContentModelAdminSummaries(\n models: readonly ContentModelDefinition<NotionFieldMap>[] = getRegisteredSources()\n) {\n return models.map(summarizeContentModelForAdmin);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NotionFieldMap, NotionSort } from '../notion/types.js';
|
|
2
|
+
export { NotionSortDirection } from '../notion/types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Canonical content-source shape. The shape mirrors the starter's
|
|
6
|
+
* prior `ContentModelDefinition` exactly so that registered sources
|
|
7
|
+
* remain type-compatible across the package boundary.
|
|
8
|
+
*/
|
|
9
|
+
type ContentModelDefinition<TFields extends NotionFieldMap = NotionFieldMap> = {
|
|
10
|
+
id: string;
|
|
11
|
+
kind: "article" | "catalog" | "directory";
|
|
12
|
+
visibility: {
|
|
13
|
+
public: boolean;
|
|
14
|
+
admin: boolean;
|
|
15
|
+
};
|
|
16
|
+
source: {
|
|
17
|
+
type: "notion";
|
|
18
|
+
tokenEnv: "NOTION_TOKEN";
|
|
19
|
+
dataSourceEnv: string;
|
|
20
|
+
defaultDataSourceId?: string;
|
|
21
|
+
fields: TFields;
|
|
22
|
+
query: {
|
|
23
|
+
pageSize: number;
|
|
24
|
+
sorts?: readonly NotionSort[];
|
|
25
|
+
filterProperties?: readonly string[];
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
routes: {
|
|
29
|
+
listPath: string;
|
|
30
|
+
detailPath: string;
|
|
31
|
+
detailParam: string;
|
|
32
|
+
publicApiPath?: string;
|
|
33
|
+
};
|
|
34
|
+
ui: {
|
|
35
|
+
name: string;
|
|
36
|
+
pluralName: string;
|
|
37
|
+
navLabel: string;
|
|
38
|
+
listTitle: string;
|
|
39
|
+
listDescription: string;
|
|
40
|
+
emptyState: string;
|
|
41
|
+
};
|
|
42
|
+
capabilities: {
|
|
43
|
+
richBlocks: boolean;
|
|
44
|
+
coverImages: boolean;
|
|
45
|
+
gatedAssets: boolean;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Public alias for `ContentModelDefinition`. External consumers import
|
|
50
|
+
* this name from `@notionx/core/content`; the internal
|
|
51
|
+
* `ContentModelDefinition` name remains available for the starter's
|
|
52
|
+
* `model.ts`.
|
|
53
|
+
*/
|
|
54
|
+
type ContentSource<TFields extends NotionFieldMap = NotionFieldMap> = ContentModelDefinition<TFields>;
|
|
55
|
+
/**
|
|
56
|
+
* Register a content source. Returns the value unchanged. Re-registering
|
|
57
|
+
* the same `id` replaces the prior value (idempotent on the id, useful
|
|
58
|
+
* for HMR + tests).
|
|
59
|
+
*/
|
|
60
|
+
declare function defineContentSource<const TFields extends NotionFieldMap>(model: ContentModelDefinition<TFields>): ContentModelDefinition<TFields>;
|
|
61
|
+
declare function getRegisteredSources(): readonly ContentSource[];
|
|
62
|
+
declare function getRegisteredSource(id: string): ContentSource | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Test-only escape hatch: empties the registry so vitest cases do not
|
|
65
|
+
* leak state between files. Not for production use.
|
|
66
|
+
*/
|
|
67
|
+
declare function clearRegistryForTests(): void;
|
|
68
|
+
|
|
69
|
+
export { type ContentModelDefinition, type ContentSource, NotionFieldMap, NotionSort, clearRegistryForTests, defineContentSource, getRegisteredSource, getRegisteredSources };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/content/models.ts
|
|
2
|
+
var registry = [];
|
|
3
|
+
function defineContentSource(model) {
|
|
4
|
+
const existing = registry.findIndex((s) => s.id === model.id);
|
|
5
|
+
if (existing >= 0) registry[existing] = model;
|
|
6
|
+
else registry.push(model);
|
|
7
|
+
return model;
|
|
8
|
+
}
|
|
9
|
+
function getRegisteredSources() {
|
|
10
|
+
return registry;
|
|
11
|
+
}
|
|
12
|
+
function getRegisteredSource(id) {
|
|
13
|
+
return registry.find((s) => s.id === id);
|
|
14
|
+
}
|
|
15
|
+
function clearRegistryForTests() {
|
|
16
|
+
registry.length = 0;
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
clearRegistryForTests,
|
|
20
|
+
defineContentSource,
|
|
21
|
+
getRegisteredSource,
|
|
22
|
+
getRegisteredSources
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/content/models.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"],"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;AAEO,SAAS,uBAAiD;AAC/D,SAAO;AACT;AAEO,SAAS,oBAAoB,IAAuC;AACzE,SAAO,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACzC;AAMO,SAAS,wBAA8B;AAC5C,WAAS,SAAS;AACpB;","names":[]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type ContentPrewarmModelResult = {
|
|
2
|
+
modelId: string;
|
|
3
|
+
ok: boolean;
|
|
4
|
+
total: number;
|
|
5
|
+
indexed: number;
|
|
6
|
+
skipped: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
};
|
|
9
|
+
type ContentPrewarmResult = {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
startedAt: string;
|
|
12
|
+
finishedAt: string;
|
|
13
|
+
durationMs: number;
|
|
14
|
+
models: ContentPrewarmModelResult[];
|
|
15
|
+
};
|
|
16
|
+
type PrewarmTarget = {
|
|
17
|
+
modelId: string;
|
|
18
|
+
run: () => Promise<{
|
|
19
|
+
total: number;
|
|
20
|
+
indexed: number;
|
|
21
|
+
skipped: boolean;
|
|
22
|
+
}>;
|
|
23
|
+
};
|
|
24
|
+
declare function prewarmPublicContentSearchIndex(targets: readonly PrewarmTarget[], options?: {
|
|
25
|
+
models?: readonly string[];
|
|
26
|
+
}): Promise<ContentPrewarmResult>;
|
|
27
|
+
|
|
28
|
+
export { type ContentPrewarmModelResult, type ContentPrewarmResult, type PrewarmTarget, prewarmPublicContentSearchIndex };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/content/prewarm.ts
|
|
2
|
+
function logContentPrewarm(fields) {
|
|
3
|
+
try {
|
|
4
|
+
console.log(JSON.stringify({ tag: "content_prewarm", ...fields }));
|
|
5
|
+
} catch {
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
async function prewarmPublicContentSearchIndex(targets, options) {
|
|
9
|
+
const startedAtDate = /* @__PURE__ */ new Date();
|
|
10
|
+
const startedAt = startedAtDate.toISOString();
|
|
11
|
+
const t0 = performance.now();
|
|
12
|
+
const requestedModels = new Set(options?.models?.filter(Boolean));
|
|
13
|
+
const selected = requestedModels.size > 0 ? targets.filter((target) => requestedModels.has(target.modelId)) : targets;
|
|
14
|
+
const models = [];
|
|
15
|
+
for (const target of selected) {
|
|
16
|
+
try {
|
|
17
|
+
const result = await target.run();
|
|
18
|
+
models.push({
|
|
19
|
+
modelId: target.modelId,
|
|
20
|
+
ok: true,
|
|
21
|
+
total: result.total,
|
|
22
|
+
indexed: result.indexed,
|
|
23
|
+
skipped: result.skipped
|
|
24
|
+
});
|
|
25
|
+
} catch (error) {
|
|
26
|
+
models.push({
|
|
27
|
+
modelId: target.modelId,
|
|
28
|
+
ok: false,
|
|
29
|
+
total: 0,
|
|
30
|
+
indexed: 0,
|
|
31
|
+
skipped: true,
|
|
32
|
+
error: error instanceof Error ? error.message : String(error)
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
37
|
+
const output = {
|
|
38
|
+
ok: models.every((model) => model.ok),
|
|
39
|
+
startedAt,
|
|
40
|
+
finishedAt,
|
|
41
|
+
durationMs: Math.round((performance.now() - t0) * 100) / 100,
|
|
42
|
+
models
|
|
43
|
+
};
|
|
44
|
+
logContentPrewarm({
|
|
45
|
+
ok: output.ok,
|
|
46
|
+
started_at: output.startedAt,
|
|
47
|
+
finished_at: output.finishedAt,
|
|
48
|
+
duration_ms: output.durationMs,
|
|
49
|
+
models: output.models
|
|
50
|
+
});
|
|
51
|
+
return output;
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
prewarmPublicContentSearchIndex
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=prewarm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/content/prewarm.ts"],"sourcesContent":["// packages/nextion/src/content/prewarm.ts\n//\n// Generic content search index prewarming. The function takes a list\n// of (modelId, runner) targets and runs them. The starter wires the\n// project-specific runners in its `lib/content/prewarm.ts` shim.\n\nexport type ContentPrewarmModelResult = {\n modelId: string;\n ok: boolean;\n total: number;\n indexed: number;\n skipped: boolean;\n error?: string;\n};\n\nexport type ContentPrewarmResult = {\n ok: boolean;\n startedAt: string;\n finishedAt: string;\n durationMs: number;\n models: ContentPrewarmModelResult[];\n};\n\nexport type PrewarmTarget = {\n modelId: string;\n run: () => Promise<{ total: number; indexed: number; skipped: boolean }>;\n};\n\nfunction logContentPrewarm(fields: Record<string, unknown>) {\n try {\n console.log(JSON.stringify({ tag: \"content_prewarm\", ...fields }));\n } catch {\n // Ignore logging serialization errors.\n }\n}\n\nexport async function prewarmPublicContentSearchIndex(\n targets: readonly PrewarmTarget[],\n options?: { models?: readonly string[] }\n): Promise<ContentPrewarmResult> {\n const startedAtDate = new Date();\n const startedAt = startedAtDate.toISOString();\n const t0 = performance.now();\n const requestedModels = new Set(options?.models?.filter(Boolean));\n const selected =\n requestedModels.size > 0\n ? targets.filter((target) => requestedModels.has(target.modelId))\n : targets;\n\n const models: ContentPrewarmModelResult[] = [];\n for (const target of selected) {\n try {\n const result = await target.run();\n models.push({\n modelId: target.modelId,\n ok: true,\n total: result.total,\n indexed: result.indexed,\n skipped: result.skipped,\n });\n } catch (error) {\n models.push({\n modelId: target.modelId,\n ok: false,\n total: 0,\n indexed: 0,\n skipped: true,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n const finishedAt = new Date().toISOString();\n const output: ContentPrewarmResult = {\n ok: models.every((model) => model.ok),\n startedAt,\n finishedAt,\n durationMs: Math.round((performance.now() - t0) * 100) / 100,\n models,\n };\n\n logContentPrewarm({\n ok: output.ok,\n started_at: output.startedAt,\n finished_at: output.finishedAt,\n duration_ms: output.durationMs,\n models: output.models,\n });\n\n return output;\n}\n"],"mappings":";AA4BA,SAAS,kBAAkB,QAAiC;AAC1D,MAAI;AACF,YAAQ,IAAI,KAAK,UAAU,EAAE,KAAK,mBAAmB,GAAG,OAAO,CAAC,CAAC;AAAA,EACnE,QAAQ;AAAA,EAER;AACF;AAEA,eAAsB,gCACpB,SACA,SAC+B;AAC/B,QAAM,gBAAgB,oBAAI,KAAK;AAC/B,QAAM,YAAY,cAAc,YAAY;AAC5C,QAAM,KAAK,YAAY,IAAI;AAC3B,QAAM,kBAAkB,IAAI,IAAI,SAAS,QAAQ,OAAO,OAAO,CAAC;AAChE,QAAM,WACJ,gBAAgB,OAAO,IACnB,QAAQ,OAAO,CAAC,WAAW,gBAAgB,IAAI,OAAO,OAAO,CAAC,IAC9D;AAEN,QAAM,SAAsC,CAAC;AAC7C,aAAW,UAAU,UAAU;AAC7B,QAAI;AACF,YAAM,SAAS,MAAM,OAAO,IAAI;AAChC,aAAO,KAAK;AAAA,QACV,SAAS,OAAO;AAAA,QAChB,IAAI;AAAA,QACJ,OAAO,OAAO;AAAA,QACd,SAAS,OAAO;AAAA,QAChB,SAAS,OAAO;AAAA,MAClB,CAAC;AAAA,IACH,SAAS,OAAO;AACd,aAAO,KAAK;AAAA,QACV,SAAS,OAAO;AAAA,QAChB,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,SAAS;AAAA,QACT,SAAS;AAAA,QACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC9D,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAC1C,QAAM,SAA+B;AAAA,IACnC,IAAI,OAAO,MAAM,CAAC,UAAU,MAAM,EAAE;AAAA,IACpC;AAAA,IACA;AAAA,IACA,YAAY,KAAK,OAAO,YAAY,IAAI,IAAI,MAAM,GAAG,IAAI;AAAA,IACzD;AAAA,EACF;AAEA,oBAAkB;AAAA,IAChB,IAAI,OAAO;AAAA,IACX,YAAY,OAAO;AAAA,IACnB,aAAa,OAAO;AAAA,IACpB,aAAa,OAAO;AAAA,IACpB,QAAQ,OAAO;AAAA,EACjB,CAAC;AAED,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ContentModelDefinition } from './models.js';
|
|
2
|
+
import '../notion/types.js';
|
|
3
|
+
|
|
4
|
+
type RevalidatePathFn = (path: string, type?: "page" | "layout") => void | Promise<void>;
|
|
5
|
+
|
|
6
|
+
type InvalidationKind = "publish" | "update" | "delete";
|
|
7
|
+
type ContentRevalidateRequest = {
|
|
8
|
+
modelId: string;
|
|
9
|
+
pageId?: string;
|
|
10
|
+
routeId?: string;
|
|
11
|
+
previousRouteId?: string;
|
|
12
|
+
locale?: string;
|
|
13
|
+
kind?: InvalidationKind;
|
|
14
|
+
includeApi?: boolean;
|
|
15
|
+
};
|
|
16
|
+
declare function buildContentRevalidationPaths(input: {
|
|
17
|
+
model: Pick<ContentModelDefinition, "id" | "routes">;
|
|
18
|
+
routeId?: string;
|
|
19
|
+
previousRouteId?: string;
|
|
20
|
+
locale?: string;
|
|
21
|
+
includeApi?: boolean;
|
|
22
|
+
localizedMovieDetailPaths?: readonly string[];
|
|
23
|
+
}): {
|
|
24
|
+
pagePaths: string[];
|
|
25
|
+
routePaths: string[];
|
|
26
|
+
all: string[];
|
|
27
|
+
};
|
|
28
|
+
declare function authorizeContentRevalidate(request: Request, token?: string | null): boolean;
|
|
29
|
+
declare function readContentRevalidateRequest(request: Request): Promise<ContentRevalidateRequest | null>;
|
|
30
|
+
declare function readContentRevalidateRequestFromUrl(url: URL): ContentRevalidateRequest | null;
|
|
31
|
+
declare function previewContentModelInvalidation(input: ContentRevalidateRequest): {
|
|
32
|
+
pagePaths: string[];
|
|
33
|
+
routePaths: string[];
|
|
34
|
+
all: string[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { type ContentRevalidateRequest, type InvalidationKind, type RevalidatePathFn, authorizeContentRevalidate, buildContentRevalidationPaths, previewContentModelInvalidation, readContentRevalidateRequest, readContentRevalidateRequestFromUrl };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// src/i18n/config.ts
|
|
2
|
+
var supportedLocales = ["zh-CN", "en-US"];
|
|
3
|
+
function isAppLocale(value) {
|
|
4
|
+
return supportedLocales.includes(value);
|
|
5
|
+
}
|
|
6
|
+
function expandLocalizedMoviePaths(paths, locale) {
|
|
7
|
+
const locales = locale && isAppLocale(locale) ? [locale] : [...supportedLocales];
|
|
8
|
+
const expanded = [];
|
|
9
|
+
for (const path of paths) {
|
|
10
|
+
if (path === "/movies" || path.startsWith("/movies/")) {
|
|
11
|
+
for (const currentLocale of locales) {
|
|
12
|
+
expanded.push(`/${currentLocale}${path}`);
|
|
13
|
+
}
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
expanded.push(path);
|
|
17
|
+
}
|
|
18
|
+
return Array.from(new Set(expanded));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/content/models.ts
|
|
22
|
+
var registry = [];
|
|
23
|
+
function getRegisteredSource(id) {
|
|
24
|
+
return registry.find((s) => s.id === id);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/content/revalidate.ts
|
|
28
|
+
function asObject(input) {
|
|
29
|
+
return input && typeof input === "object" && !Array.isArray(input) ? input : null;
|
|
30
|
+
}
|
|
31
|
+
function readString(input, key) {
|
|
32
|
+
const value = input[key];
|
|
33
|
+
return typeof value === "string" ? value.trim() : "";
|
|
34
|
+
}
|
|
35
|
+
function readKind(input) {
|
|
36
|
+
const value = readString(input, "kind");
|
|
37
|
+
if (value === "publish" || value === "delete" || value === "update") {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
return "update";
|
|
41
|
+
}
|
|
42
|
+
function detailPathForRouteId(detailPath, routeId) {
|
|
43
|
+
return detailPath.replace(/\[[^\]]+\]/g, routeId);
|
|
44
|
+
}
|
|
45
|
+
function publicApiDetailPathForRouteId(publicApiPath, routeId) {
|
|
46
|
+
return `${publicApiPath.replace(/\/+$/, "")}/${routeId.replace(/^\/+/, "")}`;
|
|
47
|
+
}
|
|
48
|
+
function bearerToken(request) {
|
|
49
|
+
const authorization = request.headers.get("authorization") ?? "";
|
|
50
|
+
const match = authorization.match(/^Bearer\s+(.+)$/i);
|
|
51
|
+
return match?.[1]?.trim() ?? "";
|
|
52
|
+
}
|
|
53
|
+
function timingSafeEqualString(a, b) {
|
|
54
|
+
const encoder = new TextEncoder();
|
|
55
|
+
const left = encoder.encode(a);
|
|
56
|
+
const right = encoder.encode(b);
|
|
57
|
+
if (left.byteLength !== right.byteLength) return false;
|
|
58
|
+
let diff = 0;
|
|
59
|
+
for (let index = 0; index < left.byteLength; index += 1) {
|
|
60
|
+
diff |= (left[index] ?? 0) ^ (right[index] ?? 0);
|
|
61
|
+
}
|
|
62
|
+
return diff === 0;
|
|
63
|
+
}
|
|
64
|
+
function shouldLocalizeMoviePaths(modelId) {
|
|
65
|
+
return modelId === "movies" || modelId === "movie-translations";
|
|
66
|
+
}
|
|
67
|
+
function buildContentRevalidationPaths(input) {
|
|
68
|
+
const pagePaths = [input.model.routes.listPath];
|
|
69
|
+
const routePaths = [];
|
|
70
|
+
if (input.routeId) {
|
|
71
|
+
pagePaths.push(
|
|
72
|
+
detailPathForRouteId(input.model.routes.detailPath, input.routeId)
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (input.previousRouteId) {
|
|
76
|
+
pagePaths.push(
|
|
77
|
+
detailPathForRouteId(
|
|
78
|
+
input.model.routes.detailPath,
|
|
79
|
+
input.previousRouteId
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (input.localizedMovieDetailPaths?.length) {
|
|
84
|
+
pagePaths.push(...input.localizedMovieDetailPaths);
|
|
85
|
+
}
|
|
86
|
+
if (input.includeApi !== false && input.model.routes.publicApiPath) {
|
|
87
|
+
routePaths.push(input.model.routes.publicApiPath);
|
|
88
|
+
if (input.routeId) {
|
|
89
|
+
routePaths.push(
|
|
90
|
+
publicApiDetailPathForRouteId(
|
|
91
|
+
input.model.routes.publicApiPath,
|
|
92
|
+
input.routeId
|
|
93
|
+
)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (input.previousRouteId) {
|
|
97
|
+
routePaths.push(
|
|
98
|
+
publicApiDetailPathForRouteId(
|
|
99
|
+
input.model.routes.publicApiPath,
|
|
100
|
+
input.previousRouteId
|
|
101
|
+
)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const localizedPagePaths = shouldLocalizeMoviePaths(input.model.id) ? expandLocalizedMoviePaths(pagePaths, input.locale) : pagePaths;
|
|
106
|
+
return {
|
|
107
|
+
pagePaths: Array.from(new Set(localizedPagePaths)),
|
|
108
|
+
routePaths: Array.from(new Set(routePaths)),
|
|
109
|
+
all: Array.from(/* @__PURE__ */ new Set([...localizedPagePaths, ...routePaths]))
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function authorizeContentRevalidate(request, token) {
|
|
113
|
+
const expected = String(token ?? "").trim();
|
|
114
|
+
if (!expected) return false;
|
|
115
|
+
const actual = bearerToken(request);
|
|
116
|
+
return Boolean(actual && timingSafeEqualString(actual, expected));
|
|
117
|
+
}
|
|
118
|
+
async function readContentRevalidateRequest(request) {
|
|
119
|
+
const body = asObject(await request.json().catch(() => null));
|
|
120
|
+
if (!body) return null;
|
|
121
|
+
const modelId = readString(body, "modelId");
|
|
122
|
+
const pageId = readString(body, "pageId");
|
|
123
|
+
const routeId = readString(body, "routeId");
|
|
124
|
+
const previousRouteId = readString(body, "previousRouteId");
|
|
125
|
+
const locale = readString(body, "locale");
|
|
126
|
+
return {
|
|
127
|
+
modelId,
|
|
128
|
+
pageId: pageId || void 0,
|
|
129
|
+
routeId: routeId || void 0,
|
|
130
|
+
previousRouteId: previousRouteId || void 0,
|
|
131
|
+
locale: locale || void 0,
|
|
132
|
+
kind: readKind(body),
|
|
133
|
+
includeApi: body.includeApi !== false
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function readContentRevalidateRequestFromUrl(url) {
|
|
137
|
+
const modelId = url.searchParams.get("modelId")?.trim() ?? "";
|
|
138
|
+
if (!modelId) return null;
|
|
139
|
+
const kind = url.searchParams.get("kind")?.trim() ?? "";
|
|
140
|
+
const includeApi = url.searchParams.get("includeApi");
|
|
141
|
+
return {
|
|
142
|
+
modelId,
|
|
143
|
+
pageId: url.searchParams.get("pageId")?.trim() || void 0,
|
|
144
|
+
routeId: url.searchParams.get("routeId")?.trim() || void 0,
|
|
145
|
+
previousRouteId: url.searchParams.get("previousRouteId")?.trim() || void 0,
|
|
146
|
+
locale: url.searchParams.get("locale")?.trim() || void 0,
|
|
147
|
+
kind: kind === "publish" || kind === "delete" || kind === "update" ? kind : "update",
|
|
148
|
+
includeApi: includeApi !== "false"
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function previewContentModelInvalidation(input) {
|
|
152
|
+
const model = getRegisteredSource(input.modelId);
|
|
153
|
+
if (!model) {
|
|
154
|
+
throw new Error(`Unknown content model: ${input.modelId}`);
|
|
155
|
+
}
|
|
156
|
+
return buildContentRevalidationPaths({
|
|
157
|
+
model,
|
|
158
|
+
routeId: input.routeId,
|
|
159
|
+
previousRouteId: input.previousRouteId,
|
|
160
|
+
includeApi: input.includeApi
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
export {
|
|
164
|
+
authorizeContentRevalidate,
|
|
165
|
+
buildContentRevalidationPaths,
|
|
166
|
+
previewContentModelInvalidation,
|
|
167
|
+
readContentRevalidateRequest,
|
|
168
|
+
readContentRevalidateRequestFromUrl
|
|
169
|
+
};
|
|
170
|
+
//# sourceMappingURL=revalidate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/i18n/config.ts","../../src/content/models.ts","../../src/content/revalidate.ts"],"sourcesContent":["export const supportedLocales = [\"zh-CN\", \"en-US\"] as const;\n\nexport type AppLocale = (typeof supportedLocales)[number];\n\nexport const defaultLocale: AppLocale = \"zh-CN\";\n\nexport function isAppLocale(value: string): value is AppLocale {\n return (supportedLocales as readonly string[]).includes(value);\n}\n\nexport function localizedMovieListPath(locale: AppLocale) {\n return `/${locale}/movies`;\n}\n\nexport function localizedMovieDetailPath(locale: AppLocale, slug: string) {\n return `/${locale}/movies/${slug}`;\n}\n\nexport function expandLocalizedMoviePaths(\n paths: readonly string[],\n locale?: string\n) {\n const locales =\n locale && isAppLocale(locale) ? [locale] : [...supportedLocales];\n const expanded: string[] = [];\n\n for (const path of paths) {\n if (path === \"/movies\" || path.startsWith(\"/movies/\")) {\n for (const currentLocale of locales) {\n expanded.push(`/${currentLocale}${path}`);\n }\n continue;\n }\n expanded.push(path);\n }\n\n return Array.from(new Set(expanded));\n}\n","// 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","// packages/nextion/src/content/revalidate.ts\n//\n// Generic content revalidation helpers. The starter's\n// `revalidateContentModel` is project-specific (it knows how to\n// resolve localized movie paths and which content models exist) and\n// lives in the starter. The helpers below are model-agnostic and are\n// re-exported by the starter's `lib/content/revalidate.ts`.\n\nimport { expandLocalizedMoviePaths } from \"../i18n/config\";\nimport type { ContentModelDefinition } from \"./models\";\nimport { getRegisteredSource } from \"./models\";\n\ntype RevalidatePathFn = (\n path: string,\n type?: \"page\" | \"layout\"\n) => void | Promise<void>;\n\nexport type { RevalidatePathFn };\n\nexport type InvalidationKind = \"publish\" | \"update\" | \"delete\";\n\nexport type ContentRevalidateRequest = {\n modelId: string;\n pageId?: string;\n routeId?: string;\n previousRouteId?: string;\n locale?: string;\n kind?: InvalidationKind;\n includeApi?: boolean;\n};\n\nfunction asObject(input: unknown): Record<string, unknown> | null {\n return input && typeof input === \"object\" && !Array.isArray(input)\n ? (input as Record<string, unknown>)\n : null;\n}\n\nfunction readString(input: Record<string, unknown>, key: string) {\n const value = input[key];\n return typeof value === \"string\" ? value.trim() : \"\";\n}\n\nfunction readKind(input: Record<string, unknown>): InvalidationKind {\n const value = readString(input, \"kind\");\n if (value === \"publish\" || value === \"delete\" || value === \"update\") {\n return value;\n }\n return \"update\";\n}\n\nfunction detailPathForRouteId(detailPath: string, routeId: string) {\n return detailPath.replace(/\\[[^\\]]+\\]/g, routeId);\n}\n\nfunction publicApiDetailPathForRouteId(publicApiPath: string, routeId: string) {\n return `${publicApiPath.replace(/\\/+$/, \"\")}/${routeId.replace(/^\\/+/, \"\")}`;\n}\n\nfunction bearerToken(request: Request) {\n const authorization = request.headers.get(\"authorization\") ?? \"\";\n const match = authorization.match(/^Bearer\\s+(.+)$/i);\n return match?.[1]?.trim() ?? \"\";\n}\n\nfunction timingSafeEqualString(a: string, b: string) {\n const encoder = new TextEncoder();\n const left = encoder.encode(a);\n const right = encoder.encode(b);\n if (left.byteLength !== right.byteLength) return false;\n\n let diff = 0;\n for (let index = 0; index < left.byteLength; index += 1) {\n diff |= (left[index] ?? 0) ^ (right[index] ?? 0);\n }\n return diff === 0;\n}\n\nfunction shouldLocalizeMoviePaths(modelId: string) {\n return modelId === \"movies\" || modelId === \"movie-translations\";\n}\n\nexport function buildContentRevalidationPaths(input: {\n model: Pick<ContentModelDefinition, \"id\" | \"routes\">;\n routeId?: string;\n previousRouteId?: string;\n locale?: string;\n includeApi?: boolean;\n localizedMovieDetailPaths?: readonly string[];\n}) {\n const pagePaths = [input.model.routes.listPath];\n const routePaths: string[] = [];\n\n if (input.routeId) {\n pagePaths.push(\n detailPathForRouteId(input.model.routes.detailPath, input.routeId)\n );\n }\n if (input.previousRouteId) {\n pagePaths.push(\n detailPathForRouteId(\n input.model.routes.detailPath,\n input.previousRouteId\n )\n );\n }\n if (input.localizedMovieDetailPaths?.length) {\n pagePaths.push(...input.localizedMovieDetailPaths);\n }\n if (input.includeApi !== false && input.model.routes.publicApiPath) {\n routePaths.push(input.model.routes.publicApiPath);\n if (input.routeId) {\n routePaths.push(\n publicApiDetailPathForRouteId(\n input.model.routes.publicApiPath,\n input.routeId\n )\n );\n }\n if (input.previousRouteId) {\n routePaths.push(\n publicApiDetailPathForRouteId(\n input.model.routes.publicApiPath,\n input.previousRouteId\n )\n );\n }\n }\n\n const localizedPagePaths = shouldLocalizeMoviePaths(input.model.id)\n ? expandLocalizedMoviePaths(pagePaths, input.locale)\n : pagePaths;\n\n return {\n pagePaths: Array.from(new Set(localizedPagePaths)),\n routePaths: Array.from(new Set(routePaths)),\n all: Array.from(new Set([...localizedPagePaths, ...routePaths])),\n };\n}\n\nexport function authorizeContentRevalidate(\n request: Request,\n token?: string | null\n) {\n const expected = String(token ?? \"\").trim();\n if (!expected) return false;\n const actual = bearerToken(request);\n return Boolean(actual && timingSafeEqualString(actual, expected));\n}\n\nexport async function readContentRevalidateRequest(\n request: Request\n): Promise<ContentRevalidateRequest | null> {\n const body = asObject(await request.json().catch(() => null));\n if (!body) return null;\n\n const modelId = readString(body, \"modelId\");\n const pageId = readString(body, \"pageId\");\n const routeId = readString(body, \"routeId\");\n const previousRouteId = readString(body, \"previousRouteId\");\n const locale = readString(body, \"locale\");\n\n return {\n modelId,\n pageId: pageId || undefined,\n routeId: routeId || undefined,\n previousRouteId: previousRouteId || undefined,\n locale: locale || undefined,\n kind: readKind(body),\n includeApi: body.includeApi !== false,\n };\n}\n\nexport function readContentRevalidateRequestFromUrl(\n url: URL\n): ContentRevalidateRequest | null {\n const modelId = url.searchParams.get(\"modelId\")?.trim() ?? \"\";\n if (!modelId) return null;\n\n const kind = url.searchParams.get(\"kind\")?.trim() ?? \"\";\n const includeApi = url.searchParams.get(\"includeApi\");\n return {\n modelId,\n pageId: url.searchParams.get(\"pageId\")?.trim() || undefined,\n routeId: url.searchParams.get(\"routeId\")?.trim() || undefined,\n previousRouteId:\n url.searchParams.get(\"previousRouteId\")?.trim() || undefined,\n locale: url.searchParams.get(\"locale\")?.trim() || undefined,\n kind:\n kind === \"publish\" || kind === \"delete\" || kind === \"update\"\n ? kind\n : \"update\",\n includeApi: includeApi !== \"false\",\n };\n}\n\nexport function previewContentModelInvalidation(input: ContentRevalidateRequest) {\n const model = getRegisteredSource(input.modelId);\n if (!model) {\n throw new Error(`Unknown content model: ${input.modelId}`);\n }\n\n return buildContentRevalidationPaths({\n model,\n routeId: input.routeId,\n previousRouteId: input.previousRouteId,\n includeApi: input.includeApi,\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { SqlDatabaseAdapter } from '../platform/runtime.js';
|
|
2
|
+
import '../env-C5qu-0R-.js';
|
|
3
|
+
|
|
4
|
+
type SearchIndexedItem = {
|
|
5
|
+
pageId?: string;
|
|
6
|
+
slug?: string;
|
|
7
|
+
routeId?: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
date?: string;
|
|
11
|
+
author?: string;
|
|
12
|
+
tags?: readonly string[];
|
|
13
|
+
releaseDate?: string;
|
|
14
|
+
director?: string;
|
|
15
|
+
actors?: string;
|
|
16
|
+
summary?: string;
|
|
17
|
+
genres?: readonly string[];
|
|
18
|
+
};
|
|
19
|
+
type SearchIndexDocument = {
|
|
20
|
+
modelId: string;
|
|
21
|
+
pageId: string;
|
|
22
|
+
routeId: string;
|
|
23
|
+
title: string;
|
|
24
|
+
summary: string;
|
|
25
|
+
bodyText: string;
|
|
26
|
+
facets: readonly string[];
|
|
27
|
+
sourceUpdatedAt?: string | null;
|
|
28
|
+
};
|
|
29
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
30
|
+
declare function upsertSearchIndexDocument(db: SqlDatabaseAdapter, document: SearchIndexDocument): Promise<void>;
|
|
31
|
+
declare function deleteSearchIndexDocument(db: SqlDatabaseAdapter, input: {
|
|
32
|
+
modelId: string;
|
|
33
|
+
routeId: string;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
declare function deleteSearchIndexForModel(db: SqlDatabaseAdapter, input: {
|
|
36
|
+
modelId: string;
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
declare function getMissingSearchIndexRouteIds(db: SqlDatabaseAdapter, input: {
|
|
39
|
+
modelId: string;
|
|
40
|
+
routeIds: readonly string[];
|
|
41
|
+
}): Promise<string[]>;
|
|
42
|
+
declare function querySearchIndexRouteIds(db: SqlDatabaseAdapter, input: {
|
|
43
|
+
modelId: string;
|
|
44
|
+
query: string;
|
|
45
|
+
limit?: number;
|
|
46
|
+
}): Promise<string[]>;
|
|
47
|
+
declare function filterItemsBySearchIndex<T extends SearchIndexedItem>(items: readonly T[], query: string | null | undefined, input: {
|
|
48
|
+
modelId: string;
|
|
49
|
+
filterFallback: (items: readonly T[], query: string | null | undefined) => T[];
|
|
50
|
+
getDatabase?: () => MaybePromise<SqlDatabaseAdapter | null>;
|
|
51
|
+
}): Promise<T[]>;
|
|
52
|
+
declare function matchesIndexedItem(item: SearchIndexedItem, query: string | null | undefined): boolean;
|
|
53
|
+
|
|
54
|
+
export { type SearchIndexDocument, type SearchIndexedItem, deleteSearchIndexDocument, deleteSearchIndexForModel, filterItemsBySearchIndex, getMissingSearchIndexRouteIds, matchesIndexedItem, querySearchIndexRouteIds, upsertSearchIndexDocument };
|