@nexpress/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/dist/audit-54XLVCWD.js +14 -0
  4. package/dist/audit-54XLVCWD.js.map +1 -0
  5. package/dist/auth.d.ts +640 -0
  6. package/dist/auth.js +94 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/can-YLUHRJAB.js +19 -0
  9. package/dist/can-YLUHRJAB.js.map +1 -0
  10. package/dist/chunk-2G264RCD.js +68 -0
  11. package/dist/chunk-2G264RCD.js.map +1 -0
  12. package/dist/chunk-2YDGE7YX.js +92 -0
  13. package/dist/chunk-2YDGE7YX.js.map +1 -0
  14. package/dist/chunk-473S4TER.js +538 -0
  15. package/dist/chunk-473S4TER.js.map +1 -0
  16. package/dist/chunk-4ZLMEKFX.js +18 -0
  17. package/dist/chunk-4ZLMEKFX.js.map +1 -0
  18. package/dist/chunk-55FU6WED.js +179 -0
  19. package/dist/chunk-55FU6WED.js.map +1 -0
  20. package/dist/chunk-6YI5K2TI.js +1959 -0
  21. package/dist/chunk-6YI5K2TI.js.map +1 -0
  22. package/dist/chunk-BHK3AD3Q.js +41 -0
  23. package/dist/chunk-BHK3AD3Q.js.map +1 -0
  24. package/dist/chunk-CRUQBZUF.js +39 -0
  25. package/dist/chunk-CRUQBZUF.js.map +1 -0
  26. package/dist/chunk-CTSQ7BRI.js +175 -0
  27. package/dist/chunk-CTSQ7BRI.js.map +1 -0
  28. package/dist/chunk-DK2JBJH7.js +81 -0
  29. package/dist/chunk-DK2JBJH7.js.map +1 -0
  30. package/dist/chunk-DP2PREDU.js +597 -0
  31. package/dist/chunk-DP2PREDU.js.map +1 -0
  32. package/dist/chunk-EQ2Z3KMD.js +24 -0
  33. package/dist/chunk-EQ2Z3KMD.js.map +1 -0
  34. package/dist/chunk-FZ7O6DWI.js +305 -0
  35. package/dist/chunk-FZ7O6DWI.js.map +1 -0
  36. package/dist/chunk-ISLYFQWL.js +1270 -0
  37. package/dist/chunk-ISLYFQWL.js.map +1 -0
  38. package/dist/chunk-JJL74ZPK.js +68 -0
  39. package/dist/chunk-JJL74ZPK.js.map +1 -0
  40. package/dist/chunk-JKXAPSU4.js +24 -0
  41. package/dist/chunk-JKXAPSU4.js.map +1 -0
  42. package/dist/chunk-KU5M27ZC.js +24 -0
  43. package/dist/chunk-KU5M27ZC.js.map +1 -0
  44. package/dist/chunk-LSHHRDVR.js +34 -0
  45. package/dist/chunk-LSHHRDVR.js.map +1 -0
  46. package/dist/chunk-M43PGOQY.js +715 -0
  47. package/dist/chunk-M43PGOQY.js.map +1 -0
  48. package/dist/chunk-MEJAHXIO.js +150 -0
  49. package/dist/chunk-MEJAHXIO.js.map +1 -0
  50. package/dist/chunk-NUCGHWCF.js +101 -0
  51. package/dist/chunk-NUCGHWCF.js.map +1 -0
  52. package/dist/chunk-OK5HOCQI.js +845 -0
  53. package/dist/chunk-OK5HOCQI.js.map +1 -0
  54. package/dist/chunk-OROPGO65.js +13 -0
  55. package/dist/chunk-OROPGO65.js.map +1 -0
  56. package/dist/chunk-PPAS4SZR.js +176 -0
  57. package/dist/chunk-PPAS4SZR.js.map +1 -0
  58. package/dist/chunk-PPBWRKO2.js +171 -0
  59. package/dist/chunk-PPBWRKO2.js.map +1 -0
  60. package/dist/chunk-PZ5AY32C.js +10 -0
  61. package/dist/chunk-PZ5AY32C.js.map +1 -0
  62. package/dist/chunk-QO7LAQZH.js +321 -0
  63. package/dist/chunk-QO7LAQZH.js.map +1 -0
  64. package/dist/chunk-QVJ2HCAX.js +225 -0
  65. package/dist/chunk-QVJ2HCAX.js.map +1 -0
  66. package/dist/chunk-RIPHIRPP.js +68 -0
  67. package/dist/chunk-RIPHIRPP.js.map +1 -0
  68. package/dist/chunk-S27S42QY.js +134 -0
  69. package/dist/chunk-S27S42QY.js.map +1 -0
  70. package/dist/chunk-SBCVAC2Z.js +40 -0
  71. package/dist/chunk-SBCVAC2Z.js.map +1 -0
  72. package/dist/chunk-TFJ4MKPH.js +694 -0
  73. package/dist/chunk-TFJ4MKPH.js.map +1 -0
  74. package/dist/chunk-THX3SHYA.js +75 -0
  75. package/dist/chunk-THX3SHYA.js.map +1 -0
  76. package/dist/chunk-UGQSQO5B.js +222 -0
  77. package/dist/chunk-UGQSQO5B.js.map +1 -0
  78. package/dist/chunk-V2UNHGAP.js +26 -0
  79. package/dist/chunk-V2UNHGAP.js.map +1 -0
  80. package/dist/chunk-VGTPQXNQ.js +2790 -0
  81. package/dist/chunk-VGTPQXNQ.js.map +1 -0
  82. package/dist/chunk-VNIHXQ7W.js +194 -0
  83. package/dist/chunk-VNIHXQ7W.js.map +1 -0
  84. package/dist/chunk-WV272MPW.js +31 -0
  85. package/dist/chunk-WV272MPW.js.map +1 -0
  86. package/dist/chunk-X5KKBOUS.js +26 -0
  87. package/dist/chunk-X5KKBOUS.js.map +1 -0
  88. package/dist/chunk-XANPEOJC.js +17 -0
  89. package/dist/chunk-XANPEOJC.js.map +1 -0
  90. package/dist/chunk-XPVQIHAQ.js +83 -0
  91. package/dist/chunk-XPVQIHAQ.js.map +1 -0
  92. package/dist/chunk-ZCINJSS4.js +75 -0
  93. package/dist/chunk-ZCINJSS4.js.map +1 -0
  94. package/dist/community.d.ts +1425 -0
  95. package/dist/community.js +206 -0
  96. package/dist/community.js.map +1 -0
  97. package/dist/config-2GDU7PCK.js +32 -0
  98. package/dist/config-2GDU7PCK.js.map +1 -0
  99. package/dist/context-MNZ4QXPC.js +16 -0
  100. package/dist/context-MNZ4QXPC.js.map +1 -0
  101. package/dist/db-schema.d.ts +4 -0
  102. package/dist/db-schema.js +102 -0
  103. package/dist/db-schema.js.map +1 -0
  104. package/dist/db.d.ts +7 -0
  105. package/dist/db.js +117 -0
  106. package/dist/db.js.map +1 -0
  107. package/dist/digest-SY42GQSU.js +17 -0
  108. package/dist/digest-SY42GQSU.js.map +1 -0
  109. package/dist/errors-5OS3S2J3.js +22 -0
  110. package/dist/errors-5OS3S2J3.js.map +1 -0
  111. package/dist/host-OBOI4MJK.js +51 -0
  112. package/dist/host-OBOI4MJK.js.map +1 -0
  113. package/dist/i18n.d.ts +301 -0
  114. package/dist/i18n.js +68 -0
  115. package/dist/i18n.js.map +1 -0
  116. package/dist/index-B6-_vr_m.d.ts +590 -0
  117. package/dist/index-CY55LC0u.d.ts +4722 -0
  118. package/dist/index-CeiTvwbp.d.ts +168 -0
  119. package/dist/index-XwP1ET8b.d.ts +61 -0
  120. package/dist/index.d.ts +2037 -0
  121. package/dist/index.js +2205 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/job-log-VZXWQUDK.js +24 -0
  124. package/dist/job-log-VZXWQUDK.js.map +1 -0
  125. package/dist/jobs.d.ts +4 -0
  126. package/dist/jobs.js +76 -0
  127. package/dist/jobs.js.map +1 -0
  128. package/dist/logger-DqGaOU_j.d.ts +29 -0
  129. package/dist/logger-S7REWDNE.js +16 -0
  130. package/dist/logger-S7REWDNE.js.map +1 -0
  131. package/dist/media.d.ts +5 -0
  132. package/dist/media.js +41 -0
  133. package/dist/media.js.map +1 -0
  134. package/dist/mentions-2IHFVSHW.js +23 -0
  135. package/dist/mentions-2IHFVSHW.js.map +1 -0
  136. package/dist/mutes-EWAE5FZR.js +21 -0
  137. package/dist/mutes-EWAE5FZR.js.map +1 -0
  138. package/dist/notification-prefs-VPJDU7I6.js +21 -0
  139. package/dist/notification-prefs-VPJDU7I6.js.map +1 -0
  140. package/dist/observability.d.ts +156 -0
  141. package/dist/observability.js +32 -0
  142. package/dist/observability.js.map +1 -0
  143. package/dist/profanity-adapter-NU2JQSLX.js +12 -0
  144. package/dist/profanity-adapter-NU2JQSLX.js.map +1 -0
  145. package/dist/queue-XE5BC75T.js +14 -0
  146. package/dist/queue-XE5BC75T.js.map +1 -0
  147. package/dist/rate-limit.d.ts +99 -0
  148. package/dist/rate-limit.js +14 -0
  149. package/dist/rate-limit.js.map +1 -0
  150. package/dist/registry-XIXDEPVI.js +31 -0
  151. package/dist/registry-XIXDEPVI.js.map +1 -0
  152. package/dist/reputation-JRL2YQHM.js +11 -0
  153. package/dist/reputation-JRL2YQHM.js.map +1 -0
  154. package/dist/routes.d.ts +43 -0
  155. package/dist/routes.js +12 -0
  156. package/dist/routes.js.map +1 -0
  157. package/dist/scheduled-CIQM57HT.js +20 -0
  158. package/dist/scheduled-CIQM57HT.js.map +1 -0
  159. package/dist/seo.d.ts +410 -0
  160. package/dist/seo.js +44 -0
  161. package/dist/seo.js.map +1 -0
  162. package/dist/settings-FOBIESPB.js +17 -0
  163. package/dist/settings-FOBIESPB.js.map +1 -0
  164. package/dist/spam-adapter-XX3G737Z.js +12 -0
  165. package/dist/spam-adapter-XX3G737Z.js.map +1 -0
  166. package/dist/strings-VAE47B2C.js +29 -0
  167. package/dist/strings-VAE47B2C.js.map +1 -0
  168. package/dist/templates-IFVJMCJ6.js +12 -0
  169. package/dist/templates-IFVJMCJ6.js.map +1 -0
  170. package/dist/types-TlsbXS0T.d.ts +871 -0
  171. package/package.json +129 -0
