@nexpress/core 0.1.3 → 0.1.6

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugins/context.ts","../src/collections/pipeline.ts","../src/collections/slug.ts","../src/collections/validation.ts","../src/collections/search.ts","../src/plugins/enabled-gate.ts","../src/plugins/compat.ts","../src/plugins/host.ts"],"sourcesContent":["import type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { and, eq, gt, isNull, like, or } from \"drizzle-orm\";\n\nimport type { NpAuthUser, NpFindOptions } from \"../config/types.js\";\nimport { NpError, NpForbiddenError } from \"../errors.js\";\nimport {\n deleteDocument as coreDeleteDocument,\n findDocuments as coreFindDocuments,\n getDocumentById as coreGetDocumentById,\n saveDocument as coreSaveDocument,\n} from \"../collections/pipeline.js\";\nimport {\n listMedia as coreListMedia,\n getMediaById as coreGetMediaById,\n deleteMedia as coreDeleteMedia,\n uploadMedia as coreUploadMedia,\n getStorageAdapter,\n} from \"../media/service.js\";\nimport { getDb } from \"../db/runtime.js\";\nimport {\n NP_GLOBAL_PLUGIN_SITE_ID,\n npPluginStorage,\n npSettings,\n} from \"../db/schema/system.js\";\nimport { getScopedLogger } from \"../observability/logger.js\";\nimport { reportError } from \"../observability/error-reporter.js\";\nimport { getCurrentSiteId } from \"../sites/context.js\";\nimport { NP_DEFAULT_SITE_ID } from \"../sites/registry.js\";\n\n/**\n * Two distinct fallbacks live here, intentionally:\n *\n * - `resolveStorageSiteId` returns the `_global_` sentinel when no\n * site context is set. `np_plugin_storage` is keyed by\n * `(plugin_id, site_id, key)` and the sentinel scopes data as\n * \"process-wide / cross-site shared.\" Background workers, CLI\n * tasks, and migrations all run without a site resolver and\n * should land in the global keyspace by default.\n *\n * - `resolveSettingsSiteId` returns the actual default site id\n * when no context is set. `np_settings` rows ALWAYS belong to\n * a real site, so falling through to a sentinel would orphan\n * the row outside `np_sites` and break joins.\n *\n * They look superficially the same — one helper per intent so the\n * next reader doesn't have to reverse-engineer which fallback is\n * which by reading both schema definitions.\n */\nasync function resolveStorageSiteId(): Promise<string> {\n return (await getCurrentSiteId()) ?? NP_GLOBAL_PLUGIN_SITE_ID;\n}\n\nasync function resolveSettingsSiteId(): Promise<string> {\n return (await getCurrentSiteId()) ?? NP_DEFAULT_SITE_ID;\n}\n\n/**\n * Plugin principal used when plugin-initiated operations need an NpAuthUser.\n * Plugin ops bypass per-doc ACL; authorisation is enforced via the plugin's\n * declared `capabilities` instead. This matches the Phase 3 design: plugins\n * are trusted in-process code, capability flags gate coarse permissions.\n */\nconst pluginPrincipal = (pluginId: string): NpAuthUser => ({\n id: `plugin:${pluginId}`,\n email: `${pluginId}@plugins.local`,\n name: `plugin/${pluginId}`,\n role: \"admin\",\n tokenVersion: 0,\n});\n\ninterface RegistrationLike {\n actions: Map<string, (data: unknown) => Promise<{ ok: boolean; data?: unknown; error?: string }>>;\n}\n\ninterface BuildContextOptions {\n pluginId: string;\n capabilities: readonly string[];\n allowedHosts: readonly string[];\n config: Record<string, unknown>;\n registration: RegistrationLike;\n lookupRegistration: (pluginId: string) => RegistrationLike | undefined;\n}\n\n/**\n * Per-process in-memory cache for `ctx.cache.*`. Keyed by `pluginId:key`,\n * each entry carries its expiry (ms) so `get()` lazily evicts stale entries.\n * Lost on process restart — use `ctx.storage` for durable state.\n */\nconst pluginCache = new Map<string, { value: unknown; expiresAt: number | null }>();\n\nfunction cacheKey(pluginId: string, key: string): string {\n return `${pluginId}:${key}`;\n}\n\nfunction assertCap(pluginId: string, capabilities: readonly string[], required: string): void {\n if (!capabilities.includes(required)) {\n throw new NpForbiddenError(\n `plugin:${pluginId}`,\n `capability \"${required}\" not declared in manifest`,\n );\n }\n}\n\nasync function loadOptionalNextCache(): Promise<{\n revalidatePath?: (path: string) => void;\n revalidateTag?: (tag: string) => void;\n} | null> {\n try {\n // Indirect specifier so TypeScript doesn't try to resolve\n // `next/cache` at compile time — `@nexpress/core` doesn't\n // depend on Next.js. Only the Next-runtime path needs\n // the cache helpers; worker / CLI / standalone Node\n // consumers see this fall through to null cleanly.\n const moduleId: string = \"next/cache\";\n const mod = (await import(moduleId)) as {\n revalidatePath?: (path: string) => void;\n revalidateTag?: (tag: string) => void;\n };\n return mod;\n } catch {\n return null;\n }\n}\n\n/**\n * Produces the runtime ctx passed to plugin hook / route / setup handlers.\n * Matches the `NpPluginContext` shape declared in `@nexpress/plugin-sdk`.\n *\n * Every namespace declared on `NpPluginContext` is implemented:\n * - `pluginId`, `config`, `capabilities`\n * - `content.*` (find / findOne / save / delete)\n * - `media.*` (list / getById / getUrl / upload / delete)\n * - `settings.*`\n * - `log.*`\n * - `next.*`\n * - `actions.*`\n * - `storage.*` (plugin-scoped key/value persistence)\n * - `cache.*` (revalidatePath / revalidateTag wrappers)\n * - `theme.*` (read theme tokens / active theme)\n * - `http.fetch` (allowlist-gated outbound HTTP)\n *\n * Capability checks (`assertCap`) gate every namespace so a\n * plugin that only declares `content:read` can't reach into\n * `media:upload` etc. without explicit opt-in.\n */\nexport function createPluginRuntimeContext(options: BuildContextOptions): Record<string, unknown> {\n const { pluginId, capabilities, allowedHosts, config, registration, lookupRegistration } =\n options;\n const db = (): NodePgDatabase<Record<string, unknown>> => getDb();\n const principal = pluginPrincipal(pluginId);\n\n // Plugin logs flow through the global logger (`setLogger` at app boot)\n // with `pluginId` bound, so operators can filter / route / aggregate\n // plugin output without each plugin reaching for `console.*`.\n const pluginLog = getScopedLogger({ pluginId });\n\n return {\n pluginId,\n config,\n capabilities,\n\n content: {\n async find(collection: string, query?: Partial<NpFindOptions>) {\n assertCap(pluginId, capabilities, \"content:read\");\n return coreFindDocuments(collection, query ?? {}, principal);\n },\n async findOne(collection: string, id: string) {\n assertCap(pluginId, capabilities, \"content:read\");\n const doc = await coreGetDocumentById(collection, id, principal);\n return doc ?? null;\n },\n async create(collection: string, data: Record<string, unknown>) {\n assertCap(pluginId, capabilities, \"content:write\");\n const result = await coreSaveDocument(collection, null, data, principal);\n return result.doc;\n },\n async update(collection: string, id: string, data: Record<string, unknown>) {\n assertCap(pluginId, capabilities, \"content:write\");\n const result = await coreSaveDocument(collection, id, data, principal);\n return result.doc;\n },\n async delete(collection: string, id: string) {\n assertCap(pluginId, capabilities, \"content:delete\");\n await coreDeleteDocument(collection, id, principal);\n },\n async count(collection: string) {\n assertCap(pluginId, capabilities, \"content:read\");\n const result = await coreFindDocuments(collection, { limit: 1 }, principal);\n return result.totalDocs;\n },\n },\n\n media: {\n async list(query?: { page?: number; limit?: number; mimeType?: string; folder?: string }) {\n assertCap(pluginId, capabilities, \"media:read\");\n return coreListMedia({\n page: query?.page,\n limit: query?.limit,\n mimeType: query?.mimeType,\n folderId: query?.folder,\n });\n },\n async getById(id: string) {\n assertCap(pluginId, capabilities, \"media:read\");\n return coreGetMediaById(id);\n },\n async getUrl(id: string) {\n assertCap(pluginId, capabilities, \"media:read\");\n const media = await coreGetMediaById(id);\n if (!media || typeof media.storageKey !== \"string\") return \"\";\n const adapter = getStorageAdapter();\n return adapter.getUrl(media.storageKey);\n },\n async upload(\n file: Uint8Array | ArrayBuffer,\n metadata: { filename: string; mimeType: string; folder?: string },\n ) {\n assertCap(pluginId, capabilities, \"media:write\");\n const buffer = Buffer.from(file instanceof ArrayBuffer ? new Uint8Array(file) : file);\n return coreUploadMedia(\n {\n buffer,\n originalFilename: metadata.filename,\n mimeType: metadata.mimeType,\n },\n // `uploaded_by` is a nullable FK to `np_users.id`. The\n // previous `plugin:<id>` synthetic value violated the FK\n // and threw at insert time, leaving the storage object\n // orphaned. (#62) Plugin attribution lives in the audit\n // log + plugin-storage layer; the uploader column is for\n // staff-user provenance only.\n null,\n metadata.folder,\n );\n },\n async delete(id: string) {\n assertCap(pluginId, capabilities, \"media:delete\");\n const result = await coreDeleteMedia(id);\n if (!result.deleted && result.references && result.references.length > 0) {\n throw new NpError(\n `[plugin:${pluginId}] media.delete: ${id} is referenced by ${result.references.length} document(s).`,\n \"CONFLICT\",\n 409,\n );\n }\n },\n },\n\n storage: {\n // Phase 17 — every storage call resolves the current\n // site at call time and uses it as part of the composite\n // PK `(plugin_id, site_id, key)`. Background workers and\n // scripts (no site resolver) fall back to the\n // `_global_` sentinel so legacy single-site callers keep\n // their existing keyspace.\n async get<T = unknown>(key: string): Promise<T | null> {\n assertCap(pluginId, capabilities, \"storage:kv\");\n const siteId = await resolveStorageSiteId();\n const now = new Date();\n const rows = await db()\n .select()\n .from(npPluginStorage)\n .where(\n and(\n eq(npPluginStorage.pluginId, pluginId),\n eq(npPluginStorage.siteId, siteId),\n eq(npPluginStorage.key, key),\n or(isNull(npPluginStorage.expiresAt), gt(npPluginStorage.expiresAt, now)),\n ),\n )\n .limit(1);\n const row = rows[0] as { value?: unknown } | undefined;\n return (row?.value as T | undefined) ?? null;\n },\n async set(key: string, value: unknown, opts?: { ttl?: number }): Promise<void> {\n assertCap(pluginId, capabilities, \"storage:kv\");\n const siteId = await resolveStorageSiteId();\n const expiresAt = opts?.ttl && opts.ttl > 0 ? new Date(Date.now() + opts.ttl * 1000) : null;\n await db()\n .insert(npPluginStorage)\n .values({\n pluginId,\n siteId,\n key,\n value,\n expiresAt,\n updatedAt: new Date(),\n })\n .onConflictDoUpdate({\n target: [npPluginStorage.pluginId, npPluginStorage.siteId, npPluginStorage.key],\n set: { value, expiresAt, updatedAt: new Date() },\n });\n },\n async delete(key: string): Promise<void> {\n assertCap(pluginId, capabilities, \"storage:kv\");\n const siteId = await resolveStorageSiteId();\n await db()\n .delete(npPluginStorage)\n .where(\n and(\n eq(npPluginStorage.pluginId, pluginId),\n eq(npPluginStorage.siteId, siteId),\n eq(npPluginStorage.key, key),\n ),\n );\n },\n async list(prefix?: string): Promise<string[]> {\n assertCap(pluginId, capabilities, \"storage:kv\");\n const siteId = await resolveStorageSiteId();\n const now = new Date();\n const where = prefix\n ? and(\n eq(npPluginStorage.pluginId, pluginId),\n eq(npPluginStorage.siteId, siteId),\n like(npPluginStorage.key, `${prefix}%`),\n or(isNull(npPluginStorage.expiresAt), gt(npPluginStorage.expiresAt, now)),\n )\n : and(\n eq(npPluginStorage.pluginId, pluginId),\n eq(npPluginStorage.siteId, siteId),\n or(isNull(npPluginStorage.expiresAt), gt(npPluginStorage.expiresAt, now)),\n );\n const rows = (await db()\n .select({ key: npPluginStorage.key })\n .from(npPluginStorage)\n .where(where)) as Array<{ key: string }>;\n return rows.map((row) => row.key);\n },\n async has(key: string): Promise<boolean> {\n assertCap(pluginId, capabilities, \"storage:kv\");\n const siteId = await resolveStorageSiteId();\n const now = new Date();\n const rows = await db()\n .select({ key: npPluginStorage.key })\n .from(npPluginStorage)\n .where(\n and(\n eq(npPluginStorage.pluginId, pluginId),\n eq(npPluginStorage.siteId, siteId),\n eq(npPluginStorage.key, key),\n or(isNull(npPluginStorage.expiresAt), gt(npPluginStorage.expiresAt, now)),\n ),\n )\n .limit(1);\n return rows.length > 0;\n },\n },\n\n cache: {\n // The cache namespace is in-memory today (a process-\n // scoped Map). The interface is `Promise<...>` so a\n // future Redis-backed implementation can swap in\n // without breaking plugin authors; the sync\n // implementations return resolved promises directly so\n // the require-await rule stays happy.\n get<T = unknown>(key: string): Promise<T | null> {\n const entry = pluginCache.get(cacheKey(pluginId, key));\n if (!entry) return Promise.resolve(null);\n if (entry.expiresAt !== null && entry.expiresAt <= Date.now()) {\n pluginCache.delete(cacheKey(pluginId, key));\n return Promise.resolve(null);\n }\n return Promise.resolve(entry.value as T);\n },\n set(key: string, value: unknown, ttl?: number): Promise<void> {\n pluginCache.set(cacheKey(pluginId, key), {\n value,\n expiresAt: ttl && ttl > 0 ? Date.now() + ttl * 1000 : null,\n });\n return Promise.resolve();\n },\n invalidate(key: string): Promise<void> {\n pluginCache.delete(cacheKey(pluginId, key));\n return Promise.resolve();\n },\n invalidateAll(): Promise<void> {\n const prefix = `${pluginId}:`;\n for (const key of pluginCache.keys()) {\n if (key.startsWith(prefix)) pluginCache.delete(key);\n }\n return Promise.resolve();\n },\n },\n\n settings: {\n async getSite(): Promise<Record<string, unknown>> {\n assertCap(pluginId, capabilities, \"settings:read\");\n const siteId = await resolveSettingsSiteId();\n const rows = await db()\n .select()\n .from(npSettings)\n .where(and(eq(npSettings.siteId, siteId), eq(npSettings.key, \"site\")));\n const row = rows[0] as { value?: unknown } | undefined;\n if (!row || !row.value || typeof row.value !== \"object\" || Array.isArray(row.value)) {\n return {};\n }\n return row.value as Record<string, unknown>;\n },\n async getPlugin(): Promise<Record<string, unknown>> {\n // G.1 — plugin config moved from np_plugins.config to\n // np_settings.(siteId, \"plugin.config:<id>\"). Read via the\n // versioned-envelope-aware helper so plugins still see the\n // unwrapped value.\n const { getPluginConfig } = await import(\"./config.js\");\n const value = await getPluginConfig(pluginId);\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return {};\n },\n async setPlugin(data: Record<string, unknown>): Promise<void> {\n // G.1 — plugin config moved to np_settings. When the plugin\n // declares a configSchema, route through `setPluginConfig`\n // so the plugin's own schema validates writes coming from\n // its own setup() / handlers (the same path the admin form\n // uses). Plugins without configSchema bypass validation and\n // write the v1 envelope directly — preserves legacy\n // ctx.settings.setPlugin behavior.\n const { setPluginConfig } = await import(\"./config.js\");\n const { getPluginRegistration } = await import(\"./host.js\");\n const reg = getPluginRegistration(pluginId);\n if (reg?.configSchema) {\n await setPluginConfig(pluginId, data, null);\n return;\n }\n const siteId = (await getCurrentSiteId()) ?? NP_DEFAULT_SITE_ID;\n const now = new Date();\n await db()\n .insert(npSettings)\n .values({\n siteId,\n key: `plugin.config:${pluginId}`,\n value: { __npVersion: 1, __npSettings: data },\n updatedAt: now,\n updatedBy: null,\n })\n .onConflictDoUpdate({\n target: [npSettings.siteId, npSettings.key],\n set: {\n value: { __npVersion: 1, __npSettings: data },\n updatedAt: now,\n updatedBy: null,\n },\n });\n },\n },\n\n theme: {\n async getTokens(): Promise<Record<string, unknown>> {\n assertCap(pluginId, capabilities, \"theme:read\");\n const siteId = await resolveSettingsSiteId();\n const rows = await db()\n .select()\n .from(npSettings)\n .where(and(eq(npSettings.siteId, siteId), eq(npSettings.key, \"theme\")));\n const row = rows[0] as { value?: unknown } | undefined;\n if (!row || !row.value || typeof row.value !== \"object\" || Array.isArray(row.value)) {\n return {};\n }\n return row.value as Record<string, unknown>;\n },\n async setTokens(partial: Record<string, unknown>): Promise<void> {\n assertCap(pluginId, capabilities, \"theme:write\");\n const siteId = await resolveSettingsSiteId();\n const rows = await db()\n .select()\n .from(npSettings)\n .where(and(eq(npSettings.siteId, siteId), eq(npSettings.key, \"theme\")));\n const existing =\n rows[0] &&\n (rows[0] as { value?: unknown }).value &&\n typeof (rows[0] as { value?: unknown }).value === \"object\" &&\n !Array.isArray((rows[0] as { value?: unknown }).value)\n ? ((rows[0] as { value: unknown }).value as Record<string, unknown>)\n : {};\n const merged = { ...existing, ...partial };\n await db()\n .insert(npSettings)\n .values({ siteId, key: \"theme\", value: merged, updatedAt: new Date() })\n .onConflictDoUpdate({\n target: [npSettings.siteId, npSettings.key],\n set: { value: merged, updatedAt: new Date() },\n });\n },\n },\n\n http: {\n async fetch(\n url: string,\n opts?: {\n method?: string;\n headers?: Record<string, string>;\n body?: unknown;\n timeoutMs?: number;\n },\n ): Promise<{ ok: boolean; status: number; headers: Record<string, string>; body?: unknown }> {\n assertCap(pluginId, capabilities, \"network:fetch\");\n // Allowed-host check: manifest.allowedHosts gates every fetch. Empty\n // list means the plugin declared network:fetch but didn't scope it\n // — refuse rather than allow anything.\n let target: URL;\n try {\n target = new URL(url);\n } catch {\n throw new NpError(\n `[plugin:${pluginId}] http.fetch: invalid URL \"${url}\"`,\n \"INVALID_URL\",\n 400,\n );\n }\n const hostMatches = allowedHosts.some((pattern) => {\n if (pattern === target.hostname) return true;\n if (pattern.startsWith(\"*.\") && target.hostname.endsWith(pattern.slice(1))) return true;\n return false;\n });\n if (!hostMatches) {\n throw new NpForbiddenError(\n `plugin:${pluginId}`,\n `http.fetch to \"${target.hostname}\" blocked; add it to manifest.allowedHosts`,\n );\n }\n\n const timeoutMs = opts?.timeoutMs ?? 10_000;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n try {\n // `BodyInit` isn't in @types/node's global scope even with lib.dom\n // off, so keep the local union narrow enough for fetch() while\n // staying portable across runtimes.\n let body: string | Uint8Array | undefined;\n if (opts?.body !== undefined && opts.body !== null) {\n if (typeof opts.body === \"string\") {\n body = opts.body;\n } else if (opts.body instanceof Uint8Array) {\n body = opts.body;\n } else {\n body = JSON.stringify(opts.body);\n }\n }\n const response = await globalThis.fetch(url, {\n method: opts?.method ?? (body !== undefined ? \"POST\" : \"GET\"),\n headers: opts?.headers,\n body,\n signal: controller.signal,\n });\n const headers: Record<string, string> = {};\n response.headers.forEach((v, k) => {\n headers[k] = v;\n });\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n let parsedBody: unknown = undefined;\n if (contentType.includes(\"application/json\")) {\n parsedBody = await response.json().catch(() => undefined);\n } else if (contentType.startsWith(\"text/\")) {\n parsedBody = await response.text();\n }\n return {\n ok: response.ok,\n status: response.status,\n headers,\n body: parsedBody,\n };\n } finally {\n clearTimeout(timeout);\n }\n },\n },\n\n log: {\n debug(message: string, data?: Record<string, unknown>): void {\n pluginLog.debug(message, data);\n },\n info(message: string, data?: Record<string, unknown>): void {\n pluginLog.info(message, data);\n },\n warn(message: string, data?: Record<string, unknown>): void {\n pluginLog.warn(message, data);\n },\n error(message: string, data?: Record<string, unknown>): void {\n pluginLog.error(message, data);\n },\n },\n\n errors: {\n // Plugin-side error reporting with pluginId auto-tagged. The host\n // already auto-reports thrown hook handlers (in `dispatchHookHandler`),\n // so plugins typically only need this when *catching* an error\n // internally — e.g. a non-fatal upstream failure they want to log to\n // Sentry but recover from.\n report(\n error: unknown,\n context?: {\n extra?: Record<string, unknown>;\n tags?: Record<string, string>;\n user?: { id?: string; email?: string; role?: string };\n },\n ): Promise<void> {\n const err = error instanceof Error ? error : new Error(String(error));\n return reportError(err, {\n tags: { source: \"plugin\", pluginId, ...context?.tags },\n extra: context?.extra,\n user: context?.user,\n });\n },\n },\n\n next: {\n async revalidatePath(path: string): Promise<void> {\n const mod = await loadOptionalNextCache();\n mod?.revalidatePath?.(path);\n },\n async revalidateTag(tag: string): Promise<void> {\n const mod = await loadOptionalNextCache();\n const fn = mod?.revalidateTag;\n if (typeof fn !== \"function\") return;\n // Next 16 widened the signature to `(tag, profile)`.\n // Forward `\"default\"` when the runtime accepts the\n // extra arg so plugins keep their single-arg ergonomics.\n if (fn.length >= 2) {\n (fn as (tag: string, profile: string) => void)(tag, \"default\");\n } else {\n (fn as (tag: string) => void)(tag);\n }\n },\n },\n\n actions: {\n register(\n actionName: string,\n handler: (data: unknown) => Promise<{ ok: boolean; data?: unknown; error?: string }>,\n ): void {\n registration.actions.set(actionName, handler);\n },\n async dispatch(\n targetPluginId: string,\n actionName: string,\n data?: unknown,\n ): Promise<{ ok: boolean; data?: unknown; error?: string }> {\n const target = lookupRegistration(targetPluginId);\n const action = target?.actions.get(actionName);\n if (!action) {\n return {\n ok: false,\n error: `Action \"${actionName}\" not found on plugin \"${targetPluginId}\"`,\n };\n }\n return action(data);\n },\n },\n };\n}\n","import { randomUUID } from \"node:crypto\";\n\nimport { asc, count, desc, eq, inArray, sql, type SQL } from \"drizzle-orm\";\nimport type { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\n\nimport {\n type NpCollectionConfig,\n type NpDocumentStatus,\n type NpFindOptions,\n type NpFindResult,\n type NpSaveOptions,\n type NpSaveResult,\n type NpAuthUser,\n type NpCollectionHook,\n type NpFieldConfig,\n type NpHookPrincipal,\n} from \"../config/types.js\";\nimport { NpForbiddenError, NpNotFoundError, NpValidationError } from \"../errors.js\";\nimport { applySlugField } from \"./slug.js\";\nimport { getI18nConfig } from \"../i18n/registry.js\";\nimport { getCurrentSiteId } from \"../sites/context.js\";\nimport { NP_DEFAULT_SITE_ID } from \"../sites/registry.js\";\nimport { getCollectionZodSchema } from \"./validation.js\";\nimport { getCollectionConfig, getCollectionTable, getCollectionRegistration } from \"./registry.js\";\nimport { buildSearchVector, buildWeightedSearchVectorSql } from \"./search.js\";\nimport { enqueueJob } from \"../jobs/queue.js\";\nimport { runHook } from \"../plugins/host.js\";\nimport { npRevisions, npSlugHistory } from \"../db/schema/system.js\";\nimport { npComments, npReactions, npReports } from \"../db/schema/community.js\";\nimport { npMediaRefs } from \"../db/schema/media.js\";\nimport { getDb } from \"../db/runtime.js\";\n\ninterface PreparedDocumentData {\n mainData: Record<string, unknown>;\n childRows: Record<string, Record<string, unknown>[]>;\n joinRows: Record<string, string[]>;\n}\n\ntype QueryCondition = ReturnType<typeof sql>;\n\ninterface SelectQuery extends Promise<unknown[]> {\n where(condition: QueryCondition): SelectQuery;\n orderBy(order: QueryCondition): SelectQuery;\n limit(limit: number): SelectQuery;\n offset(offset: number): SelectQuery;\n}\n\ninterface InsertValuesQuery extends Promise<unknown> {\n returning(): Promise<unknown[]>;\n}\n\ninterface DrizzleTransactionLike {\n insert(table: PgTable): {\n values(values: Record<string, unknown> | Record<string, unknown>[]): InsertValuesQuery;\n };\n update(table: PgTable): {\n set(values: Record<string, unknown>): {\n where(condition: QueryCondition): {\n returning(): Promise<unknown[]>;\n };\n };\n };\n delete(table: PgTable): {\n where(condition: QueryCondition): Promise<unknown>;\n };\n select(selection?: Record<string, unknown>): {\n from(table: PgTable): SelectQuery;\n };\n}\n\ninterface DrizzleDatabaseLike extends DrizzleTransactionLike {\n transaction<T>(callback: (tx: DrizzleTransactionLike) => Promise<T>): Promise<T>;\n}\n\n/**\n * Internal actor type. The pipeline accepts either a staff `NpAuthUser`\n * (the original behavior) or a `{ kind: \"member\", memberId }` shape\n * (Phase 9.7a — `community.memberWrite.create` collections). Member\n * writes bypass the staff `access.create` access function: gating is\n * the per-collection opt-in flag plus `assertNotBanned(memberId)`,\n * not the staff access tree. `createdBy` / `updatedBy` / `authorId`\n * (revisions) are stored as null when the actor is a member; the\n * audit log captures the actual member id.\n */\ntype SaveActor = { kind: \"staff\"; user: NpAuthUser } | { kind: \"member\"; memberId: string };\n\nfunction actorUserOrNull(actor: SaveActor): NpAuthUser | null {\n return actor.kind === \"staff\" ? actor.user : null;\n}\n\nfunction actorUserId(actor: SaveActor): string | null {\n return actor.kind === \"staff\" ? actor.user.id : null;\n}\n\n/**\n * Polymorphic actor reference passed to collection hooks and\n * surfaced to plugin hooks via the `principal` payload field.\n * Mirrors `SaveActor` — kept structurally identical so hook\n * authors can switch on `kind` without importing a separate type.\n */\nfunction actorPrincipal(actor: SaveActor): NpHookPrincipal {\n switch (actor.kind) {\n case \"staff\":\n return { kind: \"staff\", user: actor.user };\n case \"member\":\n return { kind: \"member\", memberId: actor.memberId };\n default: {\n const _exhaustive: never = actor;\n void _exhaustive;\n throw new Error(\"actorPrincipal: unhandled SaveActor kind\");\n }\n }\n}\n\n/**\n * Run a side-effect that fires AFTER the document transaction has\n * already committed (job enqueue, plugin hook). The doc is durable\n * by this point — surfacing the error to the caller would make a\n * successful save look like a failure, so we swallow and surface\n * via the framework logger instead.\n *\n * Operators rely on this log line to discover skipped follow-ups\n * (search reindex, mention fanout, cache invalidation, etc.) and\n * replay manually. The full outbox-pattern fix lives in #277; this\n * is the minimum viable visibility shim.\n *\n * @internal — exported so the unit test can verify the\n * swallow + log contract directly. Not part of the package's\n * public API; do not use from outside `@nexpress/core`.\n */\nexport async function runPostCommit(\n label: string,\n context: { collection: string; documentId: string; operation?: string },\n fn: () => Promise<unknown>,\n): Promise<void> {\n try {\n await fn();\n } catch (err) {\n const { getLogger } = await import(\"../observability/logger.js\");\n getLogger().error(\n `post-commit ${label} failed — document persisted, follow-up skipped`,\n {\n collection: context.collection,\n documentId: context.documentId,\n operation: context.operation,\n label,\n error: err instanceof Error ? err.message : String(err),\n stack: err instanceof Error ? err.stack : undefined,\n },\n );\n }\n}\n\nexport async function saveDocument(\n collection: string,\n docId: string | null,\n data: Record<string, unknown>,\n user: NpAuthUser,\n options?: NpSaveOptions,\n): Promise<NpSaveResult> {\n return saveDocumentImpl(collection, docId, data, { kind: \"staff\", user }, options);\n}\n\n/**\n * Member-side document create. Only valid when\n * `config.community?.memberWrite?.create === true`. Assumes the API\n * layer has already authenticated the member (and that the cookie's\n * status was checked) — this function adds:\n * - the per-collection opt-in gate, and\n * - `assertNotBanned(memberId)` (site-wide; per-collection bans\n * resolve to the same site scope)\n *\n * Fires the `document.created` reputation event after a successful\n * write so adapters can credit the author the same way they credit\n * comments / reactions. Member-side update / delete live in\n * `updateMemberDocument` / `deleteMemberDocument` below.\n */\n/**\n * Member-side document update. Only valid when\n * `config.community?.memberWrite?.update === true` AND the existing\n * row's `member_author_id` matches the caller. Members can NOT\n * change `_status` via update — `options.status` is stripped here\n * so a forged body field can't bypass the moderation pipeline. The\n * author column itself is also locked — see saveDocumentImpl.\n */\nexport async function updateMemberDocument(\n collection: string,\n docId: string,\n data: Record<string, unknown>,\n memberId: string,\n options?: NpSaveOptions,\n): Promise<NpSaveResult> {\n const memberOptions: NpSaveOptions = { ...(options ?? {}) };\n delete memberOptions.status;\n\n // Cheap authorization checks BEFORE moderation (#139). Without\n // this gate a banned member or a non-owner could still trigger\n // the (potentially-paid, potentially-network) spam/profanity\n // adapters before `saveDocumentImpl` rejects them. Order:\n // 1. collection opt-in\n // 2. doc existence\n // 3. owner check\n // 4. ban check\n // These are duplicated inside `saveDocumentImpl`, but both\n // execute the same SELECT-or-cache queries; doing them here\n // saves the moderation round-trip on doomed requests. If any\n // of these throw, the moderation never runs.\n const config = getCollectionConfig(collection);\n if (!config.community?.memberWrite?.update) {\n throw new NpForbiddenError(collection, \"update\");\n }\n const table = getCollectionTable(collection) as PgTable;\n const dbForGate = getDb() as unknown as DrizzleDatabaseLike;\n const originalDoc = await getDocumentByIdInternal(dbForGate, table, collection, docId);\n if (!originalDoc) {\n throw new NpNotFoundError(collection, docId);\n }\n const authorId = (originalDoc as { memberAuthorId?: string | null }).memberAuthorId ?? null;\n if (authorId !== memberId) {\n throw new NpForbiddenError(collection, \"update\");\n }\n const { assertNotBanned } = await import(\"../community/can.js\");\n await assertNotBanned(memberId);\n\n // Re-run the spam + profanity adapters on the submitted patch.\n // Pre-fix this path skipped moderation entirely, so a member\n // could create a clean discussion, get it published, then PATCH\n // it to spam/profanity and the row stayed published. The same\n // verdict semantics apply as the create path:\n // - reject → 400, no write\n // - flag → status forced to `pending`\n // - pass → status untouched (the original status survives)\n const moderation = await runMemberDocModeration({\n collection,\n data,\n memberId,\n targetId: docId,\n });\n if (moderation.flaggedBy.length > 0) {\n memberOptions.status = \"pending\";\n }\n\n const result = await saveDocumentImpl(\n collection,\n docId,\n data,\n { kind: \"member\", memberId },\n memberOptions,\n );\n const { recordAuditEvent } = await import(\"../community/audit.js\");\n await recordAuditEvent({\n actor: { kind: \"member\", memberId },\n action: moderation.flaggedBy.length > 0 ? \"document.flag\" : \"document.update\",\n targetType: collection,\n targetId: docId,\n payload: {\n collectionSlug: collection,\n event: \"update\",\n ...(moderation.flaggedBy.length > 0 ? { sources: moderation.flaggedBy } : {}),\n ...(moderation.profanityVerdict ? { profanityVerdict: moderation.profanityVerdict } : {}),\n ...(moderation.spamVerdict ? { spamVerdict: moderation.spamVerdict } : {}),\n },\n });\n\n // Phase 16.2 — @mention fan-out on edit. Only fire for edits\n // that landed `published` (skip flagged-to-pending edits — same\n // policy as `comment.mention` on update). Delta the prior\n // body so toggling unrelated fields doesn't re-notify the same\n // recipients.\n const resultStatus = (result.doc as { status?: unknown }).status;\n if (resultStatus === \"published\") {\n const { extractMentionHandlesFromDocData, fanOutMentionNotifications } =\n await import(\"../community/mentions.js\");\n const previousHandles = new Set(extractMentionHandlesFromDocData(originalDoc));\n await fanOutMentionNotifications({\n actorMemberId: memberId,\n kind: \"document.mention\",\n data,\n previousHandles,\n payload: {\n collectionSlug: collection,\n documentId: docId,\n },\n });\n }\n return result;\n}\n\nexport async function createMemberDocument(\n collection: string,\n data: Record<string, unknown>,\n memberId: string,\n options?: NpSaveOptions,\n): Promise<NpSaveResult> {\n // Members can't author drafts / archive / schedule — those status\n // transitions are admin-side affordances. The status that\n // member-authored creates land in is governed by:\n // 1. The collection's `community.memberWrite.defaultStatus`\n // (default `\"published\"` — sites that want a moderation gate\n // flip to `\"pending\"`).\n // 2. The spam adapter's verdict on this individual write — `flag`\n // forces `\"pending\"` regardless of the default; `reject`\n // refuses the write entirely; `pass` accepts the default.\n // The API body's `_status` is always ignored.\n const config = getCollectionConfig(collection);\n\n // Cheap authorization checks BEFORE moderation (#139). Banned\n // members or members in collections that haven't opted into\n // member-write should never reach the (potentially-paid)\n // spam/profanity adapters. These same checks run again inside\n // `saveDocumentImpl`, but doing them here saves the\n // moderation round-trip on doomed requests.\n if (!config.community?.memberWrite?.create) {\n throw new NpForbiddenError(collection, \"create\");\n }\n const { assertNotBanned } = await import(\"../community/can.js\");\n await assertNotBanned(memberId);\n\n const defaultStatus: NpDocumentStatus =\n config.community?.memberWrite?.defaultStatus === \"pending\" ? \"pending\" : \"published\";\n\n const moderation = await runMemberDocModeration({\n collection,\n data,\n memberId,\n targetId: \"\",\n });\n const flaggedBy = moderation.flaggedBy;\n const spamStatus: NpDocumentStatus = flaggedBy.length > 0 ? \"pending\" : defaultStatus;\n\n const memberOptions: NpSaveOptions = { ...(options ?? {}), status: spamStatus };\n const result = await saveDocumentImpl(\n collection,\n null,\n data,\n { kind: \"member\", memberId },\n memberOptions,\n );\n\n const { applyReputation } = await import(\"../community/reputation.js\");\n const { recordAuditEvent } = await import(\"../community/audit.js\");\n const documentId = getRecordId(result.doc);\n // `document.flag` action when either adapter flagged this row.\n // A pending row that got there via `defaultStatus=\"pending\"` is\n // config-driven, not a per-row flag, so it stays under\n // `document.create`. The `sources` array tells mods which\n // adapter(s) flagged the row.\n await recordAuditEvent({\n actor: { kind: \"member\", memberId },\n action: flaggedBy.length > 0 ? \"document.flag\" : \"document.create\",\n targetType: collection,\n targetId: documentId,\n payload: {\n collectionSlug: collection,\n event: \"create\",\n ...(flaggedBy.length > 0 ? { sources: flaggedBy } : {}),\n ...(moderation.profanityVerdict ? { profanityVerdict: moderation.profanityVerdict } : {}),\n ...(moderation.spamVerdict ? { spamVerdict: moderation.spamVerdict } : {}),\n },\n });\n // Reputation only credits visible (i.e. `published`) creates.\n // Pending docs wait on a mod restore — at that point the\n // moderation surface can decide whether to retroactively credit.\n // Mirrors the `comment.created` semantic.\n if (spamStatus === \"published\") {\n await applyReputation(memberId, {\n kind: \"document.created\",\n collectionSlug: collection,\n documentId,\n memberId,\n });\n }\n\n // Phase 16.2 — @mention fan-out. Same gate as reputation: only\n // visible (`published`) creates fire. Pending docs wait on mod\n // restore so notifications can't surface text the public list\n // won't render.\n if (spamStatus === \"published\") {\n const { fanOutMentionNotifications } = await import(\"../community/mentions.js\");\n await fanOutMentionNotifications({\n actorMemberId: memberId,\n kind: \"document.mention\",\n data,\n payload: {\n collectionSlug: collection,\n documentId,\n },\n });\n }\n return result;\n}\n\ninterface MemberDocModerationResult {\n flaggedBy: Array<\"profanity\" | \"spam\">;\n profanityVerdict: { reason: string | null; metadata: Record<string, unknown> | null } | null;\n spamVerdict: { reason: string | null; metadata: Record<string, unknown> | null } | null;\n}\n\ninterface RunMemberDocModerationInput {\n collection: string;\n data: Record<string, unknown>;\n memberId: string;\n /** Empty string for create, the doc id for update. */\n targetId: string;\n}\n\n/**\n * Runs the profanity → spam adapter chain on a member-authored\n * document write (create or update). Shared between\n * `createMemberDocument` and `updateMemberDocument` so the\n * moderation gate can't drift between the two surfaces (#121).\n *\n * The moderation text is built from every text / textarea /\n * richText field present in `data` — `buildSearchVector` already\n * implements that walk for the FTS index, so we reuse it here\n * (same input set, different downstream consumer). Pre-fix this\n * function only saw `data.title`, so a member could keep a\n * benign title and put spam / slurs in the rich-text body and\n * the adapters would never see them (#119).\n *\n * Verdict semantics match the comment write path:\n * - reject → throws `NpValidationError`\n * - flag → returned with the source recorded in `flaggedBy`\n * - pass → returned with empty `flaggedBy`\n *\n * Adapter throws are fail-open (logged as warnings, treated as\n * pass) — same policy as comments and the original create-only\n * gate.\n */\nasync function runMemberDocModeration(\n input: RunMemberDocModerationInput,\n): Promise<MemberDocModerationResult> {\n const { collection, data, memberId, targetId } = input;\n const config = getCollectionConfig(collection);\n const { getSpamAdapter } = await import(\"../community/spam-adapter.js\");\n const { getProfanityAdapter } = await import(\"../community/profanity-adapter.js\");\n const { getLogger } = await import(\"../observability/logger.js\");\n\n // Walk every text / textarea / richText field in the patch.\n // Empty string when none of the moderated fields are touched\n // — the adapters then run on empty text, which by convention\n // passes (no content = no policy violation).\n const moderationText = buildSearchVector(config, data);\n const ctx = {\n memberId,\n targetType: collection,\n targetId,\n parentId: null,\n };\n\n let profanityVerdict: MemberDocModerationResult[\"profanityVerdict\"] = null;\n try {\n const verdict = await getProfanityAdapter().check(moderationText, ctx);\n if (verdict.kind === \"reject\") {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"body\",\n message: verdict.reason ?? \"Submission contains prohibited language\",\n },\n ]);\n }\n if (verdict.kind === \"flag\") {\n profanityVerdict = {\n reason: verdict.reason ?? null,\n metadata: verdict.metadata ?? null,\n };\n }\n } catch (err) {\n if (err instanceof NpValidationError) throw err;\n getLogger().warn(\"profanity adapter threw on doc write — treating as pass\", {\n error: err instanceof Error ? err.message : String(err),\n collection,\n memberId,\n });\n }\n\n let spamVerdict: MemberDocModerationResult[\"spamVerdict\"] = null;\n try {\n const verdict = await getSpamAdapter().check(moderationText, ctx);\n if (verdict.kind === \"reject\") {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"body\",\n message: verdict.reason ?? \"Submission rejected\",\n },\n ]);\n }\n if (verdict.kind === \"flag\") {\n spamVerdict = {\n reason: verdict.reason ?? null,\n metadata: verdict.metadata ?? null,\n };\n }\n } catch (err) {\n if (err instanceof NpValidationError) throw err;\n getLogger().warn(\"spam adapter threw on doc write — treating as pass\", {\n error: err instanceof Error ? err.message : String(err),\n collection,\n memberId,\n });\n }\n\n const flaggedBy: Array<\"profanity\" | \"spam\"> = [];\n if (profanityVerdict) flaggedBy.push(\"profanity\");\n if (spamVerdict) flaggedBy.push(\"spam\");\n\n return { flaggedBy, profanityVerdict, spamVerdict };\n}\n\n/**\n * Threaded state for `saveDocumentImpl`'s four concerns (#314).\n * Helpers add to this object as the request progresses through the\n * pipeline; the final concern (`firePostCommitHooks`) consumes the\n * accumulated context. The split makes it visible at review time\n * which step gates what.\n *\n * Some fields (`prepared`, `searchVector`, `publishTransition`) are\n * undefined until `prepareDocumentForWrite` runs; downstream helpers\n * can assume the prepare step has completed because the chain is\n * fixed in `saveDocumentImpl`.\n */\ninterface SaveContext {\n // === Setup, present from initSaveContext onward. ===\n collection: string;\n docId: string | null;\n validatedData: Record<string, unknown>;\n actor: SaveActor;\n options: NpSaveOptions | undefined;\n config: ReturnType<typeof getCollectionConfig>;\n registration: ReturnType<typeof getCollectionRegistration>;\n table: PgTable;\n db: DrizzleDatabaseLike;\n operation: \"create\" | \"update\";\n originalDoc: Record<string, unknown> | null;\n userForHooks: NpAuthUser | null;\n principal: NpHookPrincipal;\n // === Populated by prepareDocumentForWrite. ===\n hookData: Record<string, unknown>;\n prepared: PreparedDocumentData;\n searchVector: ReturnType<typeof buildWeightedSearchVectorSql>;\n publishTransition: boolean;\n unpublishTransition: boolean;\n now: Date;\n}\n\nasync function initSaveContext(\n collection: string,\n docId: string | null,\n data: Record<string, unknown>,\n actor: SaveActor,\n options: NpSaveOptions | undefined,\n): Promise<Omit<SaveContext, \"hookData\" | \"prepared\" | \"searchVector\" | \"publishTransition\" | \"unpublishTransition\" | \"now\">> {\n const config = getCollectionConfig(collection);\n const registration = getCollectionRegistration(collection);\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n const validatedData = toRecord(getCollectionZodSchema(config).parse(data));\n const operation: \"create\" | \"update\" = docId ? \"update\" : \"create\";\n const originalDoc = docId ? await getDocumentByIdInternal(db, table, collection, docId) : null;\n return {\n collection,\n docId,\n validatedData,\n actor,\n options,\n config,\n registration,\n table,\n db,\n operation,\n originalDoc,\n userForHooks: actorUserOrNull(actor),\n principal: actorPrincipal(actor),\n };\n}\n\n/**\n * Concern 1 — access checks. Staff writes go through the configured\n * `access.create` / `access.update` tree; member writes hit the\n * per-collection `community.memberWrite.create / update` opt-in\n * plus a ban check, plus an ownership check on update.\n *\n * Throws `NpForbiddenError` / `NpNotFoundError` on rejection.\n */\nasync function validateActorAccess(ctx: SaveContext): Promise<void> {\n if (ctx.actor.kind === \"staff\") {\n await assertWriteAccess(\n ctx.config,\n ctx.collection,\n ctx.operation,\n ctx.actor.user,\n ctx.validatedData,\n ctx.originalDoc,\n );\n return;\n }\n // Member actor. Phase 9.7a opened create; 9.7b opens update with\n // an owner-only check. Each transition has a separate opt-in\n // flag so a site can allow self-authoring without enabling\n // self-edit. Defer-load to avoid the community ↔ collections\n // import cycle.\n const { assertNotBanned } = await import(\"../community/can.js\");\n if (ctx.operation === \"create\") {\n if (!ctx.config.community?.memberWrite?.create) {\n throw new NpForbiddenError(ctx.collection, \"create\");\n }\n await assertNotBanned(ctx.actor.memberId);\n return;\n }\n // update — the doc must exist and must be authored by THIS\n // member (`member_author_id` matches). 404 / 403 disambiguate:\n // 404 when there's no row at all, 403 when the row belongs to\n // someone else (or to staff with `member_author_id = null`).\n if (!ctx.originalDoc) {\n throw new NpNotFoundError(ctx.collection, ctx.docId ?? \"unknown\");\n }\n if (!ctx.config.community?.memberWrite?.update) {\n throw new NpForbiddenError(ctx.collection, \"update\");\n }\n const authorId = (ctx.originalDoc as { memberAuthorId?: string | null }).memberAuthorId ?? null;\n if (authorId !== ctx.actor.memberId) {\n throw new NpForbiddenError(ctx.collection, \"update\");\n }\n await assertNotBanned(ctx.actor.memberId);\n}\n\n/**\n * Concern 2 — prepare the document for write. Runs the\n * collection's `beforeCreate` / `beforeUpdate` hooks, applies\n * slug generation, resolves i18n locale + translation group,\n * runs `prepareDocumentData`, stamps multi-site / member-author\n * columns, demotes future-dated `published` to `scheduled`,\n * builds the search-vector SQL fragment, and computes the\n * publish/unpublish transition.\n *\n * Mutates `ctx` to install the prepared fields.\n */\nasync function prepareDocumentForWrite(c: SaveContext): Promise<void> {\n c.hookData = await runHooks(\n c.operation === \"create\" ? c.config.hooks?.beforeCreate : c.config.hooks?.beforeUpdate,\n {\n data: c.validatedData,\n user: c.userForHooks,\n principal: c.principal,\n collection: c.collection,\n originalDoc: c.originalDoc,\n },\n );\n\n applySlugField(c.config, c.hookData, c.originalDoc);\n\n // Phase 12.1 — i18n collections need locale + translation\n // group resolved before the row is written. On creates the\n // locale defaults to `defaultLocale`, the translationGroupId\n // defaults to a new UUID. On updates those columns are\n // sticky — pulled from `originalDoc` so a body field can't\n // reassign them.\n let i18nResolved: { locale: string; translationGroupId: string } | null = null;\n if (c.config.i18n) {\n const i18n = getI18nConfig();\n if (!i18n) {\n throw new Error(\n `Collection \"${c.collection}\" is i18n-enabled but the framework has no i18n config (setI18nConfig was never called).`,\n );\n }\n if (c.operation === \"create\") {\n const requestedLocale = (c.hookData as { locale?: unknown }).locale;\n const locale =\n typeof requestedLocale === \"string\" && requestedLocale.length > 0\n ? requestedLocale\n : i18n.defaultLocale;\n if (!i18n.locales.includes(locale)) {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"locale\",\n message: `Locale \"${locale}\" is not configured. Allowed: ${i18n.locales.join(\", \")}.`,\n },\n ]);\n }\n const requestedGroup = (c.hookData as { translationGroupId?: unknown }).translationGroupId;\n const translationGroupId =\n typeof requestedGroup === \"string\" && requestedGroup.length > 0\n ? requestedGroup\n : randomUUID();\n i18nResolved = { locale, translationGroupId };\n } else {\n const original = c.originalDoc as { locale?: string; translationGroupId?: string } | null;\n if (!original?.locale || !original.translationGroupId) {\n throw new Error(\n `i18n collection \"${c.collection}\" doc ${c.docId} is missing locale/translationGroupId. The row predates i18n opt-in; backfill required.`,\n );\n }\n i18nResolved = {\n locale: original.locale,\n translationGroupId: original.translationGroupId,\n };\n }\n }\n\n c.prepared = prepareDocumentData(c.config.fields, c.hookData);\n if (c.options?.status) {\n c.prepared.mainData.status = c.options.status;\n }\n if (i18nResolved) {\n c.prepared.mainData.locale = i18nResolved.locale;\n c.prepared.mainData.translationGroupId = i18nResolved.translationGroupId;\n }\n\n // Phase 15.2 — multi-site scoping. Stamp every write with\n // the resolved site id. Creates pull from the request\n // context (or fall back to the default site for scripts /\n // workers / tests with no resolver). Updates inherit the\n // original row's site id — body fields can't reassign a doc.\n if (c.operation === \"create\") {\n const resolved = await getCurrentSiteId();\n c.prepared.mainData.siteId = resolved ?? NP_DEFAULT_SITE_ID;\n } else {\n const original = c.originalDoc as { siteId?: string } | null;\n c.prepared.mainData.siteId = original?.siteId ?? NP_DEFAULT_SITE_ID;\n }\n // Stamp / strip member_author_id. The column is generated only\n // when `community.memberWrite.create` is on; staff-authored docs\n // leave it null. Defense-in-depth on update: even though zod\n // strips unknown keys, we explicitly delete the field on member\n // updates so a body-injected value can't reassign authorship.\n if (c.actor.kind === \"member\") {\n if (c.operation === \"create\") {\n c.prepared.mainData.memberAuthorId = c.actor.memberId;\n } else {\n delete c.prepared.mainData.memberAuthorId;\n }\n }\n c.now = new Date();\n\n // Scheduled publishing: if the caller wants status=published but\n // publishedAt is in the future, demote to \"scheduled\" so the\n // public site doesn't render it until the scheduler flips it back.\n const desiredStatus = c.prepared.mainData.status as string | undefined;\n const publishedAtValue = c.prepared.mainData.publishedAt;\n if (desiredStatus === \"published\" && publishedAtValue instanceof Date && publishedAtValue > c.now) {\n c.prepared.mainData.status = \"scheduled\";\n }\n\n // Phase 10.7 — weighted tsvector so titles outrank body\n // matches at query time.\n c.searchVector = buildWeightedSearchVectorSql(c.config, c.hookData);\n\n // Publish-transition tracking for content:beforePublish /\n // afterPublish / beforeUnpublish hooks. Status precedence:\n // explicit prepared status > original doc (on update) >\n // \"published\" default (on create).\n const nextStatus =\n (c.prepared.mainData.status as string | undefined) ??\n (c.operation === \"update\"\n ? ((c.originalDoc?.status as string | undefined) ?? \"published\")\n : \"published\");\n const previousStatus = c.originalDoc?.status as string | undefined;\n const wasPublished = previousStatus === \"published\";\n const willBePublished = nextStatus === \"published\";\n c.publishTransition = !wasPublished && willBePublished;\n c.unpublishTransition = wasPublished && !willBePublished;\n}\n\n/**\n * Concern 3 — fire pre-write plugin hooks, then run the document\n * persistence inside one transaction (main row + child + join +\n * media-ref + revision). Returns the saved doc.\n */\nasync function persistDocumentTx(ctx: SaveContext): Promise<Record<string, unknown>> {\n await runHook(\n ctx.operation === \"create\" ? \"content:beforeCreate\" : \"content:beforeUpdate\",\n {\n collection: ctx.collection,\n data: ctx.hookData,\n originalDoc: ctx.originalDoc,\n user: ctx.userForHooks,\n principal: ctx.principal,\n operation: ctx.operation,\n },\n );\n if (ctx.publishTransition) {\n await runHook(\"content:beforePublish\", {\n collection: ctx.collection,\n data: ctx.hookData,\n originalDoc: ctx.originalDoc,\n user: ctx.userForHooks,\n principal: ctx.principal,\n });\n }\n if (ctx.unpublishTransition) {\n await runHook(\"content:beforeUnpublish\", {\n collection: ctx.collection,\n data: ctx.hookData,\n originalDoc: ctx.originalDoc,\n user: ctx.userForHooks,\n principal: ctx.principal,\n });\n }\n\n return ctx.db.transaction(async (tx) => {\n const persistedDoc: Record<string, unknown> =\n ctx.operation === \"update\"\n ? await updateMainDocument(\n tx,\n ctx.table,\n ctx.collection,\n ctx.docId,\n ctx.prepared.mainData,\n ctx.searchVector,\n ctx.config,\n ctx.userForHooks,\n ctx.now,\n )\n : await createMainDocument(\n tx,\n ctx.table,\n ctx.prepared.mainData,\n ctx.searchVector,\n ctx.config,\n ctx.userForHooks,\n ctx.now,\n );\n const persistedDocId = getRecordId(persistedDoc);\n\n await syncChildTables(tx, ctx.registration.childTables, ctx.prepared.childRows, persistedDocId);\n await syncJoinTables(tx, ctx.registration.joinTables, ctx.prepared.joinRows, persistedDocId);\n await syncMediaRefsForDocument(tx, ctx.collection, persistedDocId, ctx.config.fields, ctx.hookData);\n\n // Slug-rename history. When a slug-having collection's row\n // changes its slug, write an `oldSlug → newSlug` record so\n // the public-site catch-all can 301 old URLs (search-engine\n // indices, external links, bookmarks) to the new path. Doing\n // this inside the same tx keeps the redirect map consistent\n // with the actual doc — half-applied state isn't possible.\n // Skipped on creates and on updates that don't change slug.\n if (\n ctx.operation === \"update\" &&\n ctx.config.slugField &&\n ctx.originalDoc &&\n typeof ctx.originalDoc.slug === \"string\" &&\n typeof persistedDoc.slug === \"string\" &&\n ctx.originalDoc.slug.length > 0 &&\n ctx.originalDoc.slug !== persistedDoc.slug\n ) {\n const siteId = (persistedDoc.siteId as string | undefined) ?? NP_DEFAULT_SITE_ID;\n await tx.insert(npSlugHistory).values({\n siteId,\n collection: ctx.collection,\n documentId: String(persistedDocId),\n oldSlug: ctx.originalDoc.slug,\n newSlug: persistedDoc.slug,\n });\n }\n\n if (ctx.config.versions) {\n const docStatus = persistedDoc.status as string | undefined;\n // \"scheduled\" documents haven't actually gone live yet — treat their\n // revisions as drafts (they map to the pre-publish snapshot).\n const revisionStatus = docStatus === \"published\" ? \"published\" : \"draft\";\n const maxRevisions =\n typeof ctx.config.versions === \"object\" && ctx.config.versions.max !== undefined\n ? ctx.config.versions.max\n : undefined;\n await insertRevision(\n tx,\n ctx.collection,\n persistedDocId,\n ctx.operation,\n ctx.hookData,\n ctx.originalDoc,\n ctx.userForHooks,\n revisionStatus,\n maxRevisions,\n );\n }\n\n return persistedDoc;\n });\n}\n\n/**\n * Concern 4 — fire post-commit work: enqueue the\n * `content:afterSave` job, then `content:afterCreate` /\n * `content:afterUpdate` plugin hooks, plus `content:afterPublish`\n * on a publish transition. Each is wrapped in `runPostCommit` so\n * a hook error doesn't roll back the durable write.\n */\nasync function firePostCommitHooks(\n ctx: SaveContext,\n savedDoc: Record<string, unknown>,\n): Promise<void> {\n const savedDocId = getRecordId(savedDoc);\n const postCommitCtx = {\n collection: ctx.collection,\n documentId: savedDocId,\n operation: ctx.operation,\n };\n\n await runPostCommit(\"enqueue:content:afterSave\", postCommitCtx, () =>\n enqueueJob(\"content:afterSave\", {\n collection: ctx.collection,\n documentId: savedDocId,\n operation: ctx.operation,\n userId: actorUserId(ctx.actor),\n }),\n );\n\n const pluginHookName = ctx.operation === \"create\" ? \"content:afterCreate\" : \"content:afterUpdate\";\n await runPostCommit(`hook:${pluginHookName}`, postCommitCtx, () =>\n runHook(pluginHookName, {\n collection: ctx.collection,\n doc: savedDoc,\n operation: ctx.operation,\n user: ctx.userForHooks,\n principal: ctx.principal,\n }),\n );\n if (ctx.publishTransition) {\n await runPostCommit(\"hook:content:afterPublish\", postCommitCtx, () =>\n runHook(\"content:afterPublish\", {\n collection: ctx.collection,\n doc: savedDoc,\n operation: ctx.operation,\n user: ctx.userForHooks,\n principal: ctx.principal,\n }),\n );\n }\n}\n\nasync function saveDocumentImpl(\n collection: string,\n docId: string | null,\n data: Record<string, unknown>,\n actor: SaveActor,\n options?: NpSaveOptions,\n): Promise<NpSaveResult> {\n const ctxBase = await initSaveContext(collection, docId, data, actor, options);\n await validateActorAccess(ctxBase as SaveContext);\n const ctx = ctxBase as SaveContext;\n await prepareDocumentForWrite(ctx);\n const savedDoc = await persistDocumentTx(ctx);\n await firePostCommitHooks(ctx, savedDoc);\n return { doc: savedDoc, operation: ctx.operation };\n}\n\n/**\n * Persist an in-flight editor snapshot as a revision **without** touching\n * the main document row. Designed for client-side autosave loops: the\n * editor sends every few seconds while the user types, and a crash mid-\n * edit can be recovered by restoring the latest autosave revision.\n *\n * - Requires `versions.drafts` to be enabled on the collection.\n * - Optionally gated by `versions.drafts.autosave === true` (when\n * `versions` is the object form). Throws `NpValidationError` otherwise\n * so the API can return a tidy 4xx instead of silently writing.\n * - Skips the full zod validation that `saveDocument` runs — autosave\n * payloads may be temporarily incomplete (the user is still typing).\n * - Skips hooks, jobs, and revalidation: nothing is \"saved\" yet.\n * - Deduplicates against the most recent autosave: if the snapshot is\n * byte-identical to the previous autosave row, returns the existing\n * summary instead of writing a new one. Avoids unbounded autosave\n * rows during long idle edit sessions where react-hook-form fires\n * spurious \"change\" events.\n */\nexport async function autosaveRevision(\n collection: string,\n documentId: string,\n data: Record<string, unknown>,\n user: NpAuthUser,\n): Promise<{\n id: string;\n version: number;\n status: \"autosave\";\n createdAt: Date;\n reused: boolean;\n}> {\n const config = getCollectionConfig(collection);\n const registration = getCollectionRegistration(collection);\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n\n const drafts = config.versions?.drafts;\n if (!drafts) {\n throw new NpValidationError(\"Autosave not available\", [\n {\n field: \"collection\",\n message: `Collection \"${collection}\" has versions.drafts disabled — autosave is unavailable.`,\n },\n ]);\n }\n // `drafts: true` opts in to drafts but stays silent on autosave; we\n // require an explicit `{ autosave: true }` to avoid surprising existing\n // collections with extra DB writes per keystroke.\n const autosaveEnabled = typeof drafts === \"object\" && drafts.autosave === true;\n if (!autosaveEnabled) {\n throw new NpValidationError(\"Autosave disabled\", [\n {\n field: \"collection\",\n message: `Autosave is not enabled for \"${collection}\" — set versions.drafts.autosave = true.`,\n },\n ]);\n }\n\n const originalDoc = await getDocumentByIdInternal(db, table, collection, documentId);\n if (!originalDoc) {\n throw new NpNotFoundError(collection, documentId);\n }\n\n // Reuse the same access gate `saveDocument` runs for an update — autosave\n // is a write, even if it only lands in np_revisions.\n await assertWriteAccess(config, collection, \"update\", user, data, originalDoc);\n\n // Dedup against the latest autosave for this doc.\n const [latestAutosave] = (await db\n .select({\n id: npRevisions.id,\n version: npRevisions.version,\n snapshot: npRevisions.snapshot,\n createdAt: npRevisions.createdAt,\n })\n .from(npRevisions)\n .where(\n sql`${eq(npRevisions.collection, collection)} and ${eq(npRevisions.documentId, documentId)} and ${eq(npRevisions.status, \"autosave\")}`,\n )\n .orderBy(desc(npRevisions.version))\n .limit(1)) as Array<{\n id: string;\n version: number;\n snapshot: Record<string, unknown> | null;\n createdAt: Date;\n }>;\n if (latestAutosave && stableJson(latestAutosave.snapshot) === stableJson(data)) {\n return {\n id: latestAutosave.id,\n version: latestAutosave.version,\n status: \"autosave\",\n createdAt: latestAutosave.createdAt,\n reused: true,\n };\n }\n\n const maxRevisions =\n typeof config.versions === \"object\" && config.versions.max !== undefined\n ? config.versions.max\n : undefined;\n\n const inserted = await db.transaction(async (tx) => {\n const [revisionCount] = (await tx\n .select({ total: count() })\n .from(npRevisions)\n .where(\n sql`${eq(npRevisions.collection, collection)} and ${eq(npRevisions.documentId, documentId)}`,\n )) as Array<{ total: number | string }>;\n const nextVersion = Number(revisionCount?.total ?? 0) + 1;\n const createdAt = new Date();\n\n await tx.insert(npRevisions).values({\n collection,\n documentId,\n version: nextVersion,\n status: \"autosave\",\n snapshot: data,\n changedFields: getChangedFields(data, originalDoc, \"update\"),\n authorId: user.id,\n createdAt,\n });\n\n if (maxRevisions !== undefined && maxRevisions > 0 && nextVersion > maxRevisions) {\n const overflow = nextVersion - maxRevisions;\n const toDelete = (await tx\n .select({ id: npRevisions.id })\n .from(npRevisions)\n .where(\n sql`${eq(npRevisions.collection, collection)} and ${eq(npRevisions.documentId, documentId)}`,\n )\n .orderBy(asc(npRevisions.version))\n .limit(overflow)) as Array<{ id: string }>;\n if (toDelete.length > 0) {\n const ids = toDelete.map((r) => r.id);\n await tx.delete(npRevisions).where(sql`${npRevisions.id} = any(${ids}::uuid[])`);\n }\n }\n\n // Read back the row we just inserted to get its generated id —\n // `tx.insert(...).returning(...)` isn't part of our Drizzle adapter\n // interface, so a follow-up SELECT is the simplest portable path.\n const [row] = (await tx\n .select({ id: npRevisions.id })\n .from(npRevisions)\n .where(\n sql`${eq(npRevisions.collection, collection)} and ${eq(npRevisions.documentId, documentId)} and ${eq(npRevisions.version, nextVersion)}`,\n )\n .limit(1)) as Array<{ id: string }>;\n\n return { id: row?.id ?? \"\", version: nextVersion, createdAt };\n });\n // `registration` reference silences the unused-binding lint; we keep\n // the lookup early so misconfigured collections fail fast.\n void registration;\n\n return { ...inserted, status: \"autosave\", reused: false };\n}\n\nfunction stableJson(value: unknown): string {\n // JSON.stringify with deterministic key ordering is enough for dedup —\n // autosave payloads are user-edited records, not arbitrary structures.\n return JSON.stringify(value, (_key, val) => {\n if (val && typeof val === \"object\" && !Array.isArray(val)) {\n const sorted: Record<string, unknown> = {};\n for (const k of Object.keys(val).sort()) {\n sorted[k] = (val as Record<string, unknown>)[k];\n }\n return sorted;\n }\n return val;\n });\n}\n\nexport async function deleteDocument(\n collection: string,\n docId: string,\n user: NpAuthUser,\n): Promise<void> {\n return deleteDocumentImpl(collection, docId, { kind: \"staff\", user });\n}\n\n/**\n * Member-side delete. Owner-only — the existing row's\n * `member_author_id` must match the caller. Fires\n * `document.deleted` reputation event so adapters can debit the\n * author the same way `comment.deleted` debits commenters.\n *\n * The reputation event is gated on the row's status at delete\n * time: only `published` docs ever earned a `document.created`\n * credit (the create path withholds it for pending rows; promote\n * later backfills the credit). Issuing a `document.deleted`\n * debit for a row that was never credited would drive the\n * member negative for deleting their own not-yet-visible\n * content (#126). The audit row is unconditional — the operator\n * still wants to see \"member deleted X\".\n */\nexport async function deleteMemberDocument(\n collection: string,\n docId: string,\n memberId: string,\n): Promise<void> {\n // Read the current status BEFORE delete so we know whether a\n // `document.created` credit was ever granted. `deleteDocumentImpl`\n // also looks the row up internally, so this is a small redundant\n // SELECT — but the alternative (returning status from the impl)\n // would change a private API for one caller. Fine to repeat.\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n const original = await getDocumentByIdInternal(db, table, collection, docId);\n const wasPublished =\n typeof (original as { status?: unknown } | null)?.status === \"string\" &&\n (original as { status: string }).status === \"published\";\n\n await deleteDocumentImpl(collection, docId, { kind: \"member\", memberId });\n const { applyReputation } = await import(\"../community/reputation.js\");\n const { recordAuditEvent } = await import(\"../community/audit.js\");\n await recordAuditEvent({\n actor: { kind: \"member\", memberId },\n action: \"document.delete\",\n targetType: collection,\n targetId: docId,\n payload: {\n collectionSlug: collection,\n // Capture the status that was in effect at delete time so a\n // mod re-reading the audit log can tell \"they deleted a\n // pending submission\" from \"they retracted a published\n // post.\"\n previousStatus:\n typeof (original as { status?: unknown } | null)?.status === \"string\"\n ? (original as { status: string }).status\n : null,\n },\n });\n if (wasPublished) {\n await applyReputation(memberId, {\n kind: \"document.deleted\",\n collectionSlug: collection,\n documentId: docId,\n memberId,\n });\n }\n}\n\n/**\n * Staff promotion of a member-authored `pending` row to `published`\n * (Phase 9.7d). Closes the loop on the 9.7c moderation gate:\n * - the row's status flips to `published` (visible on the public\n * site immediately)\n * - the deferred `document.created` reputation event fires now,\n * crediting the author for content that was held in review\n * (mirrors how a comment promoted from `pending` would, in a\n * hypothetical comment-promote API — not implemented yet)\n * - audit log records `document.promote` with the staff actor\n * and the original member author in the payload\n *\n * Guards:\n * - 404 if the row doesn't exist\n * - 400 (validation) if the row isn't currently `pending`\n * - 400 (validation) if the row isn't member-authored\n * (`member_author_id` is null) — staff drafts use the standard\n * edit path\n *\n * Idempotence: a second promote on an already-`published` row 400s\n * rather than silently no-op'ing — the audit trail and reputation\n * backfill must run exactly once per row.\n */\nexport async function promoteMemberDocument(\n collection: string,\n docId: string,\n staffUserId: string,\n): Promise<NpSaveResult> {\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n const originalDoc = await getDocumentByIdInternal(db, table, collection, docId);\n if (!originalDoc) {\n throw new NpNotFoundError(collection, docId);\n }\n const status = (originalDoc as { status?: string }).status;\n if (status !== \"pending\") {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"status\",\n message: `Cannot promote: document is ${status ?? \"unknown\"}, expected pending`,\n },\n ]);\n }\n const memberAuthorId = (originalDoc as { memberAuthorId?: string | null }).memberAuthorId ?? null;\n if (!memberAuthorId) {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"memberAuthorId\",\n message: \"Cannot promote: document is not member-authored\",\n },\n ]);\n }\n\n // Conditional UPDATE on `status = 'pending'`. Two mods racing to\n // promote the same row would each pass the read-side check above\n // and each fire the audit + reputation events; conditioning on\n // status here means the second UPDATE returns zero rows and we\n // surface that as 400 — the row already moved on, no second\n // event-fire. Same pattern protects against an interleaved staff\n // PATCH that ran between our read and our write.\n //\n // Issue #367 — also pin `siteId` in the predicate. The read-side\n // `getDocumentByIdInternal` already enforced the site match, but\n // including siteId in the WHERE means even a stale resolver value\n // between the load and the update can't promote the wrong row.\n const requestSiteId = (await getCurrentSiteId()) ?? NP_DEFAULT_SITE_ID;\n const now = new Date();\n const updated = (await db\n .update(table)\n .set({ status: \"published\", updatedAt: now, updatedBy: staffUserId })\n .where(\n sql`${eq(getTableColumn(table, \"id\"), docId)} and ${eq(getTableColumn(table, \"status\"), \"pending\")} and ${eq(getTableColumn(table, \"siteId\"), requestSiteId)}`,\n )\n .returning()) as Array<Record<string, unknown>>;\n if (updated.length === 0) {\n // Either a concurrent promote already flipped this row, or\n // staff edited it out of `pending` between our read and our\n // write. Surface as a validation error — the caller should\n // re-fetch and retry only if they still want to act.\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"status\",\n message: \"Cannot promote: row is no longer pending (concurrent change)\",\n },\n ]);\n }\n const persistedDoc = toRecord(updated[0]);\n\n const { applyReputation } = await import(\"../community/reputation.js\");\n const { recordAuditEvent } = await import(\"../community/audit.js\");\n await recordAuditEvent({\n actor: { kind: \"staff\", userId: staffUserId },\n action: \"document.promote\",\n targetType: collection,\n targetId: docId,\n payload: {\n collectionSlug: collection,\n memberAuthorId,\n previousStatus: \"pending\",\n },\n });\n // Backfill the reputation credit that was withheld at create time\n // when status landed as pending. The adapter sees the same event\n // shape as a fresh member create — adapters that key off creation\n // time should consult the audit log, not infer from the event.\n await applyReputation(memberAuthorId, {\n kind: \"document.created\",\n collectionSlug: collection,\n documentId: docId,\n memberId: memberAuthorId,\n });\n\n return { doc: persistedDoc, operation: \"update\" };\n}\n\nasync function deleteDocumentImpl(\n collection: string,\n docId: string,\n actor: SaveActor,\n): Promise<void> {\n const config = getCollectionConfig(collection);\n const registration = getCollectionRegistration(collection);\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n const originalDoc = await getDocumentByIdInternal(db, table, collection, docId);\n\n // Without this guard the call returns success for non-existent ids:\n // hooks fire with `originalDoc = null`, the DELETE matches zero rows,\n // and the route returns 204. Bulk delete then records phantom ids as\n // succeeded. (#59)\n if (!originalDoc) {\n throw new NpNotFoundError(collection, docId);\n }\n\n if (actor.kind === \"staff\") {\n if (config.access?.delete) {\n const allowed = await config.access.delete({ user: actor.user, doc: originalDoc });\n if (!allowed) {\n throw new NpForbiddenError(collection, \"delete\");\n }\n }\n } else {\n // Member delete: opt-in flag plus owner check plus ban check.\n if (!config.community?.memberWrite?.delete) {\n throw new NpForbiddenError(collection, \"delete\");\n }\n const authorId = (originalDoc as { memberAuthorId?: string | null }).memberAuthorId ?? null;\n if (authorId !== actor.memberId) {\n throw new NpForbiddenError(collection, \"delete\");\n }\n const { assertNotBanned } = await import(\"../community/can.js\");\n await assertNotBanned(actor.memberId);\n }\n\n const userForHooks = actorUserOrNull(actor);\n const principal = actorPrincipal(actor);\n await runHooks(config.hooks?.beforeDelete, {\n data: originalDoc,\n user: userForHooks,\n principal,\n collection,\n originalDoc,\n });\n\n await runHook(\"content:beforeDelete\", {\n collection,\n doc: originalDoc,\n user: userForHooks,\n principal,\n });\n\n await db.transaction(async (tx) => {\n await deleteChildTables(tx, registration.childTables, docId);\n await deleteJoinTables(tx, registration.joinTables, docId);\n await tx\n .delete(npMediaRefs as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npMediaRefs as unknown as PgTable, \"collection\"), collection)} and ${eq(getTableColumn(npMediaRefs as unknown as PgTable, \"documentId\"), docId)}`,\n );\n // Phase 9.7m: cascade comments + reactions on the deleted doc.\n // The polymorphic `(target_type, target_id)` shape on\n // `np_comments` / `np_reactions` doesn't have a DB-level FK\n // (it can't — the target table varies per row), so without an\n // explicit cleanup these rows would orphan once the parent\n // doc was gone. Order matters: reactions targeting the comments\n // (`target_type='comment'`) must go before the comments\n // themselves, since after the comment rows are gone we can't\n // discover their ids anymore. Top-level comments and replies\n // both carry `target_id=$docId`, so a single SELECT covers both.\n const commentIdRows = (await tx\n .select({\n id: getTableColumn(npComments as unknown as PgTable, \"id\"),\n })\n .from(npComments as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npComments as unknown as PgTable, \"targetType\"), collection)} and ${eq(getTableColumn(npComments as unknown as PgTable, \"targetId\"), docId)}`,\n )) as Array<{ id: string }>;\n if (commentIdRows.length > 0) {\n const commentIds = commentIdRows.map((row) => row.id);\n await tx\n .delete(npReactions as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npReactions as unknown as PgTable, \"targetType\"), \"comment\")} and ${inArray(getTableColumn(npReactions as unknown as PgTable, \"targetId\"), commentIds)}`,\n );\n // Phase 9.7q: same orphan story for `np_reports` — a member\n // who reported one of these comments would otherwise be left\n // with a row pointing at a non-existent comment id. The\n // existing audit row carries enough context for after-the-\n // fact tracing, so the report itself can go.\n await tx\n .delete(npReports as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npReports as unknown as PgTable, \"targetType\"), \"comment\")} and ${inArray(getTableColumn(npReports as unknown as PgTable, \"targetId\"), commentIds)}`,\n );\n }\n await tx\n .delete(npComments as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npComments as unknown as PgTable, \"targetType\"), collection)} and ${eq(getTableColumn(npComments as unknown as PgTable, \"targetId\"), docId)}`,\n );\n await tx\n .delete(npReactions as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npReactions as unknown as PgTable, \"targetType\"), collection)} and ${eq(getTableColumn(npReactions as unknown as PgTable, \"targetId\"), docId)}`,\n );\n // Doc-level reports (sites that file `target_type=$collection`\n // reports against a post / discussion). The shipped report API\n // today only files against comments + members, but the schema\n // is polymorphic — a future surface could add doc-level reports\n // and this cascade keeps that case correct from day one.\n await tx\n .delete(npReports as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npReports as unknown as PgTable, \"targetType\"), collection)} and ${eq(getTableColumn(npReports as unknown as PgTable, \"targetId\"), docId)}`,\n );\n await tx.delete(table).where(eq(getTableColumn(table, \"id\"), docId));\n });\n\n const postCommitCtx = { collection, documentId: docId, operation: \"delete\" };\n await runPostCommit(\"enqueue:content:afterDelete\", postCommitCtx, () =>\n enqueueJob(\"content:afterDelete\", {\n collection,\n documentId: docId,\n userId: actorUserId(actor),\n }),\n );\n\n await runPostCommit(\"hook:content:afterDelete\", postCommitCtx, () =>\n runHook(\"content:afterDelete\", {\n collection,\n documentId: docId,\n user: userForHooks,\n principal,\n }),\n );\n}\n\nexport async function findDocuments<T extends object = Record<string, unknown>>(\n collection: string,\n options: NpFindOptions<NoInfer<T>>,\n user?: NpAuthUser,\n): Promise<NpFindResult<T>> {\n const config = getCollectionConfig(collection);\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n const page = normalizePage(options.page);\n const limit = normalizeLimit(options.limit);\n const offset = (page - 1) * limit;\n\n await assertReadAccess(config, collection, user ?? null);\n\n // Phase 12.1 — i18n collections honor the top-level `locale`\n // option as an additional `locale = $1` filter. Non-i18n\n // collections silently drop it (the column doesn't exist;\n // forwarding it would 500 on the SQL parse).\n let effectiveWhere: Record<string, unknown> = options.where ?? {};\n if (config.i18n && options.locale) {\n effectiveWhere = { ...effectiveWhere, locale: options.locale };\n }\n\n // Phase 15.2 — multi-site scoping. Reads filter by the\n // resolved site id so cross-site content can't leak. The\n // resolver returns null in non-request contexts (workers,\n // scripts, tests with no resolver wired) — those default\n // to the framework's `default` site. Callers that want\n // cross-site reads (super-admin search, bulk export) can\n // pass `siteId: \"*\"` or override `where.siteId` directly,\n // which the dedicated where-clause builder forwards as-is.\n if (effectiveWhere.siteId === undefined) {\n const resolved = await getCurrentSiteId();\n effectiveWhere = {\n ...effectiveWhere,\n siteId: resolved ?? NP_DEFAULT_SITE_ID,\n };\n } else if (effectiveWhere.siteId === \"*\") {\n // Sentinel: drop the filter entirely (admin-side\n // cross-site queries).\n const { siteId: _siteId, ...rest } = effectiveWhere;\n void _siteId;\n effectiveWhere = rest;\n }\n\n // Phase 21.17 — per-doc visibility filter. Anonymous reads\n // (no `user` argument, e.g. site-side `findDocuments` from\n // the catch-all renderer or the sitemap) auto-restrict to\n // `visibility = \"public\"` so a private row never leaks to\n // a crawler / unauthenticated visitor. Authenticated\n // principals (any signed-in member or staff) see both\n // public and private — matching WordPress's \"logged-in\n // users see private posts\" semantics. Callers that want\n // explicit control (admin queries, bulk export) pass\n // `where.visibility` and bypass the gate.\n if (effectiveWhere.visibility === undefined && !user) {\n effectiveWhere = { ...effectiveWhere, visibility: \"public\" };\n } else if (effectiveWhere.visibility === \"*\") {\n const { visibility: _vis, ...rest } = effectiveWhere;\n void _vis;\n effectiveWhere = rest;\n }\n\n const effectiveOptions: NpFindOptions = {\n ...options,\n where: effectiveWhere,\n };\n const conditions = buildQueryConditions(table, effectiveOptions);\n const whereClause = combineConditions(conditions);\n\n const docs = await executeFindQuery(db, table, options, whereClause, limit, offset);\n const totalResult = (await (whereClause\n ? db.select({ total: count() }).from(table).where(whereClause)\n : db.select({ total: count() }).from(table).limit(1))) as Array<{ total: number | string }>;\n const totalDocs = Number(totalResult[0]?.total ?? 0);\n const totalPages = totalDocs === 0 ? 0 : Math.ceil(totalDocs / limit);\n\n return {\n // Runtime rows are `Record<string, unknown>`; the generic `T`\n // is a structural promise — generated wrapper functions narrow\n // it to the per-collection document shape. We don't validate\n // at runtime (Drizzle has already shaped the row to the table\n // schema, which the generator derived from the same field\n // configs T was generated from).\n docs: docs as unknown as T[],\n totalDocs,\n totalPages,\n page,\n limit,\n hasNextPage: page < totalPages,\n hasPrevPage: page > 1 && totalDocs > 0,\n };\n}\n\nexport async function getDocumentById<T extends object = Record<string, unknown>>(\n collection: string,\n id: string,\n user?: NpAuthUser,\n): Promise<T | null> {\n const config = getCollectionConfig(collection);\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n const doc = await getDocumentByIdOptional(db, table, id);\n\n if (!doc) {\n return null;\n }\n\n // Issue #367 — even read-by-id has to honor the tenant boundary\n // before access.read fires. A site A caller naming a site B doc\n // must not get a site B read decision back. Throw `Forbidden\n // cross-site` to match the existing pattern callers (e.g.\n // createComment, the sister-PR community fixes #362–#364) already\n // assert against.\n const requestSiteId = (await getCurrentSiteId()) ?? NP_DEFAULT_SITE_ID;\n const docSiteId =\n typeof doc.siteId === \"string\" && doc.siteId.length > 0\n ? doc.siteId\n : NP_DEFAULT_SITE_ID;\n if (docSiteId !== requestSiteId) {\n throw new NpForbiddenError(collection, \"cross-site\");\n }\n\n if (config.access?.read) {\n const allowed = await config.access.read({ user: user ?? null, doc });\n if (!allowed) {\n throw new NpForbiddenError(collection, \"read\");\n }\n }\n\n return doc as unknown as T;\n}\n\nasync function assertWriteAccess(\n config: NpCollectionConfig,\n collection: string,\n operation: NpSaveResult[\"operation\"],\n user: NpAuthUser,\n data: Record<string, unknown>,\n originalDoc: Record<string, unknown> | null,\n): Promise<void> {\n const access = operation === \"create\" ? config.access?.create : config.access?.update;\n\n if (!access) {\n return;\n }\n\n const allowed = await access({ user, doc: originalDoc ?? undefined, data });\n\n if (!allowed) {\n throw new NpForbiddenError(collection, operation);\n }\n}\n\nasync function assertReadAccess(\n config: NpCollectionConfig,\n collection: string,\n user: NpAuthUser | null,\n): Promise<void> {\n if (!config.access?.read) {\n return;\n }\n\n const allowed = await config.access.read({ user });\n\n if (!allowed) {\n throw new NpForbiddenError(collection, \"read\");\n }\n}\n\nasync function runHooks(\n hooks: NpCollectionHook[] | undefined,\n args: Parameters<NpCollectionHook>[0],\n): Promise<Record<string, unknown>> {\n let nextData = args.data;\n\n for (const hook of hooks ?? []) {\n nextData = await hook({\n ...args,\n data: nextData,\n });\n }\n\n return nextData;\n}\n\nasync function createMainDocument(\n tx: DrizzleTransactionLike,\n table: PgTable,\n mainData: Record<string, unknown>,\n searchVectorSql: SQL,\n config: NpCollectionConfig,\n user: NpAuthUser | null,\n now: Date,\n): Promise<Record<string, unknown>> {\n // Member writes (`user === null`) leave `createdBy` / `updatedBy`\n // unset so the FK to `np_users` stays null. The audit log captures\n // the actual member; readers that need authorship for member-\n // authored docs should join through the dedicated `member_author_id`\n // column (codegen'd onto every collection that opts into\n // `community.memberWrite.create`).\n const values: Record<string, unknown> = {\n id: randomUUID(),\n status: \"published\",\n ...mainData,\n createdBy: user?.id ?? null,\n updatedBy: user?.id ?? null,\n // Phase 10.7 — composed setweight() tsvector so titles\n // outrank body matches at query time. The 11.x\n // to_tsvector wrap (so colon-containing content doesn't\n // crash the cast) is preserved inside each setweight call\n // by buildWeightedSearchVectorSql.\n searchVector: searchVectorSql,\n };\n\n if (config.timestamps !== false) {\n values.createdAt = now;\n values.updatedAt = now;\n }\n\n const [created] = await tx.insert(table).values(values).returning();\n\n return toRecord(created);\n}\n\nasync function updateMainDocument(\n tx: DrizzleTransactionLike,\n table: PgTable,\n collection: string,\n docId: string | null,\n mainData: Record<string, unknown>,\n searchVectorSql: SQL,\n config: NpCollectionConfig,\n user: NpAuthUser | null,\n now: Date,\n): Promise<Record<string, unknown>> {\n if (!docId) {\n throw new NpNotFoundError(collection, \"unknown\");\n }\n\n const values: Record<string, unknown> = {\n ...mainData,\n updatedBy: user?.id ?? null,\n // Phase 10.7 — see createMainDocument: weighted setweight()\n // tsvector preserves the 11.x to_tsvector safety AND adds\n // title boost.\n searchVector: searchVectorSql,\n };\n\n if (config.timestamps !== false) {\n values.updatedAt = now;\n }\n\n const [updated] = await tx\n .update(table)\n .set(values)\n .where(eq(getTableColumn(table, \"id\"), docId))\n .returning();\n\n if (!updated) {\n throw new NpNotFoundError(collection, docId);\n }\n\n return toRecord(updated);\n}\n\nasync function syncChildTables(\n tx: DrizzleTransactionLike,\n childTables: Record<string, unknown> | undefined,\n childRows: Record<string, Record<string, unknown>[]>,\n documentId: string,\n): Promise<void> {\n for (const [fieldPath, rows] of Object.entries(childRows)) {\n const table = resolveRelatedTable(childTables, fieldPath);\n\n if (!table) {\n continue;\n }\n\n const pgTable = table as PgTable;\n const parentColumnName = findParentColumnName(pgTable, [\"parentId\"]);\n await tx.delete(pgTable).where(eq(getTableColumn(pgTable, parentColumnName), documentId));\n\n if (rows.length === 0) {\n continue;\n }\n\n const values = rows.map((row, index) => ({\n id: randomUUID(),\n ...row,\n [parentColumnName]: documentId,\n order: index,\n }));\n\n await tx.insert(pgTable).values(values);\n }\n}\n\nasync function syncJoinTables(\n tx: DrizzleTransactionLike,\n joinTables: Record<string, unknown> | undefined,\n joinRows: Record<string, string[]>,\n documentId: string,\n): Promise<void> {\n for (const [fieldPath, ids] of Object.entries(joinRows)) {\n const table = resolveRelatedTable(joinTables, fieldPath);\n\n if (!table) {\n continue;\n }\n\n const pgTable = table as PgTable;\n const parentColumnName = findParentColumnName(pgTable, [\"parentId\"]);\n await tx.delete(pgTable).where(eq(getTableColumn(pgTable, parentColumnName), documentId));\n\n if (ids.length === 0) {\n continue;\n }\n\n const values = ids.map((targetId, index) => ({\n id: randomUUID(),\n [parentColumnName]: documentId,\n targetId,\n order: index,\n }));\n\n await tx.insert(pgTable).values(values);\n }\n}\n\nasync function deleteChildTables(\n tx: DrizzleTransactionLike,\n childTables: Record<string, unknown> | undefined,\n documentId: string,\n): Promise<void> {\n for (const table of Object.values(childTables ?? {})) {\n const pgTable = table as PgTable;\n const parentColumnName = findParentColumnName(pgTable, [\"parentId\"]);\n await tx.delete(pgTable).where(eq(getTableColumn(pgTable, parentColumnName), documentId));\n }\n}\n\nasync function deleteJoinTables(\n tx: DrizzleTransactionLike,\n joinTables: Record<string, unknown> | undefined,\n documentId: string,\n): Promise<void> {\n for (const table of Object.values(joinTables ?? {})) {\n const pgTable = table as PgTable;\n const parentColumnName = findParentColumnName(pgTable, [\"parentId\"]);\n await tx.delete(pgTable).where(eq(getTableColumn(pgTable, parentColumnName), documentId));\n }\n}\n\nasync function insertRevision(\n tx: DrizzleTransactionLike,\n collection: string,\n documentId: string,\n operation: NpSaveResult[\"operation\"],\n data: Record<string, unknown>,\n originalDoc: Record<string, unknown> | null,\n user: NpAuthUser | null,\n status: string,\n maxRevisions?: number,\n): Promise<void> {\n const revisionConditions = sql`${eq(npRevisions.collection, collection)} and ${eq(npRevisions.documentId, documentId)}`;\n const [revisionCount] = (await tx\n .select({ total: count() })\n .from(npRevisions)\n .where(revisionConditions)) as Array<{ total: number | string }>;\n\n await tx.insert(npRevisions).values({\n collection,\n documentId,\n version: Number(revisionCount?.total ?? 0) + 1,\n status,\n snapshot: data,\n changedFields: getChangedFields(data, originalDoc, operation),\n // `authorId` references np_users; member-authored revisions\n // store null and the audit log carries the actual member id.\n authorId: user?.id ?? null,\n createdAt: new Date(),\n });\n\n // Enforce versions.max: drop the oldest revisions so this doc never\n // accumulates more than `maxRevisions` rows. Runs in the same tx as the\n // insert so the row count is stable against races.\n if (maxRevisions !== undefined && maxRevisions > 0) {\n const currentCount = Number(revisionCount?.total ?? 0) + 1;\n const overflow = currentCount - maxRevisions;\n if (overflow > 0) {\n // Select the oldest `overflow` revision ids and delete them. Postgres\n // doesn't support DELETE with LIMIT directly but `id IN (subquery)`\n // works fine.\n const toDelete = (await tx\n .select({ id: npRevisions.id })\n .from(npRevisions)\n .where(revisionConditions)\n .orderBy(asc(npRevisions.version))\n .limit(overflow)) as Array<{ id: string }>;\n\n if (toDelete.length > 0) {\n const ids = toDelete.map((r) => r.id);\n await tx.delete(npRevisions).where(sql`${npRevisions.id} = any(${ids}::uuid[])`);\n }\n }\n }\n}\n\nfunction buildQueryConditions(table: PgTable, options: NpFindOptions): QueryCondition[] {\n const conditions: QueryCondition[] = [];\n\n if (options.where) {\n for (const [field, value] of Object.entries(options.where)) {\n if (value === undefined) {\n continue;\n }\n\n // Array values → `IN (...)`. Empty array means \"match\n // nothing\" — emit a tautologically-false condition so the\n // overall query short-circuits to zero rows. (Without this\n // guard, Drizzle's `inArray(col, [])` produces `col IN ()`,\n // which Postgres rejects with a syntax error.)\n if (Array.isArray(value)) {\n if (value.length === 0) {\n conditions.push(sql`false`);\n } else {\n conditions.push(inArray(getTableColumn(table, field), value));\n }\n continue;\n }\n\n conditions.push(eq(getTableColumn(table, field), value));\n }\n }\n\n if (options.search) {\n conditions.push(\n sql`${getTableColumn(table, \"searchVector\")} @@ plainto_tsquery('english', ${options.search})`,\n );\n }\n\n return conditions;\n}\n\nasync function executeFindQuery(\n db: DrizzleDatabaseLike,\n table: PgTable,\n options: NpFindOptions,\n whereClause: ReturnType<typeof sql> | undefined,\n limit: number,\n offset: number,\n): Promise<Record<string, unknown>[]> {\n if (options.search) {\n const query = whereClause\n ? db\n .select()\n .from(table)\n .where(whereClause)\n .orderBy(\n sql`ts_rank(${getTableColumn(table, \"searchVector\")}, plainto_tsquery('english', ${options.search})) DESC`,\n )\n .limit(limit)\n .offset(offset)\n : db\n .select()\n .from(table)\n .orderBy(\n sql`ts_rank(${getTableColumn(table, \"searchVector\")}, plainto_tsquery('english', ${options.search})) DESC`,\n )\n .limit(limit)\n .offset(offset);\n\n return (await query) as Record<string, unknown>[];\n }\n\n const orderClause = getSortOrderClause(table, options.sort);\n\n if (whereClause && orderClause) {\n return (await db\n .select()\n .from(table)\n .where(whereClause)\n .orderBy(orderClause)\n .limit(limit)\n .offset(offset)) as Record<string, unknown>[];\n }\n\n if (whereClause) {\n return (await db.select().from(table).where(whereClause).limit(limit).offset(offset)) as Record<\n string,\n unknown\n >[];\n }\n\n if (orderClause) {\n return (await db\n .select()\n .from(table)\n .orderBy(orderClause)\n .limit(limit)\n .offset(offset)) as Record<string, unknown>[];\n }\n\n return (await db.select().from(table).limit(limit).offset(offset)) as Record<string, unknown>[];\n}\n\nfunction getSortOrderClause(\n table: PgTable,\n sortValue: string | undefined,\n): ReturnType<typeof sql> | undefined {\n const sort = sortValue?.trim();\n\n if (!sort) {\n return undefined;\n }\n\n const isDescending = sort.startsWith(\"-\");\n const field = isDescending ? sort.slice(1) : sort;\n const column = getTableColumn(table, field);\n\n return isDescending ? desc(column) : asc(column);\n}\n\n/**\n * Issue #367 — by-id loader for write paths and admin reads.\n *\n * `findDocuments` (the list path) has been site-scoped since Phase\n * 18, but every by-id load was id-only. A staff user with a foreign\n * doc id could reach `getDocumentById`, `saveDocument`,\n * `deleteDocument`, `promoteMemberDocument`, or\n * `createTranslation` outside their tenant. By default this loader\n * now compares the loaded row's `siteId` to the request's resolved\n * site and throws `NpForbiddenError(collection, \"cross-site\")` on\n * divergence.\n *\n * Cross-site is opt-in via `{ allowCrossSite: true }`. The legitimate\n * users today are background jobs / scripts that run without a\n * request site context (the wp-importer wraps its own\n * `withCurrentSite`, so it stays on the default path).\n */\nasync function getDocumentByIdInternal(\n db: DrizzleDatabaseLike,\n table: PgTable,\n collection: string,\n id: string,\n options?: { allowCrossSite?: boolean },\n): Promise<Record<string, unknown>> {\n const doc = await getDocumentByIdOptional(db, table, id);\n\n if (!doc) {\n throw new NpNotFoundError(collection, id);\n }\n\n if (!options?.allowCrossSite) {\n const requestSiteId = (await getCurrentSiteId()) ?? NP_DEFAULT_SITE_ID;\n const docSiteId =\n typeof doc.siteId === \"string\" && doc.siteId.length > 0\n ? doc.siteId\n : NP_DEFAULT_SITE_ID;\n if (docSiteId !== requestSiteId) {\n throw new NpForbiddenError(collection, \"cross-site\");\n }\n }\n\n return doc;\n}\n\nasync function getDocumentByIdOptional(\n db: DrizzleDatabaseLike,\n table: PgTable,\n id: string,\n): Promise<Record<string, unknown> | null> {\n const [doc] = await db\n .select()\n .from(table)\n .where(eq(getTableColumn(table, \"id\"), id))\n .limit(1);\n return doc ? toRecord(doc) : null;\n}\n\nfunction prepareDocumentData(\n fields: NpFieldConfig[],\n data: Record<string, unknown>,\n): PreparedDocumentData {\n const prepared: PreparedDocumentData = {\n mainData: {},\n childRows: {},\n joinRows: {},\n };\n\n collectPreparedDocumentData(fields, data, prepared, []);\n\n if (typeof data.slug === \"string\") {\n prepared.mainData.slug = data.slug;\n }\n // i18n columns aren't fields and aren't auto-emitted by\n // collectPreparedDocumentData. Let them through so the\n // caller (saveDocumentImpl) can persist the resolved locale\n // / translationGroupId.\n if (typeof data.locale === \"string\") {\n prepared.mainData.locale = data.locale;\n }\n if (typeof data.translationGroupId === \"string\") {\n prepared.mainData.translationGroupId = data.translationGroupId;\n }\n // Phase 15.2 — siteId is also non-field but framework-managed.\n if (typeof data.siteId === \"string\") {\n prepared.mainData.siteId = data.siteId;\n }\n // Phase 21.17 — visibility is a non-field framework-managed\n // column (codegen'd onto every collection by `getBaseColumns`).\n // Let it through so `createMainDocument` / `updateMainDocument`\n // can persist it; the Zod schema already validated the value\n // is `\"public\" | \"private\"`.\n if (typeof data.visibility === \"string\") {\n prepared.mainData.visibility = data.visibility;\n }\n\n return prepared;\n}\n\nfunction collectPreparedDocumentData(\n fields: NpFieldConfig[],\n data: Record<string, unknown>,\n prepared: PreparedDocumentData,\n prefix: string[],\n): void {\n for (const field of fields) {\n if (field.type === \"row\" || field.type === \"collapsible\") {\n collectPreparedDocumentData(field.fields, data, prepared, prefix);\n continue;\n }\n\n if (field.type === \"group\") {\n const groupValue = toOptionalRecord(data[field.name]);\n if (groupValue) {\n collectPreparedDocumentData(field.fields, groupValue, prepared, [...prefix, field.name]);\n }\n continue;\n }\n\n const fieldPath = [...prefix, field.name];\n const fieldKey = fieldPath.join(\".\");\n const value = data[field.name];\n\n if (field.type === \"array\") {\n prepared.childRows[fieldKey] = normalizeChildRows(field.fields, value);\n continue;\n }\n\n if (field.type === \"relationship\" && field.hasMany) {\n prepared.joinRows[fieldKey] = normalizeJoinIds(value);\n continue;\n }\n\n prepared.mainData[getFlattenedFieldName(prefix, field.name)] = value ?? null;\n }\n}\n\nfunction normalizeChildRows(fields: NpFieldConfig[], value: unknown): Record<string, unknown>[] {\n if (!Array.isArray(value)) {\n return [];\n }\n\n return value.map((item) => {\n const row = toOptionalRecord(item) ?? {};\n const prepared: PreparedDocumentData = {\n mainData: {},\n childRows: {},\n joinRows: {},\n };\n\n collectPreparedDocumentData(fields, row, prepared, []);\n return prepared.mainData;\n });\n}\n\nfunction normalizeJoinIds(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nasync function syncMediaRefsForDocument(\n tx: DrizzleTransactionLike,\n collection: string,\n documentId: string,\n fields: NpFieldConfig[],\n data: Record<string, unknown>,\n): Promise<void> {\n const refs = extractMediaIdsFromFields(fields, data, []);\n\n if (refs.length === 0) {\n await tx\n .delete(npMediaRefs as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npMediaRefs as unknown as PgTable, \"collection\"), collection)} and ${eq(getTableColumn(npMediaRefs as unknown as PgTable, \"documentId\"), documentId)}`,\n );\n return;\n }\n\n await tx\n .delete(npMediaRefs as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npMediaRefs as unknown as PgTable, \"collection\"), collection)} and ${eq(getTableColumn(npMediaRefs as unknown as PgTable, \"documentId\"), documentId)}`,\n );\n\n const values = refs.map((ref) => ({\n id: randomUUID(),\n mediaId: ref.mediaId,\n collection,\n documentId,\n field: ref.field,\n }));\n\n await tx.insert(npMediaRefs as unknown as PgTable).values(values);\n}\n\nfunction extractMediaIdsFromFields(\n fields: NpFieldConfig[],\n data: Record<string, unknown>,\n prefix: string[],\n): Array<{ mediaId: string; field: string }> {\n const refs: Array<{ mediaId: string; field: string }> = [];\n\n for (const field of fields) {\n if (field.type === \"row\" || field.type === \"collapsible\") {\n refs.push(...extractMediaIdsFromFields(field.fields, data, prefix));\n continue;\n }\n\n if (field.type === \"group\") {\n const groupData = toOptionalRecord(data[field.name]);\n if (groupData) {\n refs.push(...extractMediaIdsFromFields(field.fields, groupData, [...prefix, field.name]));\n }\n continue;\n }\n\n const fieldPath = [...prefix, field.name].join(\".\");\n\n if (field.type === \"upload\") {\n const mediaId = data[field.name];\n if (typeof mediaId === \"string\" && mediaId.length > 0) {\n refs.push({ mediaId, field: fieldPath });\n }\n continue;\n }\n\n if (field.type === \"richText\") {\n const richTextValue = data[field.name];\n if (richTextValue && typeof richTextValue === \"object\") {\n refs.push(...extractMediaIdsFromLexicalJson(richTextValue, fieldPath));\n }\n continue;\n }\n\n if (field.type === \"array\") {\n const arrayValue = data[field.name];\n if (Array.isArray(arrayValue)) {\n for (const item of arrayValue) {\n const itemRecord = toOptionalRecord(item);\n if (itemRecord) {\n refs.push(\n ...extractMediaIdsFromFields(field.fields, itemRecord, [...prefix, field.name]),\n );\n }\n }\n }\n continue;\n }\n\n if (field.type === \"blocks\") {\n const blocksValue = data[field.name];\n if (Array.isArray(blocksValue)) {\n for (const block of blocksValue) {\n const blockRecord = toOptionalRecord(block);\n if (blockRecord) {\n extractBlockMediaIds(blockRecord, fieldPath, refs);\n }\n }\n }\n continue;\n }\n }\n\n return refs;\n}\n\nfunction extractMediaIdsFromLexicalJson(\n node: unknown,\n fieldPath: string,\n): Array<{ mediaId: string; field: string }> {\n const refs: Array<{ mediaId: string; field: string }> = [];\n\n if (!node || typeof node !== \"object\") {\n return refs;\n }\n\n const record = node as Record<string, unknown>;\n\n if (record.type === \"image\" || record.type === \"upload\") {\n const mediaId = record.mediaId ?? record.value;\n if (typeof mediaId === \"string\" && mediaId.length > 0) {\n refs.push({ mediaId, field: fieldPath });\n }\n }\n\n const children = record.children ?? toOptionalRecord(record.root)?.children;\n if (Array.isArray(children)) {\n for (const child of children) {\n refs.push(...extractMediaIdsFromLexicalJson(child, fieldPath));\n }\n }\n\n return refs;\n}\n\nfunction extractBlockMediaIds(\n block: Record<string, unknown>,\n fieldPath: string,\n refs: Array<{ mediaId: string; field: string }>,\n): void {\n for (const [key, value] of Object.entries(block)) {\n if (key === \"blockType\" || key === \"id\") {\n continue;\n }\n\n if (typeof value === \"string\" && isUuid(value)) {\n refs.push({ mediaId: value, field: `${fieldPath}.${key}` });\n }\n }\n}\n\nfunction isUuid(value: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);\n}\n\nfunction getChangedFields(\n data: Record<string, unknown>,\n originalDoc: Record<string, unknown> | null,\n operation: NpSaveResult[\"operation\"],\n): string[] {\n if (operation === \"create\" || !originalDoc) {\n return Object.keys(data);\n }\n\n return Object.keys(data).filter((field) => !Object.is(data[field], originalDoc[field]));\n}\n\nfunction combineConditions(conditions: QueryCondition[]): ReturnType<typeof sql> | undefined {\n if (conditions.length === 0) {\n return undefined;\n }\n\n return sql`${sql.join(conditions, sql` and `)}`;\n}\n\nfunction resolveRelatedTable(\n tables: Record<string, unknown> | undefined,\n fieldPath: string,\n): unknown {\n return tables?.[fieldPath] ?? tables?.[fieldPath.split(\".\").at(-1) ?? fieldPath];\n}\n\nfunction findParentColumnName(table: PgTable, preferred: string[]): string {\n const keys = Object.keys(table as unknown as Record<string, unknown>);\n\n for (const key of preferred) {\n if (keys.includes(key)) {\n return key;\n }\n }\n\n const derived = keys.find(\n (key) => key !== \"id\" && key !== \"targetId\" && key !== \"order\" && key.endsWith(\"Id\"),\n );\n\n if (!derived) {\n throw new Error(\"Unable to resolve parent column for related table.\");\n }\n\n return derived;\n}\n\nfunction getTableColumn(table: PgTable, key: string): AnyPgColumn {\n const column = (table as unknown as Record<string, unknown>)[key];\n\n if (!column) {\n throw new Error(`Column '${key}' not found on table.`);\n }\n\n return column as AnyPgColumn;\n}\n\nfunction getRecordId(record: Record<string, unknown>): string {\n const id = record.id;\n\n if (typeof id !== \"string\") {\n throw new Error(\"Expected saved document to include a string id.\");\n }\n\n return id;\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(\"Expected object record.\");\n }\n\n return value as Record<string, unknown>;\n}\n\nfunction toOptionalRecord(value: unknown): Record<string, unknown> | null {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return null;\n }\n\n return value as Record<string, unknown>;\n}\n\nfunction normalizePage(page?: number): number {\n if (!page || page < 1) {\n return 1;\n }\n\n return Math.floor(page);\n}\n\nfunction normalizeLimit(limit?: number): number {\n if (!limit || limit < 1) {\n return 10;\n }\n\n return Math.floor(limit);\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) =>\n char.toLowerCase(),\n );\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).map(capitalize).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\nfunction capitalize(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n","import { NpValidationError } from \"../errors.js\";\nimport type { NpCollectionConfig } from \"../config/types.js\";\n\n/**\n * Stable URL-slug derivation. Lowercases, strips Latin diacritics\n * (Cr\\u00e8me \\u2192 creme), keeps any Unicode letter or number including\n * Korean/Japanese/Chinese/Cyrillic/Greek/etc., replaces runs of\n * separators (anything that isn't a letter or number) with a\n * single hyphen, trims edge hyphens, and caps at 96 chars so the\n * result fits standard DB slug columns without needing a larger\n * index.\n *\n * The two-step `NFKD \\u2192 strip combining marks \\u2192 NFC` dance does\n * the diacritic strip without permanently decomposing scripts\n * that NFKD breaks apart at the syllable level (most notably\n * Hangul, which NFKD turns into jamo and NFC then reassembles).\n */\nexport function slugify(value: string): string {\n return value\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .normalize(\"NFC\")\n .replace(/[^\\p{L}\\p{N}]+/gu, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 96);\n}\n\n/**\n * Ensures `data.slug` is set for collections that declare `slugField`.\n * - If the caller supplied `slug`, it gets normalized through slugify.\n * - If updating an existing doc, the previous slug is preserved when the\n * caller didn't provide one (so titles can change without breaking URLs).\n * - Otherwise the slug is derived from the configured `useField` (default\n * \"title\"). Throws `NpValidationError` if no candidate source exists.\n *\n * Mutates `data` in place.\n */\nexport function applySlugField(\n config: NpCollectionConfig,\n data: Record<string, unknown>,\n originalDoc: Record<string, unknown> | null,\n): void {\n if (!config.slugField) return;\n\n const existingSlug = typeof data.slug === \"string\" ? data.slug.trim() : \"\";\n\n if (existingSlug.length > 0) {\n data.slug = slugify(existingSlug) || existingSlug;\n return;\n }\n\n if (originalDoc && typeof originalDoc.slug === \"string\" && originalDoc.slug.length > 0) {\n data.slug = originalDoc.slug;\n return;\n }\n\n const useField =\n typeof config.slugField === \"object\" && config.slugField.useField\n ? config.slugField.useField\n : \"title\";\n const source = data[useField];\n const candidate = typeof source === \"string\" ? slugify(source) : \"\";\n\n if (candidate.length === 0) {\n throw new NpValidationError(\"Slug generation failed\", [\n {\n field: \"slug\",\n message: `Cannot derive a slug — provide \"slug\" or a non-empty \"${useField}\".`,\n },\n ]);\n }\n\n data.slug = candidate;\n}\n","import { z } from \"zod\";\n\nimport { type NpCollectionConfig, type NpFieldConfig } from \"../config/types.js\";\n\nexport function buildZodSchema(\n fields: NpFieldConfig[],\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).shape);\n continue;\n }\n\n if (field.type === \"group\") {\n const schema = buildZodSchema(field.fields);\n shape[field.name] = applyOptionality(schema, field.required);\n continue;\n }\n\n shape[field.name] = applyOptionality(buildFieldSchema(field), field.required);\n }\n\n return z.object(shape);\n}\n\nexport function getCollectionZodSchema(config: NpCollectionConfig): z.ZodSchema {\n const base = buildZodSchema(config.fields).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","import { sql, type SQL } from \"drizzle-orm\";\n\nimport { type NpCollectionConfig, type NpRichTextContent } from \"../config/types.js\";\n\n/**\n * Plain-text concatenation of every searchable field. Used by\n * the moderation pipeline (spam/profanity adapters need the\n * full text, weights don't matter there).\n */\nexport function buildSearchVector(\n config: NpCollectionConfig,\n data: Record<string, unknown>,\n): string {\n const parts: string[] = [];\n\n for (const field of config.fields) {\n if (field.type === \"text\" || field.type === \"textarea\") {\n const value = data[field.name];\n if (typeof value === \"string\") parts.push(value);\n }\n if (field.type === \"richText\") {\n const value = data[field.name];\n if (value) parts.push(extractPlainText(value as NpRichTextContent));\n }\n }\n\n return parts.join(\" \");\n}\n\n/**\n * Phase 10.7 — split searchable fields into Postgres tsvector\n * weight buckets so title-like matches outrank body matches at\n * query time.\n *\n * Convention (no per-field opt-in to keep collections\n * declarations terse):\n * - field.name === \"title\" or \"name\" → weight A\n * - other text / textarea / email → weight B\n * - richText → weight C\n *\n * Sites that want different weights can name their primary\n * field \"title\" and the framework picks the right bucket\n * automatically. (A future revision can add `field.search.weight`\n * for explicit control.)\n *\n * Postgres ts_rank() applies the default weight scale\n * { D: 0.1, C: 0.2, B: 0.4, A: 1.0 }, so an A-weighted match\n * scores ~10× a D-weighted one — meaningful boost for titles.\n */\nexport interface NpSearchVectorParts {\n /** Title-like fields. Highest rank weight. */\n a: string;\n /** Body fields (text/textarea/email). */\n b: string;\n /** Rich-text body. */\n c: string;\n /** Reserved for future categorization (tags, slugs, etc.). */\n d: string;\n}\n\nconst TITLE_LIKE_NAMES = new Set([\"title\", \"name\"]);\n\nexport function buildSearchVectorParts(\n config: NpCollectionConfig,\n data: Record<string, unknown>,\n): NpSearchVectorParts {\n const parts: NpSearchVectorParts = { a: \"\", b: \"\", c: \"\", d: \"\" };\n const append = (bucket: keyof NpSearchVectorParts, value: string): void => {\n parts[bucket] = parts[bucket] ? `${parts[bucket]} ${value}` : value;\n };\n\n for (const field of config.fields) {\n if (\n field.type === \"text\" ||\n field.type === \"textarea\" ||\n field.type === \"email\"\n ) {\n const value = data[field.name];\n if (typeof value !== \"string\" || value.length === 0) continue;\n if (TITLE_LIKE_NAMES.has(field.name)) {\n append(\"a\", value);\n } else {\n append(\"b\", value);\n }\n }\n if (field.type === \"richText\") {\n const value = data[field.name];\n if (!value) continue;\n const text = extractPlainText(value as NpRichTextContent);\n if (text.length > 0) append(\"c\", text);\n }\n }\n\n return parts;\n}\n\n/**\n * Build the weighted-tsvector SQL fragment that the pipeline\n * binds to `searchVector` on insert/update. Each non-empty\n * bucket becomes `setweight(to_tsvector('english', $bucket), '<W>')`;\n * the buckets are concatenated with `||`. Empty buckets are\n * skipped so the resulting expression is always non-trivial.\n *\n * If every bucket is empty (collection has no text fields, or\n * every text field is null on this row), returns an empty\n * tsvector cast — Postgres accepts this as a valid empty\n * vector value.\n */\nexport function buildWeightedSearchVectorSql(\n config: NpCollectionConfig,\n data: Record<string, unknown>,\n): SQL {\n const parts = buildSearchVectorParts(config, data);\n const chunks: SQL[] = [];\n if (parts.a)\n chunks.push(sql`setweight(to_tsvector('english', ${parts.a}), 'A')`);\n if (parts.b)\n chunks.push(sql`setweight(to_tsvector('english', ${parts.b}), 'B')`);\n if (parts.c)\n chunks.push(sql`setweight(to_tsvector('english', ${parts.c}), 'C')`);\n if (parts.d)\n chunks.push(sql`setweight(to_tsvector('english', ${parts.d}), 'D')`);\n if (chunks.length === 0) {\n return sql`''::tsvector`;\n }\n if (chunks.length === 1) {\n return chunks[0];\n }\n // Drizzle's sql.join uses the second arg as the separator\n // SQL fragment. `||` is Postgres tsvector concatenation.\n return sql.join(chunks, sql` || `);\n}\n\nfunction extractPlainText(content: NpRichTextContent): string {\n if (!content || typeof content !== \"object\") return \"\";\n\n const root = content.root as { children?: unknown[] } | undefined;\n if (!root?.children) return \"\";\n\n const parts: string[] = [];\n walkNodes(root.children, parts);\n return parts.join(\" \");\n}\n\nfunction walkNodes(nodes: unknown[], parts: string[]): void {\n for (const node of nodes) {\n if (!node || typeof node !== \"object\") continue;\n const n = node as Record<string, unknown>;\n\n if (typeof n.text === \"string\") {\n parts.push(n.text);\n }\n\n if (Array.isArray(n.children)) {\n walkNodes(n.children, parts);\n }\n }\n}\n","import { eq } from \"drizzle-orm\";\n\nimport { npPlugins } from \"../db/schema/system.js\";\nimport { getDb } from \"../db/runtime.js\";\n\n/**\n * Per-request enabled gate for already-loaded plugins.\n *\n * The plugin host registers every hook and route at boot regardless of the\n * `np_plugins.enabled` row state, so toggling a plugin from the admin UI\n * historically required a server restart to take effect. This module fronts\n * the registry with a short-lived cache of the DB flag so dispatch sites\n * (`runHook`, the catch-all route handler, `dispatchPluginAction`) can skip\n * disabled plugins immediately, without paying a DB round-trip per call.\n *\n * Cache semantics:\n * - Default-enabled: a missing row OR a DB read failure yields `true`. This\n * matches `syncPluginRegistrations` (which inserts new rows with\n * `enabled=true`) and avoids a hard failure mode where a flaky DB silently\n * disables every plugin.\n * - 5 second TTL by default — short enough that a toggle feels immediate,\n * long enough to absorb a burst of hook calls within one request.\n * - `invalidatePluginEnabled(id)` is called from `updatePluginState` so the\n * next dispatch after a toggle re-reads the DB instead of waiting out the\n * TTL.\n * - `setPluginEnabledForTest()` / `resetEnabledGate()` let unit tests\n * bypass the DB entirely.\n */\n\nconst DEFAULT_TTL_MS = 5_000;\n\ninterface CacheEntry {\n enabled: boolean;\n expiresAt: number;\n}\n\nconst cache = new Map<string, CacheEntry>();\nconst inflight = new Map<string, Promise<boolean>>();\n/**\n * Per-plugin generation counter. Bumped by `invalidatePluginEnabled()` so\n * an in-flight `fetchEnabled()` can tell whether its result is still\n * relevant before writing to the cache. Without this token, the original\n * implementation (#462) had a race:\n * T0 — request A starts fetchEnabled, reads `enabled=true` from DB\n * T1 — admin toggles → invalidate clears cache + inflight\n * T2 — request B starts a fresh fetchEnabled, reads `enabled=false`,\n * writes `false` to cache\n * T3 — A's `.then()` finally runs and overwrites cache with the\n * stale `true`, sticking until the TTL expires\n * Now A bumps its captured generation against the current value before\n * writing; if they disagree, A drops its result.\n */\nconst generation = new Map<string, number>();\nlet ttlMs = DEFAULT_TTL_MS;\n\nfunction currentGeneration(pluginId: string): number {\n return generation.get(pluginId) ?? 0;\n}\n\nasync function fetchEnabled(pluginId: string): Promise<boolean> {\n if (fetchOverride) return fetchOverride(pluginId);\n try {\n const db = getDb();\n const rows = await db\n .select({ enabled: npPlugins.enabled })\n .from(npPlugins)\n .where(eq(npPlugins.id, pluginId))\n .limit(1);\n const row = rows[0] as { enabled?: unknown } | undefined;\n if (row && typeof row.enabled === \"boolean\") {\n return row.enabled;\n }\n // Row missing — treat as enabled. `syncPluginRegistrations` will insert\n // the row with enabled=true on the next boot anyway.\n return true;\n } catch {\n // DB not ready (test, CLI scaffold) or transient failure — fail open so\n // a degraded DB can't silently disable every loaded plugin.\n return true;\n }\n}\n\nexport async function isPluginEnabled(pluginId: string): Promise<boolean> {\n const now = Date.now();\n const cached = cache.get(pluginId);\n if (cached && cached.expiresAt > now) {\n return cached.enabled;\n }\n\n // Coalesce concurrent lookups for the same id so a hook that fans out\n // doesn't fire N parallel SELECTs against the same row.\n const existing = inflight.get(pluginId);\n if (existing) return existing;\n\n // Capture the generation BEFORE awaiting. If the cache is invalidated\n // while we're in flight, the generation map will tick and the .then()\n // below skips the cache write.\n const fetchGeneration = currentGeneration(pluginId);\n const promise = fetchEnabled(pluginId)\n .then((enabled) => {\n if (currentGeneration(pluginId) === fetchGeneration) {\n cache.set(pluginId, { enabled, expiresAt: Date.now() + ttlMs });\n }\n return enabled;\n })\n .finally(() => {\n // Only clear the inflight slot if it's still ours — a concurrent\n // invalidate may have cleared and a sibling request may have\n // installed a fresh promise. Don't yank theirs.\n if (inflight.get(pluginId) === promise) {\n inflight.delete(pluginId);\n }\n });\n inflight.set(pluginId, promise);\n return promise;\n}\n\nexport function invalidatePluginEnabled(pluginId: string): void {\n cache.delete(pluginId);\n inflight.delete(pluginId);\n // Tick the generation so any already-running fetchEnabled() promise\n // for this id refuses to write its result back into the cache when\n // it eventually settles. Without the bump, a slow DB read started\n // before the toggle could re-cache the stale value for up to TTL.\n generation.set(pluginId, currentGeneration(pluginId) + 1);\n}\n\n/**\n * Test-only: bypass the DB and force a known enabled value. The cache holds\n * it for the configured TTL so subsequent reads in the same test see the\n * forced value without hitting the DB stub.\n */\nexport function setPluginEnabledForTest(pluginId: string, enabled: boolean): void {\n cache.set(pluginId, { enabled, expiresAt: Number.POSITIVE_INFINITY });\n}\n\nexport function resetEnabledGate(): void {\n cache.clear();\n inflight.clear();\n generation.clear();\n ttlMs = DEFAULT_TTL_MS;\n fetchOverride = null;\n}\n\n/** Test-only: tighten the TTL so cache-expiry behavior is observable. */\nexport function setEnabledGateTtlForTest(ms: number): void {\n ttlMs = ms;\n}\n\n/**\n * Test-only: replace the DB read with a deterministic implementation so\n * race-window tests can resolve fetches in a controlled order. Production\n * code goes through `fetchEnabled()` directly; the override is wired in\n * via this setter and torn down by `resetEnabledGate()`.\n */\nlet fetchOverride: ((pluginId: string) => Promise<boolean>) | null = null;\n\nexport function setFetchImplForTest(\n impl: ((pluginId: string) => Promise<boolean>) | null,\n): void {\n fetchOverride = impl;\n}\n","/**\n * Plugin compatibility checks: framework semver range + inter-plugin\n * dependency ordering.\n *\n * The plugin manifest declares two compatibility hints:\n * 1. `nexpress.minVersion` / `nexpress.maxVersion` — the plugin must run\n * against a framework version inside this range. The host enforces it\n * at load time so an outdated plugin can't crash deeper in the call\n * stack with an unrelated `TypeError`.\n * 2. `requires` — other plugins this one depends on. Used to sort the\n * load order so a plugin's `setup()` can assume its prerequisites\n * have already registered hooks/actions.\n *\n * Both checks fail open by default — an incompatible plugin or one with\n * missing deps is logged and skipped, never thrown. Operators see the warn\n * lines in boot logs and decide whether to upgrade or pin a version.\n */\n\n/**\n * Framework version reported to plugin compatibility checks. Kept in sync\n * with `@nexpress/core`'s `package.json`'s `version` by `version.test.ts`,\n * which fails CI if the two drift. We don't import package.json directly\n * because it sits outside the package's `rootDir` and would force a\n * tsconfig-wide change for one constant.\n */\nconst FRAMEWORK_VERSION_FROM_PACKAGE = \"0.1.0\";\nlet frameworkVersion: string = FRAMEWORK_VERSION_FROM_PACKAGE;\n\n/**\n * Returns the running framework version, read from `@nexpress/core`'s\n * package.json at build time and inlined by tsup. Tests can override via\n * `setFrameworkVersionForTest()`.\n */\nexport function getFrameworkVersion(): string {\n return frameworkVersion;\n}\n\nexport function setFrameworkVersionForTest(version: string): void {\n frameworkVersion = version;\n}\n\nexport function resetFrameworkVersion(): void {\n frameworkVersion = FRAMEWORK_VERSION_FROM_PACKAGE;\n}\n\ninterface ParsedSemver {\n major: number;\n minor: number;\n patch: number;\n /** `null` for a release version, otherwise the prerelease identifier. */\n prerelease: string | null;\n}\n\n/**\n * Parses a semver string per the regex enforced by the manifest schema:\n * `\\d+\\.\\d+\\.\\d+(-prerelease)?(+build)?`.\n *\n * Build metadata is ignored for ordering (per semver §10). Prerelease is\n * compared lexicographically, which is enough for the major.minor.patch[-tag]\n * shapes plugins typically use; this is not a full semver implementation.\n */\nfunction parse(version: string): ParsedSemver | null {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z-.]+))?(?:\\+[0-9A-Za-z-.]+)?$/.exec(version);\n if (!match) return null;\n const [, majorStr, minorStr, patchStr, prerelease] = match;\n return {\n major: Number.parseInt(majorStr ?? \"0\", 10),\n minor: Number.parseInt(minorStr ?? \"0\", 10),\n patch: Number.parseInt(patchStr ?? \"0\", 10),\n prerelease: prerelease ?? null,\n };\n}\n\n/** Returns -1 / 0 / 1 — same contract as `Array.prototype.sort` callbacks. */\nexport function compareSemver(a: string, b: string): number {\n const pa = parse(a);\n const pb = parse(b);\n if (!pa || !pb) {\n // Malformed input — fall back to lexicographic so we still produce a\n // total order. Manifest validation should catch this before us.\n return a < b ? -1 : a > b ? 1 : 0;\n }\n if (pa.major !== pb.major) return pa.major < pb.major ? -1 : 1;\n if (pa.minor !== pb.minor) return pa.minor < pb.minor ? -1 : 1;\n if (pa.patch !== pb.patch) return pa.patch < pb.patch ? -1 : 1;\n // 1.0.0-alpha < 1.0.0 (semver §11.3 — a release is greater than its\n // prerelease counterpart).\n if (pa.prerelease === pb.prerelease) return 0;\n if (pa.prerelease === null) return 1;\n if (pb.prerelease === null) return -1;\n return pa.prerelease < pb.prerelease ? -1 : 1;\n}\n\nexport interface NexpressCompatResult {\n compatible: boolean;\n reason?: string;\n}\n\n/**\n * Verifies that `frameworkVersion` falls inside `[minVersion, maxVersion]`\n * (inclusive). `maxVersion` is optional — a plugin that omits it claims to\n * support every later version of the framework.\n */\nexport function checkNexpressCompat(\n manifest: { nexpress?: { minVersion?: string; maxVersion?: string } },\n framework: string = getFrameworkVersion(),\n): NexpressCompatResult {\n const min = manifest.nexpress?.minVersion;\n if (!min) {\n // Manifest is missing the field — should have been caught by the zod\n // schema, but be lenient at runtime.\n return { compatible: true };\n }\n if (compareSemver(framework, min) < 0) {\n return {\n compatible: false,\n reason: `requires NexPress >= ${min}, host is ${framework}`,\n };\n }\n const max = manifest.nexpress?.maxVersion;\n if (max && compareSemver(framework, max) > 0) {\n return {\n compatible: false,\n reason: `requires NexPress <= ${max}, host is ${framework}`,\n };\n }\n return { compatible: true };\n}\n\nexport interface SortedPlugins<T> {\n /** Plugins in load order — every plugin's `requires` appear before it. */\n ordered: T[];\n /** Plugins skipped because of a missing or cyclic dependency. */\n skipped: Array<{ id: string; reason: string }>;\n}\n\n/**\n * Sorts plugins so each one's declared `requires` are loaded first. Missing\n * deps and cycles produce a `skipped` entry with a human-readable reason\n * instead of throwing — boot logs will surface the issue to the operator.\n *\n * Algorithm: Kahn's. Stable for plugins with no incoming edges (preserves\n * the input order for a tie), so users still get a deterministic sort when\n * `requires` is empty for everyone.\n */\nexport function topoSort<T extends { id: string; requires: readonly string[] }>(\n plugins: T[],\n): SortedPlugins<T> {\n const skipped: Array<{ id: string; reason: string }> = [];\n\n // Iteratively narrow the eligible set: any plugin whose `requires` aren't\n // all in the *current* eligible set gets skipped, which may then make its\n // own dependents ineligible. Repeat until the set stops shrinking. The\n // earlier single-pass version (#464) only checked the *original* input ids,\n // so a plugin A → B chain where B was skipped (because B → missing C) would\n // still let A load with no error — A passed the input-set check, then the\n // edge-build phase silently dropped the B → A link.\n let eligible: T[] = [...plugins];\n while (true) {\n const eligibleIds = new Set(eligible.map((p) => p.id));\n const stillEligible: T[] = [];\n let dropped = false;\n for (const plugin of eligible) {\n const missing = plugin.requires.filter((dep) => !eligibleIds.has(dep));\n if (missing.length > 0) {\n skipped.push({\n id: plugin.id,\n reason: `missing required plugin(s): ${missing.join(\", \")}`,\n });\n dropped = true;\n continue;\n }\n stillEligible.push(plugin);\n }\n eligible = stillEligible;\n if (!dropped) break;\n }\n\n const eligibleIds = new Set(eligible.map((p) => p.id));\n const indegree = new Map<string, number>();\n const dependents = new Map<string, string[]>();\n for (const plugin of eligible) {\n indegree.set(plugin.id, 0);\n dependents.set(plugin.id, []);\n }\n for (const plugin of eligible) {\n for (const dep of plugin.requires) {\n if (!eligibleIds.has(dep)) continue;\n indegree.set(plugin.id, (indegree.get(plugin.id) ?? 0) + 1);\n dependents.get(dep)!.push(plugin.id);\n }\n }\n\n // Process in input order so the output stays stable for sibling plugins.\n const queue: T[] = eligible.filter((p) => (indegree.get(p.id) ?? 0) === 0);\n const ordered: T[] = [];\n const byId = new Map(eligible.map((p) => [p.id, p] as const));\n\n while (queue.length > 0) {\n const next = queue.shift()!;\n ordered.push(next);\n for (const dependent of dependents.get(next.id) ?? []) {\n const updated = (indegree.get(dependent) ?? 0) - 1;\n indegree.set(dependent, updated);\n if (updated === 0) {\n const plugin = byId.get(dependent);\n if (plugin) queue.push(plugin);\n }\n }\n }\n\n if (ordered.length !== eligible.length) {\n for (const plugin of eligible) {\n if (!ordered.includes(plugin)) {\n skipped.push({\n id: plugin.id,\n reason: \"dependency cycle — refusing to load\",\n });\n }\n }\n }\n\n return { ordered, skipped };\n}\n","import type { NpFieldConfig, NpPluginConfig, NpPluginContext } from \"../config/types.js\";\nimport { getLogger } from \"../observability/logger.js\";\nimport { reportError } from \"../observability/error-reporter.js\";\nimport { createPluginRuntimeContext } from \"./context.js\";\nimport { isPluginEnabled } from \"./enabled-gate.js\";\nimport { checkNexpressCompat, topoSort } from \"./compat.js\";\n\nexport interface PluginHookHandler {\n pluginId: string;\n /**\n * Returns `void` for fire-and-forget hooks (most of them). Render / extension\n * hooks may return a value; `runHookAndCollect` gathers those, while\n * `runHook` ignores returns.\n */\n handler: (data: Record<string, unknown>) => unknown;\n /**\n * Lower priority runs first. Default `100`, leaving headroom in both\n * directions: a plugin that wants to observe AFTER everyone else picks\n * `200`, one that needs to mutate the payload first picks `0`. Stable\n * ordering: ties keep registration order (which is itself topo-sorted by\n * plugin `requires`).\n */\n priority: number;\n /**\n * Per-handler timeout in milliseconds. When the handler doesn't settle\n * within the budget, `dispatchHookHandler` treats it as a failure —\n * logged and reported the same way a thrown error is. The remaining\n * handlers continue. `undefined` means \"no timeout enforced\", which is\n * the default for fire-and-forget hooks (`runHook`); render-collecting\n * hooks may want a tighter budget (e.g. 250ms) so a slow plugin can't\n * stall page rendering.\n */\n timeoutMs?: number;\n}\n\nexport interface PluginRouteHandler {\n pluginId: string;\n path: string;\n method: string;\n /** When true, the dispatcher must verify a staff session before\n * invoking `handler` and pass the resolved user as `req.user`.\n * When false (default), the route is publicly reachable. */\n auth: boolean;\n handler: (req: PluginRouteRequest) => Promise<PluginRouteResponse>;\n}\n\nexport interface PluginRouteRequest {\n method: string;\n path: string;\n params: Record<string, string>;\n query: Record<string, string>;\n body: unknown;\n headers: Record<string, string>;\n user?: { id: string; email: string; role: string };\n}\n\nexport interface PluginRouteResponse {\n status: number;\n body?: unknown;\n headers?: Record<string, string>;\n}\n\nexport interface PluginCapabilityRequirement {\n requirement: string;\n declared: readonly string[];\n}\n\n/**\n * Declarative admin extension snapshot stored per registration. Shape mirrors\n * `@nexpress/plugin-sdk`'s `NpAdminExtension` but kept structural here to\n * avoid a plugin-sdk → core cycle. The admin UI reads this via\n * `getPluginAdminExtension(id)` and renders it with its own primitives.\n */\nexport interface PluginAdminExtension {\n settings?: {\n title?: string;\n description?: string;\n fields: NpFieldConfig[];\n };\n widgets?: Array<{\n id: string;\n label: string;\n kind: \"metric\" | \"status\";\n actionId: string;\n description?: string;\n }>;\n actions?: Array<{\n id: string;\n label: string;\n actionId: string;\n confirm?: string;\n description?: string;\n }>;\n tables?: Array<{\n id: string;\n label: string;\n columns: Array<{ name: string; label: string }>;\n rowsActionId: string;\n emptyMessage?: string;\n }>;\n collectionTabs?: Array<{\n id: string;\n label: string;\n collections: string[] | \"*\";\n widgets?: Array<{\n id: string;\n label: string;\n kind: \"metric\" | \"status\";\n actionId: string;\n description?: string;\n }>;\n actions?: Array<{\n id: string;\n label: string;\n actionId: string;\n confirm?: string;\n description?: string;\n }>;\n description?: string;\n }>;\n dashboardWidgets?: Array<{\n id: string;\n label: string;\n kind: \"metric\" | \"status\";\n actionId: string;\n description?: string;\n priority?: number;\n }>;\n}\n\n/**\n * Phase 19 — first-class plugin cron schedules. Plugins\n * declare `scheduled: [{ id, cron, handler }]` in their\n * definition; the host stores the list here and pg-boss\n * registers one recurring schedule per entry. The handler\n * runs in the same context shape `setup()` saw, so plugins\n * already familiar with `ctx.content` / `ctx.storage` /\n * `ctx.next` use the same surface from a cron tick.\n */\nexport interface PluginScheduleHandler {\n pluginId: string;\n taskId: string;\n cron: string;\n description?: string;\n handler: (ctx: Record<string, unknown>) => unknown;\n}\n\ninterface PluginRegistration {\n id: string;\n name: string;\n version?: string;\n description?: string;\n capabilities: readonly string[];\n allowedHosts: readonly string[];\n admin?: PluginAdminExtension;\n hooks: Map<string, PluginHookHandler[]>;\n routes: PluginRouteHandler[];\n actions: Map<string, (data: unknown) => Promise<{ ok: boolean; data?: unknown; error?: string }>>;\n schedules: Map<string, PluginScheduleHandler>;\n /**\n * G.1 — Zod schema describing the plugin's operator-tunable\n * config. Read by `getPluginConfig` (introspection +\n * validation) and the admin auto-form. `unknown` here so the\n * host doesn't pull a hard zod dep into its type surface;\n * narrowed at the call site (same pattern theme uses for\n * `settingsSchema`).\n */\n configSchema?: unknown;\n /** G.1 — schema version (defaults to 1). See plugin-sdk types. */\n configVersion?: number;\n /** G.1 — migration callback for v(N-1) → current. */\n configMigrate?: (old: unknown, fromVersion: number) => unknown;\n /**\n * Plugin-contributed page routes (#623). Stored as the same\n * shape the SDK accepts (component + optional metadata as\n * `unknown`); the route-dispatcher in `@nexpress/next`\n * narrows them at render time. See\n * `docs/design/plugin-routes.md` for precedence + surface\n * semantics.\n */\n pageRoutes: readonly PluginPageRouteEntry[];\n}\n\nexport interface PluginPageRouteEntry {\n pattern: string;\n component: unknown;\n metadata?: unknown;\n surface: \"site\" | \"member\";\n locale: \"auto\" | \"none\";\n}\n\n/**\n * Hook names start with a namespace — \"content:afterCreate\",\n * \"auth:afterLogin\", \"render:beforePage\", etc. The plugin must declare the\n * matching \"hooks:<namespace>\" capability to register a handler. This is the\n * v1 runtime enforcement: coarse, easy to reason about, and deliberately\n * additive — plugins without any capabilities can still do nothing.\n */\nfunction hookCapabilityFor(hookName: string): string | null {\n const namespace = hookName.split(\":\")[0];\n if (!namespace) return null;\n return `hooks:${namespace}`;\n}\n\nfunction assertCapability(\n pluginId: string,\n requirement: string,\n declared: readonly string[],\n): void {\n if (declared.includes(requirement)) return;\n\n throw new Error(\n `[plugin:${pluginId}] declares capabilities ${JSON.stringify(declared)} ` +\n `but is registering something that requires \"${requirement}\". ` +\n `Add \"${requirement}\" to the plugin manifest's capabilities array.`,\n );\n}\n\nconst pluginRegistry = new Map<string, PluginRegistration>();\nconst globalHooks = new Map<string, PluginHookHandler[]>();\nconst globalRoutes: PluginRouteHandler[] = [];\n\n/**\n * Default priority assigned to a hook handler that doesn't pick one. The\n * value matters less than the headroom — a plugin can always go above or\n * below to override, and 100 leaves room for both. Keep in sync with the\n * docstring on `PluginHookHandler.priority`.\n */\nconst DEFAULT_HOOK_PRIORITY = 100;\n\n/**\n * Normalizes the two valid hook value shapes:\n * - bare function (the original shape — implicit priority 100, no timeout)\n * - `{ handler, priority?, timeoutMs? }` object\n *\n * Returns `null` for malformed input so the caller can skip silently —\n * Zod schema on the plugin-sdk side already rejects bad shapes at\n * authoring time, so this is just defense in depth at the host boundary.\n */\nfunction normalizeHookValue(\n value: unknown,\n): { handler: ResolvedHookFn; priority: number; timeoutMs?: number } | null {\n if (typeof value === \"function\") {\n return { handler: value as ResolvedHookFn, priority: DEFAULT_HOOK_PRIORITY };\n }\n if (value && typeof value === \"object\") {\n const v = value as { handler?: unknown; priority?: unknown; timeoutMs?: unknown };\n if (typeof v.handler !== \"function\") return null;\n return {\n handler: v.handler as ResolvedHookFn,\n priority: typeof v.priority === \"number\" ? v.priority : DEFAULT_HOOK_PRIORITY,\n timeoutMs: typeof v.timeoutMs === \"number\" && v.timeoutMs > 0 ? v.timeoutMs : undefined,\n };\n }\n return null;\n}\n\n/**\n * Inserts a handler into the global per-hook list while keeping the array\n * sorted by `(priority asc, registration order)`. Array sort in V8 is\n * stable, so we can append + sort and ties keep insertion order.\n *\n * Sorting at registration time means dispatch is allocation-free — the\n * hot path just iterates the array. Registrations happen at boot (and on\n * a hot reload), so re-sorting on each insert is fine.\n */\nfunction insertSortedByPriority(\n list: PluginHookHandler[],\n entry: PluginHookHandler,\n): void {\n list.push(entry);\n list.sort((a, b) => a.priority - b.priority);\n}\n\n/**\n * Structural shape for plugins built via `@nexpress/plugin-sdk`'s\n * `definePlugin()`. Matches `NpResolvedPluginLike` in config/types.ts —\n * kept deliberately loose so `loadPlugins` can accept the same array\n * that `NpConfig.plugins` does without narrowing gymnastics.\n */\nexport interface ResolvedPluginLike {\n manifest: {\n id: string;\n name: string;\n version?: string;\n description?: string;\n capabilities: readonly string[];\n allowedHosts?: readonly string[];\n /**\n * Compatibility range for the framework. The plugin loads only when\n * `nexpress.minVersion <= host <= nexpress.maxVersion?` (inclusive).\n * Optional here so legacy / hand-rolled plugins keep loading; the\n * plugin-sdk schema requires it for new plugins.\n */\n nexpress?: { minVersion?: string; maxVersion?: string };\n /**\n * IDs of other plugins that must load first. The host topo-sorts the\n * load list so this plugin's `setup()` can assume its prerequisites\n * have already registered hooks/actions/blocks.\n */\n requires?: readonly string[];\n };\n hooks?: Record<string, unknown>;\n routes?: ReadonlyArray<{\n path: string;\n method: string;\n handler: unknown;\n description?: string;\n auth?: boolean;\n }>;\n admin?: PluginAdminExtension;\n /** G.1 — runtime zod schema for plugin config (auto-form). */\n configSchema?: unknown;\n /** G.1 — schema version for the lazy migration pipeline. */\n configVersion?: number;\n /** G.1 — old → current value migrator. */\n configMigrate?: (old: unknown, fromVersion: number) => unknown;\n}\n\ntype ResolvedHookFn = (ctx: {\n hook: string;\n data: Record<string, unknown>;\n collection?: string;\n ctx: Record<string, unknown>;\n}) => unknown;\n\ntype ResolvedRouteFn = (\n req: PluginRouteRequest,\n ctx: Record<string, unknown>,\n) => Promise<PluginRouteResponse>;\n\n/**\n * G.1 — read a plugin's persisted config from `np_settings`.\n *\n * Internal helper for the runtime context builder; external callers\n * import `getPluginConfig` from `./config.js` directly. We avoid a\n * cross-module call here so `host.ts` doesn't import from `config.ts`\n * (one-way: config.ts imports from host.ts via `getPluginRegistration`).\n */\nasync function loadPluginConfig(pluginId: string): Promise<Record<string, unknown>> {\n const { getPluginConfig } = await import(\"./config.js\");\n const value = await getPluginConfig(pluginId);\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return {};\n}\n\nasync function buildCtxFor(pluginId: string): Promise<Record<string, unknown>> {\n const registration = pluginRegistry.get(pluginId);\n if (!registration) {\n throw new Error(`[plugin:${pluginId}] attempted to build ctx before registration.`);\n }\n const config = await loadPluginConfig(pluginId);\n return createPluginRuntimeContext({\n pluginId,\n capabilities: registration.capabilities,\n allowedHosts: registration.allowedHosts,\n config,\n registration,\n lookupRegistration: (id) => pluginRegistry.get(id),\n });\n}\n\nfunction isResolvedPlugin(value: unknown): value is ResolvedPluginLike {\n if (!value || typeof value !== \"object\") return false;\n const candidate = value as { manifest?: unknown };\n if (!candidate.manifest || typeof candidate.manifest !== \"object\") return false;\n const manifest = candidate.manifest as { id?: unknown; capabilities?: unknown };\n return typeof manifest.id === \"string\" && Array.isArray(manifest.capabilities);\n}\n\nfunction registerHookHandler(\n registration: PluginRegistration,\n hookName: string,\n handler: PluginHookHandler,\n): void {\n if (!registration.hooks.has(hookName)) {\n registration.hooks.set(hookName, []);\n }\n insertSortedByPriority(registration.hooks.get(hookName)!, handler);\n\n if (!globalHooks.has(hookName)) {\n globalHooks.set(hookName, []);\n }\n insertSortedByPriority(globalHooks.get(hookName)!, handler);\n}\n\nfunction createPluginContext(pluginId: string, registration: PluginRegistration): NpPluginContext {\n return {\n addCollection: () => {\n throw new Error(\n `[plugin:${pluginId}] Runtime collection registration not supported in v1. Add collections to nexpress.config.ts.`,\n );\n },\n addBlock: () => {\n throw new Error(\n `[plugin:${pluginId}] Runtime block registration not supported in v1. Add blocks to nexpress.config.ts.`,\n );\n },\n addHook: (collection: string, event: string, hook) => {\n // Legacy API: collection is the docs' collection (\"posts\"), event is the\n // lifecycle step (\"afterCreate\"). The pipeline emits canonical hook names\n // under the \"content:\" namespace (e.g. `content:afterCreate`), so\n // register there and filter by collection at dispatch time. This keeps\n // legacy hooks firing on the same stream as resolved-plugin hooks.\n const hookName = `content:${event}`;\n const requirement = hookCapabilityFor(hookName);\n if (requirement) {\n assertCapability(pluginId, requirement, registration.capabilities);\n }\n\n registerHookHandler(registration, hookName, {\n pluginId,\n priority: DEFAULT_HOOK_PRIORITY,\n handler: async (data) => {\n if (typeof data.collection === \"string\" && data.collection !== collection) {\n return;\n }\n await hook({ data, collection } as never);\n },\n });\n },\n };\n}\n\nasync function loadResolvedPlugin(plugin: ResolvedPluginLike): Promise<void> {\n const { manifest } = plugin;\n\n // Defense in depth: if this id was already registered, scrub the old\n // entry's hooks + routes from the global maps before overwriting. The\n // documented reload flow (`reloadPlugins()`) always calls `resetPlugins()`\n // first, so we shouldn't normally hit this — but a stray double-load\n // would otherwise leave both registrations dispatching, which is much\n // harder to diagnose than a clean re-register.\n const previous = pluginRegistry.get(manifest.id);\n if (previous) {\n for (const [hookName, list] of previous.hooks) {\n const global = globalHooks.get(hookName);\n if (!global) continue;\n const filtered = global.filter((h) => !list.includes(h));\n if (filtered.length === 0) globalHooks.delete(hookName);\n else globalHooks.set(hookName, filtered);\n }\n for (const route of previous.routes) {\n const idx = globalRoutes.indexOf(route);\n if (idx !== -1) globalRoutes.splice(idx, 1);\n }\n }\n\n const registration: PluginRegistration = {\n id: manifest.id,\n name: manifest.name,\n version: manifest.version,\n description: manifest.description,\n capabilities: [...manifest.capabilities],\n allowedHosts: [...(manifest.allowedHosts ?? [])],\n admin: plugin.admin,\n hooks: new Map(),\n routes: [],\n actions: new Map(),\n schedules: new Map(),\n configSchema: plugin.configSchema,\n configVersion: plugin.configVersion,\n configMigrate: plugin.configMigrate,\n pageRoutes: normalizePageRoutes(plugin),\n };\n\n pluginRegistry.set(manifest.id, registration);\n\n // G.1 — declaring BOTH `configSchema` (auto-form) and\n // `admin.settings.fields` (legacy declarative form) is a sign\n // the plugin is mid-migration. The auto-form wins (per design\n // doc § 5.1.1); warn so the operator/author notices the\n // ignored field list. Migrating PRs should remove\n // `admin.settings.fields` in the same diff that adds\n // `configSchema`.\n if (\n registration.configSchema !== undefined &&\n plugin.admin?.settings?.fields &&\n plugin.admin.settings.fields.length > 0\n ) {\n getLogger().warn(\"Plugin declares both configSchema and admin.settings.fields\", {\n pluginId: manifest.id,\n note: \"Auto-form wins; admin.settings.fields is ignored at render time. Remove admin.settings.fields when migrating to configSchema.\",\n });\n }\n\n // Phase 19 — first-class cron schedules. Each entry maps to\n // one pg-boss schedule. Duplicates within a plugin overwrite\n // (idempotent across hot reloads); cross-plugin id collisions\n // are fine because the queue name namespaces by plugin id.\n const scheduledRaw = (plugin as { scheduled?: unknown }).scheduled;\n if (Array.isArray(scheduledRaw)) {\n for (const entry of scheduledRaw) {\n if (!entry || typeof entry !== \"object\") continue;\n const e = entry as {\n id?: unknown;\n cron?: unknown;\n handler?: unknown;\n description?: unknown;\n };\n if (typeof e.id !== \"string\" || e.id.length === 0) continue;\n if (typeof e.cron !== \"string\" || e.cron.length === 0) continue;\n if (typeof e.handler !== \"function\") continue;\n registration.schedules.set(e.id, {\n pluginId: manifest.id,\n taskId: e.id,\n cron: e.cron,\n description: typeof e.description === \"string\" ? e.description : undefined,\n handler: e.handler as PluginScheduleHandler[\"handler\"],\n });\n }\n }\n\n for (const [hookName, rawValue] of Object.entries(plugin.hooks ?? {})) {\n const normalized = normalizeHookValue(rawValue);\n if (!normalized) continue;\n\n const requirement = hookCapabilityFor(hookName);\n if (requirement) {\n assertCapability(manifest.id, requirement, registration.capabilities);\n }\n\n const userHandler = normalized.handler;\n registerHookHandler(registration, hookName, {\n pluginId: manifest.id,\n priority: normalized.priority,\n timeoutMs: normalized.timeoutMs,\n handler: async (data) => {\n const collection = typeof data.collection === \"string\" ? data.collection : undefined;\n const ctx = await buildCtxFor(manifest.id);\n return await userHandler({ hook: hookName, data, collection, ctx });\n },\n });\n }\n\n for (const route of plugin.routes ?? []) {\n if (typeof route.handler !== \"function\") continue;\n\n assertCapability(manifest.id, \"api:route\", registration.capabilities);\n\n const userHandler = route.handler as ResolvedRouteFn;\n const wrapped: (req: PluginRouteRequest) => Promise<PluginRouteResponse> = async (req) => {\n const ctx = await buildCtxFor(manifest.id);\n return userHandler(req, ctx);\n };\n\n const auth = route.auth === true;\n const method = route.method.toUpperCase();\n\n // #316 — public plugin routes carry the framework's least-\n // protected default rate limit (proxy.ts caps the catch-all at\n // 30 req/min/IP) and run *plugin-supplied* code without staff\n // session checks. Mutating ones double the surface area: an\n // attacker that finds the route can hit the handler at the IP\n // ceiling. Plugins that legitimately need a public mutating\n // endpoint (webhooks, callback URLs) own the auth themselves —\n // log a warning at load time so this is at least visible in\n // boot logs and a tracker can grep for it.\n if (\n !auth &&\n method !== \"GET\" &&\n method !== \"HEAD\" &&\n method !== \"OPTIONS\"\n ) {\n getLogger().warn(\"Plugin registered a public mutating route\", {\n pluginId: manifest.id,\n path: route.path,\n method,\n note:\n \"Plugins are responsible for their own auth on `auth: false` \" +\n \"routes. The framework rate-limits the plugin catch-all to \" +\n \"30 req/min/IP; verify the handler enforces signature / token \" +\n \"checks before mutating state.\",\n });\n }\n\n const entry: PluginRouteHandler = {\n pluginId: manifest.id,\n path: route.path,\n method,\n auth,\n handler: wrapped,\n };\n registration.routes.push(entry);\n globalRoutes.push(entry);\n }\n\n // Phase 12.5 — merge any UI-string bundles the plugin\n // ships into the global registry. Bundles are scoped per\n // locale; later plugins overwrite earlier ones on key\n // collision so plugin-order in the config drives override\n // priority. Plugin authors typically namespace their keys\n // (e.g. `forum.replyButton`) to avoid collisions across\n // unrelated plugins.\n const i18nBundles = (plugin as { i18n?: Record<string, Record<string, string>> }).i18n;\n if (i18nBundles && typeof i18nBundles === \"object\") {\n const { addStrings } = await import(\"../i18n/strings.js\");\n for (const [locale, bundle] of Object.entries(i18nBundles)) {\n if (bundle && typeof bundle === \"object\") {\n addStrings(locale, bundle);\n }\n }\n }\n\n // Phase 14.5 — merge any page templates the plugin\n // contributes. Theme templates win on id collision (handled\n // downstream in `getThemeTemplateSummaries`), so plugin\n // authors don't need to coordinate id namespaces with the\n // active theme — the theme just stays authoritative. Re-\n // registering the same plugin overwrites its previous\n // entries (idempotent across hot reloads).\n const pluginTemplates = (plugin as { templates?: Record<string, Record<string, unknown>> })\n .templates;\n if (pluginTemplates && typeof pluginTemplates === \"object\") {\n const { registerPluginTemplates } = await import(\"./templates.js\");\n registerPluginTemplates(manifest.id, pluginTemplates);\n }\n\n // Invoke optional setup() after hooks + routes are registered so setup can\n // call ctx.actions.register(…) and have it visible to subsequent dispatches.\n const setup = (plugin as { setup?: (ctx: Record<string, unknown>) => void | Promise<void> })\n .setup;\n if (typeof setup === \"function\") {\n const ctx = await buildCtxFor(manifest.id);\n await setup(ctx);\n }\n}\n\nasync function loadLegacyPlugin(plugin: NpPluginConfig): Promise<void> {\n const registration: PluginRegistration = {\n id: plugin.id,\n name: plugin.name,\n capabilities: [\"hooks:content\"],\n allowedHosts: [],\n hooks: new Map(),\n routes: [],\n actions: new Map(),\n schedules: new Map(),\n // Legacy `init()` plugins predate the page-routes contract;\n // they always register zero routes. Kept as a literal `[]` so\n // the registration shape is consistent across the two paths\n // and `getPluginPageRoutes()` doesn't need to special-case\n // legacy entries.\n pageRoutes: [],\n };\n\n pluginRegistry.set(plugin.id, registration);\n\n if (plugin.init) {\n const ctx = createPluginContext(plugin.id, registration);\n await plugin.init(ctx);\n }\n}\n\nexport async function loadPlugins(\n plugins: Array<NpPluginConfig | ResolvedPluginLike>,\n): Promise<void> {\n // Pass 1 — drop plugins whose declared `nexpress` range excludes the\n // running framework version. We warn instead of throwing so a host that\n // ships with eight plugins doesn't refuse to boot when one is stale.\n const filtered: Array<NpPluginConfig | ResolvedPluginLike> = [];\n for (const plugin of plugins) {\n if (isResolvedPlugin(plugin)) {\n const compat = checkNexpressCompat(plugin.manifest);\n if (!compat.compatible) {\n getLogger().warn(\"Skipping incompatible plugin\", {\n pluginId: plugin.manifest.id,\n reason: compat.reason,\n });\n continue;\n }\n }\n filtered.push(plugin);\n }\n\n // Pass 2 — order resolved plugins by their `requires` graph. Legacy\n // (init()-shape) plugins have no manifest so they ride at the front in\n // their original order; they predate the dependency model and never\n // declare requirements.\n const legacy: NpPluginConfig[] = [];\n const resolved: ResolvedPluginLike[] = [];\n for (const plugin of filtered) {\n if (isResolvedPlugin(plugin)) {\n resolved.push(plugin);\n } else {\n legacy.push(plugin);\n }\n }\n\n const sortInput = resolved.map((plugin) => ({\n id: plugin.manifest.id,\n requires: plugin.manifest.requires ?? [],\n plugin,\n }));\n const { ordered, skipped } = topoSort(sortInput);\n for (const entry of skipped) {\n getLogger().warn(\"Skipping plugin with unsatisfied dependency\", {\n pluginId: entry.id,\n reason: entry.reason,\n });\n }\n\n // Pass 3 — actually load. Legacy first, then resolved in topo order.\n // Each load is wrapped in error isolation: a throwing plugin (most\n // commonly a buggy `setup()` callback or a missing required config)\n // is logged and skipped, so one broken plugin can't take down the\n // whole boot. Partial state from a half-loaded plugin (hooks/routes\n // registered before `setup` threw) is scrubbed via\n // `pluginRegistry.delete(id)` so callers don't see an inconsistent\n // shell registration. Plugins that depend on the failed one will\n // either fail their own require check (handled cleanly) or fail at\n // dispatch time (also caught at the dispatch layer).\n for (const plugin of legacy) {\n try {\n await loadLegacyPlugin(plugin);\n } catch (err) {\n pluginRegistry.delete(plugin.id);\n getLogger().error(\"Plugin failed to load — skipped\", {\n pluginId: plugin.id,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n for (const entry of ordered) {\n try {\n await loadResolvedPlugin(entry.plugin);\n } catch (err) {\n const pluginId = entry.plugin.manifest.id;\n pluginRegistry.delete(pluginId);\n getLogger().error(\"Plugin failed to load — skipped\", {\n pluginId,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n}\n\n/**\n * Invokes one plugin hook handler with error isolation: a thrown handler\n * is logged, reported, and swallowed so a single broken plugin can't take\n * down the rest of the dispatch chain (or the caller — pipeline write,\n * page render, etc.).\n *\n * Returns `{ ok: true, value }` on success and `{ ok: false }` on failure.\n * Callers that aggregate return values (`runHookAndCollect`) skip failed\n * handlers; fire-and-forget callers (`runHook`) ignore the value entirely.\n */\nasync function dispatchHookHandler(\n hookName: string,\n handler: PluginHookHandler,\n data: Record<string, unknown>,\n): Promise<{ ok: true; value: unknown } | { ok: false }> {\n try {\n const result = handler.handler(data);\n // Fast path: handler returned a non-Promise. Skip the timer +\n // Promise.race overhead for the common synchronous case.\n if (handler.timeoutMs === undefined || !(result instanceof Promise)) {\n const value = await result;\n return { ok: true, value };\n }\n // Slow path: race the handler against a timeout. We allocate the\n // timer here (not in the resolved Promise.then) so a fast-resolving\n // handler still pays only the timer-creation cost; the timer is\n // cleared in `finally`.\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(\n new Error(\n `Plugin hook handler timed out after ${handler.timeoutMs}ms`,\n ),\n );\n }, handler.timeoutMs);\n });\n try {\n const value = await Promise.race([result, timeoutPromise]);\n return { ok: true, value };\n } finally {\n if (timer) clearTimeout(timer);\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n getLogger().error(\"Plugin hook handler threw\", {\n pluginId: handler.pluginId,\n hook: hookName,\n timeoutMs: handler.timeoutMs,\n message: err.message,\n stack: err.stack,\n });\n void reportError(err, {\n tags: { source: \"plugin-hook\", pluginId: handler.pluginId, hook: hookName },\n });\n return { ok: false };\n }\n}\n\nexport async function runHook(hookName: string, data: Record<string, unknown>): Promise<void> {\n const handlers = globalHooks.get(hookName);\n if (!handlers || handlers.length === 0) return;\n\n for (const handler of handlers) {\n if (!(await isPluginEnabled(handler.pluginId))) continue;\n await dispatchHookHandler(hookName, handler, data);\n }\n}\n\n/**\n * Like `runHook`, but collects every non-null/undefined return value from\n * registered handlers. Used by render extension points (`render:beforePage`,\n * etc.) where each plugin contributes structured data — head tags, scripts —\n * that the renderer aggregates into a single output.\n *\n * Handlers that throw are isolated (logged + reported, then skipped). A\n * broken plugin contributing meta tags is allowed to fail silently so the\n * page itself still ships — incomplete SEO output beats a 500.\n */\nexport async function runHookAndCollect<T>(\n hookName: string,\n data: Record<string, unknown>,\n): Promise<T[]> {\n const handlers = globalHooks.get(hookName);\n if (!handlers || handlers.length === 0) return [];\n\n const results: T[] = [];\n for (const handler of handlers) {\n if (!(await isPluginEnabled(handler.pluginId))) continue;\n const outcome = await dispatchHookHandler(hookName, handler, data);\n if (outcome.ok && outcome.value !== undefined && outcome.value !== null) {\n results.push(outcome.value as T);\n }\n }\n return results;\n}\n\nexport function getPluginRoutes(): PluginRouteHandler[] {\n return globalRoutes;\n}\n\n/**\n * Plugin page routes (#623). Returns the flat list of registered\n * routes from EVERY loaded plugin in registration order, regardless\n * of enabled state — call sites that care about enabled gating\n * (e.g. the route dispatcher) walk the list and re-check via\n * `isPluginEnabled(pluginId)`. Keeping the gate at the call site\n * means tests can assert the registered shape without mocking the\n * enabled-state singleton.\n */\nexport function getPluginPageRoutes(): Array<{\n pluginId: string;\n route: PluginPageRouteEntry;\n}> {\n const out: Array<{ pluginId: string; route: PluginPageRouteEntry }> = [];\n for (const [pluginId, registration] of pluginRegistry) {\n for (const route of registration.pageRoutes) {\n out.push({ pluginId, route });\n }\n }\n return out;\n}\n\n/**\n * Normalize a plugin's `pageRoutes` field at registration time.\n * Drops malformed entries silently — same defensive shape as\n * `scheduled` / hooks normalization above. Defaults `surface` to\n * `\"site\"` and `locale` to `\"auto\"` so the dispatcher always gets\n * concrete values.\n */\nfunction normalizePageRoutes(plugin: ResolvedPluginLike): readonly PluginPageRouteEntry[] {\n const raw = (plugin as { pageRoutes?: unknown }).pageRoutes;\n if (!Array.isArray(raw)) return [];\n const out: PluginPageRouteEntry[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== \"object\") continue;\n const r = entry as {\n pattern?: unknown;\n component?: unknown;\n metadata?: unknown;\n surface?: unknown;\n locale?: unknown;\n };\n if (typeof r.pattern !== \"string\" || r.pattern.length === 0) continue;\n // Accept either a function (functional / class component) or a\n // non-null object (memo / forwardRef / Suspense-wrapped, all of\n // which are objects with a `$$typeof` brand). Reject anything\n // else — strings, numbers, booleans — that the dispatcher would\n // hand to React only to crash at render time. Same defensive\n // shape as the scheduled / hooks normalization above; tighter\n // than the earlier draft which only rejected null / undefined.\n if (typeof r.component !== \"function\") {\n if (typeof r.component !== \"object\" || r.component === null) continue;\n }\n out.push({\n pattern: r.pattern,\n component: r.component,\n metadata: r.metadata,\n surface: r.surface === \"member\" ? \"member\" : \"site\",\n locale: r.locale === \"none\" ? \"none\" : \"auto\",\n });\n }\n return out;\n}\n\nexport function getPluginRegistration(pluginId: string): PluginRegistration | undefined {\n return pluginRegistry.get(pluginId);\n}\n\nexport function getAllPluginIds(): string[] {\n return [...pluginRegistry.keys()];\n}\n\nexport function getPluginAdminExtension(pluginId: string): PluginAdminExtension | undefined {\n return pluginRegistry.get(pluginId)?.admin;\n}\n\n/**\n * Resolved collection-tab descriptor for the admin collection edit view.\n * Each entry carries pluginId + pluginName so the client component can\n * dispatch actions and label cards per-plugin.\n */\nexport interface ResolvedCollectionTab {\n pluginId: string;\n pluginName: string;\n id: string;\n label: string;\n widgets?: NonNullable<PluginAdminExtension[\"collectionTabs\"]>[number][\"widgets\"];\n actions?: NonNullable<PluginAdminExtension[\"collectionTabs\"]>[number][\"actions\"];\n description?: string;\n}\n\n/**\n * Collects all `collectionTabs` entries declared by loaded plugins whose\n * `collections` filter matches the given slug (either `\"*\"` or includes it).\n * The returned array is already flattened and annotated with the source\n * plugin, ready to pass into the admin edit view.\n */\nexport function getCollectionTabsForSlug(collectionSlug: string): ResolvedCollectionTab[] {\n const result: ResolvedCollectionTab[] = [];\n for (const registration of pluginRegistry.values()) {\n const tabs = registration.admin?.collectionTabs;\n if (!tabs || tabs.length === 0) continue;\n for (const tab of tabs) {\n const matches =\n tab.collections === \"*\" ||\n (Array.isArray(tab.collections) && tab.collections.includes(collectionSlug));\n if (!matches) continue;\n result.push({\n pluginId: registration.id,\n pluginName: registration.name,\n id: tab.id,\n label: tab.label,\n widgets: tab.widgets,\n actions: tab.actions,\n description: tab.description,\n });\n }\n }\n return result;\n}\n\n/**\n * Dashboard widget descriptor annotated with its source plugin. The admin\n * dashboard dispatches the widget's action with an empty payload — dashboard\n * widgets are global, not per-document.\n */\nexport interface ResolvedDashboardWidget {\n pluginId: string;\n pluginName: string;\n id: string;\n label: string;\n kind: \"metric\" | \"status\";\n actionId: string;\n description?: string;\n priority?: number;\n}\n\n/**\n * Collects `dashboardWidgets` declared by every loaded plugin and returns\n * them in render order: `priority` asc (missing priority = Infinity, i.e.\n * rendered last), ties broken by plugin registration order.\n */\nexport function getDashboardWidgetsFromPlugins(): ResolvedDashboardWidget[] {\n const result: ResolvedDashboardWidget[] = [];\n for (const registration of pluginRegistry.values()) {\n const widgets = registration.admin?.dashboardWidgets;\n if (!widgets || widgets.length === 0) continue;\n for (const widget of widgets) {\n result.push({\n pluginId: registration.id,\n pluginName: registration.name,\n id: widget.id,\n label: widget.label,\n kind: widget.kind,\n actionId: widget.actionId,\n description: widget.description,\n priority: widget.priority,\n });\n }\n }\n // Stable sort: items keep registration order when priorities tie.\n return result\n .map((widget, index) => ({ widget, index }))\n .sort((a, b) => {\n const ap = a.widget.priority ?? Number.POSITIVE_INFINITY;\n const bp = b.widget.priority ?? Number.POSITIVE_INFINITY;\n if (ap !== bp) return ap - bp;\n return a.index - b.index;\n })\n .map(({ widget }) => widget);\n}\n\n/**\n * Dispatches a named action registered by the plugin via\n * `ctx.actions.register(actionId, handler)`. Admin widgets / actions / tables\n * call this via POST /api/plugins/:id/actions/:actionId — the handler is\n * responsible for returning `{ ok, data?, error? }`.\n */\nexport async function dispatchPluginAction(\n pluginId: string,\n actionId: string,\n data?: unknown,\n): Promise<{ ok: boolean; data?: unknown; error?: string }> {\n const registration = pluginRegistry.get(pluginId);\n if (!registration) {\n return { ok: false, error: `Plugin \"${pluginId}\" is not registered` };\n }\n if (!(await isPluginEnabled(pluginId))) {\n return { ok: false, error: `Plugin \"${pluginId}\" is disabled` };\n }\n const handler = registration.actions.get(actionId);\n if (!handler) {\n return { ok: false, error: `Action \"${actionId}\" not found on plugin \"${pluginId}\"` };\n }\n return handler(data);\n}\n\nexport async function schedulePluginTask(pluginId: string, taskId: string): Promise<void> {\n const { enqueueJob } = await import(\"../jobs/queue.js\");\n await enqueueJob(\"plugin:scheduledTask\", { pluginId, taskId });\n}\n\n/**\n * Phase 19 — return every registered schedule across loaded\n * plugins. The pg-boss adapter calls this from\n * `scheduleRecurring()` so each `definePlugin({ scheduled })`\n * entry becomes a real cron in `pgboss.schedule`.\n */\nexport function getRegisteredPluginSchedules(): PluginScheduleHandler[] {\n const out: PluginScheduleHandler[] = [];\n for (const reg of pluginRegistry.values()) {\n for (const schedule of reg.schedules.values()) {\n out.push(schedule);\n }\n }\n return out;\n}\n\n/**\n * Phase 19 — runs the handler for one plugin's scheduled task.\n * Called from the `plugin:scheduledTask` job handler when a\n * tick fires. Builds the same plugin context the `setup()`\n * call sees so handlers reuse `ctx.content` / `ctx.storage` /\n * etc. Throws when the plugin or task isn't registered so the\n * worker's retry policy surfaces the misconfiguration.\n */\nexport async function runPluginScheduledTask(pluginId: string, taskId: string): Promise<void> {\n const registration = pluginRegistry.get(pluginId);\n if (!registration) {\n throw new Error(`Plugin \"${pluginId}\" is not registered`);\n }\n if (!(await isPluginEnabled(pluginId))) {\n // pg-boss keeps firing the cron entry even when disabled; bail quietly so\n // the queue records a successful tick instead of a retry-storm.\n getLogger().debug(\"Skipping plugin scheduled task — plugin disabled\", {\n pluginId,\n taskId,\n });\n return;\n }\n const entry = registration.schedules.get(taskId);\n if (!entry) {\n throw new Error(`Plugin \"${pluginId}\" has no scheduled task with id \"${taskId}\"`);\n }\n const ctx = await buildCtxFor(pluginId);\n await entry.handler(ctx);\n}\n\nexport function resetPlugins(): void {\n pluginRegistry.clear();\n globalHooks.clear();\n globalRoutes.length = 0;\n}\n\nexport { isPluginEnabled, invalidatePluginEnabled } from \"./enabled-gate.js\";\n"],"mappings":"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","names":["eq","sql","getLogger","sql","eq","getPluginRegistration","eq","eq","eligibleIds","enqueueJob"]}
1
+ {"version":3,"sources":["../src/plugins/context.ts","../src/collections/pipeline.ts","../src/collections/slug.ts","../src/collections/validation.ts","../src/collections/search.ts","../src/plugins/enabled-gate.ts","../src/plugins/compat.ts","../src/plugins/host.ts"],"sourcesContent":["import type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { and, eq, gt, isNull, like, or } from \"drizzle-orm\";\n\nimport type { NpAuthUser, NpFindOptions } from \"../config/types.js\";\nimport { NpError, NpForbiddenError } from \"../errors.js\";\nimport {\n deleteDocument as coreDeleteDocument,\n findDocuments as coreFindDocuments,\n getDocumentById as coreGetDocumentById,\n saveDocument as coreSaveDocument,\n} from \"../collections/pipeline.js\";\nimport {\n listMedia as coreListMedia,\n getMediaById as coreGetMediaById,\n deleteMedia as coreDeleteMedia,\n uploadMedia as coreUploadMedia,\n getStorageAdapter,\n} from \"../media/service.js\";\nimport { getDb } from \"../db/runtime.js\";\nimport {\n NP_GLOBAL_PLUGIN_SITE_ID,\n npPluginStorage,\n npSettings,\n} from \"../db/schema/system.js\";\nimport { getScopedLogger } from \"../observability/logger.js\";\nimport { reportError } from \"../observability/error-reporter.js\";\nimport { getCurrentSiteId } from \"../sites/context.js\";\nimport { NP_DEFAULT_SITE_ID } from \"../sites/registry.js\";\n\n/**\n * Two distinct fallbacks live here, intentionally:\n *\n * - `resolveStorageSiteId` returns the `_global_` sentinel when no\n * site context is set. `np_plugin_storage` is keyed by\n * `(plugin_id, site_id, key)` and the sentinel scopes data as\n * \"process-wide / cross-site shared.\" Background workers, CLI\n * tasks, and migrations all run without a site resolver and\n * should land in the global keyspace by default.\n *\n * - `resolveSettingsSiteId` returns the actual default site id\n * when no context is set. `np_settings` rows ALWAYS belong to\n * a real site, so falling through to a sentinel would orphan\n * the row outside `np_sites` and break joins.\n *\n * They look superficially the same — one helper per intent so the\n * next reader doesn't have to reverse-engineer which fallback is\n * which by reading both schema definitions.\n */\nasync function resolveStorageSiteId(): Promise<string> {\n return (await getCurrentSiteId()) ?? NP_GLOBAL_PLUGIN_SITE_ID;\n}\n\nasync function resolveSettingsSiteId(): Promise<string> {\n return (await getCurrentSiteId()) ?? NP_DEFAULT_SITE_ID;\n}\n\n/**\n * Plugin principal used when plugin-initiated operations need an NpAuthUser.\n * Plugin ops bypass per-doc ACL; authorisation is enforced via the plugin's\n * declared `capabilities` instead. This matches the Phase 3 design: plugins\n * are trusted in-process code, capability flags gate coarse permissions.\n */\nconst pluginPrincipal = (pluginId: string): NpAuthUser => ({\n id: `plugin:${pluginId}`,\n email: `${pluginId}@plugins.local`,\n name: `plugin/${pluginId}`,\n role: \"admin\",\n tokenVersion: 0,\n});\n\ninterface RegistrationLike {\n actions: Map<string, (data: unknown) => Promise<{ ok: boolean; data?: unknown; error?: string }>>;\n}\n\ninterface BuildContextOptions {\n pluginId: string;\n capabilities: readonly string[];\n allowedHosts: readonly string[];\n config: Record<string, unknown>;\n registration: RegistrationLike;\n lookupRegistration: (pluginId: string) => RegistrationLike | undefined;\n}\n\n/**\n * Per-process in-memory cache for `ctx.cache.*`. Keyed by `pluginId:key`,\n * each entry carries its expiry (ms) so `get()` lazily evicts stale entries.\n * Lost on process restart — use `ctx.storage` for durable state.\n */\nconst pluginCache = new Map<string, { value: unknown; expiresAt: number | null }>();\n\nfunction cacheKey(pluginId: string, key: string): string {\n return `${pluginId}:${key}`;\n}\n\nfunction assertCap(pluginId: string, capabilities: readonly string[], required: string): void {\n if (!capabilities.includes(required)) {\n throw new NpForbiddenError(\n `plugin:${pluginId}`,\n `capability \"${required}\" not declared in manifest`,\n );\n }\n}\n\nasync function loadOptionalNextCache(): Promise<{\n revalidatePath?: (path: string) => void;\n revalidateTag?: (tag: string) => void;\n} | null> {\n try {\n // Indirect specifier so TypeScript doesn't try to resolve\n // `next/cache` at compile time — `@nexpress/core` doesn't\n // depend on Next.js. Only the Next-runtime path needs\n // the cache helpers; worker / CLI / standalone Node\n // consumers see this fall through to null cleanly.\n const moduleId: string = \"next/cache\";\n const mod = (await import(moduleId)) as {\n revalidatePath?: (path: string) => void;\n revalidateTag?: (tag: string) => void;\n };\n return mod;\n } catch {\n return null;\n }\n}\n\n/**\n * Produces the runtime ctx passed to plugin hook / route / setup handlers.\n * Matches the `NpPluginContext` shape declared in `@nexpress/plugin-sdk`.\n *\n * Every namespace declared on `NpPluginContext` is implemented:\n * - `pluginId`, `config`, `capabilities`\n * - `content.*` (find / findOne / save / delete)\n * - `media.*` (list / getById / getUrl / upload / delete)\n * - `settings.*`\n * - `log.*`\n * - `next.*`\n * - `actions.*`\n * - `storage.*` (plugin-scoped key/value persistence)\n * - `cache.*` (revalidatePath / revalidateTag wrappers)\n * - `theme.*` (read theme tokens / active theme)\n * - `http.fetch` (allowlist-gated outbound HTTP)\n *\n * Capability checks (`assertCap`) gate every namespace so a\n * plugin that only declares `content:read` can't reach into\n * `media:upload` etc. without explicit opt-in.\n */\nexport function createPluginRuntimeContext(options: BuildContextOptions): Record<string, unknown> {\n const { pluginId, capabilities, allowedHosts, config, registration, lookupRegistration } =\n options;\n const db = (): NodePgDatabase<Record<string, unknown>> => getDb();\n const principal = pluginPrincipal(pluginId);\n\n // Plugin logs flow through the global logger (`setLogger` at app boot)\n // with `pluginId` bound, so operators can filter / route / aggregate\n // plugin output without each plugin reaching for `console.*`.\n const pluginLog = getScopedLogger({ pluginId });\n\n return {\n pluginId,\n config,\n capabilities,\n\n content: {\n async find(collection: string, query?: Partial<NpFindOptions>) {\n assertCap(pluginId, capabilities, \"content:read\");\n return coreFindDocuments(collection, query ?? {}, principal);\n },\n async findOne(collection: string, id: string) {\n assertCap(pluginId, capabilities, \"content:read\");\n const doc = await coreGetDocumentById(collection, id, principal);\n return doc ?? null;\n },\n async create(collection: string, data: Record<string, unknown>) {\n assertCap(pluginId, capabilities, \"content:write\");\n const result = await coreSaveDocument(collection, null, data, principal);\n return result.doc;\n },\n async update(collection: string, id: string, data: Record<string, unknown>) {\n assertCap(pluginId, capabilities, \"content:write\");\n const result = await coreSaveDocument(collection, id, data, principal);\n return result.doc;\n },\n async delete(collection: string, id: string) {\n assertCap(pluginId, capabilities, \"content:delete\");\n await coreDeleteDocument(collection, id, principal);\n },\n async count(collection: string) {\n assertCap(pluginId, capabilities, \"content:read\");\n const result = await coreFindDocuments(collection, { limit: 1 }, principal);\n return result.totalDocs;\n },\n },\n\n media: {\n async list(query?: { page?: number; limit?: number; mimeType?: string; folder?: string }) {\n assertCap(pluginId, capabilities, \"media:read\");\n return coreListMedia({\n page: query?.page,\n limit: query?.limit,\n mimeType: query?.mimeType,\n folderId: query?.folder,\n });\n },\n async getById(id: string) {\n assertCap(pluginId, capabilities, \"media:read\");\n return coreGetMediaById(id);\n },\n async getUrl(id: string) {\n assertCap(pluginId, capabilities, \"media:read\");\n const media = await coreGetMediaById(id);\n if (!media || typeof media.storageKey !== \"string\") return \"\";\n const adapter = getStorageAdapter();\n return adapter.getUrl(media.storageKey);\n },\n async upload(\n file: Uint8Array | ArrayBuffer,\n metadata: { filename: string; mimeType: string; folder?: string },\n ) {\n assertCap(pluginId, capabilities, \"media:write\");\n const buffer = Buffer.from(file instanceof ArrayBuffer ? new Uint8Array(file) : file);\n return coreUploadMedia(\n {\n buffer,\n originalFilename: metadata.filename,\n mimeType: metadata.mimeType,\n },\n // `uploaded_by` is a nullable FK to `np_users.id`. The\n // previous `plugin:<id>` synthetic value violated the FK\n // and threw at insert time, leaving the storage object\n // orphaned. (#62) Plugin attribution lives in the audit\n // log + plugin-storage layer; the uploader column is for\n // staff-user provenance only.\n null,\n metadata.folder,\n );\n },\n async delete(id: string) {\n assertCap(pluginId, capabilities, \"media:delete\");\n const result = await coreDeleteMedia(id);\n if (!result.deleted && result.references && result.references.length > 0) {\n throw new NpError(\n `[plugin:${pluginId}] media.delete: ${id} is referenced by ${result.references.length} document(s).`,\n \"CONFLICT\",\n 409,\n );\n }\n },\n },\n\n storage: {\n // Phase 17 — every storage call resolves the current\n // site at call time and uses it as part of the composite\n // PK `(plugin_id, site_id, key)`. Background workers and\n // scripts (no site resolver) fall back to the\n // `_global_` sentinel so legacy single-site callers keep\n // their existing keyspace.\n async get<T = unknown>(key: string): Promise<T | null> {\n assertCap(pluginId, capabilities, \"storage:kv\");\n const siteId = await resolveStorageSiteId();\n const now = new Date();\n const rows = await db()\n .select()\n .from(npPluginStorage)\n .where(\n and(\n eq(npPluginStorage.pluginId, pluginId),\n eq(npPluginStorage.siteId, siteId),\n eq(npPluginStorage.key, key),\n or(isNull(npPluginStorage.expiresAt), gt(npPluginStorage.expiresAt, now)),\n ),\n )\n .limit(1);\n const row = rows[0] as { value?: unknown } | undefined;\n return (row?.value as T | undefined) ?? null;\n },\n async set(key: string, value: unknown, opts?: { ttl?: number }): Promise<void> {\n assertCap(pluginId, capabilities, \"storage:kv\");\n const siteId = await resolveStorageSiteId();\n const expiresAt = opts?.ttl && opts.ttl > 0 ? new Date(Date.now() + opts.ttl * 1000) : null;\n await db()\n .insert(npPluginStorage)\n .values({\n pluginId,\n siteId,\n key,\n value,\n expiresAt,\n updatedAt: new Date(),\n })\n .onConflictDoUpdate({\n target: [npPluginStorage.pluginId, npPluginStorage.siteId, npPluginStorage.key],\n set: { value, expiresAt, updatedAt: new Date() },\n });\n },\n async delete(key: string): Promise<void> {\n assertCap(pluginId, capabilities, \"storage:kv\");\n const siteId = await resolveStorageSiteId();\n await db()\n .delete(npPluginStorage)\n .where(\n and(\n eq(npPluginStorage.pluginId, pluginId),\n eq(npPluginStorage.siteId, siteId),\n eq(npPluginStorage.key, key),\n ),\n );\n },\n async list(prefix?: string): Promise<string[]> {\n assertCap(pluginId, capabilities, \"storage:kv\");\n const siteId = await resolveStorageSiteId();\n const now = new Date();\n const where = prefix\n ? and(\n eq(npPluginStorage.pluginId, pluginId),\n eq(npPluginStorage.siteId, siteId),\n like(npPluginStorage.key, `${prefix}%`),\n or(isNull(npPluginStorage.expiresAt), gt(npPluginStorage.expiresAt, now)),\n )\n : and(\n eq(npPluginStorage.pluginId, pluginId),\n eq(npPluginStorage.siteId, siteId),\n or(isNull(npPluginStorage.expiresAt), gt(npPluginStorage.expiresAt, now)),\n );\n const rows = (await db()\n .select({ key: npPluginStorage.key })\n .from(npPluginStorage)\n .where(where)) as Array<{ key: string }>;\n return rows.map((row) => row.key);\n },\n async has(key: string): Promise<boolean> {\n assertCap(pluginId, capabilities, \"storage:kv\");\n const siteId = await resolveStorageSiteId();\n const now = new Date();\n const rows = await db()\n .select({ key: npPluginStorage.key })\n .from(npPluginStorage)\n .where(\n and(\n eq(npPluginStorage.pluginId, pluginId),\n eq(npPluginStorage.siteId, siteId),\n eq(npPluginStorage.key, key),\n or(isNull(npPluginStorage.expiresAt), gt(npPluginStorage.expiresAt, now)),\n ),\n )\n .limit(1);\n return rows.length > 0;\n },\n },\n\n cache: {\n // The cache namespace is in-memory today (a process-\n // scoped Map). The interface is `Promise<...>` so a\n // future Redis-backed implementation can swap in\n // without breaking plugin authors; the sync\n // implementations return resolved promises directly so\n // the require-await rule stays happy.\n get<T = unknown>(key: string): Promise<T | null> {\n const entry = pluginCache.get(cacheKey(pluginId, key));\n if (!entry) return Promise.resolve(null);\n if (entry.expiresAt !== null && entry.expiresAt <= Date.now()) {\n pluginCache.delete(cacheKey(pluginId, key));\n return Promise.resolve(null);\n }\n return Promise.resolve(entry.value as T);\n },\n set(key: string, value: unknown, ttl?: number): Promise<void> {\n pluginCache.set(cacheKey(pluginId, key), {\n value,\n expiresAt: ttl && ttl > 0 ? Date.now() + ttl * 1000 : null,\n });\n return Promise.resolve();\n },\n invalidate(key: string): Promise<void> {\n pluginCache.delete(cacheKey(pluginId, key));\n return Promise.resolve();\n },\n invalidateAll(): Promise<void> {\n const prefix = `${pluginId}:`;\n for (const key of pluginCache.keys()) {\n if (key.startsWith(prefix)) pluginCache.delete(key);\n }\n return Promise.resolve();\n },\n },\n\n settings: {\n async getSite(): Promise<Record<string, unknown>> {\n assertCap(pluginId, capabilities, \"settings:read\");\n const siteId = await resolveSettingsSiteId();\n const rows = await db()\n .select()\n .from(npSettings)\n .where(and(eq(npSettings.siteId, siteId), eq(npSettings.key, \"site\")));\n const row = rows[0] as { value?: unknown } | undefined;\n if (!row || !row.value || typeof row.value !== \"object\" || Array.isArray(row.value)) {\n return {};\n }\n return row.value as Record<string, unknown>;\n },\n async getPlugin(): Promise<Record<string, unknown>> {\n // G.1 — plugin config moved from np_plugins.config to\n // np_settings.(siteId, \"plugin.config:<id>\"). Read via the\n // versioned-envelope-aware helper so plugins still see the\n // unwrapped value.\n const { getPluginConfig } = await import(\"./config.js\");\n const value = await getPluginConfig(pluginId);\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return {};\n },\n async setPlugin(data: Record<string, unknown>): Promise<void> {\n // G.1 — plugin config moved to np_settings. When the plugin\n // declares a configSchema, route through `setPluginConfig`\n // so the plugin's own schema validates writes coming from\n // its own setup() / handlers (the same path the admin form\n // uses). Plugins without configSchema bypass validation and\n // write the v1 envelope directly — preserves legacy\n // ctx.settings.setPlugin behavior.\n const { setPluginConfig } = await import(\"./config.js\");\n const { getPluginRegistration } = await import(\"./host.js\");\n const reg = getPluginRegistration(pluginId);\n if (reg?.configSchema) {\n await setPluginConfig(pluginId, data, null);\n return;\n }\n const siteId = (await getCurrentSiteId()) ?? NP_DEFAULT_SITE_ID;\n const now = new Date();\n await db()\n .insert(npSettings)\n .values({\n siteId,\n key: `plugin.config:${pluginId}`,\n value: { __npVersion: 1, __npSettings: data },\n updatedAt: now,\n updatedBy: null,\n })\n .onConflictDoUpdate({\n target: [npSettings.siteId, npSettings.key],\n set: {\n value: { __npVersion: 1, __npSettings: data },\n updatedAt: now,\n updatedBy: null,\n },\n });\n },\n },\n\n theme: {\n async getTokens(): Promise<Record<string, unknown>> {\n assertCap(pluginId, capabilities, \"theme:read\");\n const siteId = await resolveSettingsSiteId();\n const rows = await db()\n .select()\n .from(npSettings)\n .where(and(eq(npSettings.siteId, siteId), eq(npSettings.key, \"theme\")));\n const row = rows[0] as { value?: unknown } | undefined;\n if (!row || !row.value || typeof row.value !== \"object\" || Array.isArray(row.value)) {\n return {};\n }\n return row.value as Record<string, unknown>;\n },\n async setTokens(partial: Record<string, unknown>): Promise<void> {\n assertCap(pluginId, capabilities, \"theme:write\");\n const siteId = await resolveSettingsSiteId();\n const rows = await db()\n .select()\n .from(npSettings)\n .where(and(eq(npSettings.siteId, siteId), eq(npSettings.key, \"theme\")));\n const existing =\n rows[0] &&\n (rows[0] as { value?: unknown }).value &&\n typeof (rows[0] as { value?: unknown }).value === \"object\" &&\n !Array.isArray((rows[0] as { value?: unknown }).value)\n ? ((rows[0] as { value: unknown }).value as Record<string, unknown>)\n : {};\n const merged = { ...existing, ...partial };\n await db()\n .insert(npSettings)\n .values({ siteId, key: \"theme\", value: merged, updatedAt: new Date() })\n .onConflictDoUpdate({\n target: [npSettings.siteId, npSettings.key],\n set: { value: merged, updatedAt: new Date() },\n });\n },\n },\n\n http: {\n async fetch(\n url: string,\n opts?: {\n method?: string;\n headers?: Record<string, string>;\n body?: unknown;\n timeoutMs?: number;\n },\n ): Promise<{ ok: boolean; status: number; headers: Record<string, string>; body?: unknown }> {\n assertCap(pluginId, capabilities, \"network:fetch\");\n // Allowed-host check: manifest.allowedHosts gates every fetch. Empty\n // list means the plugin declared network:fetch but didn't scope it\n // — refuse rather than allow anything.\n let target: URL;\n try {\n target = new URL(url);\n } catch {\n throw new NpError(\n `[plugin:${pluginId}] http.fetch: invalid URL \"${url}\"`,\n \"INVALID_URL\",\n 400,\n );\n }\n const hostMatches = allowedHosts.some((pattern) => {\n if (pattern === target.hostname) return true;\n if (pattern.startsWith(\"*.\") && target.hostname.endsWith(pattern.slice(1))) return true;\n return false;\n });\n if (!hostMatches) {\n throw new NpForbiddenError(\n `plugin:${pluginId}`,\n `http.fetch to \"${target.hostname}\" blocked; add it to manifest.allowedHosts`,\n );\n }\n\n const timeoutMs = opts?.timeoutMs ?? 10_000;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n try {\n // `BodyInit` isn't in @types/node's global scope even with lib.dom\n // off, so keep the local union narrow enough for fetch() while\n // staying portable across runtimes.\n let body: string | Uint8Array | undefined;\n if (opts?.body !== undefined && opts.body !== null) {\n if (typeof opts.body === \"string\") {\n body = opts.body;\n } else if (opts.body instanceof Uint8Array) {\n body = opts.body;\n } else {\n body = JSON.stringify(opts.body);\n }\n }\n const response = await globalThis.fetch(url, {\n method: opts?.method ?? (body !== undefined ? \"POST\" : \"GET\"),\n headers: opts?.headers,\n body,\n signal: controller.signal,\n });\n const headers: Record<string, string> = {};\n response.headers.forEach((v, k) => {\n headers[k] = v;\n });\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n let parsedBody: unknown = undefined;\n if (contentType.includes(\"application/json\")) {\n parsedBody = await response.json().catch(() => undefined);\n } else if (contentType.startsWith(\"text/\")) {\n parsedBody = await response.text();\n }\n return {\n ok: response.ok,\n status: response.status,\n headers,\n body: parsedBody,\n };\n } finally {\n clearTimeout(timeout);\n }\n },\n },\n\n log: {\n debug(message: string, data?: Record<string, unknown>): void {\n pluginLog.debug(message, data);\n },\n info(message: string, data?: Record<string, unknown>): void {\n pluginLog.info(message, data);\n },\n warn(message: string, data?: Record<string, unknown>): void {\n pluginLog.warn(message, data);\n },\n error(message: string, data?: Record<string, unknown>): void {\n pluginLog.error(message, data);\n },\n },\n\n errors: {\n // Plugin-side error reporting with pluginId auto-tagged. The host\n // already auto-reports thrown hook handlers (in `dispatchHookHandler`),\n // so plugins typically only need this when *catching* an error\n // internally — e.g. a non-fatal upstream failure they want to log to\n // Sentry but recover from.\n report(\n error: unknown,\n context?: {\n extra?: Record<string, unknown>;\n tags?: Record<string, string>;\n user?: { id?: string; email?: string; role?: string };\n },\n ): Promise<void> {\n const err = error instanceof Error ? error : new Error(String(error));\n return reportError(err, {\n tags: { source: \"plugin\", pluginId, ...context?.tags },\n extra: context?.extra,\n user: context?.user,\n });\n },\n },\n\n next: {\n async revalidatePath(path: string): Promise<void> {\n const mod = await loadOptionalNextCache();\n mod?.revalidatePath?.(path);\n },\n async revalidateTag(tag: string): Promise<void> {\n const mod = await loadOptionalNextCache();\n const fn = mod?.revalidateTag;\n if (typeof fn !== \"function\") return;\n // Next 16 widened the signature to `(tag, profile)`.\n // Forward `\"default\"` when the runtime accepts the\n // extra arg so plugins keep their single-arg ergonomics.\n if (fn.length >= 2) {\n (fn as (tag: string, profile: string) => void)(tag, \"default\");\n } else {\n (fn as (tag: string) => void)(tag);\n }\n },\n },\n\n actions: {\n register(\n actionName: string,\n handler: (data: unknown) => Promise<{ ok: boolean; data?: unknown; error?: string }>,\n ): void {\n registration.actions.set(actionName, handler);\n },\n async dispatch(\n targetPluginId: string,\n actionName: string,\n data?: unknown,\n ): Promise<{ ok: boolean; data?: unknown; error?: string }> {\n const target = lookupRegistration(targetPluginId);\n const action = target?.actions.get(actionName);\n if (!action) {\n return {\n ok: false,\n error: `Action \"${actionName}\" not found on plugin \"${targetPluginId}\"`,\n };\n }\n return action(data);\n },\n },\n };\n}\n","import { randomUUID } from \"node:crypto\";\n\nimport { asc, count, desc, eq, inArray, sql, type SQL } from \"drizzle-orm\";\nimport type { AnyPgColumn, PgTable } from \"drizzle-orm/pg-core\";\n\nimport {\n type NpCollectionConfig,\n type NpDocumentStatus,\n type NpFindOptions,\n type NpFindResult,\n type NpSaveOptions,\n type NpSaveResult,\n type NpAuthUser,\n type NpCollectionHook,\n type NpFieldConfig,\n type NpHookPrincipal,\n} from \"../config/types.js\";\nimport { NpForbiddenError, NpNotFoundError, NpValidationError } from \"../errors.js\";\nimport { applySlugField } from \"./slug.js\";\nimport { getI18nConfig } from \"../i18n/registry.js\";\nimport { getCurrentSiteId } from \"../sites/context.js\";\nimport { NP_DEFAULT_SITE_ID } from \"../sites/registry.js\";\nimport { getCollectionZodSchema } from \"./validation.js\";\nimport { getCollectionConfig, getCollectionTable, getCollectionRegistration } from \"./registry.js\";\nimport { buildSearchVector, buildWeightedSearchVectorSql } from \"./search.js\";\nimport { enqueueJob } from \"../jobs/queue.js\";\nimport { runHook } from \"../plugins/host.js\";\nimport { npRevisions, npSlugHistory } from \"../db/schema/system.js\";\nimport { npComments, npReactions, npReports } from \"../db/schema/community.js\";\nimport { npMediaRefs } from \"../db/schema/media.js\";\nimport { getDb } from \"../db/runtime.js\";\n\ninterface PreparedDocumentData {\n mainData: Record<string, unknown>;\n childRows: Record<string, Record<string, unknown>[]>;\n joinRows: Record<string, string[]>;\n}\n\ntype QueryCondition = ReturnType<typeof sql>;\n\ninterface SelectQuery extends Promise<unknown[]> {\n where(condition: QueryCondition): SelectQuery;\n orderBy(order: QueryCondition): SelectQuery;\n limit(limit: number): SelectQuery;\n offset(offset: number): SelectQuery;\n}\n\ninterface InsertValuesQuery extends Promise<unknown> {\n returning(): Promise<unknown[]>;\n}\n\ninterface DrizzleTransactionLike {\n insert(table: PgTable): {\n values(values: Record<string, unknown> | Record<string, unknown>[]): InsertValuesQuery;\n };\n update(table: PgTable): {\n set(values: Record<string, unknown>): {\n where(condition: QueryCondition): {\n returning(): Promise<unknown[]>;\n };\n };\n };\n delete(table: PgTable): {\n where(condition: QueryCondition): Promise<unknown>;\n };\n select(selection?: Record<string, unknown>): {\n from(table: PgTable): SelectQuery;\n };\n}\n\ninterface DrizzleDatabaseLike extends DrizzleTransactionLike {\n transaction<T>(callback: (tx: DrizzleTransactionLike) => Promise<T>): Promise<T>;\n}\n\n/**\n * Internal actor type. The pipeline accepts either a staff `NpAuthUser`\n * (the original behavior) or a `{ kind: \"member\", memberId }` shape\n * (Phase 9.7a — `community.memberWrite.create` collections). Member\n * writes bypass the staff `access.create` access function: gating is\n * the per-collection opt-in flag plus `assertNotBanned(memberId)`,\n * not the staff access tree. `createdBy` / `updatedBy` / `authorId`\n * (revisions) are stored as null when the actor is a member; the\n * audit log captures the actual member id.\n */\ntype SaveActor = { kind: \"staff\"; user: NpAuthUser } | { kind: \"member\"; memberId: string };\n\nfunction actorUserOrNull(actor: SaveActor): NpAuthUser | null {\n return actor.kind === \"staff\" ? actor.user : null;\n}\n\nfunction actorUserId(actor: SaveActor): string | null {\n return actor.kind === \"staff\" ? actor.user.id : null;\n}\n\n/**\n * Polymorphic actor reference passed to collection hooks and\n * surfaced to plugin hooks via the `principal` payload field.\n * Mirrors `SaveActor` — kept structurally identical so hook\n * authors can switch on `kind` without importing a separate type.\n */\nfunction actorPrincipal(actor: SaveActor): NpHookPrincipal {\n switch (actor.kind) {\n case \"staff\":\n return { kind: \"staff\", user: actor.user };\n case \"member\":\n return { kind: \"member\", memberId: actor.memberId };\n default: {\n const _exhaustive: never = actor;\n void _exhaustive;\n throw new Error(\"actorPrincipal: unhandled SaveActor kind\");\n }\n }\n}\n\n/**\n * Run a side-effect that fires AFTER the document transaction has\n * already committed (job enqueue, plugin hook). The doc is durable\n * by this point — surfacing the error to the caller would make a\n * successful save look like a failure, so we swallow and surface\n * via the framework logger instead.\n *\n * Operators rely on this log line to discover skipped follow-ups\n * (search reindex, mention fanout, cache invalidation, etc.) and\n * replay manually. The full outbox-pattern fix lives in #277; this\n * is the minimum viable visibility shim.\n *\n * @internal — exported so the unit test can verify the\n * swallow + log contract directly. Not part of the package's\n * public API; do not use from outside `@nexpress/core`.\n */\nexport async function runPostCommit(\n label: string,\n context: { collection: string; documentId: string; operation?: string },\n fn: () => Promise<unknown>,\n): Promise<void> {\n try {\n await fn();\n } catch (err) {\n const { getLogger } = await import(\"../observability/logger.js\");\n getLogger().error(\n `post-commit ${label} failed — document persisted, follow-up skipped`,\n {\n collection: context.collection,\n documentId: context.documentId,\n operation: context.operation,\n label,\n error: err instanceof Error ? err.message : String(err),\n stack: err instanceof Error ? err.stack : undefined,\n },\n );\n }\n}\n\nexport async function saveDocument(\n collection: string,\n docId: string | null,\n data: Record<string, unknown>,\n user: NpAuthUser,\n options?: NpSaveOptions,\n): Promise<NpSaveResult> {\n return saveDocumentImpl(collection, docId, data, { kind: \"staff\", user }, options);\n}\n\n/**\n * Member-side document create. Only valid when\n * `config.community?.memberWrite?.create === true`. Assumes the API\n * layer has already authenticated the member (and that the cookie's\n * status was checked) — this function adds:\n * - the per-collection opt-in gate, and\n * - `assertNotBanned(memberId)` (site-wide; per-collection bans\n * resolve to the same site scope)\n *\n * Fires the `document.created` reputation event after a successful\n * write so adapters can credit the author the same way they credit\n * comments / reactions. Member-side update / delete live in\n * `updateMemberDocument` / `deleteMemberDocument` below.\n */\n/**\n * Member-side document update. Only valid when\n * `config.community?.memberWrite?.update === true` AND the existing\n * row's `member_author_id` matches the caller. Members can NOT\n * change `_status` via update — `options.status` is stripped here\n * so a forged body field can't bypass the moderation pipeline. The\n * author column itself is also locked — see saveDocumentImpl.\n */\nexport async function updateMemberDocument(\n collection: string,\n docId: string,\n data: Record<string, unknown>,\n memberId: string,\n options?: NpSaveOptions,\n): Promise<NpSaveResult> {\n const memberOptions: NpSaveOptions = { ...(options ?? {}) };\n delete memberOptions.status;\n\n // Cheap authorization checks BEFORE moderation (#139). Without\n // this gate a banned member or a non-owner could still trigger\n // the (potentially-paid, potentially-network) spam/profanity\n // adapters before `saveDocumentImpl` rejects them. Order:\n // 1. collection opt-in\n // 2. doc existence\n // 3. owner check\n // 4. ban check\n // These are duplicated inside `saveDocumentImpl`, but both\n // execute the same SELECT-or-cache queries; doing them here\n // saves the moderation round-trip on doomed requests. If any\n // of these throw, the moderation never runs.\n const config = getCollectionConfig(collection);\n if (!config.community?.memberWrite?.update) {\n throw new NpForbiddenError(collection, \"update\");\n }\n const table = getCollectionTable(collection) as PgTable;\n const dbForGate = getDb() as unknown as DrizzleDatabaseLike;\n const originalDoc = await getDocumentByIdInternal(dbForGate, table, collection, docId);\n if (!originalDoc) {\n throw new NpNotFoundError(collection, docId);\n }\n const authorId = (originalDoc as { memberAuthorId?: string | null }).memberAuthorId ?? null;\n if (authorId !== memberId) {\n throw new NpForbiddenError(collection, \"update\");\n }\n const { assertNotBanned } = await import(\"../community/can.js\");\n await assertNotBanned(memberId);\n\n // Re-run the spam + profanity adapters on the submitted patch.\n // Pre-fix this path skipped moderation entirely, so a member\n // could create a clean discussion, get it published, then PATCH\n // it to spam/profanity and the row stayed published. The same\n // verdict semantics apply as the create path:\n // - reject → 400, no write\n // - flag → status forced to `pending`\n // - pass → status untouched (the original status survives)\n const moderation = await runMemberDocModeration({\n collection,\n data,\n memberId,\n targetId: docId,\n });\n if (moderation.flaggedBy.length > 0) {\n memberOptions.status = \"pending\";\n }\n\n const result = await saveDocumentImpl(\n collection,\n docId,\n data,\n { kind: \"member\", memberId },\n memberOptions,\n );\n const { recordAuditEvent } = await import(\"../community/audit.js\");\n await recordAuditEvent({\n actor: { kind: \"member\", memberId },\n action: moderation.flaggedBy.length > 0 ? \"document.flag\" : \"document.update\",\n targetType: collection,\n targetId: docId,\n payload: {\n collectionSlug: collection,\n event: \"update\",\n ...(moderation.flaggedBy.length > 0 ? { sources: moderation.flaggedBy } : {}),\n ...(moderation.profanityVerdict ? { profanityVerdict: moderation.profanityVerdict } : {}),\n ...(moderation.spamVerdict ? { spamVerdict: moderation.spamVerdict } : {}),\n },\n });\n\n // Phase 16.2 — @mention fan-out on edit. Only fire for edits\n // that landed `published` (skip flagged-to-pending edits — same\n // policy as `comment.mention` on update). Delta the prior\n // body so toggling unrelated fields doesn't re-notify the same\n // recipients.\n const resultStatus = (result.doc as { status?: unknown }).status;\n if (resultStatus === \"published\") {\n const { extractMentionHandlesFromDocData, fanOutMentionNotifications } =\n await import(\"../community/mentions.js\");\n const previousHandles = new Set(extractMentionHandlesFromDocData(originalDoc));\n await fanOutMentionNotifications({\n actorMemberId: memberId,\n kind: \"document.mention\",\n data,\n previousHandles,\n payload: {\n collectionSlug: collection,\n documentId: docId,\n },\n });\n }\n return result;\n}\n\nexport async function createMemberDocument(\n collection: string,\n data: Record<string, unknown>,\n memberId: string,\n options?: NpSaveOptions,\n): Promise<NpSaveResult> {\n // Members can't author drafts / archive / schedule — those status\n // transitions are admin-side affordances. The status that\n // member-authored creates land in is governed by:\n // 1. The collection's `community.memberWrite.defaultStatus`\n // (default `\"published\"` — sites that want a moderation gate\n // flip to `\"pending\"`).\n // 2. The spam adapter's verdict on this individual write — `flag`\n // forces `\"pending\"` regardless of the default; `reject`\n // refuses the write entirely; `pass` accepts the default.\n // The API body's `_status` is always ignored.\n const config = getCollectionConfig(collection);\n\n // Cheap authorization checks BEFORE moderation (#139). Banned\n // members or members in collections that haven't opted into\n // member-write should never reach the (potentially-paid)\n // spam/profanity adapters. These same checks run again inside\n // `saveDocumentImpl`, but doing them here saves the\n // moderation round-trip on doomed requests.\n if (!config.community?.memberWrite?.create) {\n throw new NpForbiddenError(collection, \"create\");\n }\n const { assertNotBanned } = await import(\"../community/can.js\");\n await assertNotBanned(memberId);\n\n const defaultStatus: NpDocumentStatus =\n config.community?.memberWrite?.defaultStatus === \"pending\" ? \"pending\" : \"published\";\n\n const moderation = await runMemberDocModeration({\n collection,\n data,\n memberId,\n targetId: \"\",\n });\n const flaggedBy = moderation.flaggedBy;\n const spamStatus: NpDocumentStatus = flaggedBy.length > 0 ? \"pending\" : defaultStatus;\n\n const memberOptions: NpSaveOptions = { ...(options ?? {}), status: spamStatus };\n const result = await saveDocumentImpl(\n collection,\n null,\n data,\n { kind: \"member\", memberId },\n memberOptions,\n );\n\n const { applyReputation } = await import(\"../community/reputation.js\");\n const { recordAuditEvent } = await import(\"../community/audit.js\");\n const documentId = getRecordId(result.doc);\n // `document.flag` action when either adapter flagged this row.\n // A pending row that got there via `defaultStatus=\"pending\"` is\n // config-driven, not a per-row flag, so it stays under\n // `document.create`. The `sources` array tells mods which\n // adapter(s) flagged the row.\n await recordAuditEvent({\n actor: { kind: \"member\", memberId },\n action: flaggedBy.length > 0 ? \"document.flag\" : \"document.create\",\n targetType: collection,\n targetId: documentId,\n payload: {\n collectionSlug: collection,\n event: \"create\",\n ...(flaggedBy.length > 0 ? { sources: flaggedBy } : {}),\n ...(moderation.profanityVerdict ? { profanityVerdict: moderation.profanityVerdict } : {}),\n ...(moderation.spamVerdict ? { spamVerdict: moderation.spamVerdict } : {}),\n },\n });\n // Reputation only credits visible (i.e. `published`) creates.\n // Pending docs wait on a mod restore — at that point the\n // moderation surface can decide whether to retroactively credit.\n // Mirrors the `comment.created` semantic.\n if (spamStatus === \"published\") {\n await applyReputation(memberId, {\n kind: \"document.created\",\n collectionSlug: collection,\n documentId,\n memberId,\n });\n }\n\n // Phase 16.2 — @mention fan-out. Same gate as reputation: only\n // visible (`published`) creates fire. Pending docs wait on mod\n // restore so notifications can't surface text the public list\n // won't render.\n if (spamStatus === \"published\") {\n const { fanOutMentionNotifications } = await import(\"../community/mentions.js\");\n await fanOutMentionNotifications({\n actorMemberId: memberId,\n kind: \"document.mention\",\n data,\n payload: {\n collectionSlug: collection,\n documentId,\n },\n });\n }\n return result;\n}\n\ninterface MemberDocModerationResult {\n flaggedBy: Array<\"profanity\" | \"spam\">;\n profanityVerdict: { reason: string | null; metadata: Record<string, unknown> | null } | null;\n spamVerdict: { reason: string | null; metadata: Record<string, unknown> | null } | null;\n}\n\ninterface RunMemberDocModerationInput {\n collection: string;\n data: Record<string, unknown>;\n memberId: string;\n /** Empty string for create, the doc id for update. */\n targetId: string;\n}\n\n/**\n * Runs the profanity → spam adapter chain on a member-authored\n * document write (create or update). Shared between\n * `createMemberDocument` and `updateMemberDocument` so the\n * moderation gate can't drift between the two surfaces (#121).\n *\n * The moderation text is built from every text / textarea /\n * richText field present in `data` — `buildSearchVector` already\n * implements that walk for the FTS index, so we reuse it here\n * (same input set, different downstream consumer). Pre-fix this\n * function only saw `data.title`, so a member could keep a\n * benign title and put spam / slurs in the rich-text body and\n * the adapters would never see them (#119).\n *\n * Verdict semantics match the comment write path:\n * - reject → throws `NpValidationError`\n * - flag → returned with the source recorded in `flaggedBy`\n * - pass → returned with empty `flaggedBy`\n *\n * Adapter throws are fail-open (logged as warnings, treated as\n * pass) — same policy as comments and the original create-only\n * gate.\n */\nasync function runMemberDocModeration(\n input: RunMemberDocModerationInput,\n): Promise<MemberDocModerationResult> {\n const { collection, data, memberId, targetId } = input;\n const config = getCollectionConfig(collection);\n const { getSpamAdapter } = await import(\"../community/spam-adapter.js\");\n const { getProfanityAdapter } = await import(\"../community/profanity-adapter.js\");\n const { getLogger } = await import(\"../observability/logger.js\");\n\n // Walk every text / textarea / richText field in the patch.\n // Empty string when none of the moderated fields are touched\n // — the adapters then run on empty text, which by convention\n // passes (no content = no policy violation).\n const moderationText = buildSearchVector(config, data);\n const ctx = {\n memberId,\n targetType: collection,\n targetId,\n parentId: null,\n };\n\n let profanityVerdict: MemberDocModerationResult[\"profanityVerdict\"] = null;\n try {\n const verdict = await getProfanityAdapter().check(moderationText, ctx);\n if (verdict.kind === \"reject\") {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"body\",\n message: verdict.reason ?? \"Submission contains prohibited language\",\n },\n ]);\n }\n if (verdict.kind === \"flag\") {\n profanityVerdict = {\n reason: verdict.reason ?? null,\n metadata: verdict.metadata ?? null,\n };\n }\n } catch (err) {\n if (err instanceof NpValidationError) throw err;\n getLogger().warn(\"profanity adapter threw on doc write — treating as pass\", {\n error: err instanceof Error ? err.message : String(err),\n collection,\n memberId,\n });\n }\n\n let spamVerdict: MemberDocModerationResult[\"spamVerdict\"] = null;\n try {\n const verdict = await getSpamAdapter().check(moderationText, ctx);\n if (verdict.kind === \"reject\") {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"body\",\n message: verdict.reason ?? \"Submission rejected\",\n },\n ]);\n }\n if (verdict.kind === \"flag\") {\n spamVerdict = {\n reason: verdict.reason ?? null,\n metadata: verdict.metadata ?? null,\n };\n }\n } catch (err) {\n if (err instanceof NpValidationError) throw err;\n getLogger().warn(\"spam adapter threw on doc write — treating as pass\", {\n error: err instanceof Error ? err.message : String(err),\n collection,\n memberId,\n });\n }\n\n const flaggedBy: Array<\"profanity\" | \"spam\"> = [];\n if (profanityVerdict) flaggedBy.push(\"profanity\");\n if (spamVerdict) flaggedBy.push(\"spam\");\n\n return { flaggedBy, profanityVerdict, spamVerdict };\n}\n\n/**\n * Threaded state for `saveDocumentImpl`'s four concerns (#314).\n * Helpers add to this object as the request progresses through the\n * pipeline; the final concern (`firePostCommitHooks`) consumes the\n * accumulated context. The split makes it visible at review time\n * which step gates what.\n *\n * Some fields (`prepared`, `searchVector`, `publishTransition`) are\n * undefined until `prepareDocumentForWrite` runs; downstream helpers\n * can assume the prepare step has completed because the chain is\n * fixed in `saveDocumentImpl`.\n */\ninterface SaveContext {\n // === Setup, present from initSaveContext onward. ===\n collection: string;\n docId: string | null;\n validatedData: Record<string, unknown>;\n actor: SaveActor;\n options: NpSaveOptions | undefined;\n config: ReturnType<typeof getCollectionConfig>;\n registration: ReturnType<typeof getCollectionRegistration>;\n table: PgTable;\n db: DrizzleDatabaseLike;\n operation: \"create\" | \"update\";\n originalDoc: Record<string, unknown> | null;\n userForHooks: NpAuthUser | null;\n principal: NpHookPrincipal;\n // === Populated by prepareDocumentForWrite. ===\n hookData: Record<string, unknown>;\n prepared: PreparedDocumentData;\n searchVector: ReturnType<typeof buildWeightedSearchVectorSql>;\n publishTransition: boolean;\n unpublishTransition: boolean;\n now: Date;\n}\n\nasync function initSaveContext(\n collection: string,\n docId: string | null,\n data: Record<string, unknown>,\n actor: SaveActor,\n options: NpSaveOptions | undefined,\n): Promise<Omit<SaveContext, \"hookData\" | \"prepared\" | \"searchVector\" | \"publishTransition\" | \"unpublishTransition\" | \"now\">> {\n const config = getCollectionConfig(collection);\n const registration = getCollectionRegistration(collection);\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n const validatedData = toRecord(getCollectionZodSchema(config).parse(data));\n const operation: \"create\" | \"update\" = docId ? \"update\" : \"create\";\n const originalDoc = docId ? await getDocumentByIdInternal(db, table, collection, docId) : null;\n return {\n collection,\n docId,\n validatedData,\n actor,\n options,\n config,\n registration,\n table,\n db,\n operation,\n originalDoc,\n userForHooks: actorUserOrNull(actor),\n principal: actorPrincipal(actor),\n };\n}\n\n/**\n * Concern 1 — access checks. Staff writes go through the configured\n * `access.create` / `access.update` tree; member writes hit the\n * per-collection `community.memberWrite.create / update` opt-in\n * plus a ban check, plus an ownership check on update.\n *\n * Throws `NpForbiddenError` / `NpNotFoundError` on rejection.\n */\nasync function validateActorAccess(ctx: SaveContext): Promise<void> {\n if (ctx.actor.kind === \"staff\") {\n await assertWriteAccess(\n ctx.config,\n ctx.collection,\n ctx.operation,\n ctx.actor.user,\n ctx.validatedData,\n ctx.originalDoc,\n );\n return;\n }\n // Member actor. Phase 9.7a opened create; 9.7b opens update with\n // an owner-only check. Each transition has a separate opt-in\n // flag so a site can allow self-authoring without enabling\n // self-edit. Defer-load to avoid the community ↔ collections\n // import cycle.\n const { assertNotBanned } = await import(\"../community/can.js\");\n if (ctx.operation === \"create\") {\n if (!ctx.config.community?.memberWrite?.create) {\n throw new NpForbiddenError(ctx.collection, \"create\");\n }\n await assertNotBanned(ctx.actor.memberId);\n return;\n }\n // update — the doc must exist and must be authored by THIS\n // member (`member_author_id` matches). 404 / 403 disambiguate:\n // 404 when there's no row at all, 403 when the row belongs to\n // someone else (or to staff with `member_author_id = null`).\n if (!ctx.originalDoc) {\n throw new NpNotFoundError(ctx.collection, ctx.docId ?? \"unknown\");\n }\n if (!ctx.config.community?.memberWrite?.update) {\n throw new NpForbiddenError(ctx.collection, \"update\");\n }\n const authorId = (ctx.originalDoc as { memberAuthorId?: string | null }).memberAuthorId ?? null;\n if (authorId !== ctx.actor.memberId) {\n throw new NpForbiddenError(ctx.collection, \"update\");\n }\n await assertNotBanned(ctx.actor.memberId);\n}\n\n/**\n * Concern 2 — prepare the document for write. Runs the\n * collection's `beforeCreate` / `beforeUpdate` hooks, applies\n * slug generation, resolves i18n locale + translation group,\n * runs `prepareDocumentData`, stamps multi-site / member-author\n * columns, demotes future-dated `published` to `scheduled`,\n * builds the search-vector SQL fragment, and computes the\n * publish/unpublish transition.\n *\n * Mutates `ctx` to install the prepared fields.\n */\nasync function prepareDocumentForWrite(c: SaveContext): Promise<void> {\n c.hookData = await runHooks(\n c.operation === \"create\" ? c.config.hooks?.beforeCreate : c.config.hooks?.beforeUpdate,\n {\n data: c.validatedData,\n user: c.userForHooks,\n principal: c.principal,\n collection: c.collection,\n originalDoc: c.originalDoc,\n },\n );\n\n applySlugField(c.config, c.hookData, c.originalDoc);\n\n // Phase 12.1 — i18n collections need locale + translation\n // group resolved before the row is written. On creates the\n // locale defaults to `defaultLocale`, the translationGroupId\n // defaults to a new UUID. On updates those columns are\n // sticky — pulled from `originalDoc` so a body field can't\n // reassign them.\n let i18nResolved: { locale: string; translationGroupId: string } | null = null;\n if (c.config.i18n) {\n const i18n = getI18nConfig();\n if (!i18n) {\n throw new Error(\n `Collection \"${c.collection}\" is i18n-enabled but the framework has no i18n config (setI18nConfig was never called).`,\n );\n }\n if (c.operation === \"create\") {\n const requestedLocale = (c.hookData as { locale?: unknown }).locale;\n const locale =\n typeof requestedLocale === \"string\" && requestedLocale.length > 0\n ? requestedLocale\n : i18n.defaultLocale;\n if (!i18n.locales.includes(locale)) {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"locale\",\n message: `Locale \"${locale}\" is not configured. Allowed: ${i18n.locales.join(\", \")}.`,\n },\n ]);\n }\n const requestedGroup = (c.hookData as { translationGroupId?: unknown }).translationGroupId;\n const translationGroupId =\n typeof requestedGroup === \"string\" && requestedGroup.length > 0\n ? requestedGroup\n : randomUUID();\n i18nResolved = { locale, translationGroupId };\n } else {\n const original = c.originalDoc as { locale?: string; translationGroupId?: string } | null;\n if (!original?.locale || !original.translationGroupId) {\n throw new Error(\n `i18n collection \"${c.collection}\" doc ${c.docId} is missing locale/translationGroupId. The row predates i18n opt-in; backfill required.`,\n );\n }\n i18nResolved = {\n locale: original.locale,\n translationGroupId: original.translationGroupId,\n };\n }\n }\n\n c.prepared = prepareDocumentData(c.config.fields, c.hookData);\n if (c.options?.status) {\n c.prepared.mainData.status = c.options.status;\n }\n if (i18nResolved) {\n c.prepared.mainData.locale = i18nResolved.locale;\n c.prepared.mainData.translationGroupId = i18nResolved.translationGroupId;\n }\n\n // Phase 15.2 — multi-site scoping. Stamp every write with\n // the resolved site id. Creates pull from the request\n // context (or fall back to the default site for scripts /\n // workers / tests with no resolver). Updates inherit the\n // original row's site id — body fields can't reassign a doc.\n if (c.operation === \"create\") {\n const resolved = await getCurrentSiteId();\n c.prepared.mainData.siteId = resolved ?? NP_DEFAULT_SITE_ID;\n } else {\n const original = c.originalDoc as { siteId?: string } | null;\n c.prepared.mainData.siteId = original?.siteId ?? NP_DEFAULT_SITE_ID;\n }\n // Stamp / strip member_author_id. The column is generated only\n // when `community.memberWrite.create` is on; staff-authored docs\n // leave it null. Defense-in-depth on update: even though zod\n // strips unknown keys, we explicitly delete the field on member\n // updates so a body-injected value can't reassign authorship.\n if (c.actor.kind === \"member\") {\n if (c.operation === \"create\") {\n c.prepared.mainData.memberAuthorId = c.actor.memberId;\n } else {\n delete c.prepared.mainData.memberAuthorId;\n }\n }\n c.now = new Date();\n\n // Scheduled publishing: if the caller wants status=published but\n // publishedAt is in the future, demote to \"scheduled\" so the\n // public site doesn't render it until the scheduler flips it back.\n const desiredStatus = c.prepared.mainData.status as string | undefined;\n const publishedAtValue = c.prepared.mainData.publishedAt;\n if (desiredStatus === \"published\" && publishedAtValue instanceof Date && publishedAtValue > c.now) {\n c.prepared.mainData.status = \"scheduled\";\n }\n\n // Phase 10.7 — weighted tsvector so titles outrank body\n // matches at query time.\n c.searchVector = buildWeightedSearchVectorSql(c.config, c.hookData);\n\n // Publish-transition tracking for content:beforePublish /\n // afterPublish / beforeUnpublish hooks. Status precedence:\n // explicit prepared status > original doc (on update) >\n // \"published\" default (on create).\n const nextStatus =\n (c.prepared.mainData.status as string | undefined) ??\n (c.operation === \"update\"\n ? ((c.originalDoc?.status as string | undefined) ?? \"published\")\n : \"published\");\n const previousStatus = c.originalDoc?.status as string | undefined;\n const wasPublished = previousStatus === \"published\";\n const willBePublished = nextStatus === \"published\";\n c.publishTransition = !wasPublished && willBePublished;\n c.unpublishTransition = wasPublished && !willBePublished;\n}\n\n/**\n * Concern 3 — fire pre-write plugin hooks, then run the document\n * persistence inside one transaction (main row + child + join +\n * media-ref + revision). Returns the saved doc.\n */\nasync function persistDocumentTx(ctx: SaveContext): Promise<Record<string, unknown>> {\n await runHook(\n ctx.operation === \"create\" ? \"content:beforeCreate\" : \"content:beforeUpdate\",\n {\n collection: ctx.collection,\n data: ctx.hookData,\n originalDoc: ctx.originalDoc,\n user: ctx.userForHooks,\n principal: ctx.principal,\n operation: ctx.operation,\n },\n );\n if (ctx.publishTransition) {\n await runHook(\"content:beforePublish\", {\n collection: ctx.collection,\n data: ctx.hookData,\n originalDoc: ctx.originalDoc,\n user: ctx.userForHooks,\n principal: ctx.principal,\n });\n }\n if (ctx.unpublishTransition) {\n await runHook(\"content:beforeUnpublish\", {\n collection: ctx.collection,\n data: ctx.hookData,\n originalDoc: ctx.originalDoc,\n user: ctx.userForHooks,\n principal: ctx.principal,\n });\n }\n\n return ctx.db.transaction(async (tx) => {\n const persistedDoc: Record<string, unknown> =\n ctx.operation === \"update\"\n ? await updateMainDocument(\n tx,\n ctx.table,\n ctx.collection,\n ctx.docId,\n ctx.prepared.mainData,\n ctx.searchVector,\n ctx.config,\n ctx.userForHooks,\n ctx.now,\n )\n : await createMainDocument(\n tx,\n ctx.table,\n ctx.prepared.mainData,\n ctx.searchVector,\n ctx.config,\n ctx.userForHooks,\n ctx.now,\n );\n const persistedDocId = getRecordId(persistedDoc);\n\n await syncChildTables(tx, ctx.registration.childTables, ctx.prepared.childRows, persistedDocId);\n await syncJoinTables(tx, ctx.registration.joinTables, ctx.prepared.joinRows, persistedDocId);\n await syncMediaRefsForDocument(tx, ctx.collection, persistedDocId, ctx.config.fields, ctx.hookData);\n\n // Slug-rename history. When a slug-having collection's row\n // changes its slug, write an `oldSlug → newSlug` record so\n // the public-site catch-all can 301 old URLs (search-engine\n // indices, external links, bookmarks) to the new path. Doing\n // this inside the same tx keeps the redirect map consistent\n // with the actual doc — half-applied state isn't possible.\n // Skipped on creates and on updates that don't change slug.\n if (\n ctx.operation === \"update\" &&\n ctx.config.slugField &&\n ctx.originalDoc &&\n typeof ctx.originalDoc.slug === \"string\" &&\n typeof persistedDoc.slug === \"string\" &&\n ctx.originalDoc.slug.length > 0 &&\n ctx.originalDoc.slug !== persistedDoc.slug\n ) {\n const siteId = (persistedDoc.siteId as string | undefined) ?? NP_DEFAULT_SITE_ID;\n await tx.insert(npSlugHistory).values({\n siteId,\n collection: ctx.collection,\n documentId: String(persistedDocId),\n oldSlug: ctx.originalDoc.slug,\n newSlug: persistedDoc.slug,\n });\n }\n\n if (ctx.config.versions) {\n const docStatus = persistedDoc.status as string | undefined;\n // \"scheduled\" documents haven't actually gone live yet — treat their\n // revisions as drafts (they map to the pre-publish snapshot).\n const revisionStatus = docStatus === \"published\" ? \"published\" : \"draft\";\n const maxRevisions =\n typeof ctx.config.versions === \"object\" && ctx.config.versions.max !== undefined\n ? ctx.config.versions.max\n : undefined;\n await insertRevision(\n tx,\n ctx.collection,\n persistedDocId,\n ctx.operation,\n ctx.hookData,\n ctx.originalDoc,\n ctx.userForHooks,\n revisionStatus,\n maxRevisions,\n );\n }\n\n return persistedDoc;\n });\n}\n\n/**\n * Concern 4 — fire post-commit work: enqueue the\n * `content:afterSave` job, then `content:afterCreate` /\n * `content:afterUpdate` plugin hooks, plus `content:afterPublish`\n * on a publish transition. Each is wrapped in `runPostCommit` so\n * a hook error doesn't roll back the durable write.\n */\nasync function firePostCommitHooks(\n ctx: SaveContext,\n savedDoc: Record<string, unknown>,\n): Promise<void> {\n const savedDocId = getRecordId(savedDoc);\n const postCommitCtx = {\n collection: ctx.collection,\n documentId: savedDocId,\n operation: ctx.operation,\n };\n\n await runPostCommit(\"enqueue:content:afterSave\", postCommitCtx, () =>\n enqueueJob(\"content:afterSave\", {\n collection: ctx.collection,\n documentId: savedDocId,\n operation: ctx.operation,\n userId: actorUserId(ctx.actor),\n }),\n );\n\n const pluginHookName = ctx.operation === \"create\" ? \"content:afterCreate\" : \"content:afterUpdate\";\n await runPostCommit(`hook:${pluginHookName}`, postCommitCtx, () =>\n runHook(pluginHookName, {\n collection: ctx.collection,\n doc: savedDoc,\n operation: ctx.operation,\n user: ctx.userForHooks,\n principal: ctx.principal,\n }),\n );\n if (ctx.publishTransition) {\n await runPostCommit(\"hook:content:afterPublish\", postCommitCtx, () =>\n runHook(\"content:afterPublish\", {\n collection: ctx.collection,\n doc: savedDoc,\n operation: ctx.operation,\n user: ctx.userForHooks,\n principal: ctx.principal,\n }),\n );\n }\n}\n\nasync function saveDocumentImpl(\n collection: string,\n docId: string | null,\n data: Record<string, unknown>,\n actor: SaveActor,\n options?: NpSaveOptions,\n): Promise<NpSaveResult> {\n const ctxBase = await initSaveContext(collection, docId, data, actor, options);\n await validateActorAccess(ctxBase as SaveContext);\n const ctx = ctxBase as SaveContext;\n await prepareDocumentForWrite(ctx);\n const savedDoc = await persistDocumentTx(ctx);\n await firePostCommitHooks(ctx, savedDoc);\n return { doc: savedDoc, operation: ctx.operation };\n}\n\n/**\n * Persist an in-flight editor snapshot as a revision **without** touching\n * the main document row. Designed for client-side autosave loops: the\n * editor sends every few seconds while the user types, and a crash mid-\n * edit can be recovered by restoring the latest autosave revision.\n *\n * - Requires `versions.drafts` to be enabled on the collection.\n * - Optionally gated by `versions.drafts.autosave === true` (when\n * `versions` is the object form). Throws `NpValidationError` otherwise\n * so the API can return a tidy 4xx instead of silently writing.\n * - Skips the full zod validation that `saveDocument` runs — autosave\n * payloads may be temporarily incomplete (the user is still typing).\n * - Skips hooks, jobs, and revalidation: nothing is \"saved\" yet.\n * - Deduplicates against the most recent autosave: if the snapshot is\n * byte-identical to the previous autosave row, returns the existing\n * summary instead of writing a new one. Avoids unbounded autosave\n * rows during long idle edit sessions where react-hook-form fires\n * spurious \"change\" events.\n */\nexport async function autosaveRevision(\n collection: string,\n documentId: string,\n data: Record<string, unknown>,\n user: NpAuthUser,\n): Promise<{\n id: string;\n version: number;\n status: \"autosave\";\n createdAt: Date;\n reused: boolean;\n}> {\n const config = getCollectionConfig(collection);\n const registration = getCollectionRegistration(collection);\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n\n const drafts = config.versions?.drafts;\n if (!drafts) {\n throw new NpValidationError(\"Autosave not available\", [\n {\n field: \"collection\",\n message: `Collection \"${collection}\" has versions.drafts disabled — autosave is unavailable.`,\n },\n ]);\n }\n // `drafts: true` opts in to drafts but stays silent on autosave; we\n // require an explicit `{ autosave: true }` to avoid surprising existing\n // collections with extra DB writes per keystroke.\n const autosaveEnabled = typeof drafts === \"object\" && drafts.autosave === true;\n if (!autosaveEnabled) {\n throw new NpValidationError(\"Autosave disabled\", [\n {\n field: \"collection\",\n message: `Autosave is not enabled for \"${collection}\" — set versions.drafts.autosave = true.`,\n },\n ]);\n }\n\n const originalDoc = await getDocumentByIdInternal(db, table, collection, documentId);\n if (!originalDoc) {\n throw new NpNotFoundError(collection, documentId);\n }\n\n // Reuse the same access gate `saveDocument` runs for an update — autosave\n // is a write, even if it only lands in np_revisions.\n await assertWriteAccess(config, collection, \"update\", user, data, originalDoc);\n\n // Dedup against the latest autosave for this doc.\n const [latestAutosave] = (await db\n .select({\n id: npRevisions.id,\n version: npRevisions.version,\n snapshot: npRevisions.snapshot,\n createdAt: npRevisions.createdAt,\n })\n .from(npRevisions)\n .where(\n sql`${eq(npRevisions.collection, collection)} and ${eq(npRevisions.documentId, documentId)} and ${eq(npRevisions.status, \"autosave\")}`,\n )\n .orderBy(desc(npRevisions.version))\n .limit(1)) as Array<{\n id: string;\n version: number;\n snapshot: Record<string, unknown> | null;\n createdAt: Date;\n }>;\n if (latestAutosave && stableJson(latestAutosave.snapshot) === stableJson(data)) {\n return {\n id: latestAutosave.id,\n version: latestAutosave.version,\n status: \"autosave\",\n createdAt: latestAutosave.createdAt,\n reused: true,\n };\n }\n\n const maxRevisions =\n typeof config.versions === \"object\" && config.versions.max !== undefined\n ? config.versions.max\n : undefined;\n\n const inserted = await db.transaction(async (tx) => {\n const [revisionCount] = (await tx\n .select({ total: count() })\n .from(npRevisions)\n .where(\n sql`${eq(npRevisions.collection, collection)} and ${eq(npRevisions.documentId, documentId)}`,\n )) as Array<{ total: number | string }>;\n const nextVersion = Number(revisionCount?.total ?? 0) + 1;\n const createdAt = new Date();\n\n await tx.insert(npRevisions).values({\n collection,\n documentId,\n version: nextVersion,\n status: \"autosave\",\n snapshot: data,\n changedFields: getChangedFields(data, originalDoc, \"update\"),\n authorId: user.id,\n createdAt,\n });\n\n if (maxRevisions !== undefined && maxRevisions > 0 && nextVersion > maxRevisions) {\n const overflow = nextVersion - maxRevisions;\n const toDelete = (await tx\n .select({ id: npRevisions.id })\n .from(npRevisions)\n .where(\n sql`${eq(npRevisions.collection, collection)} and ${eq(npRevisions.documentId, documentId)}`,\n )\n .orderBy(asc(npRevisions.version))\n .limit(overflow)) as Array<{ id: string }>;\n if (toDelete.length > 0) {\n const ids = toDelete.map((r) => r.id);\n await tx.delete(npRevisions).where(sql`${npRevisions.id} = any(${ids}::uuid[])`);\n }\n }\n\n // Read back the row we just inserted to get its generated id —\n // `tx.insert(...).returning(...)` isn't part of our Drizzle adapter\n // interface, so a follow-up SELECT is the simplest portable path.\n const [row] = (await tx\n .select({ id: npRevisions.id })\n .from(npRevisions)\n .where(\n sql`${eq(npRevisions.collection, collection)} and ${eq(npRevisions.documentId, documentId)} and ${eq(npRevisions.version, nextVersion)}`,\n )\n .limit(1)) as Array<{ id: string }>;\n\n return { id: row?.id ?? \"\", version: nextVersion, createdAt };\n });\n // `registration` reference silences the unused-binding lint; we keep\n // the lookup early so misconfigured collections fail fast.\n void registration;\n\n return { ...inserted, status: \"autosave\", reused: false };\n}\n\nfunction stableJson(value: unknown): string {\n // JSON.stringify with deterministic key ordering is enough for dedup —\n // autosave payloads are user-edited records, not arbitrary structures.\n return JSON.stringify(value, (_key, val) => {\n if (val && typeof val === \"object\" && !Array.isArray(val)) {\n const sorted: Record<string, unknown> = {};\n for (const k of Object.keys(val).sort()) {\n sorted[k] = (val as Record<string, unknown>)[k];\n }\n return sorted;\n }\n return val;\n });\n}\n\nexport async function deleteDocument(\n collection: string,\n docId: string,\n user: NpAuthUser,\n): Promise<void> {\n return deleteDocumentImpl(collection, docId, { kind: \"staff\", user });\n}\n\n/**\n * Member-side delete. Owner-only — the existing row's\n * `member_author_id` must match the caller. Fires\n * `document.deleted` reputation event so adapters can debit the\n * author the same way `comment.deleted` debits commenters.\n *\n * The reputation event is gated on the row's status at delete\n * time: only `published` docs ever earned a `document.created`\n * credit (the create path withholds it for pending rows; promote\n * later backfills the credit). Issuing a `document.deleted`\n * debit for a row that was never credited would drive the\n * member negative for deleting their own not-yet-visible\n * content (#126). The audit row is unconditional — the operator\n * still wants to see \"member deleted X\".\n */\nexport async function deleteMemberDocument(\n collection: string,\n docId: string,\n memberId: string,\n): Promise<void> {\n // Read the current status BEFORE delete so we know whether a\n // `document.created` credit was ever granted. `deleteDocumentImpl`\n // also looks the row up internally, so this is a small redundant\n // SELECT — but the alternative (returning status from the impl)\n // would change a private API for one caller. Fine to repeat.\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n const original = await getDocumentByIdInternal(db, table, collection, docId);\n const wasPublished =\n typeof (original as { status?: unknown } | null)?.status === \"string\" &&\n (original as { status: string }).status === \"published\";\n\n await deleteDocumentImpl(collection, docId, { kind: \"member\", memberId });\n const { applyReputation } = await import(\"../community/reputation.js\");\n const { recordAuditEvent } = await import(\"../community/audit.js\");\n await recordAuditEvent({\n actor: { kind: \"member\", memberId },\n action: \"document.delete\",\n targetType: collection,\n targetId: docId,\n payload: {\n collectionSlug: collection,\n // Capture the status that was in effect at delete time so a\n // mod re-reading the audit log can tell \"they deleted a\n // pending submission\" from \"they retracted a published\n // post.\"\n previousStatus:\n typeof (original as { status?: unknown } | null)?.status === \"string\"\n ? (original as { status: string }).status\n : null,\n },\n });\n if (wasPublished) {\n await applyReputation(memberId, {\n kind: \"document.deleted\",\n collectionSlug: collection,\n documentId: docId,\n memberId,\n });\n }\n}\n\n/**\n * Staff promotion of a member-authored `pending` row to `published`\n * (Phase 9.7d). Closes the loop on the 9.7c moderation gate:\n * - the row's status flips to `published` (visible on the public\n * site immediately)\n * - the deferred `document.created` reputation event fires now,\n * crediting the author for content that was held in review\n * (mirrors how a comment promoted from `pending` would, in a\n * hypothetical comment-promote API — not implemented yet)\n * - audit log records `document.promote` with the staff actor\n * and the original member author in the payload\n *\n * Guards:\n * - 404 if the row doesn't exist\n * - 400 (validation) if the row isn't currently `pending`\n * - 400 (validation) if the row isn't member-authored\n * (`member_author_id` is null) — staff drafts use the standard\n * edit path\n *\n * Idempotence: a second promote on an already-`published` row 400s\n * rather than silently no-op'ing — the audit trail and reputation\n * backfill must run exactly once per row.\n */\nexport async function promoteMemberDocument(\n collection: string,\n docId: string,\n staffUserId: string,\n): Promise<NpSaveResult> {\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n const originalDoc = await getDocumentByIdInternal(db, table, collection, docId);\n if (!originalDoc) {\n throw new NpNotFoundError(collection, docId);\n }\n const status = (originalDoc as { status?: string }).status;\n if (status !== \"pending\") {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"status\",\n message: `Cannot promote: document is ${status ?? \"unknown\"}, expected pending`,\n },\n ]);\n }\n const memberAuthorId = (originalDoc as { memberAuthorId?: string | null }).memberAuthorId ?? null;\n if (!memberAuthorId) {\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"memberAuthorId\",\n message: \"Cannot promote: document is not member-authored\",\n },\n ]);\n }\n\n // Conditional UPDATE on `status = 'pending'`. Two mods racing to\n // promote the same row would each pass the read-side check above\n // and each fire the audit + reputation events; conditioning on\n // status here means the second UPDATE returns zero rows and we\n // surface that as 400 — the row already moved on, no second\n // event-fire. Same pattern protects against an interleaved staff\n // PATCH that ran between our read and our write.\n //\n // Issue #367 — also pin `siteId` in the predicate. The read-side\n // `getDocumentByIdInternal` already enforced the site match, but\n // including siteId in the WHERE means even a stale resolver value\n // between the load and the update can't promote the wrong row.\n const requestSiteId = (await getCurrentSiteId()) ?? NP_DEFAULT_SITE_ID;\n const now = new Date();\n const updated = (await db\n .update(table)\n .set({ status: \"published\", updatedAt: now, updatedBy: staffUserId })\n .where(\n sql`${eq(getTableColumn(table, \"id\"), docId)} and ${eq(getTableColumn(table, \"status\"), \"pending\")} and ${eq(getTableColumn(table, \"siteId\"), requestSiteId)}`,\n )\n .returning()) as Array<Record<string, unknown>>;\n if (updated.length === 0) {\n // Either a concurrent promote already flipped this row, or\n // staff edited it out of `pending` between our read and our\n // write. Surface as a validation error — the caller should\n // re-fetch and retry only if they still want to act.\n throw new NpValidationError(\"Invalid input\", [\n {\n field: \"status\",\n message: \"Cannot promote: row is no longer pending (concurrent change)\",\n },\n ]);\n }\n const persistedDoc = toRecord(updated[0]);\n\n const { applyReputation } = await import(\"../community/reputation.js\");\n const { recordAuditEvent } = await import(\"../community/audit.js\");\n await recordAuditEvent({\n actor: { kind: \"staff\", userId: staffUserId },\n action: \"document.promote\",\n targetType: collection,\n targetId: docId,\n payload: {\n collectionSlug: collection,\n memberAuthorId,\n previousStatus: \"pending\",\n },\n });\n // Backfill the reputation credit that was withheld at create time\n // when status landed as pending. The adapter sees the same event\n // shape as a fresh member create — adapters that key off creation\n // time should consult the audit log, not infer from the event.\n await applyReputation(memberAuthorId, {\n kind: \"document.created\",\n collectionSlug: collection,\n documentId: docId,\n memberId: memberAuthorId,\n });\n\n return { doc: persistedDoc, operation: \"update\" };\n}\n\nasync function deleteDocumentImpl(\n collection: string,\n docId: string,\n actor: SaveActor,\n): Promise<void> {\n const config = getCollectionConfig(collection);\n const registration = getCollectionRegistration(collection);\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n const originalDoc = await getDocumentByIdInternal(db, table, collection, docId);\n\n // Without this guard the call returns success for non-existent ids:\n // hooks fire with `originalDoc = null`, the DELETE matches zero rows,\n // and the route returns 204. Bulk delete then records phantom ids as\n // succeeded. (#59)\n if (!originalDoc) {\n throw new NpNotFoundError(collection, docId);\n }\n\n if (actor.kind === \"staff\") {\n if (config.access?.delete) {\n const allowed = await config.access.delete({ user: actor.user, doc: originalDoc });\n if (!allowed) {\n throw new NpForbiddenError(collection, \"delete\");\n }\n }\n } else {\n // Member delete: opt-in flag plus owner check plus ban check.\n if (!config.community?.memberWrite?.delete) {\n throw new NpForbiddenError(collection, \"delete\");\n }\n const authorId = (originalDoc as { memberAuthorId?: string | null }).memberAuthorId ?? null;\n if (authorId !== actor.memberId) {\n throw new NpForbiddenError(collection, \"delete\");\n }\n const { assertNotBanned } = await import(\"../community/can.js\");\n await assertNotBanned(actor.memberId);\n }\n\n const userForHooks = actorUserOrNull(actor);\n const principal = actorPrincipal(actor);\n await runHooks(config.hooks?.beforeDelete, {\n data: originalDoc,\n user: userForHooks,\n principal,\n collection,\n originalDoc,\n });\n\n await runHook(\"content:beforeDelete\", {\n collection,\n doc: originalDoc,\n user: userForHooks,\n principal,\n });\n\n await db.transaction(async (tx) => {\n await deleteChildTables(tx, registration.childTables, docId);\n await deleteJoinTables(tx, registration.joinTables, docId);\n await tx\n .delete(npMediaRefs as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npMediaRefs as unknown as PgTable, \"collection\"), collection)} and ${eq(getTableColumn(npMediaRefs as unknown as PgTable, \"documentId\"), docId)}`,\n );\n // Phase 9.7m: cascade comments + reactions on the deleted doc.\n // The polymorphic `(target_type, target_id)` shape on\n // `np_comments` / `np_reactions` doesn't have a DB-level FK\n // (it can't — the target table varies per row), so without an\n // explicit cleanup these rows would orphan once the parent\n // doc was gone. Order matters: reactions targeting the comments\n // (`target_type='comment'`) must go before the comments\n // themselves, since after the comment rows are gone we can't\n // discover their ids anymore. Top-level comments and replies\n // both carry `target_id=$docId`, so a single SELECT covers both.\n const commentIdRows = (await tx\n .select({\n id: getTableColumn(npComments as unknown as PgTable, \"id\"),\n })\n .from(npComments as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npComments as unknown as PgTable, \"targetType\"), collection)} and ${eq(getTableColumn(npComments as unknown as PgTable, \"targetId\"), docId)}`,\n )) as Array<{ id: string }>;\n if (commentIdRows.length > 0) {\n const commentIds = commentIdRows.map((row) => row.id);\n await tx\n .delete(npReactions as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npReactions as unknown as PgTable, \"targetType\"), \"comment\")} and ${inArray(getTableColumn(npReactions as unknown as PgTable, \"targetId\"), commentIds)}`,\n );\n // Phase 9.7q: same orphan story for `np_reports` — a member\n // who reported one of these comments would otherwise be left\n // with a row pointing at a non-existent comment id. The\n // existing audit row carries enough context for after-the-\n // fact tracing, so the report itself can go.\n await tx\n .delete(npReports as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npReports as unknown as PgTable, \"targetType\"), \"comment\")} and ${inArray(getTableColumn(npReports as unknown as PgTable, \"targetId\"), commentIds)}`,\n );\n }\n await tx\n .delete(npComments as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npComments as unknown as PgTable, \"targetType\"), collection)} and ${eq(getTableColumn(npComments as unknown as PgTable, \"targetId\"), docId)}`,\n );\n await tx\n .delete(npReactions as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npReactions as unknown as PgTable, \"targetType\"), collection)} and ${eq(getTableColumn(npReactions as unknown as PgTable, \"targetId\"), docId)}`,\n );\n // Doc-level reports (sites that file `target_type=$collection`\n // reports against a post / discussion). The shipped report API\n // today only files against comments + members, but the schema\n // is polymorphic — a future surface could add doc-level reports\n // and this cascade keeps that case correct from day one.\n await tx\n .delete(npReports as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npReports as unknown as PgTable, \"targetType\"), collection)} and ${eq(getTableColumn(npReports as unknown as PgTable, \"targetId\"), docId)}`,\n );\n await tx.delete(table).where(eq(getTableColumn(table, \"id\"), docId));\n });\n\n const postCommitCtx = { collection, documentId: docId, operation: \"delete\" };\n await runPostCommit(\"enqueue:content:afterDelete\", postCommitCtx, () =>\n enqueueJob(\"content:afterDelete\", {\n collection,\n documentId: docId,\n userId: actorUserId(actor),\n }),\n );\n\n await runPostCommit(\"hook:content:afterDelete\", postCommitCtx, () =>\n runHook(\"content:afterDelete\", {\n collection,\n documentId: docId,\n user: userForHooks,\n principal,\n }),\n );\n}\n\nexport async function findDocuments<T extends object = Record<string, unknown>>(\n collection: string,\n options: NpFindOptions<NoInfer<T>>,\n user?: NpAuthUser,\n): Promise<NpFindResult<T>> {\n const config = getCollectionConfig(collection);\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n const page = normalizePage(options.page);\n const limit = normalizeLimit(options.limit);\n const offset = (page - 1) * limit;\n\n await assertReadAccess(config, collection, user ?? null);\n\n // Phase 12.1 — i18n collections honor the top-level `locale`\n // option as an additional `locale = $1` filter. Non-i18n\n // collections silently drop it (the column doesn't exist;\n // forwarding it would 500 on the SQL parse).\n let effectiveWhere: Record<string, unknown> = options.where ?? {};\n if (config.i18n && options.locale) {\n effectiveWhere = { ...effectiveWhere, locale: options.locale };\n }\n\n // Phase 15.2 — multi-site scoping. Reads filter by the\n // resolved site id so cross-site content can't leak. The\n // resolver returns null in non-request contexts (workers,\n // scripts, tests with no resolver wired) — those default\n // to the framework's `default` site. Callers that want\n // cross-site reads (super-admin search, bulk export) can\n // pass `siteId: \"*\"` or override `where.siteId` directly,\n // which the dedicated where-clause builder forwards as-is.\n if (effectiveWhere.siteId === undefined) {\n const resolved = await getCurrentSiteId();\n effectiveWhere = {\n ...effectiveWhere,\n siteId: resolved ?? NP_DEFAULT_SITE_ID,\n };\n } else if (effectiveWhere.siteId === \"*\") {\n // Sentinel: drop the filter entirely (admin-side\n // cross-site queries).\n const { siteId: _siteId, ...rest } = effectiveWhere;\n void _siteId;\n effectiveWhere = rest;\n }\n\n // Phase 21.17 — per-doc visibility filter. Anonymous reads\n // (no `user` argument, e.g. site-side `findDocuments` from\n // the catch-all renderer or the sitemap) auto-restrict to\n // `visibility = \"public\"` so a private row never leaks to\n // a crawler / unauthenticated visitor. Authenticated\n // principals (any signed-in member or staff) see both\n // public and private — matching WordPress's \"logged-in\n // users see private posts\" semantics. Callers that want\n // explicit control (admin queries, bulk export) pass\n // `where.visibility` and bypass the gate.\n if (effectiveWhere.visibility === undefined && !user) {\n effectiveWhere = { ...effectiveWhere, visibility: \"public\" };\n } else if (effectiveWhere.visibility === \"*\") {\n const { visibility: _vis, ...rest } = effectiveWhere;\n void _vis;\n effectiveWhere = rest;\n }\n\n const effectiveOptions: NpFindOptions = {\n ...options,\n where: effectiveWhere,\n };\n const conditions = buildQueryConditions(table, effectiveOptions);\n const whereClause = combineConditions(conditions);\n\n const docs = await executeFindQuery(db, table, options, whereClause, limit, offset);\n const totalResult = (await (whereClause\n ? db.select({ total: count() }).from(table).where(whereClause)\n : db.select({ total: count() }).from(table).limit(1))) as Array<{ total: number | string }>;\n const totalDocs = Number(totalResult[0]?.total ?? 0);\n const totalPages = totalDocs === 0 ? 0 : Math.ceil(totalDocs / limit);\n\n return {\n // Runtime rows are `Record<string, unknown>`; the generic `T`\n // is a structural promise — generated wrapper functions narrow\n // it to the per-collection document shape. We don't validate\n // at runtime (Drizzle has already shaped the row to the table\n // schema, which the generator derived from the same field\n // configs T was generated from).\n docs: docs as unknown as T[],\n totalDocs,\n totalPages,\n page,\n limit,\n hasNextPage: page < totalPages,\n hasPrevPage: page > 1 && totalDocs > 0,\n };\n}\n\nexport async function getDocumentById<T extends object = Record<string, unknown>>(\n collection: string,\n id: string,\n user?: NpAuthUser,\n): Promise<T | null> {\n const config = getCollectionConfig(collection);\n const table = getCollectionTable(collection) as PgTable;\n const db = getDb() as unknown as DrizzleDatabaseLike;\n const doc = await getDocumentByIdOptional(db, table, id);\n\n if (!doc) {\n return null;\n }\n\n // Issue #367 — even read-by-id has to honor the tenant boundary\n // before access.read fires. A site A caller naming a site B doc\n // must not get a site B read decision back. Throw `Forbidden\n // cross-site` to match the existing pattern callers (e.g.\n // createComment, the sister-PR community fixes #362–#364) already\n // assert against.\n const requestSiteId = (await getCurrentSiteId()) ?? NP_DEFAULT_SITE_ID;\n const docSiteId =\n typeof doc.siteId === \"string\" && doc.siteId.length > 0\n ? doc.siteId\n : NP_DEFAULT_SITE_ID;\n if (docSiteId !== requestSiteId) {\n throw new NpForbiddenError(collection, \"cross-site\");\n }\n\n if (config.access?.read) {\n const allowed = await config.access.read({ user: user ?? null, doc });\n if (!allowed) {\n throw new NpForbiddenError(collection, \"read\");\n }\n }\n\n return doc as unknown as T;\n}\n\nasync function assertWriteAccess(\n config: NpCollectionConfig,\n collection: string,\n operation: NpSaveResult[\"operation\"],\n user: NpAuthUser,\n data: Record<string, unknown>,\n originalDoc: Record<string, unknown> | null,\n): Promise<void> {\n const access = operation === \"create\" ? config.access?.create : config.access?.update;\n\n if (!access) {\n return;\n }\n\n const allowed = await access({ user, doc: originalDoc ?? undefined, data });\n\n if (!allowed) {\n throw new NpForbiddenError(collection, operation);\n }\n}\n\nasync function assertReadAccess(\n config: NpCollectionConfig,\n collection: string,\n user: NpAuthUser | null,\n): Promise<void> {\n if (!config.access?.read) {\n return;\n }\n\n const allowed = await config.access.read({ user });\n\n if (!allowed) {\n throw new NpForbiddenError(collection, \"read\");\n }\n}\n\nasync function runHooks(\n hooks: NpCollectionHook[] | undefined,\n args: Parameters<NpCollectionHook>[0],\n): Promise<Record<string, unknown>> {\n let nextData = args.data;\n\n for (const hook of hooks ?? []) {\n nextData = await hook({\n ...args,\n data: nextData,\n });\n }\n\n return nextData;\n}\n\nasync function createMainDocument(\n tx: DrizzleTransactionLike,\n table: PgTable,\n mainData: Record<string, unknown>,\n searchVectorSql: SQL,\n config: NpCollectionConfig,\n user: NpAuthUser | null,\n now: Date,\n): Promise<Record<string, unknown>> {\n // Member writes (`user === null`) leave `createdBy` / `updatedBy`\n // unset so the FK to `np_users` stays null. The audit log captures\n // the actual member; readers that need authorship for member-\n // authored docs should join through the dedicated `member_author_id`\n // column (codegen'd onto every collection that opts into\n // `community.memberWrite.create`).\n const values: Record<string, unknown> = {\n id: randomUUID(),\n status: \"published\",\n ...mainData,\n createdBy: user?.id ?? null,\n updatedBy: user?.id ?? null,\n // Phase 10.7 — composed setweight() tsvector so titles\n // outrank body matches at query time. The 11.x\n // to_tsvector wrap (so colon-containing content doesn't\n // crash the cast) is preserved inside each setweight call\n // by buildWeightedSearchVectorSql.\n searchVector: searchVectorSql,\n };\n\n if (config.timestamps !== false) {\n values.createdAt = now;\n values.updatedAt = now;\n }\n\n const [created] = await tx.insert(table).values(values).returning();\n\n return toRecord(created);\n}\n\nasync function updateMainDocument(\n tx: DrizzleTransactionLike,\n table: PgTable,\n collection: string,\n docId: string | null,\n mainData: Record<string, unknown>,\n searchVectorSql: SQL,\n config: NpCollectionConfig,\n user: NpAuthUser | null,\n now: Date,\n): Promise<Record<string, unknown>> {\n if (!docId) {\n throw new NpNotFoundError(collection, \"unknown\");\n }\n\n const values: Record<string, unknown> = {\n ...mainData,\n updatedBy: user?.id ?? null,\n // Phase 10.7 — see createMainDocument: weighted setweight()\n // tsvector preserves the 11.x to_tsvector safety AND adds\n // title boost.\n searchVector: searchVectorSql,\n };\n\n if (config.timestamps !== false) {\n values.updatedAt = now;\n }\n\n const [updated] = await tx\n .update(table)\n .set(values)\n .where(eq(getTableColumn(table, \"id\"), docId))\n .returning();\n\n if (!updated) {\n throw new NpNotFoundError(collection, docId);\n }\n\n return toRecord(updated);\n}\n\nasync function syncChildTables(\n tx: DrizzleTransactionLike,\n childTables: Record<string, unknown> | undefined,\n childRows: Record<string, Record<string, unknown>[]>,\n documentId: string,\n): Promise<void> {\n for (const [fieldPath, rows] of Object.entries(childRows)) {\n const table = resolveRelatedTable(childTables, fieldPath);\n\n if (!table) {\n continue;\n }\n\n const pgTable = table as PgTable;\n const parentColumnName = findParentColumnName(pgTable, [\"parentId\"]);\n await tx.delete(pgTable).where(eq(getTableColumn(pgTable, parentColumnName), documentId));\n\n if (rows.length === 0) {\n continue;\n }\n\n const values = rows.map((row, index) => ({\n id: randomUUID(),\n ...row,\n [parentColumnName]: documentId,\n order: index,\n }));\n\n await tx.insert(pgTable).values(values);\n }\n}\n\nasync function syncJoinTables(\n tx: DrizzleTransactionLike,\n joinTables: Record<string, unknown> | undefined,\n joinRows: Record<string, string[]>,\n documentId: string,\n): Promise<void> {\n for (const [fieldPath, ids] of Object.entries(joinRows)) {\n const table = resolveRelatedTable(joinTables, fieldPath);\n\n if (!table) {\n continue;\n }\n\n const pgTable = table as PgTable;\n const parentColumnName = findParentColumnName(pgTable, [\"parentId\"]);\n await tx.delete(pgTable).where(eq(getTableColumn(pgTable, parentColumnName), documentId));\n\n if (ids.length === 0) {\n continue;\n }\n\n const values = ids.map((targetId, index) => ({\n id: randomUUID(),\n [parentColumnName]: documentId,\n targetId,\n order: index,\n }));\n\n await tx.insert(pgTable).values(values);\n }\n}\n\nasync function deleteChildTables(\n tx: DrizzleTransactionLike,\n childTables: Record<string, unknown> | undefined,\n documentId: string,\n): Promise<void> {\n for (const table of Object.values(childTables ?? {})) {\n const pgTable = table as PgTable;\n const parentColumnName = findParentColumnName(pgTable, [\"parentId\"]);\n await tx.delete(pgTable).where(eq(getTableColumn(pgTable, parentColumnName), documentId));\n }\n}\n\nasync function deleteJoinTables(\n tx: DrizzleTransactionLike,\n joinTables: Record<string, unknown> | undefined,\n documentId: string,\n): Promise<void> {\n for (const table of Object.values(joinTables ?? {})) {\n const pgTable = table as PgTable;\n const parentColumnName = findParentColumnName(pgTable, [\"parentId\"]);\n await tx.delete(pgTable).where(eq(getTableColumn(pgTable, parentColumnName), documentId));\n }\n}\n\nasync function insertRevision(\n tx: DrizzleTransactionLike,\n collection: string,\n documentId: string,\n operation: NpSaveResult[\"operation\"],\n data: Record<string, unknown>,\n originalDoc: Record<string, unknown> | null,\n user: NpAuthUser | null,\n status: string,\n maxRevisions?: number,\n): Promise<void> {\n const revisionConditions = sql`${eq(npRevisions.collection, collection)} and ${eq(npRevisions.documentId, documentId)}`;\n const [revisionCount] = (await tx\n .select({ total: count() })\n .from(npRevisions)\n .where(revisionConditions)) as Array<{ total: number | string }>;\n\n await tx.insert(npRevisions).values({\n collection,\n documentId,\n version: Number(revisionCount?.total ?? 0) + 1,\n status,\n snapshot: data,\n changedFields: getChangedFields(data, originalDoc, operation),\n // `authorId` references np_users; member-authored revisions\n // store null and the audit log carries the actual member id.\n authorId: user?.id ?? null,\n createdAt: new Date(),\n });\n\n // Enforce versions.max: drop the oldest revisions so this doc never\n // accumulates more than `maxRevisions` rows. Runs in the same tx as the\n // insert so the row count is stable against races.\n if (maxRevisions !== undefined && maxRevisions > 0) {\n const currentCount = Number(revisionCount?.total ?? 0) + 1;\n const overflow = currentCount - maxRevisions;\n if (overflow > 0) {\n // Select the oldest `overflow` revision ids and delete them. Postgres\n // doesn't support DELETE with LIMIT directly but `id IN (subquery)`\n // works fine.\n const toDelete = (await tx\n .select({ id: npRevisions.id })\n .from(npRevisions)\n .where(revisionConditions)\n .orderBy(asc(npRevisions.version))\n .limit(overflow)) as Array<{ id: string }>;\n\n if (toDelete.length > 0) {\n const ids = toDelete.map((r) => r.id);\n await tx.delete(npRevisions).where(sql`${npRevisions.id} = any(${ids}::uuid[])`);\n }\n }\n }\n}\n\nfunction buildQueryConditions(table: PgTable, options: NpFindOptions): QueryCondition[] {\n const conditions: QueryCondition[] = [];\n\n if (options.where) {\n for (const [field, value] of Object.entries(options.where)) {\n if (value === undefined) {\n continue;\n }\n\n // Array values → `IN (...)`. Empty array means \"match\n // nothing\" — emit a tautologically-false condition so the\n // overall query short-circuits to zero rows. (Without this\n // guard, Drizzle's `inArray(col, [])` produces `col IN ()`,\n // which Postgres rejects with a syntax error.)\n if (Array.isArray(value)) {\n if (value.length === 0) {\n conditions.push(sql`false`);\n } else {\n conditions.push(inArray(getTableColumn(table, field), value));\n }\n continue;\n }\n\n conditions.push(eq(getTableColumn(table, field), value));\n }\n }\n\n if (options.search) {\n conditions.push(\n sql`${getTableColumn(table, \"searchVector\")} @@ plainto_tsquery('english', ${options.search})`,\n );\n }\n\n return conditions;\n}\n\nasync function executeFindQuery(\n db: DrizzleDatabaseLike,\n table: PgTable,\n options: NpFindOptions,\n whereClause: ReturnType<typeof sql> | undefined,\n limit: number,\n offset: number,\n): Promise<Record<string, unknown>[]> {\n if (options.search) {\n const query = whereClause\n ? db\n .select()\n .from(table)\n .where(whereClause)\n .orderBy(\n sql`ts_rank(${getTableColumn(table, \"searchVector\")}, plainto_tsquery('english', ${options.search})) DESC`,\n )\n .limit(limit)\n .offset(offset)\n : db\n .select()\n .from(table)\n .orderBy(\n sql`ts_rank(${getTableColumn(table, \"searchVector\")}, plainto_tsquery('english', ${options.search})) DESC`,\n )\n .limit(limit)\n .offset(offset);\n\n return (await query) as Record<string, unknown>[];\n }\n\n const orderClause = getSortOrderClause(table, options.sort);\n\n if (whereClause && orderClause) {\n return (await db\n .select()\n .from(table)\n .where(whereClause)\n .orderBy(orderClause)\n .limit(limit)\n .offset(offset)) as Record<string, unknown>[];\n }\n\n if (whereClause) {\n return (await db.select().from(table).where(whereClause).limit(limit).offset(offset)) as Record<\n string,\n unknown\n >[];\n }\n\n if (orderClause) {\n return (await db\n .select()\n .from(table)\n .orderBy(orderClause)\n .limit(limit)\n .offset(offset)) as Record<string, unknown>[];\n }\n\n return (await db.select().from(table).limit(limit).offset(offset)) as Record<string, unknown>[];\n}\n\nfunction getSortOrderClause(\n table: PgTable,\n sortValue: string | undefined,\n): ReturnType<typeof sql> | undefined {\n const sort = sortValue?.trim();\n\n if (!sort) {\n return undefined;\n }\n\n const isDescending = sort.startsWith(\"-\");\n const field = isDescending ? sort.slice(1) : sort;\n const column = getTableColumn(table, field);\n\n return isDescending ? desc(column) : asc(column);\n}\n\n/**\n * Issue #367 — by-id loader for write paths and admin reads.\n *\n * `findDocuments` (the list path) has been site-scoped since Phase\n * 18, but every by-id load was id-only. A staff user with a foreign\n * doc id could reach `getDocumentById`, `saveDocument`,\n * `deleteDocument`, `promoteMemberDocument`, or\n * `createTranslation` outside their tenant. By default this loader\n * now compares the loaded row's `siteId` to the request's resolved\n * site and throws `NpForbiddenError(collection, \"cross-site\")` on\n * divergence.\n *\n * Cross-site is opt-in via `{ allowCrossSite: true }`. The legitimate\n * users today are background jobs / scripts that run without a\n * request site context (the wp-importer wraps its own\n * `withCurrentSite`, so it stays on the default path).\n */\nasync function getDocumentByIdInternal(\n db: DrizzleDatabaseLike,\n table: PgTable,\n collection: string,\n id: string,\n options?: { allowCrossSite?: boolean },\n): Promise<Record<string, unknown>> {\n const doc = await getDocumentByIdOptional(db, table, id);\n\n if (!doc) {\n throw new NpNotFoundError(collection, id);\n }\n\n if (!options?.allowCrossSite) {\n const requestSiteId = (await getCurrentSiteId()) ?? NP_DEFAULT_SITE_ID;\n const docSiteId =\n typeof doc.siteId === \"string\" && doc.siteId.length > 0\n ? doc.siteId\n : NP_DEFAULT_SITE_ID;\n if (docSiteId !== requestSiteId) {\n throw new NpForbiddenError(collection, \"cross-site\");\n }\n }\n\n return doc;\n}\n\nasync function getDocumentByIdOptional(\n db: DrizzleDatabaseLike,\n table: PgTable,\n id: string,\n): Promise<Record<string, unknown> | null> {\n const [doc] = await db\n .select()\n .from(table)\n .where(eq(getTableColumn(table, \"id\"), id))\n .limit(1);\n return doc ? toRecord(doc) : null;\n}\n\nfunction prepareDocumentData(\n fields: NpFieldConfig[],\n data: Record<string, unknown>,\n): PreparedDocumentData {\n const prepared: PreparedDocumentData = {\n mainData: {},\n childRows: {},\n joinRows: {},\n };\n\n collectPreparedDocumentData(fields, data, prepared, []);\n\n if (typeof data.slug === \"string\") {\n prepared.mainData.slug = data.slug;\n }\n // i18n columns aren't fields and aren't auto-emitted by\n // collectPreparedDocumentData. Let them through so the\n // caller (saveDocumentImpl) can persist the resolved locale\n // / translationGroupId.\n if (typeof data.locale === \"string\") {\n prepared.mainData.locale = data.locale;\n }\n if (typeof data.translationGroupId === \"string\") {\n prepared.mainData.translationGroupId = data.translationGroupId;\n }\n // Phase 15.2 — siteId is also non-field but framework-managed.\n if (typeof data.siteId === \"string\") {\n prepared.mainData.siteId = data.siteId;\n }\n // Phase 21.17 — visibility is a non-field framework-managed\n // column (codegen'd onto every collection by `getBaseColumns`).\n // Let it through so `createMainDocument` / `updateMainDocument`\n // can persist it; the Zod schema already validated the value\n // is `\"public\" | \"private\"`.\n if (typeof data.visibility === \"string\") {\n prepared.mainData.visibility = data.visibility;\n }\n\n return prepared;\n}\n\nfunction collectPreparedDocumentData(\n fields: NpFieldConfig[],\n data: Record<string, unknown>,\n prepared: PreparedDocumentData,\n prefix: string[],\n): void {\n for (const field of fields) {\n if (field.type === \"row\" || field.type === \"collapsible\") {\n collectPreparedDocumentData(field.fields, data, prepared, prefix);\n continue;\n }\n\n if (field.type === \"group\") {\n const groupValue = toOptionalRecord(data[field.name]);\n if (groupValue) {\n collectPreparedDocumentData(field.fields, groupValue, prepared, [...prefix, field.name]);\n }\n continue;\n }\n\n const fieldPath = [...prefix, field.name];\n const fieldKey = fieldPath.join(\".\");\n const value = data[field.name];\n\n if (field.type === \"array\") {\n prepared.childRows[fieldKey] = normalizeChildRows(field.fields, value);\n continue;\n }\n\n if (field.type === \"relationship\" && field.hasMany) {\n prepared.joinRows[fieldKey] = normalizeJoinIds(value);\n continue;\n }\n\n prepared.mainData[getFlattenedFieldName(prefix, field.name)] = value ?? null;\n }\n}\n\nfunction normalizeChildRows(fields: NpFieldConfig[], value: unknown): Record<string, unknown>[] {\n if (!Array.isArray(value)) {\n return [];\n }\n\n return value.map((item) => {\n const row = toOptionalRecord(item) ?? {};\n const prepared: PreparedDocumentData = {\n mainData: {},\n childRows: {},\n joinRows: {},\n };\n\n collectPreparedDocumentData(fields, row, prepared, []);\n return prepared.mainData;\n });\n}\n\nfunction normalizeJoinIds(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nasync function syncMediaRefsForDocument(\n tx: DrizzleTransactionLike,\n collection: string,\n documentId: string,\n fields: NpFieldConfig[],\n data: Record<string, unknown>,\n): Promise<void> {\n const refs = extractMediaIdsFromFields(fields, data, []);\n\n if (refs.length === 0) {\n await tx\n .delete(npMediaRefs as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npMediaRefs as unknown as PgTable, \"collection\"), collection)} and ${eq(getTableColumn(npMediaRefs as unknown as PgTable, \"documentId\"), documentId)}`,\n );\n return;\n }\n\n await tx\n .delete(npMediaRefs as unknown as PgTable)\n .where(\n sql`${eq(getTableColumn(npMediaRefs as unknown as PgTable, \"collection\"), collection)} and ${eq(getTableColumn(npMediaRefs as unknown as PgTable, \"documentId\"), documentId)}`,\n );\n\n const values = refs.map((ref) => ({\n id: randomUUID(),\n mediaId: ref.mediaId,\n collection,\n documentId,\n field: ref.field,\n }));\n\n await tx.insert(npMediaRefs as unknown as PgTable).values(values);\n}\n\nfunction extractMediaIdsFromFields(\n fields: NpFieldConfig[],\n data: Record<string, unknown>,\n prefix: string[],\n): Array<{ mediaId: string; field: string }> {\n const refs: Array<{ mediaId: string; field: string }> = [];\n\n for (const field of fields) {\n if (field.type === \"row\" || field.type === \"collapsible\") {\n refs.push(...extractMediaIdsFromFields(field.fields, data, prefix));\n continue;\n }\n\n if (field.type === \"group\") {\n const groupData = toOptionalRecord(data[field.name]);\n if (groupData) {\n refs.push(...extractMediaIdsFromFields(field.fields, groupData, [...prefix, field.name]));\n }\n continue;\n }\n\n const fieldPath = [...prefix, field.name].join(\".\");\n\n if (field.type === \"upload\") {\n const mediaId = data[field.name];\n if (typeof mediaId === \"string\" && mediaId.length > 0) {\n refs.push({ mediaId, field: fieldPath });\n }\n continue;\n }\n\n if (field.type === \"richText\") {\n const richTextValue = data[field.name];\n if (richTextValue && typeof richTextValue === \"object\") {\n refs.push(...extractMediaIdsFromLexicalJson(richTextValue, fieldPath));\n }\n continue;\n }\n\n if (field.type === \"array\") {\n const arrayValue = data[field.name];\n if (Array.isArray(arrayValue)) {\n for (const item of arrayValue) {\n const itemRecord = toOptionalRecord(item);\n if (itemRecord) {\n refs.push(\n ...extractMediaIdsFromFields(field.fields, itemRecord, [...prefix, field.name]),\n );\n }\n }\n }\n continue;\n }\n\n if (field.type === \"blocks\") {\n const blocksValue = data[field.name];\n if (Array.isArray(blocksValue)) {\n for (const block of blocksValue) {\n const blockRecord = toOptionalRecord(block);\n if (blockRecord) {\n extractBlockMediaIds(blockRecord, fieldPath, refs);\n }\n }\n }\n continue;\n }\n }\n\n return refs;\n}\n\nfunction extractMediaIdsFromLexicalJson(\n node: unknown,\n fieldPath: string,\n): Array<{ mediaId: string; field: string }> {\n const refs: Array<{ mediaId: string; field: string }> = [];\n\n if (!node || typeof node !== \"object\") {\n return refs;\n }\n\n const record = node as Record<string, unknown>;\n\n if (record.type === \"image\" || record.type === \"upload\") {\n const mediaId = record.mediaId ?? record.value;\n if (typeof mediaId === \"string\" && mediaId.length > 0) {\n refs.push({ mediaId, field: fieldPath });\n }\n }\n\n const children = record.children ?? toOptionalRecord(record.root)?.children;\n if (Array.isArray(children)) {\n for (const child of children) {\n refs.push(...extractMediaIdsFromLexicalJson(child, fieldPath));\n }\n }\n\n return refs;\n}\n\nfunction extractBlockMediaIds(\n block: Record<string, unknown>,\n fieldPath: string,\n refs: Array<{ mediaId: string; field: string }>,\n): void {\n for (const [key, value] of Object.entries(block)) {\n if (key === \"blockType\" || key === \"id\") {\n continue;\n }\n\n if (typeof value === \"string\" && isUuid(value)) {\n refs.push({ mediaId: value, field: `${fieldPath}.${key}` });\n }\n }\n}\n\nfunction isUuid(value: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);\n}\n\nfunction getChangedFields(\n data: Record<string, unknown>,\n originalDoc: Record<string, unknown> | null,\n operation: NpSaveResult[\"operation\"],\n): string[] {\n if (operation === \"create\" || !originalDoc) {\n return Object.keys(data);\n }\n\n return Object.keys(data).filter((field) => !Object.is(data[field], originalDoc[field]));\n}\n\nfunction combineConditions(conditions: QueryCondition[]): ReturnType<typeof sql> | undefined {\n if (conditions.length === 0) {\n return undefined;\n }\n\n return sql`${sql.join(conditions, sql` and `)}`;\n}\n\nfunction resolveRelatedTable(\n tables: Record<string, unknown> | undefined,\n fieldPath: string,\n): unknown {\n return tables?.[fieldPath] ?? tables?.[fieldPath.split(\".\").at(-1) ?? fieldPath];\n}\n\nfunction findParentColumnName(table: PgTable, preferred: string[]): string {\n const keys = Object.keys(table as unknown as Record<string, unknown>);\n\n for (const key of preferred) {\n if (keys.includes(key)) {\n return key;\n }\n }\n\n const derived = keys.find(\n (key) => key !== \"id\" && key !== \"targetId\" && key !== \"order\" && key.endsWith(\"Id\"),\n );\n\n if (!derived) {\n throw new Error(\"Unable to resolve parent column for related table.\");\n }\n\n return derived;\n}\n\nfunction getTableColumn(table: PgTable, key: string): AnyPgColumn {\n const column = (table as unknown as Record<string, unknown>)[key];\n\n if (!column) {\n throw new Error(`Column '${key}' not found on table.`);\n }\n\n return column as AnyPgColumn;\n}\n\nfunction getRecordId(record: Record<string, unknown>): string {\n const id = record.id;\n\n if (typeof id !== \"string\") {\n throw new Error(\"Expected saved document to include a string id.\");\n }\n\n return id;\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(\"Expected object record.\");\n }\n\n return value as Record<string, unknown>;\n}\n\nfunction toOptionalRecord(value: unknown): Record<string, unknown> | null {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return null;\n }\n\n return value as Record<string, unknown>;\n}\n\nfunction normalizePage(page?: number): number {\n if (!page || page < 1) {\n return 1;\n }\n\n return Math.floor(page);\n}\n\nfunction normalizeLimit(limit?: number): number {\n if (!limit || limit < 1) {\n return 10;\n }\n\n return Math.floor(limit);\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) =>\n char.toLowerCase(),\n );\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).map(capitalize).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\nfunction capitalize(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n","import { NpValidationError } from \"../errors.js\";\nimport type { NpCollectionConfig } from \"../config/types.js\";\n\n/**\n * Stable URL-slug derivation. Lowercases, strips Latin diacritics\n * (Cr\\u00e8me \\u2192 creme), keeps any Unicode letter or number including\n * Korean/Japanese/Chinese/Cyrillic/Greek/etc., replaces runs of\n * separators (anything that isn't a letter or number) with a\n * single hyphen, trims edge hyphens, and caps at 96 chars so the\n * result fits standard DB slug columns without needing a larger\n * index.\n *\n * The two-step `NFKD \\u2192 strip combining marks \\u2192 NFC` dance does\n * the diacritic strip without permanently decomposing scripts\n * that NFKD breaks apart at the syllable level (most notably\n * Hangul, which NFKD turns into jamo and NFC then reassembles).\n */\nexport function slugify(value: string): string {\n return value\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .normalize(\"NFC\")\n .replace(/[^\\p{L}\\p{N}]+/gu, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 96);\n}\n\n/**\n * Ensures `data.slug` is set for collections that declare `slugField`.\n * - If the caller supplied `slug`, it gets normalized through slugify.\n * - If updating an existing doc, the previous slug is preserved when the\n * caller didn't provide one (so titles can change without breaking URLs).\n * - Otherwise the slug is derived from the configured `useField` (default\n * \"title\"). Throws `NpValidationError` if no candidate source exists.\n *\n * Mutates `data` in place.\n */\nexport function applySlugField(\n config: NpCollectionConfig,\n data: Record<string, unknown>,\n originalDoc: Record<string, unknown> | null,\n): void {\n if (!config.slugField) return;\n\n const existingSlug = typeof data.slug === \"string\" ? data.slug.trim() : \"\";\n\n if (existingSlug.length > 0) {\n data.slug = slugify(existingSlug) || existingSlug;\n return;\n }\n\n if (originalDoc && typeof originalDoc.slug === \"string\" && originalDoc.slug.length > 0) {\n data.slug = originalDoc.slug;\n return;\n }\n\n const useField =\n typeof config.slugField === \"object\" && config.slugField.useField\n ? config.slugField.useField\n : \"title\";\n const source = data[useField];\n const candidate = typeof source === \"string\" ? slugify(source) : \"\";\n\n if (candidate.length === 0) {\n throw new NpValidationError(\"Slug generation failed\", [\n {\n field: \"slug\",\n message: `Cannot derive a slug — provide \"slug\" or a non-empty \"${useField}\".`,\n },\n ]);\n }\n\n data.slug = candidate;\n}\n","import { z } from \"zod\";\n\nimport { type NpCollectionConfig, type NpFieldConfig } from \"../config/types.js\";\n\nexport function buildZodSchema(\n fields: NpFieldConfig[],\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).shape);\n continue;\n }\n\n if (field.type === \"group\") {\n const schema = buildZodSchema(field.fields);\n shape[field.name] = applyOptionality(schema, field.required);\n continue;\n }\n\n shape[field.name] = applyOptionality(buildFieldSchema(field), field.required);\n }\n\n return z.object(shape);\n}\n\nexport function getCollectionZodSchema(config: NpCollectionConfig): z.ZodSchema {\n const base = buildZodSchema(config.fields).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","import { sql, type SQL } from \"drizzle-orm\";\n\nimport { type NpCollectionConfig, type NpRichTextContent } from \"../config/types.js\";\n\n/**\n * Plain-text concatenation of every searchable field. Used by\n * the moderation pipeline (spam/profanity adapters need the\n * full text, weights don't matter there).\n */\nexport function buildSearchVector(\n config: NpCollectionConfig,\n data: Record<string, unknown>,\n): string {\n const parts: string[] = [];\n\n for (const field of config.fields) {\n if (field.type === \"text\" || field.type === \"textarea\") {\n const value = data[field.name];\n if (typeof value === \"string\") parts.push(value);\n }\n if (field.type === \"richText\") {\n const value = data[field.name];\n if (value) parts.push(extractPlainText(value as NpRichTextContent));\n }\n }\n\n return parts.join(\" \");\n}\n\n/**\n * Phase 10.7 — split searchable fields into Postgres tsvector\n * weight buckets so title-like matches outrank body matches at\n * query time.\n *\n * Convention (no per-field opt-in to keep collections\n * declarations terse):\n * - field.name === \"title\" or \"name\" → weight A\n * - other text / textarea / email → weight B\n * - richText → weight C\n *\n * Sites that want different weights can name their primary\n * field \"title\" and the framework picks the right bucket\n * automatically. (A future revision can add `field.search.weight`\n * for explicit control.)\n *\n * Postgres ts_rank() applies the default weight scale\n * { D: 0.1, C: 0.2, B: 0.4, A: 1.0 }, so an A-weighted match\n * scores ~10× a D-weighted one — meaningful boost for titles.\n */\nexport interface NpSearchVectorParts {\n /** Title-like fields. Highest rank weight. */\n a: string;\n /** Body fields (text/textarea/email). */\n b: string;\n /** Rich-text body. */\n c: string;\n /** Reserved for future categorization (tags, slugs, etc.). */\n d: string;\n}\n\nconst TITLE_LIKE_NAMES = new Set([\"title\", \"name\"]);\n\nexport function buildSearchVectorParts(\n config: NpCollectionConfig,\n data: Record<string, unknown>,\n): NpSearchVectorParts {\n const parts: NpSearchVectorParts = { a: \"\", b: \"\", c: \"\", d: \"\" };\n const append = (bucket: keyof NpSearchVectorParts, value: string): void => {\n parts[bucket] = parts[bucket] ? `${parts[bucket]} ${value}` : value;\n };\n\n for (const field of config.fields) {\n if (\n field.type === \"text\" ||\n field.type === \"textarea\" ||\n field.type === \"email\"\n ) {\n const value = data[field.name];\n if (typeof value !== \"string\" || value.length === 0) continue;\n if (TITLE_LIKE_NAMES.has(field.name)) {\n append(\"a\", value);\n } else {\n append(\"b\", value);\n }\n }\n if (field.type === \"richText\") {\n const value = data[field.name];\n if (!value) continue;\n const text = extractPlainText(value as NpRichTextContent);\n if (text.length > 0) append(\"c\", text);\n }\n }\n\n return parts;\n}\n\n/**\n * Build the weighted-tsvector SQL fragment that the pipeline\n * binds to `searchVector` on insert/update. Each non-empty\n * bucket becomes `setweight(to_tsvector('english', $bucket), '<W>')`;\n * the buckets are concatenated with `||`. Empty buckets are\n * skipped so the resulting expression is always non-trivial.\n *\n * If every bucket is empty (collection has no text fields, or\n * every text field is null on this row), returns an empty\n * tsvector cast — Postgres accepts this as a valid empty\n * vector value.\n */\nexport function buildWeightedSearchVectorSql(\n config: NpCollectionConfig,\n data: Record<string, unknown>,\n): SQL {\n const parts = buildSearchVectorParts(config, data);\n const chunks: SQL[] = [];\n if (parts.a)\n chunks.push(sql`setweight(to_tsvector('english', ${parts.a}), 'A')`);\n if (parts.b)\n chunks.push(sql`setweight(to_tsvector('english', ${parts.b}), 'B')`);\n if (parts.c)\n chunks.push(sql`setweight(to_tsvector('english', ${parts.c}), 'C')`);\n if (parts.d)\n chunks.push(sql`setweight(to_tsvector('english', ${parts.d}), 'D')`);\n if (chunks.length === 0) {\n return sql`''::tsvector`;\n }\n if (chunks.length === 1) {\n return chunks[0];\n }\n // Drizzle's sql.join uses the second arg as the separator\n // SQL fragment. `||` is Postgres tsvector concatenation.\n return sql.join(chunks, sql` || `);\n}\n\nfunction extractPlainText(content: NpRichTextContent): string {\n if (!content || typeof content !== \"object\") return \"\";\n\n const root = content.root as { children?: unknown[] } | undefined;\n if (!root?.children) return \"\";\n\n const parts: string[] = [];\n walkNodes(root.children, parts);\n return parts.join(\" \");\n}\n\nfunction walkNodes(nodes: unknown[], parts: string[]): void {\n for (const node of nodes) {\n if (!node || typeof node !== \"object\") continue;\n const n = node as Record<string, unknown>;\n\n if (typeof n.text === \"string\") {\n parts.push(n.text);\n }\n\n if (Array.isArray(n.children)) {\n walkNodes(n.children, parts);\n }\n }\n}\n","import { eq } from \"drizzle-orm\";\n\nimport { npPlugins } from \"../db/schema/system.js\";\nimport { getDb } from \"../db/runtime.js\";\n\n/**\n * Per-request enabled gate for already-loaded plugins.\n *\n * The plugin host registers every hook and route at boot regardless of the\n * `np_plugins.enabled` row state, so toggling a plugin from the admin UI\n * historically required a server restart to take effect. This module fronts\n * the registry with a short-lived cache of the DB flag so dispatch sites\n * (`runHook`, the catch-all route handler, `dispatchPluginAction`) can skip\n * disabled plugins immediately, without paying a DB round-trip per call.\n *\n * Cache semantics:\n * - Default-enabled: a missing row OR a DB read failure yields `true`. This\n * matches `syncPluginRegistrations` (which inserts new rows with\n * `enabled=true`) and avoids a hard failure mode where a flaky DB silently\n * disables every plugin.\n * - 5 second TTL by default — short enough that a toggle feels immediate,\n * long enough to absorb a burst of hook calls within one request.\n * - `invalidatePluginEnabled(id)` is called from `updatePluginState` so the\n * next dispatch after a toggle re-reads the DB instead of waiting out the\n * TTL.\n * - `setPluginEnabledForTest()` / `resetEnabledGate()` let unit tests\n * bypass the DB entirely.\n */\n\nconst DEFAULT_TTL_MS = 5_000;\n\ninterface CacheEntry {\n enabled: boolean;\n expiresAt: number;\n}\n\nconst cache = new Map<string, CacheEntry>();\nconst inflight = new Map<string, Promise<boolean>>();\n/**\n * Per-plugin generation counter. Bumped by `invalidatePluginEnabled()` so\n * an in-flight `fetchEnabled()` can tell whether its result is still\n * relevant before writing to the cache. Without this token, the original\n * implementation (#462) had a race:\n * T0 — request A starts fetchEnabled, reads `enabled=true` from DB\n * T1 — admin toggles → invalidate clears cache + inflight\n * T2 — request B starts a fresh fetchEnabled, reads `enabled=false`,\n * writes `false` to cache\n * T3 — A's `.then()` finally runs and overwrites cache with the\n * stale `true`, sticking until the TTL expires\n * Now A bumps its captured generation against the current value before\n * writing; if they disagree, A drops its result.\n */\nconst generation = new Map<string, number>();\nlet ttlMs = DEFAULT_TTL_MS;\n\nfunction currentGeneration(pluginId: string): number {\n return generation.get(pluginId) ?? 0;\n}\n\nasync function fetchEnabled(pluginId: string): Promise<boolean> {\n if (fetchOverride) return fetchOverride(pluginId);\n try {\n const db = getDb();\n const rows = await db\n .select({ enabled: npPlugins.enabled })\n .from(npPlugins)\n .where(eq(npPlugins.id, pluginId))\n .limit(1);\n const row = rows[0] as { enabled?: unknown } | undefined;\n if (row && typeof row.enabled === \"boolean\") {\n return row.enabled;\n }\n // Row missing — treat as enabled. `syncPluginRegistrations` will insert\n // the row with enabled=true on the next boot anyway.\n return true;\n } catch {\n // DB not ready (test, CLI scaffold) or transient failure — fail open so\n // a degraded DB can't silently disable every loaded plugin.\n return true;\n }\n}\n\nexport async function isPluginEnabled(pluginId: string): Promise<boolean> {\n const now = Date.now();\n const cached = cache.get(pluginId);\n if (cached && cached.expiresAt > now) {\n return cached.enabled;\n }\n\n // Coalesce concurrent lookups for the same id so a hook that fans out\n // doesn't fire N parallel SELECTs against the same row.\n const existing = inflight.get(pluginId);\n if (existing) return existing;\n\n // Capture the generation BEFORE awaiting. If the cache is invalidated\n // while we're in flight, the generation map will tick and the .then()\n // below skips the cache write.\n const fetchGeneration = currentGeneration(pluginId);\n const promise = fetchEnabled(pluginId)\n .then((enabled) => {\n if (currentGeneration(pluginId) === fetchGeneration) {\n cache.set(pluginId, { enabled, expiresAt: Date.now() + ttlMs });\n }\n return enabled;\n })\n .finally(() => {\n // Only clear the inflight slot if it's still ours — a concurrent\n // invalidate may have cleared and a sibling request may have\n // installed a fresh promise. Don't yank theirs.\n if (inflight.get(pluginId) === promise) {\n inflight.delete(pluginId);\n }\n });\n inflight.set(pluginId, promise);\n return promise;\n}\n\nexport function invalidatePluginEnabled(pluginId: string): void {\n cache.delete(pluginId);\n inflight.delete(pluginId);\n // Tick the generation so any already-running fetchEnabled() promise\n // for this id refuses to write its result back into the cache when\n // it eventually settles. Without the bump, a slow DB read started\n // before the toggle could re-cache the stale value for up to TTL.\n generation.set(pluginId, currentGeneration(pluginId) + 1);\n}\n\n/**\n * Test-only: bypass the DB and force a known enabled value. The cache holds\n * it for the configured TTL so subsequent reads in the same test see the\n * forced value without hitting the DB stub.\n */\nexport function setPluginEnabledForTest(pluginId: string, enabled: boolean): void {\n cache.set(pluginId, { enabled, expiresAt: Number.POSITIVE_INFINITY });\n}\n\nexport function resetEnabledGate(): void {\n cache.clear();\n inflight.clear();\n generation.clear();\n ttlMs = DEFAULT_TTL_MS;\n fetchOverride = null;\n}\n\n/** Test-only: tighten the TTL so cache-expiry behavior is observable. */\nexport function setEnabledGateTtlForTest(ms: number): void {\n ttlMs = ms;\n}\n\n/**\n * Test-only: replace the DB read with a deterministic implementation so\n * race-window tests can resolve fetches in a controlled order. Production\n * code goes through `fetchEnabled()` directly; the override is wired in\n * via this setter and torn down by `resetEnabledGate()`.\n */\nlet fetchOverride: ((pluginId: string) => Promise<boolean>) | null = null;\n\nexport function setFetchImplForTest(\n impl: ((pluginId: string) => Promise<boolean>) | null,\n): void {\n fetchOverride = impl;\n}\n","/**\n * Plugin compatibility checks: framework semver range + inter-plugin\n * dependency ordering.\n *\n * The plugin manifest declares two compatibility hints:\n * 1. `nexpress.minVersion` / `nexpress.maxVersion` — the plugin must run\n * against a framework version inside this range. The host enforces it\n * at load time so an outdated plugin can't crash deeper in the call\n * stack with an unrelated `TypeError`.\n * 2. `requires` — other plugins this one depends on. Used to sort the\n * load order so a plugin's `setup()` can assume its prerequisites\n * have already registered hooks/actions.\n *\n * Both checks fail open by default — an incompatible plugin or one with\n * missing deps is logged and skipped, never thrown. Operators see the warn\n * lines in boot logs and decide whether to upgrade or pin a version.\n */\n\n/**\n * Framework version reported to plugin compatibility checks.\n *\n * `__NP_FRAMEWORK_VERSION__` is replaced at build time by tsup's\n * `define` option (see `tsup.config.ts`) with the actual\n * `package.json.version` of `@nexpress/core`. The published tarball\n * carries the literal version string inlined here — no JSON import\n * at runtime, no cross-rootDir reach. Drift between\n * package.json and this constant is structurally impossible.\n *\n * For local `vitest` runs the `define` substitution doesn't fire\n * (vitest reads source files directly), so a `declare const` ambient\n * + a fallback constant covers the test path.\n */\ndeclare const __NP_FRAMEWORK_VERSION__: string;\nconst FRAMEWORK_VERSION_FROM_PACKAGE: string =\n typeof __NP_FRAMEWORK_VERSION__ !== \"undefined\"\n ? __NP_FRAMEWORK_VERSION__\n : \"0.0.0-dev\";\nlet frameworkVersion: string = FRAMEWORK_VERSION_FROM_PACKAGE;\n\n/**\n * Returns the running framework version, read from `@nexpress/core`'s\n * package.json at build time and inlined by tsup. Tests can override via\n * `setFrameworkVersionForTest()`.\n */\nexport function getFrameworkVersion(): string {\n return frameworkVersion;\n}\n\nexport function setFrameworkVersionForTest(version: string): void {\n frameworkVersion = version;\n}\n\nexport function resetFrameworkVersion(): void {\n frameworkVersion = FRAMEWORK_VERSION_FROM_PACKAGE;\n}\n\ninterface ParsedSemver {\n major: number;\n minor: number;\n patch: number;\n /** `null` for a release version, otherwise the prerelease identifier. */\n prerelease: string | null;\n}\n\n/**\n * Parses a semver string per the regex enforced by the manifest schema:\n * `\\d+\\.\\d+\\.\\d+(-prerelease)?(+build)?`.\n *\n * Build metadata is ignored for ordering (per semver §10). Prerelease is\n * compared lexicographically, which is enough for the major.minor.patch[-tag]\n * shapes plugins typically use; this is not a full semver implementation.\n */\nfunction parse(version: string): ParsedSemver | null {\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z-.]+))?(?:\\+[0-9A-Za-z-.]+)?$/.exec(version);\n if (!match) return null;\n const [, majorStr, minorStr, patchStr, prerelease] = match;\n return {\n major: Number.parseInt(majorStr ?? \"0\", 10),\n minor: Number.parseInt(minorStr ?? \"0\", 10),\n patch: Number.parseInt(patchStr ?? \"0\", 10),\n prerelease: prerelease ?? null,\n };\n}\n\n/** Returns -1 / 0 / 1 — same contract as `Array.prototype.sort` callbacks. */\nexport function compareSemver(a: string, b: string): number {\n const pa = parse(a);\n const pb = parse(b);\n if (!pa || !pb) {\n // Malformed input — fall back to lexicographic so we still produce a\n // total order. Manifest validation should catch this before us.\n return a < b ? -1 : a > b ? 1 : 0;\n }\n if (pa.major !== pb.major) return pa.major < pb.major ? -1 : 1;\n if (pa.minor !== pb.minor) return pa.minor < pb.minor ? -1 : 1;\n if (pa.patch !== pb.patch) return pa.patch < pb.patch ? -1 : 1;\n // 1.0.0-alpha < 1.0.0 (semver §11.3 — a release is greater than its\n // prerelease counterpart).\n if (pa.prerelease === pb.prerelease) return 0;\n if (pa.prerelease === null) return 1;\n if (pb.prerelease === null) return -1;\n return pa.prerelease < pb.prerelease ? -1 : 1;\n}\n\nexport interface NexpressCompatResult {\n compatible: boolean;\n reason?: string;\n}\n\n/**\n * Verifies that `frameworkVersion` falls inside `[minVersion, maxVersion]`\n * (inclusive). `maxVersion` is optional — a plugin that omits it claims to\n * support every later version of the framework.\n */\nexport function checkNexpressCompat(\n manifest: { nexpress?: { minVersion?: string; maxVersion?: string } },\n framework: string = getFrameworkVersion(),\n): NexpressCompatResult {\n const min = manifest.nexpress?.minVersion;\n if (!min) {\n // Manifest is missing the field — should have been caught by the zod\n // schema, but be lenient at runtime.\n return { compatible: true };\n }\n if (compareSemver(framework, min) < 0) {\n return {\n compatible: false,\n reason: `requires NexPress >= ${min}, host is ${framework}`,\n };\n }\n const max = manifest.nexpress?.maxVersion;\n if (max && compareSemver(framework, max) > 0) {\n return {\n compatible: false,\n reason: `requires NexPress <= ${max}, host is ${framework}`,\n };\n }\n return { compatible: true };\n}\n\nexport interface SortedPlugins<T> {\n /** Plugins in load order — every plugin's `requires` appear before it. */\n ordered: T[];\n /** Plugins skipped because of a missing or cyclic dependency. */\n skipped: Array<{ id: string; reason: string }>;\n}\n\n/**\n * Sorts plugins so each one's declared `requires` are loaded first. Missing\n * deps and cycles produce a `skipped` entry with a human-readable reason\n * instead of throwing — boot logs will surface the issue to the operator.\n *\n * Algorithm: Kahn's. Stable for plugins with no incoming edges (preserves\n * the input order for a tie), so users still get a deterministic sort when\n * `requires` is empty for everyone.\n */\nexport function topoSort<T extends { id: string; requires: readonly string[] }>(\n plugins: T[],\n): SortedPlugins<T> {\n const skipped: Array<{ id: string; reason: string }> = [];\n\n // Iteratively narrow the eligible set: any plugin whose `requires` aren't\n // all in the *current* eligible set gets skipped, which may then make its\n // own dependents ineligible. Repeat until the set stops shrinking. The\n // earlier single-pass version (#464) only checked the *original* input ids,\n // so a plugin A → B chain where B was skipped (because B → missing C) would\n // still let A load with no error — A passed the input-set check, then the\n // edge-build phase silently dropped the B → A link.\n let eligible: T[] = [...plugins];\n while (true) {\n const eligibleIds = new Set(eligible.map((p) => p.id));\n const stillEligible: T[] = [];\n let dropped = false;\n for (const plugin of eligible) {\n const missing = plugin.requires.filter((dep) => !eligibleIds.has(dep));\n if (missing.length > 0) {\n skipped.push({\n id: plugin.id,\n reason: `missing required plugin(s): ${missing.join(\", \")}`,\n });\n dropped = true;\n continue;\n }\n stillEligible.push(plugin);\n }\n eligible = stillEligible;\n if (!dropped) break;\n }\n\n const eligibleIds = new Set(eligible.map((p) => p.id));\n const indegree = new Map<string, number>();\n const dependents = new Map<string, string[]>();\n for (const plugin of eligible) {\n indegree.set(plugin.id, 0);\n dependents.set(plugin.id, []);\n }\n for (const plugin of eligible) {\n for (const dep of plugin.requires) {\n if (!eligibleIds.has(dep)) continue;\n indegree.set(plugin.id, (indegree.get(plugin.id) ?? 0) + 1);\n dependents.get(dep)!.push(plugin.id);\n }\n }\n\n // Process in input order so the output stays stable for sibling plugins.\n const queue: T[] = eligible.filter((p) => (indegree.get(p.id) ?? 0) === 0);\n const ordered: T[] = [];\n const byId = new Map(eligible.map((p) => [p.id, p] as const));\n\n while (queue.length > 0) {\n const next = queue.shift()!;\n ordered.push(next);\n for (const dependent of dependents.get(next.id) ?? []) {\n const updated = (indegree.get(dependent) ?? 0) - 1;\n indegree.set(dependent, updated);\n if (updated === 0) {\n const plugin = byId.get(dependent);\n if (plugin) queue.push(plugin);\n }\n }\n }\n\n if (ordered.length !== eligible.length) {\n for (const plugin of eligible) {\n if (!ordered.includes(plugin)) {\n skipped.push({\n id: plugin.id,\n reason: \"dependency cycle — refusing to load\",\n });\n }\n }\n }\n\n return { ordered, skipped };\n}\n","import type { NpFieldConfig, NpPluginConfig, NpPluginContext } from \"../config/types.js\";\nimport { getLogger } from \"../observability/logger.js\";\nimport { reportError } from \"../observability/error-reporter.js\";\nimport { createPluginRuntimeContext } from \"./context.js\";\nimport { isPluginEnabled } from \"./enabled-gate.js\";\nimport { checkNexpressCompat, topoSort } from \"./compat.js\";\n\nexport interface PluginHookHandler {\n pluginId: string;\n /**\n * Returns `void` for fire-and-forget hooks (most of them). Render / extension\n * hooks may return a value; `runHookAndCollect` gathers those, while\n * `runHook` ignores returns.\n */\n handler: (data: Record<string, unknown>) => unknown;\n /**\n * Lower priority runs first. Default `100`, leaving headroom in both\n * directions: a plugin that wants to observe AFTER everyone else picks\n * `200`, one that needs to mutate the payload first picks `0`. Stable\n * ordering: ties keep registration order (which is itself topo-sorted by\n * plugin `requires`).\n */\n priority: number;\n /**\n * Per-handler timeout in milliseconds. When the handler doesn't settle\n * within the budget, `dispatchHookHandler` treats it as a failure —\n * logged and reported the same way a thrown error is. The remaining\n * handlers continue. `undefined` means \"no timeout enforced\", which is\n * the default for fire-and-forget hooks (`runHook`); render-collecting\n * hooks may want a tighter budget (e.g. 250ms) so a slow plugin can't\n * stall page rendering.\n */\n timeoutMs?: number;\n}\n\nexport interface PluginRouteHandler {\n pluginId: string;\n path: string;\n method: string;\n /** When true, the dispatcher must verify a staff session before\n * invoking `handler` and pass the resolved user as `req.user`.\n * When false (default), the route is publicly reachable. */\n auth: boolean;\n handler: (req: PluginRouteRequest) => Promise<PluginRouteResponse>;\n}\n\nexport interface PluginRouteRequest {\n method: string;\n path: string;\n params: Record<string, string>;\n query: Record<string, string>;\n body: unknown;\n headers: Record<string, string>;\n user?: { id: string; email: string; role: string };\n}\n\nexport interface PluginRouteResponse {\n status: number;\n body?: unknown;\n headers?: Record<string, string>;\n}\n\nexport interface PluginCapabilityRequirement {\n requirement: string;\n declared: readonly string[];\n}\n\n/**\n * Declarative admin extension snapshot stored per registration. Shape mirrors\n * `@nexpress/plugin-sdk`'s `NpAdminExtension` but kept structural here to\n * avoid a plugin-sdk → core cycle. The admin UI reads this via\n * `getPluginAdminExtension(id)` and renders it with its own primitives.\n */\nexport interface PluginAdminExtension {\n settings?: {\n title?: string;\n description?: string;\n fields: NpFieldConfig[];\n };\n widgets?: Array<{\n id: string;\n label: string;\n kind: \"metric\" | \"status\";\n actionId: string;\n description?: string;\n }>;\n actions?: Array<{\n id: string;\n label: string;\n actionId: string;\n confirm?: string;\n description?: string;\n }>;\n tables?: Array<{\n id: string;\n label: string;\n columns: Array<{ name: string; label: string }>;\n rowsActionId: string;\n emptyMessage?: string;\n }>;\n collectionTabs?: Array<{\n id: string;\n label: string;\n collections: string[] | \"*\";\n widgets?: Array<{\n id: string;\n label: string;\n kind: \"metric\" | \"status\";\n actionId: string;\n description?: string;\n }>;\n actions?: Array<{\n id: string;\n label: string;\n actionId: string;\n confirm?: string;\n description?: string;\n }>;\n description?: string;\n }>;\n dashboardWidgets?: Array<{\n id: string;\n label: string;\n kind: \"metric\" | \"status\";\n actionId: string;\n description?: string;\n priority?: number;\n }>;\n}\n\n/**\n * Phase 19 — first-class plugin cron schedules. Plugins\n * declare `scheduled: [{ id, cron, handler }]` in their\n * definition; the host stores the list here and pg-boss\n * registers one recurring schedule per entry. The handler\n * runs in the same context shape `setup()` saw, so plugins\n * already familiar with `ctx.content` / `ctx.storage` /\n * `ctx.next` use the same surface from a cron tick.\n */\nexport interface PluginScheduleHandler {\n pluginId: string;\n taskId: string;\n cron: string;\n description?: string;\n handler: (ctx: Record<string, unknown>) => unknown;\n}\n\ninterface PluginRegistration {\n id: string;\n name: string;\n version?: string;\n description?: string;\n capabilities: readonly string[];\n allowedHosts: readonly string[];\n admin?: PluginAdminExtension;\n hooks: Map<string, PluginHookHandler[]>;\n routes: PluginRouteHandler[];\n actions: Map<string, (data: unknown) => Promise<{ ok: boolean; data?: unknown; error?: string }>>;\n schedules: Map<string, PluginScheduleHandler>;\n /**\n * G.1 — Zod schema describing the plugin's operator-tunable\n * config. Read by `getPluginConfig` (introspection +\n * validation) and the admin auto-form. `unknown` here so the\n * host doesn't pull a hard zod dep into its type surface;\n * narrowed at the call site (same pattern theme uses for\n * `settingsSchema`).\n */\n configSchema?: unknown;\n /** G.1 — schema version (defaults to 1). See plugin-sdk types. */\n configVersion?: number;\n /** G.1 — migration callback for v(N-1) → current. */\n configMigrate?: (old: unknown, fromVersion: number) => unknown;\n /**\n * Plugin-contributed page routes (#623). Stored as the same\n * shape the SDK accepts (component + optional metadata as\n * `unknown`); the route-dispatcher in `@nexpress/next`\n * narrows them at render time. See\n * `docs/design/plugin-routes.md` for precedence + surface\n * semantics.\n */\n pageRoutes: readonly PluginPageRouteEntry[];\n}\n\nexport interface PluginPageRouteEntry {\n pattern: string;\n component: unknown;\n metadata?: unknown;\n surface: \"site\" | \"member\";\n locale: \"auto\" | \"none\";\n}\n\n/**\n * Hook names start with a namespace — \"content:afterCreate\",\n * \"auth:afterLogin\", \"render:beforePage\", etc. The plugin must declare the\n * matching \"hooks:<namespace>\" capability to register a handler. This is the\n * v1 runtime enforcement: coarse, easy to reason about, and deliberately\n * additive — plugins without any capabilities can still do nothing.\n */\nfunction hookCapabilityFor(hookName: string): string | null {\n const namespace = hookName.split(\":\")[0];\n if (!namespace) return null;\n return `hooks:${namespace}`;\n}\n\nfunction assertCapability(\n pluginId: string,\n requirement: string,\n declared: readonly string[],\n): void {\n if (declared.includes(requirement)) return;\n\n throw new Error(\n `[plugin:${pluginId}] declares capabilities ${JSON.stringify(declared)} ` +\n `but is registering something that requires \"${requirement}\". ` +\n `Add \"${requirement}\" to the plugin manifest's capabilities array.`,\n );\n}\n\nconst pluginRegistry = new Map<string, PluginRegistration>();\nconst globalHooks = new Map<string, PluginHookHandler[]>();\nconst globalRoutes: PluginRouteHandler[] = [];\n\n/**\n * Default priority assigned to a hook handler that doesn't pick one. The\n * value matters less than the headroom — a plugin can always go above or\n * below to override, and 100 leaves room for both. Keep in sync with the\n * docstring on `PluginHookHandler.priority`.\n */\nconst DEFAULT_HOOK_PRIORITY = 100;\n\n/**\n * Normalizes the two valid hook value shapes:\n * - bare function (the original shape — implicit priority 100, no timeout)\n * - `{ handler, priority?, timeoutMs? }` object\n *\n * Returns `null` for malformed input so the caller can skip silently —\n * Zod schema on the plugin-sdk side already rejects bad shapes at\n * authoring time, so this is just defense in depth at the host boundary.\n */\nfunction normalizeHookValue(\n value: unknown,\n): { handler: ResolvedHookFn; priority: number; timeoutMs?: number } | null {\n if (typeof value === \"function\") {\n return { handler: value as ResolvedHookFn, priority: DEFAULT_HOOK_PRIORITY };\n }\n if (value && typeof value === \"object\") {\n const v = value as { handler?: unknown; priority?: unknown; timeoutMs?: unknown };\n if (typeof v.handler !== \"function\") return null;\n return {\n handler: v.handler as ResolvedHookFn,\n priority: typeof v.priority === \"number\" ? v.priority : DEFAULT_HOOK_PRIORITY,\n timeoutMs: typeof v.timeoutMs === \"number\" && v.timeoutMs > 0 ? v.timeoutMs : undefined,\n };\n }\n return null;\n}\n\n/**\n * Inserts a handler into the global per-hook list while keeping the array\n * sorted by `(priority asc, registration order)`. Array sort in V8 is\n * stable, so we can append + sort and ties keep insertion order.\n *\n * Sorting at registration time means dispatch is allocation-free — the\n * hot path just iterates the array. Registrations happen at boot (and on\n * a hot reload), so re-sorting on each insert is fine.\n */\nfunction insertSortedByPriority(\n list: PluginHookHandler[],\n entry: PluginHookHandler,\n): void {\n list.push(entry);\n list.sort((a, b) => a.priority - b.priority);\n}\n\n/**\n * Structural shape for plugins built via `@nexpress/plugin-sdk`'s\n * `definePlugin()`. Matches `NpResolvedPluginLike` in config/types.ts —\n * kept deliberately loose so `loadPlugins` can accept the same array\n * that `NpConfig.plugins` does without narrowing gymnastics.\n */\nexport interface ResolvedPluginLike {\n manifest: {\n id: string;\n name: string;\n version?: string;\n description?: string;\n capabilities: readonly string[];\n allowedHosts?: readonly string[];\n /**\n * Compatibility range for the framework. The plugin loads only when\n * `nexpress.minVersion <= host <= nexpress.maxVersion?` (inclusive).\n * Optional here so legacy / hand-rolled plugins keep loading; the\n * plugin-sdk schema requires it for new plugins.\n */\n nexpress?: { minVersion?: string; maxVersion?: string };\n /**\n * IDs of other plugins that must load first. The host topo-sorts the\n * load list so this plugin's `setup()` can assume its prerequisites\n * have already registered hooks/actions/blocks.\n */\n requires?: readonly string[];\n };\n hooks?: Record<string, unknown>;\n routes?: ReadonlyArray<{\n path: string;\n method: string;\n handler: unknown;\n description?: string;\n auth?: boolean;\n }>;\n admin?: PluginAdminExtension;\n /** G.1 — runtime zod schema for plugin config (auto-form). */\n configSchema?: unknown;\n /** G.1 — schema version for the lazy migration pipeline. */\n configVersion?: number;\n /** G.1 — old → current value migrator. */\n configMigrate?: (old: unknown, fromVersion: number) => unknown;\n}\n\ntype ResolvedHookFn = (ctx: {\n hook: string;\n data: Record<string, unknown>;\n collection?: string;\n ctx: Record<string, unknown>;\n}) => unknown;\n\ntype ResolvedRouteFn = (\n req: PluginRouteRequest,\n ctx: Record<string, unknown>,\n) => Promise<PluginRouteResponse>;\n\n/**\n * G.1 — read a plugin's persisted config from `np_settings`.\n *\n * Internal helper for the runtime context builder; external callers\n * import `getPluginConfig` from `./config.js` directly. We avoid a\n * cross-module call here so `host.ts` doesn't import from `config.ts`\n * (one-way: config.ts imports from host.ts via `getPluginRegistration`).\n */\nasync function loadPluginConfig(pluginId: string): Promise<Record<string, unknown>> {\n const { getPluginConfig } = await import(\"./config.js\");\n const value = await getPluginConfig(pluginId);\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return {};\n}\n\nasync function buildCtxFor(pluginId: string): Promise<Record<string, unknown>> {\n const registration = pluginRegistry.get(pluginId);\n if (!registration) {\n throw new Error(`[plugin:${pluginId}] attempted to build ctx before registration.`);\n }\n const config = await loadPluginConfig(pluginId);\n return createPluginRuntimeContext({\n pluginId,\n capabilities: registration.capabilities,\n allowedHosts: registration.allowedHosts,\n config,\n registration,\n lookupRegistration: (id) => pluginRegistry.get(id),\n });\n}\n\nfunction isResolvedPlugin(value: unknown): value is ResolvedPluginLike {\n if (!value || typeof value !== \"object\") return false;\n const candidate = value as { manifest?: unknown };\n if (!candidate.manifest || typeof candidate.manifest !== \"object\") return false;\n const manifest = candidate.manifest as { id?: unknown; capabilities?: unknown };\n return typeof manifest.id === \"string\" && Array.isArray(manifest.capabilities);\n}\n\nfunction registerHookHandler(\n registration: PluginRegistration,\n hookName: string,\n handler: PluginHookHandler,\n): void {\n if (!registration.hooks.has(hookName)) {\n registration.hooks.set(hookName, []);\n }\n insertSortedByPriority(registration.hooks.get(hookName)!, handler);\n\n if (!globalHooks.has(hookName)) {\n globalHooks.set(hookName, []);\n }\n insertSortedByPriority(globalHooks.get(hookName)!, handler);\n}\n\nfunction createPluginContext(pluginId: string, registration: PluginRegistration): NpPluginContext {\n return {\n addCollection: () => {\n throw new Error(\n `[plugin:${pluginId}] Runtime collection registration not supported in v1. Add collections to nexpress.config.ts.`,\n );\n },\n addBlock: () => {\n throw new Error(\n `[plugin:${pluginId}] Runtime block registration not supported in v1. Add blocks to nexpress.config.ts.`,\n );\n },\n addHook: (collection: string, event: string, hook) => {\n // Legacy API: collection is the docs' collection (\"posts\"), event is the\n // lifecycle step (\"afterCreate\"). The pipeline emits canonical hook names\n // under the \"content:\" namespace (e.g. `content:afterCreate`), so\n // register there and filter by collection at dispatch time. This keeps\n // legacy hooks firing on the same stream as resolved-plugin hooks.\n const hookName = `content:${event}`;\n const requirement = hookCapabilityFor(hookName);\n if (requirement) {\n assertCapability(pluginId, requirement, registration.capabilities);\n }\n\n registerHookHandler(registration, hookName, {\n pluginId,\n priority: DEFAULT_HOOK_PRIORITY,\n handler: async (data) => {\n if (typeof data.collection === \"string\" && data.collection !== collection) {\n return;\n }\n await hook({ data, collection } as never);\n },\n });\n },\n };\n}\n\nasync function loadResolvedPlugin(plugin: ResolvedPluginLike): Promise<void> {\n const { manifest } = plugin;\n\n // Defense in depth: if this id was already registered, scrub the old\n // entry's hooks + routes from the global maps before overwriting. The\n // documented reload flow (`reloadPlugins()`) always calls `resetPlugins()`\n // first, so we shouldn't normally hit this — but a stray double-load\n // would otherwise leave both registrations dispatching, which is much\n // harder to diagnose than a clean re-register.\n const previous = pluginRegistry.get(manifest.id);\n if (previous) {\n for (const [hookName, list] of previous.hooks) {\n const global = globalHooks.get(hookName);\n if (!global) continue;\n const filtered = global.filter((h) => !list.includes(h));\n if (filtered.length === 0) globalHooks.delete(hookName);\n else globalHooks.set(hookName, filtered);\n }\n for (const route of previous.routes) {\n const idx = globalRoutes.indexOf(route);\n if (idx !== -1) globalRoutes.splice(idx, 1);\n }\n }\n\n const registration: PluginRegistration = {\n id: manifest.id,\n name: manifest.name,\n version: manifest.version,\n description: manifest.description,\n capabilities: [...manifest.capabilities],\n allowedHosts: [...(manifest.allowedHosts ?? [])],\n admin: plugin.admin,\n hooks: new Map(),\n routes: [],\n actions: new Map(),\n schedules: new Map(),\n configSchema: plugin.configSchema,\n configVersion: plugin.configVersion,\n configMigrate: plugin.configMigrate,\n pageRoutes: normalizePageRoutes(plugin),\n };\n\n pluginRegistry.set(manifest.id, registration);\n\n // G.1 — declaring BOTH `configSchema` (auto-form) and\n // `admin.settings.fields` (legacy declarative form) is a sign\n // the plugin is mid-migration. The auto-form wins (per design\n // doc § 5.1.1); warn so the operator/author notices the\n // ignored field list. Migrating PRs should remove\n // `admin.settings.fields` in the same diff that adds\n // `configSchema`.\n if (\n registration.configSchema !== undefined &&\n plugin.admin?.settings?.fields &&\n plugin.admin.settings.fields.length > 0\n ) {\n getLogger().warn(\"Plugin declares both configSchema and admin.settings.fields\", {\n pluginId: manifest.id,\n note: \"Auto-form wins; admin.settings.fields is ignored at render time. Remove admin.settings.fields when migrating to configSchema.\",\n });\n }\n\n // Phase 19 — first-class cron schedules. Each entry maps to\n // one pg-boss schedule. Duplicates within a plugin overwrite\n // (idempotent across hot reloads); cross-plugin id collisions\n // are fine because the queue name namespaces by plugin id.\n const scheduledRaw = (plugin as { scheduled?: unknown }).scheduled;\n if (Array.isArray(scheduledRaw)) {\n for (const entry of scheduledRaw) {\n if (!entry || typeof entry !== \"object\") continue;\n const e = entry as {\n id?: unknown;\n cron?: unknown;\n handler?: unknown;\n description?: unknown;\n };\n if (typeof e.id !== \"string\" || e.id.length === 0) continue;\n if (typeof e.cron !== \"string\" || e.cron.length === 0) continue;\n if (typeof e.handler !== \"function\") continue;\n registration.schedules.set(e.id, {\n pluginId: manifest.id,\n taskId: e.id,\n cron: e.cron,\n description: typeof e.description === \"string\" ? e.description : undefined,\n handler: e.handler as PluginScheduleHandler[\"handler\"],\n });\n }\n }\n\n for (const [hookName, rawValue] of Object.entries(plugin.hooks ?? {})) {\n const normalized = normalizeHookValue(rawValue);\n if (!normalized) continue;\n\n const requirement = hookCapabilityFor(hookName);\n if (requirement) {\n assertCapability(manifest.id, requirement, registration.capabilities);\n }\n\n const userHandler = normalized.handler;\n registerHookHandler(registration, hookName, {\n pluginId: manifest.id,\n priority: normalized.priority,\n timeoutMs: normalized.timeoutMs,\n handler: async (data) => {\n const collection = typeof data.collection === \"string\" ? data.collection : undefined;\n const ctx = await buildCtxFor(manifest.id);\n return await userHandler({ hook: hookName, data, collection, ctx });\n },\n });\n }\n\n for (const route of plugin.routes ?? []) {\n if (typeof route.handler !== \"function\") continue;\n\n assertCapability(manifest.id, \"api:route\", registration.capabilities);\n\n const userHandler = route.handler as ResolvedRouteFn;\n const wrapped: (req: PluginRouteRequest) => Promise<PluginRouteResponse> = async (req) => {\n const ctx = await buildCtxFor(manifest.id);\n return userHandler(req, ctx);\n };\n\n const auth = route.auth === true;\n const method = route.method.toUpperCase();\n\n // #316 — public plugin routes carry the framework's least-\n // protected default rate limit (proxy.ts caps the catch-all at\n // 30 req/min/IP) and run *plugin-supplied* code without staff\n // session checks. Mutating ones double the surface area: an\n // attacker that finds the route can hit the handler at the IP\n // ceiling. Plugins that legitimately need a public mutating\n // endpoint (webhooks, callback URLs) own the auth themselves —\n // log a warning at load time so this is at least visible in\n // boot logs and a tracker can grep for it.\n if (\n !auth &&\n method !== \"GET\" &&\n method !== \"HEAD\" &&\n method !== \"OPTIONS\"\n ) {\n getLogger().warn(\"Plugin registered a public mutating route\", {\n pluginId: manifest.id,\n path: route.path,\n method,\n note:\n \"Plugins are responsible for their own auth on `auth: false` \" +\n \"routes. The framework rate-limits the plugin catch-all to \" +\n \"30 req/min/IP; verify the handler enforces signature / token \" +\n \"checks before mutating state.\",\n });\n }\n\n const entry: PluginRouteHandler = {\n pluginId: manifest.id,\n path: route.path,\n method,\n auth,\n handler: wrapped,\n };\n registration.routes.push(entry);\n globalRoutes.push(entry);\n }\n\n // Phase 12.5 — merge any UI-string bundles the plugin\n // ships into the global registry. Bundles are scoped per\n // locale; later plugins overwrite earlier ones on key\n // collision so plugin-order in the config drives override\n // priority. Plugin authors typically namespace their keys\n // (e.g. `forum.replyButton`) to avoid collisions across\n // unrelated plugins.\n const i18nBundles = (plugin as { i18n?: Record<string, Record<string, string>> }).i18n;\n if (i18nBundles && typeof i18nBundles === \"object\") {\n const { addStrings } = await import(\"../i18n/strings.js\");\n for (const [locale, bundle] of Object.entries(i18nBundles)) {\n if (bundle && typeof bundle === \"object\") {\n addStrings(locale, bundle);\n }\n }\n }\n\n // Phase 14.5 — merge any page templates the plugin\n // contributes. Theme templates win on id collision (handled\n // downstream in `getThemeTemplateSummaries`), so plugin\n // authors don't need to coordinate id namespaces with the\n // active theme — the theme just stays authoritative. Re-\n // registering the same plugin overwrites its previous\n // entries (idempotent across hot reloads).\n const pluginTemplates = (plugin as { templates?: Record<string, Record<string, unknown>> })\n .templates;\n if (pluginTemplates && typeof pluginTemplates === \"object\") {\n const { registerPluginTemplates } = await import(\"./templates.js\");\n registerPluginTemplates(manifest.id, pluginTemplates);\n }\n\n // Invoke optional setup() after hooks + routes are registered so setup can\n // call ctx.actions.register(…) and have it visible to subsequent dispatches.\n const setup = (plugin as { setup?: (ctx: Record<string, unknown>) => void | Promise<void> })\n .setup;\n if (typeof setup === \"function\") {\n const ctx = await buildCtxFor(manifest.id);\n await setup(ctx);\n }\n}\n\nasync function loadLegacyPlugin(plugin: NpPluginConfig): Promise<void> {\n const registration: PluginRegistration = {\n id: plugin.id,\n name: plugin.name,\n capabilities: [\"hooks:content\"],\n allowedHosts: [],\n hooks: new Map(),\n routes: [],\n actions: new Map(),\n schedules: new Map(),\n // Legacy `init()` plugins predate the page-routes contract;\n // they always register zero routes. Kept as a literal `[]` so\n // the registration shape is consistent across the two paths\n // and `getPluginPageRoutes()` doesn't need to special-case\n // legacy entries.\n pageRoutes: [],\n };\n\n pluginRegistry.set(plugin.id, registration);\n\n if (plugin.init) {\n const ctx = createPluginContext(plugin.id, registration);\n await plugin.init(ctx);\n }\n}\n\nexport async function loadPlugins(\n plugins: Array<NpPluginConfig | ResolvedPluginLike>,\n): Promise<void> {\n // Pass 1 — drop plugins whose declared `nexpress` range excludes the\n // running framework version. We warn instead of throwing so a host that\n // ships with eight plugins doesn't refuse to boot when one is stale.\n const filtered: Array<NpPluginConfig | ResolvedPluginLike> = [];\n for (const plugin of plugins) {\n if (isResolvedPlugin(plugin)) {\n const compat = checkNexpressCompat(plugin.manifest);\n if (!compat.compatible) {\n getLogger().warn(\"Skipping incompatible plugin\", {\n pluginId: plugin.manifest.id,\n reason: compat.reason,\n });\n continue;\n }\n }\n filtered.push(plugin);\n }\n\n // Pass 2 — order resolved plugins by their `requires` graph. Legacy\n // (init()-shape) plugins have no manifest so they ride at the front in\n // their original order; they predate the dependency model and never\n // declare requirements.\n const legacy: NpPluginConfig[] = [];\n const resolved: ResolvedPluginLike[] = [];\n for (const plugin of filtered) {\n if (isResolvedPlugin(plugin)) {\n resolved.push(plugin);\n } else {\n legacy.push(plugin);\n }\n }\n\n const sortInput = resolved.map((plugin) => ({\n id: plugin.manifest.id,\n requires: plugin.manifest.requires ?? [],\n plugin,\n }));\n const { ordered, skipped } = topoSort(sortInput);\n for (const entry of skipped) {\n getLogger().warn(\"Skipping plugin with unsatisfied dependency\", {\n pluginId: entry.id,\n reason: entry.reason,\n });\n }\n\n // Pass 3 — actually load. Legacy first, then resolved in topo order.\n // Each load is wrapped in error isolation: a throwing plugin (most\n // commonly a buggy `setup()` callback or a missing required config)\n // is logged and skipped, so one broken plugin can't take down the\n // whole boot. Partial state from a half-loaded plugin (hooks/routes\n // registered before `setup` threw) is scrubbed via\n // `pluginRegistry.delete(id)` so callers don't see an inconsistent\n // shell registration. Plugins that depend on the failed one will\n // either fail their own require check (handled cleanly) or fail at\n // dispatch time (also caught at the dispatch layer).\n for (const plugin of legacy) {\n try {\n await loadLegacyPlugin(plugin);\n } catch (err) {\n pluginRegistry.delete(plugin.id);\n getLogger().error(\"Plugin failed to load — skipped\", {\n pluginId: plugin.id,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n for (const entry of ordered) {\n try {\n await loadResolvedPlugin(entry.plugin);\n } catch (err) {\n const pluginId = entry.plugin.manifest.id;\n pluginRegistry.delete(pluginId);\n getLogger().error(\"Plugin failed to load — skipped\", {\n pluginId,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n}\n\n/**\n * Invokes one plugin hook handler with error isolation: a thrown handler\n * is logged, reported, and swallowed so a single broken plugin can't take\n * down the rest of the dispatch chain (or the caller — pipeline write,\n * page render, etc.).\n *\n * Returns `{ ok: true, value }` on success and `{ ok: false }` on failure.\n * Callers that aggregate return values (`runHookAndCollect`) skip failed\n * handlers; fire-and-forget callers (`runHook`) ignore the value entirely.\n */\nasync function dispatchHookHandler(\n hookName: string,\n handler: PluginHookHandler,\n data: Record<string, unknown>,\n): Promise<{ ok: true; value: unknown } | { ok: false }> {\n try {\n const result = handler.handler(data);\n // Fast path: handler returned a non-Promise. Skip the timer +\n // Promise.race overhead for the common synchronous case.\n if (handler.timeoutMs === undefined || !(result instanceof Promise)) {\n const value = await result;\n return { ok: true, value };\n }\n // Slow path: race the handler against a timeout. We allocate the\n // timer here (not in the resolved Promise.then) so a fast-resolving\n // handler still pays only the timer-creation cost; the timer is\n // cleared in `finally`.\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(\n new Error(\n `Plugin hook handler timed out after ${handler.timeoutMs}ms`,\n ),\n );\n }, handler.timeoutMs);\n });\n try {\n const value = await Promise.race([result, timeoutPromise]);\n return { ok: true, value };\n } finally {\n if (timer) clearTimeout(timer);\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n getLogger().error(\"Plugin hook handler threw\", {\n pluginId: handler.pluginId,\n hook: hookName,\n timeoutMs: handler.timeoutMs,\n message: err.message,\n stack: err.stack,\n });\n void reportError(err, {\n tags: { source: \"plugin-hook\", pluginId: handler.pluginId, hook: hookName },\n });\n return { ok: false };\n }\n}\n\nexport async function runHook(hookName: string, data: Record<string, unknown>): Promise<void> {\n const handlers = globalHooks.get(hookName);\n if (!handlers || handlers.length === 0) return;\n\n for (const handler of handlers) {\n if (!(await isPluginEnabled(handler.pluginId))) continue;\n await dispatchHookHandler(hookName, handler, data);\n }\n}\n\n/**\n * Like `runHook`, but collects every non-null/undefined return value from\n * registered handlers. Used by render extension points (`render:beforePage`,\n * etc.) where each plugin contributes structured data — head tags, scripts —\n * that the renderer aggregates into a single output.\n *\n * Handlers that throw are isolated (logged + reported, then skipped). A\n * broken plugin contributing meta tags is allowed to fail silently so the\n * page itself still ships — incomplete SEO output beats a 500.\n */\nexport async function runHookAndCollect<T>(\n hookName: string,\n data: Record<string, unknown>,\n): Promise<T[]> {\n const handlers = globalHooks.get(hookName);\n if (!handlers || handlers.length === 0) return [];\n\n const results: T[] = [];\n for (const handler of handlers) {\n if (!(await isPluginEnabled(handler.pluginId))) continue;\n const outcome = await dispatchHookHandler(hookName, handler, data);\n if (outcome.ok && outcome.value !== undefined && outcome.value !== null) {\n results.push(outcome.value as T);\n }\n }\n return results;\n}\n\nexport function getPluginRoutes(): PluginRouteHandler[] {\n return globalRoutes;\n}\n\n/**\n * Plugin page routes (#623). Returns the flat list of registered\n * routes from EVERY loaded plugin in registration order, regardless\n * of enabled state — call sites that care about enabled gating\n * (e.g. the route dispatcher) walk the list and re-check via\n * `isPluginEnabled(pluginId)`. Keeping the gate at the call site\n * means tests can assert the registered shape without mocking the\n * enabled-state singleton.\n */\nexport function getPluginPageRoutes(): Array<{\n pluginId: string;\n route: PluginPageRouteEntry;\n}> {\n const out: Array<{ pluginId: string; route: PluginPageRouteEntry }> = [];\n for (const [pluginId, registration] of pluginRegistry) {\n for (const route of registration.pageRoutes) {\n out.push({ pluginId, route });\n }\n }\n return out;\n}\n\n/**\n * Normalize a plugin's `pageRoutes` field at registration time.\n * Drops malformed entries silently — same defensive shape as\n * `scheduled` / hooks normalization above. Defaults `surface` to\n * `\"site\"` and `locale` to `\"auto\"` so the dispatcher always gets\n * concrete values.\n */\nfunction normalizePageRoutes(plugin: ResolvedPluginLike): readonly PluginPageRouteEntry[] {\n const raw = (plugin as { pageRoutes?: unknown }).pageRoutes;\n if (!Array.isArray(raw)) return [];\n const out: PluginPageRouteEntry[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== \"object\") continue;\n const r = entry as {\n pattern?: unknown;\n component?: unknown;\n metadata?: unknown;\n surface?: unknown;\n locale?: unknown;\n };\n if (typeof r.pattern !== \"string\" || r.pattern.length === 0) continue;\n // Accept either a function (functional / class component) or a\n // non-null object (memo / forwardRef / Suspense-wrapped, all of\n // which are objects with a `$$typeof` brand). Reject anything\n // else — strings, numbers, booleans — that the dispatcher would\n // hand to React only to crash at render time. Same defensive\n // shape as the scheduled / hooks normalization above; tighter\n // than the earlier draft which only rejected null / undefined.\n if (typeof r.component !== \"function\") {\n if (typeof r.component !== \"object\" || r.component === null) continue;\n }\n out.push({\n pattern: r.pattern,\n component: r.component,\n metadata: r.metadata,\n surface: r.surface === \"member\" ? \"member\" : \"site\",\n locale: r.locale === \"none\" ? \"none\" : \"auto\",\n });\n }\n return out;\n}\n\nexport function getPluginRegistration(pluginId: string): PluginRegistration | undefined {\n return pluginRegistry.get(pluginId);\n}\n\nexport function getAllPluginIds(): string[] {\n return [...pluginRegistry.keys()];\n}\n\nexport function getPluginAdminExtension(pluginId: string): PluginAdminExtension | undefined {\n return pluginRegistry.get(pluginId)?.admin;\n}\n\n/**\n * Resolved collection-tab descriptor for the admin collection edit view.\n * Each entry carries pluginId + pluginName so the client component can\n * dispatch actions and label cards per-plugin.\n */\nexport interface ResolvedCollectionTab {\n pluginId: string;\n pluginName: string;\n id: string;\n label: string;\n widgets?: NonNullable<PluginAdminExtension[\"collectionTabs\"]>[number][\"widgets\"];\n actions?: NonNullable<PluginAdminExtension[\"collectionTabs\"]>[number][\"actions\"];\n description?: string;\n}\n\n/**\n * Collects all `collectionTabs` entries declared by loaded plugins whose\n * `collections` filter matches the given slug (either `\"*\"` or includes it).\n * The returned array is already flattened and annotated with the source\n * plugin, ready to pass into the admin edit view.\n */\nexport function getCollectionTabsForSlug(collectionSlug: string): ResolvedCollectionTab[] {\n const result: ResolvedCollectionTab[] = [];\n for (const registration of pluginRegistry.values()) {\n const tabs = registration.admin?.collectionTabs;\n if (!tabs || tabs.length === 0) continue;\n for (const tab of tabs) {\n const matches =\n tab.collections === \"*\" ||\n (Array.isArray(tab.collections) && tab.collections.includes(collectionSlug));\n if (!matches) continue;\n result.push({\n pluginId: registration.id,\n pluginName: registration.name,\n id: tab.id,\n label: tab.label,\n widgets: tab.widgets,\n actions: tab.actions,\n description: tab.description,\n });\n }\n }\n return result;\n}\n\n/**\n * Dashboard widget descriptor annotated with its source plugin. The admin\n * dashboard dispatches the widget's action with an empty payload — dashboard\n * widgets are global, not per-document.\n */\nexport interface ResolvedDashboardWidget {\n pluginId: string;\n pluginName: string;\n id: string;\n label: string;\n kind: \"metric\" | \"status\";\n actionId: string;\n description?: string;\n priority?: number;\n}\n\n/**\n * Collects `dashboardWidgets` declared by every loaded plugin and returns\n * them in render order: `priority` asc (missing priority = Infinity, i.e.\n * rendered last), ties broken by plugin registration order.\n */\nexport function getDashboardWidgetsFromPlugins(): ResolvedDashboardWidget[] {\n const result: ResolvedDashboardWidget[] = [];\n for (const registration of pluginRegistry.values()) {\n const widgets = registration.admin?.dashboardWidgets;\n if (!widgets || widgets.length === 0) continue;\n for (const widget of widgets) {\n result.push({\n pluginId: registration.id,\n pluginName: registration.name,\n id: widget.id,\n label: widget.label,\n kind: widget.kind,\n actionId: widget.actionId,\n description: widget.description,\n priority: widget.priority,\n });\n }\n }\n // Stable sort: items keep registration order when priorities tie.\n return result\n .map((widget, index) => ({ widget, index }))\n .sort((a, b) => {\n const ap = a.widget.priority ?? Number.POSITIVE_INFINITY;\n const bp = b.widget.priority ?? Number.POSITIVE_INFINITY;\n if (ap !== bp) return ap - bp;\n return a.index - b.index;\n })\n .map(({ widget }) => widget);\n}\n\n/**\n * Dispatches a named action registered by the plugin via\n * `ctx.actions.register(actionId, handler)`. Admin widgets / actions / tables\n * call this via POST /api/plugins/:id/actions/:actionId — the handler is\n * responsible for returning `{ ok, data?, error? }`.\n */\nexport async function dispatchPluginAction(\n pluginId: string,\n actionId: string,\n data?: unknown,\n): Promise<{ ok: boolean; data?: unknown; error?: string }> {\n const registration = pluginRegistry.get(pluginId);\n if (!registration) {\n return { ok: false, error: `Plugin \"${pluginId}\" is not registered` };\n }\n if (!(await isPluginEnabled(pluginId))) {\n return { ok: false, error: `Plugin \"${pluginId}\" is disabled` };\n }\n const handler = registration.actions.get(actionId);\n if (!handler) {\n return { ok: false, error: `Action \"${actionId}\" not found on plugin \"${pluginId}\"` };\n }\n return handler(data);\n}\n\nexport async function schedulePluginTask(pluginId: string, taskId: string): Promise<void> {\n const { enqueueJob } = await import(\"../jobs/queue.js\");\n await enqueueJob(\"plugin:scheduledTask\", { pluginId, taskId });\n}\n\n/**\n * Phase 19 — return every registered schedule across loaded\n * plugins. The pg-boss adapter calls this from\n * `scheduleRecurring()` so each `definePlugin({ scheduled })`\n * entry becomes a real cron in `pgboss.schedule`.\n */\nexport function getRegisteredPluginSchedules(): PluginScheduleHandler[] {\n const out: PluginScheduleHandler[] = [];\n for (const reg of pluginRegistry.values()) {\n for (const schedule of reg.schedules.values()) {\n out.push(schedule);\n }\n }\n return out;\n}\n\n/**\n * Phase 19 — runs the handler for one plugin's scheduled task.\n * Called from the `plugin:scheduledTask` job handler when a\n * tick fires. Builds the same plugin context the `setup()`\n * call sees so handlers reuse `ctx.content` / `ctx.storage` /\n * etc. Throws when the plugin or task isn't registered so the\n * worker's retry policy surfaces the misconfiguration.\n */\nexport async function runPluginScheduledTask(pluginId: string, taskId: string): Promise<void> {\n const registration = pluginRegistry.get(pluginId);\n if (!registration) {\n throw new Error(`Plugin \"${pluginId}\" is not registered`);\n }\n if (!(await isPluginEnabled(pluginId))) {\n // pg-boss keeps firing the cron entry even when disabled; bail quietly so\n // the queue records a successful tick instead of a retry-storm.\n getLogger().debug(\"Skipping plugin scheduled task — plugin disabled\", {\n pluginId,\n taskId,\n });\n return;\n }\n const entry = registration.schedules.get(taskId);\n if (!entry) {\n throw new Error(`Plugin \"${pluginId}\" has no scheduled task with id \"${taskId}\"`);\n }\n const ctx = await buildCtxFor(pluginId);\n await entry.handler(ctx);\n}\n\nexport function resetPlugins(): void {\n pluginRegistry.clear();\n globalHooks.clear();\n globalRoutes.length = 0;\n}\n\nexport { isPluginEnabled, invalidatePluginEnabled } from \"./enabled-gate.js\";\n"],"mappings":"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","names":["eq","sql","getLogger","sql","eq","getPluginRegistration","eq","eq","eligibleIds","enqueueJob"]}