@jwilleke/ngdpbase-types 0.0.0 → 5.18.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 (302) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +16 -2
  3. package/lib/packages/ngdpbase-types/index.d.ts +32 -0
  4. package/lib/packages/ngdpbase-types/testing.d.ts +43 -0
  5. package/lib/packages/ngdpbase-types/testing.js +53 -0
  6. package/lib/src/cache/CacheDependencies.d.ts +50 -0
  7. package/lib/src/cache/CacheDependencies.js +50 -0
  8. package/lib/src/cache/ICacheAdapter.d.ts +86 -0
  9. package/lib/src/cache/RegionCache.d.ts +171 -0
  10. package/lib/src/context/ActorContext.d.ts +48 -0
  11. package/lib/src/context/ActorContext.js +33 -0
  12. package/lib/src/context/ApiContext.d.ts +194 -0
  13. package/lib/src/context/ApiContext.js +273 -0
  14. package/lib/src/context/BaseContext.d.ts +110 -0
  15. package/lib/src/context/BaseContext.js +143 -0
  16. package/lib/src/context/JobContext.d.ts +153 -0
  17. package/lib/src/context/JobContext.js +144 -0
  18. package/lib/src/context/WikiContext.d.ts +404 -0
  19. package/lib/src/context/bootActions.d.ts +64 -0
  20. package/lib/src/context/bootActions.js +111 -0
  21. package/lib/src/converters/HtmlConverter.d.ts +70 -0
  22. package/lib/src/converters/IContentConverter.d.ts +94 -0
  23. package/lib/src/converters/JSPWikiConverter.d.ts +117 -0
  24. package/lib/src/converters/conversionWarning.d.ts +15 -0
  25. package/lib/src/converters/ncm/bridge.d.ts +22 -0
  26. package/lib/src/converters/ncm/existing.d.ts +23 -0
  27. package/lib/src/converters/ncm/fix/blocks.d.ts +89 -0
  28. package/lib/src/converters/ncm/fix/bulletMarkers.d.ts +21 -0
  29. package/lib/src/converters/ncm/fix/index.d.ts +36 -0
  30. package/lib/src/converters/ncm/fix/jspwikiBullets.d.ts +20 -0
  31. package/lib/src/converters/ncm/fix/jspwikiCodeMarkers.d.ts +38 -0
  32. package/lib/src/converters/ncm/fix/jspwikiHeadings.d.ts +25 -0
  33. package/lib/src/converters/ncm/fix/jspwikiIndent.d.ts +24 -0
  34. package/lib/src/converters/ncm/fix/jspwikiInsert.d.ts +21 -0
  35. package/lib/src/converters/ncm/fix/jspwikiItalic.d.ts +29 -0
  36. package/lib/src/converters/ncm/fix/jspwikiLinkLeftovers.d.ts +27 -0
  37. package/lib/src/converters/ncm/fix/moreInformationFooter.d.ts +25 -0
  38. package/lib/src/converters/ncm/fix/styleClosers.d.ts +25 -0
  39. package/lib/src/converters/ncm/fix/tightenLists.d.ts +30 -0
  40. package/lib/src/converters/ncm/fix/types.d.ts +33 -0
  41. package/lib/src/converters/ncm/footnotes.d.ts +39 -0
  42. package/lib/src/converters/ncm/images.d.ts +49 -0
  43. package/lib/src/converters/ncm/index.d.ts +20 -0
  44. package/lib/src/converters/ncm/links.d.ts +21 -0
  45. package/lib/src/converters/ncm/normalize.d.ts +39 -0
  46. package/lib/src/converters/ncm/placeholder.d.ts +25 -0
  47. package/lib/src/converters/ncm/tables.d.ts +36 -0
  48. package/lib/src/converters/ncm/types.d.ts +42 -0
  49. package/lib/src/http/egressPolicy.d.ts +52 -0
  50. package/lib/src/http/guardedFetch.d.ts +89 -0
  51. package/lib/src/http/guardedFetch.js +113 -0
  52. package/lib/src/http/guardedLookup.d.ts +48 -0
  53. package/lib/src/http/guardedLookup.js +73 -0
  54. package/lib/src/http/ssrf.d.ts +83 -0
  55. package/lib/src/http/ssrf.js +311 -0
  56. package/lib/src/mail/MailProvider.d.ts +36 -0
  57. package/lib/src/mail/NodemailerMailProvider.d.ts +26 -0
  58. package/lib/src/managers/AddonsManager.d.ts +691 -0
  59. package/lib/src/managers/AgentTokenManager.d.ts +269 -0
  60. package/lib/src/managers/AssetManager.d.ts +116 -0
  61. package/lib/src/managers/AttachmentManager.d.ts +1032 -0
  62. package/lib/src/managers/AuthManager.d.ts +1257 -0
  63. package/lib/src/managers/BackgroundJobManager.d.ts +434 -0
  64. package/lib/src/managers/BaseManager.d.ts +391 -0
  65. package/lib/src/managers/BaseManager.js +415 -0
  66. package/lib/src/managers/CacheManager.d.ts +159 -0
  67. package/lib/src/managers/CatalogManager.d.ts +235 -0
  68. package/lib/src/managers/CommentManager.d.ts +75 -0
  69. package/lib/src/managers/ConfigurationManager.d.ts +747 -0
  70. package/lib/src/managers/DatabaseManager.d.ts +89 -0
  71. package/lib/src/managers/DatabaseManager.js +236 -0
  72. package/lib/src/managers/EmailManager.d.ts +47 -0
  73. package/lib/src/managers/ExportManager.d.ts +129 -0
  74. package/lib/src/managers/ExportManager.js +420 -0
  75. package/lib/src/managers/FootnoteManager.d.ts +118 -0
  76. package/lib/src/managers/MediaManager.d.ts +279 -0
  77. package/lib/src/managers/MetricsManager.d.ts +89 -0
  78. package/lib/src/managers/NotificationManager.d.ts +165 -0
  79. package/lib/src/managers/OidcManager.d.ts +210 -0
  80. package/lib/src/managers/OrganizationManager.d.ts +107 -0
  81. package/lib/src/managers/PageManager.d.ts +1645 -0
  82. package/lib/src/managers/PersonManager.d.ts +48 -0
  83. package/lib/src/managers/PluginManager.d.ts +179 -0
  84. package/lib/src/managers/PluginManager.js +393 -0
  85. package/lib/src/managers/PolicyEvaluator.d.ts +213 -0
  86. package/lib/src/managers/RenderingManager.d.ts +358 -0
  87. package/lib/src/managers/RoleManager.d.ts +134 -0
  88. package/lib/src/managers/SearchManager.d.ts +402 -0
  89. package/lib/src/managers/ShareManager.d.ts +251 -0
  90. package/lib/src/managers/TemplateManager.d.ts +176 -0
  91. package/lib/src/managers/ThemeManager.d.ts +56 -0
  92. package/lib/src/managers/UserManager.d.ts +480 -0
  93. package/lib/src/managers/ValidationManager.d.ts +426 -0
  94. package/lib/src/managers/VariableManager.d.ts +145 -0
  95. package/lib/src/middleware/csrf.d.ts +3 -0
  96. package/lib/src/parsers/LinkParser.d.ts +457 -0
  97. package/lib/src/parsers/MarkupParser.d.ts +769 -0
  98. package/lib/src/parsers/context/ParseContext.d.ts +314 -0
  99. package/lib/src/parsers/data/emoji-map.d.ts +18 -0
  100. package/lib/src/parsers/dom/DOMBuilder.d.ts +210 -0
  101. package/lib/src/parsers/dom/DOMParser.d.ts +252 -0
  102. package/lib/src/parsers/dom/Tokenizer.d.ts +358 -0
  103. package/lib/src/parsers/dom/WikiDocument.d.ts +272 -0
  104. package/lib/src/parsers/dom/handlers/DOMLinkHandler.d.ts +260 -0
  105. package/lib/src/parsers/dom/handlers/DOMPluginHandler.d.ts +212 -0
  106. package/lib/src/parsers/dom/handlers/DOMVariableHandler.d.ts +171 -0
  107. package/lib/src/parsers/filters/BaseFilter.d.ts +269 -0
  108. package/lib/src/parsers/filters/FilterChain.d.ts +320 -0
  109. package/lib/src/parsers/handlers/BaseSyntaxHandler.d.ts +331 -0
  110. package/lib/src/parsers/handlers/HandlerRegistry.d.ts +225 -0
  111. package/lib/src/parsers/handlers/JSPWikiPreprocessor.d.ts +89 -0
  112. package/lib/src/parsers/handlers/LinkParserHandler.d.ts +80 -0
  113. package/lib/src/parsers/handlers/PluginSyntaxHandler.d.ts +78 -0
  114. package/lib/src/parsers/handlers/WikiFormHandler.d.ts +149 -0
  115. package/lib/src/parsers/handlers/WikiTagHandler.d.ts +125 -0
  116. package/lib/src/parsers/jspwikiTableRow.d.ts +39 -0
  117. package/lib/src/parsers/styleBlockSyntax.d.ts +14 -0
  118. package/lib/src/plugins/types.d.ts +79 -0
  119. package/lib/src/providers/AgentTokenAuthProvider.d.ts +40 -0
  120. package/lib/src/providers/AuthentikBearerAuthProvider.d.ts +75 -0
  121. package/lib/src/providers/BaseAttachmentProvider.d.ts +197 -0
  122. package/lib/src/providers/BaseAuthProvider.d.ts +220 -0
  123. package/lib/src/providers/BaseCacheProvider.d.ts +140 -0
  124. package/lib/src/providers/BaseCredentialsProvider.d.ts +84 -0
  125. package/lib/src/providers/BaseDatabaseProvider.d.ts +22 -0
  126. package/lib/src/providers/BaseDatabaseProvider.js +12 -0
  127. package/lib/src/providers/BaseLoggingProvider.d.ts +68 -0
  128. package/lib/src/providers/BaseLoggingProvider.js +37 -0
  129. package/lib/src/providers/BaseMediaProvider.d.ts +308 -0
  130. package/lib/src/providers/BasePageProvider.d.ts +337 -0
  131. package/lib/src/providers/BaseProvider.d.ts +98 -0
  132. package/lib/src/providers/BaseProvider.js +74 -0
  133. package/lib/src/providers/BaseSearchProvider.d.ts +362 -0
  134. package/lib/src/providers/BasicAttachmentProvider.d.ts +599 -0
  135. package/lib/src/providers/CloudflareAccessAuthProvider.d.ts +72 -0
  136. package/lib/src/providers/FileCredentialsProvider.d.ts +33 -0
  137. package/lib/src/providers/FileJobStateProvider.d.ts +58 -0
  138. package/lib/src/providers/FileLoggingProvider.d.ts +43 -0
  139. package/lib/src/providers/FileLoggingProvider.js +120 -0
  140. package/lib/src/providers/FileOidcAdapter.d.ts +25 -0
  141. package/lib/src/providers/FileSystemMediaProvider.d.ts +403 -0
  142. package/lib/src/providers/LunrSearchProvider.d.ts +280 -0
  143. package/lib/src/providers/MagicLinkAuthProvider.d.ts +125 -0
  144. package/lib/src/providers/NullCacheProvider.d.ts +72 -0
  145. package/lib/src/providers/OidcAuthProvider.d.ts +180 -0
  146. package/lib/src/providers/OidcBearerAuthProvider.d.ts +30 -0
  147. package/lib/src/providers/PasskeyAuthProvider.d.ts +58 -0
  148. package/lib/src/providers/PasswordAuthProvider.d.ts +20 -0
  149. package/lib/src/providers/SqliteDatabaseProvider.d.ts +55 -0
  150. package/lib/src/providers/SqliteDatabaseProvider.js +90 -0
  151. package/lib/src/providers/SqliteJobStateProvider.d.ts +41 -0
  152. package/lib/src/providers/databaseMigrations.d.ts +8 -0
  153. package/lib/src/providers/databaseMigrations.js +47 -0
  154. package/lib/src/providers/jobState.d.ts +99 -0
  155. package/lib/src/providers/lunrQueryToMiniSearch.d.ts +32 -0
  156. package/lib/src/providers/mediaFilenameDate.d.ts +22 -0
  157. package/lib/src/providers/sqliteLocation.d.ts +17 -0
  158. package/lib/src/providers/sqliteLocation.js +55 -0
  159. package/lib/src/providers/sqliteMigrations.d.ts +48 -0
  160. package/lib/src/providers/sqliteMigrations.js +72 -0
  161. package/lib/src/rendering/githubAlerts.d.ts +22 -0
  162. package/lib/src/rendering/htmlPolicy.d.ts +76 -0
  163. package/lib/src/rendering/markdownConverter.d.ts +73 -0
  164. package/lib/src/security/PolicyDecisionPoint.d.ts +245 -0
  165. package/lib/src/security/PolicyInformationPoint.d.ts +448 -0
  166. package/lib/src/security/pageActions.d.ts +40 -0
  167. package/lib/src/security/permissions.generated.d.ts +78 -0
  168. package/lib/src/types/Asset.d.ts +414 -0
  169. package/lib/src/types/Catalog.d.ts +61 -0
  170. package/lib/src/types/Comment.d.ts +11 -0
  171. package/lib/src/types/Config.d.ts +388 -0
  172. package/lib/src/types/Organization.d.ts +78 -0
  173. package/lib/src/types/OrganizationProvider.d.ts +38 -0
  174. package/lib/src/types/Page.d.ts +220 -0
  175. package/lib/src/types/Person.d.ts +56 -0
  176. package/lib/src/types/PersonProvider.d.ts +25 -0
  177. package/lib/src/types/Policy.d.ts +139 -0
  178. package/lib/src/types/Policy.js +17 -0
  179. package/lib/src/types/Provider.d.ts +810 -0
  180. package/lib/src/types/Role.d.ts +84 -0
  181. package/lib/src/types/RoleProvider.d.ts +35 -0
  182. package/lib/src/types/Schema.d.ts +425 -0
  183. package/lib/src/types/Share.d.ts +172 -0
  184. package/lib/src/types/Share.js +81 -0
  185. package/lib/src/types/User.d.ts +304 -0
  186. package/lib/src/types/Version.d.ts +189 -0
  187. package/lib/src/types/WikiEngine.d.ts +128 -0
  188. package/lib/src/types/express.d.ts +120 -0
  189. package/lib/src/types/express.js +1 -0
  190. package/lib/src/types/guards.d.ts +78 -0
  191. package/lib/src/types/heic-decode.d.ts +16 -0
  192. package/lib/src/types/index.d.ts +19 -0
  193. package/lib/src/types/linkedom.d.ts +77 -0
  194. package/lib/src/types/markdown-it-plugins.d.ts +21 -0
  195. package/lib/src/utils/DeltaStorage.d.ts +118 -0
  196. package/lib/src/utils/LocaleUtils.d.ts +105 -0
  197. package/lib/src/utils/LocaleUtils.js +294 -0
  198. package/lib/src/utils/PageNameMatcher.d.ts +98 -0
  199. package/lib/src/utils/PathPreflight.d.ts +38 -0
  200. package/lib/src/utils/PathPreflight.js +50 -0
  201. package/lib/src/utils/SectionUtils.d.ts +40 -0
  202. package/lib/src/utils/addonConfigLayer.d.ts +144 -0
  203. package/lib/src/utils/addonConfigLayer.js +382 -0
  204. package/lib/src/utils/addonDatabaseRules.d.ts +25 -0
  205. package/lib/src/utils/addonDatabaseRules.js +57 -0
  206. package/lib/src/utils/addonHost.d.ts +51 -0
  207. package/lib/src/utils/addonHost.js +72 -0
  208. package/lib/src/utils/addonInstaller.d.ts +109 -0
  209. package/lib/src/utils/addonLock.d.ts +37 -0
  210. package/lib/src/utils/addonLock.js +48 -0
  211. package/lib/src/utils/addonPageSync.d.ts +45 -0
  212. package/lib/src/utils/addonProvenance.d.ts +48 -0
  213. package/lib/src/utils/addonTarball.d.ts +41 -0
  214. package/lib/src/utils/addonsPathResolver.d.ts +76 -0
  215. package/lib/src/utils/addonsPathResolver.js +129 -0
  216. package/lib/src/utils/assetMetadataTags.d.ts +66 -0
  217. package/lib/src/utils/atomicWrite.d.ts +28 -0
  218. package/lib/src/utils/auditConfigChange.d.ts +57 -0
  219. package/lib/src/utils/auditEventNames.d.ts +50 -0
  220. package/lib/src/utils/auditEventNames.generated.d.ts +163 -0
  221. package/lib/src/utils/auditEventNames.generated.js +168 -0
  222. package/lib/src/utils/auditEventNames.js +49 -0
  223. package/lib/src/utils/auditEvents.d.ts +265 -0
  224. package/lib/src/utils/auditEvents.js +309 -0
  225. package/lib/src/utils/auditRegistry.d.ts +102 -0
  226. package/lib/src/utils/auditRegistry.js +168 -0
  227. package/lib/src/utils/bip39English.d.ts +2 -0
  228. package/lib/src/utils/configEnvKeys.d.ts +68 -0
  229. package/lib/src/utils/configFiles.d.ts +74 -0
  230. package/lib/src/utils/configFiles.js +150 -0
  231. package/lib/src/utils/delegation.d.ts +48 -0
  232. package/lib/src/utils/deriveUsername.d.ts +21 -0
  233. package/lib/src/utils/explainMediaPath.d.ts +123 -0
  234. package/lib/src/utils/formerTitles.d.ts +69 -0
  235. package/lib/src/utils/frontmatterAccess.d.ts +54 -0
  236. package/lib/src/utils/headlessAdminPassword.d.ts +55 -0
  237. package/lib/src/utils/imageTransform.d.ts +52 -0
  238. package/lib/src/utils/instanceEnvSecret.d.ts +51 -0
  239. package/lib/src/utils/jobSlots.d.ts +43 -0
  240. package/lib/src/utils/keywordNormalizer.d.ts +127 -0
  241. package/lib/src/utils/logger.d.ts +70 -0
  242. package/lib/src/utils/logger.js +106 -0
  243. package/lib/src/utils/magicLinkDeviceState.d.ts +95 -0
  244. package/lib/src/utils/managerUtils.d.ts +40 -0
  245. package/lib/src/utils/migrateLegacyPrivateAttachments.d.ts +15 -0
  246. package/lib/src/utils/orgFilename.d.ts +22 -0
  247. package/lib/src/utils/pageFeature.d.ts +27 -0
  248. package/lib/src/utils/pageFrontmatter.d.ts +21 -0
  249. package/lib/src/utils/pageNotes.d.ts +150 -0
  250. package/lib/src/utils/pageTitleRule.d.ts +37 -0
  251. package/lib/src/utils/pageToArticle.d.ts +59 -0
  252. package/lib/src/utils/pageUrl.d.ts +18 -0
  253. package/lib/src/utils/passwordChange.d.ts +12 -0
  254. package/lib/src/utils/passwordHash.d.ts +60 -0
  255. package/lib/src/utils/permissionCatalog.d.ts +52 -0
  256. package/lib/src/utils/pluginFormatters.d.ts +452 -0
  257. package/lib/src/utils/pluginFormatters.js +714 -0
  258. package/lib/src/utils/privateLinkRewrite.d.ts +42 -0
  259. package/lib/src/utils/privateStoreAccess.d.ts +57 -0
  260. package/lib/src/utils/privateStoreAccess.js +65 -0
  261. package/lib/src/utils/privateStoreCatalogs.d.ts +40 -0
  262. package/lib/src/utils/privateStoreCrypto.d.ts +127 -0
  263. package/lib/src/utils/privateStoreDoor.d.ts +181 -0
  264. package/lib/src/utils/privateStoreExport.d.ts +72 -0
  265. package/lib/src/utils/privateStoreFiles.d.ts +120 -0
  266. package/lib/src/utils/privateStoreMeta.d.ts +64 -0
  267. package/lib/src/utils/privateStoreMigrations.d.ts +37 -0
  268. package/lib/src/utils/privateStorePath.d.ts +259 -0
  269. package/lib/src/utils/privateStorePath.js +436 -0
  270. package/lib/src/utils/privateStoreTakeout.d.ts +76 -0
  271. package/lib/src/utils/privateStoreUnlock.d.ts +175 -0
  272. package/lib/src/utils/proxyClaim.d.ts +59 -0
  273. package/lib/src/utils/proxyClaim.js +30 -0
  274. package/lib/src/utils/redactPrivateNames.d.ts +21 -0
  275. package/lib/src/utils/redactPrivateNames.js +89 -0
  276. package/lib/src/utils/redactSecrets.d.ts +58 -0
  277. package/lib/src/utils/redactSecrets.js +171 -0
  278. package/lib/src/utils/renameLinkRewrite.d.ts +112 -0
  279. package/lib/src/utils/roleGrants.d.ts +35 -0
  280. package/lib/src/utils/safeRedirect.d.ts +32 -0
  281. package/lib/src/utils/schedule.d.ts +54 -0
  282. package/lib/src/utils/secretFileMode.d.ts +17 -0
  283. package/lib/src/utils/seededShippedPages.d.ts +45 -0
  284. package/lib/src/utils/sensitiveValues.d.ts +59 -0
  285. package/lib/src/utils/sensitiveValues.js +106 -0
  286. package/lib/src/utils/sessionEnd.d.ts +12 -0
  287. package/lib/src/utils/sessionGeneration.d.ts +24 -0
  288. package/lib/src/utils/sessionIdle.d.ts +41 -0
  289. package/lib/src/utils/sessionLimits.d.ts +68 -0
  290. package/lib/src/utils/shareLockbox.d.ts +64 -0
  291. package/lib/src/utils/sigstoreTrustedRoot.d.ts +33 -0
  292. package/lib/src/utils/sniffMime.d.ts +21 -0
  293. package/lib/src/utils/storeSearchIndex.d.ts +75 -0
  294. package/lib/src/utils/subjectMayDo.d.ts +21 -0
  295. package/lib/src/utils/systemCategoryRules.d.ts +30 -0
  296. package/lib/src/utils/systemCategoryRules.js +41 -0
  297. package/lib/src/utils/userCreateError.d.ts +62 -0
  298. package/lib/src/utils/username.d.ts +53 -0
  299. package/lib/src/utils/vaultUrlRewrite.d.ts +27 -0
  300. package/lib/src/utils/version.d.ts +84 -0
  301. package/lib/src/utils/videoFrame.d.ts +21 -0
  302. package/package.json +54 -3
