@nexpress/core 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth.d.ts +2 -2
- package/dist/{chunk-UIQYA3Y7.js → chunk-2TOS5BYT.js} +2 -2
- package/dist/{chunk-HVHV3IHF.js → chunk-AOEN5EIX.js} +7 -4
- package/dist/chunk-AOEN5EIX.js.map +1 -0
- package/dist/{chunk-P5WGQRSG.js → chunk-EFZH6UPY.js} +5 -2
- package/dist/chunk-EFZH6UPY.js.map +1 -0
- package/dist/{chunk-HUESWYZJ.js → chunk-HYYMUVUK.js} +2 -2
- package/dist/{chunk-AEKO4MXK.js → chunk-LMPYQLMH.js} +17 -2
- package/dist/chunk-LMPYQLMH.js.map +1 -0
- package/dist/{chunk-6IEYOY2L.js → chunk-MXOHWU2P.js} +60 -17
- package/dist/chunk-MXOHWU2P.js.map +1 -0
- package/dist/{chunk-DKOCKZVG.js → chunk-WG3B2GNE.js} +6 -6
- package/dist/{chunk-56ZK5PWM.js → chunk-WOLMQGW7.js} +2 -2
- package/dist/community.d.ts +1 -1
- package/dist/community.js +4 -4
- package/dist/{config-44MFLLIX.js → config-CO6TXCA6.js} +5 -5
- package/dist/db-schema.d.ts +2 -2
- package/dist/db.d.ts +3 -3
- package/dist/db.js +1 -1
- package/dist/fields.d.ts +1 -1
- package/dist/fields.js +1 -1
- package/dist/{host-DKOWZWKA.js → host-HDRI7WIP.js} +3 -3
- package/dist/i18n.d.ts +1 -1
- package/dist/{index-C-jKU1St.d.ts → index-CPp-zGgL.d.ts} +2 -2
- package/dist/{index-BmR3Z8Y5.d.ts → index-DKoSaszT.d.ts} +1 -1
- package/dist/{index-Ca-WUDH5.d.ts → index-DymnczhP.d.ts} +1 -1
- package/dist/{index-lACZ9sON.d.ts → index-UZn29S3i.d.ts} +1 -1
- package/dist/index.d.ts +100 -12
- package/dist/index.js +14 -12
- package/dist/index.js.map +1 -1
- package/dist/jobs.d.ts +2 -2
- package/dist/jobs.js +1 -1
- package/dist/media.d.ts +2 -2
- package/dist/{scheduled-VEOGI5EW.js → scheduled-7MSJIJLE.js} +4 -4
- package/dist/seo.d.ts +16 -3
- package/dist/seo.js +3 -3
- package/dist/{types-BY1UmEiY.d.ts → types-D31ppGJw.d.ts} +45 -0
- package/package.json +1 -1
- package/dist/chunk-6IEYOY2L.js.map +0 -1
- package/dist/chunk-AEKO4MXK.js.map +0 -1
- package/dist/chunk-HVHV3IHF.js.map +0 -1
- package/dist/chunk-P5WGQRSG.js.map +0 -1
- /package/dist/{chunk-UIQYA3Y7.js.map → chunk-2TOS5BYT.js.map} +0 -0
- /package/dist/{chunk-HUESWYZJ.js.map → chunk-HYYMUVUK.js.map} +0 -0
- /package/dist/{chunk-DKOCKZVG.js.map → chunk-WG3B2GNE.js.map} +0 -0
- /package/dist/{chunk-56ZK5PWM.js.map → chunk-WOLMQGW7.js.map} +0 -0
- /package/dist/{config-44MFLLIX.js.map → config-CO6TXCA6.js.map} +0 -0
- /package/dist/{host-DKOWZWKA.js.map → host-HDRI7WIP.js.map} +0 -0
- /package/dist/{scheduled-VEOGI5EW.js.map → scheduled-7MSJIJLE.js.map} +0 -0
package/dist/auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { p as NpAccessFunction, m as NpUserRole, e as NpAuthUser } from './types-
|
|
2
|
-
export { H as NpPrincipal } from './types-
|
|
1
|
+
import { p as NpAccessFunction, m as NpUserRole, e as NpAuthUser } from './types-D31ppGJw.js';
|
|
2
|
+
export { H as NpPrincipal } from './types-D31ppGJw.js';
|
|
3
3
|
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|
4
4
|
import { Options } from '@node-rs/argon2';
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getPluginRegistration
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-MXOHWU2P.js";
|
|
4
4
|
import {
|
|
5
5
|
getCurrentSiteId
|
|
6
6
|
} from "./chunk-SBCVAC2Z.js";
|
|
@@ -319,4 +319,4 @@ export {
|
|
|
319
319
|
setPluginConfig,
|
|
320
320
|
pluginConfigCacheTag
|
|
321
321
|
};
|
|
322
|
-
//# sourceMappingURL=chunk-
|
|
322
|
+
//# sourceMappingURL=chunk-2TOS5BYT.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
findDocuments
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-MXOHWU2P.js";
|
|
4
4
|
import {
|
|
5
5
|
getI18nConfig
|
|
6
6
|
} from "./chunk-4ZLMEKFX.js";
|
|
@@ -232,9 +232,12 @@ function readString(v) {
|
|
|
232
232
|
async function buildPageMetadata(input = {}) {
|
|
233
233
|
const settings = await getSiteSeoSettings();
|
|
234
234
|
const path = normalizePath(input.path);
|
|
235
|
+
const canonicalPath = normalizePath(input.canonicalPath ?? input.path);
|
|
235
236
|
const titleText = input.title?.trim() ? `${input.title.trim()} \xB7 ${settings.siteName}` : settings.siteName;
|
|
236
237
|
const descriptionText = input.description?.trim() ?? settings.defaultDescription;
|
|
237
|
-
const
|
|
238
|
+
const siteOrigin = settings.siteUrl.replace(/\/+$/, "");
|
|
239
|
+
const canonicalUrl = `${siteOrigin}${canonicalPath}`;
|
|
240
|
+
const ogUrl = `${siteOrigin}${path}`;
|
|
238
241
|
const ogImage = resolveOgImage(input.ogImage, settings);
|
|
239
242
|
const ogType = input.ogType ?? "website";
|
|
240
243
|
const metadata = {
|
|
@@ -245,7 +248,7 @@ async function buildPageMetadata(input = {}) {
|
|
|
245
248
|
title: titleText,
|
|
246
249
|
description: descriptionText,
|
|
247
250
|
siteName: settings.siteName,
|
|
248
|
-
url:
|
|
251
|
+
url: ogUrl,
|
|
249
252
|
type: ogType,
|
|
250
253
|
// Page-supplied locale wins over the site default so
|
|
251
254
|
// translated copies surface their actual language to
|
|
@@ -594,4 +597,4 @@ export {
|
|
|
594
597
|
buildDiscussionForumPostingJsonLd,
|
|
595
598
|
buildPersonJsonLd
|
|
596
599
|
};
|
|
597
|
-
//# sourceMappingURL=chunk-
|
|
600
|
+
//# sourceMappingURL=chunk-AOEN5EIX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/seo/sitemap.ts","../src/seo/page-metadata.ts","../src/seo/feed.ts","../src/seo/json-ld.ts"],"sourcesContent":["import { getAllCollectionSlugs, getCollectionConfig } from \"../collections/registry.js\";\nimport { findDocuments } from \"../collections/pipeline.js\";\nimport { getI18nConfig } from \"../i18n/registry.js\";\n\n/**\n * Phase 10.1 — sitemap entry shape. Mirrors the sitemap.org spec\n * fields the framework cares about. Apps format these into XML\n * (the reference app does that in `apps/web/src/app/sitemap.xml/\n * route.ts`); the core helper stays format-agnostic so a future\n * news-sitemap or video-sitemap variant can reuse the same\n * collection walk.\n */\nexport interface NpSitemapEntry {\n /** Path-only — host is prepended by the consumer. Always starts with `/`. */\n loc: string;\n /** ISO timestamp for `<lastmod>`. Falls back to `updatedAt` then `createdAt`. */\n lastmod?: string;\n changefreq?:\n | \"always\"\n | \"hourly\"\n | \"daily\"\n | \"weekly\"\n | \"monthly\"\n | \"yearly\"\n | \"never\";\n priority?: number;\n /**\n * Phase 12.2 — hreflang alternates for translated content.\n * The renderer emits an `<xhtml:link rel=\"alternate\" hreflang=\"...\" href=\"...\"/>`\n * entry per alternate, and the urlset gets the xhtml namespace\n * declaration when any entry uses alternates.\n */\n alternates?: Array<{ hreflang: string; href: string }>;\n}\n\nexport interface BuildSitemapOptions {\n /**\n * Cap per-collection at this many rows so a 100K-document blog\n * doesn't bring the sitemap.xml endpoint to its knees. Default\n * 5000 is the sitemaps.org recommended max per file. Sites with\n * more rows than that per locale should pair this with the\n * sitemap-index split (see `locale` below) so each child file\n * stays under the cap.\n */\n perCollectionLimit?: number;\n /** Restrict to specific collection slugs (default: all). */\n collections?: string[];\n /**\n * Restrict to a single locale. When set:\n * - i18n collections filter rows to `locale = $locale` (so\n * each per-locale sitemap only enumerates its own URLs).\n * - non-i18n collections are emitted only for the configured\n * `defaultLocale`; other locales' sitemaps skip them so\n * a row never appears in two sibling sitemaps.\n * Leaving this `undefined` keeps the unfiltered single-file\n * behavior used when i18n is not configured.\n */\n locale?: string;\n}\n\n/**\n * Sitemap-index entry — a pointer to a child `<urlset>` document\n * (typically a per-locale sitemap). The `loc` is path-only; the\n * renderer prepends the absolute origin.\n */\nexport interface NpSitemapIndexEntry {\n loc: string;\n /** Optional ISO timestamp for `<lastmod>` on the child sitemap. */\n lastmod?: string;\n}\n\nconst DEFAULT_LIMIT_PER_COLLECTION = 5_000;\n\n/**\n * Walks every registered collection that opts into the sitemap\n * via `seo.urlPath`, queries published documents, and emits a\n * flat list of `NpSitemapEntry` rows. Anonymous read access is\n * required — `findDocuments(slug, opts, undefined)` runs the\n * collection's `access.read` callback with no user. Collections\n * that gate reads (admin-only, member-only) won't surface in the\n * sitemap, which is the right default.\n *\n * The function intentionally doesn't include the site root `/`\n * by itself — sites add a fixed entry for the home page (and any\n * other static routes like /search, /discussions) on top of the\n * collection walk.\n */\nexport async function buildSitemap(\n options: BuildSitemapOptions = {},\n): Promise<NpSitemapEntry[]> {\n const limit = options.perCollectionLimit ?? DEFAULT_LIMIT_PER_COLLECTION;\n const slugs = options.collections ?? getAllCollectionSlugs();\n const entries: NpSitemapEntry[] = [];\n const i18n = getI18nConfig();\n const localeFilter = options.locale;\n\n for (const slug of slugs) {\n let config;\n try {\n config = getCollectionConfig(slug);\n } catch {\n continue;\n }\n const seo = config.seo;\n if (!seo?.urlPath) continue;\n\n // Phase 12.9 — per-locale sitemap split. When the caller\n // requests a specific locale, non-i18n collections only\n // surface in the default-locale sitemap so a row never\n // appears in two sibling sitemaps.\n if (localeFilter && !config.i18n) {\n if (!i18n || localeFilter !== i18n.defaultLocale) continue;\n }\n\n let result;\n try {\n result = await findDocuments(\n slug,\n {\n limit,\n page: 1,\n where: { status: \"published\" },\n // For i18n collections we deliberately fetch *every*\n // locale's rows even when a localeFilter is set so the\n // grouping pass below can still build a complete\n // hreflang-alternates list. The emission step further\n // down filters siblings to the requested locale before\n // pushing entries. Non-i18n collections take the\n // localeFilter path through the early `continue` above.\n },\n // Anonymous — `access.read` must allow it for the row to\n // appear. Collections gated to authenticated users won't\n // throw here because the access check runs on the\n // collection level (not per-row); they'll throw and we\n // skip below.\n undefined,\n );\n } catch {\n continue;\n }\n\n // Phase 12.2 — for i18n collections, group rows by\n // translationGroupId so each emitted entry can advertise\n // its hreflang alternates. Rows missing the group id\n // (shouldn't happen post-12.1) fall back to standalone\n // entries with no alternates.\n const docs = result.docs;\n if (config.i18n) {\n const groups = new Map<string, Array<Record<string, unknown>>>();\n const orphans: Array<Record<string, unknown>> = [];\n for (const doc of docs) {\n const groupId =\n typeof doc.translationGroupId === \"string\"\n ? doc.translationGroupId\n : null;\n if (!groupId) {\n orphans.push(doc);\n continue;\n }\n const list = groups.get(groupId);\n if (list) list.push(doc);\n else groups.set(groupId, [doc]);\n }\n for (const siblings of groups.values()) {\n const alternates: Array<{ hreflang: string; href: string }> = [];\n for (const sibling of siblings) {\n const siblingPath = seo.urlPath(sibling);\n const locale =\n typeof sibling.locale === \"string\" ? sibling.locale : null;\n if (siblingPath && locale) {\n alternates.push({ hreflang: locale, href: siblingPath });\n }\n }\n for (const sibling of siblings) {\n // Phase 12.9 — when emitting a per-locale sitemap, only\n // push the sibling whose locale matches the filter; the\n // alternates list still references every translation\n // (built above) so crawlers discover the others through\n // hreflang.\n if (localeFilter) {\n const siblingLocale =\n typeof sibling.locale === \"string\" ? sibling.locale : null;\n if (siblingLocale !== localeFilter) continue;\n }\n const path = seo.urlPath(sibling);\n if (!path || !path.startsWith(\"/\")) continue;\n entries.push({\n loc: path,\n lastmod: pickLastmod(sibling),\n changefreq: seo.changefreq,\n priority: seo.priority,\n alternates: alternates.length > 1 ? alternates : undefined,\n });\n }\n }\n for (const doc of orphans) {\n if (localeFilter) {\n const docLocale = typeof doc.locale === \"string\" ? doc.locale : null;\n if (docLocale !== localeFilter) continue;\n }\n const path = seo.urlPath(doc);\n if (!path || !path.startsWith(\"/\")) continue;\n entries.push({\n loc: path,\n lastmod: pickLastmod(doc),\n changefreq: seo.changefreq,\n priority: seo.priority,\n });\n }\n continue;\n }\n\n for (const doc of docs) {\n const path = seo.urlPath(doc);\n if (!path) continue;\n if (!path.startsWith(\"/\")) continue;\n entries.push({\n loc: path,\n lastmod: pickLastmod(doc),\n changefreq: seo.changefreq,\n priority: seo.priority,\n });\n }\n }\n\n return entries;\n}\n\nfunction pickLastmod(doc: Record<string, unknown>): string | undefined {\n const candidate = doc.updatedAt ?? doc.createdAt;\n if (candidate instanceof Date) return candidate.toISOString();\n if (typeof candidate === \"string\") {\n const parsed = new Date(candidate);\n if (!Number.isNaN(parsed.getTime())) return parsed.toISOString();\n }\n return undefined;\n}\n\n/**\n * Renders an `NpSitemapEntry[]` plus the absolute host into an\n * XML body suitable for `Content-Type: application/xml`. The\n * loc path is URL-joined with the host without double-slashes;\n * the host should NOT have a trailing slash. The output is\n * sitemap.org 0.9 compliant.\n */\nexport function renderSitemapXml(\n origin: string,\n entries: NpSitemapEntry[],\n): string {\n const trimmed = origin.replace(/\\/+$/, \"\");\n const usesAlternates = entries.some(\n (e) => e.alternates && e.alternates.length > 0,\n );\n const lines: string[] = [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n usesAlternates\n ? '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">'\n : '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">',\n ];\n for (const entry of entries) {\n lines.push(\" <url>\");\n lines.push(` <loc>${escapeXml(`${trimmed}${entry.loc}`)}</loc>`);\n if (entry.lastmod) {\n lines.push(` <lastmod>${entry.lastmod}</lastmod>`);\n }\n if (entry.changefreq) {\n lines.push(` <changefreq>${entry.changefreq}</changefreq>`);\n }\n if (typeof entry.priority === \"number\") {\n lines.push(` <priority>${entry.priority.toFixed(1)}</priority>`);\n }\n if (entry.alternates) {\n for (const alt of entry.alternates) {\n lines.push(\n ` <xhtml:link rel=\"alternate\" hreflang=\"${escapeXml(alt.hreflang)}\" href=\"${escapeXml(`${trimmed}${alt.href}`)}\"/>`,\n );\n }\n }\n lines.push(\" </url>\");\n }\n lines.push(\"</urlset>\");\n return lines.join(\"\\n\");\n}\n\n/**\n * Phase 12.9 — render a sitemap-index document. Sites with i18n\n * configured emit one of these at `/sitemap.xml` instead of a\n * single `<urlset>`; each child sitemap holds the URLs for one\n * locale so the per-file 50K-entry sitemaps.org cap is per-locale\n * rather than shared across the whole site.\n *\n * The index itself is small (one `<sitemap>` per locale) so it\n * doesn't need the `xhtml` namespace or alternates — those live\n * inside the child `<urlset>` documents.\n */\nexport function renderSitemapIndexXml(\n origin: string,\n entries: NpSitemapIndexEntry[],\n): string {\n const trimmed = origin.replace(/\\/+$/, \"\");\n const lines: string[] = [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">',\n ];\n for (const entry of entries) {\n lines.push(\" <sitemap>\");\n lines.push(` <loc>${escapeXml(`${trimmed}${entry.loc}`)}</loc>`);\n if (entry.lastmod) {\n lines.push(` <lastmod>${entry.lastmod}</lastmod>`);\n }\n lines.push(\" </sitemap>\");\n }\n lines.push(\"</sitemapindex>\");\n return lines.join(\"\\n\");\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n","import { getDb } from \"../db/runtime.js\";\nimport { npSettings } from \"../db/schema/system.js\";\n\n/**\n * Phase 10.3 — site-wide SEO defaults read from `np_settings`.\n * Reads three existing keys + a new `seo` key:\n *\n * - `site` → { name, url } (existing General settings)\n * - `description` → string (existing General settings)\n * - `seo` → { defaultOgImage, twitterHandle, defaultLocale }\n *\n * The shape is a flat merge so callers don't have to hop across\n * keys to pre-fill metadata.\n */\nexport interface NpSiteSeoSettings {\n /** Site name shown in the title bar suffix and `og:site_name`. */\n siteName: string;\n /** Absolute origin used as the base for canonical URLs. */\n siteUrl: string;\n /** Default `<meta name=\"description\">` when a page doesn't set its own. */\n defaultDescription: string;\n /**\n * Default Open Graph image. Either an absolute URL or a path\n * starting with `/` (which gets joined to `siteUrl`). `null` =\n * no default; pages that don't set an image won't render an\n * `og:image` tag.\n */\n defaultOgImage: string | null;\n /** Twitter `@handle` (no leading `@`) for `twitter:site`. `null` = omit. */\n twitterHandle: string | null;\n /** BCP 47 locale tag for `og:locale`. Default `\"en_US\"`. */\n defaultLocale: string;\n}\n\nexport const DEFAULT_SITE_SEO_SETTINGS: NpSiteSeoSettings = {\n siteName: \"NexPress\",\n siteUrl: \"http://localhost:3000\",\n defaultDescription: \"\",\n defaultOgImage: null,\n twitterHandle: null,\n defaultLocale: \"en_US\",\n};\n\n/**\n * Reads the three settings keys that contribute to site-wide\n * SEO and merges into a single flat object. Missing fields fall\n * back to `DEFAULT_SITE_SEO_SETTINGS`. Read-only access — no\n * permission gate; the values are surfaced in public HTML\n * `<head>` tags.\n */\nexport async function getSiteSeoSettings(): Promise<NpSiteSeoSettings> {\n const db = getDb();\n const rows = (await db\n .select()\n .from(npSettings)) as Array<{ key: string; value: unknown }>;\n\n const map = new Map<string, unknown>();\n for (const row of rows) map.set(row.key, row.value);\n\n const site = readObject(map.get(\"site\"));\n const seo = readObject(map.get(\"seo\"));\n const description = map.get(\"description\");\n\n return {\n siteName:\n readString(site?.name) ?? DEFAULT_SITE_SEO_SETTINGS.siteName,\n siteUrl:\n readString(site?.url) ?? DEFAULT_SITE_SEO_SETTINGS.siteUrl,\n defaultDescription:\n (typeof description === \"string\" ? description : null) ??\n DEFAULT_SITE_SEO_SETTINGS.defaultDescription,\n defaultOgImage:\n readString(seo?.defaultOgImage) ??\n DEFAULT_SITE_SEO_SETTINGS.defaultOgImage,\n twitterHandle:\n readString(seo?.twitterHandle) ??\n DEFAULT_SITE_SEO_SETTINGS.twitterHandle,\n defaultLocale:\n readString(seo?.defaultLocale) ??\n DEFAULT_SITE_SEO_SETTINGS.defaultLocale,\n };\n}\n\nfunction readObject(v: unknown): Record<string, unknown> | null {\n if (v && typeof v === \"object\" && !Array.isArray(v)) {\n return v as Record<string, unknown>;\n }\n return null;\n}\n\nfunction readString(v: unknown): string | null {\n if (typeof v === \"string\" && v.trim().length > 0) return v.trim();\n return null;\n}\n\n/**\n * Caller-provided metadata for a single page. All fields are\n * optional — anything missing falls back to the site-wide\n * defaults from `getSiteSeoSettings()`.\n */\nexport interface NpPageMetadataInput {\n /** Page title (without site-name suffix; that's appended below). */\n title?: string | null;\n /** Page-specific description. Falls back to site default. */\n description?: string | null;\n /**\n * Image URL for og:image / twitter:image. Either absolute or\n * a path starting with `/` (joined to siteUrl). Falls back to\n * `defaultOgImage`.\n */\n ogImage?: string | null;\n /**\n * Path of the current page (no origin). Drives the OpenGraph\n * `url` field (which represents \"this resource\"). Also serves\n * as the default for `canonicalPath` when that's not set.\n * Pass without trailing `/` — trailing slashes are normalized\n * off — but query strings are preserved so paginated /\n * filtered routes can identify themselves accurately.\n */\n path?: string;\n /**\n * Override the canonical URL path when the page represents\n * one resource but search engines should treat a different\n * URL as authoritative. Defaults to `path`. Use when /blog\n * canonicalises to / on a theme that renders the same content\n * at both, or when a paginated route should dedupe to its\n * first page. Leaving this unset means \"canonical = self\",\n * which is correct for most pages.\n */\n canonicalPath?: string;\n /** OpenGraph type. Default `\"website\"`; posts use `\"article\"`. */\n ogType?: \"website\" | \"article\" | \"profile\";\n /**\n * Article-specific dates (only honored when `ogType === \"article\"`).\n * Pass `Date` instances — the helper formats to ISO 8601.\n */\n publishedTime?: Date | null;\n modifiedTime?: Date | null;\n /**\n * Phase 12.2 — locale for the rendered page. Surfaces into\n * `og:locale` so social cards label the language correctly.\n * Optional; sites without i18n leave it unset and the helper\n * omits `og:locale` from the output.\n */\n locale?: string;\n}\n\n/**\n * Next.js Metadata shape — kept structurally typed here to avoid\n * a hard dependency on the framework from core. The reference app\n * casts the return to Next's `Metadata` (the field names match).\n */\nexport interface NpPageMetadata {\n title: string;\n description: string;\n alternates?: { canonical: string };\n openGraph?: {\n title: string;\n description: string;\n siteName: string;\n url: string;\n type: \"website\" | \"article\" | \"profile\";\n images?: Array<{ url: string }>;\n locale?: string;\n publishedTime?: string;\n modifiedTime?: string;\n };\n twitter?: {\n card: \"summary\" | \"summary_large_image\";\n title: string;\n description: string;\n site?: string;\n images?: string[];\n };\n}\n\n/**\n * Combines the page-level input with site-wide SEO defaults to\n * produce a fully-resolved metadata object suitable for\n * Next.js' `generateMetadata`. Title, description, and image\n * fall back through to defaults; the OG and Twitter blocks are\n * mirrored so both crawler families see consistent values.\n */\nexport async function buildPageMetadata(\n input: NpPageMetadataInput = {},\n): Promise<NpPageMetadata> {\n const settings = await getSiteSeoSettings();\n const path = normalizePath(input.path);\n const canonicalPath = normalizePath(input.canonicalPath ?? input.path);\n\n const titleText = input.title?.trim()\n ? `${input.title.trim()} · ${settings.siteName}`\n : settings.siteName;\n const descriptionText =\n input.description?.trim() ?? settings.defaultDescription;\n const siteOrigin = settings.siteUrl.replace(/\\/+$/, \"\");\n const canonicalUrl = `${siteOrigin}${canonicalPath}`;\n const ogUrl = `${siteOrigin}${path}`;\n const ogImage = resolveOgImage(input.ogImage, settings);\n const ogType = input.ogType ?? \"website\";\n\n const metadata: NpPageMetadata = {\n title: titleText,\n description: descriptionText,\n alternates: { canonical: canonicalUrl },\n openGraph: {\n title: titleText,\n description: descriptionText,\n siteName: settings.siteName,\n url: ogUrl,\n type: ogType,\n // Page-supplied locale wins over the site default so\n // translated copies surface their actual language to\n // social previews. Falls back to the site setting when\n // the caller doesn't pass one (non-i18n routes).\n locale: input.locale ?? settings.defaultLocale,\n ...(ogImage ? { images: [{ url: ogImage }] } : {}),\n ...(ogType === \"article\" && input.publishedTime\n ? { publishedTime: input.publishedTime.toISOString() }\n : {}),\n ...(ogType === \"article\" && input.modifiedTime\n ? { modifiedTime: input.modifiedTime.toISOString() }\n : {}),\n },\n twitter: {\n card: ogImage ? \"summary_large_image\" : \"summary\",\n title: titleText,\n description: descriptionText,\n ...(settings.twitterHandle ? { site: `@${settings.twitterHandle}` } : {}),\n ...(ogImage ? { images: [ogImage] } : {}),\n },\n };\n\n return metadata;\n}\n\nfunction normalizePath(raw: string | undefined): string {\n if (!raw || !raw.startsWith(\"/\")) return \"/\";\n if (raw === \"/\") return \"/\";\n return raw.replace(/\\/+$/, \"\");\n}\n\nfunction resolveOgImage(\n pageImage: string | null | undefined,\n settings: NpSiteSeoSettings,\n): string | null {\n const candidate = pageImage?.trim() || settings.defaultOgImage;\n if (!candidate) return null;\n if (/^https?:\\/\\//i.test(candidate)) return candidate;\n if (candidate.startsWith(\"/\")) {\n return `${settings.siteUrl.replace(/\\/+$/, \"\")}${candidate}`;\n }\n return candidate;\n}\n\n/**\n * Validates a partial patch against the `seo` settings shape.\n * Throws when fields are mistyped; returns the merged settings\n * the admin endpoint should persist. Mirrors\n * `validateCommunitySettingsPatch` in the community module.\n */\nexport interface NpSeoSettingsPatch {\n defaultOgImage?: string | null;\n twitterHandle?: string | null;\n defaultLocale?: string | null;\n}\n\nexport function validateSeoSettingsPatch(\n patch: unknown,\n): NpSeoSettingsPatch {\n if (!patch || typeof patch !== \"object\" || Array.isArray(patch)) {\n throw new Error(\"Body must be a JSON object\");\n }\n const raw = patch as Record<string, unknown>;\n const out: NpSeoSettingsPatch = {};\n\n if (\"defaultOgImage\" in raw) {\n const v = raw.defaultOgImage;\n if (v === null || v === \"\") {\n out.defaultOgImage = null;\n } else if (typeof v === \"string\") {\n // Accept absolute URLs or `/`-rooted paths. Reject anything\n // else — a stray `javascript:` URL would be a content-injection\n // hazard since the value lands in `<meta>` tags and `<img>`\n // src on social cards.\n const trimmed = v.trim();\n if (\n !/^https?:\\/\\//i.test(trimmed) &&\n !trimmed.startsWith(\"/\")\n ) {\n throw new Error(\n \"defaultOgImage must be an absolute URL or a /-rooted path\",\n );\n }\n out.defaultOgImage = trimmed;\n } else {\n throw new Error(\"defaultOgImage must be a string or null\");\n }\n }\n\n if (\"twitterHandle\" in raw) {\n const v = raw.twitterHandle;\n if (v === null || v === \"\") {\n out.twitterHandle = null;\n } else if (typeof v === \"string\") {\n // Strip a leading @ — we'll re-add it when emitting tags.\n const trimmed = v.trim().replace(/^@/, \"\");\n if (!/^[A-Za-z0-9_]{1,15}$/.test(trimmed)) {\n throw new Error(\n \"twitterHandle must be 1–15 alphanumeric/underscore characters\",\n );\n }\n out.twitterHandle = trimmed;\n } else {\n throw new Error(\"twitterHandle must be a string or null\");\n }\n }\n\n if (\"defaultLocale\" in raw) {\n const v = raw.defaultLocale;\n if (v === null || v === \"\") {\n out.defaultLocale = null;\n } else if (typeof v === \"string\") {\n const trimmed = v.trim();\n // BCP 47 language tag — loose check (full validation is\n // overkill; ICU does the real work downstream).\n if (!/^[a-z]{2,3}(?:[_-][A-Za-z0-9]{2,8})?$/.test(trimmed)) {\n throw new Error(\"defaultLocale must look like 'en' or 'en_US'\");\n }\n out.defaultLocale = trimmed.replace(\"-\", \"_\");\n } else {\n throw new Error(\"defaultLocale must be a string or null\");\n }\n }\n\n return out;\n}\n","import { findDocuments } from \"../collections/pipeline.js\";\nimport { getCollectionConfig } from \"../collections/registry.js\";\n\nimport { getSiteSeoSettings } from \"./page-metadata.js\";\n\n/**\n * Phase 10.4 — Atom feed builder. Atom (RFC 4287) over RSS 2.0\n * because Atom has tighter spec compliance, mandatory unique\n * IDs, and timezone-correct timestamps that RSS 2.0 leaves\n * underspecified. Most modern readers consume both, but new\n * surfaces should write Atom.\n *\n * Sites declare which collections expose a feed by giving them\n * `seo.urlPath` (already required for the sitemap, 10.1). The\n * feed reuses the same anonymous-read query path so non-public\n * rows never leak — same trust model as `/sitemap.xml`.\n */\n\nexport interface NpFeedEntry {\n /** Stable id (we use the absolute canonical URL). */\n id: string;\n title: string;\n /** Short summary; HTML escaped on the way out. */\n summary: string | null;\n link: string;\n /** Author display name; null when unavailable (e.g. anonymous post). */\n author: string | null;\n /** ISO 8601. The Atom `<updated>` element. */\n updated: string;\n /** ISO 8601. Optional — emitted as `<published>`. */\n published: string | null;\n}\n\nexport interface BuildAtomFeedOptions {\n collection?: string;\n /** Cap entries per feed. Default 50 — most readers ignore beyond that. */\n limit?: number;\n /**\n * Phase 12.4 — restrict an i18n collection's feed to one\n * locale. Ignored on non-i18n collections. The Atom feed's\n * top-level `<title>` / `<subtitle>` aren't translated by\n * the framework — sites that want fully translated feeds\n * pass their own language-specific siteName / description\n * via custom site settings hooks.\n */\n locale?: string;\n /**\n * Phase F.7 — extra entries to merge into the feed alongside\n * the collection walk. Supplied by the active theme's\n * `impl.seo.feedEntries` hook through the route handler.\n * Deduplicated against the collection-walk output by `id`\n * (collection-walk wins on collision); the final list is\n * sorted by `updated` desc and capped by `limit`.\n */\n extraEntries?: NpFeedEntry[];\n}\n\nconst DEFAULT_FEED_LIMIT = 50;\nconst DEFAULT_FEED_COLLECTION = \"posts\";\n\n/**\n * Walks a single collection's published documents and returns a\n * flat list of feed entries. Skips collections that don't\n * declare `seo.urlPath` (the same opt-in the sitemap uses).\n * Anonymous read access required — `findDocuments` runs the\n * collection's `access.read` callback with no user.\n */\nexport async function buildAtomFeed(\n options: BuildAtomFeedOptions = {},\n): Promise<{ entries: NpFeedEntry[]; collection: string } | null> {\n const collection = options.collection ?? DEFAULT_FEED_COLLECTION;\n const limit = options.limit ?? DEFAULT_FEED_LIMIT;\n\n let config;\n try {\n config = getCollectionConfig(collection);\n } catch {\n return null;\n }\n const urlPath = config.seo?.urlPath;\n if (!urlPath) return null;\n\n const settings = await getSiteSeoSettings();\n const origin = settings.siteUrl.replace(/\\/+$/, \"\");\n\n let result;\n try {\n result = await findDocuments(\n collection,\n {\n where: { status: \"published\" },\n limit,\n page: 1,\n sort: \"-updatedAt\",\n // Phase 12.4 — when the caller passed a locale AND\n // this collection is i18n-enabled, scope the feed to\n // that locale. findDocuments() ignores `locale` for\n // non-i18n collections so passing it unconditionally\n // is safe; we still gate on config.i18n to keep the\n // intent obvious to readers.\n ...(options.locale && config.i18n ? { locale: options.locale } : {}),\n },\n undefined,\n );\n } catch {\n return null;\n }\n\n const entries: NpFeedEntry[] = [];\n for (const doc of result.docs) {\n const path = urlPath(doc);\n if (!path) continue;\n const link = `${origin}${path}`;\n const updated = pickIso(\n (doc as { updatedAt?: unknown }).updatedAt ??\n (doc as { createdAt?: unknown }).createdAt,\n );\n if (!updated) continue;\n entries.push({\n id: link,\n title: pickTitle(doc),\n summary: pickSummary(doc),\n link,\n author: pickAuthor(doc),\n updated,\n published: pickIso(\n (doc as { publishedAt?: unknown }).publishedAt ??\n (doc as { createdAt?: unknown }).createdAt,\n ),\n });\n }\n\n // Phase F.7 — merge in theme-supplied extra entries, dedup by\n // id (collection-walk wins on collision), sort newest-first,\n // cap at the same limit.\n const extras = options.extraEntries ?? [];\n if (extras.length > 0) {\n const seenIds = new Set(entries.map((e) => e.id));\n for (const extra of extras) {\n if (seenIds.has(extra.id)) continue;\n seenIds.add(extra.id);\n entries.push(extra);\n }\n entries.sort((a, b) => (a.updated < b.updated ? 1 : -1));\n if (entries.length > limit) entries.length = limit;\n }\n\n return { entries, collection };\n}\n\nfunction pickTitle(doc: Record<string, unknown>): string {\n if (typeof doc.title === \"string\" && doc.title.length > 0) return doc.title;\n if (typeof doc.name === \"string\" && doc.name.length > 0) return doc.name;\n if (typeof doc.slug === \"string\" && doc.slug.length > 0) return doc.slug;\n return \"Untitled\";\n}\n\nfunction pickSummary(doc: Record<string, unknown>): string | null {\n for (const key of [\"excerpt\", \"summary\", \"description\", \"seoDescription\"]) {\n const value = doc[key];\n if (typeof value === \"string\" && value.trim().length > 0) {\n const trimmed = value.trim();\n return trimmed.length > 500 ? `${trimmed.slice(0, 497)}…` : trimmed;\n }\n }\n return null;\n}\n\nfunction pickAuthor(doc: Record<string, unknown>): string | null {\n // We don't follow relationship FKs here — the lookup would\n // be N+1 and the feed doesn't need a perfect display name.\n // Sites that want author names in their feed should denormalize\n // a `authorName` field onto the row, or add a feed plugin that\n // does the resolution.\n if (typeof doc.authorName === \"string\" && doc.authorName.length > 0) {\n return doc.authorName;\n }\n return null;\n}\n\nfunction pickIso(value: unknown): string | null {\n if (value instanceof Date) {\n const time = value.getTime();\n if (Number.isNaN(time)) return null;\n return value.toISOString();\n }\n if (typeof value === \"string\") {\n const parsed = new Date(value);\n if (!Number.isNaN(parsed.getTime())) return parsed.toISOString();\n }\n return null;\n}\n\n/**\n * Renders the Atom 1.0 XML body. Doesn't depend on any specific\n * runtime so it's safe to call from a route handler, a static\n * builder, or a CLI exporter.\n */\nexport async function renderAtomFeed(\n options: BuildAtomFeedOptions = {},\n): Promise<string | null> {\n const result = await buildAtomFeed(options);\n if (!result) return null;\n const settings = await getSiteSeoSettings();\n const origin = settings.siteUrl.replace(/\\/+$/, \"\");\n const queryParts: string[] = [];\n if (result.collection !== DEFAULT_FEED_COLLECTION) {\n queryParts.push(`collection=${encodeURIComponent(result.collection)}`);\n }\n if (options.locale) {\n queryParts.push(`locale=${encodeURIComponent(options.locale)}`);\n }\n const feedSelfUrl = `${origin}/feed.xml${queryParts.length ? `?${queryParts.join(\"&\")}` : \"\"}`;\n const updated = result.entries[0]?.updated ?? new Date().toISOString();\n\n const lines: string[] = [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n options.locale\n ? `<feed xmlns=\"http://www.w3.org/2005/Atom\" xml:lang=\"${escapeXml(options.locale)}\">`\n : '<feed xmlns=\"http://www.w3.org/2005/Atom\">',\n ` <title>${escapeXml(settings.siteName)}</title>`,\n settings.defaultDescription\n ? ` <subtitle>${escapeXml(settings.defaultDescription)}</subtitle>`\n : \"\",\n ` <id>${escapeXml(feedSelfUrl)}</id>`,\n ` <link rel=\"self\" href=\"${escapeXml(feedSelfUrl)}\"/>`,\n ` <link rel=\"alternate\" type=\"text/html\" href=\"${escapeXml(origin)}/\"/>`,\n ` <updated>${updated}</updated>`,\n ];\n for (const entry of result.entries) {\n lines.push(\" <entry>\");\n lines.push(` <id>${escapeXml(entry.id)}</id>`);\n lines.push(` <title>${escapeXml(entry.title)}</title>`);\n lines.push(\n ` <link rel=\"alternate\" type=\"text/html\" href=\"${escapeXml(entry.link)}\"/>`,\n );\n lines.push(` <updated>${entry.updated}</updated>`);\n if (entry.published) {\n lines.push(` <published>${entry.published}</published>`);\n }\n if (entry.author) {\n lines.push(\" <author>\");\n lines.push(` <name>${escapeXml(entry.author)}</name>`);\n lines.push(\" </author>\");\n }\n if (entry.summary) {\n lines.push(\n ` <summary type=\"text\">${escapeXml(entry.summary)}</summary>`,\n );\n }\n lines.push(\" </entry>\");\n }\n lines.push(\"</feed>\");\n return lines.filter(Boolean).join(\"\\n\");\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n","import { getSiteSeoSettings } from \"./page-metadata.js\";\n\n/**\n * Phase 10.5 — JSON-LD structured data builders. Schema.org\n * vocabulary, embedded into pages as\n * `<script type=\"application/ld+json\">{ ... }</script>`. The\n * builders here produce plain objects; pages render them via\n * the helper component the reference app declares\n * (`@/components/json-ld`). Keeping the builders structural\n * (no React dependency) lets non-Next consumers — static\n * exporters, mobile bridges, plugin tests — emit the same\n * shapes.\n *\n * Why JSON-LD over Microdata / RDFa: Google explicitly\n * recommends JSON-LD as the preferred format for structured\n * data, and it composes cleanly because it doesn't require\n * splicing schema attributes into the page markup.\n */\n\nconst SCHEMA = \"https://schema.org\";\n\nexport interface BuildJsonLdContext {\n /** Origin without trailing slash. Falls back to settings if omitted. */\n origin?: string;\n}\n\nasync function resolveOrigin(ctx: BuildJsonLdContext = {}): Promise<string> {\n if (ctx.origin) return ctx.origin.replace(/\\/+$/, \"\");\n const settings = await getSiteSeoSettings();\n return settings.siteUrl.replace(/\\/+$/, \"\");\n}\n\nfunction absoluteUrl(origin: string, path: string): string {\n if (/^https?:\\/\\//i.test(path)) return path;\n return `${origin}${path.startsWith(\"/\") ? \"\" : \"/\"}${path}`;\n}\n\n/**\n * `WebSite` + `SearchAction` for the site root. Tells search\n * engines the canonical site name and lets Google render a\n * sitelinks searchbox in result pages — when the user types\n * into it, the engine routes them straight to /search?q=… on\n * the site instead of returning more SERP results.\n */\nexport interface WebSiteJsonLd {\n \"@context\": typeof SCHEMA;\n \"@type\": \"WebSite\";\n name: string;\n url: string;\n potentialAction?: {\n \"@type\": \"SearchAction\";\n target: { \"@type\": \"EntryPoint\"; urlTemplate: string };\n \"query-input\": string;\n };\n}\n\nexport async function buildWebSiteJsonLd(\n ctx: BuildJsonLdContext = {},\n): Promise<WebSiteJsonLd> {\n const settings = await getSiteSeoSettings();\n const origin = await resolveOrigin(ctx);\n return {\n \"@context\": SCHEMA,\n \"@type\": \"WebSite\",\n name: settings.siteName,\n url: `${origin}/`,\n potentialAction: {\n \"@type\": \"SearchAction\",\n target: {\n \"@type\": \"EntryPoint\",\n urlTemplate: `${origin}/search?q={search_term_string}`,\n },\n \"query-input\": \"required name=search_term_string\",\n },\n };\n}\n\n/**\n * `BlogPosting` (a subtype of Article) for blog posts. Keeps\n * the structural fields a search engine cares about — headline,\n * dates, author, image, description — without trying to encode\n * the body content.\n */\nexport interface ArticleJsonLdInput {\n /** Canonical URL of the article. */\n url: string;\n headline: string;\n description?: string | null;\n /** Absolute URL or `/`-rooted path. Resolved against `origin`. */\n image?: string | null;\n datePublished?: Date | string | null;\n dateModified?: Date | string | null;\n authorName?: string | null;\n /** Schema.org type. Defaults to `BlogPosting`; forum threads use\n * `DiscussionForumPosting` via `buildDiscussionForumPostingJsonLd`. */\n type?: \"BlogPosting\" | \"Article\";\n}\n\nexport interface ArticleJsonLd {\n \"@context\": typeof SCHEMA;\n \"@type\": \"BlogPosting\" | \"Article\";\n headline: string;\n url: string;\n description?: string;\n image?: string;\n datePublished?: string;\n dateModified?: string;\n author?: { \"@type\": \"Person\"; name: string };\n publisher: {\n \"@type\": \"Organization\";\n name: string;\n url: string;\n };\n}\n\nexport async function buildArticleJsonLd(\n input: ArticleJsonLdInput,\n ctx: BuildJsonLdContext = {},\n): Promise<ArticleJsonLd> {\n const settings = await getSiteSeoSettings();\n const origin = await resolveOrigin(ctx);\n\n const out: ArticleJsonLd = {\n \"@context\": SCHEMA,\n \"@type\": input.type ?? \"BlogPosting\",\n headline: input.headline,\n url: input.url,\n publisher: {\n \"@type\": \"Organization\",\n name: settings.siteName,\n url: `${origin}/`,\n },\n };\n if (input.description) out.description = input.description;\n if (input.image) out.image = absoluteUrl(origin, input.image);\n const published = toIso(input.datePublished);\n if (published) out.datePublished = published;\n const modified = toIso(input.dateModified);\n if (modified) out.dateModified = modified;\n if (input.authorName) {\n out.author = { \"@type\": \"Person\", name: input.authorName };\n }\n return out;\n}\n\n/**\n * `DiscussionForumPosting` for member-authored forum threads.\n * Same skeleton as `Article` but the type tells search engines\n * (and surfaces like Google's \"Forums\" filter) that this is\n * community discussion, not editorial content.\n */\nexport interface DiscussionForumPostingJsonLd\n extends Omit<ArticleJsonLd, \"@type\"> {\n \"@type\": \"DiscussionForumPosting\";\n}\n\nexport async function buildDiscussionForumPostingJsonLd(\n input: ArticleJsonLdInput,\n ctx: BuildJsonLdContext = {},\n): Promise<DiscussionForumPostingJsonLd> {\n const article = await buildArticleJsonLd(input, ctx);\n return { ...article, \"@type\": \"DiscussionForumPosting\" };\n}\n\n/**\n * `Person` for member / user profile pages. Keeps the public\n * fields a search engine could legitimately surface — handle,\n * display name, profile URL, avatar.\n */\nexport interface PersonJsonLdInput {\n url: string;\n name: string;\n alternateName?: string | null;\n image?: string | null;\n description?: string | null;\n}\n\nexport interface PersonJsonLd {\n \"@context\": typeof SCHEMA;\n \"@type\": \"Person\";\n name: string;\n url: string;\n alternateName?: string;\n image?: string;\n description?: string;\n}\n\nexport async function buildPersonJsonLd(\n input: PersonJsonLdInput,\n ctx: BuildJsonLdContext = {},\n): Promise<PersonJsonLd> {\n const origin = await resolveOrigin(ctx);\n const out: PersonJsonLd = {\n \"@context\": SCHEMA,\n \"@type\": \"Person\",\n name: input.name,\n url: input.url,\n };\n if (input.alternateName) out.alternateName = input.alternateName;\n if (input.image) out.image = absoluteUrl(origin, input.image);\n if (input.description) out.description = input.description;\n return out;\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null;\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) return null;\n return value.toISOString();\n }\n if (typeof value === \"string\") {\n const parsed = new Date(value);\n if (!Number.isNaN(parsed.getTime())) return parsed.toISOString();\n }\n return null;\n}\n"],"mappings":"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","names":["escapeXml"]}
|
|
@@ -35,7 +35,10 @@ function buildZodSchema(fields, hiddenByCondition = /* @__PURE__ */ new Set()) {
|
|
|
35
35
|
if (field.type === "group") {
|
|
36
36
|
const schema = buildZodSchema(field.fields, hiddenByCondition);
|
|
37
37
|
const effectiveRequired2 = field.required && !hiddenByCondition.has(field.name);
|
|
38
|
-
shape[field.name] =
|
|
38
|
+
shape[field.name] = applyFieldDefault(
|
|
39
|
+
applyOptionality(schema, effectiveRequired2),
|
|
40
|
+
field
|
|
41
|
+
);
|
|
39
42
|
continue;
|
|
40
43
|
}
|
|
41
44
|
const effectiveRequired = field.required && !hiddenByCondition.has(field.name);
|
|
@@ -177,4 +180,4 @@ export {
|
|
|
177
180
|
collectHiddenFieldNames,
|
|
178
181
|
getCollectionZodSchema
|
|
179
182
|
};
|
|
180
|
-
//# sourceMappingURL=chunk-
|
|
183
|
+
//# sourceMappingURL=chunk-EFZH6UPY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/collections/validation.ts"],"sourcesContent":["import { z } from \"zod\";\n\nimport {\n type NpCollectionConfig,\n type NpFieldCondition,\n type NpFieldConditionExpr,\n type NpFieldConfig,\n} from \"../config/types.js\";\n\n/**\n * Evaluate a field condition — handles both the legacy function\n * form (`NpFieldCondition`, server-side only) and the serializable\n * expression form (`NpFieldConditionExpr`, works in both env).\n * Unset → returns true (field shows / required holds).\n *\n * Used by `collectHiddenFieldNames` server-side and by the admin\n * editor's `passesCondition` client-side. The same evaluator runs\n * both places so server validation + client visibility never\n * disagree.\n */\nexport function evaluateFieldCondition(\n condition: NpFieldCondition | NpFieldConditionExpr | undefined,\n data: Record<string, unknown>,\n): boolean {\n if (!condition) return true;\n if (typeof condition === \"function\") {\n try {\n return condition(data, data);\n } catch {\n // Buggy condition: treat as \"field visible\" — surface a\n // required error is more recoverable than silently dropping.\n return true;\n }\n }\n return evaluateExpr(condition, data);\n}\n\nfunction evaluateExpr(\n expr: NpFieldConditionExpr,\n data: Record<string, unknown>,\n): boolean {\n if (\"all\" in expr) return expr.all.every((e) => evaluateExpr(e, data));\n if (\"any\" in expr) return expr.any.some((e) => evaluateExpr(e, data));\n const value = data[expr.when];\n if (\"equals\" in expr) return value === expr.equals;\n if (\"notEquals\" in expr) return value !== expr.notEquals;\n if (\"in\" in expr) return expr.in.includes(value);\n if (\"notIn\" in expr) return !expr.notIn.includes(value);\n if (\"exists\" in expr) {\n const present =\n value !== undefined &&\n value !== null &&\n value !== \"\" &&\n !(Array.isArray(value) && value.length === 0);\n return expr.exists ? present : !present;\n }\n // Exhaustiveness — unknown shape fails open (field visible)\n // so a malformed config doesn't silently hide an entire group.\n return true;\n}\n\nexport function buildZodSchema(\n fields: NpFieldConfig[],\n /**\n * Field names whose `admin.condition` returned false against\n * the current document data. `required` is dropped for these\n * — a hidden field can't be filled in by the operator, so\n * enforcing required on save would block writes the operator\n * can't fix. Pass an empty set (the default) for static\n * contexts that don't have current data to evaluate\n * conditions against; the schema then enforces every `required`\n * verbatim, matching pre-#759 behavior.\n */\n hiddenByCondition: ReadonlySet<string> = new Set(),\n): z.ZodObject<Record<string, z.ZodTypeAny>> {\n const shape: Record<string, z.ZodTypeAny> = {};\n\n for (const field of fields) {\n if (field.type === \"row\" || field.type === \"collapsible\") {\n Object.assign(shape, buildZodSchema(field.fields, hiddenByCondition).shape);\n continue;\n }\n\n if (field.type === \"group\") {\n const schema = buildZodSchema(field.fields, hiddenByCondition);\n const effectiveRequired = field.required && !hiddenByCondition.has(field.name);\n shape[field.name] = applyFieldDefault(\n applyOptionality(schema, effectiveRequired),\n field,\n );\n continue;\n }\n\n const effectiveRequired = field.required && !hiddenByCondition.has(field.name);\n shape[field.name] = applyFieldDefault(\n applyOptionality(buildFieldSchema(field), effectiveRequired),\n field,\n );\n }\n\n return z.object(shape);\n}\n\n/**\n * Chain `.default(field.defaultValue)` onto a Zod schema when the field\n * declares one. Without this, a field like `posts.kind` (required + select\n * with a single option + `defaultValue: \"article\"`) rejects API callers who\n * omit the field — even though the framework expected the default to fill\n * in. Drizzle column defaults run at INSERT time and don't help the Zod\n * parse step that runs first; this is the validation-layer pair.\n *\n * Applies to every leaf field type that carries a `defaultValue` AND to\n * `group` fields (a group default is an object literal merged in when the\n * caller omits the whole group). `row` / `collapsible` are pure\n * admin-layout containers with no value of their own — their nested\n * fields each carry their own default — so the function returns the\n * schema unchanged for those two.\n *\n * Only applies when `defaultValue !== undefined`. `null` is a legit\n * default for nullable text/json fields and gets forwarded as-is.\n */\nfunction applyFieldDefault(schema: z.ZodTypeAny, field: NpFieldConfig): z.ZodTypeAny {\n if (field.type === \"row\" || field.type === \"collapsible\") return schema;\n if (!(\"defaultValue\" in field)) return schema;\n if (field.defaultValue === undefined) return schema;\n return schema.default(field.defaultValue as never);\n}\n\n/**\n * Walk fields recursively, evaluating `admin.condition` against\n * `data` and collecting the names of fields the condition would\n * hide. Used by the pipeline + admin client to drop required\n * checks for fields the operator can't see / set.\n *\n * When a `group` field's own condition hides the group, every\n * nested name is added too — operators can't see the inner\n * fields, so requiring them would block save with an invisible\n * error. Nested fields with their own conditions are evaluated\n * normally when the group is visible.\n */\nexport function collectHiddenFieldNames(\n fields: NpFieldConfig[],\n data: Record<string, unknown>,\n): Set<string> {\n const out = new Set<string>();\n const addAllNames = (fs: NpFieldConfig[]): void => {\n for (const field of fs) {\n if (field.type === \"row\" || field.type === \"collapsible\") {\n addAllNames(field.fields);\n continue;\n }\n if (field.type === \"group\") {\n out.add(field.name);\n addAllNames(field.fields);\n continue;\n }\n out.add(field.name);\n }\n };\n const walk = (fs: NpFieldConfig[]): void => {\n for (const field of fs) {\n if (field.type === \"row\" || field.type === \"collapsible\") {\n walk(field.fields);\n continue;\n }\n if (field.type === \"group\") {\n const condition = field.admin?.condition;\n if (condition && !evaluateFieldCondition(condition, data)) {\n out.add(field.name);\n addAllNames(field.fields);\n continue;\n }\n walk(field.fields);\n continue;\n }\n const condition = field.admin?.condition;\n if (!condition) continue;\n if (!evaluateFieldCondition(condition, data)) {\n out.add(field.name);\n }\n }\n };\n walk(fields);\n return out;\n}\n\nexport function getCollectionZodSchema(\n config: NpCollectionConfig,\n /**\n * Current document data. When provided, `admin.condition` is\n * evaluated against it and `required` is dropped for fields\n * the condition hides — mirrors the admin client's\n * condition-aware resolver so a hidden field can't bypass\n * the editor and still fail server validation.\n *\n * Pass `undefined` (or omit) for code paths that need the\n * unconditional schema — pre-#759 behavior.\n */\n forData?: Record<string, unknown>,\n): z.ZodSchema {\n const hidden = forData ? collectHiddenFieldNames(config.fields, forData) : new Set<string>();\n const base = buildZodSchema(config.fields, hidden).extend({\n // Phase 21.17 — per-doc visibility flag. Optional on writes;\n // the pipeline lets the column default to \"public\" when the\n // caller doesn't specify. Allowed values are the same\n // codegen enum from `getBaseColumns`.\n visibility: z.enum([\"public\", \"private\"]).optional(),\n });\n // Phase 12.1 — i18n collections accept `locale` and an\n // optional `translationGroupId` on writes. zod's default\n // strip behavior would otherwise drop them before the\n // pipeline could read them. Validation of `locale` against\n // the configured locales list happens later in the pipeline\n // (we don't have the parent NpConfig here).\n if (config.i18n) {\n return base.extend({\n locale: z.string().min(1).optional(),\n translationGroupId: z.string().uuid().optional(),\n });\n }\n return base;\n}\n\nfunction buildFieldSchema(field: Exclude<NpFieldConfig, { type: \"row\" | \"collapsible\" | \"group\" }>): z.ZodTypeAny {\n switch (field.type) {\n case \"text\": {\n let schema = z.string();\n if (field.minLength !== undefined) schema = schema.min(field.minLength);\n if (field.maxLength !== undefined) schema = schema.max(field.maxLength);\n return schema;\n }\n case \"textarea\": {\n let schema = z.string();\n if (field.minLength !== undefined) schema = schema.min(field.minLength);\n if (field.maxLength !== undefined) schema = schema.max(field.maxLength);\n return schema;\n }\n case \"email\":\n return z.string().email();\n case \"number\": {\n let schema = z.number();\n if (field.integerOnly) schema = schema.int();\n if (field.min !== undefined) schema = schema.min(field.min);\n if (field.max !== undefined) schema = schema.max(field.max);\n return schema;\n }\n case \"checkbox\":\n return z.boolean();\n case \"select\":\n return createEnumSchema(field.options.map((option) => option.value));\n case \"radio\":\n return createEnumSchema(field.options.map((option) => option.value));\n case \"relationship\":\n return field.hasMany ? z.array(z.string().uuid()) : z.string().uuid();\n case \"upload\":\n return z.string().uuid();\n case \"date\":\n return z.coerce.date();\n case \"richText\":\n case \"blocks\":\n case \"json\":\n return z.unknown();\n case \"array\": {\n let schema = z.array(buildZodSchema(field.fields));\n if (field.minRows !== undefined) schema = schema.min(field.minRows);\n if (field.maxRows !== undefined) schema = schema.max(field.maxRows);\n return schema;\n }\n default:\n return z.unknown();\n }\n}\n\nfunction applyOptionality(schema: z.ZodTypeAny, required?: boolean): z.ZodTypeAny {\n return required ? schema : schema.optional().nullable();\n}\n\nfunction createEnumSchema(values: string[]): z.ZodType<string> {\n const [first, ...rest] = values;\n if (!first) {\n return z.string();\n }\n\n return z.enum([first, ...rest]);\n}\n"],"mappings":"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","names":["effectiveRequired","condition"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runHook
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-MXOHWU2P.js";
|
|
4
4
|
import {
|
|
5
5
|
getAllCollectionSlugs,
|
|
6
6
|
getCollectionConfig,
|
|
@@ -80,4 +80,4 @@ async function publishScheduledDocuments(atTime = /* @__PURE__ */ new Date()) {
|
|
|
80
80
|
export {
|
|
81
81
|
publishScheduledDocuments
|
|
82
82
|
};
|
|
83
|
-
//# sourceMappingURL=chunk-
|
|
83
|
+
//# sourceMappingURL=chunk-HYYMUVUK.js.map
|
|
@@ -285,6 +285,19 @@ function buildScalarColumn(field, propertyName, columnName, collectionTables) {
|
|
|
285
285
|
if (typeof field.defaultValue !== "boolean") return "";
|
|
286
286
|
return `.default(${field.defaultValue.toString()})`;
|
|
287
287
|
}
|
|
288
|
+
if (field.type === "date") {
|
|
289
|
+
if (field.defaultValue === "now") return ".defaultNow()";
|
|
290
|
+
if (field.defaultValue instanceof Date && !Number.isNaN(field.defaultValue.getTime())) {
|
|
291
|
+
return `.default(new Date("${field.defaultValue.toISOString()}"))`;
|
|
292
|
+
}
|
|
293
|
+
if (typeof field.defaultValue === "string") {
|
|
294
|
+
const parsed = new Date(field.defaultValue);
|
|
295
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
296
|
+
return `.default(new Date("${parsed.toISOString()}"))`;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return "";
|
|
300
|
+
}
|
|
288
301
|
return "";
|
|
289
302
|
})();
|
|
290
303
|
switch (field.type) {
|
|
@@ -306,7 +319,9 @@ function buildScalarColumn(field, propertyName, columnName, collectionTables) {
|
|
|
306
319
|
return { columns: [`${propertyName}: boolean("${columnName}")${defaultClause}${notNull}`], relations: [] };
|
|
307
320
|
case "date":
|
|
308
321
|
return {
|
|
309
|
-
columns: [
|
|
322
|
+
columns: [
|
|
323
|
+
`${propertyName}: timestamp("${columnName}", { withTimezone: true })${defaultClause}${notNull}`
|
|
324
|
+
],
|
|
310
325
|
relations: []
|
|
311
326
|
};
|
|
312
327
|
case "upload": {
|
|
@@ -708,4 +723,4 @@ export {
|
|
|
708
723
|
generateTypeScript,
|
|
709
724
|
generateDocumentsModule
|
|
710
725
|
};
|
|
711
|
-
//# sourceMappingURL=chunk-
|
|
726
|
+
//# sourceMappingURL=chunk-LMPYQLMH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/db/connection.ts","../src/db/generator.ts","../src/db/type-generator.ts"],"sourcesContent":["import { drizzle } from \"drizzle-orm/node-postgres\";\nimport { Pool } from \"pg\";\n\nimport { readEnvPositiveInt } from \"../config/env.js\";\nimport * as schema from \"./schema/index.js\";\n\nexport interface CreateDbConnectionConfig {\n connectionString: string;\n pool?: Pool;\n /**\n * Override Pool option defaults. Useful for tests, or for sites that need\n * to tune connection limits / timeouts. The fields explicitly set below\n * (`connectionTimeoutMillis`, `statement_timeout`) win unless callers\n * override them here.\n */\n poolOptions?: ConstructorParameters<typeof Pool>[0];\n}\n\n/**\n * Defaults chosen so a wedged Postgres (network drop, container paused,\n * lock storm) surfaces a clear error in single-digit seconds rather than\n * letting a Next.js request handler hang past the platform's request\n * deadline. Both bounds can be raised on a per-site basis via `poolOptions`\n * or globally via `NP_DB_CONNECTION_TIMEOUT_MS` / `NP_DB_STATEMENT_TIMEOUT_MS`.\n *\n * - `connectionTimeoutMillis` caps `pool.connect()` waits — kicks in when\n * the daemon TCP-accepts but never completes the Postgres handshake (the\n * Docker-Desktop-stuck failure mode).\n * - `statement_timeout` is enforced server-side by Postgres and bounds any\n * single query, including the catch-all \"select * from np_users where\n * email = $1\" path that has to be fast on the auth hot path. Sites with\n * legitimately heavy admin workloads (large audit searches, bulk\n * exports) raise this rather than dropping the bound entirely.\n */\nconst DEFAULT_CONNECTION_TIMEOUT_MS = readEnvPositiveInt(\"NP_DB_CONNECTION_TIMEOUT_MS\", 5_000);\nconst DEFAULT_STATEMENT_TIMEOUT_MS = readEnvPositiveInt(\"NP_DB_STATEMENT_TIMEOUT_MS\", 10_000);\n\nexport function createDbConnection(config: CreateDbConnectionConfig) {\n const pool =\n config.pool ??\n new Pool({\n connectionString: config.connectionString,\n connectionTimeoutMillis: DEFAULT_CONNECTION_TIMEOUT_MS,\n statement_timeout: DEFAULT_STATEMENT_TIMEOUT_MS,\n ...config.poolOptions,\n });\n\n return drizzle(pool, { schema });\n}\n","import {\n type NpArrayField,\n type NpCollectionConfig,\n type NpFieldConfig,\n type NpRelationshipField,\n} from \"../config/types.js\";\n\ninterface TableRelation {\n key: string;\n kind: \"one\" | \"many\";\n targetIdentifier: string;\n fields?: string[];\n references?: string[];\n}\n\ninterface GeneratedTable {\n identifier: string;\n tableName: string;\n columns: string[];\n indexes: string[];\n relations: TableRelation[];\n}\n\ninterface ScalarFieldResult {\n columns: string[];\n relations: TableRelation[];\n}\n\ninterface TableContext {\n collectionSlug: string;\n tableIdentifier: string;\n tableName: string;\n relationKeyPrefix: string;\n fieldPath: string[];\n parentReferenceName: string;\n parentTargetIdentifier?: string;\n}\n\nexport interface GenerateDrizzleSchemaOptions {\n /**\n * Module specifier to import the core schema tables (npUsers, npMedia) from.\n * Defaults to \"@nexpress/core\". Override when the consumer's tooling\n * (e.g. drizzle-kit's CJS resolver) can't load the core package via its\n * exports map — point at a relative path to core's source in that case.\n */\n schemaImport?: string;\n}\n\nexport function generateDrizzleSchema(\n collections: NpCollectionConfig[],\n options?: GenerateDrizzleSchemaOptions,\n): string {\n const schemaImport = options?.schemaImport ?? \"@nexpress/core\";\n const collectionTables = new Map<string, string>();\n\n for (const collection of collections) {\n collectionTables.set(collection.slug, getCollectionTableIdentifier(collection.slug));\n }\n\n const tables: GeneratedTable[] = [];\n\n for (const collection of collections) {\n const tableIdentifier = getCollectionTableIdentifier(collection.slug);\n const tableName = `np_c_${collection.slug}`;\n const columns = getBaseColumns(collection);\n const indexes = [`index(\"${tableName}_status_idx\").on(table.status)`];\n const relations: TableRelation[] = [\n {\n key: \"createdByUser\",\n kind: \"one\",\n targetIdentifier: \"npUsers\",\n fields: [\"createdBy\"],\n references: [\"id\"],\n },\n {\n key: \"updatedByUser\",\n kind: \"one\",\n targetIdentifier: \"npUsers\",\n fields: [\"updatedBy\"],\n references: [\"id\"],\n },\n ];\n\n // Phase 9.7b: collections that opt into member-write track the\n // member-author on the row itself so update/delete can perform\n // the owner check without a separate audit-log lookup. Nullable\n // because staff-authored docs in the same table leave it null;\n // the FK to np_members keeps the column safe under member\n // deletes (cascade).\n const memberAuthored = Boolean(collection.community?.memberWrite?.create);\n if (memberAuthored) {\n columns.push(\n 'memberAuthorId: uuid(\"member_author_id\").references(() => npMembers.id, { onDelete: \"set null\" })',\n );\n indexes.push(\n `index(\"${tableName}_member_author_idx\").on(table.memberAuthorId)`,\n );\n relations.push({\n key: \"memberAuthor\",\n kind: \"one\",\n targetIdentifier: \"npMembers\",\n fields: [\"memberAuthorId\"],\n references: [\"id\"],\n });\n }\n\n const scalarResult = collectScalarColumns(collection.fields, [], collectionTables);\n columns.push(...scalarResult.columns);\n relations.push(...scalarResult.relations);\n\n if (hasSlugField(collection)) {\n columns.push('slug: text(\"slug\").notNull()');\n // Phase 15.2 — multi-site scoping. Every collection\n // table gets a `site_id` column so the same slug can\n // exist in multiple sites (e.g., `/about` on the main\n // site and on `acme.example.com`). i18n collections\n // additionally key on locale, so the unique becomes\n // `(site_id, locale, slug)`; non-i18n becomes\n // `(site_id, slug)`. Single-tenant installs leave\n // every row at `site_id = 'default'`, so the\n // uniqueness constraint behaves identically to the\n // pre-15.2 `slug-only` index.\n if (collection.i18n) {\n indexes.push(\n `uniqueIndex(\"${tableName}_site_locale_slug_idx\").on(table.siteId, table.locale, table.slug)`,\n );\n } else {\n indexes.push(\n `uniqueIndex(\"${tableName}_site_slug_idx\").on(table.siteId, table.slug)`,\n );\n }\n }\n\n if (collection.i18n) {\n columns.push('locale: text(\"locale\").notNull()');\n columns.push('translationGroupId: uuid(\"translation_group_id\").notNull()');\n indexes.push(\n `index(\"${tableName}_translation_group_idx\").on(table.translationGroupId)`,\n );\n indexes.push(\n `index(\"${tableName}_locale_idx\").on(table.locale)`,\n );\n }\n\n // Phase 15.2 — multi-site scoping column. Default is\n // 'default' so existing single-tenant deployments keep\n // working without operator intervention; the migration\n // backfills existing rows. NOT NULL so writes always\n // commit a site id; pipeline reads `getCurrentSiteId()`\n // (or falls back to 'default') and stamps every row.\n // FK to `np_sites.id` is intentionally omitted from\n // codegen — adding it forces every test fixture to\n // create the default site row first; the framework\n // invariant (default site always exists post-migration)\n // gives us the same safety without the schema-level FK.\n columns.push(\n 'siteId: text(\"site_id\").default(\"default\").notNull()',\n );\n indexes.push(`index(\"${tableName}_site_idx\").on(table.siteId)`);\n\n if (hasDraftVersions(collection)) {\n columns.push(\n '_status: text(\"_status\", { enum: [\"draft\", \"published\"] }).default(\"draft\").notNull()',\n );\n }\n\n columns.push('searchVector: tsvector(\"search_vector\")');\n\n tables.push({\n identifier: tableIdentifier,\n tableName,\n columns,\n indexes,\n relations,\n });\n\n createNestedTables(\n {\n collectionSlug: collection.slug,\n tableIdentifier,\n tableName,\n relationKeyPrefix: toCamelCase(collection.slug),\n fieldPath: [],\n parentReferenceName: `${toCamelCase(collection.slug)}Id`,\n },\n collection.fields,\n tables,\n collectionTables,\n );\n }\n\n const body = tables.map(renderTable).join(\"\\n\\n\");\n const usesMembers = collections.some((c) => c.community?.memberWrite?.create);\n const schemaImports = [\"npMedia\", \"npUsers\", ...(usesMembers ? [\"npMembers\"] : [])];\n\n return [\n 'import { relations } from \"drizzle-orm\";',\n 'import { boolean, customType, doublePrecision, index, integer, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from \"drizzle-orm/pg-core\";',\n `import { ${schemaImports.join(\", \")} } from \"${schemaImport}\";`,\n '',\n 'const tsvector = customType<{ data: string }>({',\n ' dataType() {',\n ' return \"tsvector\";',\n ' },',\n '});',\n '',\n body,\n ].join(\"\\n\");\n}\n\nfunction createNestedTables(\n context: TableContext,\n fields: NpFieldConfig[],\n tables: GeneratedTable[],\n collectionTables: Map<string, string>,\n): void {\n for (const field of fields) {\n if (field.type === \"group\") {\n createNestedTables(context, field.fields, tables, collectionTables);\n continue;\n }\n\n if (field.type === \"row\" || field.type === \"collapsible\") {\n createNestedTables(context, field.fields, tables, collectionTables);\n continue;\n }\n\n if (field.type === \"array\") {\n tables.push(createArrayTable(context, field, tables, collectionTables));\n continue;\n }\n\n if (field.type === \"relationship\" && field.hasMany && typeof field.relationTo === \"string\") {\n tables.push(\n createHasManyJoinTable(context, { ...field, relationTo: field.relationTo, hasMany: true }, collectionTables),\n );\n }\n }\n}\n\nfunction createArrayTable(\n context: TableContext,\n field: NpArrayField,\n tables: GeneratedTable[],\n collectionTables: Map<string, string>,\n): GeneratedTable {\n const path = [...context.fieldPath, field.name];\n const tableName = `np_c_${context.collectionSlug}__${path.join(\"__\")}`;\n const identifier = getNestedTableIdentifier(context.collectionSlug, path);\n const columns = [\n 'id: uuid(\"id\").defaultRandom().primaryKey()',\n `parentId: uuid(\"parent_id\").notNull().references(() => ${context.tableIdentifier}.id, { onDelete: \"cascade\" })`,\n 'order: integer(\"order\").default(0).notNull()',\n ];\n const relations: TableRelation[] = [\n {\n key: \"parent\",\n kind: \"one\",\n targetIdentifier: context.tableIdentifier,\n fields: [\"parentId\"],\n references: [\"id\"],\n },\n ];\n const scalarResult = collectScalarColumns(field.fields, [], collectionTables);\n columns.push(...scalarResult.columns);\n relations.push(...scalarResult.relations);\n\n const nestedContext: TableContext = {\n collectionSlug: context.collectionSlug,\n tableIdentifier: identifier,\n tableName,\n relationKeyPrefix: `${context.relationKeyPrefix}${toPascalCase(field.name)}`,\n fieldPath: path,\n parentReferenceName: \"parentId\",\n parentTargetIdentifier: context.tableIdentifier,\n };\n\n createNestedTables(nestedContext, field.fields, tables, collectionTables);\n\n return {\n identifier,\n tableName,\n columns,\n indexes: [`index(\"${tableName}_parent_idx\").on(table.parentId)`],\n relations,\n };\n}\n\nfunction createHasManyJoinTable(\n context: TableContext,\n field: NpRelationshipField & { relationTo: string; hasMany: true },\n collectionTables: Map<string, string>,\n): GeneratedTable {\n const path = [...context.fieldPath, field.name];\n const tableName = `np_c_${context.collectionSlug}__${path.join(\"__\")}`;\n const identifier = getNestedTableIdentifier(context.collectionSlug, path);\n const targetIdentifier = resolveRelationTarget(field.relationTo, collectionTables);\n const parentReferenceName = context.fieldPath.length === 0 ? `${toCamelCase(context.collectionSlug)}Id` : \"parentId\";\n\n return {\n identifier,\n tableName,\n columns: [\n 'id: uuid(\"id\").defaultRandom().primaryKey()',\n `${parentReferenceName}: uuid(\"${toSnakeCase(parentReferenceName)}\").notNull().references(() => ${context.tableIdentifier}.id, { onDelete: \"cascade\" })`,\n `targetId: uuid(\"target_id\").notNull().references(() => ${targetIdentifier}.id, { onDelete: \"cascade\" })`,\n 'order: integer(\"order\").default(0).notNull()',\n ],\n indexes: [\n `index(\"${tableName}_${toSnakeCase(parentReferenceName)}_idx\").on(table.${parentReferenceName})`,\n `uniqueIndex(\"${tableName}_parent_target_uidx\").on(table.${parentReferenceName}, table.targetId)`,\n ],\n relations: [\n {\n key: \"parent\",\n kind: \"one\",\n targetIdentifier: context.tableIdentifier,\n fields: [parentReferenceName],\n references: [\"id\"],\n },\n {\n key: \"target\",\n kind: \"one\",\n targetIdentifier,\n fields: [\"targetId\"],\n references: [\"id\"],\n },\n ],\n };\n}\n\nfunction collectScalarColumns(\n fields: NpFieldConfig[],\n prefix: string[],\n collectionTables: Map<string, string>,\n): ScalarFieldResult {\n const columns: string[] = [];\n const relations: TableRelation[] = [];\n\n for (const field of fields) {\n if (field.type === \"group\") {\n const nested = collectScalarColumns(field.fields, [...prefix, field.name], collectionTables);\n columns.push(...nested.columns);\n relations.push(...nested.relations);\n continue;\n }\n\n if (field.type === \"row\" || field.type === \"collapsible\") {\n const nested = collectScalarColumns(field.fields, prefix, collectionTables);\n columns.push(...nested.columns);\n relations.push(...nested.relations);\n continue;\n }\n\n if (field.type === \"array\") {\n continue;\n }\n\n if (field.type === \"relationship\" && field.hasMany) {\n continue;\n }\n\n const propertyName = getFlattenedFieldName(prefix, field.name);\n const columnName = toSnakeCase(propertyName);\n const column = buildScalarColumn(field, propertyName, columnName, collectionTables);\n\n if (!column) {\n continue;\n }\n\n columns.push(...column.columns);\n relations.push(...column.relations);\n }\n\n return { columns, relations };\n}\n\nfunction buildScalarColumn(\n field: Exclude<NpFieldConfig, { type: \"row\" | \"collapsible\" | \"group\" | \"array\" }> ,\n propertyName: string,\n columnName: string,\n collectionTables: Map<string, string>,\n): ScalarFieldResult | null {\n const notNull = field.required ? \".notNull()\" : \"\";\n\n // Honors `field.defaultValue` for SQL-mappable scalar types\n // (text family, number, checkbox, date). Drizzle's\n // `.default(<expr>)` emits a `DEFAULT` clause in the generated\n // migration, which is required for adding a NOT NULL column to\n // a table that already has rows. Non-scalar types\n // (`relationship`, `upload`, `array`, `group`, `richText`,\n // `blocks`, `json`) ignore the value — those either don't map\n // to a single column or have no sensible server-side default.\n //\n // For `date` fields the value accepts three shapes:\n // - the literal `\"now\"` sentinel → emit `.defaultNow()`\n // (Drizzle's helper that compiles to `DEFAULT now()`).\n // - a JS `Date` instance → emit\n // `.default(new Date(\"<iso>\"))`; Drizzle converts at\n // query-build time.\n // - an ISO 8601 string → parsed via `new Date(...)` and\n // emitted the same way.\n // Anything else is dropped silently (same defensive shape as\n // mismatched scalars).\n const defaultClause = ((): string => {\n if (field.defaultValue === undefined || field.defaultValue === null) return \"\";\n if (field.type === \"text\" || field.type === \"textarea\" || field.type === \"email\" || field.type === \"select\" || field.type === \"radio\") {\n if (typeof field.defaultValue !== \"string\") return \"\";\n // Escape `\\` and `\"` so the emitted TS literal is a valid\n // double-quoted string. The generator's output is consumed\n // by tsc, which would reject an unescaped embedded quote.\n const literal = field.defaultValue.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n return `.default(\"${literal}\")`;\n }\n if (field.type === \"number\") {\n if (typeof field.defaultValue !== \"number\" || !Number.isFinite(field.defaultValue)) return \"\";\n return `.default(${field.defaultValue.toString()})`;\n }\n if (field.type === \"checkbox\") {\n if (typeof field.defaultValue !== \"boolean\") return \"\";\n return `.default(${field.defaultValue.toString()})`;\n }\n if (field.type === \"date\") {\n if (field.defaultValue === \"now\") return \".defaultNow()\";\n if (field.defaultValue instanceof Date && !Number.isNaN(field.defaultValue.getTime())) {\n return `.default(new Date(\"${field.defaultValue.toISOString()}\"))`;\n }\n if (typeof field.defaultValue === \"string\") {\n const parsed = new Date(field.defaultValue);\n if (!Number.isNaN(parsed.getTime())) {\n return `.default(new Date(\"${parsed.toISOString()}\"))`;\n }\n }\n return \"\";\n }\n return \"\";\n })();\n\n switch (field.type) {\n case \"text\":\n case \"textarea\":\n case \"email\":\n case \"select\":\n case \"radio\":\n return { columns: [`${propertyName}: text(\"${columnName}\")${defaultClause}${notNull}`], relations: [] };\n case \"number\": {\n const builder = field.integerOnly ? \"integer\" : \"doublePrecision\";\n return { columns: [`${propertyName}: ${builder}(\"${columnName}\")${defaultClause}${notNull}`], relations: [] };\n }\n case \"richText\":\n case \"blocks\":\n case \"json\":\n return { columns: [`${propertyName}: jsonb(\"${columnName}\")${notNull}`], relations: [] };\n case \"checkbox\":\n return { columns: [`${propertyName}: boolean(\"${columnName}\")${defaultClause}${notNull}`], relations: [] };\n case \"date\":\n return {\n columns: [\n `${propertyName}: timestamp(\"${columnName}\", { withTimezone: true })${defaultClause}${notNull}`,\n ],\n relations: [],\n };\n case \"upload\": {\n return {\n columns: [`${propertyName}: uuid(\"${columnName}\").references(() => npMedia.id)${notNull}`],\n relations: [\n {\n key: propertyName,\n kind: \"one\",\n targetIdentifier: \"npMedia\",\n fields: [propertyName],\n references: [\"id\"],\n },\n ],\n };\n }\n case \"relationship\": {\n if (typeof field.relationTo !== \"string\") {\n return null;\n }\n\n const targetIdentifier = resolveRelationTarget(field.relationTo, collectionTables);\n return {\n columns: [`${propertyName}: uuid(\"${columnName}\").references(() => ${targetIdentifier}.id)${notNull}`],\n relations: [\n {\n key: propertyName,\n kind: \"one\",\n targetIdentifier,\n fields: [propertyName],\n references: [\"id\"],\n },\n ],\n };\n }\n default:\n return null;\n }\n}\n\nfunction getBaseColumns(collection: NpCollectionConfig): string[] {\n const columns = ['id: uuid(\"id\").defaultRandom().primaryKey()'];\n\n columns.push(\n 'status: text(\"status\", { enum: [\"draft\", \"scheduled\", \"published\", \"archived\", \"pending\"] }).default(\"draft\").notNull()',\n );\n\n columns.push('createdAt: timestamp(\"created_at\", { withTimezone: true }).defaultNow().notNull()');\n columns.push('updatedAt: timestamp(\"updated_at\", { withTimezone: true }).defaultNow().notNull()');\n columns.push('createdBy: uuid(\"created_by\").references(() => npUsers.id)');\n columns.push('updatedBy: uuid(\"updated_by\").references(() => npUsers.id)');\n\n // Phase 21.17 — per-doc visibility flag. Orthogonal to\n // `status` (workflow state): a row can be published-public,\n // published-private, draft-public, etc. Anonymous reads in\n // `findDocuments` auto-filter to `visibility = \"public\"` so a\n // newly-imported \"private\" row never leaks to crawlers; an\n // authenticated principal (member or staff) sees both. WP\n // imports use this to round-trip `<wp:status>private</wp:status>`\n // posts as `status=published, visibility=private` rather than\n // the old draft-coercion that lost the publish state.\n const visibility =\n 'visibility: text(\"visibility\", { enum: [\"public\", \"private\"] }).default(\"public\").notNull()';\n\n if (collection.timestamps === false) {\n return [columns[0], columns[1], columns[4], columns[5], visibility];\n }\n\n columns.push(visibility);\n return columns;\n}\n\nfunction renderTable(table: GeneratedTable): string {\n const tableSource = [\n `export const ${table.identifier} = pgTable(`,\n ` \"${table.tableName}\",`,\n \" {\",\n ...table.columns.map((column) => ` ${column},`),\n \" },\",\n ` (table) => [${table.indexes.join(\", \")}],`,\n \");\",\n ].join(\"\\n\");\n\n const relationsSource = [\n `export const ${table.identifier}Relations = relations(${table.identifier}, ({ many, one }) => ({`,\n ...table.relations.map((relation) => renderRelation(relation, table.identifier)),\n \"}));\",\n ].join(\"\\n\");\n\n return `${tableSource}\\n\\n${relationsSource}`;\n}\n\nfunction renderRelation(relation: TableRelation, ownerIdentifier: string): string {\n if (relation.kind === \"many\") {\n return ` ${relation.key}: many(${relation.targetIdentifier}),`;\n }\n\n const fields = (relation.fields ?? [])\n .map((field) => `${ownerIdentifier}.${field}`)\n .join(\", \");\n const references = (relation.references ?? [])\n .map((reference) => `${relation.targetIdentifier}.${reference}`)\n .join(\", \");\n\n return ` ${relation.key}: one(${relation.targetIdentifier}, { fields: [${fields}], references: [${references}] }),`;\n}\n\nfunction hasSlugField(collection: NpCollectionConfig): boolean {\n return collection.slugField !== undefined && collection.slugField !== false;\n}\n\nfunction hasDraftVersions(collection: NpCollectionConfig): boolean {\n return Boolean(collection.versions?.drafts);\n}\n\nfunction resolveRelationTarget(\n relationTo: string,\n collectionTables: Map<string, string>,\n): string {\n if (relationTo === \"media\") {\n return \"npMedia\";\n }\n\n if (relationTo === \"users\") {\n return \"npUsers\";\n }\n\n return collectionTables.get(relationTo) ?? getCollectionTableIdentifier(relationTo);\n}\n\nfunction getCollectionTableIdentifier(slug: string): string {\n return `${toCamelCase(slug)}Table`;\n}\n\nfunction getNestedTableIdentifier(collectionSlug: string, path: string[]): string {\n return `${toCamelCase(collectionSlug)}${path.map(toPascalCase).join(\"\")}Table`;\n}\n\nfunction getFlattenedFieldName(prefix: string[], name: string): string {\n if (prefix.length === 0) {\n return toCamelCase(name);\n }\n\n return `${prefix.map(toPascalCase).join(\"\")}${toPascalCase(name)}`.replace(/^./u, (char) => char.toLowerCase());\n}\n\nfunction toCamelCase(value: string): string {\n const parts = splitName(value);\n const [first = \"\", ...rest] = parts;\n return `${first}${rest.map(capitalize).join(\"\")}`;\n}\n\nfunction toPascalCase(value: string): string {\n return splitName(value).map(capitalize).join(\"\");\n}\n\nfunction toSnakeCase(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, \"$1_$2\")\n .replace(/[^a-zA-Z0-9]+/g, \"_\")\n .toLowerCase();\n}\n\nfunction splitName(value: string): string[] {\n return value\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .split(/[^a-zA-Z0-9]+/)\n .map((part) => part.toLowerCase())\n .filter(Boolean);\n}\n\nfunction capitalize(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n","import { type NpCollectionConfig, type NpFieldConfig } from \"../config/types.js\";\n\nexport function generateTypeScript(collections: NpCollectionConfig[]): string {\n const interfaces = collections.map((collection) => renderCollectionInterface(collection));\n return interfaces.join(\"\\n\\n\");\n}\n\ninterface HasManyDescriptor {\n /** Field name on the collection (the where clause key). */\n fieldName: string;\n /** Generated join-table import name (e.g., `postsCategoriesTable`). */\n joinTable: string;\n /** Parent FK column on the join table (e.g., `postsId`). */\n parentColumn: string;\n}\n\nfunction collectHasManyFields(collection: NpCollectionConfig): HasManyDescriptor[] {\n const collCamel = toCamelCase(collection.slug);\n const parentColumn = `${collCamel}Id`;\n const out: HasManyDescriptor[] = [];\n // Only top-level fields participate. Nested hasMany (inside row /\n // collapsible / group / array) is rare in practice and the join-\n // table naming convention doesn't carry through nesting cleanly.\n for (const field of collection.fields) {\n if (field.type === \"relationship\" && field.hasMany === true) {\n const joinTable = `${collCamel}${toPascalCase(field.name)}Table`;\n out.push({ fieldName: field.name, joinTable, parentColumn });\n }\n }\n return out;\n}\n\n/**\n * Renders a complete `documents.ts` module: imports from\n * `@nexpress/core`, per-collection `${Pascal}Document` interfaces,\n * and typed read-helper wrappers (`find${Pascal}` /\n * `get${Pascal}Document`) that bind the type generic so call sites\n * don't have to.\n *\n * Collections with hasMany relationship fields get a smarter\n * wrapper: when the caller's `where` clause names a hasMany\n * field, the wrapper queries the join table for matching parent\n * ids first, intersects across multiple hasMany filters, and\n * delegates to `findDocuments` with `id: idList`. That covers\n * the friction surfaced in #540's category-page dogfood — a\n * `where: { categories: id }` filter looked typed but failed at\n * runtime because `categories` isn't a column on the parent\n * table; this wrapper makes it work.\n *\n * The output is meant for `apps/<app>/src/db/generated/documents.ts`\n * and is a direct counterpart to `generateDrizzleSchema`'s\n * `collections.ts` (Drizzle schema). Both files come from the same\n * `nexpressConfig.collections` source so they stay in sync.\n */\nexport function generateDocumentsModule(collections: NpCollectionConfig[]): string {\n const hasManyByCollection = new Map(\n collections.map((c) => [c.slug, collectHasManyFields(c)]),\n );\n const anyHasMany = Array.from(hasManyByCollection.values()).some(\n (list) => list.length > 0,\n );\n\n const coreImports = [\n `import {`,\n ` findDocuments,`,\n ...(anyHasMany ? [` getDb,`] : []),\n ` getDocumentById,`,\n ` type NpAuthUser,`,\n ` type NpFindOptions,`,\n ` type NpFindResult,`,\n `} from \"@nexpress/core\";`,\n ].join(\"\\n\");\n\n // Drizzle + join-table imports only when at least one collection\n // has a hasMany relationship — keeps the file lean for sites\n // that don't use them.\n const drizzleImports = anyHasMany\n ? [\n `import { inArray } from \"drizzle-orm\";`,\n `import type { NodePgDatabase } from \"drizzle-orm/node-postgres\";`,\n ].join(\"\\n\")\n : \"\";\n const joinTables = Array.from(\n new Set(\n Array.from(hasManyByCollection.values())\n .flat()\n .map((d) => d.joinTable),\n ),\n ).sort();\n // Emit without the `.js` extension. The reference app's\n // `apps/web/tsconfig.json` uses `moduleResolution: \"Bundler\"`\n // (Next 16's standard); Bundler resolution + Turbopack don't\n // rewrite `.js` → `.ts` for relative imports the way tsc's\n // NodeNext resolution does, so the explicit extension breaks\n // `next build` even though `pnpm typecheck` passes (tsc handles\n // both shapes). Extension-less imports work under both\n // resolution strategies — Bundler resolves to the `.ts` file\n // directly, NodeNext does the same when the file extension\n // is omitted in TS source. See\n // https://github.com/vercel/next.js/issues for the long\n // history of `.js`-extension friction with Turbopack.\n const joinTableImports =\n joinTables.length > 0\n ? `import { ${joinTables.join(\", \")} } from \"./collections\";`\n : \"\";\n\n const imports = [coreImports, drizzleImports, joinTableImports]\n .filter(Boolean)\n .join(\"\\n\");\n\n const interfaces = collections.map((c) => renderCollectionInterface(c)).join(\"\\n\\n\");\n const helpers = collections\n .map((c) => renderReadHelpers(c, hasManyByCollection.get(c.slug) ?? []))\n .join(\"\\n\\n\");\n\n return [imports, \"\", interfaces, \"\", helpers, \"\"].join(\"\\n\");\n}\n\nfunction renderReadHelpers(\n collection: NpCollectionConfig,\n hasMany: HasManyDescriptor[],\n): string {\n const docType = `${toPascalCase(collection.slug)}Document`;\n const findFnName = `find${toPascalCase(collection.slug)}`;\n const getFnName = `get${toPascalCase(collection.slug)}Document`;\n const slug = JSON.stringify(collection.slug);\n\n const findFn =\n hasMany.length === 0\n ? renderSimpleFindFn(findFnName, slug, docType, collection.slug)\n : renderHasManyFindFn(findFnName, slug, docType, collection.slug, hasMany);\n\n return [\n findFn,\n \"\",\n `/** Typed by-id fetch for the \\`${collection.slug}\\` collection. */`,\n `export function ${getFnName}(`,\n ` id: string,`,\n ` user?: NpAuthUser,`,\n `): Promise<${docType} | null> {`,\n ` return getDocumentById<${docType}>(${slug}, id, user);`,\n `}`,\n ].join(\"\\n\");\n}\n\nfunction renderSimpleFindFn(\n findFnName: string,\n slug: string,\n docType: string,\n slugLabel: string,\n): string {\n return [\n `/** Typed listing query for the \\`${slugLabel}\\` collection. */`,\n `export function ${findFnName}(`,\n ` options: NpFindOptions<${docType}> = {},`,\n ` user?: NpAuthUser,`,\n `): Promise<NpFindResult<${docType}>> {`,\n ` return findDocuments<${docType}>(${slug}, options, user);`,\n `}`,\n ].join(\"\\n\");\n}\n\nfunction renderHasManyFindFn(\n findFnName: string,\n slug: string,\n docType: string,\n slugLabel: string,\n hasMany: HasManyDescriptor[],\n): string {\n // Build a literal array of `{ field, table, column }` for\n // runtime iteration. Keep it inline (instead of a loose const)\n // so the wrapper is one self-contained function — no helpers\n // bleed into consumer code completion.\n const descriptors = hasMany\n .map(\n (d) =>\n ` { field: ${JSON.stringify(d.fieldName)}, table: ${d.joinTable}, parent: ${d.joinTable}.${d.parentColumn} },`,\n )\n .join(\"\\n\");\n\n return [\n `/**`,\n ` * Typed listing query for the \\`${slugLabel}\\` collection.`,\n ` *`,\n ` * Pre-resolves hasMany relationship filters in the where`,\n ` * clause (${hasMany.map((d) => `\\`${d.fieldName}\\``).join(\", \")}) by`,\n ` * subquerying the join table for matching parent ids. Each`,\n ` * field accepts a single target id (most common) or an array`,\n ` * of target ids (OR semantics). Multiple hasMany filters`,\n ` * intersect — \\`where: { categories: catId, tags: tagId }\\``,\n ` * matches rows that have BOTH.`,\n ` */`,\n `export async function ${findFnName}(`,\n ` options: NpFindOptions<${docType}> = {},`,\n ` user?: NpAuthUser,`,\n `): Promise<NpFindResult<${docType}>> {`,\n ` const where = options.where ? { ...options.where } : {};`,\n ` const hasManyDescriptors = [`,\n descriptors,\n ` ];`,\n ``,\n ` const matched: string[][] = [];`,\n ` let touchedHasMany = false;`,\n ` for (const { field, table, parent } of hasManyDescriptors) {`,\n ` const value = (where as Record<string, unknown>)[field];`,\n ` if (value === undefined) continue;`,\n ` touchedHasMany = true;`,\n ` delete (where as Record<string, unknown>)[field];`,\n ` const targets = (Array.isArray(value) ? value : [value]).filter(`,\n ` (v): v is string => typeof v === \"string\" && v.length > 0,`,\n ` );`,\n ` if (targets.length === 0) {`,\n ` // Empty array short-circuits to no rows — match the`,\n ` // pipeline's array-where semantics.`,\n ` matched.push([]);`,\n ` continue;`,\n ` }`,\n ` // Cast getDb() to NodePgDatabase so the drizzle builder`,\n ` // chain (.select.from.where) carries proper return types.`,\n ` // The empty-schema generic narrows the return shape away`,\n ` // from any specific tables; the explicit \\`{ id: string }[]\\` `,\n ` // cast at the end matches the projection.`,\n ` const db = getDb() as unknown as NodePgDatabase<Record<string, never>>;`,\n ` const rows = (await db`,\n ` .select({ id: parent })`,\n ` .from(table)`,\n ` .where(inArray(table.targetId, targets))) as Array<{ id: string }>;`,\n ` matched.push(rows.map((r) => r.id));`,\n ` }`,\n ``,\n ` if (touchedHasMany) {`,\n ` // Intersect across all hasMany filters. Empty intersection`,\n ` // → return immediately; findDocuments would short-circuit`,\n ` // on the empty-array where clause anyway, but the early`,\n ` // exit saves a round-trip.`,\n ` let ids = matched[0] ?? [];`,\n ` for (let i = 1; i < matched.length; i++) {`,\n ` const set = new Set(matched[i]);`,\n ` ids = ids.filter((id) => set.has(id));`,\n ` }`,\n ``,\n ` // Honor any pre-existing user id constraint. Without this,`,\n ` // \\`where: { id: someId, categories: catId }\\` would silently`,\n ` // drop the user's id filter and return every post in that`,\n ` // category — a real foot-gun. Intersect instead.`,\n ` const existingId = (where as Record<string, unknown>).id;`,\n ` if (typeof existingId === \"string\") {`,\n ` ids = ids.includes(existingId) ? [existingId] : [];`,\n ` } else if (Array.isArray(existingId)) {`,\n ` const allowed = new Set(`,\n ` existingId.filter((v): v is string => typeof v === \"string\"),`,\n ` );`,\n ` ids = ids.filter((id) => allowed.has(id));`,\n ` }`,\n ``,\n ` if (ids.length === 0) {`,\n ` return {`,\n ` docs: [],`,\n ` totalDocs: 0,`,\n ` totalPages: 0,`,\n ` page: options.page ?? 1,`,\n ` limit: options.limit ?? 20,`,\n ` hasNextPage: false,`,\n ` hasPrevPage: false,`,\n ` };`,\n ` }`,\n ` (where as Record<string, unknown>).id = ids;`,\n ` }`,\n ``,\n ` return findDocuments<${docType}>(${slug}, { ...options, where }, user);`,\n `}`,\n ].join(\"\\n\");\n}\n\nfunction renderCollectionInterface(collection: NpCollectionConfig): string {\n const interfaceName = `${toPascalCase(collection.slug)}Document`;\n const fields = [\n 'id: string;',\n 'status: \"draft\" | \"published\" | \"archived\" | \"pending\";',\n 'createdAt: Date;',\n 'updatedAt: Date;',\n 'createdBy: string | null;',\n 'updatedBy: string | null;',\n ];\n\n if (collection.community?.memberWrite?.create) {\n fields.push('memberAuthorId: string | null;');\n }\n\n if (collection.slugField) {\n fields.push(\"slug: string;\");\n }\n\n if (collection.versions?.drafts) {\n fields.push('_status: \"draft\" | \"published\";');\n }\n\n fields.push(...renderFields(collection.fields));\n\n return [`export interface ${interfaceName} {`, ...fields.map((field) => ` ${field}`), \"}\"].join(\"\\n\");\n}\n\nfunction renderFields(fields: NpFieldConfig[], prefix: string[] = []): string[] {\n const lines: string[] = [];\n\n for (const field of fields) {\n if (field.type === \"row\" || field.type === \"collapsible\") {\n lines.push(...renderFields(field.fields, prefix));\n continue;\n }\n\n const fieldName = field.type === \"group\" ? getPropertyName(prefix, field.name) : \"\";\n\n if (field.type === \"group\") {\n const groupType = renderObjectType(field.fields);\n lines.push(`${fieldName}: ${applyNullability(groupType, field.required)};`);\n continue;\n }\n\n const propertyName = getPropertyName(prefix, field.name);\n const typeSource = getTypeSource(field);\n lines.push(`${propertyName}: ${applyNullability(typeSource, field.required)};`);\n }\n\n return lines;\n}\n\nfunction renderObjectType(fields: NpFieldConfig[]): string {\n const members = renderFields(fields).map((field) => ` ${field}`);\n return [`{`, ...members, `}`].join(\"\\n\");\n}\n\nfunction getTypeSource(field: Exclude<NpFieldConfig, { type: \"row\" | \"collapsible\" | \"group\" }>): string {\n switch (field.type) {\n case \"text\":\n case \"textarea\":\n case \"email\":\n case \"select\":\n case \"radio\":\n return \"string\";\n case \"number\":\n return \"number\";\n case \"checkbox\":\n return \"boolean\";\n case \"date\":\n return \"Date\";\n case \"upload\":\n return \"string\";\n case \"relationship\":\n return field.hasMany ? \"string[]\" : \"string\";\n case \"array\":\n return `Array<${renderObjectType(field.fields)}>`;\n case \"richText\":\n case \"blocks\":\n case \"json\":\n return \"unknown\";\n default:\n return \"unknown\";\n }\n}\n\nfunction applyNullability(typeSource: string, required?: boolean): string {\n return required ? typeSource : `${typeSource} | null`;\n}\n\nfunction getPropertyName(prefix: string[], name: string): string {\n if (prefix.length === 0) {\n return toCamelCase(name);\n }\n\n return `${prefix[0]}${prefix.slice(1).map(toPascalCase).join(\"\")}${toPascalCase(name)}`;\n}\n\nfunction toCamelCase(value: string): string {\n const parts = splitName(value);\n const [first = \"\", ...rest] = parts;\n return `${first}${rest.map(toPascalCase).join(\"\")}`;\n}\n\nfunction toPascalCase(value: string): string {\n return splitName(value)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\nfunction splitName(value: string): string[] {\n return value\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .split(/[^a-zA-Z0-9]+/)\n .map((part) => part.toLowerCase())\n .filter(Boolean);\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,eAAe;AACxB,SAAS,YAAY;AAiCrB,IAAM,gCAAgC,mBAAmB,+BAA+B,GAAK;AAC7F,IAAM,+BAA+B,mBAAmB,8BAA8B,GAAM;AAErF,SAAS,mBAAmB,QAAkC;AACnE,QAAM,OACJ,OAAO,QACP,IAAI,KAAK;AAAA,IACP,kBAAkB,OAAO;AAAA,IACzB,yBAAyB;AAAA,IACzB,mBAAmB;AAAA,IACnB,GAAG,OAAO;AAAA,EACZ,CAAC;AAEH,SAAO,QAAQ,MAAM,EAAE,uBAAO,CAAC;AACjC;;;ACAO,SAAS,sBACd,aACA,SACQ;AACR,QAAM,eAAe,SAAS,gBAAgB;AAC9C,QAAM,mBAAmB,oBAAI,IAAoB;AAEjD,aAAW,cAAc,aAAa;AACpC,qBAAiB,IAAI,WAAW,MAAM,6BAA6B,WAAW,IAAI,CAAC;AAAA,EACrF;AAEA,QAAM,SAA2B,CAAC;AAElC,aAAW,cAAc,aAAa;AACpC,UAAM,kBAAkB,6BAA6B,WAAW,IAAI;AACpE,UAAM,YAAY,QAAQ,WAAW,IAAI;AACzC,UAAM,UAAU,eAAe,UAAU;AACzC,UAAM,UAAU,CAAC,UAAU,SAAS,gCAAgC;AACpE,UAAM,YAA6B;AAAA,MACjC;AAAA,QACE,KAAK;AAAA,QACL,MAAM;AAAA,QACN,kBAAkB;AAAA,QAClB,QAAQ,CAAC,WAAW;AAAA,QACpB,YAAY,CAAC,IAAI;AAAA,MACnB;AAAA,MACA;AAAA,QACE,KAAK;AAAA,QACL,MAAM;AAAA,QACN,kBAAkB;AAAA,QAClB,QAAQ,CAAC,WAAW;AAAA,QACpB,YAAY,CAAC,IAAI;AAAA,MACnB;AAAA,IACF;AAQA,UAAM,iBAAiB,QAAQ,WAAW,WAAW,aAAa,MAAM;AACxE,QAAI,gBAAgB;AAClB,cAAQ;AAAA,QACN;AAAA,MACF;AACA,cAAQ;AAAA,QACN,UAAU,SAAS;AAAA,MACrB;AACA,gBAAU,KAAK;AAAA,QACb,KAAK;AAAA,QACL,MAAM;AAAA,QACN,kBAAkB;AAAA,QAClB,QAAQ,CAAC,gBAAgB;AAAA,QACzB,YAAY,CAAC,IAAI;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,UAAM,eAAe,qBAAqB,WAAW,QAAQ,CAAC,GAAG,gBAAgB;AACjF,YAAQ,KAAK,GAAG,aAAa,OAAO;AACpC,cAAU,KAAK,GAAG,aAAa,SAAS;AAExC,QAAI,aAAa,UAAU,GAAG;AAC5B,cAAQ,KAAK,8BAA8B;AAW3C,UAAI,WAAW,MAAM;AACnB,gBAAQ;AAAA,UACN,gBAAgB,SAAS;AAAA,QAC3B;AAAA,MACF,OAAO;AACL,gBAAQ;AAAA,UACN,gBAAgB,SAAS;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAEA,QAAI,WAAW,MAAM;AACnB,cAAQ,KAAK,kCAAkC;AAC/C,cAAQ,KAAK,4DAA4D;AACzE,cAAQ;AAAA,QACN,UAAU,SAAS;AAAA,MACrB;AACA,cAAQ;AAAA,QACN,UAAU,SAAS;AAAA,MACrB;AAAA,IACF;AAaA,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,KAAK,UAAU,SAAS,8BAA8B;AAE9D,QAAI,iBAAiB,UAAU,GAAG;AAChC,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,KAAK,yCAAyC;AAEtD,WAAO,KAAK;AAAA,MACV,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED;AAAA,MACE;AAAA,QACE,gBAAgB,WAAW;AAAA,QAC3B;AAAA,QACA;AAAA,QACA,mBAAmB,YAAY,WAAW,IAAI;AAAA,QAC9C,WAAW,CAAC;AAAA,QACZ,qBAAqB,GAAG,YAAY,WAAW,IAAI,CAAC;AAAA,MACtD;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,OAAO,IAAI,WAAW,EAAE,KAAK,MAAM;AAChD,QAAM,cAAc,YAAY,KAAK,CAAC,MAAM,EAAE,WAAW,aAAa,MAAM;AAC5E,QAAM,gBAAgB,CAAC,WAAW,WAAW,GAAI,cAAc,CAAC,WAAW,IAAI,CAAC,CAAE;AAElF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,YAAY,cAAc,KAAK,IAAI,CAAC,YAAY,YAAY;AAAA,IAC5D;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEA,SAAS,mBACP,SACA,QACA,QACA,kBACM;AACN,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,SAAS;AAC1B,yBAAmB,SAAS,MAAM,QAAQ,QAAQ,gBAAgB;AAClE;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,SAAS,MAAM,SAAS,eAAe;AACxD,yBAAmB,SAAS,MAAM,QAAQ,QAAQ,gBAAgB;AAClE;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,SAAS;AAC1B,aAAO,KAAK,iBAAiB,SAAS,OAAO,QAAQ,gBAAgB,CAAC;AACtE;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,kBAAkB,MAAM,WAAW,OAAO,MAAM,eAAe,UAAU;AAC1F,aAAO;AAAA,QACL,uBAAuB,SAAS,EAAE,GAAG,OAAO,YAAY,MAAM,YAAY,SAAS,KAAK,GAAG,gBAAgB;AAAA,MAC7G;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,iBACP,SACA,OACA,QACA,kBACgB;AAChB,QAAM,OAAO,CAAC,GAAG,QAAQ,WAAW,MAAM,IAAI;AAC9C,QAAM,YAAY,QAAQ,QAAQ,cAAc,KAAK,KAAK,KAAK,IAAI,CAAC;AACpE,QAAM,aAAa,yBAAyB,QAAQ,gBAAgB,IAAI;AACxE,QAAM,UAAU;AAAA,IACd;AAAA,IACA,0DAA0D,QAAQ,eAAe;AAAA,IACjF;AAAA,EACF;AACA,QAAM,YAA6B;AAAA,IACjC;AAAA,MACE,KAAK;AAAA,MACL,MAAM;AAAA,MACN,kBAAkB,QAAQ;AAAA,MAC1B,QAAQ,CAAC,UAAU;AAAA,MACnB,YAAY,CAAC,IAAI;AAAA,IACnB;AAAA,EACF;AACA,QAAM,eAAe,qBAAqB,MAAM,QAAQ,CAAC,GAAG,gBAAgB;AAC5E,UAAQ,KAAK,GAAG,aAAa,OAAO;AACpC,YAAU,KAAK,GAAG,aAAa,SAAS;AAExC,QAAM,gBAA8B;AAAA,IAClC,gBAAgB,QAAQ;AAAA,IACxB,iBAAiB;AAAA,IACjB;AAAA,IACA,mBAAmB,GAAG,QAAQ,iBAAiB,GAAG,aAAa,MAAM,IAAI,CAAC;AAAA,IAC1E,WAAW;AAAA,IACX,qBAAqB;AAAA,IACrB,wBAAwB,QAAQ;AAAA,EAClC;AAEA,qBAAmB,eAAe,MAAM,QAAQ,QAAQ,gBAAgB;AAExE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,CAAC,UAAU,SAAS,kCAAkC;AAAA,IAC/D;AAAA,EACF;AACF;AAEA,SAAS,uBACP,SACA,OACA,kBACgB;AAChB,QAAM,OAAO,CAAC,GAAG,QAAQ,WAAW,MAAM,IAAI;AAC9C,QAAM,YAAY,QAAQ,QAAQ,cAAc,KAAK,KAAK,KAAK,IAAI,CAAC;AACpE,QAAM,aAAa,yBAAyB,QAAQ,gBAAgB,IAAI;AACxE,QAAM,mBAAmB,sBAAsB,MAAM,YAAY,gBAAgB;AACjF,QAAM,sBAAsB,QAAQ,UAAU,WAAW,IAAI,GAAG,YAAY,QAAQ,cAAc,CAAC,OAAO;AAE1G,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS;AAAA,MACP;AAAA,MACA,GAAG,mBAAmB,WAAW,YAAY,mBAAmB,CAAC,iCAAiC,QAAQ,eAAe;AAAA,MACzH,0DAA0D,gBAAgB;AAAA,MAC1E;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,UAAU,SAAS,IAAI,YAAY,mBAAmB,CAAC,mBAAmB,mBAAmB;AAAA,MAC7F,gBAAgB,SAAS,kCAAkC,mBAAmB;AAAA,IAChF;AAAA,IACA,WAAW;AAAA,MACT;AAAA,QACE,KAAK;AAAA,QACL,MAAM;AAAA,QACN,kBAAkB,QAAQ;AAAA,QAC1B,QAAQ,CAAC,mBAAmB;AAAA,QAC5B,YAAY,CAAC,IAAI;AAAA,MACnB;AAAA,MACA;AAAA,QACE,KAAK;AAAA,QACL,MAAM;AAAA,QACN;AAAA,QACA,QAAQ,CAAC,UAAU;AAAA,QACnB,YAAY,CAAC,IAAI;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,qBACP,QACA,QACA,kBACmB;AACnB,QAAM,UAAoB,CAAC;AAC3B,QAAM,YAA6B,CAAC;AAEpC,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,SAAS;AAC1B,YAAM,SAAS,qBAAqB,MAAM,QAAQ,CAAC,GAAG,QAAQ,MAAM,IAAI,GAAG,gBAAgB;AAC3F,cAAQ,KAAK,GAAG,OAAO,OAAO;AAC9B,gBAAU,KAAK,GAAG,OAAO,SAAS;AAClC;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,SAAS,MAAM,SAAS,eAAe;AACxD,YAAM,SAAS,qBAAqB,MAAM,QAAQ,QAAQ,gBAAgB;AAC1E,cAAQ,KAAK,GAAG,OAAO,OAAO;AAC9B,gBAAU,KAAK,GAAG,OAAO,SAAS;AAClC;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,SAAS;AAC1B;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS;AAClD;AAAA,IACF;AAEA,UAAM,eAAe,sBAAsB,QAAQ,MAAM,IAAI;AAC7D,UAAM,aAAa,YAAY,YAAY;AAC3C,UAAM,SAAS,kBAAkB,OAAO,cAAc,YAAY,gBAAgB;AAElF,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AAEA,YAAQ,KAAK,GAAG,OAAO,OAAO;AAC9B,cAAU,KAAK,GAAG,OAAO,SAAS;AAAA,EACpC;AAEA,SAAO,EAAE,SAAS,UAAU;AAC9B;AAEA,SAAS,kBACP,OACA,cACA,YACA,kBAC0B;AAC1B,QAAM,UAAU,MAAM,WAAW,eAAe;AAqBhD,QAAM,iBAAiB,MAAc;AACnC,QAAI,MAAM,iBAAiB,UAAa,MAAM,iBAAiB,KAAM,QAAO;AAC5E,QAAI,MAAM,SAAS,UAAU,MAAM,SAAS,cAAc,MAAM,SAAS,WAAW,MAAM,SAAS,YAAY,MAAM,SAAS,SAAS;AACrI,UAAI,OAAO,MAAM,iBAAiB,SAAU,QAAO;AAInD,YAAM,UAAU,MAAM,aAAa,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK;AAC7E,aAAO,aAAa,OAAO;AAAA,IAC7B;AACA,QAAI,MAAM,SAAS,UAAU;AAC3B,UAAI,OAAO,MAAM,iBAAiB,YAAY,CAAC,OAAO,SAAS,MAAM,YAAY,EAAG,QAAO;AAC3F,aAAO,YAAY,MAAM,aAAa,SAAS,CAAC;AAAA,IAClD;AACA,QAAI,MAAM,SAAS,YAAY;AAC7B,UAAI,OAAO,MAAM,iBAAiB,UAAW,QAAO;AACpD,aAAO,YAAY,MAAM,aAAa,SAAS,CAAC;AAAA,IAClD;AACA,QAAI,MAAM,SAAS,QAAQ;AACzB,UAAI,MAAM,iBAAiB,MAAO,QAAO;AACzC,UAAI,MAAM,wBAAwB,QAAQ,CAAC,OAAO,MAAM,MAAM,aAAa,QAAQ,CAAC,GAAG;AACrF,eAAO,sBAAsB,MAAM,aAAa,YAAY,CAAC;AAAA,MAC/D;AACA,UAAI,OAAO,MAAM,iBAAiB,UAAU;AAC1C,cAAM,SAAS,IAAI,KAAK,MAAM,YAAY;AAC1C,YAAI,CAAC,OAAO,MAAM,OAAO,QAAQ,CAAC,GAAG;AACnC,iBAAO,sBAAsB,OAAO,YAAY,CAAC;AAAA,QACnD;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,GAAG;AAEH,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,SAAS,CAAC,GAAG,YAAY,WAAW,UAAU,KAAK,aAAa,GAAG,OAAO,EAAE,GAAG,WAAW,CAAC,EAAE;AAAA,IACxG,KAAK,UAAU;AACb,YAAM,UAAU,MAAM,cAAc,YAAY;AAChD,aAAO,EAAE,SAAS,CAAC,GAAG,YAAY,KAAK,OAAO,KAAK,UAAU,KAAK,aAAa,GAAG,OAAO,EAAE,GAAG,WAAW,CAAC,EAAE;AAAA,IAC9G;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,SAAS,CAAC,GAAG,YAAY,YAAY,UAAU,KAAK,OAAO,EAAE,GAAG,WAAW,CAAC,EAAE;AAAA,IACzF,KAAK;AACH,aAAO,EAAE,SAAS,CAAC,GAAG,YAAY,cAAc,UAAU,KAAK,aAAa,GAAG,OAAO,EAAE,GAAG,WAAW,CAAC,EAAE;AAAA,IAC3G,KAAK;AACH,aAAO;AAAA,QACL,SAAS;AAAA,UACP,GAAG,YAAY,gBAAgB,UAAU,6BAA6B,aAAa,GAAG,OAAO;AAAA,QAC/F;AAAA,QACA,WAAW,CAAC;AAAA,MACd;AAAA,IACF,KAAK,UAAU;AACb,aAAO;AAAA,QACL,SAAS,CAAC,GAAG,YAAY,WAAW,UAAU,kCAAkC,OAAO,EAAE;AAAA,QACzF,WAAW;AAAA,UACT;AAAA,YACE,KAAK;AAAA,YACL,MAAM;AAAA,YACN,kBAAkB;AAAA,YAClB,QAAQ,CAAC,YAAY;AAAA,YACrB,YAAY,CAAC,IAAI;AAAA,UACnB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,KAAK,gBAAgB;AACnB,UAAI,OAAO,MAAM,eAAe,UAAU;AACxC,eAAO;AAAA,MACT;AAEA,YAAM,mBAAmB,sBAAsB,MAAM,YAAY,gBAAgB;AACjF,aAAO;AAAA,QACL,SAAS,CAAC,GAAG,YAAY,WAAW,UAAU,uBAAuB,gBAAgB,OAAO,OAAO,EAAE;AAAA,QACrG,WAAW;AAAA,UACT;AAAA,YACE,KAAK;AAAA,YACL,MAAM;AAAA,YACN;AAAA,YACA,QAAQ,CAAC,YAAY;AAAA,YACrB,YAAY,CAAC,IAAI;AAAA,UACnB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,eAAe,YAA0C;AAChE,QAAM,UAAU,CAAC,6CAA6C;AAE9D,UAAQ;AAAA,IACN;AAAA,EACF;AAEA,UAAQ,KAAK,mFAAmF;AAChG,UAAQ,KAAK,mFAAmF;AAChG,UAAQ,KAAK,4DAA4D;AACzE,UAAQ,KAAK,4DAA4D;AAWzE,QAAM,aACJ;AAEF,MAAI,WAAW,eAAe,OAAO;AACnC,WAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,UAAU;AAAA,EACpE;AAEA,UAAQ,KAAK,UAAU;AACvB,SAAO;AACT;AAEA,SAAS,YAAY,OAA+B;AAClD,QAAM,cAAc;AAAA,IAClB,gBAAgB,MAAM,UAAU;AAAA,IAChC,MAAM,MAAM,SAAS;AAAA,IACrB;AAAA,IACA,GAAG,MAAM,QAAQ,IAAI,CAAC,WAAW,OAAO,MAAM,GAAG;AAAA,IACjD;AAAA,IACA,iBAAiB,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,IACzC;AAAA,EACF,EAAE,KAAK,IAAI;AAEX,QAAM,kBAAkB;AAAA,IACtB,gBAAgB,MAAM,UAAU,yBAAyB,MAAM,UAAU;AAAA,IACzE,GAAG,MAAM,UAAU,IAAI,CAAC,aAAa,eAAe,UAAU,MAAM,UAAU,CAAC;AAAA,IAC/E;AAAA,EACF,EAAE,KAAK,IAAI;AAEX,SAAO,GAAG,WAAW;AAAA;AAAA,EAAO,eAAe;AAC7C;AAEA,SAAS,eAAe,UAAyB,iBAAiC;AAChF,MAAI,SAAS,SAAS,QAAQ;AAC5B,WAAO,KAAK,SAAS,GAAG,UAAU,SAAS,gBAAgB;AAAA,EAC7D;AAEA,QAAM,UAAU,SAAS,UAAU,CAAC,GACjC,IAAI,CAAC,UAAU,GAAG,eAAe,IAAI,KAAK,EAAE,EAC5C,KAAK,IAAI;AACZ,QAAM,cAAc,SAAS,cAAc,CAAC,GACzC,IAAI,CAAC,cAAc,GAAG,SAAS,gBAAgB,IAAI,SAAS,EAAE,EAC9D,KAAK,IAAI;AAEZ,SAAO,KAAK,SAAS,GAAG,SAAS,SAAS,gBAAgB,gBAAgB,MAAM,mBAAmB,UAAU;AAC/G;AAEA,SAAS,aAAa,YAAyC;AAC7D,SAAO,WAAW,cAAc,UAAa,WAAW,cAAc;AACxE;AAEA,SAAS,iBAAiB,YAAyC;AACjE,SAAO,QAAQ,WAAW,UAAU,MAAM;AAC5C;AAEA,SAAS,sBACP,YACA,kBACQ;AACR,MAAI,eAAe,SAAS;AAC1B,WAAO;AAAA,EACT;AAEA,MAAI,eAAe,SAAS;AAC1B,WAAO;AAAA,EACT;AAEA,SAAO,iBAAiB,IAAI,UAAU,KAAK,6BAA6B,UAAU;AACpF;AAEA,SAAS,6BAA6B,MAAsB;AAC1D,SAAO,GAAG,YAAY,IAAI,CAAC;AAC7B;AAEA,SAAS,yBAAyB,gBAAwB,MAAwB;AAChF,SAAO,GAAG,YAAY,cAAc,CAAC,GAAG,KAAK,IAAI,YAAY,EAAE,KAAK,EAAE,CAAC;AACzE;AAEA,SAAS,sBAAsB,QAAkB,MAAsB;AACrE,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,YAAY,IAAI;AAAA,EACzB;AAEA,SAAO,GAAG,OAAO,IAAI,YAAY,EAAE,KAAK,EAAE,CAAC,GAAG,aAAa,IAAI,CAAC,GAAG,QAAQ,OAAO,CAAC,SAAS,KAAK,YAAY,CAAC;AAChH;AAEA,SAAS,YAAY,OAAuB;AAC1C,QAAM,QAAQ,UAAU,KAAK;AAC7B,QAAM,CAAC,QAAQ,IAAI,GAAG,IAAI,IAAI;AAC9B,SAAO,GAAG,KAAK,GAAG,KAAK,IAAI,UAAU,EAAE,KAAK,EAAE,CAAC;AACjD;AAEA,SAAS,aAAa,OAAuB;AAC3C,SAAO,UAAU,KAAK,EAAE,IAAI,UAAU,EAAE,KAAK,EAAE;AACjD;AAEA,SAAS,YAAY,OAAuB;AAC1C,SAAO,MACJ,QAAQ,sBAAsB,OAAO,EACrC,QAAQ,kBAAkB,GAAG,EAC7B,YAAY;AACjB;AAEA,SAAS,UAAU,OAAyB;AAC1C,SAAO,MACJ,QAAQ,sBAAsB,OAAO,EACrC,MAAM,eAAe,EACrB,IAAI,CAAC,SAAS,KAAK,YAAY,CAAC,EAChC,OAAO,OAAO;AACnB;AAEA,SAAS,WAAW,OAAuB;AACzC,SAAO,MAAM,OAAO,CAAC,EAAE,YAAY,IAAI,MAAM,MAAM,CAAC;AACtD;;;ACvnBO,SAAS,mBAAmB,aAA2C;AAC5E,QAAM,aAAa,YAAY,IAAI,CAAC,eAAe,0BAA0B,UAAU,CAAC;AACxF,SAAO,WAAW,KAAK,MAAM;AAC/B;AAWA,SAAS,qBAAqB,YAAqD;AACjF,QAAM,YAAYA,aAAY,WAAW,IAAI;AAC7C,QAAM,eAAe,GAAG,SAAS;AACjC,QAAM,MAA2B,CAAC;AAIlC,aAAW,SAAS,WAAW,QAAQ;AACrC,QAAI,MAAM,SAAS,kBAAkB,MAAM,YAAY,MAAM;AAC3D,YAAM,YAAY,GAAG,SAAS,GAAGC,cAAa,MAAM,IAAI,CAAC;AACzD,UAAI,KAAK,EAAE,WAAW,MAAM,MAAM,WAAW,aAAa,CAAC;AAAA,IAC7D;AAAA,EACF;AACA,SAAO;AACT;AAwBO,SAAS,wBAAwB,aAA2C;AACjF,QAAM,sBAAsB,IAAI;AAAA,IAC9B,YAAY,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,qBAAqB,CAAC,CAAC,CAAC;AAAA,EAC1D;AACA,QAAM,aAAa,MAAM,KAAK,oBAAoB,OAAO,CAAC,EAAE;AAAA,IAC1D,CAAC,SAAS,KAAK,SAAS;AAAA,EAC1B;AAEA,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA,GAAI,aAAa,CAAC,UAAU,IAAI,CAAC;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AAKX,QAAM,iBAAiB,aACnB;AAAA,IACE;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI,IACX;AACJ,QAAM,aAAa,MAAM;AAAA,IACvB,IAAI;AAAA,MACF,MAAM,KAAK,oBAAoB,OAAO,CAAC,EACpC,KAAK,EACL,IAAI,CAAC,MAAM,EAAE,SAAS;AAAA,IAC3B;AAAA,EACF,EAAE,KAAK;AAaP,QAAM,mBACJ,WAAW,SAAS,IAChB,YAAY,WAAW,KAAK,IAAI,CAAC,6BACjC;AAEN,QAAM,UAAU,CAAC,aAAa,gBAAgB,gBAAgB,EAC3D,OAAO,OAAO,EACd,KAAK,IAAI;AAEZ,QAAM,aAAa,YAAY,IAAI,CAAC,MAAM,0BAA0B,CAAC,CAAC,EAAE,KAAK,MAAM;AACnF,QAAM,UAAU,YACb,IAAI,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,IAAI,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,EACtE,KAAK,MAAM;AAEd,SAAO,CAAC,SAAS,IAAI,YAAY,IAAI,SAAS,EAAE,EAAE,KAAK,IAAI;AAC7D;AAEA,SAAS,kBACP,YACA,SACQ;AACR,QAAM,UAAU,GAAGA,cAAa,WAAW,IAAI,CAAC;AAChD,QAAM,aAAa,OAAOA,cAAa,WAAW,IAAI,CAAC;AACvD,QAAM,YAAY,MAAMA,cAAa,WAAW,IAAI,CAAC;AACrD,QAAM,OAAO,KAAK,UAAU,WAAW,IAAI;AAE3C,QAAM,SACJ,QAAQ,WAAW,IACf,mBAAmB,YAAY,MAAM,SAAS,WAAW,IAAI,IAC7D,oBAAoB,YAAY,MAAM,SAAS,WAAW,MAAM,OAAO;AAE7E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,mCAAmC,WAAW,IAAI;AAAA,IAClD,mBAAmB,SAAS;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,cAAc,OAAO;AAAA,IACrB,4BAA4B,OAAO,KAAK,IAAI;AAAA,IAC5C;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEA,SAAS,mBACP,YACA,MACA,SACA,WACQ;AACR,SAAO;AAAA,IACL,qCAAqC,SAAS;AAAA,IAC9C,mBAAmB,UAAU;AAAA,IAC7B,4BAA4B,OAAO;AAAA,IACnC;AAAA,IACA,2BAA2B,OAAO;AAAA,IAClC,0BAA0B,OAAO,KAAK,IAAI;AAAA,IAC1C;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEA,SAAS,oBACP,YACA,MACA,SACA,WACA,SACQ;AAKR,QAAM,cAAc,QACjB;AAAA,IACC,CAAC,MACC,gBAAgB,KAAK,UAAU,EAAE,SAAS,CAAC,YAAY,EAAE,SAAS,aAAa,EAAE,SAAS,IAAI,EAAE,YAAY;AAAA,EAChH,EACC,KAAK,IAAI;AAEZ,SAAO;AAAA,IACL;AAAA,IACA,oCAAoC,SAAS;AAAA,IAC7C;AAAA,IACA;AAAA,IACA,cAAc,QAAQ,IAAI,CAAC,MAAM,KAAK,EAAE,SAAS,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,IACjE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,yBAAyB,UAAU;AAAA,IACnC,4BAA4B,OAAO;AAAA,IACnC;AAAA,IACA,2BAA2B,OAAO;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,0BAA0B,OAAO,KAAK,IAAI;AAAA,IAC1C;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEA,SAAS,0BAA0B,YAAwC;AACzE,QAAM,gBAAgB,GAAGA,cAAa,WAAW,IAAI,CAAC;AACtD,QAAM,SAAS;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,WAAW,WAAW,aAAa,QAAQ;AAC7C,WAAO,KAAK,gCAAgC;AAAA,EAC9C;AAEA,MAAI,WAAW,WAAW;AACxB,WAAO,KAAK,eAAe;AAAA,EAC7B;AAEA,MAAI,WAAW,UAAU,QAAQ;AAC/B,WAAO,KAAK,iCAAiC;AAAA,EAC/C;AAEA,SAAO,KAAK,GAAG,aAAa,WAAW,MAAM,CAAC;AAE9C,SAAO,CAAC,oBAAoB,aAAa,MAAM,GAAG,OAAO,IAAI,CAAC,UAAU,KAAK,KAAK,EAAE,GAAG,GAAG,EAAE,KAAK,IAAI;AACvG;AAEA,SAAS,aAAa,QAAyB,SAAmB,CAAC,GAAa;AAC9E,QAAM,QAAkB,CAAC;AAEzB,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,SAAS,MAAM,SAAS,eAAe;AACxD,YAAM,KAAK,GAAG,aAAa,MAAM,QAAQ,MAAM,CAAC;AAChD;AAAA,IACF;AAEA,UAAM,YAAY,MAAM,SAAS,UAAU,gBAAgB,QAAQ,MAAM,IAAI,IAAI;AAEjF,QAAI,MAAM,SAAS,SAAS;AAC1B,YAAM,YAAY,iBAAiB,MAAM,MAAM;AAC/C,YAAM,KAAK,GAAG,SAAS,KAAK,iBAAiB,WAAW,MAAM,QAAQ,CAAC,GAAG;AAC1E;AAAA,IACF;AAEA,UAAM,eAAe,gBAAgB,QAAQ,MAAM,IAAI;AACvD,UAAM,aAAa,cAAc,KAAK;AACtC,UAAM,KAAK,GAAG,YAAY,KAAK,iBAAiB,YAAY,MAAM,QAAQ,CAAC,GAAG;AAAA,EAChF;AAEA,SAAO;AACT;AAEA,SAAS,iBAAiB,QAAiC;AACzD,QAAM,UAAU,aAAa,MAAM,EAAE,IAAI,CAAC,UAAU,KAAK,KAAK,EAAE;AAChE,SAAO,CAAC,KAAK,GAAG,SAAS,GAAG,EAAE,KAAK,IAAI;AACzC;AAEA,SAAS,cAAc,OAAkF;AACvG,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,MAAM,UAAU,aAAa;AAAA,IACtC,KAAK;AACH,aAAO,SAAS,iBAAiB,MAAM,MAAM,CAAC;AAAA,IAChD,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,iBAAiB,YAAoB,UAA4B;AACxE,SAAO,WAAW,aAAa,GAAG,UAAU;AAC9C;AAEA,SAAS,gBAAgB,QAAkB,MAAsB;AAC/D,MAAI,OAAO,WAAW,GAAG;AACvB,WAAOD,aAAY,IAAI;AAAA,EACzB;AAEA,SAAO,GAAG,OAAO,CAAC,CAAC,GAAG,OAAO,MAAM,CAAC,EAAE,IAAIC,aAAY,EAAE,KAAK,EAAE,CAAC,GAAGA,cAAa,IAAI,CAAC;AACvF;AAEA,SAASD,aAAY,OAAuB;AAC1C,QAAM,QAAQE,WAAU,KAAK;AAC7B,QAAM,CAAC,QAAQ,IAAI,GAAG,IAAI,IAAI;AAC9B,SAAO,GAAG,KAAK,GAAG,KAAK,IAAID,aAAY,EAAE,KAAK,EAAE,CAAC;AACnD;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAOC,WAAU,KAAK,EACnB,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,EAC1D,KAAK,EAAE;AACZ;AAEA,SAASA,WAAU,OAAyB;AAC1C,SAAO,MACJ,QAAQ,sBAAsB,OAAO,EACrC,MAAM,eAAe,EACrB,IAAI,CAAC,SAAS,KAAK,YAAY,CAAC,EAChC,OAAO,OAAO;AACnB;","names":["toCamelCase","toPascalCase","splitName"]}
|