@@ -0,0 +1,2037 @@
1
+ import { c as NpConfig, d as NpCollectionConfig, e as NpAuthUser, f as NpSaveOptions, g as NpSaveResult, h as NpFindOptions, i as NpFindResult, j as NpFieldConfig, a as NpNavItem, k as NpRegisteredTheme, l as NpThemeFieldRequirement, m as NpThemeManifest, n as NpUserRole, o as NpPluginConfig } from './types-TlsbXS0T.js';
2
+ export { p as NpAccessFunction, q as NpArrayField, r as NpBlockConfig, b as NpBlockInstance, s as NpBlocksField, t as NpCheckboxField, u as NpCollapsibleField, v as NpCollectionHook, w as NpDateField, x as NpDocumentStatus, y as NpEditorConfig, z as NpEmailField, A as NpFieldCondition, B as NpFieldValidator, C as NpFindWhere, D as NpFindWhereSystemTokens, E as NpGroupField, F as NpHookPrincipal, G as NpI18nConfig, H as NpImageSize, I as NpJobType, J as NpJsonField, K as NpNumberField, L as NpPluginContext, F as NpPrincipal, M as NpRadioField, O as NpRelationshipField, P as NpResolvedPluginLike, N as NpRichTextContent, Q as NpRichTextField, R as NpRowField, S as NpSelectField, T as NpTextField, U as NpTextareaField, V as NpThemeCollectionRequirement, W as NpUploadConfig, X as NpUploadField, Y as ROLE_HIERARCHY } from './types-TlsbXS0T.js';
3
+ export { ARGON2_OPTIONS, ArcticLikeProvider, ArcticLikeTokens, FromArcticOptions, IssuedOAuthState, NpCapability, NpConsumeMemberEmailVerifyResult, NpConsumeMemberResetResult, NpConsumeResetTokenOptions, NpConsumeResetTokenResult, NpCreateResetTokenOptions, NpIssuedMemberToken, NpIssuedResetToken, NpMemberAuthRow, NpMemberIdentityRow, NpMemberResetRequestResult, NpMemberTokenPayload, NpPasswordResetPurpose, NpResetRequestResult, NpTokenPayload, NpUserIdentityRow, OAuthAuthorizeParams, OAuthExchangeParams, OAuthProfile, OAuthProvider, OAuthStatePayload, ResolveMemberOAuthLoginInput, ResolveMemberOAuthLoginResult, ResolveOAuthLoginInput, ResolveOAuthLoginResult, ResolvedOAuthMember, ResolvedOAuthUser, VerifyOAuthStateResult, authenticated, can, consumeMemberEmailVerifyToken, consumeMemberPasswordReset, consumePasswordResetToken, createMemberEmailVerifyToken, createPasswordResetToken, fromArctic, getMemberFromTokenPayload, getOAuthProvider, hashPassword, invalidateAllMemberSessions, invalidateAllSessions, isAdmin, isEditorOrAbove, isOwnerOrAdmin, isTokenVerificationError, issueOAuthState, listMemberIdentities, listOAuthProviders, listUserIdentities, registerOAuthProvider, requestMemberPasswordReset, requestPasswordReset, resetOAuthProviders, resolveMemberOAuthLogin, resolveOAuthLogin, revokeMemberIdentity, revokeUserIdentity, sha256, signMemberToken, signToken, verifyCsrf, verifyMemberToken, verifyOAuthState, verifyPassword, verifyToken, verifyTokenFull } from './auth.js';
4
+ import { SQL } from 'drizzle-orm';
5
+ import { NpSitemapEntry, NpFeedEntry } from './seo.js';
6
+ export { ArticleJsonLd, ArticleJsonLdInput, BuildAtomFeedOptions, BuildJsonLdContext, BuildSitemapOptions, DEFAULT_SITE_SEO_SETTINGS, DiscussionForumPostingJsonLd, NpPageMetadata, NpPageMetadataInput, NpSeoSettingsPatch, NpSiteSeoSettings, NpSitemapIndexEntry, PersonJsonLd, PersonJsonLdInput, WebSiteJsonLd, buildArticleJsonLd, buildAtomFeed, buildDiscussionForumPostingJsonLd, buildPageMetadata, buildPersonJsonLd, buildSitemap, buildWebSiteJsonLd, getSiteSeoSettings, renderAtomFeed, renderSitemapIndexXml, renderSitemapXml, validateSeoSettingsPatch } from './seo.js';
7
+ export { c as createDbConnection, g as generateDocumentsModule, a as generateDrizzleSchema, b as generateTypeScript, d as getDb, s as setDb } from './index-XwP1ET8b.js';
8
+ import { z, ZodTypeAny } from 'zod';
9
+ export { N as NP_GLOBAL_PLUGIN_SITE_ID, n as npAuditEvents, a as npBanKindEnum, b as npBanScopeEnum, c as npBans, d as npCommentStatusEnum, e as npComments, f as npFollows, g as npJobLogs, h as npMedia, i as npMediaFolders, j as npMediaFoldersRelations, k as npMediaRefs, l as npMediaRefsRelations, m as npMediaRelations, o as npMediaStatusEnum, p as npMemberIdentities, q as npMemberMutes, r as npMemberRoleScopeEnum, s as npMemberRoles, t as npMemberSessions, u as npMemberStatusEnum, v as npMembers, w as npNavigation, x as npNavigationRelations, y as npNotifications, z as npPasswordResetPurposeEnum, A as npPluginStorage, B as npPlugins, C as npReactions, D as npReports, E as npRevisionStatusEnum, F as npRevisions, G as npRevisionsRelations, H as npSessions, I as npSessionsRelations, J as npSettings, K as npSettingsRelations, L as npSiteMemberships, M as npSites, O as npSlugHistory, P as npStringOverrides, Q as npUserOAuthIdentities, R as npUserRoleEnum, S as npUsers, T as npUsersRelations, U as npWorkerHeartbeats } from './index-CY55LC0u.js';
10
+ export { D as DEFAULT_JOB_LOG_RETENTION_MS, L as ListJobLogsOptions, N as NpJobCountOptions, a as NpJobHandler, b as NpJobListOptions, c as NpJobListResult, d as NpJobLogEntry, e as NpJobQueue, f as NpJobState, g as NpJobStateCounts, h as NpJobSummary, i as NpJobsPauseState, j as NpPluginScheduleStats, k as NpReconcileSchedulesResult, l as NpScheduleSummary, m as NpWorkerHealthSummary, n as NpWorkerHeartbeat, P as PAUSE_SYNC_INTERVAL_MS, o as PgBossAdapter, S as SetJobsPauseStateInput, W as WORKER_HEARTBEAT_INTERVAL_MS, p as WORKER_STALE_THRESHOLD_MS, q as configureBuiltinJobContext, r as countAliveWorkers, s as countJobLogs, t as enqueueJob, u as getAllJobHandlers, v as getCurrentJobId, w as getJobHandler, x as getJobQueue, y as getJobsPauseState, z as getOptionalJobQueue, A as listJobLogs, B as listWorkerHealth, C as markWorkerStopped, E as pruneJobLogsOlderThan, F as purgeStaleWorkers, G as recordHeartbeat, H as recordJobLog, I as registerBuiltinHandlers, J as registerJobHandler, K as runInJobContext, M as setJobQueue, O as setJobsPauseState, Q as startProducer, R as startWorker, T as stopProducer, U as stopWorker } from './index-B6-_vr_m.js';
11
+ import { N as NpStorageAdapter, a as NpFileMetadata } from './index-CeiTvwbp.js';
12
+ export { D as DEFAULT_IMAGE_SIZES, b as NpGetMediaUrlOptions, c as NpMediaUploader, d as NpMediaUploaderKindFilter, e as NpMediaVariantName, f as NpProcessedImageResult, g as NpProcessedImageVariant, h as cleanupDeletedMedia, i as deleteMedia, j as extractMediaIds, k as getMediaById, l as getMediaUrl, m as getStorageAdapter, n as listMedia, p as processImage, o as processMediaImage, s as setStorageAdapter, u as uploadMedia } from './index-CeiTvwbp.js';
13
+ import { ReadableStream } from 'node:stream/web';
14
+ export { NpLocaleDirection, NpResolveLocaleInput, NpResolveLocaleResult, NpStringOverrideRow, NpTranslationBundle, NpTranslationParams, addStrings, clearStringOverrideCacheForSite, deleteStringOverride, formatDate, formatNumber, formatRelativeTime, getAllStrings, getCurrentLocale, getI18nConfig, getLocaleDirection, getStringOverride, getStringOverridesForSite, getStrings, listStringOverridesForSite, loadStringOverridesForSite, resetI18nConfig, resetIntlFormatterCache, resetStringOverrideCache, resetStrings, resetTranslationCache, resolveLocale, setI18nConfig, setStringOverride, setStrings, t, tSync } from './i18n.js';
15
+ import { NodePgDatabase } from 'drizzle-orm/node-postgres';
16
+ export { N as NpLogLevel, a as NpLogger, c as consoleLogger, g as getLogger, b as getScopedLogger, r as resetLogger, s as setLogger } from './logger-DqGaOU_j.js';
17
+ export { NpErrorReportContext, NpErrorReporter, NpStartupSafetyInput, getErrorReporter, noopErrorReporter, reportError, resetErrorReporter, setErrorReporter, verifyStartupSafety } from './observability.js';
18
+ export { InMemoryRateLimiter, NpRateLimitDecision, NpRateLimiterAdapter, getOptionalRateLimiter, getRateLimiter, setRateLimiter } from './rate-limit.js';
19
+ export { AuditActor, AuditActorKind, AuditEventRow, BanKind, BanScope, BuildDigestEmailInput, CommentStatus, CommunityCapability, CommunityRoleDefinition, CommunityScope, CreateNotificationInput, DEFAULT_COMMUNITY_SETTINGS, DEFAULT_REACTION_KINDS, FanOutMentionsInput, FileReportInput, GrantMemberRoleInput, IssueBanInput, ListAuditOptions, ListMutesOptions, ListNotificationsOptions, ListReportsOptions, ListReportsResult, MENTION_HANDLE_RE, MarkReadInput, MemberAction, MemberCanTarget, MuteMemberInput, NpBanRow, NpCommentCreateInput, NpCommentDeleteInput, NpCommentHideInput, NpCommentListOptions, NpCommentListResult, NpCommentRestoreInput, NpCommentRow, NpCommentSort, NpCommentUpdateInput, NpCommunitySettings, NpDigestCadence, NpDigestEmailContent, NpDigestNotificationSummary, NpFollowInput, NpFollowRow, NpMemberMuteRow, NpMemberMuteSummary, NpMemberProfile, NpMemberPurgeResult, NpMemberRoleGrantRow, NpMemberUploadQuota, NpMentionTarget, NpNotificationKindMeta, NpNotificationListResult, NpNotificationPrefs, NpNotificationRow, NpProfanityAdapter, NpProfanityCheckContext, NpProfanityVerdict, NpProfanityVerdictKind, NpReactToInput, NpReactionRow, NpReportRow, NpReputationAdapter, NpReputationEvent, NpSpamAdapter, NpSpamCheckContext, NpSpamVerdict, NpSpamVerdictKind, Principal, RecordAuditEventInput, ResolveReportInput, RevokeBanInput, RevokeMemberRoleInput, RunDigestSweepInput, RunDigestSweepResult, SetMemberNotificationPrefsInput, addReaction, applyReputation, assertNotBanned, assertOwnsNotification, assertReactableExists, buildDigestEmail, countReactions, createComment, createNotification, deleteComment, extractMentionHandles, extractMentionHandlesFromDocData, extractMentionHandlesFromRichText, fanOutMentionNotifications, fileReport, follow, getCommunityRole, getCommunitySettings, getMemberNotificationPrefs, getMemberProfile, getMemberProfiles, getMutedTargetIds, getProfanityAdapter, getReputationAdapter, getSpamAdapter, grantMemberRole, hideComment, isFollowing, isMuted, isNotificationKindEnabled, issueBan, listAuditEvents, listBansForMember, listComments, listCommunityRoles, listFollowing, listMemberReactions, listMemberRoleGrants, listMutes, listNotificationKinds, listNotifications, listReports, markAllNotificationsRead, markNotificationsRead, memberCan, muteMember, principalCan, purgeMemberContent, recordAuditEvent, recordDigestSent, registerCommunityRole, registerNotificationKind, removeReaction, renderCommentMarkdown, resetCommunityRoles, resetProfanityAdapter, resetReputationAdapter, resetSpamAdapter, resolveMentionedMembers, resolveReport, restoreComment, revokeBan, revokeMemberRole, runDigestSweep, setMemberNotificationPrefs, setProfanityAdapter, setReputationAdapter, setSpamAdapter, staffDeleteComment, staffHideComment, staffRestoreComment, unfollow, unmuteMember, unreadNotificationCount, unresolvedReportCount, updateComment, updateCommunitySettings, validateCommunitySettingsPatch, withMemberWrite } from './community.js';
20
+ export { NpCustomRoute, clearCustomRoutes, getCustomRoutes, registerCustomRoute } from './routes.js';
21
+ import '@node-rs/argon2';
22
+ import 'pg';
23
+ import 'drizzle-orm/pg-core';
24
+ import 'pg-boss';
25
+
26
+ /**
27
+ * Validates the project's NpConfig against the declarative schema and returns
28
+ * it unchanged on success. Catches common mistakes (bad collection slug,
29
+ * missing auth.secret, malformed storage adapter, etc.) at module-eval time
30
+ * with a clear message instead of a cryptic runtime failure once the app
31
+ * tries to boot.
32
+ *
33
+ * The most common boot trip-up by far is "auth.secret" / "site.url" /
34
+ * "db.connectionString" missing on a fresh install. We translate Zod's raw
35
+ * `String must contain at least 1 character` style messages into actionable
36
+ * "set NP_SECRET in .env, or run `pnpm run setup`" hints so the new operator
37
+ * isn't googling Zod path strings.
38
+ *
39
+ * Unknown plugin entries are accepted here — the plugin loader does the
40
+ * deeper validation of manifests against @nexpress/plugin-sdk.
41
+ */
42
+ declare function defineConfig(config: NpConfig): NpConfig;
43
+
44
+ declare function defineCollection(config: NpCollectionConfig): NpCollectionConfig;
45
+
46
+ /**
47
+ * Public error-code contract (#290).
48
+ *
49
+ * `NpError.code` is the machine-readable string the API surface
50
+ * sends to clients (`response.error.code`). The moment a client
51
+ * branches on `error.code === "VALIDATION_ERROR"` it becomes
52
+ * part of the public contract — adding a typo'd code or
53
+ * renaming an existing one breaks every integration.
54
+ *
55
+ * `NpErrorCode` is the union of every code the framework
56
+ * currently emits. Adding a new code requires extending this
57
+ * union deliberately — no more "casual" string adoption that
58
+ * accumulates over a year of PRs.
59
+ *
60
+ * The `NpError` constructor still accepts plain `string` so
61
+ * out-of-tree plugins that throw their own codes keep working;
62
+ * internal code paths use the union to get IntelliSense and
63
+ * catch typos at compile time. The `(string & {})` trick on
64
+ * the public type keeps editor completion narrow without
65
+ * locking the runtime contract.
66
+ *
67
+ * **Stability**: codes follow semver. Renames or removals are
68
+ * major-bump only. New codes may land in minor versions. See
69
+ * `docs/api-error-codes.md` for the catalogue an operator /
70
+ * client team should rely on.
71
+ */
72
+ type NpErrorCode = "FORBIDDEN" | "NOT_FOUND" | "VALIDATION_ERROR" | "UNAUTHORIZED" | "CONFLICT" | "RATE_LIMITED" | "TOO_MANY_REQUESTS" | "INVALID_URL" | "EMAIL_ADAPTER_MISSING_DEPENDENCY" | "EMAIL_DELIVERY_FAILED" | "SITE_CONTEXT_MISSING" | "INTERNAL_ERROR";
73
+ /**
74
+ * The constructor signature accepts the union *or* an arbitrary
75
+ * string. Inside the codebase, passing a literal that isn't in
76
+ * the union triggers a TypeScript error in strict editors
77
+ * (autocompletion narrows to `NpErrorCode`). External plugins
78
+ * authoring their own codes keep working — they just won't get
79
+ * the autocomplete win.
80
+ */
81
+ type NpErrorCodeInput = NpErrorCode | (string & Record<never, never>);
82
+ declare class NpError extends Error {
83
+ readonly code: NpErrorCodeInput;
84
+ readonly statusCode: number;
85
+ constructor(message: string, code: NpErrorCodeInput, statusCode?: number);
86
+ }
87
+ declare class NpForbiddenError extends NpError {
88
+ constructor(collection: string, operation: string);
89
+ }
90
+ declare class NpNotFoundError extends NpError {
91
+ constructor(collection: string, id: string);
92
+ }
93
+ declare class NpValidationError extends NpError {
94
+ readonly errors: Array<{
95
+ field: string;
96
+ message: string;
97
+ }>;
98
+ constructor(message: string, errors: Array<{
99
+ field: string;
100
+ message: string;
101
+ }>);
102
+ }
103
+ declare class NpAuthError extends NpError {
104
+ constructor(message?: string);
105
+ }
106
+ declare class NpConflictError extends NpError {
107
+ constructor(message: string);
108
+ }
109
+ /**
110
+ * Per-actor rate limit / quota exceeded. Distinct from
111
+ * `NpValidationError` because the request shape was valid — the
112
+ * server is rejecting it on policy grounds. The 429 status lets
113
+ * client UIs recognize the case and surface a "you've hit your
114
+ * daily limit" message rather than a generic validation error.
115
+ */
116
+ declare class NpRateLimitError extends NpError {
117
+ constructor(message: string);
118
+ }
119
+ /**
120
+ * No site context resolved when one was required (#272). Thrown
121
+ * by `requireSiteId()` on write paths. 500 because this is a
122
+ * server-side wiring bug — the user request was well-formed,
123
+ * but the framework couldn't tell which tenant to write to.
124
+ * Promoted from a plain `Error` to a real `NpError` subclass so
125
+ * the API layer surfaces it as a uniform error envelope and
126
+ * clients can branch on the stable `SITE_CONTEXT_MISSING` code.
127
+ */
128
+ declare class NpSiteContextMissingError extends NpError {
129
+ constructor(message: string);
130
+ }
131
+
132
+ /**
133
+ * Plain-text concatenation of every searchable field. Used by
134
+ * the moderation pipeline (spam/profanity adapters need the
135
+ * full text, weights don't matter there).
136
+ */
137
+ declare function buildSearchVector(config: NpCollectionConfig, data: Record<string, unknown>): string;
138
+ /**
139
+ * Phase 10.7 — split searchable fields into Postgres tsvector
140
+ * weight buckets so title-like matches outrank body matches at
141
+ * query time.
142
+ *
143
+ * Convention (no per-field opt-in to keep collections
144
+ * declarations terse):
145
+ * - field.name === "title" or "name" → weight A
146
+ * - other text / textarea / email → weight B
147
+ * - richText → weight C
148
+ *
149
+ * Sites that want different weights can name their primary
150
+ * field "title" and the framework picks the right bucket
151
+ * automatically. (A future revision can add `field.search.weight`
152
+ * for explicit control.)
153
+ *
154
+ * Postgres ts_rank() applies the default weight scale
155
+ * { D: 0.1, C: 0.2, B: 0.4, A: 1.0 }, so an A-weighted match
156
+ * scores ~10× a D-weighted one — meaningful boost for titles.
157
+ */
158
+ interface NpSearchVectorParts {
159
+ /** Title-like fields. Highest rank weight. */
160
+ a: string;
161
+ /** Body fields (text/textarea/email). */
162
+ b: string;
163
+ /** Rich-text body. */
164
+ c: string;
165
+ /** Reserved for future categorization (tags, slugs, etc.). */
166
+ d: string;
167
+ }
168
+ declare function buildSearchVectorParts(config: NpCollectionConfig, data: Record<string, unknown>): NpSearchVectorParts;
169
+ /**
170
+ * Build the weighted-tsvector SQL fragment that the pipeline
171
+ * binds to `searchVector` on insert/update. Each non-empty
172
+ * bucket becomes `setweight(to_tsvector('english', $bucket), '<W>')`;
173
+ * the buckets are concatenated with `||`. Empty buckets are
174
+ * skipped so the resulting expression is always non-trivial.
175
+ *
176
+ * If every bucket is empty (collection has no text fields, or
177
+ * every text field is null on this row), returns an empty
178
+ * tsvector cast — Postgres accepts this as a valid empty
179
+ * vector value.
180
+ */
181
+ declare function buildWeightedSearchVectorSql(config: NpCollectionConfig, data: Record<string, unknown>): SQL;
182
+
183
+ interface CollectionRegistration {
184
+ config: NpCollectionConfig;
185
+ table: unknown;
186
+ childTables?: Record<string, unknown>;
187
+ joinTables?: Record<string, unknown>;
188
+ }
189
+ declare function registerCollection(slug: string, table: unknown, config: NpCollectionConfig, opts?: {
190
+ childTables?: Record<string, unknown>;
191
+ joinTables?: Record<string, unknown>;
192
+ }): void;
193
+ declare function getCollectionConfig(slug: string): NpCollectionConfig;
194
+ declare function getCollectionTable(slug: string): unknown;
195
+ declare function getCollectionRegistration(slug: string): CollectionRegistration;
196
+ declare function getAllCollectionSlugs(): string[];
197
+
198
+ declare function saveDocument(collection: string, docId: string | null, data: Record<string, unknown>, user: NpAuthUser, options?: NpSaveOptions): Promise<NpSaveResult>;
199
+ /**
200
+ * Member-side document create. Only valid when
201
+ * `config.community?.memberWrite?.create === true`. Assumes the API
202
+ * layer has already authenticated the member (and that the cookie's
203
+ * status was checked) — this function adds:
204
+ * - the per-collection opt-in gate, and
205
+ * - `assertNotBanned(memberId)` (site-wide; per-collection bans
206
+ * resolve to the same site scope)
207
+ *
208
+ * Fires the `document.created` reputation event after a successful
209
+ * write so adapters can credit the author the same way they credit
210
+ * comments / reactions. Member-side update / delete live in
211
+ * `updateMemberDocument` / `deleteMemberDocument` below.
212
+ */
213
+ /**
214
+ * Member-side document update. Only valid when
215
+ * `config.community?.memberWrite?.update === true` AND the existing
216
+ * row's `member_author_id` matches the caller. Members can NOT
217
+ * change `_status` via update — `options.status` is stripped here
218
+ * so a forged body field can't bypass the moderation pipeline. The
219
+ * author column itself is also locked — see saveDocumentImpl.
220
+ */
221
+ declare function updateMemberDocument(collection: string, docId: string, data: Record<string, unknown>, memberId: string, options?: NpSaveOptions): Promise<NpSaveResult>;
222
+ declare function createMemberDocument(collection: string, data: Record<string, unknown>, memberId: string, options?: NpSaveOptions): Promise<NpSaveResult>;
223
+ /**
224
+ * Persist an in-flight editor snapshot as a revision **without** touching
225
+ * the main document row. Designed for client-side autosave loops: the
226
+ * editor sends every few seconds while the user types, and a crash mid-
227
+ * edit can be recovered by restoring the latest autosave revision.
228
+ *
229
+ * - Requires `versions.drafts` to be enabled on the collection.
230
+ * - Optionally gated by `versions.drafts.autosave === true` (when
231
+ * `versions` is the object form). Throws `NpValidationError` otherwise
232
+ * so the API can return a tidy 4xx instead of silently writing.
233
+ * - Skips the full zod validation that `saveDocument` runs — autosave
234
+ * payloads may be temporarily incomplete (the user is still typing).
235
+ * - Skips hooks, jobs, and revalidation: nothing is "saved" yet.
236
+ * - Deduplicates against the most recent autosave: if the snapshot is
237
+ * byte-identical to the previous autosave row, returns the existing
238
+ * summary instead of writing a new one. Avoids unbounded autosave
239
+ * rows during long idle edit sessions where react-hook-form fires
240
+ * spurious "change" events.
241
+ */
242
+ declare function autosaveRevision(collection: string, documentId: string, data: Record<string, unknown>, user: NpAuthUser): Promise<{
243
+ id: string;
244
+ version: number;
245
+ status: "autosave";
246
+ createdAt: Date;
247
+ reused: boolean;
248
+ }>;
249
+ declare function deleteDocument(collection: string, docId: string, user: NpAuthUser): Promise<void>;
250
+ /**
251
+ * Member-side delete. Owner-only — the existing row's
252
+ * `member_author_id` must match the caller. Fires
253
+ * `document.deleted` reputation event so adapters can debit the
254
+ * author the same way `comment.deleted` debits commenters.
255
+ *
256
+ * The reputation event is gated on the row's status at delete
257
+ * time: only `published` docs ever earned a `document.created`
258
+ * credit (the create path withholds it for pending rows; promote
259
+ * later backfills the credit). Issuing a `document.deleted`
260
+ * debit for a row that was never credited would drive the
261
+ * member negative for deleting their own not-yet-visible
262
+ * content (#126). The audit row is unconditional — the operator
263
+ * still wants to see "member deleted X".
264
+ */
265
+ declare function deleteMemberDocument(collection: string, docId: string, memberId: string): Promise<void>;
266
+ /**
267
+ * Staff promotion of a member-authored `pending` row to `published`
268
+ * (Phase 9.7d). Closes the loop on the 9.7c moderation gate:
269
+ * - the row's status flips to `published` (visible on the public
270
+ * site immediately)
271
+ * - the deferred `document.created` reputation event fires now,
272
+ * crediting the author for content that was held in review
273
+ * (mirrors how a comment promoted from `pending` would, in a
274
+ * hypothetical comment-promote API — not implemented yet)
275
+ * - audit log records `document.promote` with the staff actor
276
+ * and the original member author in the payload
277
+ *
278
+ * Guards:
279
+ * - 404 if the row doesn't exist
280
+ * - 400 (validation) if the row isn't currently `pending`
281
+ * - 400 (validation) if the row isn't member-authored
282
+ * (`member_author_id` is null) — staff drafts use the standard
283
+ * edit path
284
+ *
285
+ * Idempotence: a second promote on an already-`published` row 400s
286
+ * rather than silently no-op'ing — the audit trail and reputation
287
+ * backfill must run exactly once per row.
288
+ */
289
+ declare function promoteMemberDocument(collection: string, docId: string, staffUserId: string): Promise<NpSaveResult>;
290
+ declare function findDocuments<T extends object = Record<string, unknown>>(collection: string, options: NpFindOptions<NoInfer<T>>, user?: NpAuthUser): Promise<NpFindResult<T>>;
291
+ declare function getDocumentById<T extends object = Record<string, unknown>>(collection: string, id: string, user?: NpAuthUser): Promise<T | null>;
292
+
293
+ type NpRevisionStatus = "draft" | "published" | "autosave";
294
+ interface NpRevisionSummary {
295
+ id: string;
296
+ collection: string;
297
+ documentId: string;
298
+ version: number;
299
+ status: NpRevisionStatus;
300
+ changedFields: string[];
301
+ authorId: string | null;
302
+ createdAt: Date;
303
+ }
304
+ interface NpRevision extends NpRevisionSummary {
305
+ snapshot: Record<string, unknown>;
306
+ }
307
+ interface NpRevisionListOptions {
308
+ limit?: number;
309
+ offset?: number;
310
+ }
311
+ interface NpRevisionListResult {
312
+ revisions: NpRevisionSummary[];
313
+ total: number;
314
+ }
315
+ declare function listRevisions(collection: string, documentId: string, options?: NpRevisionListOptions, user?: NpAuthUser | null): Promise<NpRevisionListResult>;
316
+ declare function getRevision(collection: string, documentId: string, revisionId: string, user?: NpAuthUser | null): Promise<NpRevision>;
317
+ declare function restoreRevision(collection: string, documentId: string, revisionId: string, user: NpAuthUser): Promise<NpSaveResult>;
318
+
319
+ interface PublishScheduledResult {
320
+ published: number;
321
+ byCollection: Record<string, string[]>;
322
+ }
323
+ /**
324
+ * Scans every registered collection that has a `publishedAt` date field,
325
+ * flips rows with `status="scheduled"` whose `publishedAt <= now` to
326
+ * `status="published"`, and fires `content:afterPublish` for each.
327
+ *
328
+ * Safe to call repeatedly (idempotent once a doc is published) and cheap —
329
+ * each UPDATE runs against an indexed status column and no-ops when empty.
330
+ */
331
+ declare function publishScheduledDocuments(atTime?: Date): Promise<PublishScheduledResult>;
332
+
333
+ /**
334
+ * Cross-collection pending queue (Phase 9.7e). Lists every
335
+ * member-authored row that landed `status = "pending"` — the ones
336
+ * waiting on a staff promote (9.7d) or staff delete. Only collections
337
+ * that opt into `community.memberWrite.create` are scanned; the rest
338
+ * either have no member-author column at all or aren't part of the
339
+ * member-write surface.
340
+ *
341
+ * Phase 12.11 — replaced the v1 fan-out (one round trip per
342
+ * collection, no SQL `LIMIT`, JS-side merge + page) with a single
343
+ * `UNION ALL` query whose outer `LIMIT/OFFSET` runs at the database.
344
+ * The per-collection `(status, member_author_id)` is still
345
+ * status-indexed (`np_c_<slug>_status_idx`), so each branch of the
346
+ * union narrows fast; the database does the cross-collection
347
+ * `ORDER BY created_at DESC` + paging. A site with dozens of
348
+ * member-writable surfaces no longer fans out N+2 round trips, and
349
+ * an unattended collection accumulating thousands of pendings can't
350
+ * blow heap by being read fully into memory.
351
+ */
352
+ interface NpPendingDocSummary {
353
+ id: string;
354
+ collectionSlug: string;
355
+ /** From the doc's `title` field, when it has one. Never empty. */
356
+ title: string;
357
+ slug: string | null;
358
+ status: "pending";
359
+ createdAt: Date;
360
+ /**
361
+ * Resolved from `np_members` via `member_author_id`. Null when the
362
+ * member was deleted after authoring (FK is `ON DELETE SET NULL`)
363
+ * — mods see the audit trail for the original actor in that case.
364
+ */
365
+ memberAuthor: {
366
+ id: string;
367
+ handle: string;
368
+ displayName: string;
369
+ } | null;
370
+ }
371
+ interface NpListPendingDocsOptions {
372
+ /** Restrict to one collection. Useful for per-collection queue
373
+ * pages; omit for the global queue. */
374
+ collectionSlug?: string;
375
+ limit?: number;
376
+ offset?: number;
377
+ }
378
+ interface NpListPendingDocsResult {
379
+ docs: NpPendingDocSummary[];
380
+ totalDocs: number;
381
+ }
382
+ declare function listPendingMemberDocs(options?: NpListPendingDocsOptions): Promise<NpListPendingDocsResult>;
383
+
384
+ interface SearchCollectionsOptions {
385
+ q: string;
386
+ collections?: string[];
387
+ limit?: number;
388
+ offset?: number;
389
+ /**
390
+ * Extra where-filter applied on top of the default `{ status: "published" }`
391
+ * for each collection. Pass `{}` to disable the status filter (caller should
392
+ * only do this for authenticated admin contexts).
393
+ */
394
+ where?: Record<string, unknown>;
395
+ /**
396
+ * Phase 12.4 — restrict i18n collections to one locale. Non-
397
+ * i18n collections ignore this (no `locale` column to match).
398
+ * Public site search reads this from the URL's locale prefix
399
+ * so visitors browsing in `/ko/` only see Korean hits.
400
+ */
401
+ locale?: string;
402
+ }
403
+ interface SearchResultItem {
404
+ collection: string;
405
+ doc: Record<string, unknown>;
406
+ }
407
+ interface SearchResult {
408
+ results: SearchResultItem[];
409
+ total: number;
410
+ perCollection: Record<string, number>;
411
+ }
412
+ /**
413
+ * Cross-collection full-text search using the existing `search_vector` column
414
+ * on each collection table. Built on top of `findDocuments` so it inherits
415
+ * the ts_rank ordering, access-control read checks, and pagination.
416
+ *
417
+ * Results are merged in per-collection slug order; for an MVP the within-
418
+ * collection ranking is authoritative. A future version can do a UNION across
419
+ * tables if global ranking becomes a priority.
420
+ */
421
+ declare function searchCollections(opts: SearchCollectionsOptions): Promise<SearchResult>;
422
+ interface ReindexResult {
423
+ collection: string;
424
+ processed: number;
425
+ }
426
+ /**
427
+ * Rebuilds the `search_vector` column for every row in a collection. Useful
428
+ * after bulk imports or for recovering from corrupted vectors. Idempotent —
429
+ * safe to run against a live collection while writes continue.
430
+ */
431
+ declare function reindexCollection(slug: string): Promise<ReindexResult>;
432
+
433
+ /**
434
+ * Phase 10.6 — pluggable search adapter. Same shape as the
435
+ * spam / profanity / reputation adapters from Phase 9.6+:
436
+ * a single global slot, opt-in via `setSearchAdapter`, default
437
+ * is "no adapter" which keeps the existing pg `tsvector` path
438
+ * authoritative.
439
+ *
440
+ * Plugins implement this interface to delegate search to an
441
+ * external engine — Algolia, Meilisearch, OpenSearch, etc.
442
+ * The plugin is responsible for KEEPING the engine's index
443
+ * fresh; that's done by subscribing to the `content:afterCreate` /
444
+ * `:afterUpdate` / `:afterDelete` hooks the framework already
445
+ * fires (9.7o made those Principal-aware) — no new plumbing
446
+ * in the pipeline. The adapter ONLY handles the read path.
447
+ *
448
+ * Returning `null` / `undefined` from `search()` means "I don't
449
+ * know how to handle this query; fall through to the pg
450
+ * default." Useful when an adapter only indexes certain
451
+ * collections, or wants to defer to pg under specific
452
+ * conditions (e.g. very short queries).
453
+ *
454
+ * Errors thrown by the adapter are fail-open: the framework
455
+ * logs a warning and falls back to pg. Sites that want
456
+ * fail-closed wrap their adapter in try/catch and return
457
+ * `null` on error.
458
+ */
459
+ interface NpSearchAdapterContext {
460
+ /** Trimmed query string (already non-empty by the time this runs). */
461
+ q: string;
462
+ /** Subset of collection slugs the caller asked to search. */
463
+ collections?: string[];
464
+ /** Page size cap, already normalized. */
465
+ limit: number;
466
+ /** Skip count, already normalized. */
467
+ offset: number;
468
+ /**
469
+ * Phase 12.4 — locale to scope results to. When set, the
470
+ * framework expects only docs in this locale (for i18n
471
+ * collections) plus all docs from non-i18n collections. The
472
+ * default pg path applies a `locale = $1` filter on i18n
473
+ * collections; external adapters typically rebuild the index
474
+ * with one document per (sourceId, locale) and filter on the
475
+ * locale field. Adapters that don't support per-locale
476
+ * filtering can return `null` to fall through to pg.
477
+ */
478
+ locale?: string;
479
+ }
480
+ interface NpSearchAdapter {
481
+ /**
482
+ * Implementation hook. Return a `SearchResult` to override the
483
+ * default pg tsvector path, or `null` / `undefined` to fall
484
+ * through. Throws are fail-open (logged + treated as null).
485
+ */
486
+ search(ctx: NpSearchAdapterContext): Promise<SearchResult | null | undefined> | SearchResult | null | undefined;
487
+ }
488
+ /**
489
+ * Replace the global search adapter. Call once at app boot,
490
+ * typically from a plugin's `setup()`. The framework holds at
491
+ * most one adapter; sites that want to layer multiple engines
492
+ * (e.g. blog → Algolia, products → Meilisearch) compose them
493
+ * inside a single adapter and dispatch on `ctx.collections`.
494
+ */
495
+ declare function setSearchAdapter(adapter: NpSearchAdapter): void;
496
+ declare function getSearchAdapter(): NpSearchAdapter | null;
497
+ /** Reset to no adapter. Tests use this between cases. */
498
+ declare function resetSearchAdapter(): void;
499
+
500
+ declare function buildZodSchema(fields: NpFieldConfig[]): z.ZodObject<Record<string, z.ZodTypeAny>>;
501
+ declare function getCollectionZodSchema(config: NpCollectionConfig): z.ZodSchema;
502
+
503
+ interface TranslationRow {
504
+ id: string;
505
+ locale: string;
506
+ slug: string;
507
+ status: string;
508
+ title?: unknown;
509
+ updatedAt?: Date | string | null;
510
+ translationGroupId: string;
511
+ }
512
+ /**
513
+ * Phase 12.3 — list every locale variant linked to the given
514
+ * document. The `translationGroupId` keys the sibling lookup;
515
+ * the originating row is included so callers can render a
516
+ * "current row + others" tab strip without a separate query.
517
+ *
518
+ * Returns rows in the locale order from `getI18nConfig()` so
519
+ * the UI's tab order matches the configured locale list rather
520
+ * than insertion order.
521
+ */
522
+ declare function findTranslations(collection: string, docId: string): Promise<TranslationRow[]>;
523
+ /**
524
+ * Phase 12.3 — copy a doc into a new locale. The source row's
525
+ * data is reused minus `id` / `slug` / `locale` / status; the
526
+ * new row gets the source's `translationGroupId` so the two
527
+ * link as siblings. The pipeline's locale validation runs as
528
+ * usual (rejects unknown locales).
529
+ *
530
+ * The copy lands as a draft (`status: "draft"`) — translators
531
+ * almost always want to review before publishing. Promote
532
+ * normally via the existing publish flow once the translation
533
+ * is ready.
534
+ *
535
+ * Slug is intentionally NOT copied: `applySlugField` will
536
+ * derive a fresh one from the title (or whatever the configured
537
+ * `useField` is) so the (locale, slug) unique index doesn't
538
+ * collide if the source already used the slug in that locale.
539
+ * Callers can override post-create via the regular update path.
540
+ */
541
+ declare function createTranslation(collection: string, sourceDocId: string, targetLocale: string, user: NpAuthUser): Promise<{
542
+ id: string;
543
+ }>;
544
+ /**
545
+ * Phase 12.6 — translation completeness snapshot for the
546
+ * admin Locales tab.
547
+ *
548
+ * Walks every i18n-enabled collection and counts:
549
+ * - `totalGroups` — distinct `translation_group_id` values
550
+ * (one per "logical document"; if the source has 5 base
551
+ * pages, that's 5 groups regardless of locale spread)
552
+ * - `counts[locale]` — actual rows per locale
553
+ * - `missing[locale]` — `totalGroups - counts[locale]`,
554
+ * i.e. how many groups still need this locale
555
+ *
556
+ * Returns `null` when i18n isn't configured. Non-i18n
557
+ * collections are silently skipped — they don't have the
558
+ * `translation_group_id` column and the dashboard isn't
559
+ * meaningful for them.
560
+ *
561
+ * One SQL round-trip per i18n collection (two GROUP BYs in a
562
+ * single query). For 1–2 i18n collections this is well under
563
+ * the cost of the existing dashboard widgets.
564
+ */
565
+ interface NpTranslationProgressLocaleStats {
566
+ count: number;
567
+ missing: number;
568
+ }
569
+ interface NpCollectionTranslationProgress {
570
+ collection: string;
571
+ totalGroups: number;
572
+ perLocale: Record<string, NpTranslationProgressLocaleStats>;
573
+ }
574
+ interface NpTranslationProgress {
575
+ defaultLocale: string;
576
+ locales: string[];
577
+ collections: NpCollectionTranslationProgress[];
578
+ }
579
+ declare function getTranslationProgress(): Promise<NpTranslationProgress | null>;
580
+
581
+ interface NpThemeColors {
582
+ primary: string;
583
+ primaryForeground: string;
584
+ background: string;
585
+ foreground: string;
586
+ muted: string;
587
+ mutedForeground: string;
588
+ border: string;
589
+ card: string;
590
+ cardForeground: string;
591
+ accent: string;
592
+ accentForeground: string;
593
+ destructive: string;
594
+ destructiveForeground: string;
595
+ }
596
+ interface NpThemeTypography {
597
+ fontHeading: string;
598
+ fontBody: string;
599
+ fontMono: string;
600
+ fontSizeBase: string;
601
+ lineHeight: string;
602
+ fontSizeSm: string;
603
+ fontSizeLg: string;
604
+ fontSizeXl: string;
605
+ fontSize2xl: string;
606
+ fontSize3xl: string;
607
+ fontSize4xl: string;
608
+ }
609
+ interface NpThemeShape {
610
+ radiusSm: string;
611
+ radiusMd: string;
612
+ radiusLg: string;
613
+ radiusFull: string;
614
+ shadowSm: string;
615
+ shadowMd: string;
616
+ shadowLg: string;
617
+ }
618
+ interface NpThemeTokens {
619
+ colors: NpThemeColors;
620
+ typography: NpThemeTypography;
621
+ shape: NpThemeShape;
622
+ }
623
+ /**
624
+ * Author-facing partial token shape. Themes that override only a
625
+ * few colors / fonts / radii ship one of these via `defineTheme`'s
626
+ * `impl.tokens`. Each sub-tree is `Partial<...>` so a theme that
627
+ * sets only `colors.primary` doesn't have to copy the rest of
628
+ * `colors` from `DEFAULT_THEME`. The runtime merger
629
+ * (`getTheme()` in `content/helpers.ts`) layers an overlay onto
630
+ * `DEFAULT_THEME` field-by-field.
631
+ */
632
+ interface NpThemeTokensOverlay {
633
+ colors?: Partial<NpThemeColors>;
634
+ typography?: Partial<NpThemeTypography>;
635
+ shape?: Partial<NpThemeShape>;
636
+ }
637
+
638
+ /**
639
+ * Resolves the effective theme tokens for the current site by
640
+ * layering three sources, last-writer-wins:
641
+ *
642
+ * 1. `DEFAULT_THEME` — framework baseline (always full).
643
+ * 2. The active theme's `impl.tokens` — author-shipped defaults
644
+ * that distinguish a theme from the framework baseline (e.g.
645
+ * magazine's warm cream palette, portfolio's dark surface).
646
+ * 3. The DB row in `np_settings.theme` — admin overrides via the
647
+ * theme settings tab.
648
+ *
649
+ * Each layer is `NpThemeTokensOverlay` (sub-tree-Partial) — themes
650
+ * only declare the keys they care about, admins only save deltas
651
+ * they edit. The merge ensures every emitted token has a value.
652
+ *
653
+ * This is the canonical token resolver — `apps/web`'s preview
654
+ * route calls it directly so the page-builder iframe matches what
655
+ * the public render produces for the same active theme.
656
+ */
657
+ declare function getTheme(): Promise<NpThemeTokens>;
658
+ declare function getNavigation(location?: string): Promise<NpNavItem[]>;
659
+ declare function getPageBySlug(slug: string, options?: {
660
+ draft?: boolean;
661
+ locale?: string;
662
+ }): Promise<Record<string, unknown> | null>;
663
+ declare function getPostBySlug(slug: string, options?: {
664
+ draft?: boolean;
665
+ }): Promise<Record<string, unknown> | null>;
666
+ declare function findPosts(options: NpFindOptions, user?: NpAuthUser): Promise<NpFindResult>;
667
+ declare function getAllPageSlugs(): Promise<string[]>;
668
+ declare function findSlugRedirect(collection: string, oldSlug: string): Promise<string | null>;
669
+ declare function getSetting<T = unknown>(key: string): Promise<T | null>;
670
+
671
+ declare const npConfigSchema: z.ZodObject<{
672
+ site: z.ZodObject<{
673
+ name: z.ZodString;
674
+ url: z.ZodString;
675
+ }, z.core.$strip>;
676
+ db: z.ZodObject<{
677
+ connectionString: z.ZodString;
678
+ pool: z.ZodOptional<z.ZodObject<{
679
+ max: z.ZodOptional<z.ZodNumber>;
680
+ }, z.core.$strip>>;
681
+ }, z.core.$strip>;
682
+ storage: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
683
+ adapter: z.ZodLiteral<"local">;
684
+ local: z.ZodObject<{
685
+ directory: z.ZodString;
686
+ baseUrl: z.ZodString;
687
+ }, z.core.$strip>;
688
+ }, z.core.$strip>, z.ZodObject<{
689
+ adapter: z.ZodLiteral<"s3">;
690
+ s3: z.ZodObject<{
691
+ bucket: z.ZodString;
692
+ region: z.ZodString;
693
+ endpoint: z.ZodOptional<z.ZodString>;
694
+ }, z.core.$strip>;
695
+ }, z.core.$strip>], "adapter">>;
696
+ collections: z.ZodArray<z.ZodLazy<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>>;
697
+ blocks: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
698
+ editor: z.ZodOptional<z.ZodObject<{
699
+ features: z.ZodOptional<z.ZodArray<z.ZodString>>;
700
+ }, z.core.$strip>>;
701
+ images: z.ZodOptional<z.ZodObject<{
702
+ sizes: z.ZodOptional<z.ZodArray<z.ZodObject<{
703
+ name: z.ZodString;
704
+ width: z.ZodNumber;
705
+ height: z.ZodOptional<z.ZodNumber>;
706
+ crop: z.ZodOptional<z.ZodEnum<{
707
+ center: "center";
708
+ top: "top";
709
+ bottom: "bottom";
710
+ left: "left";
711
+ right: "right";
712
+ }>>;
713
+ }, z.core.$strip>>>;
714
+ format: z.ZodOptional<z.ZodEnum<{
715
+ webp: "webp";
716
+ avif: "avif";
717
+ jpeg: "jpeg";
718
+ png: "png";
719
+ }>>;
720
+ quality: z.ZodOptional<z.ZodNumber>;
721
+ }, z.core.$strip>>;
722
+ auth: z.ZodOptional<z.ZodObject<{
723
+ secret: z.ZodString;
724
+ tokenExpiration: z.ZodOptional<z.ZodNumber>;
725
+ refreshTokenExpiration: z.ZodOptional<z.ZodNumber>;
726
+ maxLoginAttempts: z.ZodOptional<z.ZodNumber>;
727
+ lockoutDuration: z.ZodOptional<z.ZodNumber>;
728
+ }, z.core.$strip>>;
729
+ plugins: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
730
+ typescript: z.ZodOptional<z.ZodUnknown>;
731
+ themes: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
732
+ i18n: z.ZodOptional<z.ZodObject<{
733
+ locales: z.ZodArray<z.ZodString>;
734
+ defaultLocale: z.ZodString;
735
+ }, z.core.$strip>>;
736
+ }, z.core.$strip>;
737
+ declare const collectionConfigSchema: z.ZodObject<{
738
+ slug: z.ZodString;
739
+ labels: z.ZodObject<{
740
+ singular: z.ZodString;
741
+ plural: z.ZodString;
742
+ }, z.core.$strip>;
743
+ slugField: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
744
+ useField: z.ZodOptional<z.ZodString>;
745
+ unique: z.ZodOptional<z.ZodBoolean>;
746
+ }, z.core.$strip>]>>;
747
+ i18n: z.ZodOptional<z.ZodBoolean>;
748
+ fields: z.ZodArray<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
749
+ access: z.ZodOptional<z.ZodObject<{
750
+ create: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>>;
751
+ read: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>>;
752
+ update: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>>;
753
+ delete: z.ZodOptional<z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>>;
754
+ }, z.core.$strip>>;
755
+ hooks: z.ZodOptional<z.ZodObject<{
756
+ beforeCreate: z.ZodOptional<z.ZodArray<z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>>>;
757
+ afterCreate: z.ZodOptional<z.ZodArray<z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>>>;
758
+ beforeUpdate: z.ZodOptional<z.ZodArray<z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>>>;
759
+ afterUpdate: z.ZodOptional<z.ZodArray<z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>>>;
760
+ beforeDelete: z.ZodOptional<z.ZodArray<z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>>>;
761
+ afterDelete: z.ZodOptional<z.ZodArray<z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>>>;
762
+ beforeRead: z.ZodOptional<z.ZodArray<z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>>>;
763
+ afterRead: z.ZodOptional<z.ZodArray<z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>>>;
764
+ }, z.core.$strip>>;
765
+ versions: z.ZodOptional<z.ZodObject<{
766
+ drafts: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
767
+ autosave: z.ZodOptional<z.ZodBoolean>;
768
+ autosaveInterval: z.ZodOptional<z.ZodNumber>;
769
+ }, z.core.$strip>]>>;
770
+ max: z.ZodOptional<z.ZodNumber>;
771
+ }, z.core.$strip>>;
772
+ community: z.ZodOptional<z.ZodObject<{
773
+ comments: z.ZodOptional<z.ZodBoolean>;
774
+ memberWrite: z.ZodOptional<z.ZodObject<{
775
+ create: z.ZodOptional<z.ZodBoolean>;
776
+ update: z.ZodOptional<z.ZodBoolean>;
777
+ delete: z.ZodOptional<z.ZodBoolean>;
778
+ defaultStatus: z.ZodOptional<z.ZodEnum<{
779
+ published: "published";
780
+ pending: "pending";
781
+ }>>;
782
+ }, z.core.$strip>>;
783
+ }, z.core.$strip>>;
784
+ timestamps: z.ZodOptional<z.ZodBoolean>;
785
+ admin: z.ZodOptional<z.ZodObject<{
786
+ listColumns: z.ZodOptional<z.ZodArray<z.ZodString>>;
787
+ defaultSort: z.ZodOptional<z.ZodString>;
788
+ group: z.ZodOptional<z.ZodString>;
789
+ hidden: z.ZodOptional<z.ZodBoolean>;
790
+ description: z.ZodOptional<z.ZodString>;
791
+ components: z.ZodOptional<z.ZodObject<{
792
+ listView: z.ZodOptional<z.ZodString>;
793
+ editView: z.ZodOptional<z.ZodString>;
794
+ createView: z.ZodOptional<z.ZodString>;
795
+ }, z.core.$strip>>;
796
+ }, z.core.$strip>>;
797
+ upload: z.ZodOptional<z.ZodObject<{
798
+ maxFileSize: z.ZodOptional<z.ZodNumber>;
799
+ allowedMimeTypes: z.ZodOptional<z.ZodArray<z.ZodString>>;
800
+ imageSizes: z.ZodOptional<z.ZodArray<z.ZodObject<{
801
+ name: z.ZodString;
802
+ width: z.ZodNumber;
803
+ height: z.ZodOptional<z.ZodNumber>;
804
+ crop: z.ZodOptional<z.ZodEnum<{
805
+ center: "center";
806
+ top: "top";
807
+ bottom: "bottom";
808
+ left: "left";
809
+ right: "right";
810
+ }>>;
811
+ }, z.core.$strip>>>;
812
+ }, z.core.$strip>>;
813
+ }, z.core.$strip>;
814
+
815
+ interface LocalStorageAdapterConfig {
816
+ directory: string;
817
+ baseUrl: string;
818
+ }
819
+ declare class LocalStorageAdapter implements NpStorageAdapter {
820
+ private readonly config;
821
+ constructor(config: LocalStorageAdapterConfig);
822
+ upload(key: string, data: Buffer | ReadableStream, _: NpFileMetadata): Promise<void>;
823
+ getStream(key: string): Promise<ReadableStream>;
824
+ getUrl(key: string): Promise<string>;
825
+ delete(key: string): Promise<void>;
826
+ exists(key: string): Promise<boolean>;
827
+ private resolvePath;
828
+ private normalizeBaseUrl;
829
+ }
830
+
831
+ interface S3StorageAdapterConfig {
832
+ bucket: string;
833
+ region: string;
834
+ endpoint?: string;
835
+ credentials?: {
836
+ accessKeyId: string;
837
+ secretAccessKey: string;
838
+ };
839
+ }
840
+ declare class S3StorageAdapter implements NpStorageAdapter {
841
+ private readonly config;
842
+ private clientPromise;
843
+ constructor(config: S3StorageAdapterConfig);
844
+ upload(key: string, data: Buffer | ReadableStream, metadata: NpFileMetadata): Promise<void>;
845
+ getStream(key: string): Promise<ReadableStream>;
846
+ getUrl(key: string): Promise<string>;
847
+ delete(key: string): Promise<void>;
848
+ exists(key: string): Promise<boolean>;
849
+ private getClient;
850
+ private createClient;
851
+ }
852
+
853
+ declare function createStorageAdapter(config: NpConfig["storage"]): NpStorageAdapter;
854
+
855
+ interface NpEmailMessage {
856
+ /** RFC-822 recipient address. Single recipient per message. */
857
+ to: string;
858
+ /** Plain-text subject line. */
859
+ subject: string;
860
+ /** Plain-text body. Always provided as a fallback. */
861
+ text: string;
862
+ /** Optional HTML body. Adapter may choose to omit if absent. */
863
+ html?: string;
864
+ /** Override the adapter's default From header when set. */
865
+ from?: string;
866
+ }
867
+ /**
868
+ * Transactional mailer contract. One method: deliver a single message.
869
+ * Adapters throw on failure so the pg-boss worker can retry per its
870
+ * configured policy. Success is a void resolve.
871
+ */
872
+ interface NpEmailAdapter {
873
+ readonly kind: string;
874
+ send(message: NpEmailMessage): Promise<void>;
875
+ }
876
+
877
+ /**
878
+ * Default adapter used when no mailer is wired. Logs the message shape so a
879
+ * developer running the app locally can see that delivery was requested and
880
+ * follow the reset link from the logs without setting up SMTP.
881
+ *
882
+ * Swap via `setEmailAdapter(new SmtpEmailAdapter(...))` or a custom
883
+ * implementation in production.
884
+ */
885
+ declare class NoopEmailAdapter implements NpEmailAdapter {
886
+ readonly kind = "noop";
887
+ send(message: NpEmailMessage): Promise<void>;
888
+ }
889
+
890
+ interface SmtpEmailAdapterOptions {
891
+ host: string;
892
+ port: number;
893
+ user?: string;
894
+ pass?: string;
895
+ /** Default `From` header when a message doesn't override. */
896
+ from: string;
897
+ /**
898
+ * Use implicit TLS (port 465) when `true`. When `false`, STARTTLS is
899
+ * negotiated (port 587 / 25). Defaults to `port === 465`.
900
+ */
901
+ secure?: boolean;
902
+ }
903
+ /**
904
+ * Nodemailer-backed SMTP adapter. Works with any SMTP-speaking provider
905
+ * (Resend, SES, Mailgun, Postmark, Gmail, Zoho, custom relays).
906
+ *
907
+ * `nodemailer` is loaded dynamically on first send so that apps that don't
908
+ * use the SMTP adapter (noop or custom adapter) never pay its import cost.
909
+ */
910
+ declare class SmtpEmailAdapter implements NpEmailAdapter {
911
+ readonly kind = "smtp";
912
+ private readonly options;
913
+ private transporter;
914
+ constructor(options: SmtpEmailAdapterOptions);
915
+ private ensureTransporter;
916
+ send(message: NpEmailMessage): Promise<void>;
917
+ }
918
+
919
+ declare function setEmailAdapter(next: NpEmailAdapter): void;
920
+ declare function getEmailAdapter(): NpEmailAdapter;
921
+ /** Reset to the built-in stub. Primarily used by tests. */
922
+ declare function resetEmailAdapter(): void;
923
+
924
+ interface NpPasswordResetTemplateData {
925
+ siteName: string;
926
+ name: string;
927
+ resetUrl: string;
928
+ /** When the link expires (ISO string), for display only. */
929
+ expiresAt?: string;
930
+ }
931
+ interface NpEmailTemplate {
932
+ subject: string;
933
+ text: string;
934
+ html: string;
935
+ }
936
+ declare function buildInviteEmail(data: NpPasswordResetTemplateData): NpEmailTemplate;
937
+ declare function buildResetEmail(data: NpPasswordResetTemplateData): NpEmailTemplate;
938
+
939
+ declare const DEFAULT_THEME: NpThemeTokens;
940
+
941
+ declare function sanitizeTokenValue(value: string): string;
942
+
943
+ /**
944
+ * Idempotent — call once at boot from the framework's
945
+ * bootstrap, again from a hot-reload, etc. Themes are matched
946
+ * by `manifest.id`; later registrations replace earlier ones.
947
+ */
948
+ declare function registerThemes(themes: NpRegisteredTheme[]): void;
949
+ declare function getRegisteredThemes(): NpRegisteredTheme[];
950
+ declare function getThemeById(id: string): NpRegisteredTheme | undefined;
951
+ /** Tests use this between cases; production callers should never need it. */
952
+ declare function resetThemes(): void;
953
+ /**
954
+ * Reads the persisted active-theme id from `np_settings` for
955
+ * the current site. Returns `null` when no row exists —
956
+ * caller's job to decide the fallback (typically the first
957
+ * registered theme).
958
+ *
959
+ * Phase 15.4 — scoped by current site. Single-tenant
960
+ * deployments leave every row at `site_id = 'default'`, so
961
+ * the lookup behaves identically to the pre-15.4 global
962
+ * version.
963
+ */
964
+ declare function getActiveThemeId(): Promise<string | null>;
965
+ /**
966
+ * Resolves the active theme to render. Looks up the persisted id
967
+ * in the registry; falls back to the first registered theme when
968
+ * the id is unset, missing, or points at a theme that's no
969
+ * longer in the registry (e.g. it was removed from
970
+ * `nexpress.config.ts` between deploys). Returns `null` only
971
+ * when the registry is completely empty.
972
+ */
973
+ declare function getActiveTheme(): Promise<NpRegisteredTheme | null>;
974
+ /**
975
+ * Persist the active theme. Validates the id is registered so
976
+ * an admin can't pick a string that doesn't resolve to anything
977
+ * (which would silently fall back to the default and confuse
978
+ * the operator).
979
+ */
980
+ declare function setActiveThemeId(id: string, updatedBy?: string | null): Promise<void>;
981
+ /**
982
+ * Phase 11.3 — surface the active theme's templates for a
983
+ * given collection so admin pickers and the catch-all renderer
984
+ * can introspect what's available without reaching into the
985
+ * opaque `impl` themselves. The result is sanitized for serial-
986
+ * ization (no React component refs leak through this path) so
987
+ * the same shape is safe to send over the API to the admin UI.
988
+ */
989
+ interface NpThemeTemplateSummary {
990
+ id: string;
991
+ label: string;
992
+ description?: string;
993
+ }
994
+ declare function getThemeTemplateSummaries(collectionSlug: string): Promise<NpThemeTemplateSummary[]>;
995
+ /**
996
+ * Phase 14.5 — resolve a template's render component for the
997
+ * given collection + template id. Looks up theme first
998
+ * (theme always wins), falls back to plugin-contributed
999
+ * templates. Returns the opaque value (the catch-all casts to
1000
+ * `{ component }` at the render boundary).
1001
+ */
1002
+ declare function resolveTemplateComponent(collectionSlug: string, templateId: string): Promise<unknown>;
1003
+
1004
+ /**
1005
+ * Phase F.1 — theme requirement check.
1006
+ *
1007
+ * Compares a theme's `manifest.requires` against the site's
1008
+ * registered collections and reports mismatches. The result
1009
+ * drives admin warnings on theme activation; F.8 will reuse
1010
+ * the same shape to drive the CLI patcher.
1011
+ *
1012
+ * "Hard" requirements (default) are normal mismatches the
1013
+ * operator should resolve. "Soft" requirements (`hard: false`)
1014
+ * are recorded separately so admin can show them at lower
1015
+ * severity — the theme renders without them but with degraded
1016
+ * behavior.
1017
+ */
1018
+ interface NpThemeRequirementMissingField {
1019
+ collection: string;
1020
+ field: string;
1021
+ expected: NpThemeFieldRequirement;
1022
+ hard: boolean;
1023
+ }
1024
+ interface NpThemeRequirementTypeConflict {
1025
+ collection: string;
1026
+ field: string;
1027
+ expected: NpThemeFieldRequirement["type"];
1028
+ actual: string;
1029
+ hard: boolean;
1030
+ }
1031
+ interface NpThemeRequirementRelationConflict {
1032
+ collection: string;
1033
+ field: string;
1034
+ expected: NonNullable<NpThemeFieldRequirement["relationTo"]>;
1035
+ actual: string | string[];
1036
+ hard: boolean;
1037
+ }
1038
+ interface NpThemeRequirementResult {
1039
+ themeId: string;
1040
+ hasMismatches: boolean;
1041
+ /** Has at least one HARD mismatch — operator should resolve before activation. */
1042
+ hasHardMismatches: boolean;
1043
+ missingCollections: Array<{
1044
+ collection: string;
1045
+ createIfAbsent: boolean;
1046
+ }>;
1047
+ missingFields: NpThemeRequirementMissingField[];
1048
+ typeConflicts: NpThemeRequirementTypeConflict[];
1049
+ relationConflicts: NpThemeRequirementRelationConflict[];
1050
+ }
1051
+ declare function checkThemeRequirements(manifest: NpThemeManifest, collections: NpCollectionConfig[]): NpThemeRequirementResult;
1052
+
1053
+ /**
1054
+ * Read the persisted settings row for a theme and parse it via
1055
+ * the theme's schema. Returns the parsed value when valid;
1056
+ * falls back to schema defaults on parse failure (with the
1057
+ * failure recorded for the admin to surface, see
1058
+ * `getThemeSettingsWithStatus`).
1059
+ *
1060
+ * `themeId` defaults to the active theme. Pass an explicit id
1061
+ * to read another installed theme's settings (used by the
1062
+ * admin settings page).
1063
+ *
1064
+ * Return type is `unknown` because core can't type-narrow to
1065
+ * a specific theme's `z.infer<typeof schema>` — the schema
1066
+ * lives in the theme package, not in core. Theme components
1067
+ * should cast at the call site, ideally against an exported
1068
+ * type alias from the theme package itself:
1069
+ *
1070
+ * // packages/themes/magazine/src/index.ts
1071
+ * export const settingsSchema = z.object({ ... });
1072
+ * export type MagazineSettings = z.infer<typeof settingsSchema>;
1073
+ *
1074
+ * // a theme component
1075
+ * const settings = (await getThemeSettings()) as MagazineSettings;
1076
+ */
1077
+ declare function getThemeSettings(themeId?: string): Promise<unknown>;
1078
+ interface NpThemeSettingsResult {
1079
+ themeId: string | null;
1080
+ /** Parsed settings or schema defaults (never null when a theme
1081
+ * has a schema; empty object when the theme has no schema). */
1082
+ value: unknown;
1083
+ /** True when there's a stored row, regardless of whether it
1084
+ * passed validation. */
1085
+ hasPersisted: boolean;
1086
+ /** Set when the persisted value failed `schema.parse()`. The
1087
+ * admin surface uses this to show a "values reset to
1088
+ * defaults" banner. */
1089
+ parseError?: string;
1090
+ }
1091
+ declare function getThemeSettingsWithStatus(themeId?: string): Promise<NpThemeSettingsResult>;
1092
+ /**
1093
+ * Validate and persist a theme's settings. Throws
1094
+ * `NpValidationError` when `value` doesn't pass the schema —
1095
+ * the admin form must surface field-level errors before
1096
+ * calling this.
1097
+ *
1098
+ * **Cache invalidation is the caller's responsibility.** This
1099
+ * function writes to `np_settings` only; it doesn't import
1100
+ * `next/cache`. The admin API route (`PUT
1101
+ * /api/admin/themes/[id]/settings`) busts `nx:theme:<siteId>`
1102
+ * (and `nx:sitemap:*` / `nx:feed:*` when `impl.seo` is
1103
+ * declared) after a successful write. Other callers — jobs,
1104
+ * scripts, server actions — must do the same to avoid stale
1105
+ * cached reads.
1106
+ */
1107
+ declare function setThemeSettings(themeId: string, value: unknown, updatedBy?: string | null): Promise<unknown>;
1108
+ /**
1109
+ * Whether the active theme contributes SEO hooks. The settings
1110
+ * save path consults this to decide whether to additionally
1111
+ * invalidate `nx:sitemap:*` / `nx:feed:*` tags alongside the
1112
+ * always-busted `nx:theme:<siteId>`. Implemented here (in core)
1113
+ * so the API layer in `apps/web` doesn't need to duck-type the
1114
+ * theme `impl`.
1115
+ */
1116
+ declare function activeThemeContributesSeo(): Promise<boolean>;
1117
+
1118
+ /**
1119
+ * Phase F.6 — extract the active theme's declared nav
1120
+ * locations as plain JSON metadata, narrowed structurally
1121
+ * because core treats `theme.impl` as opaque (`unknown`).
1122
+ *
1123
+ * Used by:
1124
+ * - The admin nav editor's location-dropdown endpoint
1125
+ * (`/api/navigation/locations`) so the dropdown surfaces
1126
+ * theme-named slots with friendly labels.
1127
+ * - Future cookbook recipes that surface "this theme
1128
+ * expects you to fill in these menus" guidance.
1129
+ *
1130
+ * Returns `[]` when no theme is active or the active theme
1131
+ * doesn't declare any locations.
1132
+ */
1133
+ interface NpThemeNavLocationDescriptor {
1134
+ /** Location key, e.g. `"primary"`. Used as the `location`
1135
+ * argument to `getNavigation(...)` and the database row's
1136
+ * `(siteId, location)` lookup. */
1137
+ key: string;
1138
+ label: string;
1139
+ description?: string;
1140
+ maxItems?: number;
1141
+ }
1142
+ declare function getActiveThemeNavLocations(): Promise<NpThemeNavLocationDescriptor[]>;
1143
+
1144
+ /**
1145
+ * Phase F.7 — pure structural narrowers for theme `notFound`,
1146
+ * `error`, and `seo` contributions. Core treats `theme.impl`
1147
+ * as opaque (`unknown`); these helpers do the duck-typing in
1148
+ * one place so the consuming routes stay readable.
1149
+ *
1150
+ * `getActiveThemeNotFoundComponent` / `…ErrorComponent` return
1151
+ * the React component refs unchanged — typing as `unknown`
1152
+ * here, the consumers (in `apps/web`) cast to ComponentType
1153
+ * at the JSX site. Core can't import `react` directly without
1154
+ * dragging the peer into a server-only package, so the cast
1155
+ * is delegated.
1156
+ */
1157
+ interface NpThemeSeoHooksExtracted {
1158
+ sitemapEntries?: () => Promise<NpSitemapEntry[]> | NpSitemapEntry[];
1159
+ feedEntries?: () => Promise<NpFeedEntry[]> | NpFeedEntry[];
1160
+ robotsTxt?: () => string | Promise<string>;
1161
+ }
1162
+ declare function extractNotFoundComponent(impl: unknown): unknown;
1163
+ declare function extractErrorComponent(impl: unknown): unknown;
1164
+ /**
1165
+ * Phase M.3 — member-tree 404 component, with fallback to the
1166
+ * top-level `impl.notFound`. Returns `null` when neither the
1167
+ * member-specific nor the top-level component is declared (the
1168
+ * caller renders the framework default). Same opacity contract
1169
+ * as `extractNotFoundComponent` — typed as `unknown` here, the
1170
+ * consumer in `apps/web/src/app/(member)/not-found.tsx` casts
1171
+ * to `ComponentType` at the JSX site.
1172
+ */
1173
+ declare function extractMembersNotFoundComponent(impl: unknown): unknown;
1174
+ declare function extractSeoHooks(impl: unknown): NpThemeSeoHooksExtracted;
1175
+ /**
1176
+ * Async sugar over the active theme. Each helper returns a
1177
+ * fresh resolution per call; multi-site safety comes from
1178
+ * `getActiveTheme()` reading per-request site context.
1179
+ */
1180
+ declare function getActiveThemeNotFound(): Promise<unknown>;
1181
+ declare function getActiveThemeError(): Promise<unknown>;
1182
+ /**
1183
+ * Phase M.3 — async sugar for the member-tree 404. Returns the
1184
+ * theme's `impl.members.notFound` when declared, falling back
1185
+ * to its `impl.notFound` (top-level), then `null`.
1186
+ */
1187
+ declare function getActiveThemeMembersNotFound(): Promise<unknown>;
1188
+ declare function getActiveThemeSeoHooks(): Promise<NpThemeSeoHooksExtracted>;
1189
+
1190
+ /**
1191
+ * Phase F.3 — server-side introspection of a theme's
1192
+ * `settingsSchema` Zod tree into a JSON metadata shape the
1193
+ * admin form generator consumes.
1194
+ *
1195
+ * The schema lives in the theme package (server bundle); we
1196
+ * don't ship the schema itself to the browser. Instead, this
1197
+ * function walks the tree once on the server, emits the
1198
+ * metadata as plain JSON, and the admin renders form fields
1199
+ * from the metadata. The browser doesn't need zod at runtime.
1200
+ *
1201
+ * Coverage in v0.2: text, url, color (regex heuristic), number,
1202
+ * boolean, enum, array(object), object. Anything else
1203
+ * introspects as `{ type: "unsupported" }` so the form generator
1204
+ * can render a JSON textarea fallback (operator can still edit;
1205
+ * a follow-up phase widens coverage).
1206
+ */
1207
+ type NpThemeSettingsField = NpThemeSettingsTextField | NpThemeSettingsTextareaField | NpThemeSettingsPasswordField | NpThemeSettingsUrlField | NpThemeSettingsColorField | NpThemeSettingsNumberField | NpThemeSettingsBooleanField | NpThemeSettingsEnumField | NpThemeSettingsArrayField | NpThemeSettingsStringArrayField | NpThemeSettingsObjectField | NpThemeSettingsUnsupportedField;
1208
+ interface NpThemeSettingsFieldBase {
1209
+ /** Field path key ("hero", "social.0.url", etc. — the
1210
+ * introspector returns flat keys per node; nested objects
1211
+ * carry their own children). */
1212
+ name: string;
1213
+ label?: string;
1214
+ description?: string;
1215
+ required: boolean;
1216
+ default?: unknown;
1217
+ }
1218
+ interface NpThemeSettingsTextField extends NpThemeSettingsFieldBase {
1219
+ type: "text";
1220
+ }
1221
+ interface NpThemeSettingsTextareaField extends NpThemeSettingsFieldBase {
1222
+ type: "textarea";
1223
+ /** Optional row count hint for the rendered `<textarea>`.
1224
+ * Theme authors set this via `.meta({ widget: "textarea",
1225
+ * rows: 6 })`. Defaults to 4 when unset. */
1226
+ rows?: number;
1227
+ }
1228
+ interface NpThemeSettingsPasswordField extends NpThemeSettingsFieldBase {
1229
+ type: "password";
1230
+ }
1231
+ interface NpThemeSettingsUrlField extends NpThemeSettingsFieldBase {
1232
+ type: "url";
1233
+ }
1234
+ interface NpThemeSettingsColorField extends NpThemeSettingsFieldBase {
1235
+ type: "color";
1236
+ }
1237
+ interface NpThemeSettingsNumberField extends NpThemeSettingsFieldBase {
1238
+ type: "number";
1239
+ int?: boolean;
1240
+ min?: number;
1241
+ max?: number;
1242
+ }
1243
+ interface NpThemeSettingsBooleanField extends NpThemeSettingsFieldBase {
1244
+ type: "boolean";
1245
+ }
1246
+ interface NpThemeSettingsEnumField extends NpThemeSettingsFieldBase {
1247
+ type: "enum";
1248
+ options: string[];
1249
+ }
1250
+ interface NpThemeSettingsArrayField extends NpThemeSettingsFieldBase {
1251
+ type: "array";
1252
+ /** v0.2 supports `z.array(z.object(...))`. The element
1253
+ * schema introspects as the array's child fields. */
1254
+ element: NpThemeSettingsField[];
1255
+ }
1256
+ /** Phase G follow-up — `z.array(z.string())`. Renders as a
1257
+ * one-item-per-line input. Surfaced for OAuth scopes and
1258
+ * similar string-list configs that don't fit the object-array
1259
+ * shape; previously fell through to the JSON-textarea
1260
+ * `unsupported` fallback. */
1261
+ interface NpThemeSettingsStringArrayField extends NpThemeSettingsFieldBase {
1262
+ type: "string-array";
1263
+ }
1264
+ interface NpThemeSettingsObjectField extends NpThemeSettingsFieldBase {
1265
+ type: "object";
1266
+ fields: NpThemeSettingsField[];
1267
+ }
1268
+ interface NpThemeSettingsUnsupportedField extends NpThemeSettingsFieldBase {
1269
+ type: "unsupported";
1270
+ /** Best-effort label for what was at this position so
1271
+ * operators can recognize their schema in the JSON fallback. */
1272
+ zodTypeName: string;
1273
+ }
1274
+ /**
1275
+ * Walk a theme's `settingsSchema` (top-level z.object) and emit
1276
+ * the form metadata. Returns an empty array when the schema
1277
+ * isn't a top-level object — themes are expected to ship
1278
+ * `settingsSchema: z.object({...})` (validated implicitly: a
1279
+ * non-object top schema yields an empty form, signalling
1280
+ * "nothing to configure").
1281
+ */
1282
+ declare function introspectThemeSettingsSchema(schema: ZodTypeAny | undefined): NpThemeSettingsField[];
1283
+
1284
+ /**
1285
+ * Phase 14.5 — plugin-contributed page templates. Plugins
1286
+ * register templates the same way themes do (`{ label,
1287
+ * description?, component }` keyed by collection then by
1288
+ * template id) but the registry here is global, separate
1289
+ * from any one theme. The theme registry's
1290
+ * `getThemeTemplateSummaries` merges both sets so admin
1291
+ * pickers and the catch-all see a unified list; theme
1292
+ * registrations win on id collisions because the active
1293
+ * theme is the design authority for the site.
1294
+ *
1295
+ * The framework keeps `impl: unknown` on the public
1296
+ * registered-theme shape (no React peer dep in core), so
1297
+ * plugin template values stay typed as `unknown` here too.
1298
+ * The caller casts to the typed `NpThemeTemplate` shape
1299
+ * from `@nexpress/theme` at the render boundary.
1300
+ */
1301
+ /**
1302
+ * Merge a single plugin's `templates` declaration into the
1303
+ * registry. Idempotent: re-registering the same plugin id
1304
+ * overwrites its previous entries.
1305
+ */
1306
+ declare function registerPluginTemplates(pluginId: string, templates: Record<string, Record<string, unknown>>): void;
1307
+ /** Tests use this between cases; production callers shouldn't need it. */
1308
+ declare function resetPluginTemplates(): void;
1309
+ /**
1310
+ * Returns every plugin-registered template for a collection,
1311
+ * keyed by template id. The returned values are opaque
1312
+ * (`unknown`); consumers cast to the appropriate shape at the
1313
+ * call site (theme registry casts to summary metadata; the
1314
+ * catch-all casts to `{ component }`).
1315
+ */
1316
+ declare function getPluginTemplatesForCollection(collectionSlug: string): Map<string, unknown>;
1317
+
1318
+ /**
1319
+ * Phase 15.1 — multi-site registry. The framework treats
1320
+ * sites as long-lived rows in `np_sites`; the bootstrap calls
1321
+ * `ensureDefaultSite()` at boot to guarantee at least one row
1322
+ * exists so single-tenant installs (the existing reference
1323
+ * app shape) keep working without operator intervention.
1324
+ *
1325
+ * 15.1 ships the model + lookup helpers; 15.2 wires
1326
+ * collection queries through `siteId`; 15.3 ships the
1327
+ * super-admin UI for creating / managing sites. Until 15.2
1328
+ * lands, nothing in the existing pipeline knows or cares
1329
+ * about which site a row belongs to — the columns just
1330
+ * exist and the default site backfills.
1331
+ */
1332
+ interface NpSite {
1333
+ id: string;
1334
+ name: string;
1335
+ hostname: string | null;
1336
+ description: string | null;
1337
+ settings: Record<string, unknown>;
1338
+ isDefault: boolean;
1339
+ createdAt: Date;
1340
+ updatedAt: Date;
1341
+ }
1342
+ /**
1343
+ * Idempotently create the default site if no sites exist.
1344
+ * Bootstrap calls this once during framework init; tests
1345
+ * that truncate `np_sites` between cases re-trigger it.
1346
+ */
1347
+ declare function ensureDefaultSite(): Promise<NpSite>;
1348
+ declare function listSites(): Promise<NpSite[]>;
1349
+ declare function getSiteById(id: string): Promise<NpSite | null>;
1350
+ /**
1351
+ * Hostname-based lookup. Returns the matching site, or the
1352
+ * default site when no row matches (so a request hitting
1353
+ * an unconfigured host still gets served by the canonical
1354
+ * site rather than 404'ing). Case-insensitive on the host
1355
+ * string.
1356
+ */
1357
+ declare function getSiteByHostname(hostname: string): Promise<NpSite | null>;
1358
+ declare function getDefaultSite(): Promise<NpSite | null>;
1359
+ /**
1360
+ * Resolve which site a request belongs to. Tries hostname
1361
+ * lookup first; falls back to the default site. Returns
1362
+ * `null` only when the database has no sites at all (which
1363
+ * shouldn't happen post-bootstrap).
1364
+ */
1365
+ declare function resolveSiteForHostname(hostname: string | null | undefined): Promise<NpSite | null>;
1366
+ interface CreateSiteInput {
1367
+ id: string;
1368
+ name: string;
1369
+ hostname?: string | null;
1370
+ description?: string | null;
1371
+ settings?: Record<string, unknown>;
1372
+ }
1373
+ declare function createSite(input: CreateSiteInput): Promise<NpSite>;
1374
+ declare function updateSite(id: string, patch: Partial<Pick<NpSite, "name" | "hostname" | "description" | "settings">>): Promise<NpSite>;
1375
+ /**
1376
+ * Phase 15.9 — count of every site-scoped row attached to a
1377
+ * given site. Surfaces in the admin delete-site dialog so
1378
+ * operators see what they're about to nuke (or leave behind
1379
+ * as orphans, in the cascade=false path).
1380
+ *
1381
+ * Includes:
1382
+ * - per-collection row counts (codegen'd `np_c_*` tables)
1383
+ * - system tables that carry `site_id`: settings,
1384
+ * navigation, memberships, string overrides, plugin
1385
+ * storage (Issue #220)
1386
+ * - community tables that carry `site_id`: comments,
1387
+ * reactions, follows, mutes, notifications, reports,
1388
+ * audit events, bans, member roles (Issue #220)
1389
+ *
1390
+ * Does NOT include things that aren't site-scoped:
1391
+ * - users (`np_users` is global)
1392
+ * - members (`np_members` is global; per-site enrollment
1393
+ * happens through the site-scoped `bans` / `member_roles`
1394
+ * tables which DO appear in usage)
1395
+ * - media (`np_media` is global)
1396
+ * - audit events with `site_id IS NULL` — those are
1397
+ * intentional super-admin / background-job events that
1398
+ * don't belong to any tenant.
1399
+ */
1400
+ interface NpSiteUsage {
1401
+ collections: Record<string, number>;
1402
+ settings: number;
1403
+ navigation: number;
1404
+ memberships: number;
1405
+ stringOverrides: number;
1406
+ /** Issue #220 — newly-included site-scoped tables. */
1407
+ pluginStorage: number;
1408
+ comments: number;
1409
+ reactions: number;
1410
+ follows: number;
1411
+ mutes: number;
1412
+ notifications: number;
1413
+ reports: number;
1414
+ auditEvents: number;
1415
+ bans: number;
1416
+ memberRoles: number;
1417
+ /** Sum of every count above. Convenience for "is anything here?" checks. */
1418
+ total: number;
1419
+ }
1420
+ declare function getSiteUsageSummary(id: string): Promise<NpSiteUsage>;
1421
+ interface NpDeleteSiteOptions {
1422
+ /**
1423
+ * Phase 15.9 — when `true`, cascade-delete every site-scoped
1424
+ * row (collection content, settings, navigation, memberships,
1425
+ * string overrides) before dropping the `np_sites` row.
1426
+ *
1427
+ * When `false` (default, safe), the call refuses if any
1428
+ * site-scoped data still exists. The admin UI uses this to
1429
+ * force operators to confirm cascade explicitly so an
1430
+ * accidental delete can't quietly orphan thousands of rows.
1431
+ */
1432
+ cascade?: boolean;
1433
+ }
1434
+ /**
1435
+ * Delete a non-default site. The default site can't be
1436
+ * deleted (the framework's invariant is "at least one site
1437
+ * always exists"); operators who want to retire the default
1438
+ * promote a different site to default first.
1439
+ *
1440
+ * Phase 15.9 — `options.cascade` controls whether site-scoped
1441
+ * data is deleted alongside. Defaults to `false` for safety;
1442
+ * the admin UI surfaces a usage summary first so the operator
1443
+ * sees what cascade would touch.
1444
+ */
1445
+ declare function deleteSite(id: string, options?: NpDeleteSiteOptions): Promise<void>;
1446
+ declare const NP_DEFAULT_SITE_ID = "default";
1447
+
1448
+ /**
1449
+ * Phase 15.1 — process-wide "current site" resolver hook.
1450
+ *
1451
+ * The pipeline doesn't know how to find the current request's
1452
+ * site on its own (the runtime layer does — it reads the
1453
+ * `x-np-site-id` header the middleware sets). This module
1454
+ * exposes a setter the runtime calls at boot:
1455
+ *
1456
+ * setCurrentSiteResolver(async () => {
1457
+ * const headerList = await headers();
1458
+ * return headerList.get("x-np-site-id") ?? null;
1459
+ * });
1460
+ *
1461
+ * Pipeline / hooks call `getCurrentSiteId()` to read the
1462
+ * resolved id (or `null` when no resolver is wired, e.g.
1463
+ * background workers, scripts).
1464
+ *
1465
+ * This is intentionally async — the canonical Next.js
1466
+ * resolver awaits `headers()`. Sync paths (CLI, tests) can
1467
+ * register a sync resolver by returning the value directly.
1468
+ */
1469
+ type Resolver = () => string | null | Promise<string | null>;
1470
+ declare function setCurrentSiteResolver(fn: Resolver | null): void;
1471
+ declare function resetCurrentSiteResolver(): void;
1472
+ declare function getCurrentSiteId(): Promise<string | null>;
1473
+ /**
1474
+ * Tests / scripts that want to pin the current site id for the
1475
+ * duration of a block use the `withCurrentSite` helper — it swaps
1476
+ * in a constant resolver, runs `fn`, and restores the previous
1477
+ * resolver on exit.
1478
+ *
1479
+ * Contract — read this carefully (#320):
1480
+ *
1481
+ * `withCurrentSite` covers ONLY work that completes (synchronously
1482
+ * or via `await`) before `fn` returns. Any fire-and-forget async
1483
+ * work spawned inside `fn` runs AFTER the `finally` block has
1484
+ * already restored the previous resolver, so it sees the OUTER
1485
+ * site context — typically `null` for a CLI / job, or the wrong
1486
+ * site for a request that was acting on a different tenant.
1487
+ *
1488
+ * Concretely:
1489
+ * - `enqueueJob(...)` persists the row immediately but the
1490
+ * handler runs later in the worker. The worker has no
1491
+ * resolver wired, so `getCurrentSiteId()` returns `null`
1492
+ * and `requireSiteId()` throws — even though the enqueuer
1493
+ * was inside a `withCurrentSite` block.
1494
+ * - `void someAsyncFn()` patterns inside `fn` are similarly
1495
+ * exposed.
1496
+ *
1497
+ * How to do it safely:
1498
+ * - Stamp `siteId` explicitly onto every job payload at
1499
+ * enqueue time. The handler reads it back from the payload
1500
+ * and wraps its own work in `withCurrentSite(payload.siteId,
1501
+ * handlerBody)`.
1502
+ * - `await` everything that needs the site context inside
1503
+ * `fn`. Don't return from `fn` while a site-dependent
1504
+ * operation is still pending.
1505
+ *
1506
+ * This is a fundamental limit of plain module-scoped state. A
1507
+ * future refactor could switch the resolver to
1508
+ * `AsyncLocalStorage` so the site follows the async boundary
1509
+ * automatically — that's tracked under #320 but out of scope
1510
+ * for this helper today.
1511
+ */
1512
+ declare function withCurrentSite<T>(siteId: string, fn: () => Promise<T>): Promise<T>;
1513
+
1514
+ /**
1515
+ * Phase 15.5 — per-site role memberships.
1516
+ *
1517
+ * `npUsers.role` stays the "global default role" (used by
1518
+ * existing single-tenant code and as a fallback when no
1519
+ * explicit membership exists for a given site). New
1520
+ * multi-tenant deployments grant explicit memberships via
1521
+ * the helpers here; the `isSuperAdmin` flag (also new in
1522
+ * 15.5) bypasses membership checks entirely so a super-admin
1523
+ * can administer every site without having to be enrolled
1524
+ * on each one individually.
1525
+ *
1526
+ * The framework's existing `hasRole(user, minRole)` keeps
1527
+ * working as a global check. New site-scoped checks should
1528
+ * use `hasRoleOnSite(user, siteId, minRole)`.
1529
+ */
1530
+ interface SiteMembership {
1531
+ siteId: string;
1532
+ userId: string;
1533
+ role: NpUserRole;
1534
+ createdAt: Date;
1535
+ updatedAt: Date;
1536
+ }
1537
+ declare function listSiteMemberships(siteId: string): Promise<SiteMembership[]>;
1538
+ declare function listMembershipsForUser(userId: string): Promise<SiteMembership[]>;
1539
+ declare function getMembership(siteId: string, userId: string): Promise<SiteMembership | null>;
1540
+ declare function grantSiteMembership(siteId: string, userId: string, role: NpUserRole): Promise<SiteMembership>;
1541
+ declare function revokeSiteMembership(siteId: string, userId: string): Promise<void>;
1542
+ /**
1543
+ * Promote / demote a user's super-admin status. Super-admins
1544
+ * bypass per-site membership checks; this is the framework's
1545
+ * "I can do anything" gate so it should be granted sparingly
1546
+ * (one or two operators per deployment, typically).
1547
+ */
1548
+ declare function setSuperAdmin(userId: string, isSuperAdmin: boolean): Promise<void>;
1549
+ /**
1550
+ * Resolve a user's effective role on a specific site:
1551
+ * 1. Super-admins always get `admin`.
1552
+ * 2. Explicit site membership wins over the global role.
1553
+ * 3. Fallback: the user's global `npUsers.role` (preserves
1554
+ * single-tenant behavior).
1555
+ *
1556
+ * Use this rather than `user.role` for any check that should
1557
+ * respect tenant boundaries.
1558
+ */
1559
+ declare function resolveUserRoleOnSite(user: NpAuthUser, siteId: string): Promise<NpUserRole>;
1560
+ /**
1561
+ * Site-scoped variant of `hasRole`. Resolves the user's
1562
+ * effective role on the site (super-admin → admin, explicit
1563
+ * membership → that role, otherwise global default) and
1564
+ * compares against `minRole` using the same rank order
1565
+ * `hasRole` uses.
1566
+ *
1567
+ * Defaults to the current request site (or the framework's
1568
+ * `default` site when no resolver is wired) so callers don't
1569
+ * have to thread siteId everywhere.
1570
+ */
1571
+ declare function hasRoleOnSite(user: NpAuthUser, minRole: NpUserRole, siteId?: string): Promise<boolean>;
1572
+ /**
1573
+ * Quick boolean check for super-admin status. Cheaper than
1574
+ * `resolveUserRoleOnSite` when the caller only needs to know
1575
+ * "can this user manage the framework as a whole?".
1576
+ */
1577
+ declare function isSuperAdmin(user: NpAuthUser): Promise<boolean>;
1578
+
1579
+ declare function isPluginEnabled(pluginId: string): Promise<boolean>;
1580
+ declare function invalidatePluginEnabled(pluginId: string): void;
1581
+
1582
+ interface PluginHookHandler {
1583
+ pluginId: string;
1584
+ /**
1585
+ * Returns `void` for fire-and-forget hooks (most of them). Render / extension
1586
+ * hooks may return a value; `runHookAndCollect` gathers those, while
1587
+ * `runHook` ignores returns.
1588
+ */
1589
+ handler: (data: Record<string, unknown>) => unknown;
1590
+ /**
1591
+ * Lower priority runs first. Default `100`, leaving headroom in both
1592
+ * directions: a plugin that wants to observe AFTER everyone else picks
1593
+ * `200`, one that needs to mutate the payload first picks `0`. Stable
1594
+ * ordering: ties keep registration order (which is itself topo-sorted by
1595
+ * plugin `requires`).
1596
+ */
1597
+ priority: number;
1598
+ /**
1599
+ * Per-handler timeout in milliseconds. When the handler doesn't settle
1600
+ * within the budget, `dispatchHookHandler` treats it as a failure —
1601
+ * logged and reported the same way a thrown error is. The remaining
1602
+ * handlers continue. `undefined` means "no timeout enforced", which is
1603
+ * the default for fire-and-forget hooks (`runHook`); render-collecting
1604
+ * hooks may want a tighter budget (e.g. 250ms) so a slow plugin can't
1605
+ * stall page rendering.
1606
+ */
1607
+ timeoutMs?: number;
1608
+ }
1609
+ interface PluginRouteHandler {
1610
+ pluginId: string;
1611
+ path: string;
1612
+ method: string;
1613
+ /** When true, the dispatcher must verify a staff session before
1614
+ * invoking `handler` and pass the resolved user as `req.user`.
1615
+ * When false (default), the route is publicly reachable. */
1616
+ auth: boolean;
1617
+ handler: (req: PluginRouteRequest) => Promise<PluginRouteResponse>;
1618
+ }
1619
+ interface PluginRouteRequest {
1620
+ method: string;
1621
+ path: string;
1622
+ params: Record<string, string>;
1623
+ query: Record<string, string>;
1624
+ body: unknown;
1625
+ headers: Record<string, string>;
1626
+ user?: {
1627
+ id: string;
1628
+ email: string;
1629
+ role: string;
1630
+ };
1631
+ }
1632
+ interface PluginRouteResponse {
1633
+ status: number;
1634
+ body?: unknown;
1635
+ headers?: Record<string, string>;
1636
+ }
1637
+ /**
1638
+ * Declarative admin extension snapshot stored per registration. Shape mirrors
1639
+ * `@nexpress/plugin-sdk`'s `NpAdminExtension` but kept structural here to
1640
+ * avoid a plugin-sdk → core cycle. The admin UI reads this via
1641
+ * `getPluginAdminExtension(id)` and renders it with its own primitives.
1642
+ */
1643
+ interface PluginAdminExtension {
1644
+ settings?: {
1645
+ title?: string;
1646
+ description?: string;
1647
+ fields: NpFieldConfig[];
1648
+ };
1649
+ widgets?: Array<{
1650
+ id: string;
1651
+ label: string;
1652
+ kind: "metric" | "status";
1653
+ actionId: string;
1654
+ description?: string;
1655
+ }>;
1656
+ actions?: Array<{
1657
+ id: string;
1658
+ label: string;
1659
+ actionId: string;
1660
+ confirm?: string;
1661
+ description?: string;
1662
+ }>;
1663
+ tables?: Array<{
1664
+ id: string;
1665
+ label: string;
1666
+ columns: Array<{
1667
+ name: string;
1668
+ label: string;
1669
+ }>;
1670
+ rowsActionId: string;
1671
+ emptyMessage?: string;
1672
+ }>;
1673
+ collectionTabs?: Array<{
1674
+ id: string;
1675
+ label: string;
1676
+ collections: string[] | "*";
1677
+ widgets?: Array<{
1678
+ id: string;
1679
+ label: string;
1680
+ kind: "metric" | "status";
1681
+ actionId: string;
1682
+ description?: string;
1683
+ }>;
1684
+ actions?: Array<{
1685
+ id: string;
1686
+ label: string;
1687
+ actionId: string;
1688
+ confirm?: string;
1689
+ description?: string;
1690
+ }>;
1691
+ description?: string;
1692
+ }>;
1693
+ dashboardWidgets?: Array<{
1694
+ id: string;
1695
+ label: string;
1696
+ kind: "metric" | "status";
1697
+ actionId: string;
1698
+ description?: string;
1699
+ priority?: number;
1700
+ }>;
1701
+ }
1702
+ /**
1703
+ * Phase 19 — first-class plugin cron schedules. Plugins
1704
+ * declare `scheduled: [{ id, cron, handler }]` in their
1705
+ * definition; the host stores the list here and pg-boss
1706
+ * registers one recurring schedule per entry. The handler
1707
+ * runs in the same context shape `setup()` saw, so plugins
1708
+ * already familiar with `ctx.content` / `ctx.storage` /
1709
+ * `ctx.next` use the same surface from a cron tick.
1710
+ */
1711
+ interface PluginScheduleHandler {
1712
+ pluginId: string;
1713
+ taskId: string;
1714
+ cron: string;
1715
+ description?: string;
1716
+ handler: (ctx: Record<string, unknown>) => unknown;
1717
+ }
1718
+ interface PluginRegistration {
1719
+ id: string;
1720
+ name: string;
1721
+ version?: string;
1722
+ description?: string;
1723
+ capabilities: readonly string[];
1724
+ allowedHosts: readonly string[];
1725
+ admin?: PluginAdminExtension;
1726
+ hooks: Map<string, PluginHookHandler[]>;
1727
+ routes: PluginRouteHandler[];
1728
+ actions: Map<string, (data: unknown) => Promise<{
1729
+ ok: boolean;
1730
+ data?: unknown;
1731
+ error?: string;
1732
+ }>>;
1733
+ schedules: Map<string, PluginScheduleHandler>;
1734
+ /**
1735
+ * G.1 — Zod schema describing the plugin's operator-tunable
1736
+ * config. Read by `getPluginConfig` (introspection +
1737
+ * validation) and the admin auto-form. `unknown` here so the
1738
+ * host doesn't pull a hard zod dep into its type surface;
1739
+ * narrowed at the call site (same pattern theme uses for
1740
+ * `settingsSchema`).
1741
+ */
1742
+ configSchema?: unknown;
1743
+ /** G.1 — schema version (defaults to 1). See plugin-sdk types. */
1744
+ configVersion?: number;
1745
+ /** G.1 — migration callback for v(N-1) → current. */
1746
+ configMigrate?: (old: unknown, fromVersion: number) => unknown;
1747
+ /**
1748
+ * Plugin-contributed page routes (#623). Stored as the same
1749
+ * shape the SDK accepts (component + optional metadata as
1750
+ * `unknown`); the route-dispatcher in `@nexpress/next`
1751
+ * narrows them at render time. See
1752
+ * `docs/design/plugin-routes.md` for precedence + surface
1753
+ * semantics.
1754
+ */
1755
+ pageRoutes: readonly PluginPageRouteEntry[];
1756
+ }
1757
+ interface PluginPageRouteEntry {
1758
+ pattern: string;
1759
+ component: unknown;
1760
+ metadata?: unknown;
1761
+ surface: "site" | "member";
1762
+ locale: "auto" | "none";
1763
+ }
1764
+ /**
1765
+ * Structural shape for plugins built via `@nexpress/plugin-sdk`'s
1766
+ * `definePlugin()`. Matches `NpResolvedPluginLike` in config/types.ts —
1767
+ * kept deliberately loose so `loadPlugins` can accept the same array
1768
+ * that `NpConfig.plugins` does without narrowing gymnastics.
1769
+ */
1770
+ interface ResolvedPluginLike {
1771
+ manifest: {
1772
+ id: string;
1773
+ name: string;
1774
+ version?: string;
1775
+ description?: string;
1776
+ capabilities: readonly string[];
1777
+ allowedHosts?: readonly string[];
1778
+ /**
1779
+ * Compatibility range for the framework. The plugin loads only when
1780
+ * `nexpress.minVersion <= host <= nexpress.maxVersion?` (inclusive).
1781
+ * Optional here so legacy / hand-rolled plugins keep loading; the
1782
+ * plugin-sdk schema requires it for new plugins.
1783
+ */
1784
+ nexpress?: {
1785
+ minVersion?: string;
1786
+ maxVersion?: string;
1787
+ };
1788
+ /**
1789
+ * IDs of other plugins that must load first. The host topo-sorts the
1790
+ * load list so this plugin's `setup()` can assume its prerequisites
1791
+ * have already registered hooks/actions/blocks.
1792
+ */
1793
+ requires?: readonly string[];
1794
+ };
1795
+ hooks?: Record<string, unknown>;
1796
+ routes?: ReadonlyArray<{
1797
+ path: string;
1798
+ method: string;
1799
+ handler: unknown;
1800
+ description?: string;
1801
+ auth?: boolean;
1802
+ }>;
1803
+ admin?: PluginAdminExtension;
1804
+ /** G.1 — runtime zod schema for plugin config (auto-form). */
1805
+ configSchema?: unknown;
1806
+ /** G.1 — schema version for the lazy migration pipeline. */
1807
+ configVersion?: number;
1808
+ /** G.1 — old → current value migrator. */
1809
+ configMigrate?: (old: unknown, fromVersion: number) => unknown;
1810
+ }
1811
+ declare function loadPlugins(plugins: Array<NpPluginConfig | ResolvedPluginLike>): Promise<void>;
1812
+ declare function runHook(hookName: string, data: Record<string, unknown>): Promise<void>;
1813
+ /**
1814
+ * Like `runHook`, but collects every non-null/undefined return value from
1815
+ * registered handlers. Used by render extension points (`render:beforePage`,
1816
+ * etc.) where each plugin contributes structured data — head tags, scripts —
1817
+ * that the renderer aggregates into a single output.
1818
+ *
1819
+ * Handlers that throw are isolated (logged + reported, then skipped). A
1820
+ * broken plugin contributing meta tags is allowed to fail silently so the
1821
+ * page itself still ships — incomplete SEO output beats a 500.
1822
+ */
1823
+ declare function runHookAndCollect<T>(hookName: string, data: Record<string, unknown>): Promise<T[]>;
1824
+ declare function getPluginRoutes(): PluginRouteHandler[];
1825
+ /**
1826
+ * Plugin page routes (#623). Returns the flat list of registered
1827
+ * routes from EVERY loaded plugin in registration order, regardless
1828
+ * of enabled state — call sites that care about enabled gating
1829
+ * (e.g. the route dispatcher) walk the list and re-check via
1830
+ * `isPluginEnabled(pluginId)`. Keeping the gate at the call site
1831
+ * means tests can assert the registered shape without mocking the
1832
+ * enabled-state singleton.
1833
+ */
1834
+ declare function getPluginPageRoutes(): Array<{
1835
+ pluginId: string;
1836
+ route: PluginPageRouteEntry;
1837
+ }>;
1838
+ declare function getPluginRegistration(pluginId: string): PluginRegistration | undefined;
1839
+ declare function getAllPluginIds(): string[];
1840
+ declare function getPluginAdminExtension(pluginId: string): PluginAdminExtension | undefined;
1841
+ /**
1842
+ * Resolved collection-tab descriptor for the admin collection edit view.
1843
+ * Each entry carries pluginId + pluginName so the client component can
1844
+ * dispatch actions and label cards per-plugin.
1845
+ */
1846
+ interface ResolvedCollectionTab {
1847
+ pluginId: string;
1848
+ pluginName: string;
1849
+ id: string;
1850
+ label: string;
1851
+ widgets?: NonNullable<PluginAdminExtension["collectionTabs"]>[number]["widgets"];
1852
+ actions?: NonNullable<PluginAdminExtension["collectionTabs"]>[number]["actions"];
1853
+ description?: string;
1854
+ }
1855
+ /**
1856
+ * Collects all `collectionTabs` entries declared by loaded plugins whose
1857
+ * `collections` filter matches the given slug (either `"*"` or includes it).
1858
+ * The returned array is already flattened and annotated with the source
1859
+ * plugin, ready to pass into the admin edit view.
1860
+ */
1861
+ declare function getCollectionTabsForSlug(collectionSlug: string): ResolvedCollectionTab[];
1862
+ /**
1863
+ * Dashboard widget descriptor annotated with its source plugin. The admin
1864
+ * dashboard dispatches the widget's action with an empty payload — dashboard
1865
+ * widgets are global, not per-document.
1866
+ */
1867
+ interface ResolvedDashboardWidget {
1868
+ pluginId: string;
1869
+ pluginName: string;
1870
+ id: string;
1871
+ label: string;
1872
+ kind: "metric" | "status";
1873
+ actionId: string;
1874
+ description?: string;
1875
+ priority?: number;
1876
+ }
1877
+ /**
1878
+ * Collects `dashboardWidgets` declared by every loaded plugin and returns
1879
+ * them in render order: `priority` asc (missing priority = Infinity, i.e.
1880
+ * rendered last), ties broken by plugin registration order.
1881
+ */
1882
+ declare function getDashboardWidgetsFromPlugins(): ResolvedDashboardWidget[];
1883
+ /**
1884
+ * Dispatches a named action registered by the plugin via
1885
+ * `ctx.actions.register(actionId, handler)`. Admin widgets / actions / tables
1886
+ * call this via POST /api/plugins/:id/actions/:actionId — the handler is
1887
+ * responsible for returning `{ ok, data?, error? }`.
1888
+ */
1889
+ declare function dispatchPluginAction(pluginId: string, actionId: string, data?: unknown): Promise<{
1890
+ ok: boolean;
1891
+ data?: unknown;
1892
+ error?: string;
1893
+ }>;
1894
+ declare function schedulePluginTask(pluginId: string, taskId: string): Promise<void>;
1895
+ /**
1896
+ * Phase 19 — return every registered schedule across loaded
1897
+ * plugins. The pg-boss adapter calls this from
1898
+ * `scheduleRecurring()` so each `definePlugin({ scheduled })`
1899
+ * entry becomes a real cron in `pgboss.schedule`.
1900
+ */
1901
+ declare function getRegisteredPluginSchedules(): PluginScheduleHandler[];
1902
+ /**
1903
+ * Phase 19 — runs the handler for one plugin's scheduled task.
1904
+ * Called from the `plugin:scheduledTask` job handler when a
1905
+ * tick fires. Builds the same plugin context the `setup()`
1906
+ * call sees so handlers reuse `ctx.content` / `ctx.storage` /
1907
+ * etc. Throws when the plugin or task isn't registered so the
1908
+ * worker's retry policy surfaces the misconfiguration.
1909
+ */
1910
+ declare function runPluginScheduledTask(pluginId: string, taskId: string): Promise<void>;
1911
+ declare function resetPlugins(): void;
1912
+
1913
+ interface NpPluginConfigResult {
1914
+ pluginId: string;
1915
+ /** Parsed config or schema defaults. Empty object when the plugin has
1916
+ * no configSchema. */
1917
+ value: unknown;
1918
+ /** True when there's a stored row, regardless of whether it passed
1919
+ * validation. */
1920
+ hasPersisted: boolean;
1921
+ /** Set when the persisted value failed `schema.parse()`. The admin
1922
+ * surface uses this to render a "settings were reset" banner. */
1923
+ parseError?: string;
1924
+ }
1925
+ /**
1926
+ * Read the persisted config for a plugin and parse it via the plugin's
1927
+ * `configSchema`. Returns the parsed value when valid; falls back to
1928
+ * schema defaults on parse failure (with the failure recorded for the
1929
+ * admin to surface, see `getPluginConfigWithStatus`).
1930
+ *
1931
+ * Return type is `unknown` because core can't type-narrow to the plugin's
1932
+ * `z.infer<typeof configSchema>` — the schema lives in the plugin
1933
+ * package, not in core. Plugin code that reads its own config should
1934
+ * cast at the call site, ideally against an exported type alias from the
1935
+ * plugin package itself:
1936
+ *
1937
+ * // packages/plugins/oauth-github/src/index.ts
1938
+ * export const configSchema = z.object({ ... });
1939
+ * export type GithubOauthConfig = z.infer<typeof configSchema>;
1940
+ *
1941
+ * // a plugin handler
1942
+ * const config = (await getPluginConfig("oauth-github")) as GithubOauthConfig;
1943
+ */
1944
+ declare function getPluginConfig(pluginId: string): Promise<unknown>;
1945
+ declare function getPluginConfigWithStatus(pluginId: string): Promise<NpPluginConfigResult>;
1946
+ /**
1947
+ * Validate and persist a plugin's config. Throws `NpValidationError` when
1948
+ * `value` doesn't pass the schema — the admin form must surface
1949
+ * field-level errors before calling this.
1950
+ *
1951
+ * **Cache invalidation is the caller's responsibility.** This function
1952
+ * writes to `np_settings` only; it doesn't import `next/cache`. The
1953
+ * admin API route (`PUT /api/admin/plugins/[id]/config`) busts
1954
+ * `np:plugin:<id>` after a successful write.
1955
+ *
1956
+ * Mirrors `setThemeSettings` in `packages/core/src/themes/settings.ts`.
1957
+ */
1958
+ declare function setPluginConfig(pluginId: string, value: unknown, updatedBy?: string | null): Promise<unknown>;
1959
+ /** Cache tag for a plugin's config invalidation. Per the prefix policy
1960
+ * in CLAUDE.md (Naming convention table) every framework-owned tag
1961
+ * uses the `np` prefix. Distinct from the legacy `nx:theme:<siteId>`
1962
+ * tag — see `docs/design/plugin-config-auto-form.md` § 7. */
1963
+ declare function pluginConfigCacheTag(pluginId: string): string;
1964
+
1965
+ /**
1966
+ * G.1 — `np_plugins` is now a lean meta row: `(id, enabled,
1967
+ * installed_at, updated_at)`. The legacy `config` jsonb column was
1968
+ * dropped in favor of `np_settings` rows keyed by `plugin.config:<id>`
1969
+ * (see `packages/core/src/plugins/config.ts` and decision E in
1970
+ * `docs/design/plugin-config-auto-form.md`). Read / write plugin
1971
+ * config through `getPluginConfig` / `setPluginConfig` from the
1972
+ * config module — `getPluginState` only knows about the enable flag.
1973
+ */
1974
+ interface NpPluginState {
1975
+ id: string;
1976
+ enabled: boolean;
1977
+ installedAt: Date;
1978
+ updatedAt: Date;
1979
+ }
1980
+ interface NpPluginStateUpdate {
1981
+ enabled?: boolean;
1982
+ }
1983
+ declare function listPluginStates(db: NodePgDatabase<Record<string, unknown>>): Promise<NpPluginState[]>;
1984
+ declare function getPluginState(db: NodePgDatabase<Record<string, unknown>>, id: string): Promise<NpPluginState | null>;
1985
+ /**
1986
+ * Ensures every known plugin id has a row in `np_plugins`. Missing rows are
1987
+ * inserted with `enabled=true`. Existing rows are never touched — this is
1988
+ * called on boot and must not clobber operator edits.
1989
+ *
1990
+ * Uses a single INSERT … ON CONFLICT DO NOTHING so concurrent boots (multi-
1991
+ * process deployments) can all race safely without unique-key violations.
1992
+ */
1993
+ declare function syncPluginRegistrations(db: NodePgDatabase<Record<string, unknown>>, pluginIds: readonly string[]): Promise<void>;
1994
+ declare function updatePluginState(db: NodePgDatabase<Record<string, unknown>>, id: string, patch: NpPluginStateUpdate): Promise<NpPluginState | null>;
1995
+
1996
+ /**
1997
+ * Plugin compatibility checks: framework semver range + inter-plugin
1998
+ * dependency ordering.
1999
+ *
2000
+ * The plugin manifest declares two compatibility hints:
2001
+ * 1. `nexpress.minVersion` / `nexpress.maxVersion` — the plugin must run
2002
+ * against a framework version inside this range. The host enforces it
2003
+ * at load time so an outdated plugin can't crash deeper in the call
2004
+ * stack with an unrelated `TypeError`.
2005
+ * 2. `requires` — other plugins this one depends on. Used to sort the
2006
+ * load order so a plugin's `setup()` can assume its prerequisites
2007
+ * have already registered hooks/actions.
2008
+ *
2009
+ * Both checks fail open by default — an incompatible plugin or one with
2010
+ * missing deps is logged and skipped, never thrown. Operators see the warn
2011
+ * lines in boot logs and decide whether to upgrade or pin a version.
2012
+ */
2013
+ /**
2014
+ * Returns the running framework version, read from `@nexpress/core`'s
2015
+ * package.json at build time and inlined by tsup. Tests can override via
2016
+ * `setFrameworkVersionForTest()`.
2017
+ */
2018
+ declare function getFrameworkVersion(): string;
2019
+ /** Returns -1 / 0 / 1 — same contract as `Array.prototype.sort` callbacks. */
2020
+ declare function compareSemver(a: string, b: string): number;
2021
+ interface NexpressCompatResult {
2022
+ compatible: boolean;
2023
+ reason?: string;
2024
+ }
2025
+ /**
2026
+ * Verifies that `frameworkVersion` falls inside `[minVersion, maxVersion]`
2027
+ * (inclusive). `maxVersion` is optional — a plugin that omits it claims to
2028
+ * support every later version of the framework.
2029
+ */
2030
+ declare function checkNexpressCompat(manifest: {
2031
+ nexpress?: {
2032
+ minVersion?: string;
2033
+ maxVersion?: string;
2034
+ };
2035
+ }, framework?: string): NexpressCompatResult;
2036
+
2037
+ export { type CreateSiteInput, DEFAULT_THEME, LocalStorageAdapter, NP_DEFAULT_SITE_ID, type NexpressCompatResult, NoopEmailAdapter, NpAuthError, NpAuthUser, NpCollectionConfig, type NpCollectionTranslationProgress, NpConfig, NpConflictError, type NpDeleteSiteOptions, type NpEmailAdapter, type NpEmailMessage, type NpEmailTemplate, NpError, type NpErrorCode, type NpErrorCodeInput, NpFeedEntry, NpFieldConfig, NpFileMetadata, NpFindOptions, NpFindResult, NpForbiddenError, type NpListPendingDocsOptions, type NpListPendingDocsResult, NpNavItem, NpNotFoundError, type NpPasswordResetTemplateData, type NpPendingDocSummary, NpPluginConfig, type NpPluginConfigResult, type NpPluginState, type NpPluginStateUpdate, NpRateLimitError, NpRegisteredTheme, type NpRevision, type NpRevisionListOptions, type NpRevisionListResult, type NpRevisionStatus, type NpRevisionSummary, NpSaveOptions, NpSaveResult, type NpSearchAdapter, type NpSearchAdapterContext, type NpSearchVectorParts, type NpSite, NpSiteContextMissingError, type NpSiteUsage, NpSitemapEntry, NpStorageAdapter, type NpThemeColors, NpThemeFieldRequirement, NpThemeManifest, type NpThemeNavLocationDescriptor, type NpThemeRequirementMissingField, type NpThemeRequirementRelationConflict, type NpThemeRequirementResult, type NpThemeRequirementTypeConflict, type NpThemeSeoHooksExtracted, type NpThemeSettingsArrayField, type NpThemeSettingsBooleanField, type NpThemeSettingsColorField, type NpThemeSettingsEnumField, type NpThemeSettingsField, type NpThemeSettingsNumberField, type NpThemeSettingsObjectField, type NpThemeSettingsResult, type NpThemeSettingsTextField, type NpThemeSettingsTextareaField, type NpThemeSettingsUnsupportedField, type NpThemeSettingsUrlField, type NpThemeShape, type NpThemeTemplateSummary, type NpThemeTokens, type NpThemeTokensOverlay, type NpThemeTypography, type NpTranslationProgress, type NpTranslationProgressLocaleStats, NpUserRole, NpValidationError, type PluginAdminExtension, type PluginHookHandler, type PluginPageRouteEntry, type PluginRouteHandler, type PluginRouteRequest, type PluginRouteResponse, type PublishScheduledResult, type ReindexResult, type ResolvedCollectionTab, type ResolvedDashboardWidget, S3StorageAdapter, type SearchCollectionsOptions, type SearchResult, type SearchResultItem, type SiteMembership, SmtpEmailAdapter, type SmtpEmailAdapterOptions, activeThemeContributesSeo, autosaveRevision, buildInviteEmail, buildResetEmail, buildSearchVector, buildSearchVectorParts, buildWeightedSearchVectorSql, buildZodSchema, checkNexpressCompat, checkThemeRequirements, collectionConfigSchema, compareSemver, createMemberDocument, createSite, createStorageAdapter, createTranslation, defineCollection, defineConfig, deleteDocument, deleteMemberDocument, deleteSite, dispatchPluginAction, ensureDefaultSite, extractErrorComponent, extractMembersNotFoundComponent, extractNotFoundComponent, extractSeoHooks, findDocuments, findPosts, findSlugRedirect, findTranslations, getActiveTheme, getActiveThemeError, getActiveThemeId, getActiveThemeMembersNotFound, getActiveThemeNavLocations, getActiveThemeNotFound, getActiveThemeSeoHooks, getAllCollectionSlugs, getAllPageSlugs, getAllPluginIds, getCollectionConfig, getCollectionRegistration, getCollectionTable, getCollectionTabsForSlug, getCollectionZodSchema, getCurrentSiteId, getDashboardWidgetsFromPlugins, getDefaultSite, getDocumentById, getEmailAdapter, getFrameworkVersion, getMembership, getNavigation, getPageBySlug, getPluginAdminExtension, getPluginConfig, getPluginConfigWithStatus, getPluginPageRoutes, getPluginRegistration, getPluginRoutes, getPluginState, getPluginTemplatesForCollection, getPostBySlug, getRegisteredPluginSchedules, getRegisteredThemes, getRevision, getSearchAdapter, getSetting, getSiteByHostname, getSiteById, getSiteUsageSummary, getTheme, getThemeById, getThemeSettings, getThemeSettingsWithStatus, getThemeTemplateSummaries, getTranslationProgress, grantSiteMembership, hasRoleOnSite, introspectThemeSettingsSchema, invalidatePluginEnabled, isPluginEnabled, isSuperAdmin, listMembershipsForUser, listPendingMemberDocs, listPluginStates, listRevisions, listSiteMemberships, listSites, loadPlugins, npConfigSchema, pluginConfigCacheTag, promoteMemberDocument, publishScheduledDocuments, registerCollection, registerPluginTemplates, registerThemes, reindexCollection, resetCurrentSiteResolver, resetEmailAdapter, resetPluginTemplates, resetPlugins, resetSearchAdapter, resetThemes, resolveSiteForHostname, resolveTemplateComponent, resolveUserRoleOnSite, restoreRevision, revokeSiteMembership, runHook, runHookAndCollect, runPluginScheduledTask, sanitizeTokenValue, saveDocument, schedulePluginTask, searchCollections, setActiveThemeId, setCurrentSiteResolver, setEmailAdapter, setPluginConfig, setSearchAdapter, setSuperAdmin, setThemeSettings, syncPluginRegistrations, updateMemberDocument, updatePluginState, updateSite, withCurrentSite };