import { a as __exportAll } from "./runner-K8XK101B.mjs";
import { n as getI18nConfig, r as isI18nEnabled, t as getFallbackChain } from "./config-CVssduLe.mjs";
import { getRequestContext } from "./request-context.mjs";
import { i as contentNamespaces, n as cachedQuery, u as invalidateSchemaObjectCache } from "./object-cache-Bok5j2ae.mjs";
import { a as encodeCursor } from "./types-CuW0zWDA.mjs";
import { r as requestCached } from "./request-cache-KCNHp_RJ.mjs";
import { n as isMissingTableError } from "./db-errors-CK46D-ly.mjs";
import { n as FOLDED_BYLINES, r as FOLDED_TERMS, t as CURSOR_RAW_VALUES } from "./loader-CB6YHeul.mjs";

//#region src/visual-editing/editable.ts
/**
* Create an editable proxy for an entry.
*
* Usage:
* - `{...entry.edit}` - entry-level annotation (includes status/hasDraft)
* - `{...entry.edit.title}` - field-level annotation
* - `{...entry.edit['nested.field']}` - nested field (bracket notation)
*/
function createEditable(collection, id, options) {
	const base = {
		collection,
		id,
		...options?.status && { status: options.status },
		...options?.hasDraft && { hasDraft: true }
	};
	return new Proxy({}, {
		get(_, prop) {
			if (prop === "toJSON") return () => ({ "data-emdash-ref": JSON.stringify(base) });
			if (typeof prop === "symbol") return void 0;
			if (prop === "data-emdash-ref") return JSON.stringify(base);
			return { "data-emdash-ref": JSON.stringify({
				...base,
				field: String(prop)
			}) };
		},
		ownKeys() {
			return ["data-emdash-ref"];
		},
		getOwnPropertyDescriptor(_, prop) {
			if (prop === "data-emdash-ref") return {
				configurable: true,
				enumerable: true,
				value: JSON.stringify(base)
			};
		}
	});
}
/**
* Create a noop proxy for production mode.
* Spreading this produces no attributes.
*/
function createNoop() {
	return new Proxy({}, {
		get(_, prop) {
			if (typeof prop === "symbol") return void 0;
		},
		ownKeys() {
			return [];
		},
		getOwnPropertyDescriptor() {}
	});
}

