{"version":3,"file":"query-DKdhVgLJ.mjs","names":[],"sources":["../src/visual-editing/editable.ts","../src/query.ts"],"sourcesContent":["/**\n * Visual editing annotation system\n *\n * Creates Proxy objects that emit data-emdash-ref attributes when spread onto elements.\n */\n\nexport interface CMSAnnotation {\n\tcollection: string;\n\tid: string;\n\tfield?: string;\n\t/** Entry status — only present on entry-level annotations (not field-level) */\n\tstatus?: string;\n\t/** Whether the entry has unpublished draft changes */\n\thasDraft?: boolean;\n}\n\n/** The shape returned when spreading an edit annotation onto an element */\nexport interface FieldAnnotation {\n\t\"data-emdash-ref\": string;\n}\n\nexport interface EditableOptions {\n\t/** Entry status: \"draft\", \"published\", \"scheduled\" */\n\tstatus?: string;\n\t/** true when draftRevisionId exists and differs from liveRevisionId */\n\thasDraft?: boolean;\n}\n\n/**\n * Create an editable proxy for an entry.\n *\n * Usage:\n * - `{...entry.edit}` - entry-level annotation (includes status/hasDraft)\n * - `{...entry.edit.title}` - field-level annotation\n * - `{...entry.edit['nested.field']}` - nested field (bracket notation)\n */\nexport function createEditable(\n\tcollection: string,\n\tid: string,\n\toptions?: EditableOptions,\n): EditProxy {\n\tconst base: CMSAnnotation = {\n\t\tcollection,\n\t\tid,\n\t\t...(options?.status && { status: options.status }),\n\t\t...(options?.hasDraft && { hasDraft: true }),\n\t};\n\n\treturn new Proxy(\n\t\t{},\n\t\t{\n\t\t\tget(_, prop) {\n\t\t\t\tif (prop === \"toJSON\") return () => ({ \"data-emdash-ref\": JSON.stringify(base) });\n\t\t\t\tif (typeof prop === \"symbol\") return undefined;\n\n\t\t\t\t// data-emdash-ref access returns the entry-level string\n\t\t\t\tif (prop === \"data-emdash-ref\") return JSON.stringify(base);\n\n\t\t\t\t// Field-level: return a FieldAnnotation for the specific field\n\t\t\t\treturn {\n\t\t\t\t\t\"data-emdash-ref\": JSON.stringify({ ...base, field: String(prop) }),\n\t\t\t\t} satisfies FieldAnnotation;\n\t\t\t},\n\t\t\townKeys() {\n\t\t\t\treturn [\"data-emdash-ref\"];\n\t\t\t},\n\t\t\tgetOwnPropertyDescriptor(_, prop) {\n\t\t\t\tif (prop === \"data-emdash-ref\") {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tconfigurable: true,\n\t\t\t\t\t\tenumerable: true,\n\t\t\t\t\t\tvalue: JSON.stringify(base),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t},\n\t);\n}\n\n/**\n * Create a noop proxy for production mode.\n * Spreading this produces no attributes.\n */\nexport function createNoop(): EditProxy {\n\treturn new Proxy(\n\t\t{},\n\t\t{\n\t\t\tget(_, prop) {\n\t\t\t\tif (typeof prop === \"symbol\") return undefined;\n\t\t\t\t// All property access returns undefined in noop mode\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t\townKeys() {\n\t\t\t\treturn [];\n\t\t\t},\n\t\t\tgetOwnPropertyDescriptor() {\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t},\n\t);\n}\n\n/**\n * Visual editing proxy type.\n *\n * Spread directly onto elements for entry-level annotations: `{...entry.edit}`\n * Access a field for field-level annotations: `{...entry.edit.title}`\n *\n * In production, spreading produces no attributes (noop).\n */\nexport type EditProxy = {\n\treadonly [field: string]: Partial<FieldAnnotation>;\n};\n","/// <reference types=\"astro/client\" />\n/**\n * Query functions for EmDash content\n *\n * These wrap Astro's getLiveCollection/getLiveEntry with type filtering.\n * Use these instead of calling Astro's functions directly.\n *\n * Error handling follows Astro's pattern - returns { entries/entry, error }\n * so callers can gracefully handle errors (including 404s).\n *\n * Preview mode is handled implicitly via ALS request context —\n * no parameters needed. The middleware verifies the preview token\n * and sets the context; query functions read it automatically.\n *\n * The triple-slash directive above pulls in the ambient declaration for\n * `astro:content` (used by the dynamic imports below) so this source\n * file typechecks even when reached transitively by a sibling package\n * whose tsconfig doesn't list `astro/client` in `compilerOptions.types`.\n *\n * Note: the directive is stripped from the compiled output (`dist/*`)\n * by tsdown, so it does not propagate to downstream consumers of the\n * published package. Consumers are Astro sites and already provide their\n * own `astro/client` ambient surface anyway, so the runtime dynamic\n * import resolves there at typecheck time without our help.\n */\n\nimport { encodeCursor } from \"./database/repositories/types.js\";\nimport { getFallbackChain, getI18nConfig, isI18nEnabled } from \"./i18n/config.js\";\nimport {\n\tcreditsFromFoldedBylines,\n\tCURSOR_RAW_VALUES,\n\tFOLDED_BYLINES,\n\tFOLDED_BYLINES_EXIST,\n\tFOLDED_TERMS,\n\ttype WhereRange,\n\ttype WhereValue,\n} from \"./loader.js\";\nimport {\n\tcachedQuery,\n\tcontentNamespaces,\n\tinvalidateSchemaObjectCache,\n} from \"./object-cache/index.js\";\nimport { requestCached } from \"./request-cache.js\";\nimport { getRequestContext } from \"./request-context.js\";\nimport { compileUrlPattern } from \"./schema/url-pattern.js\";\nimport type { TaxonomyTerm } from \"./taxonomies/types.js\";\nimport { isMissingTableError } from \"./utils/db-errors.js\";\nimport {\n\tcreateEditable,\n\tcreateNoop,\n\ttype EditProxy,\n\ttype EditableOptions,\n} from \"./visual-editing/editable.js\";\n\n/**\n * Collection type registry for type-safe queries.\n *\n * This interface is extended by the generated emdash-env.d.ts file\n * to provide type inference for collection names and their data shapes.\n *\n * @example\n * ```ts\n * // In emdash-env.d.ts (generated):\n * declare module \"@premium-cms/emdash\" {\n *   interface EmDashCollections {\n *     posts: { title: string; content: PortableTextBlock[]; };\n *     pages: { title: string; body: PortableTextBlock[]; };\n *   }\n * }\n *\n * // Then in your code:\n * const { entries } = await getEmDashCollection(\"posts\");\n * // entries[0].data.title is typed as string\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface EmDashCollections {}\n\n/**\n * Helper type to infer the data type for a collection.\n * Returns the registered type if known, otherwise falls back to Record<string, unknown>.\n */\nexport type InferCollectionData<T extends string> = T extends keyof EmDashCollections\n\t? EmDashCollections[T]\n\t: Record<string, unknown>;\n\n/**\n * Sort direction\n */\nexport type SortDirection = \"asc\" | \"desc\";\n\n/**\n * Order by specification - field name to direction\n * @example { created_at: \"desc\" } - Sort by created_at descending\n * @example { title: \"asc\" } - Sort by title ascending\n * @example { published_at: \"desc\", title: \"asc\" } - Multi-field sort\n */\nexport type OrderBySpec = Record<string, SortDirection>;\n\nexport type { WhereRange, WhereValue };\n\n/**\n * Fields shared by every collection query, independent of pagination mode.\n *\n * Cursor and offset pagination are mutually exclusive, so they live on the\n * `CursorCollectionFilter` / `OffsetCollectionFilter` variants rather than\n * here. Use the {@link CollectionFilter} union for any value that may be\n * either.\n */\nexport interface CollectionFilterBase {\n\tstatus?: \"draft\" | \"published\" | \"archived\";\n\tlimit?: number;\n\t/**\n\t * Filter by field values, taxonomy terms, byline credits, or ranges.\n\t *\n\t * Taxonomy names are detected automatically and filtered via JOIN.\n\t * The reserved `byline` key filters by byline credit (any credit, not\n\t * just the primary one) via the `_emdash_content_bylines` junction\n\t * table; its value is one or more byline translation groups. This\n\t * matches co-authored entries, which `primary_byline_id` alone misses.\n\t * Other keys are treated as column filters on the content table.\n\t *\n\t * @example { category: 'news' } - Filter by taxonomy term\n\t * @example { category: ['news', 'featured'] } - Filter by multiple terms (OR)\n\t * @example { byline: '01HXYZ...' } - Entries credited to a byline (any position)\n\t * @example { byline: ['01HXYZ...', '01HABC...'] } - Credited to any of these bylines (OR)\n\t * @example { series: 'main' } - Exact match on a content field\n\t * @example { published_at: { gte: '2024-01-01', lt: '2025-01-01' } } - Date range\n\t */\n\twhere?: Record<string, WhereValue>;\n\t/**\n\t * Order results by field(s)\n\t * @default { created_at: \"desc\" }\n\t * @example { created_at: \"desc\" } - Sort by created_at descending (default)\n\t * @example { title: \"asc\" } - Sort by title ascending\n\t * @example { published_at: \"desc\", title: \"asc\" } - Multi-field sort\n\t */\n\torderBy?: OrderBySpec;\n\t/**\n\t * Filter by locale. When set, only returns entries in this locale.\n\t * Only relevant when i18n is configured.\n\t * @example \"en\" — English entries only\n\t * @example \"fr\" — French entries only\n\t */\n\tlocale?: string;\n}\n\n/** Keyset-paginated query filter. Cannot also carry an `offset`. */\nexport interface CursorCollectionFilter extends CollectionFilterBase {\n\t/**\n\t * Opaque cursor for keyset pagination.\n\t * Pass the `nextCursor` value from a previous result to fetch the next page.\n\t * @example\n\t * ```ts\n\t * const cursor = Astro.url.searchParams.get(\"cursor\") ?? undefined;\n\t * const { entries, nextCursor } = await getEmDashCollection(\"posts\", {\n\t *   limit: 10,\n\t *   cursor,\n\t * });\n\t * ```\n\t */\n\tcursor?: string;\n\toffset?: never;\n}\n\n/** Offset-paginated query filter. Cannot also carry a `cursor`. */\nexport interface OffsetCollectionFilter extends CollectionFilterBase {\n\t/**\n\t * Skip this many entries before returning results (offset pagination).\n\t *\n\t * Use with `limit` to render numbered archive routes like `/page/2`\n\t * without walking cursors or over-fetching from the start:\n\t *\n\t * ```ts\n\t * const perPage = 20;\n\t * const { entries, hasMore } = await getEmDashCollection(\"posts\", {\n\t *   limit: perPage,\n\t *   offset: (page - 1) * perPage,\n\t *   orderBy: { published_at: \"desc\" },\n\t * });\n\t * ```\n\t *\n\t * Only a positive integer applies.\n\t */\n\toffset?: number;\n\tcursor?: never;\n}\n\n/**\n * Filter for `getEmDashCollection`.\n *\n * A union of the cursor and offset pagination variants: supplying both\n * `cursor` and `offset` is a compile-time error, since they are mutually\n * exclusive ways to express \"the next page\" (cursor wins at runtime).\n */\nexport type CollectionFilter = CursorCollectionFilter | OffsetCollectionFilter;\n\nexport interface ContentEntry<T = Record<string, unknown>> {\n\tid: string;\n\tdata: T;\n\t/** Visual editing annotations. Spread onto elements: {...entry.edit.title} */\n\tedit: EditProxy;\n}\n\n/** Cache hint returned by the content loader for route caching */\nexport interface CacheHint {\n\ttags?: string[];\n\tlastModified?: Date;\n}\n\n/**\n * Result from getEmDashCollection\n */\nexport interface CollectionResult<T> {\n\t/** The entries (empty array if error or none found) */\n\tentries: ContentEntry<T>[];\n\t/** Error if the query failed */\n\terror?: Error;\n\t/** Cache hint for route caching (pass to Astro.cache.set()) */\n\tcacheHint: CacheHint;\n\t/**\n\t * Opaque cursor for the next page.\n\t * Undefined when there are no more results.\n\t * Pass this as `cursor` in the next query to get the next page.\n\t */\n\tnextCursor?: string;\n\t/**\n\t * Whether more entries exist beyond this page. Set whenever `limit` is\n\t * provided (cursor or offset pagination), so numbered archive routes can\n\t * render a \"next page\" link without computing a total count.\n\t */\n\thasMore?: boolean;\n}\n\n/**\n * Result from getEmDashEntry\n */\nexport interface EntryResult<T> {\n\t/** The entry, or null if not found */\n\tentry: ContentEntry<T> | null;\n\t/** Error if the query failed (not set for \"not found\", only for actual errors) */\n\terror?: Error;\n\t/** Whether we're in preview mode (valid token was provided) */\n\tisPreview: boolean;\n\t/** Set when a fallback locale was used instead of the requested locale */\n\tfallbackLocale?: string;\n\t/** Cache hint for route caching (pass to Astro.cache.set()) */\n\tcacheHint: CacheHint;\n}\n\nconst COLLECTION_NAME = \"_emdash\";\n\n/** Symbol key for edit metadata on PT arrays — avoids collision with user data */\nconst EMDASH_EDIT = Symbol.for(\"__emdash\");\n\n/** Edit metadata attached to PT arrays in edit mode */\nexport interface EditFieldMeta {\n\tcollection: string;\n\tid: string;\n\tfield: string;\n}\n\n/** Type guard for EditFieldMeta */\nfunction isEditFieldMeta(value: unknown): value is EditFieldMeta {\n\tif (typeof value !== \"object\" || value === null) return false;\n\tif (!(\"collection\" in value) || !(\"id\" in value) || !(\"field\" in value)) return false;\n\t// After `in` checks, TS narrows to Record<\"collection\" | \"id\" | \"field\", unknown>\n\tconst { collection, id, field } = value;\n\treturn typeof collection === \"string\" && typeof id === \"string\" && typeof field === \"string\";\n}\n\n/**\n * Read edit metadata from a value (returns undefined if not tagged).\n * Uses Object.getOwnPropertyDescriptor to access Symbol-keyed property\n * without an unsafe type assertion.\n */\nexport function getEditMeta(value: unknown): EditFieldMeta | undefined {\n\tif (value && typeof value === \"object\") {\n\t\tconst desc = Object.getOwnPropertyDescriptor(value, EMDASH_EDIT);\n\t\tconst meta: unknown = desc?.value;\n\t\tif (isEditFieldMeta(meta)) {\n\t\t\treturn meta;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * Tag PT-like arrays in entry data with edit metadata (non-enumerable).\n * A PT array is identified by: is an array, first element has _type property.\n */\nfunction tagEditableFields(data: Record<string, unknown>, collection: string, id: string): void {\n\tfor (const [field, value] of Object.entries(data)) {\n\t\tif (\n\t\t\tArray.isArray(value) &&\n\t\t\tvalue.length > 0 &&\n\t\t\tvalue[0] &&\n\t\t\ttypeof value[0] === \"object\" &&\n\t\t\t\"_type\" in value[0]\n\t\t) {\n\t\t\tObject.defineProperty(value, EMDASH_EDIT, {\n\t\t\t\tvalue: { collection, id, field } satisfies EditFieldMeta,\n\t\t\t\tenumerable: false,\n\t\t\t\tconfigurable: true,\n\t\t\t});\n\t\t}\n\t}\n}\n\n/** Safely read a string field from a Record, with optional fallback */\nfunction dataStr(data: Record<string, unknown>, key: string, fallback = \"\"): string {\n\tconst val = data[key];\n\treturn typeof val === \"string\" ? val : fallback;\n}\n\n/** Type guard for Record<string, unknown> */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Extract data as Record from an Astro entry (which is any-typed) */\nfunction entryData(entry: { data?: unknown }): Record<string, unknown> {\n\treturn isRecord(entry.data) ? entry.data : {};\n}\n\n/** Extract the database ID from entry data (data.id is the ULID, entry.id is the slug) */\nfunction entryDatabaseId(entry: { id: string; data?: unknown }): string {\n\tconst d = entryData(entry);\n\treturn dataStr(d, \"id\") || entry.id;\n}\n\n/** Extract edit options from entry data for the proxy */\nfunction entryEditOptions(entry: { data?: unknown }): EditableOptions {\n\tconst data = entryData(entry);\n\tconst status = dataStr(data, \"status\", \"draft\");\n\tconst draftRevisionId = dataStr(data, \"draftRevisionId\") || undefined;\n\tconst liveRevisionId = dataStr(data, \"liveRevisionId\") || undefined;\n\tconst hasDraft = !!draftRevisionId && draftRevisionId !== liveRevisionId;\n\treturn { status, hasDraft };\n}\n\nfunction stripRevisionMetadata(entry: { data?: unknown }): void {\n\tconst data = entryData(entry);\n\tdelete data.draftRevisionId;\n\tdelete data.liveRevisionId;\n}\n\nfunction canExposeRevisionMetadata(\n\tentry: { id: string; data?: unknown },\n\tcollection: string,\n): boolean {\n\tconst ctx = getRequestContext();\n\tif (ctx?.editMode) return true;\n\tif (ctx?.preview?.collection !== collection) return false;\n\tconst dbId = entryDatabaseId(entry);\n\treturn ctx.preview.id === dbId || ctx.preview.id === entry.id;\n}\n\n/**\n * Get all entries of a content type\n *\n * Returns { entries, error } for graceful error handling.\n *\n * When emdash-env.d.ts is generated, the collection name will be\n * type-checked and the return type will be inferred automatically.\n *\n * @example\n * ```ts\n * import { getEmDashCollection } from \"@premium-cms/emdash\";\n *\n * const { entries: posts, error } = await getEmDashCollection(\"posts\");\n * if (error) {\n *   console.error(\"Failed to load posts:\", error);\n *   return;\n * }\n * // posts[0].data.title is typed (if emdash-env.d.ts exists)\n *\n * // With filters\n * const { entries: drafts } = await getEmDashCollection(\"posts\", { status: \"draft\" });\n * ```\n */\nexport async function getEmDashCollection<T extends string, D = InferCollectionData<T>>(\n\ttype: T,\n\tfilter?: CollectionFilter,\n): Promise<CollectionResult<D>> {\n\t// Cache per (type, filter) within a single request. Edit mode and\n\t// preview are request-scoped and stable, so they don't need to be\n\t// part of the key. Widgets and layouts frequently request the same\n\t// collection shape as the page itself (e.g. a \"recent posts\" list\n\t// appears on the home page AND in the sidebar) — caching collapses\n\t// those duplicate queries, along with the bylines and taxonomy-term\n\t// hydration each call would otherwise re-do.\n\t//\n\t// Bucket small limits to a shared minimum so a page with several\n\t// \"recent N posts\" widgets at slightly different limits (e.g. a\n\t// post-detail page asking for 4 in the body and 5 in the sidebar)\n\t// shares one fetch + hydration round-trip rather than running two.\n\t// Cursor-paginated calls are exempt: their limit is part of the\n\t// pagination contract.\n\tconst bucketed = bucketFilter(filter);\n\tconst cached = await requestCached(collectionCacheKey(type, bucketed.fetchFilter), () =>\n\t\tloadCollectionCached<T, D>(type, bucketed.fetchFilter),\n\t);\n\treturn bucketed.requestedLimit === undefined\n\t\t? cached\n\t\t: sliceCollectionResult(cached, bucketed.requestedLimit, filter?.orderBy);\n}\n\n/** Shape of a cached collection snapshot (entries reduced to JSON-safe form). */\ninterface CachedCollectionValue {\n\tentries: unknown[];\n\tnextCursor?: string;\n\thasMore?: boolean;\n\tcacheHint: CacheHint;\n}\n\n/**\n * Distributed (L2) read-through around {@link getEmDashCollectionUncached}.\n *\n * Caches a JSON-safe snapshot keyed by collection + filter + effective locale,\n * folding the shared `bylines`/`taxonomies` epochs into the key so renaming an\n * author or term invalidates affected lists. Errors are never cached.\n */\nasync function loadCollectionCached<T extends string, D = InferCollectionData<T>>(\n\ttype: T,\n\tfilter?: CollectionFilter,\n): Promise<CollectionResult<D>> {\n\tconst snapshot = await cachedQuery<ContentSnapshot<CachedCollectionValue>>({\n\t\tnamespace: contentNamespaces(type),\n\t\tkey: `collection:${collectionCacheKey(type, filter)}|loc=${effectiveLocaleKey(filter)}`,\n\t\tload: async () => {\n\t\t\tconst result = await getEmDashCollectionUncached<T, D>(type, filter);\n\t\t\tif (result.error) {\n\t\t\t\treturn { ok: false, error: result.error, cacheHint: result.cacheHint };\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tvalue: {\n\t\t\t\t\tentries: result.entries.map(entrySnapshot),\n\t\t\t\t\tnextCursor: result.nextCursor,\n\t\t\t\t\thasMore: result.hasMore,\n\t\t\t\t\tcacheHint: result.cacheHint,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tcacheable: (snap) => snap.ok,\n\t});\n\n\tif (!snapshot.ok) {\n\t\treturn { entries: [], error: snapshot.error, cacheHint: snapshot.cacheHint };\n\t}\n\treturn {\n\t\tentries: snapshot.value.entries.map((entry) => {\n\t\t\tconst revived = reviveEntry<D>(entry);\n\t\t\tif (!canExposeRevisionMetadata(revived, type)) stripRevisionMetadata(revived);\n\t\t\treturn revived;\n\t\t}),\n\t\tnextCursor: snapshot.value.nextCursor,\n\t\thasMore: snapshot.value.hasMore,\n\t\tcacheHint: snapshot.value.cacheHint,\n\t};\n}\n\n/**\n * Threshold for limit bucketing. Page templates routinely render small\n * \"recent posts\" widgets at limits 3-8; rounding those up to a single\n * shared bucket lets one fetch satisfy several widgets within a request.\n * Above this, the requested limit is honoured exactly — bucketing limit:50\n * to limit:64 would waste hydration work for callers fetching real pages.\n */\nconst BUCKET_LIMIT_THRESHOLD = 10;\n\ninterface BucketedFilter {\n\t/** Filter to pass to the loader (with limit possibly raised). */\n\tfetchFilter: CollectionFilter | undefined;\n\t/** Original limit; defined only when bucketing was applied. */\n\trequestedLimit: number | undefined;\n}\n\n/** @internal exported for unit tests; not part of the public API. */\nexport function bucketFilter(filter: CollectionFilter | undefined): BucketedFilter {\n\tconst limit = filter?.limit;\n\tif (\n\t\tlimit === undefined ||\n\t\tlimit >= BUCKET_LIMIT_THRESHOLD ||\n\t\tlimit <= 0 ||\n\t\tfilter?.cursor !== undefined ||\n\t\t// Offset paginates a deliberate page window; its limit is part of the\n\t\t// pagination contract, so don't round it up the way \"recent N\" widgets get.\n\t\tfilter?.offset !== undefined\n\t) {\n\t\treturn { fetchFilter: filter, requestedLimit: undefined };\n\t}\n\treturn {\n\t\tfetchFilter: { ...filter, limit: BUCKET_LIMIT_THRESHOLD },\n\t\trequestedLimit: limit,\n\t};\n}\n\n/**\n * Slice a cached bucketed result down to the originally-requested limit\n * and recompute `nextCursor` from the row that would have been the\n * over-fetch detector for that limit. When truncation is needed, returns\n * a shallow-copied result with a new `entries` array; otherwise returns\n * the cached result unchanged (including error results and results\n * already within the requested limit).\n */\n/** @internal exported for unit tests; not part of the public API. */\nexport function sliceCollectionResult<D>(\n\tcached: CollectionResult<D>,\n\tlimit: number,\n\torderBy: OrderBySpec | undefined,\n): CollectionResult<D> {\n\tif (cached.error) return cached;\n\tif (cached.entries.length <= limit) return cached;\n\tconst sliced = cached.entries.slice(0, limit);\n\t// Mirror the loader's encoding: cursor points at the last returned row,\n\t// so \"next page\" picks up at the row immediately after it. See\n\t// buildCursorCondition in loader.ts — it filters strictly past this row.\n\tconst lastEntry = sliced.at(-1);\n\tconst nextCursor = lastEntry ? encodeEntryCursor(lastEntry, orderBy) : undefined;\n\t// Truncating to the requested limit means at least one more entry existed.\n\treturn { ...cached, entries: sliced, nextCursor, hasMore: true };\n}\n\n/** Map of database column names to camelCase keys present on entry.data. */\nconst ENTRY_DATA_KEY_MAP: Record<string, string> = {\n\tcreated_at: \"createdAt\",\n\tupdated_at: \"updatedAt\",\n\tpublished_at: \"publishedAt\",\n\tscheduled_at: \"scheduledAt\",\n\tauthor_id: \"authorId\",\n\tprimary_byline_id: \"primaryBylineId\",\n};\n\n// Mirror loader.ts FIELD_NAME_PATTERN. Kept in sync intentionally — diverging\n// would let the encoder accept a field name the loader's getPrimarySort then\n// rejected, producing a cursor that paginates against a different column.\nconst FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Encode a `nextCursor` from a content entry, mirroring the loader's\n * encoding scheme: `(orderValue, id)` where `orderValue` is the primary\n * sort field's stringified value. For date columns, reads the raw DB\n * string the loader stashed via CURSOR_RAW_VALUES — round-tripping the\n * parsed Date through `toISOString()` would lose precision for stored\n * values that aren't already ISO-with-milliseconds.\n */\nfunction encodeEntryCursor<D>(\n\tentry: ContentEntry<D>,\n\torderBy: OrderBySpec | undefined,\n): string | undefined {\n\tconst data = entryData(entry);\n\tconst id = dataStr(data, \"id\");\n\tif (!id) return undefined;\n\n\t// Match loader.ts getPrimarySort: take the first valid field, default to created_at.\n\tlet dbField = \"created_at\";\n\tif (orderBy) {\n\t\tfor (const field of Object.keys(orderBy)) {\n\t\t\tif (FIELD_NAME_PATTERN.test(field)) {\n\t\t\t\tdbField = field;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Date columns: prefer the raw stored string captured by the loader so\n\t// the cursor matches what a direct loader fetch would emit, regardless\n\t// of how the DB stored the timestamp.\n\tconst rawDateValuesRaw = Reflect.get(data, CURSOR_RAW_VALUES);\n\tif (rawDateValuesRaw !== null && typeof rawDateValuesRaw === \"object\") {\n\t\tconst raw = Reflect.get(rawDateValuesRaw, dbField);\n\t\tif (typeof raw === \"string\") return encodeCursor(raw, id);\n\t}\n\n\tconst dataKey = ENTRY_DATA_KEY_MAP[dbField] ?? dbField;\n\tconst value = data[dataKey];\n\tlet orderValue: string;\n\tif (value instanceof Date) {\n\t\torderValue = value.toISOString();\n\t} else if (typeof value === \"string\" || typeof value === \"number\") {\n\t\torderValue = String(value);\n\t} else {\n\t\t// Match the loader's empty-string fallback for null/undefined order\n\t\t// values so cursor decoding stays valid even at the boundary.\n\t\torderValue = \"\";\n\t}\n\treturn encodeCursor(orderValue, id);\n}\n\n/**\n * Build a canonical cache key for `getEmDashCollection`.\n *\n * `JSON.stringify` is insertion-order-sensitive, so two callers passing\n * semantically identical filters with different key orders would miss\n * the cache. We fix the top-level field order and sort `where` keys\n * (order there is irrelevant), while preserving `orderBy` key order\n * because that's the sort priority.\n */\nfunction collectionCacheKey(type: string, filter?: CollectionFilter): string {\n\tif (!filter) return `collection:${type}:`;\n\tconst parts = [\n\t\tfilter.status ?? \"\",\n\t\tfilter.limit ?? \"\",\n\t\tfilter.cursor ?? \"\",\n\t\tfilter.offset ?? \"\",\n\t\tfilter.where ? stableStringify(filter.where) : \"\",\n\t\tfilter.orderBy ? JSON.stringify(filter.orderBy) : \"\",\n\t\tfilter.locale ?? \"\",\n\t];\n\treturn `collection:${type}:${parts.join(\"|\")}`;\n}\n\nfunction stableStringify(value: Record<string, unknown>): string {\n\treturn JSON.stringify(stableOrder(value));\n}\n\nfunction stableOrder(value: Record<string, unknown>): Record<string, unknown> {\n\tconst keys = Object.keys(value).toSorted();\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const k of keys) {\n\t\tconst v = value[k];\n\t\tif (isRecord(v)) {\n\t\t\tordered[k] = stableOrder(v);\n\t\t} else {\n\t\t\tordered[k] = v;\n\t\t}\n\t}\n\treturn ordered;\n}\n\n// ── Object-cache (L2) serialization for content reads ───────────────────────\n//\n// Content entries can't be stored verbatim: each carries a non-serializable\n// `edit` proxy (a function) and a non-enumerable `CURSOR_RAW_VALUES` symbol on\n// `data` (raw date strings used to reproduce the loader's pagination cursor).\n// We reduce each entry to a JSON-safe snapshot before caching — copying the\n// cursor-raw values into an enumerable field and dropping `edit` — then rebuild\n// the symbol and re-attach a no-op `edit` on the way out. The object cache's\n// codec preserves `Date` instances, so timestamps survive the round-trip.\n//\n// L2 is only consulted for anonymous, non-preview, non-edit requests (see\n// `shouldBypass` in object-cache), where `edit` is always the no-op variant —\n// so dropping and recreating it is lossless.\n\n/** Enumerable field carrying the {@link CURSOR_RAW_VALUES} payload in snapshots. */\nconst CURSOR_RAW_FIELD = \"__emdashCursorRaw\";\n\n/** Result wrapper distinguishing a cached error from a cacheable success. */\ntype ContentSnapshot<S> =\n\t| { ok: true; value: S }\n\t| { ok: false; error?: Error; cacheHint: CacheHint };\n\nfunction entrySnapshot<D>(entry: ContentEntry<D>): Record<string, unknown> {\n\tconst data = entryData(entry);\n\tconst rawCursor = Reflect.get(data, CURSOR_RAW_VALUES);\n\t// Drop the `edit` function; copy enumerable data + the cursor-raw values.\n\tconst { edit: _edit, ...rest } = entry as ContentEntry<D> & { edit?: unknown };\n\treturn {\n\t\t...rest,\n\t\tdata: { ...data, [CURSOR_RAW_FIELD]: rawCursor ?? {} },\n\t};\n}\n\nfunction reviveEntry<D>(raw: unknown): ContentEntry<D> {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- snapshot shape produced by entrySnapshot\n\tconst entry = raw as Record<string, unknown>;\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- snapshot `data` is always a record\n\tconst data: Record<string, unknown> = { ...(entry.data as Record<string, unknown>) };\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- snapshot field written by entrySnapshot\n\tconst rawCursor = (data[CURSOR_RAW_FIELD] as Record<string, string> | undefined) ?? {};\n\tdelete data[CURSOR_RAW_FIELD];\n\tObject.defineProperty(data, CURSOR_RAW_VALUES, {\n\t\tvalue: rawCursor,\n\t\tenumerable: false,\n\t\tconfigurable: false,\n\t\twritable: false,\n\t});\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- rebuilt to the ContentEntry shape with a no-op edit proxy\n\treturn { ...entry, data, edit: createNoop() } as ContentEntry<D>;\n}\n\n/** Resolve the effective locale used by content reads, for the L2 cache key. */\nfunction effectiveLocaleKey(filter?: { locale?: string }): string {\n\tconst ctx = getRequestContext();\n\tconst i18nConfig = getI18nConfig();\n\treturn (\n\t\tfilter?.locale ?? ctx?.locale ?? (isI18nEnabled() ? i18nConfig!.defaultLocale : undefined) ?? \"\"\n\t);\n}\n\nasync function getEmDashCollectionUncached<T extends string, D = InferCollectionData<T>>(\n\ttype: T,\n\tfilter?: CollectionFilter,\n): Promise<CollectionResult<D>> {\n\t// Dynamic import to avoid build-time issues\n\tconst { getLiveCollection } = await import(\"astro:content\");\n\n\t// Resolve locale: explicit filter > ALS context > defaultLocale (when i18n enabled)\n\t// Without this, queries return all locale rows, producing broken IDs\n\tconst ctx = getRequestContext();\n\tconst i18nConfig = getI18nConfig();\n\tconst resolvedLocale =\n\t\tfilter?.locale ?? ctx?.locale ?? (isI18nEnabled() ? i18nConfig!.defaultLocale : undefined);\n\n\tconst requestedLimit = filter?.limit;\n\t// cursor and offset are mutually exclusive on the loader filter union, so\n\t// spread only the one in play (cursor wins) rather than emitting both keys.\n\tconst pageParam =\n\t\tfilter?.cursor !== undefined\n\t\t\t? { cursor: filter.cursor }\n\t\t\t: filter?.offset !== undefined\n\t\t\t\t? { offset: filter.offset }\n\t\t\t\t: {};\n\tconst result = await getLiveCollection(COLLECTION_NAME, {\n\t\ttype,\n\t\tstatus: filter?.status,\n\t\tlimit: requestedLimit && requestedLimit > 0 ? requestedLimit + 1 : filter?.limit,\n\t\t...pageParam,\n\t\twhere: filter?.where,\n\t\torderBy: filter?.orderBy,\n\t\tlocale: resolvedLocale,\n\t});\n\n\tconst { entries, error, cacheHint } = result;\n\n\tif (error) {\n\t\treturn { entries: [], error, cacheHint: {} };\n\t}\n\n\tconst hasMore = requestedLimit != null && requestedLimit > 0 && entries.length > requestedLimit;\n\tconst pageEntries = hasMore ? entries.slice(0, requestedLimit) : entries;\n\tconst nextCursor = hasMore ? encodeEntryCursor(pageEntries.at(-1), filter?.orderBy) : undefined;\n\t// `hasMore` is only meaningful when a limit bounds the page; otherwise the\n\t// query returned everything and there is no \"next page\" to report.\n\tconst hasMoreResult = requestedLimit != null && requestedLimit > 0 ? hasMore : undefined;\n\n\tconst isEditMode = ctx?.editMode ?? false;\n\tconst entriesWithEdit = pageEntries.map((entry: ContentEntry<D>) => {\n\t\tconst dbId = entryDatabaseId(entry);\n\t\tif (isEditMode) {\n\t\t\ttagEditableFields(entryData(entry), type, dbId);\n\t\t}\n\t\tif (!canExposeRevisionMetadata(entry, type)) {\n\t\t\tstripRevisionMetadata(entry);\n\t\t}\n\t\treturn {\n\t\t\t...entry,\n\t\t\tedit: isEditMode ? createEditable(type, dbId, entryEditOptions(entry)) : createNoop(),\n\t\t};\n\t});\n\n\t// Eagerly hydrate bylines and taxonomy terms for all entries in parallel.\n\t// Both are independent queries, so running them concurrently halves the\n\t// round-trip cost on remote databases (D1 replicas, etc.).\n\tawait Promise.all([\n\t\thydrateEntryBylines(type, entriesWithEdit),\n\t\t// Use the content query locale as the preferred term locale; hydration\n\t\t// falls back only to the configured default when a group lacks that variant.\n\t\thydrateEntryTerms(type, entriesWithEdit, resolvedLocale),\n\t]);\n\n\treturn {\n\t\tentries: entriesWithEdit,\n\t\tnextCursor,\n\t\thasMore: hasMoreResult,\n\t\tcacheHint: cacheHint ?? {},\n\t};\n}\n\n/**\n * Get a single entry by type and ID/slug\n *\n * Returns { entry, error, isPreview } for graceful error handling.\n * - entry is null if not found (not an error)\n * - error is set only for actual errors (db issues, etc.)\n *\n * Preview mode is detected automatically from request context (ALS).\n * When the URL has a valid `_preview` token, the middleware sets preview\n * context and this function serves draft revision data if available.\n *\n * @example\n * ```ts\n * import { getEmDashEntry } from \"@premium-cms/emdash\";\n *\n * // Simple usage — preview just works via middleware\n * const { entry: post, isPreview, error } = await getEmDashEntry(\"posts\", \"my-slug\");\n * if (!post) return Astro.redirect(\"/404\");\n * ```\n */\nexport async function getEmDashEntry<T extends string, D = InferCollectionData<T>>(\n\ttype: T,\n\tid: string,\n\toptions?: { locale?: string },\n): Promise<EntryResult<D>> {\n\t// Dynamic import to avoid build-time issues\n\tconst { getLiveEntry } = await import(\"astro:content\");\n\n\t// Check ALS for preview and edit mode context\n\tconst ctx = getRequestContext();\n\tconst preview = ctx?.preview;\n\tconst isEditMode = ctx?.editMode ?? false;\n\tconst isPreviewMode = !!preview && preview.collection === type;\n\t// Edit mode implies preview — editors should see draft content\n\tconst serveDrafts = isPreviewMode || isEditMode;\n\n\t// Resolve locale: explicit option > ALS context > undefined (no filter)\n\tconst requestedLocale = options?.locale ?? ctx?.locale;\n\n\t/** Wrap a raw Astro entry with edit proxy, tagging editable fields if needed */\n\tfunction wrapEntry(raw: ContentEntry<D>): ContentEntry<D> {\n\t\tconst dbId = entryDatabaseId(raw);\n\t\tif (isEditMode) {\n\t\t\ttagEditableFields(entryData(raw), type, dbId);\n\t\t}\n\t\treturn {\n\t\t\t...raw,\n\t\t\tedit: isEditMode ? createEditable(type, dbId, entryEditOptions(raw)) : createNoop(),\n\t\t};\n\t}\n\n\t/** Check if an entry has completed publication. */\n\tfunction isVisible(entry: ContentEntry<D>): boolean {\n\t\treturn dataStr(entryData(entry), \"status\") === \"published\";\n\t}\n\n\t// Build the fallback chain: [requestedLocale, fallback1, ..., defaultLocale]\n\t// When i18n is disabled or no locale requested, just use a single-element chain\n\tconst localeChain =\n\t\trequestedLocale && isI18nEnabled() ? getFallbackChain(requestedLocale) : [requestedLocale];\n\n\t/** Return a successful EntryResult with bylines and taxonomy terms hydrated */\n\tasync function successResult(\n\t\twrapped: ContentEntry<D>,\n\t\topts: { isPreview: boolean; fallbackLocale?: string; cacheHint: CacheHint },\n\t): Promise<EntryResult<D>> {\n\t\tif (!opts.isPreview) stripRevisionMetadata(wrapped);\n\t\t// No-i18n callers use the legacy wildcard cache key. The query path still\n\t\t// resolves against the stored content-row locale when this is undefined.\n\t\tconst termLocale = isI18nEnabled()\n\t\t\t? dataStr(entryData(wrapped), \"locale\") || undefined\n\t\t\t: undefined;\n\t\tawait Promise.all([\n\t\t\thydrateEntryBylines(type, [wrapped]),\n\t\t\thydrateEntryTerms(type, [wrapped], termLocale),\n\t\t]);\n\t\treturn {\n\t\t\tentry: wrapped,\n\t\t\tisPreview: opts.isPreview,\n\t\t\tfallbackLocale: opts.fallbackLocale,\n\t\t\tcacheHint: opts.cacheHint,\n\t\t};\n\t}\n\n\tif (serveDrafts) {\n\t\t// Draft mode: try each locale in the fallback chain\n\t\tfor (let i = 0; i < localeChain.length; i++) {\n\t\t\tconst locale = localeChain[i];\n\t\t\tconst fallbackLocale = i > 0 ? locale : undefined;\n\n\t\t\tconst {\n\t\t\t\tentry: baseEntry,\n\t\t\t\terror: baseError,\n\t\t\t\tcacheHint,\n\t\t\t} = await getLiveEntry(COLLECTION_NAME, {\n\t\t\t\ttype,\n\t\t\t\tid,\n\t\t\t\tlocale,\n\t\t\t});\n\n\t\t\tif (baseError) {\n\t\t\t\treturn { entry: null, error: baseError, isPreview: serveDrafts, cacheHint: {} };\n\t\t\t}\n\n\t\t\tif (!baseEntry) continue; // Try next locale in chain\n\n\t\t\t// Preview tokens are item-scoped: verify the resolved entry matches.\n\t\t\t// Edit mode (authenticated editors) has collection-wide draft access.\n\t\t\tif (isPreviewMode && !isEditMode) {\n\t\t\t\tconst dbId = entryDatabaseId(baseEntry);\n\t\t\t\tif (preview.id !== dbId && preview.id !== id) {\n\t\t\t\t\t// Token doesn't match — serve only if publicly visible, without draft access\n\t\t\t\t\tif (isVisible(baseEntry)) {\n\t\t\t\t\t\treturn successResult(wrapEntry(baseEntry), {\n\t\t\t\t\t\t\tisPreview: false,\n\t\t\t\t\t\t\tfallbackLocale,\n\t\t\t\t\t\t\tcacheHint: cacheHint ?? {},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\t// Not visible — try next locale in fallback chain\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check if entry has a draft revision — if so, re-fetch with revision data\n\t\t\tconst baseData = entryData(baseEntry);\n\t\t\tconst draftRevisionId = dataStr(baseData, \"draftRevisionId\") || undefined;\n\n\t\t\tif (draftRevisionId) {\n\t\t\t\tconst { entry: draftEntry, error: draftError } = await getLiveEntry(COLLECTION_NAME, {\n\t\t\t\t\ttype,\n\t\t\t\t\tid,\n\t\t\t\t\trevisionId: draftRevisionId,\n\t\t\t\t\tlocale,\n\t\t\t\t});\n\n\t\t\t\tif (!draftError && draftEntry) {\n\t\t\t\t\treturn successResult(wrapEntry(draftEntry), {\n\t\t\t\t\t\tisPreview: serveDrafts,\n\t\t\t\t\t\tfallbackLocale,\n\t\t\t\t\t\tcacheHint: cacheHint ?? {},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn successResult(wrapEntry(baseEntry), {\n\t\t\t\tisPreview: serveDrafts,\n\t\t\t\tfallbackLocale,\n\t\t\t\tcacheHint: cacheHint ?? {},\n\t\t\t});\n\t\t}\n\n\t\t// No entry found in any locale\n\t\treturn { entry: null, isPreview: serveDrafts, cacheHint: {} };\n\t}\n\n\t// Normal mode: try each locale in the fallback chain, only return published\n\t// content. The full resolution (fallback chain + visibility + byline/term\n\t// hydration) is wrapped in the distributed L2 cache, keyed by the requested\n\t// locale. Preview/edit requests took the `serveDrafts` branch above and\n\t// never reach here; the object cache additionally bypasses them.\n\n\tconst resolveNormal = async (): Promise<EntryResult<D>> => {\n\t\tfor (let i = 0; i < localeChain.length; i++) {\n\t\t\tconst locale = localeChain[i];\n\t\t\tconst fallbackLocale = i > 0 ? locale : undefined;\n\n\t\t\tconst { entry, error, cacheHint } = await getLiveEntry(COLLECTION_NAME, { type, id, locale });\n\t\t\tif (error) {\n\t\t\t\treturn { entry: null, error, isPreview: false, cacheHint: {} };\n\t\t\t}\n\n\t\t\tif (entry && isVisible(entry)) {\n\t\t\t\treturn successResult(wrapEntry(entry), {\n\t\t\t\t\tisPreview: false,\n\t\t\t\t\tfallbackLocale,\n\t\t\t\t\tcacheHint: cacheHint ?? {},\n\t\t\t\t});\n\t\t\t}\n\t\t\t// Entry not found or not visible in this locale — try next\n\t\t}\n\t\treturn { entry: null, isPreview: false, cacheHint: {} };\n\t};\n\n\tconst snapshot = await cachedQuery<ContentSnapshot<CachedEntryValue>>({\n\t\tnamespace: contentNamespaces(type),\n\t\tkey: `entry:${id}|loc=${requestedLocale ?? \"\"}`,\n\t\tload: async () => {\n\t\t\tconst result = await resolveNormal();\n\t\t\tif (result.error) {\n\t\t\t\treturn { ok: false, error: result.error, cacheHint: result.cacheHint };\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tvalue: {\n\t\t\t\t\tentry: result.entry ? entrySnapshot(result.entry) : null,\n\t\t\t\t\tisPreview: result.isPreview,\n\t\t\t\t\tfallbackLocale: result.fallbackLocale,\n\t\t\t\t\tcacheHint: result.cacheHint,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\tcacheable: (snap) => snap.ok,\n\t});\n\n\tif (!snapshot.ok) {\n\t\treturn { entry: null, error: snapshot.error, isPreview: false, cacheHint: snapshot.cacheHint };\n\t}\n\tconst revived = snapshot.value.entry ? reviveEntry<D>(snapshot.value.entry) : null;\n\tif (revived && !canExposeRevisionMetadata(revived, type)) stripRevisionMetadata(revived);\n\treturn {\n\t\tentry: revived,\n\t\tisPreview: snapshot.value.isPreview,\n\t\tfallbackLocale: snapshot.value.fallbackLocale,\n\t\tcacheHint: snapshot.value.cacheHint,\n\t};\n}\n\n/** Shape of a cached single-entry snapshot. */\ninterface CachedEntryValue {\n\tentry: Record<string, unknown> | null;\n\tisPreview: boolean;\n\tfallbackLocale?: string;\n\tcacheHint: CacheHint;\n}\n\n/**\n * Eagerly hydrate byline data onto entry.data for one or more entries.\n *\n * Attaches `bylines` (array of ContentBylineCredit) and `byline`\n * (primary BylineSummary or null) to each entry's data object.\n * Uses batch queries to avoid N+1.\n *\n * Fails silently if the byline tables don't exist yet (pre-migration).\n */\nasync function hydrateEntryBylines<D>(type: string, entries: ContentEntry<D>[]): Promise<void> {\n\tif (entries.length === 0) return;\n\n\t// Fast path: bylines were folded into the content query. Parse the JSON\n\t// (no extra round trip) for the common case — explicit credits, no byline\n\t// custom fields, no author fallback. The query path below handles the rest:\n\t//  - author fallback (entry has authorId but no explicit credit), and\n\t//  - custom byline fields (can't be expressed in the folded subquery).\n\tif (entries.every((e) => FOLDED_BYLINES in entryData(e))) {\n\t\tconst parsed = entries.map((entry) => {\n\t\t\tconst data = entryData(entry);\n\t\t\tconst folded = Reflect.get(data, FOLDED_BYLINES);\n\t\t\tconst rows = Array.isArray(folded) ? folded : [];\n\t\t\tconst credits = creditsFromFoldedBylines(rows);\n\t\t\treturn { data, credits };\n\t\t});\n\n\t\t// An empty `_emdash_bylines` table makes an empty fold authoritative: no\n\t\t// credit can exist in any locale and the author fallback has no byline\n\t\t// to resolve to, so skip the query path and the custom-fields probe\n\t\t// entirely. A missing marker (older cached rows) means \"unknown\" and\n\t\t// keeps the conservative fallback below.\n\t\tconst knownEmpty = entries.every(\n\t\t\t(e) => Reflect.get(entryData(e), FOLDED_BYLINES_EXIST) === false,\n\t\t);\n\n\t\t// Fall back to the full query path when the fold can't be trusted to be\n\t\t// complete: an entry with a byline reference (explicit primary, or an\n\t\t// author for the author-fallback) but no folded credits — e.g. a credit\n\t\t// in a different locale than the row, which the locale-correlated subquery\n\t\t// skips, or the author-fallback path which the fold doesn't express.\n\t\tlet needsQueryPath =\n\t\t\t!knownEmpty &&\n\t\t\tparsed.some(\n\t\t\t\t(p) =>\n\t\t\t\t\tp.credits.length === 0 &&\n\t\t\t\t\t(dataStr(p.data, \"authorId\") !== \"\" || dataStr(p.data, \"primaryBylineId\") !== \"\"),\n\t\t\t);\n\t\tlet hasCustomFields = false;\n\t\tif (!needsQueryPath && !knownEmpty) {\n\t\t\ttry {\n\t\t\t\tconst { getDb } = await import(\"./loader.js\");\n\t\t\t\tconst db = await getDb();\n\t\t\t\tconst { getBylineFieldDefs } = await import(\"./bylines/field-defs-cache.js\");\n\t\t\t\thasCustomFields = (await getBylineFieldDefs(db)).length > 0;\n\t\t\t} catch (error) {\n\t\t\t\t// A missing table is expected pre-migration and means there are no\n\t\t\t\t// custom fields — the fold's values are complete. Any other error\n\t\t\t\t// (lock-init failure, dialect error) means the probe can't be\n\t\t\t\t// trusted, so fall back to the query path rather than risk serving\n\t\t\t\t// folded bylines with empty customFields.\n\t\t\t\tif (!isMissingTableError(error)) needsQueryPath = true;\n\t\t\t}\n\t\t}\n\n\t\tif (!needsQueryPath && !hasCustomFields) {\n\t\t\tfor (const p of parsed) {\n\t\t\t\tp.data.bylines = p.credits;\n\t\t\t\tp.data.byline = p.credits[0]?.byline ?? null;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Fall through to the full query path for fallback / custom-field cases.\n\t}\n\n\ttry {\n\t\tconst { getBylinesForEntries } = await import(\"./bylines/index.js\");\n\n\t\tconst refs = entries\n\t\t\t.map((e) => {\n\t\t\t\tconst data = entryData(e);\n\t\t\t\tconst id = dataStr(data, \"id\");\n\t\t\t\tif (!id) return null;\n\t\t\t\treturn {\n\t\t\t\t\tid,\n\t\t\t\t\tauthorId: dataStr(data, \"authorId\") || null,\n\t\t\t\t\tprimaryBylineId: dataStr(data, \"primaryBylineId\") || null,\n\t\t\t\t\tlocale: dataStr(data, \"locale\") || null,\n\t\t\t\t};\n\t\t\t})\n\t\t\t.filter(\n\t\t\t\t(\n\t\t\t\t\tr,\n\t\t\t\t): r is {\n\t\t\t\t\tid: string;\n\t\t\t\t\tauthorId: string | null;\n\t\t\t\t\tprimaryBylineId: string | null;\n\t\t\t\t\tlocale: string | null;\n\t\t\t\t} => r !== null,\n\t\t\t);\n\t\tif (refs.length === 0) return;\n\n\t\tconst bylinesMap = await getBylinesForEntries(type, refs);\n\n\t\tfor (const entry of entries) {\n\t\t\tconst data = entryData(entry);\n\t\t\tconst dbId = dataStr(data, \"id\");\n\t\t\tif (!dbId) continue;\n\n\t\t\tconst credits = bylinesMap.get(dbId) ?? [];\n\t\t\tdata.bylines = credits;\n\t\t\tdata.byline = credits[0]?.byline ?? null;\n\t\t}\n\t} catch (err) {\n\t\t// Only swallow \"table not found\" errors from pre-migration databases.\n\t\t// Matches SQLite/D1 (\"no such table\") and PostgreSQL (\"relation/table\n\t\t// ... does not exist\") via the shared helper.\n\t\tif (!isMissingTableError(err)) {\n\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\tconsole.warn(\"[emdash] Failed to hydrate bylines:\", msg);\n\t\t}\n\t}\n}\n\n/**\n * Eagerly hydrate taxonomy term data onto entry.data for one or more entries.\n *\n * Attaches `terms` (Record keyed by taxonomy name with an array of TaxonomyTerm\n * values) to each entry's data object. Uses a single batched JOIN query across\n * all taxonomies so the cost is O(1) regardless of the number of entries or\n * taxonomies on the site.\n *\n * This eliminates the common N+1 pattern where templates loop over list\n * results and call getEntryTerms() per entry. With hydration, the list page\n * stays at a single round-trip for term data.\n *\n * `locale` is the preferred locale the entries were resolved to. Each assigned\n * group resolves that variant first, then the configured default. When it is\n * omitted, the stored locale of each content row is preferred.\n *\n * Fails silently if the taxonomy tables don't exist yet (pre-migration).\n */\nasync function hydrateEntryTerms<D>(\n\ttype: string,\n\tentries: ContentEntry<D>[],\n\tlocale?: string,\n): Promise<void> {\n\tif (entries.length === 0) return;\n\n\t// Fast path: terms were folded into the content query. Group the JSON and\n\t// skip the separate content_taxonomies query.\n\tif (entries.every((e) => FOLDED_TERMS in entryData(e))) {\n\t\tconst perEntry: Array<{ entryId: string; byTaxonomy: Record<string, TaxonomyTerm[]> }> = [];\n\t\tfor (const entry of entries) {\n\t\t\tconst data = entryData(entry);\n\t\t\tconst folded = Reflect.get(data, FOLDED_TERMS);\n\t\t\tconst rows = Array.isArray(folded) ? folded : [];\n\t\t\tconst grouped: Record<string, TaxonomyTerm[]> = {};\n\t\t\tfor (const r of rows) {\n\t\t\t\tconst name = String(r?.name);\n\t\t\t\t(grouped[name] ??= []).push({\n\t\t\t\t\tid: r?.id,\n\t\t\t\t\tname,\n\t\t\t\t\tslug: r?.slug,\n\t\t\t\t\tlabel: r?.label,\n\t\t\t\t\tparentId: r?.parent_id ?? undefined,\n\t\t\t\t\tchildren: [],\n\t\t\t\t\tlocale: r?.locale,\n\t\t\t\t\ttranslationGroup: r?.translation_group,\n\t\t\t\t});\n\t\t\t}\n\t\t\t// Match getAllTermsForEntries' ORDER BY label (dropped from the\n\t\t\t// aggregate since SQLite and Postgres order it differently).\n\t\t\tfor (const [name, arr] of Object.entries(grouped)) {\n\t\t\t\tgrouped[name] = arr.toSorted((a, b) => String(a.label).localeCompare(String(b.label)));\n\t\t\t}\n\t\t\tdata.terms = grouped;\n\t\t\tconst entryId = dataStr(data, \"id\");\n\t\t\tif (entryId) perEntry.push({ entryId, byTaxonomy: grouped });\n\t\t}\n\t\t// Prime the per-entry request cache (wildcard + present taxonomies) so\n\t\t// subsequent getEntryTerms(...) calls in this render hit the cache instead\n\t\t// of issuing an N+1 query. No DB lookup — purely from the folded data.\n\t\tconst { primeFoldedEntryTerms } = await import(\"./taxonomies/index.js\");\n\t\tprimeFoldedEntryTerms(type, perEntry, { locale });\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst { getAllTermsForEntries } = await import(\"./taxonomies/index.js\");\n\n\t\tconst ids = entries.map((e) => dataStr(entryData(e), \"id\")).filter(Boolean);\n\t\tif (ids.length === 0) return;\n\n\t\tconst termsMap = await getAllTermsForEntries(type, ids, { locale });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst data = entryData(entry);\n\t\t\tconst dbId = dataStr(data, \"id\");\n\t\t\tif (!dbId) continue;\n\n\t\t\tdata.terms = termsMap.get(dbId) ?? {};\n\t\t}\n\t} catch (err) {\n\t\t// Only swallow \"table not found\" errors from pre-migration databases.\n\t\t// Matches SQLite/D1 (\"no such table\") and PostgreSQL (\"relation/table\n\t\t// ... does not exist\") via the shared helper.\n\t\tif (!isMissingTableError(err)) {\n\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\tconsole.warn(\"[emdash] Failed to hydrate terms:\", msg);\n\t\t}\n\t}\n}\n\n/**\n * Translation summary for a single locale variant\n */\nexport interface TranslationSummary {\n\t/** Content item ID */\n\tid: string;\n\t/** Locale code (e.g. \"en\", \"fr\") */\n\tlocale: string;\n\t/** URL slug */\n\tslug: string | null;\n\t/** Current status */\n\tstatus: string;\n}\n\n/**\n * Result from getTranslations\n */\nexport interface TranslationsResult {\n\t/** The translation group ID (shared across locales) */\n\ttranslationGroup: string;\n\t/** All locale variants in this group */\n\ttranslations: TranslationSummary[];\n\t/** Error if the query failed */\n\terror?: Error;\n}\n\n/**\n * Get all translations of a content item.\n *\n * Given a content entry, returns all locale variants that share the same\n * translation group. This is useful for building language switcher UI.\n *\n * @example\n * ```ts\n * import { getEmDashEntry, getTranslations } from \"@premium-cms/emdash\";\n *\n * const { entry: post } = await getEmDashEntry(\"posts\", \"hello-world\", { locale: \"en\" });\n * const { translations } = await getTranslations(\"posts\", post.data.id);\n * // translations = [{ id: \"...\", locale: \"en\", slug: \"hello-world\", status: \"published\" }, ...]\n * ```\n */\nexport async function getTranslations(type: string, id: string): Promise<TranslationsResult> {\n\ttry {\n\t\tconst db = (await import(\"./loader.js\")).getDb;\n\t\tconst dbInstance = await db();\n\t\tconst { ContentRepository } = await import(\"./database/repositories/content.js\");\n\t\tconst repo = new ContentRepository(dbInstance);\n\n\t\t// Find the item to get its translation group\n\t\tconst item = await repo.findByIdOrSlug(type, id);\n\t\tif (!item) {\n\t\t\treturn {\n\t\t\t\ttranslationGroup: \"\",\n\t\t\t\ttranslations: [],\n\t\t\t\terror: new Error(`Content item not found: ${id}`),\n\t\t\t};\n\t\t}\n\n\t\tconst group = item.translationGroup || item.id;\n\t\tconst translations = await repo.findTranslations(type, group);\n\n\t\treturn {\n\t\t\ttranslationGroup: group,\n\t\t\ttranslations: translations.map((t) => ({\n\t\t\t\tid: t.id,\n\t\t\t\tlocale: t.locale || \"en\",\n\t\t\t\tslug: t.slug,\n\t\t\t\tstatus: t.status,\n\t\t\t})),\n\t\t};\n\t} catch (error) {\n\t\treturn {\n\t\t\ttranslationGroup: \"\",\n\t\t\ttranslations: [],\n\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t};\n\t}\n}\n\n/**\n * Result from resolveEmDashPath\n */\nexport interface ResolvePathResult<T = Record<string, unknown>> {\n\t/** The matched entry */\n\tentry: ContentEntry<T>;\n\t/** The collection slug that matched */\n\tcollection: string;\n\t/** Extracted parameters from the URL pattern (e.g. { slug: \"my-post\" }) */\n\tparams: Record<string, string>;\n}\n\n/** Cached compiled URL patterns for resolveEmDashPath */\ninterface CachedPattern {\n\tslug: string;\n\tregex: RegExp;\n\tparamNames: string[];\n}\n\ninterface UrlPatternCache {\n\tpatterns: CachedPattern[] | null;\n}\n\nconst URL_PATTERN_CACHE_KEY = Symbol.for(\"emdash:url-pattern-cache\");\nconst queryGlobal = globalThis as Record<symbol, unknown>;\nconst urlPatternCache: UrlPatternCache =\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see request-context.ts)\n\t(queryGlobal[URL_PATTERN_CACHE_KEY] as UrlPatternCache | undefined) ??\n\t(() => {\n\t\tconst cache: UrlPatternCache = { patterns: null };\n\t\tqueryGlobal[URL_PATTERN_CACHE_KEY] = cache;\n\t\treturn cache;\n\t})();\n\n/**\n * Invalidate the cached URL patterns used by resolveEmDashPath.\n * Call when collection URL patterns change (schema updates).\n *\n * Also busts the distributed schema cache (collection metadata such as\n * `commentsEnabled`, `supports`, fields read by `getCollectionInfo`), since\n * every schema-mutation path already routes through here.\n */\nexport function invalidateUrlPatternCache(): void {\n\turlPatternCache.patterns = null;\n\tinvalidateSchemaObjectCache();\n}\n\n/**\n * Resolve a URL path to a content entry by matching against collection URL patterns.\n *\n * Loads all collections with a `urlPattern` set, converts each pattern to a regex,\n * and tests the given path. On match, extracts the slug and fetches the entry.\n *\n * @example\n * ```ts\n * import { resolveEmDashPath } from \"@premium-cms/emdash\";\n *\n * // Given pages with urlPattern \"/{slug}\" and posts with \"/blog/{slug}\":\n * const result = await resolveEmDashPath(\"/blog/hello-world\");\n * if (result) {\n *   console.log(result.collection); // \"posts\"\n *   console.log(result.params.slug); // \"hello-world\"\n *   console.log(result.entry.data); // post data\n * }\n * ```\n */\nexport async function resolveEmDashPath<T = Record<string, unknown>>(\n\tpath: string,\n): Promise<ResolvePathResult<T> | null> {\n\t// Build and cache compiled patterns on first call\n\tlet cachedUrlPatterns = urlPatternCache.patterns;\n\tif (!cachedUrlPatterns) {\n\t\tconst { getDb } = await import(\"./loader.js\");\n\t\tconst { SchemaRegistry } = await import(\"./schema/registry.js\");\n\t\tconst db = await getDb();\n\t\tconst registry = new SchemaRegistry(db);\n\t\tconst collections = await registry.listCollections();\n\n\t\tcachedUrlPatterns = [];\n\t\tfor (const collection of collections) {\n\t\t\tif (!collection.urlPattern) continue;\n\t\t\tconst { regex, paramNames } = compileUrlPattern(collection.urlPattern);\n\t\t\tcachedUrlPatterns.push({ slug: collection.slug, regex, paramNames });\n\t\t}\n\t\turlPatternCache.patterns = cachedUrlPatterns;\n\t}\n\n\tfor (const pattern of cachedUrlPatterns) {\n\t\tconst match = path.match(pattern.regex);\n\t\tif (!match) continue;\n\n\t\t// Extract params\n\t\tconst params: Record<string, string> = {};\n\t\tfor (let i = 0; i < pattern.paramNames.length; i++) {\n\t\t\tparams[pattern.paramNames[i]] = match[i + 1];\n\t\t}\n\n\t\t// Look up entry by slug (most common pattern)\n\t\tconst slug = params.slug;\n\t\tif (!slug) continue;\n\n\t\tconst { entry } = await getEmDashEntry<string, T>(pattern.slug, slug);\n\t\tif (entry) {\n\t\t\treturn { entry, collection: pattern.slug, params };\n\t\t}\n\t}\n\n\treturn null;\n}\n"],"mappings":"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"}