@@ -0,0 +1,691 @@
1
+ /**
2
+ * AddonsManager - Core Add-on Management System
3
+ *
4
+ * Handles discovery, registration, lifecycle management, and dependencies
5
+ * for optional ngdpbase add-ons. This enables optional business modules
6
+ * (person-contacts, financial-ledger, etc.) without modifying ngdpbase core.
7
+ *
8
+ * @class AddonsManager
9
+ * @extends BaseManager
10
+ *
11
+ * @see {@link https://github.com/jwilleke/ngdpbase/issues/158}
12
+ */
13
+ import type { ActorContext } from '../context/ActorContext.js';
14
+ import BaseManager from './BaseManager.js';
15
+ import type { BackupData } from './BaseManager.js';
16
+ import type { WikiEngine } from '../types/WikiEngine.js';
17
+ import type { User } from '../types/User.js';
18
+ import { type StoreOwnerState } from '../utils/privateStoreDoor.js';
19
+ import { type AddonHost } from '../utils/addonHost.js';
20
+ /**
21
+ * Type passed to `AddonModule.profileSection()` (#534). Matches what
22
+ * `UserManager.getUser()` returns at runtime — `User` minus the password
23
+ * hash — so addons can read preferences/roles/email without coupling
24
+ * to whether the host happened to fetch the full User record.
25
+ */
26
+ export type AddonProfileUser = Omit<User, 'password'>;
27
+ /**
28
+ * Flatten nested plain-object values into dotted-key form (#534 defensive).
29
+ *
30
+ * `express.urlencoded({ extended: true })` keeps dotted form-field names FLAT
31
+ * by default — `<input name="journal.x">` → `body['journal.x']` directly,
32
+ * NOT `body.journal.x`. (Verified: qs's `allowDots` defaults to false, and
33
+ * body-parser doesn't override that.) But the WikiRoutes core code defends
34
+ * against a future config change with its own `getBodyValue` walker, and
35
+ * `AddonsManager.saveProfileSections` normalizes addon-bound body shape so
36
+ * addon authors can rely on flat lookups regardless of host config.
37
+ *
38
+ * Rules:
39
+ * - Non-object values (string, number, boolean, null, undefined) pass
40
+ * through as-is at the current dotted key.
41
+ * - Arrays pass through as-is (not flattened element-wise — qs already
42
+ * produces sensible array shapes for `name="x[]"` inputs).
43
+ * - Plain objects are recursively flattened.
44
+ * - Flat dotted keys present on the input pass through unchanged.
45
+ * - If both flat (`'a.b': X`) and nested (`a: { b: Y }`) shapes are
46
+ * present, the LAST seen during iteration wins — typically the nested
47
+ * value, since flat dotted keys are inserted before nested objects in
48
+ * qs output. This is intentional: nested is the more-specific shape.
49
+ */
50
+ export declare function flattenDottedKeys(obj: Record<string, unknown>, prefix?: string): Record<string, unknown>;
51
+ /**
52
+ * Standard interface that all add-ons must implement
53
+ */
54
+ export interface AddonModule {
55
+ /** Unique identifier for the add-on */
56
+ name: string;
57
+ /** Semantic version string */
58
+ version: string;
59
+ /** Human-readable description */
60
+ description?: string;
61
+ /** Author name or organization */
62
+ author?: string;
63
+ /** List of add-on names this add-on depends on */
64
+ dependencies?: string[];
65
+ /**
66
+ * #1808: add-ons to load first when they are installed and enabled. Unlike
67
+ * `dependencies`, a missing or disabled one is skipped, not an error.
68
+ */
69
+ optionalDependencies?: string[];
70
+ /**
71
+ * Called during app startup if add-on is enabled.
72
+ * Use this to register routes, initialize databases, etc.
73
+ *
74
+ * #1813: `host` carries the core values an add-on uses (ApiContext,
75
+ * recordAuditEvent, logger, guardedFetch, …), built for this add-on and
76
+ * frozen. An add-on that takes two arguments keeps working until it moves.
77
+ */
78
+ register(engine: WikiEngine, config: Record<string, unknown>, host?: AddonHost): Promise<void> | void;
79
+ /**
80
+ * Optional health check returning add-on status details.
81
+ */
82
+ status?(): Promise<AddonStatusDetails> | AddonStatusDetails;
83
+ /**
84
+ * Optional `/profile` page extension hook (#534).
85
+ *
86
+ * When implemented, the addon contributes a section to the core profile
87
+ * page rather than maintaining a standalone settings route. The returned
88
+ * `html` is inserted **inside** the core preferences form, so the form
89
+ * fields it carries are submitted with the existing `POST /preferences`
90
+ * and surfaced to `saveProfileSection()` for persistence.
91
+ *
92
+ * Return `null` to opt out per-user (e.g. when the addon's feature is
93
+ * disabled for this user's roles).
94
+ *
95
+ * ⚠️ SECURITY — the returned `html` is rendered with EJS `<%- %>` (RAW,
96
+ * NOT escaped) inside the user's `/profile` form. **The addon is fully
97
+ * responsible for escaping any user-controlled data it interpolates.**
98
+ * Use `<%= %>` in your own EJS templates, or an explicit escape helper.
99
+ * A buggy `profileSection()` that string-concatenates user input becomes
100
+ * stored-XSS against every user who views their profile.
101
+ *
102
+ * Two specific things to avoid:
103
+ * - Do NOT emit `<input name="_csrf" …>` — that would override the
104
+ * host form's CSRF token and corrupt the user's save.
105
+ * - Do NOT emit a wrapping `<form>` — your fields submit with the host
106
+ * preferences form. A nested `<form>` is invalid HTML and browsers
107
+ * will hoist your inputs unpredictably.
108
+ *
109
+ * RECOMMENDED — include a hidden marker so your paired `saveProfileSection`
110
+ * can distinguish "section was rendered but checkboxes unchecked" from
111
+ * "section was not rendered at all" (addon disabled mid-session, cached
112
+ * form). Without it, absent checkbox fields look identical to "user
113
+ * submitted no such field" and you may silently clobber stored values.
114
+ */
115
+ profileSection?(user: AddonProfileUser): Promise<AddonProfileSection | null> | AddonProfileSection | null;
116
+ /**
117
+ * Optional save handler paired with `profileSection()` (#534).
118
+ *
119
+ * Receives a shallow-cloned `req.body` from `POST /preferences`; the addon
120
+ * extracts whichever fields its `profileSection()` form rendered and
121
+ * persists them. Errors are caught by AddonsManager and logged — they
122
+ * do NOT block the core-preferences save or other addons' saves.
123
+ *
124
+ * Two pitfalls the implementation must guard against (the host cannot
125
+ * do this for you):
126
+ *
127
+ * 1. ABSENT-FIELD CLOBBER. HTML checkboxes only submit when checked, so
128
+ * an absent key is ambiguous between "user unchecked it" and "the
129
+ * field was never rendered". Without a presence signal (see the
130
+ * recommended hidden marker on `profileSection()`), writing
131
+ * `body['x'] === 'on'` unconditionally turns every save where the
132
+ * section was hidden into a stealth reset to `false`.
133
+ *
134
+ * 2. CONFIG-GATED FIELDS. If your partial gates a field behind an admin
135
+ * config flag (`<% if (enabled) %>`), your save handler MUST re-check
136
+ * the same flag server-side before writing — otherwise a crafted
137
+ * POST can bypass the admin's gate.
138
+ *
139
+ * __Since #1234 the first argument is the caller's context, not a name.__
140
+ * `ctx` is the request's subject (`PermissionSubject`: `username`, `roles`,
141
+ * `isAuthenticated`, `ipAddress`, `viaToken` / `viaShare` when delegated)
142
+ * — forward it to whatever you write (`userManager.updateUser(ctx.username,
143
+ * updates, ctx)`), never rebuild one from `ctx.username`. An addon still
144
+ * written against the old `(username, body)` shape gets a warning at load
145
+ * naming it; its saves then fail per call and are logged.
146
+ */
147
+ saveProfileSection?(ctx: ActorContext, body: Record<string, unknown>): Promise<void> | void;
148
+ /**
149
+ * Optional cleanup on app shutdown.
150
+ */
151
+ shutdown?(): Promise<void> | void;
152
+ }
153
+ /**
154
+ * Returned from `AddonModule.profileSection()` — a single section rendered as
155
+ * a card inside the `/profile` preferences form. `html` is inserted with `<%-`
156
+ * (raw); the addon is responsible for any escaping of its own data.
157
+ */
158
+ export interface AddonProfileSection {
159
+ /** Heading shown in the card header (e.g. "Journal"). */
160
+ title: string;
161
+ /** Raw HTML rendered inside the card body; should contain form input fields. */
162
+ html: string;
163
+ }
164
+ /**
165
+ * One entry per addon that contributed a profile section, returned by
166
+ * `AddonsManager.getProfileSections()`. `addonName` is the registry key —
167
+ * useful for debug logging and CSS scoping.
168
+ */
169
+ export interface AddonProfileSectionEntry extends AddonProfileSection {
170
+ addonName: string;
171
+ }
172
+ /**
173
+ * Shape of the `ngdpbase` key in an add-on's package.json.
174
+ * All fields are optional — omitting the key entirely is valid.
175
+ */
176
+ export interface AddonManifest {
177
+ /**
178
+ * Canonical addon identity (#927). Authoritative id used as the registry
179
+ * key, the `ngdpbase.addons.<slug>.enabled` config key, dependency
180
+ * references, and the boot-time validator's match — read statically from
181
+ * `package.json` with no module import. When absent, identity falls back
182
+ * to the package/folder name (minus a conventional trailing `-addon`).
183
+ * The module's exported `name` is a display label and must equal this.
184
+ */
185
+ slug?: string;
186
+ /** 'domain' = this addon IS the site identity; 'additive' = augments an existing wiki */
187
+ type?: 'domain' | 'additive';
188
+ /**
189
+ * The oldest ngdpbase release this addon runs on (`x.y.z`). Checked before
190
+ * the addon is imported: on an older core it is not loaded, and its status
191
+ * says which release it needs, instead of failing at import with a missing
192
+ * module.
193
+ */
194
+ minVersion?: string;
195
+ /**
196
+ * Config keys this addon wants applied by default at load time.
197
+ * Each key is only set if the operator has not already explicitly
198
+ * set it in custom config. Values are ephemeral (merged config only,
199
+ * not persisted to app-custom-config.json).
200
+ */
201
+ domainDefaults?: Record<string, unknown>;
202
+ /** Capability flags this addon advertises */
203
+ capabilities?: string[];
204
+ /**
205
+ * Private store kinds this addon owns (#1414): `[{ id, encrypt }]`. Saved to
206
+ * configuration at the addon's first load, owned by its slug; from then on
207
+ * configuration wins and this is never consulted again. See
208
+ * docs/guides/addons-developer-guide.md "Declare a Private Store".
209
+ */
210
+ stores?: unknown;
211
+ }
212
+ /**
213
+ * Details returned by add-on's status() method
214
+ */
215
+ export interface AddonStatusDetails {
216
+ healthy: boolean;
217
+ database?: string;
218
+ records?: number;
219
+ message?: string;
220
+ [key: string]: unknown;
221
+ }
222
+ /**
223
+ * Card registered by an add-on for display on the admin dashboard.
224
+ * The card's live stats are sourced from the add-on's existing status() method.
225
+ */
226
+ export interface AddonDashboardCard {
227
+ /** Must match the add-on's registered name */
228
+ addonName: string;
229
+ /** Card heading */
230
+ title: string;
231
+ /** Font Awesome class, e.g. 'fas fa-wpforms' */
232
+ icon: string;
233
+ /** URL of the add-on's admin page */
234
+ adminUrl: string;
235
+ }
236
+ /**
237
+ * Status information returned by getStatus()
238
+ */
239
+ export interface AddonStatus {
240
+ name: string;
241
+ version: string;
242
+ description: string;
243
+ author: string;
244
+ enabled: boolean;
245
+ loaded: boolean;
246
+ dependencies: string[];
247
+ /** #1808: loaded first when installed and enabled; never required */
248
+ optionalDependencies: string[];
249
+ error: string | null;
250
+ /** 'domain' | 'additive' | undefined (unset addons behave as additive) */
251
+ type?: 'domain' | 'additive';
252
+ details?: AddonStatusDetails;
253
+ statusError?: string;
254
+ /** #443: add-on ships a deployable theme/ (theme.json sentinel present) */
255
+ hasTheme?: boolean;
256
+ /** #443: themes/<name>/ already exists in the instance */
257
+ themeDeployed?: boolean;
258
+ /** #1414: what turning this addon off would shut — shown in the Disable confirmation */
259
+ disableWarnings?: string[];
260
+ }
261
+ /**
262
+ * AddonsManager - Manages optional add-on modules
263
+ */
264
+ declare class AddonsManager extends BaseManager {
265
+ /** Map of discovered add-ons by name */
266
+ private addons;
267
+ /** Configured paths to addons directories (one or more) */
268
+ private addonsPaths;
269
+ /** Resolved absolute paths to addons directories */
270
+ private resolvedAddonsPaths;
271
+ /** #673: npm package globs (from `node_modules:` addons-path entries) to discover from node_modules. */
272
+ private npmAddonPatterns;
273
+ /** #1830: `<data>/addons`, where installed add-ons live; null when there is no data folder. */
274
+ private installedDir;
275
+ /** #1830: the installed add-ons verified this boot — the only ones loaded from `installedDir`. */
276
+ private verifiedInstalled;
277
+ /** Stylesheets registered by add-ons via registerStylesheet() */
278
+ private registeredStylesheets;
279
+ /** Dashboard cards registered by add-ons via registerDashboardCard() */
280
+ private dashboardCards;
281
+ /** Name of the first domain addon loaded — only one is permitted */
282
+ private domainAddonName;
283
+ constructor(engine: WikiEngine);
284
+ /**
285
+ * Initialize the AddonsManager
286
+ *
287
+ * Reads configuration, discovers add-ons, and loads enabled ones
288
+ * in dependency order.
289
+ */
290
+ initialize(config?: Record<string, unknown>): Promise<void>;
291
+ /**
292
+ * #1830: install the add-ons `ngdpbase.addons.install` names into
293
+ * `<data>/addons` (sha512 and Sigstore provenance checked), or verify the
294
+ * ones already there. An empty map (the shipped default) does nothing at all.
295
+ * Every request — registry, attestations, the Sigstore TUF repository — goes
296
+ * through the guarded client and the egress policy (#1139). A problem with one add-on
297
+ * never stops the boot: it is logged and posted to admins, and that add-on is
298
+ * simply not there to load. When anything on disk changed, the configuration
299
+ * is re-merged so the add-on's config/default-config.json layer applies on
300
+ * this boot, not the next.
301
+ */
302
+ private installConfiguredAddons;
303
+ /**
304
+ * `addon-install` (#1830): written after every check passed and before
305
+ * anything is written to disk. on-failure: refuse — code does not join the
306
+ * site unrecorded, so a record that cannot be written stops the install
307
+ * (recordSystemAction throws, and the installer writes nothing).
308
+ */
309
+ private recordAddonInstall;
310
+ /**
311
+ * `addon-verify-failed` (#1830): an integrity, provenance, tarball, manifest,
312
+ * lock or folder check refused (or, for a repaired folder, found it changed).
313
+ * on-failure: continue — the refusal has already happened; a lost record
314
+ * must not change it.
315
+ */
316
+ private recordVerifyFailed;
317
+ /** Tell admins about an install problem (#1830). Best-effort, like recordSeedFailure. */
318
+ private notifyInstallProblem;
319
+ /**
320
+ * Discover available add-ons by scanning all configured addons directories.
321
+ */
322
+ discoverAddons(): Promise<void>;
323
+ /**
324
+ * #1830: register the add-ons in `<data>/addons` that the installer verified
325
+ * this boot. Anything else there — a folder copied in by hand, or one whose
326
+ * entry left the install map — is not loaded, and admins are told.
327
+ */
328
+ private scanInstalledAddons;
329
+ /**
330
+ * Scan a single addons directory and register any add-ons found.
331
+ */
332
+ private scanAddonsDirectory;
333
+ /**
334
+ * #673 (packaged model): discover addons published as npm packages, matching a
335
+ * `node_modules:<glob>` addons-path entry (e.g. `node_modules:@jwilleke/*-addon`).
336
+ * Each matching package is loaded through the same slug/module/register()
337
+ * contract as bundled and drop-in addons — only discovery differs.
338
+ */
339
+ private scanNpmAddons;
340
+ /** Locate the node_modules directory (app root / cwd). */
341
+ private findNodeModules;
342
+ /** Expand a `@scope/glob` (or bare `glob`) pattern to package directories. */
343
+ private resolveNpmAddonDirs;
344
+ /**
345
+ * Load a single addon from its directory (index.js/ts + optional package.json
346
+ * `ngdpbase` manifest) and register it. Shared by directory scans and npm
347
+ * (#673) discovery; `sourceLabel` is for logging only. `source` selects the
348
+ * no-slug identity fallback (#927): a directory addon's folder name is its
349
+ * identity verbatim, while an npm package strips the conventional `-addon`
350
+ * suffix. Duplicate ids are skipped — since npm discovery runs last, a
351
+ * bundled/drop-in addon wins.
352
+ */
353
+ private registerAddonFromDir;
354
+ /**
355
+ * Why an addon cannot run on this core (#1757): its manifest's `minVersion`
356
+ * is newer than this release. Null when it can, or when it states none.
357
+ */
358
+ private incompatibility;
359
+ /**
360
+ * Check if an add-on is enabled in configuration
361
+ */
362
+ isEnabled(addonName: string): boolean;
363
+ /**
364
+ * Validate that an add-on can be safely disabled (#617).
365
+ *
366
+ * Mirrors the enable-time topological-sort dep check at `resolveLoadOrder()`:
367
+ * if any *enabled* add-on declares `dependencies: ['<addonName>']`, the
368
+ * disable is blocked and the dependent's name is returned. Callers should
369
+ * surface the blocker to the operator and refuse the disable.
370
+ *
371
+ * @param addonName The add-on the operator wants to disable.
372
+ * @returns `{ ok: true }` if safe to disable. Otherwise
373
+ * `{ ok: false, blockedBy: ['dep1', 'dep2', ...] }` with
374
+ * the names of every enabled addon that depends on it.
375
+ */
376
+ canDisable(addonName: string): {
377
+ ok: true;
378
+ } | {
379
+ ok: false;
380
+ blockedBy: string[];
381
+ };
382
+ /**
383
+ * Get add-on specific configuration
384
+ */
385
+ getAddonConfig(addonName: string): Record<string, unknown>;
386
+ /**
387
+ * Seed wiki pages shipped with an add-on.
388
+ *
389
+ * If the add-on directory contains a `pages/` subdirectory, any `.md` files
390
+ * found there are copied to the instance pages directory — but only if a file
391
+ * with the same name does not already exist (user edits are never overwritten).
392
+ *
393
+ * This mirrors how `required-pages/` seeds core pages at install time.
394
+ *
395
+ * @param addonName Name of the add-on (for logging)
396
+ * @param addonPath Filesystem path to the add-on directory
397
+ */
398
+ /**
399
+ * Default edit-protection for a seeded addon page (#971, addons.md §3).
400
+ *
401
+ * §3 settled the mechanism: the seeder stamps frontmatter `access`, which
402
+ * Tier 1 of the evaluator honours directly, needs no `PolicyEvaluator`
403
+ * change, and is hash-neutral (`pageSourceHash` covers the body only, so
404
+ * adding metadata cannot disturb the #920 reseed comparison).
405
+ *
406
+ * §9 settled *who owns what*, and the two must be read together. A blanket
407
+ * admin lock would contradict the operator's decision that domain content
408
+ * pages "should be normal pages, purely seeded by the addon" — the addon
409
+ * provides a starting corpus and then lets go. So the default follows §9's
410
+ * ownership column rather than locking everything:
411
+ *
412
+ * | Category | Purpose | Default |
413
+ * |---|---|---|
414
+ * | `system` | Feature UI, site chrome — infrastructure | admin-only edit |
415
+ * | `documentation` | Help / docs — addon-owned | admin-only edit |
416
+ * | `general` | Domain content, demo — **instance-owned** | no stamp |
417
+ * | anything else | unclassified, treated as addon-owned | admin-only edit |
418
+ *
419
+ * An addon can always override by declaring its own `access`, which is the
420
+ * documented escape hatch in §3 and the reason the default is a default.
421
+ *
422
+ * @param category - The page's resolved `system-category`
423
+ * @returns The access object to stamp, or undefined to leave the page unprotected
424
+ */
425
+ private defaultAddonPageAccess;
426
+ /**
427
+ * Surface a page-level seed failure beyond the boot log (#951).
428
+ *
429
+ * A boot-time log line is invisible ten minutes later, which is how a page
430
+ * that never seeded stays missing for weeks. Raising an operator
431
+ * notification makes the failure outlive the boot it happened on.
432
+ *
433
+ * Deliberately NOT fail-fast. Refusing to start because a third-party addon
434
+ * shipped one malformed page turns an authoring typo into an outage, and the
435
+ * #672 precedent does not transfer — that was the operator's own config
436
+ * naming a nonexistent addon, which the operator can fix. A vendor's
437
+ * malformed file is not in the operator's control. Skip the page, report
438
+ * loudly, surface it after boot.
439
+ *
440
+ * Best-effort: notification failure must never break seeding.
441
+ */
442
+ private recordSeedFailure;
443
+ /**
444
+ * #1559: tell admins what the configuration merge refused in the add-ons'
445
+ * system-categories, or overrode in the operator's file — logged there at
446
+ * boot, surfaced here once notifications exist. Not fail-fast, for the
447
+ * reason recordSeedFailure gives: an add-on's file is not in the operator's
448
+ * control. A refused entry fails closed (its category and vault do not
449
+ * exist, so nothing is written unsealed or public by mistake). Best-effort.
450
+ */
451
+ private notifySystemCategoryProblems;
452
+ private seedAddonPages;
453
+ /** Pages reseeded by the current `seedAddonPages` pass (#920). */
454
+ private lastReseedCount;
455
+ /**
456
+ * An addon page this site already holds: metadata tidy-ups (#971, #1003) and
457
+ * the opt-in content reseed (#920), then a search-index refresh. Called by the
458
+ * shared seeder for each source page that is live (#1406).
459
+ */
460
+ private reconcileSeededAddonPage;
461
+ private getInstanceThemesDir;
462
+ /**
463
+ * True if the add-on ships a deployable theme. `theme/theme.json` is the
464
+ * presence sentinel (mirrors `pages/` needing at least one `.md`).
465
+ */
466
+ addonShipsTheme(addonPath: string): boolean;
467
+ /**
468
+ * #443: Deploy an add-on's `theme/` into the instance `themes/<name>/`.
469
+ *
470
+ * When an add-on ships a `theme/` subdirectory it should land in the
471
+ * instance themes directory the same way `seedAddonPages()` handles
472
+ * `pages/`. ThemeManager then reads it via `ngdpbase.theme.active` (set by
473
+ * the addon's domainDefaults) — no ThemeManager change needed.
474
+ *
475
+ * @param addonName Add-on name (also the destination theme folder)
476
+ * @param addonPath Filesystem path to the add-on directory
477
+ * @param overwrite false (default) = first-boot copy, never overwrites an
478
+ * existing `themes/<name>/` (preserves instance edits);
479
+ * true = dashboard-triggered redeploy, replaces it.
480
+ * @returns Result describing what happened (for the admin UI).
481
+ */
482
+ seedAddonTheme(addonName: string, addonPath: string, overwrite?: boolean): Promise<{
483
+ ok: boolean;
484
+ deployed: boolean;
485
+ message: string;
486
+ }>;
487
+ /**
488
+ * #443: Dashboard-triggered theme (re)deploy — always overwrites so theme
489
+ * updates shipped by the add-on can be pulled in without a server restart
490
+ * (theme CSS is served as static files; a page reload suffices).
491
+ *
492
+ * @param addonName Add-on name
493
+ * @returns Result for the admin UI
494
+ */
495
+ deployAddonTheme(addonName: string): Promise<{
496
+ ok: boolean;
497
+ deployed: boolean;
498
+ message: string;
499
+ }>;
500
+ /**
501
+ * Load all enabled add-ons in dependency order
502
+ */
503
+ private loadAddons;
504
+ /**
505
+ * Resolve dependency order using topological sort
506
+ *
507
+ * @returns Array of add-on names in load order
508
+ * @throws Error if circular dependency detected
509
+ */
510
+ resolveLoadOrder(): string[];
511
+ /**
512
+ * Load a single add-on
513
+ */
514
+ loadAddon(addonName: string): Promise<void>;
515
+ /**
516
+ * What turning this addon off would shut (#1414): one line per store kind it
517
+ * owns that users hold data in. Turning it off is warned, never refused —
518
+ * nothing is destroyed, the kind stays in configuration, every copy stays on
519
+ * disk (sealed ones keep their wrapped key), and turning it back on reopens
520
+ * them. Separate from `canDisable`, which stays synchronous: counting copies
521
+ * reads the disk.
522
+ */
523
+ storeDisableWarnings(addonName: string): Promise<string[]>;
524
+ /**
525
+ * Where the addon that owns a store kind stands, for the store door (#1414):
526
+ * loaded, enabled but failed to load, turned off, or not on this site.
527
+ */
528
+ storeOwnerState(addonName: string): StoreOwnerState;
529
+ /**
530
+ * Inject domainDefaults from the add-on's package.json ngdpbase key
531
+ * into the merged config, but only for keys not already explicitly
532
+ * set by the operator in custom config.
533
+ */
534
+ private applyDomainDefaults;
535
+ /**
536
+ * Get status of all discovered add-ons
537
+ */
538
+ getStatus(): Promise<AddonStatus[]>;
539
+ /**
540
+ * Collect profile-page sections contributed by loaded addons (#534).
541
+ *
542
+ * Iterates every loaded addon that implements `profileSection()`, fans
543
+ * out IN PARALLEL via `Promise.allSettled`, then packages results for
544
+ * `profile.ejs`. Errors and `null` returns are skipped (malformed shapes
545
+ * are logged so addon authors get a diagnostic instead of an invisibly-
546
+ * missing card). One failing addon must not break the profile page.
547
+ *
548
+ * Mirrors the `getStatus()` pattern but runs in parallel so page-render
549
+ * latency is bounded by the slowest addon, not the sum.
550
+ *
551
+ * Result order follows registration order of the addons map so the
552
+ * displayed section order is stable across renders.
553
+ */
554
+ getProfileSections(user: AddonProfileUser): Promise<AddonProfileSectionEntry[]>;
555
+ /**
556
+ * #1234: say at load when an addon still implements the pre-#1234 hook.
557
+ *
558
+ * The two shapes have the same arity, so the only tell is the name the
559
+ * addon gave its first parameter — a heuristic, and stated as one. A false
560
+ * negative costs nothing new: the save still fails per call and is logged
561
+ * by `saveProfileSections`. A hit turns that silent stop into a boot line
562
+ * naming the addon and the migration.
563
+ */
564
+ private warnOldProfileHook;
565
+ /**
566
+ * Fan out `POST /preferences` body to every loaded addon that registered a
567
+ * `saveProfileSection()` handler (#534).
568
+ *
569
+ * Runs SEQUENTIALLY — each addon's read-modify-write of user preferences
570
+ * (typical implementation: `getUser → merge → updateUser`) must see the
571
+ * previous addon's writes. Parallel execution would create a same-snapshot
572
+ * race where two addons read identical preferences, each merge their own
573
+ * keys, and the second writer clobbers the first writer's changes.
574
+ * Self-inflicted by the original #534 PR; reverted to sequential here.
575
+ *
576
+ * (`getProfileSections()` above stays parallel — it's a read path with no
577
+ * cross-addon write contention.)
578
+ *
579
+ * Body shape: the host normalizes whatever shape qs produced into FLAT
580
+ * dotted keys before fan-out, so addons always see `body['journal.X']`
581
+ * regardless of whether body-parser was configured with `allowDots: true`
582
+ * (which would have produced `body.journal.X` instead). This is a
583
+ * defensive contract — today's `express.urlencoded({ extended: true })`
584
+ * leaves dotted form-field names flat by default, but a future config
585
+ * change shouldn't silently break addons. See `flattenDottedKeys`.
586
+ *
587
+ * Errors are caught per-addon and logged so one bad addon cannot block
588
+ * other addons' saves or the core-preferences save.
589
+ */
590
+ saveProfileSections(ctx: ActorContext, body: Record<string, unknown>): Promise<void>;
591
+ /**
592
+ * Get count of loaded add-ons
593
+ */
594
+ private getLoadedCount;
595
+ /**
596
+ * Register a stylesheet URL to be injected into every page's <head>.
597
+ *
598
+ * Call this from your add-on's register() function:
599
+ * ```js
600
+ * const addonsManager = engine.getManager('AddonsManager');
601
+ * addonsManager.registerStylesheet('/addons/my-addon/css/style.css');
602
+ * ```
603
+ *
604
+ * The URL must be publicly accessible (served via Express static or a CDN).
605
+ * Add-on CSS files under `addons/<name>/public/` are automatically served at
606
+ * `/addons/<name>/public/` when the server starts.
607
+ *
608
+ * @param url Public URL of the stylesheet (e.g. '/addons/my-addon/css/style.css')
609
+ * @param addonName Optional: name of the registering add-on (for logging)
610
+ */
611
+ registerStylesheet(url: string, addonName?: string): void;
612
+ /**
613
+ * Return the ordered list of stylesheet URLs registered by all add-ons.
614
+ * Called by the template layer to inject <link> tags into <head>.
615
+ */
616
+ getRegisteredStylesheets(): string[];
617
+ /**
618
+ * Register a dashboard card for this add-on. Called from the add-on's register() function.
619
+ * Live stats shown on the card are sourced from the add-on's existing status() method.
620
+ */
621
+ registerDashboardCard(card: AddonDashboardCard): void;
622
+ /**
623
+ * Return all registered dashboard cards in registration order.
624
+ * Called by the admin dashboard route to populate the addon cards row.
625
+ */
626
+ getDashboardCards(): AddonDashboardCard[];
627
+ /**
628
+ * Get list of all discovered add-on names
629
+ */
630
+ getAddonNames(): string[];
631
+ /**
632
+ * Return the `pages/` directory path for each enabled addon that has one.
633
+ * Used by Required Pages Sync to surface addon pages alongside required pages.
634
+ */
635
+ getEnabledAddonPagesDirectories(): Array<{
636
+ name: string;
637
+ pagesDir: string;
638
+ }>;
639
+ /**
640
+ * #930: addon-seeded instance pages whose source file the addon **no longer
641
+ * ships** ("source removed"). Leave-and-flag — this only *detects*; nothing is
642
+ * deleted here. The admin surface surfaces these and offers an opt-in,
643
+ * versioned removal.
644
+ *
645
+ * Enumeration uses the indexed `system-category: addon` (via the search index)
646
+ * to narrow to addon pages — a small candidate set — instead of scanning the
647
+ * whole page store. Each candidate is attributed to its addon via the `addon`
648
+ * frontmatter stamp and diffed against that addon's CURRENT source UUID set.
649
+ * If search is unavailable, returns `[]` (detection is best-effort, never a
650
+ * source of deletions).
651
+ */
652
+ findOrphanedAddonPages(): Promise<Array<{
653
+ addonName: string;
654
+ uuid: string;
655
+ slug: string;
656
+ title: string;
657
+ userModified: boolean;
658
+ }>>;
659
+ /**
660
+ * Back-fill the page index's `addon` stamp for pages seeded before the field
661
+ * was indexed.
662
+ *
663
+ * Without this, orphan detection is blind on any existing instance: the field
664
+ * is written on save, and seeded pages are deliberately not re-saved once
665
+ * they exist. Cheap — it walks only each addon's own source pages (tens of
666
+ * files), never the whole page tree.
667
+ */
668
+ private backfillIndexAddonStamps;
669
+ /**
670
+ * Check if an add-on exists (discovered)
671
+ */
672
+ hasAddon(addonName: string): boolean;
673
+ /**
674
+ * Check if an add-on is loaded
675
+ */
676
+ isLoaded(addonName: string): boolean;
677
+ /**
678
+ * Graceful shutdown of all loaded add-ons
679
+ */
680
+ shutdown(): Promise<void>;
681
+ /**
682
+ * Backup add-on manager state
683
+ */
684
+ backup(): Promise<BackupData>;
685
+ /**
686
+ * Restore is not supported for AddonsManager
687
+ * (add-ons are discovered at startup)
688
+ */
689
+ restore(backupData: BackupData): Promise<void>;
690
+ }
691
+ export default AddonsManager;