//#endregion
//#region src/query.ts
/**
* Query functions for EmDash content
*
* These wrap Astro's getLiveCollection/getLiveEntry with type filtering.
* Use these instead of calling Astro's functions directly.
*
* Error handling follows Astro's pattern - returns { entries/entry, error }
* so callers can gracefully handle errors (including 404s).
*
* Preview mode is handled implicitly via ALS request context —
* no parameters needed. The middleware verifies the preview token
* and sets the context; query functions read it automatically.
*
* The triple-slash directive above pulls in the ambient declaration for
* `astro:content` (used by the dynamic imports below) so this source
* file typechecks even when reached transitively by a sibling package
* whose tsconfig doesn't list `astro/client` in `compilerOptions.types`.
*
* Note: the directive is stripped from the compiled output (`dist/*`)
* by tsdown, so it does not propagate to downstream consumers of the
* published package. Consumers are Astro sites and already provide their
* own `astro/client` ambient surface anyway, so the runtime dynamic
* import resolves there at typecheck time without our help.
*/
var query_exports = /* @__PURE__ */ __exportAll({
	bucketFilter: () => bucketFilter,
	getEditMeta: () => getEditMeta,
	getEmDashCollection: () => getEmDashCollection,
	getEmDashEntry: () => getEmDashEntry,
	getTranslations: () => getTranslations,
	invalidateUrlPatternCache: () => invalidateUrlPatternCache,
	resolveEmDashPath: () => resolveEmDashPath,
	sliceCollectionResult: () => sliceCollectionResult
});
const COLLECTION_NAME = "_emdash";
/** Symbol key for edit metadata on PT arrays — avoids collision with user data */
const EMDASH_EDIT = Symbol.for("__emdash");
/** Type guard for EditFieldMeta */
function isEditFieldMeta(value) {
	if (typeof value !== "object" || value === null) return false;
	if (!("collection" in value) || !("id" in value) || !("field" in value)) return false;
	const { collection, id, field } = value;
	return typeof collection === "string" && typeof id === "string" && typeof field === "string";
}
/**
* Read edit metadata from a value (returns undefined if not tagged).
* Uses Object.getOwnPropertyDescriptor to access Symbol-keyed property
* without an unsafe type assertion.
*/
function getEditMeta(value) {
	if (value && typeof value === "object") {
		const meta = Object.getOwnPropertyDescriptor(value, EMDASH_EDIT)?.value;
		if (isEditFieldMeta(meta)) return meta;
	}
}
/**
* Tag PT-like arrays in entry data with edit metadata (non-enumerable).
* A PT array is identified by: is an array, first element has _type property.
*/
function tagEditableFields(data, collection, id) {
	for (const [field, value] of Object.entries(data)) if (Array.isArray(value) && value.length > 0 && value[0] && typeof value[0] === "object" && "_type" in value[0]) Object.defineProperty(value, EMDASH_EDIT, {
		value: {
			collection,
			id,
			field
		},
		enumerable: false,
		configurable: true
	});
}
/** Safely read a string field from a Record, with optional fallback */
function dataStr(data, key, fallback = "") {
	const val = data[key];
	return typeof val === "string" ? val : fallback;
}
/** Safely read a date-like field from a Record */
function dataDate(data, key) {
	const val = data[key];
	if (val instanceof Date) return Number.isNaN(val.getTime()) ? void 0 : val;
	if (typeof val !== "string" && typeof val !== "number") return void 0;
	const date = new Date(val);
	return Number.isNaN(date.getTime()) ? void 0 : date;
}
/** Type guard for Record<string, unknown> */
function isRecord(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
/** Extract data as Record from an Astro entry (which is any-typed) */
function entryData(entry) {
	return isRecord(entry.data) ? entry.data : {};
}
/** Extract the database ID from entry data (data.id is the ULID, entry.id is the slug) */
function entryDatabaseId(entry) {
	return dataStr(entryData(entry), "id") || entry.id;
}
/** Extract edit options from entry data for the proxy */
function entryEditOptions(entry) {
	const data = entryData(entry);
	const status = dataStr(data, "status", "draft");
	const draftRevisionId = dataStr(data, "draftRevisionId") || void 0;
	const liveRevisionId = dataStr(data, "liveRevisionId") || void 0;
	return {
		status,
		hasDraft: !!draftRevisionId && draftRevisionId !== liveRevisionId
	};
}
/**
* Get all entries of a content type
*
* Returns { entries, error } for graceful error handling.
*
* When emdash-env.d.ts is generated, the collection name will be
* type-checked and the return type will be inferred automatically.
*
* @example
* ```ts
* import { getEmDashCollection } from "emdash";
*
* const { entries: posts, error } = await getEmDashCollection("posts");
* if (error) {
*   console.error("Failed to load posts:", error);
*   return;
* }
* // posts[0].data.title is typed (if emdash-env.d.ts exists)
*
* // With filters
* const { entries: drafts } = await getEmDashCollection("posts", { status: "draft" });
* ```
*/
async function getEmDashCollection(type, filter) {
	const bucketed = bucketFilter(filter);
	const cached = await requestCached(collectionCacheKey(type, bucketed.fetchFilter), () => loadCollectionCached(type, bucketed.fetchFilter));
	return bucketed.requestedLimit === void 0 ? cached : sliceCollectionResult(cached, bucketed.requestedLimit, filter?.orderBy);
}
/**
* Distributed (L2) read-through around {@link getEmDashCollectionUncached}.
*
* Caches a JSON-safe snapshot keyed by collection + filter + effective locale,
* folding the shared `bylines`/`taxonomies` epochs into the key so renaming an
* author or term invalidates affected lists. Errors are never cached.
*/
async function loadCollectionCached(type, filter) {
	const snapshot = await cachedQuery({
		namespace: contentNamespaces(type),
		key: `collection:${collectionCacheKey(type, filter)}|loc=${effectiveLocaleKey(filter)}`,
		load: async () => {
			const result = await getEmDashCollectionUncached(type, filter);
			if (result.error) return {
				ok: false,
				error: result.error,
				cacheHint: result.cacheHint
			};
			return {
				ok: true,
				value: {
					entries: result.entries.map(entrySnapshot),
					nextCursor: result.nextCursor,
					hasMore: result.hasMore,
					cacheHint: result.cacheHint
				}
			};
		},
		cacheable: (snap) => snap.ok
	});
	if (!snapshot.ok) return {
		entries: [],
		error: snapshot.error,
		cacheHint: snapshot.cacheHint
	};
	return {
		entries: snapshot.value.entries.map((entry) => reviveEntry(entry)),
		nextCursor: snapshot.value.nextCursor,
		hasMore: snapshot.value.hasMore,
		cacheHint: snapshot.value.cacheHint
	};
}
/**
* Threshold for limit bucketing. Page templates routinely render small
* "recent posts" widgets at limits 3-8; rounding those up to a single
* shared bucket lets one fetch satisfy several widgets within a request.
* Above this, the requested limit is honoured exactly — bucketing limit:50
* to limit:64 would waste hydration work for callers fetching real pages.
*/
const BUCKET_LIMIT_THRESHOLD = 10;
/** @internal exported for unit tests; not part of the public API. */
function bucketFilter(filter) {
	const limit = filter?.limit;
	if (limit === void 0 || limit >= BUCKET_LIMIT_THRESHOLD || limit <= 0 || filter?.cursor !== void 0 || filter?.offset !== void 0) return {
		fetchFilter: filter,
		requestedLimit: void 0
	};
	return {
		fetchFilter: {
			...filter,
			limit: BUCKET_LIMIT_THRESHOLD
		},
		requestedLimit: limit
	};
}
/**
* Slice a cached bucketed result down to the originally-requested limit
* and recompute `nextCursor` from the row that would have been the
* over-fetch detector for that limit. When truncation is needed, returns
* a shallow-copied result with a new `entries` array; otherwise returns
* the cached result unchanged (including error results and results
* already within the requested limit).
*/
/** @internal exported for unit tests; not part of the public API. */
function sliceCollectionResult(cached, limit, orderBy) {
	if (cached.error) return cached;
	if (cached.entries.length <= limit) return cached;
	const sliced = cached.entries.slice(0, limit);
	const lastEntry = sliced.at(-1);
	const nextCursor = lastEntry ? encodeEntryCursor(lastEntry, orderBy) : void 0;
	return {
		...cached,
		entries: sliced,
		nextCursor,
		hasMore: true
	};
}
/** Map of database column names to camelCase keys present on entry.data. */
const ENTRY_DATA_KEY_MAP = {
	created_at: "createdAt",
	updated_at: "updatedAt",
	published_at: "publishedAt",
	scheduled_at: "scheduledAt",
	author_id: "authorId",
	primary_byline_id: "primaryBylineId"
};
const FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
/**
* Encode a `nextCursor` from a content entry, mirroring the loader's
* encoding scheme: `(orderValue, id)` where `orderValue` is the primary
* sort field's stringified value. For date columns, reads the raw DB
* string the loader stashed via CURSOR_RAW_VALUES — round-tripping the
* parsed Date through `toISOString()` would lose precision for stored
* values that aren't already ISO-with-milliseconds.
*/
function encodeEntryCursor(entry, orderBy) {
	const data = entryData(entry);
	const id = dataStr(data, "id");
	if (!id) return void 0;
	let dbField = "created_at";
	if (orderBy) {
		for (const field of Object.keys(orderBy)) if (FIELD_NAME_PATTERN.test(field)) {
			dbField = field;
			break;
		}
	}
	const rawDateValuesRaw = Reflect.get(data, CURSOR_RAW_VALUES);
	if (rawDateValuesRaw !== null && typeof rawDateValuesRaw === "object") {
		const raw = Reflect.get(rawDateValuesRaw, dbField);
		if (typeof raw === "string") return encodeCursor(raw, id);
	}
	const value = data[ENTRY_DATA_KEY_MAP[dbField] ?? dbField];
	let orderValue;
	if (value instanceof Date) orderValue = value.toISOString();
	else if (typeof value === "string" || typeof value === "number") orderValue = String(value);
	else orderValue = "";
	return encodeCursor(orderValue, id);
}
/**
* Build a canonical cache key for `getEmDashCollection`.
*
* `JSON.stringify` is insertion-order-sensitive, so two callers passing
* semantically identical filters with different key orders would miss
* the cache. We fix the top-level field order and sort `where` keys
* (order there is irrelevant), while preserving `orderBy` key order
* because that's the sort priority.
*/
function collectionCacheKey(type, filter) {
	if (!filter) return `collection:${type}:`;
	return `collection:${type}:${[
		filter.status ?? "",
		filter.limit ?? "",
		filter.cursor ?? "",
		filter.offset ?? "",
		filter.where ? stableStringify(filter.where) : "",
		filter.orderBy ? JSON.stringify(filter.orderBy) : "",
		filter.locale ?? ""
	].join("|")}`;
}
function stableStringify(value) {
	return JSON.stringify(stableOrder(value));
}
function stableOrder(value) {
	const keys = Object.keys(value).toSorted();
	const ordered = {};
	for (const k of keys) {
		const v = value[k];
		if (isRecord(v)) ordered[k] = stableOrder(v);
		else ordered[k] = v;
	}
	return ordered;
}
/** Enumerable field carrying the {@link CURSOR_RAW_VALUES} payload in snapshots. */
const CURSOR_RAW_FIELD = "__emdashCursorRaw";
function entrySnapshot(entry) {
	const data = entryData(entry);
	const rawCursor = Reflect.get(data, CURSOR_RAW_VALUES);
	const { edit: _edit, ...rest } = entry;
	return {
		...rest,
		data: {
			...data,
			[CURSOR_RAW_FIELD]: rawCursor ?? {}
		}
	};
}
function reviveEntry(raw) {
	const entry = raw;
	const data = { ...entry.data };
	const rawCursor = data[CURSOR_RAW_FIELD] ?? {};
	delete data[CURSOR_RAW_FIELD];
	Object.defineProperty(data, CURSOR_RAW_VALUES, {
		value: rawCursor,
		enumerable: false,
		configurable: false,
		writable: false
	});
	return {
		...entry,
		data,
		edit: createNoop()
	};
}
/** Resolve the effective locale used by content reads, for the L2 cache key. */
function effectiveLocaleKey(filter) {
	const ctx = getRequestContext();
	const i18nConfig = getI18nConfig();
	return filter?.locale ?? ctx?.locale ?? (isI18nEnabled() ? i18nConfig.defaultLocale : void 0) ?? "";
}
async function getEmDashCollectionUncached(type, filter) {
	const { getLiveCollection } = await import("astro:content");
	const ctx = getRequestContext();
	const i18nConfig = getI18nConfig();
	const resolvedLocale = filter?.locale ?? ctx?.locale ?? (isI18nEnabled() ? i18nConfig.defaultLocale : void 0);
	const requestedLimit = filter?.limit;
	const pageParam = filter?.cursor !== void 0 ? { cursor: filter.cursor } : filter?.offset !== void 0 ? { offset: filter.offset } : {};
	const { entries, error, cacheHint } = await getLiveCollection(COLLECTION_NAME, {
		type,
		status: filter?.status,
		limit: requestedLimit && requestedLimit > 0 ? requestedLimit + 1 : filter?.limit,
		...pageParam,
		where: filter?.where,
		orderBy: filter?.orderBy,
		locale: resolvedLocale
	});
	if (error) return {
		entries: [],
		error,
		cacheHint: {}
	};
	const hasMore = requestedLimit != null && requestedLimit > 0 && entries.length > requestedLimit;
	const pageEntries = hasMore ? entries.slice(0, requestedLimit) : entries;
	const nextCursor = hasMore ? encodeEntryCursor(pageEntries.at(-1), filter?.orderBy) : void 0;
	const hasMoreResult = requestedLimit != null && requestedLimit > 0 ? hasMore : void 0;
	const isEditMode = ctx?.editMode ?? false;
	const entriesWithEdit = pageEntries.map((entry) => {
		const dbId = entryDatabaseId(entry);
		if (isEditMode) tagEditableFields(entryData(entry), type, dbId);
		return {
			...entry,
			edit: isEditMode ? createEditable(type, dbId, entryEditOptions(entry)) : createNoop()
		};
	});
	await Promise.all([hydrateEntryBylines(type, entriesWithEdit), hydrateEntryTerms(type, entriesWithEdit, resolvedLocale)]);
	return {
		entries: entriesWithEdit,
		nextCursor,
		hasMore: hasMoreResult,
		cacheHint: cacheHint ?? {}
	};
}
/**
* Get a single entry by type and ID/slug
*
* Returns { entry, error, isPreview } for graceful error handling.
* - entry is null if not found (not an error)
* - error is set only for actual errors (db issues, etc.)
*
* Preview mode is detected automatically from request context (ALS).
* When the URL has a valid `_preview` token, the middleware sets preview
* context and this function serves draft revision data if available.
*
* @example
* ```ts
* import { getEmDashEntry } from "emdash";
*
* // Simple usage — preview just works via middleware
* const { entry: post, isPreview, error } = await getEmDashEntry("posts", "my-slug");
* if (!post) return Astro.redirect("/404");
* ```
*/
async function getEmDashEntry(type, id, options) {
	const { getLiveEntry } = await import("astro:content");
	const ctx = getRequestContext();
	const preview = ctx?.preview;
	const isEditMode = ctx?.editMode ?? false;
	const isPreviewMode = !!preview && preview.collection === type;
	const serveDrafts = isPreviewMode || isEditMode;
	const requestedLocale = options?.locale ?? ctx?.locale;
	/** Wrap a raw Astro entry with edit proxy, tagging editable fields if needed */
	function wrapEntry(raw) {
		const dbId = entryDatabaseId(raw);
		if (isEditMode) tagEditableFields(entryData(raw), type, dbId);
		return {
			...raw,
			edit: isEditMode ? createEditable(type, dbId, entryEditOptions(raw)) : createNoop()
		};
	}
	/** Check if an entry is publicly visible (published or scheduled past its time) */
	function isVisible(entry) {
		const data = entryData(entry);
		const status = dataStr(data, "status");
		const scheduledAt = dataDate(data, "scheduledAt");
		const isPublished = status === "published";
		const isScheduledAndReady = status === "scheduled" && scheduledAt !== void 0 && scheduledAt.getTime() <= Date.now();
		return isPublished || !!isScheduledAndReady;
	}
	/** True when an entry is scheduled to become visible at a future time. */
	function isPendingScheduled(entry) {
		const data = entryData(entry);
		if (dataStr(data, "status") !== "scheduled") return false;
		const scheduledAt = dataDate(data, "scheduledAt");
		return scheduledAt !== void 0 && scheduledAt.getTime() > Date.now();
	}
	const localeChain = requestedLocale && isI18nEnabled() ? getFallbackChain(requestedLocale) : [requestedLocale];
	/** Return a successful EntryResult with bylines and taxonomy terms hydrated */
	async function successResult(wrapped, opts) {
		const termLocale = isI18nEnabled() ? dataStr(entryData(wrapped), "locale") || void 0 : void 0;
		await Promise.all([hydrateEntryBylines(type, [wrapped]), hydrateEntryTerms(type, [wrapped], termLocale)]);
		return {
			entry: wrapped,
			isPreview: opts.isPreview,
			fallbackLocale: opts.fallbackLocale,
			cacheHint: opts.cacheHint
		};
	}
	if (serveDrafts) {
		for (let i = 0; i < localeChain.length; i++) {
			const locale = localeChain[i];
			const fallbackLocale = i > 0 ? locale : void 0;
			const { entry: baseEntry, error: baseError, cacheHint } = await getLiveEntry(COLLECTION_NAME, {
				type,
				id,
				locale
			});
			if (baseError) return {
				entry: null,
				error: baseError,
				isPreview: serveDrafts,
				cacheHint: {}
			};
			if (!baseEntry) continue;
			if (isPreviewMode && !isEditMode) {
				const dbId = entryDatabaseId(baseEntry);
				if (preview.id !== dbId && preview.id !== id) {
					if (isVisible(baseEntry)) return successResult(wrapEntry(baseEntry), {
						isPreview: false,
						fallbackLocale,
						cacheHint: cacheHint ?? {}
					});
					continue;
				}
			}
			const draftRevisionId = dataStr(entryData(baseEntry), "draftRevisionId") || void 0;
			if (draftRevisionId) {
				const { entry: draftEntry, error: draftError } = await getLiveEntry(COLLECTION_NAME, {
					type,
					id,
					revisionId: draftRevisionId,
					locale
				});
				if (!draftError && draftEntry) return successResult(wrapEntry(draftEntry), {
					isPreview: serveDrafts,
					fallbackLocale,
					cacheHint: cacheHint ?? {}
				});
			}
			return successResult(wrapEntry(baseEntry), {
				isPreview: serveDrafts,
				fallbackLocale,
				cacheHint: cacheHint ?? {}
			});
		}
		return {
			entry: null,
			isPreview: serveDrafts,
			cacheHint: {}
		};
	}
	let timeSensitive = false;
	const resolveNormal = async () => {
		for (let i = 0; i < localeChain.length; i++) {
			const locale = localeChain[i];
			const fallbackLocale = i > 0 ? locale : void 0;
			const { entry, error, cacheHint } = await getLiveEntry(COLLECTION_NAME, {
				type,
				id,
				locale
			});
			if (error) return {
				entry: null,
				error,
				isPreview: false,
				cacheHint: {}
			};
			if (entry && isVisible(entry)) return successResult(wrapEntry(entry), {
				isPreview: false,
				fallbackLocale,
				cacheHint: cacheHint ?? {}
			});
			if (entry && isPendingScheduled(entry)) timeSensitive = true;
		}
		return {
			entry: null,
			isPreview: false,
			cacheHint: {}
		};
	};
	const snapshot = await cachedQuery({
		namespace: contentNamespaces(type),
		key: `entry:${id}|loc=${requestedLocale ?? ""}`,
		load: async () => {
			const result = await resolveNormal();
			if (result.error) return {
				ok: false,
				error: result.error,
				cacheHint: result.cacheHint
			};
			return {
				ok: true,
				value: {
					entry: result.entry ? entrySnapshot(result.entry) : null,
					isPreview: result.isPreview,
					fallbackLocale: result.fallbackLocale,
					cacheHint: result.cacheHint
				}
			};
		},
		cacheable: (snap) => snap.ok && !timeSensitive
	});
	if (!snapshot.ok) return {
		entry: null,
		error: snapshot.error,
		isPreview: false,
		cacheHint: snapshot.cacheHint
	};
	return {
		entry: snapshot.value.entry ? reviveEntry(snapshot.value.entry) : null,
		isPreview: snapshot.value.isPreview,
		fallbackLocale: snapshot.value.fallbackLocale,
		cacheHint: snapshot.value.cacheHint
	};
}
/**
* Eagerly hydrate byline data onto entry.data for one or more entries.
*
* Attaches `bylines` (array of ContentBylineCredit) and `byline`
* (primary BylineSummary or null) to each entry's data object.
* Uses batch queries to avoid N+1.
*
* Fails silently if the byline tables don't exist yet (pre-migration).
*/
async function hydrateEntryBylines(type, entries) {
	if (entries.length === 0) return;
	if (entries.every((e) => FOLDED_BYLINES in entryData(e))) {
		const parsed = entries.map((entry) => {
			const data = entryData(entry);
			const folded = Reflect.get(data, FOLDED_BYLINES);
			return {
				data,
				credits: (Array.isArray(folded) ? folded : []).map((raw) => {
					const b = raw?.byline ?? {};
					return {
						roleLabel: raw?.roleLabel ?? null,
						sortOrder: Number(raw?.sortOrder ?? 0),
						source: "explicit",
						byline: {
							...b,
							isGuest: Boolean(b.isGuest),
							customFields: {}
						}
					};
				}).toSorted((a, b) => a.sortOrder - b.sortOrder)
			};
		});
		let needsQueryPath = parsed.some((p) => p.credits.length === 0 && (dataStr(p.data, "authorId") !== "" || dataStr(p.data, "primaryBylineId") !== ""));
		let hasCustomFields = false;
		if (!needsQueryPath) try {
			const { getDb } = await import("./loader-CB6YHeul.mjs").then((n) => n.o);
			const db = await getDb();
			const { getBylineFieldDefs } = await import("./field-defs-cache-WUI88LX5.mjs").then((n) => n.t);
			hasCustomFields = (await getBylineFieldDefs(db)).length > 0;
		} catch (error) {
			if (!isMissingTableError(error)) needsQueryPath = true;
		}
		if (!needsQueryPath && !hasCustomFields) {
			for (const p of parsed) {
				p.data.bylines = p.credits;
				p.data.byline = p.credits[0]?.byline ?? null;
			}
			return;
		}
	}
	try {
		const { getBylinesForEntries } = await import("./bylines-Woq87129.mjs").then((n) => n.t);
		const refs = entries.map((e) => {
			const data = entryData(e);
			const id = dataStr(data, "id");
			if (!id) return null;
			return {
				id,
				authorId: dataStr(data, "authorId") || null,
				primaryBylineId: dataStr(data, "primaryBylineId") || null,
				locale: dataStr(data, "locale") || null
			};
		}).filter((r) => r !== null);
		if (refs.length === 0) return;
		const bylinesMap = await getBylinesForEntries(type, refs);
		for (const entry of entries) {
			const data = entryData(entry);
			const dbId = dataStr(data, "id");
			if (!dbId) continue;
			const credits = bylinesMap.get(dbId) ?? [];
			data.bylines = credits;
			data.byline = credits[0]?.byline ?? null;
		}
	} catch (err) {
		if (!isMissingTableError(err)) {
			const msg = err instanceof Error ? err.message : String(err);
			console.warn("[emdash] Failed to hydrate bylines:", msg);
		}
	}
}
/**
* Eagerly hydrate taxonomy term data onto entry.data for one or more entries.
*
* Attaches `terms` (Record keyed by taxonomy name with an array of TaxonomyTerm
* values) to each entry's data object. Uses a single batched JOIN query across
* all taxonomies so the cost is O(1) regardless of the number of entries or
* taxonomies on the site.
*
* This eliminates the common N+1 pattern where templates loop over list
* results and call getEntryTerms() per entry. With hydration, the list page
* stays at a single round-trip for term data.
*
* `locale` must be the locale the entries were resolved to. It is forwarded to
* `getAllTermsForEntries` so terms are returned in the entry's locale rather
* than falling back to the request-context / default locale (#1441). Pass
* `undefined` to keep the legacy "do not filter by locale" behaviour.
*
* Fails silently if the taxonomy tables don't exist yet (pre-migration).
*/
async function hydrateEntryTerms(type, entries, locale) {
	if (entries.length === 0) return;
	if (entries.every((e) => FOLDED_TERMS in entryData(e))) {
		const perEntry = [];
		for (const entry of entries) {
			const data = entryData(entry);
			const folded = Reflect.get(data, FOLDED_TERMS);
			const rows = Array.isArray(folded) ? folded : [];
			const grouped = {};
			for (const r of rows) {
				const name = String(r?.name);
				(grouped[name] ??= []).push({
					id: r?.id,
					name,
					slug: r?.slug,
					label: r?.label,
					parentId: r?.parent_id ?? void 0,
					children: [],
					locale: r?.locale,
					translationGroup: r?.translation_group
				});
			}
			for (const [name, arr] of Object.entries(grouped)) grouped[name] = arr.toSorted((a, b) => String(a.label).localeCompare(String(b.label)));
			data.terms = grouped;
			const entryId = dataStr(data, "id");
			if (entryId) perEntry.push({
				entryId,
				byTaxonomy: grouped
			});
		}
		const { primeFoldedEntryTerms } = await import("./taxonomies-Cnb4F-Pb.mjs").then((n) => n.d);
		primeFoldedEntryTerms(type, perEntry, { locale });
		return;
	}
	try {
		const { getAllTermsForEntries } = await import("./taxonomies-Cnb4F-Pb.mjs").then((n) => n.d);
		const ids = entries.map((e) => dataStr(entryData(e), "id")).filter(Boolean);
		if (ids.length === 0) return;
		const termsMap = await getAllTermsForEntries(type, ids, { locale });
		for (const entry of entries) {
			const data = entryData(entry);
			const dbId = dataStr(data, "id");
			if (!dbId) continue;
			data.terms = termsMap.get(dbId) ?? {};
		}
	} catch (err) {
		if (!isMissingTableError(err)) {
			const msg = err instanceof Error ? err.message : String(err);
			console.warn("[emdash] Failed to hydrate terms:", msg);
		}
	}
}
/**
* Get all translations of a content item.
*
* Given a content entry, returns all locale variants that share the same
* translation group. This is useful for building language switcher UI.
*
* @example
* ```ts
* import { getEmDashEntry, getTranslations } from "emdash";
*
* const { entry: post } = await getEmDashEntry("posts", "hello-world", { locale: "en" });
* const { translations } = await getTranslations("posts", post.data.id);
* // translations = [{ id: "...", locale: "en", slug: "hello-world", status: "published" }, ...]
* ```
*/
async function getTranslations(type, id) {
	try {
		const db = (await import("./loader-CB6YHeul.mjs").then((n) => n.o)).getDb;
		const dbInstance = await db();
		const { ContentRepository } = await import("./content-DhgEth4k.mjs").then((n) => n.n);
		const repo = new ContentRepository(dbInstance);
		const item = await repo.findByIdOrSlug(type, id);
		if (!item) return {
			translationGroup: "",
			translations: [],
			error: /* @__PURE__ */ new Error(`Content item not found: ${id}`)
		};
		const group = item.translationGroup || item.id;
		return {
			translationGroup: group,
			translations: (await repo.findTranslations(type, group)).map((t) => ({
				id: t.id,
				locale: t.locale || "en",
				slug: t.slug,
				status: t.status
			}))
		};
	} catch (error) {
		return {
			translationGroup: "",
			translations: [],
			error: error instanceof Error ? error : new Error(String(error))
		};
	}
}
/** Matches `{paramName}` placeholders in URL patterns */
const URL_PARAM_PATTERN = /\{(\w+)\}/g;
/** Convert a URL pattern like "/blog/{slug}" to a regex and param name list */
function patternToRegex(pattern) {
	const paramNames = [];
	const regexStr = pattern.replace(URL_PARAM_PATTERN, (_match, name) => {
		paramNames.push(name);
		return "([^/]+)";
	});
	return {
		regex: new RegExp(`^${regexStr}$`),
		paramNames
	};
}
let cachedUrlPatterns = null;
/**
* Invalidate the cached URL patterns used by resolveEmDashPath.
* Call when collection URL patterns change (schema updates).
*
* Also busts the distributed schema cache (collection metadata such as
* `commentsEnabled`, `supports`, fields read by `getCollectionInfo`), since
* every schema-mutation path already routes through here.
*/
function invalidateUrlPatternCache() {
	cachedUrlPatterns = null;
	invalidateSchemaObjectCache();
}
/**
* Resolve a URL path to a content entry by matching against collection URL patterns.
*
* Loads all collections with a `urlPattern` set, converts each pattern to a regex,
* and tests the given path. On match, extracts the slug and fetches the entry.
*
* @example
* ```ts
* import { resolveEmDashPath } from "emdash";
*
* // Given pages with urlPattern "/{slug}" and posts with "/blog/{slug}":
* const result = await resolveEmDashPath("/blog/hello-world");
* if (result) {
*   console.log(result.collection); // "posts"
*   console.log(result.params.slug); // "hello-world"
*   console.log(result.entry.data); // post data
* }
* ```
*/
async function resolveEmDashPath(path) {
	if (!cachedUrlPatterns) {
		const { getDb } = await import("./loader-CB6YHeul.mjs").then((n) => n.o);
		const { SchemaRegistry } = await import("./registry-B65mUtPa.mjs").then((n) => n.r);
		const collections = await new SchemaRegistry(await getDb()).listCollections();
		cachedUrlPatterns = [];
		for (const collection of collections) {
			if (!collection.urlPattern) continue;
			const { regex, paramNames } = patternToRegex(collection.urlPattern);
			cachedUrlPatterns.push({
				slug: collection.slug,
				regex,
				paramNames
			});
		}
	}
	for (const pattern of cachedUrlPatterns) {
		const match = path.match(pattern.regex);
		if (!match) continue;
		const params = {};
		for (let i = 0; i < pattern.paramNames.length; i++) params[pattern.paramNames[i]] = match[i + 1];
		const slug = params.slug;
		if (!slug) continue;
		const { entry } = await getEmDashEntry(pattern.slug, slug);
		if (entry) return {
			entry,
			collection: pattern.slug,
			params
		};
	}
	return null;
}

//#endregion
export { invalidateUrlPatternCache as a, createEditable as c, getTranslations as i, createNoop as l, getEmDashCollection as n, query_exports as o, getEmDashEntry as r, resolveEmDashPath as s, getEditMeta as t };
//# sourceMappingURL=query-ByGq6ver.mjs.map