@sonicjs-cms/core 2.19.0 → 3.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/README.md +4 -3
  2. package/dist/admin-documents-form.template-KN7JF66Q.cjs +19 -0
  3. package/dist/{admin-layout-catalyst.template-UMTIN66R.js.map → admin-documents-form.template-KN7JF66Q.cjs.map} +1 -1
  4. package/dist/admin-documents-form.template-NLSI6Z42.js +6 -0
  5. package/dist/{admin-layout-catalyst.template-HFD37TY5.cjs.map → admin-documents-form.template-NLSI6Z42.js.map} +1 -1
  6. package/dist/admin-layout-catalyst.template-WHJGSWWD.js +7 -0
  7. package/dist/admin-layout-catalyst.template-WHJGSWWD.js.map +1 -0
  8. package/dist/admin-layout-catalyst.template-ZK5HD545.cjs +17 -0
  9. package/dist/admin-layout-catalyst.template-ZK5HD545.cjs.map +1 -0
  10. package/dist/app-Bo0X1OWX.d.ts +1268 -0
  11. package/dist/app-Do66yCcV.d.cts +1268 -0
  12. package/dist/cache-DDARE4QE.js +4 -0
  13. package/dist/cache-DDARE4QE.js.map +1 -0
  14. package/dist/cache-LVYS4BPL.cjs +33 -0
  15. package/dist/cache-LVYS4BPL.cjs.map +1 -0
  16. package/dist/chunk-2CB4KY7I.cjs +771 -0
  17. package/dist/chunk-2CB4KY7I.cjs.map +1 -0
  18. package/dist/chunk-2JTWQZHG.cjs +276 -0
  19. package/dist/chunk-2JTWQZHG.cjs.map +1 -0
  20. package/dist/{chunk-55RDMDOP.js → chunk-3TB6AT6X.js} +148 -55
  21. package/dist/chunk-3TB6AT6X.js.map +1 -0
  22. package/dist/{chunk-E4YFJBM2.cjs → chunk-673S7EBY.cjs} +621 -829
  23. package/dist/chunk-673S7EBY.cjs.map +1 -0
  24. package/dist/{chunk-ON5ZMSU4.js → chunk-6JQOUUOB.js} +3 -3
  25. package/dist/chunk-6JQOUUOB.js.map +1 -0
  26. package/dist/chunk-7LHUVREV.cjs +158 -0
  27. package/dist/chunk-7LHUVREV.cjs.map +1 -0
  28. package/dist/chunk-AI663NBO.js +821 -0
  29. package/dist/chunk-AI663NBO.js.map +1 -0
  30. package/dist/chunk-BDDABDAB.cjs +1149 -0
  31. package/dist/chunk-BDDABDAB.cjs.map +1 -0
  32. package/dist/chunk-BLMTL57B.js +767 -0
  33. package/dist/chunk-BLMTL57B.js.map +1 -0
  34. package/dist/{chunk-ABB34XUS.cjs → chunk-CHCBHIBM.cjs} +667 -19
  35. package/dist/chunk-CHCBHIBM.cjs.map +1 -0
  36. package/dist/chunk-DNQCEKUK.cjs +327 -0
  37. package/dist/chunk-DNQCEKUK.cjs.map +1 -0
  38. package/dist/chunk-EF2NQUIQ.js +323 -0
  39. package/dist/chunk-EF2NQUIQ.js.map +1 -0
  40. package/dist/chunk-GCDZZNIN.js +192 -0
  41. package/dist/chunk-GCDZZNIN.js.map +1 -0
  42. package/dist/{chunk-XWIA3HVX.js → chunk-HDWE5FRJ.js} +6 -1249
  43. package/dist/chunk-HDWE5FRJ.js.map +1 -0
  44. package/dist/chunk-HIKBY7MS.cjs +70 -0
  45. package/dist/chunk-HIKBY7MS.cjs.map +1 -0
  46. package/dist/chunk-IESEVHXL.js +66 -0
  47. package/dist/chunk-IESEVHXL.js.map +1 -0
  48. package/dist/chunk-IVPRUGTY.js +242 -0
  49. package/dist/chunk-IVPRUGTY.js.map +1 -0
  50. package/dist/{chunk-JZVHLLSI.cjs → chunk-IXUHXTHW.cjs} +2 -151
  51. package/dist/chunk-IXUHXTHW.cjs.map +1 -0
  52. package/dist/{chunk-7A4CB7T3.cjs → chunk-J5DCX3F6.cjs} +509 -91
  53. package/dist/chunk-J5DCX3F6.cjs.map +1 -0
  54. package/dist/chunk-J6JTWD2A.cjs +100 -0
  55. package/dist/chunk-J6JTWD2A.cjs.map +1 -0
  56. package/dist/chunk-JEQ7FLOD.cjs +199 -0
  57. package/dist/chunk-JEQ7FLOD.cjs.map +1 -0
  58. package/dist/chunk-K25XHMM3.js +566 -0
  59. package/dist/chunk-K25XHMM3.js.map +1 -0
  60. package/dist/{chunk-R4FOLLFB.cjs → chunk-LO6MEPRW.cjs} +8730 -11520
  61. package/dist/chunk-LO6MEPRW.cjs.map +1 -0
  62. package/dist/chunk-MHP7HYTT.js +273 -0
  63. package/dist/chunk-MHP7HYTT.js.map +1 -0
  64. package/dist/chunk-MP3Q2W76.js +387 -0
  65. package/dist/chunk-MP3Q2W76.js.map +1 -0
  66. package/dist/{chunk-OHYBNCVL.cjs → chunk-MVIZJOO5.cjs} +10 -1256
  67. package/dist/chunk-MVIZJOO5.cjs.map +1 -0
  68. package/dist/{chunk-UYJ6TJHX.cjs → chunk-NAVPFIG5.cjs} +148 -55
  69. package/dist/chunk-NAVPFIG5.cjs.map +1 -0
  70. package/dist/chunk-NUKJ54GA.cjs +245 -0
  71. package/dist/chunk-NUKJ54GA.cjs.map +1 -0
  72. package/dist/{chunk-BU7SFHGP.js → chunk-QZGABF2M.js} +3 -149
  73. package/dist/chunk-QZGABF2M.js.map +1 -0
  74. package/dist/{chunk-JZV22DEV.js → chunk-RJV6UXLZ.js} +611 -817
  75. package/dist/chunk-RJV6UXLZ.js.map +1 -0
  76. package/dist/chunk-RNZFGN4R.js +88 -0
  77. package/dist/chunk-RNZFGN4R.js.map +1 -0
  78. package/dist/{chunk-TFNTM3OA.js → chunk-SL6XS6YT.js} +645 -15
  79. package/dist/chunk-SL6XS6YT.js.map +1 -0
  80. package/dist/{chunk-OCL3HMEG.js → chunk-SO2T3OXR.js} +7004 -9807
  81. package/dist/chunk-SO2T3OXR.js.map +1 -0
  82. package/dist/chunk-VQHAJUZZ.cjs +408 -0
  83. package/dist/chunk-VQHAJUZZ.cjs.map +1 -0
  84. package/dist/{chunk-4NPCDK6B.js → chunk-VZ3NHR5Z.js} +505 -90
  85. package/dist/chunk-VZ3NHR5Z.js.map +1 -0
  86. package/dist/{chunk-4ZSNJDLS.cjs → chunk-WULONYGB.cjs} +9 -9
  87. package/dist/chunk-WULONYGB.cjs.map +1 -0
  88. package/dist/chunk-XFQHK64T.js +154 -0
  89. package/dist/chunk-XFQHK64T.js.map +1 -0
  90. package/dist/chunk-YA3TJ65D.cjs +575 -0
  91. package/dist/chunk-YA3TJ65D.cjs.map +1 -0
  92. package/dist/chunk-YP7GW2G5.cjs +866 -0
  93. package/dist/chunk-YP7GW2G5.cjs.map +1 -0
  94. package/dist/{chunk-QFWHAFEO.js → chunk-ZEZ245PW.js} +148 -858
  95. package/dist/chunk-ZEZ245PW.js.map +1 -0
  96. package/dist/{collection-config-B4PG-AaF.d.cts → collection-config-JgHOpFCG.d.cts} +30 -2
  97. package/dist/{collection-config-B4PG-AaF.d.ts → collection-config-JgHOpFCG.d.ts} +30 -2
  98. package/dist/config-HFXANXCC.js +6 -0
  99. package/dist/config-HFXANXCC.js.map +1 -0
  100. package/dist/config-ON6FNMYX.cjs +19 -0
  101. package/dist/config-ON6FNMYX.cjs.map +1 -0
  102. package/dist/define-plugin-BzNHc1ZI.d.ts +1321 -0
  103. package/dist/define-plugin-IWDKYaVm.d.cts +1321 -0
  104. package/dist/document-projection-TDWRJX3Z.cjs +13 -0
  105. package/dist/document-projection-TDWRJX3Z.cjs.map +1 -0
  106. package/dist/document-projection-YYMC6I4U.js +4 -0
  107. package/dist/document-projection-YYMC6I4U.js.map +1 -0
  108. package/dist/index.cjs +13734 -4328
  109. package/dist/index.cjs.map +1 -1
  110. package/dist/index.d.cts +329 -492
  111. package/dist/index.d.ts +329 -492
  112. package/dist/index.js +13385 -3998
  113. package/dist/index.js.map +1 -1
  114. package/dist/middleware.cjs +36 -32
  115. package/dist/middleware.d.cts +50 -7
  116. package/dist/middleware.d.ts +50 -7
  117. package/dist/middleware.js +7 -3
  118. package/dist/migrations-DK2YFPLR.js +4 -0
  119. package/dist/{migrations-H5IXZNCO.js.map → migrations-DK2YFPLR.js.map} +1 -1
  120. package/dist/migrations-YHNHTJOO.cjs +13 -0
  121. package/dist/{migrations-566IIPS2.cjs.map → migrations-YHNHTJOO.cjs.map} +1 -1
  122. package/dist/{plugin-bootstrap-DfVerYV4.d.cts → plugin-bootstrap-B8ThJU21.d.cts} +4315 -1661
  123. package/dist/{plugin-bootstrap-P_ciLp_C.d.ts → plugin-bootstrap-qu8hJgUt.d.ts} +4315 -1661
  124. package/dist/plugins.cjs +171 -12
  125. package/dist/plugins.d.cts +36 -2
  126. package/dist/plugins.d.ts +36 -2
  127. package/dist/plugins.js +5 -2
  128. package/dist/rbac-O73MFKDA.js +5 -0
  129. package/dist/rbac-O73MFKDA.js.map +1 -0
  130. package/dist/rbac-VONLJJKB.cjs +14 -0
  131. package/dist/rbac-VONLJJKB.cjs.map +1 -0
  132. package/dist/routes.cjs +41 -45
  133. package/dist/routes.d.cts +56 -146
  134. package/dist/routes.d.ts +56 -146
  135. package/dist/routes.js +17 -9
  136. package/dist/services.cjs +39 -72
  137. package/dist/services.d.cts +79 -54
  138. package/dist/services.d.ts +79 -54
  139. package/dist/services.js +6 -3
  140. package/dist/templates.cjs +17 -29
  141. package/dist/templates.d.cts +1 -66
  142. package/dist/templates.d.ts +1 -66
  143. package/dist/templates.js +3 -3
  144. package/dist/types-Dea1eNxU.d.cts +286 -0
  145. package/dist/types-Dea1eNxU.d.ts +286 -0
  146. package/dist/types.d.cts +1 -1
  147. package/dist/types.d.ts +1 -1
  148. package/dist/utils.cjs +18 -17
  149. package/dist/utils.d.cts +1 -1
  150. package/dist/utils.d.ts +1 -1
  151. package/dist/utils.js +2 -1
  152. package/migrations/0001_core.sql +184 -0
  153. package/migrations/0002_documents.sql +163 -0
  154. package/package.json +12 -7
  155. package/dist/admin-layout-catalyst.template-HFD37TY5.cjs +0 -17
  156. package/dist/admin-layout-catalyst.template-UMTIN66R.js +0 -7
  157. package/dist/app-C9esKLmh.d.cts +0 -112
  158. package/dist/app-C9esKLmh.d.ts +0 -112
  159. package/dist/chunk-4NPCDK6B.js.map +0 -1
  160. package/dist/chunk-4ZSNJDLS.cjs.map +0 -1
  161. package/dist/chunk-55RDMDOP.js.map +0 -1
  162. package/dist/chunk-635JAMSE.cjs +0 -653
  163. package/dist/chunk-635JAMSE.cjs.map +0 -1
  164. package/dist/chunk-7A4CB7T3.cjs.map +0 -1
  165. package/dist/chunk-ABB34XUS.cjs.map +0 -1
  166. package/dist/chunk-BU7SFHGP.js.map +0 -1
  167. package/dist/chunk-E4YFJBM2.cjs.map +0 -1
  168. package/dist/chunk-EXNEW5US.js +0 -648
  169. package/dist/chunk-EXNEW5US.js.map +0 -1
  170. package/dist/chunk-JZV22DEV.js.map +0 -1
  171. package/dist/chunk-JZVHLLSI.cjs.map +0 -1
  172. package/dist/chunk-OCL3HMEG.js.map +0 -1
  173. package/dist/chunk-OHYBNCVL.cjs.map +0 -1
  174. package/dist/chunk-ON5ZMSU4.js.map +0 -1
  175. package/dist/chunk-QFWHAFEO.js.map +0 -1
  176. package/dist/chunk-R4FOLLFB.cjs.map +0 -1
  177. package/dist/chunk-RLMUFFUD.cjs +0 -2219
  178. package/dist/chunk-RLMUFFUD.cjs.map +0 -1
  179. package/dist/chunk-TFNTM3OA.js.map +0 -1
  180. package/dist/chunk-UYJ6TJHX.cjs.map +0 -1
  181. package/dist/chunk-WAEQXGCX.cjs +0 -1898
  182. package/dist/chunk-WAEQXGCX.cjs.map +0 -1
  183. package/dist/chunk-XWIA3HVX.js.map +0 -1
  184. package/dist/chunk-ZYAYUIZE.js +0 -2217
  185. package/dist/chunk-ZYAYUIZE.js.map +0 -1
  186. package/dist/migrations-566IIPS2.cjs +0 -13
  187. package/dist/migrations-H5IXZNCO.js +0 -4
  188. package/dist/plugin-manager-BoM3Q7o7.d.cts +0 -328
  189. package/dist/plugin-manager-Efx9RyDX.d.ts +0 -328
  190. package/migrations/001_initial_schema.sql +0 -170
  191. package/migrations/002_faq_plugin.sql +0 -86
  192. package/migrations/003_stage5_enhancements.sql +0 -121
  193. package/migrations/004_stage6_user_management.sql +0 -183
  194. package/migrations/005_stage7_workflow_automation.sql +0 -294
  195. package/migrations/006_plugin_system.sql +0 -155
  196. package/migrations/007_demo_login_plugin.sql +0 -23
  197. package/migrations/008_fix_slug_validation.sql +0 -22
  198. package/migrations/009_system_logging.sql +0 -57
  199. package/migrations/011_config_managed_collections.sql +0 -15
  200. package/migrations/012_testimonials_plugin.sql +0 -80
  201. package/migrations/013_code_examples_plugin.sql +0 -177
  202. package/migrations/014_fix_plugin_registry.sql +0 -88
  203. package/migrations/015_add_remaining_plugins.sql +0 -89
  204. package/migrations/016_remove_duplicate_cache_plugin.sql +0 -17
  205. package/migrations/017_auth_configurable_fields.sql +0 -49
  206. package/migrations/018_settings_table.sql +0 -23
  207. package/migrations/019_remove_blog_posts_collection.sql +0 -15
  208. package/migrations/020_add_email_plugin.sql +0 -22
  209. package/migrations/021_add_magic_link_auth_plugin.sql +0 -42
  210. package/migrations/022_add_tinymce_plugin.sql +0 -25
  211. package/migrations/023_add_easy_mdx_plugin.sql +0 -25
  212. package/migrations/024_add_quill_editor_plugin.sql +0 -25
  213. package/migrations/025_add_easymde_plugin.sql +0 -25
  214. package/migrations/026_add_otp_login.sql +0 -42
  215. package/migrations/027_fix_slug_field_type.sql +0 -18
  216. package/migrations/028_fix_slug_field_type_in_schemas.sql +0 -30
  217. package/migrations/029_add_forms_system.sql +0 -184
  218. package/migrations/030_add_turnstile_to_forms.sql +0 -14
  219. package/migrations/031_ai_search_plugin.sql +0 -45
  220. package/migrations/032_user_profiles.sql +0 -37
  221. package/migrations/033_form_content_integration.sql +0 -19
  222. package/migrations/034_security_audit_plugin.sql +0 -27
  223. package/migrations/035_user_profiles_data_column.sql +0 -16
  224. package/migrations/036_analytics_events.sql +0 -22
@@ -0,0 +1,1321 @@
1
+ import { i as HookSystemLike, m as TypedHooks, E as EmailProvider, s as EmailLogRow, t as EmailMessage, q as SendResult, f as HookEventName, l as TypedHookHandler } from './types-Dea1eNxU.js';
2
+ import { Hono } from 'hono';
3
+ import { d as HookSystem, b as HookHandler, k as PluginHook, v as PluginValidator$1, P as Plugin, u as PluginValidationResult, q as PluginRegistry, i as PluginConfig, t as PluginStatus, m as PluginManager$1, j as PluginContext } from './plugin-DDYetMF-.js';
4
+
5
+ /**
6
+ * Plugin cron surface
7
+ *
8
+ * A plugin declares scheduled work as data:
9
+ *
10
+ * crons: [{ schedule: '*\/15 * * * *', hookFamily: 'email-reconciliation' }]
11
+ * async onCronTick(event, ctx) {
12
+ * if (event.hookFamily !== 'email-reconciliation') return
13
+ * ...
14
+ * }
15
+ *
16
+ * Declaring a schedule does NOT by itself run anything (same as Payload's jobs
17
+ * queue): on Cloudflare Workers the execution mechanism is a Cron Trigger, which
18
+ * the runtime delivers to the Worker's `scheduled()` handler. `createScheduledHandler`
19
+ * builds that handler; it fans a fired trigger out to the plugins whose declared
20
+ * schedule matches, tagging each call with the matching `hookFamily` so a plugin
21
+ * with several crons can branch on it.
22
+ *
23
+ * The consumer must still register the cron expressions in `wrangler.toml`
24
+ * (`[triggers] crons = [...]`); the schedules declared here are the source of
25
+ * truth for which plugin handles which expression.
26
+ *
27
+ * Cron runs OUTSIDE the HTTP request context (no per-request `c.env`), which is
28
+ * exactly why services are reached through env-independent singletons.
29
+ */
30
+
31
+ /** A scheduled-work declaration on a plugin. */
32
+ interface CronDeclaration {
33
+ /** Cron expression, e.g. `'*\/15 * * * *'`. Must also be in wrangler.toml triggers. */
34
+ schedule: string;
35
+ /** Logical family the handler branches on (e.g. `'email-reconciliation'`). */
36
+ hookFamily: string;
37
+ }
38
+ /** The event passed to a plugin's `onCronTick`. */
39
+ interface CronTickEvent {
40
+ /** The cron expression that fired. */
41
+ cron: string;
42
+ /** Epoch ms the trigger was scheduled for. */
43
+ scheduledTime: number;
44
+ /** The matching declaration's family, so a multi-cron plugin can branch. */
45
+ hookFamily: string;
46
+ }
47
+ /** Context handed to `onCronTick`. Mirrors the boot context; carries no request env. */
48
+ interface CronContext {
49
+ /** The live hook system (so cron work can dispatch/observe hooks). */
50
+ hooks: HookSystemLike;
51
+ /** Runtime bindings supplied by the Worker's scheduled() invocation. */
52
+ env?: Record<string, unknown>;
53
+ [key: string]: unknown;
54
+ }
55
+ /**
56
+ * Structural contract this module needs from a plugin. Deliberately minimal (not
57
+ * the full `Plugin`) so the `src`/`dist` duplicate identities and user plugins all
58
+ * satisfy it without casts.
59
+ */
60
+ interface CronablePlugin {
61
+ name?: string;
62
+ crons?: CronDeclaration[];
63
+ onCronTick?: (event: CronTickEvent, context: CronContext) => void | Promise<void>;
64
+ }
65
+ /** A flattened view of every declared cron across a set of plugins. */
66
+ interface CollectedCron {
67
+ plugin: string;
68
+ schedule: string;
69
+ hookFamily: string;
70
+ }
71
+ /** Flatten every plugin's `crons[]` into one list (for diagnostics / wrangler sync). */
72
+ declare function collectCrons(plugins: Array<CronablePlugin | undefined | null>): CollectedCron[];
73
+ /** The set of distinct cron expressions declared across plugins. */
74
+ declare function collectCronSchedules(plugins: Array<CronablePlugin | undefined | null>): string[];
75
+ /** Outcome of a cron dispatch. */
76
+ interface CronDispatchResult {
77
+ /** Plugins whose `onCronTick` ran successfully (one entry per matching declaration). */
78
+ invoked: Array<{
79
+ plugin: string;
80
+ hookFamily: string;
81
+ }>;
82
+ /** Per-plugin errors (dispatch never throws). */
83
+ errors: Array<{
84
+ plugin: string;
85
+ hookFamily: string;
86
+ error: unknown;
87
+ }>;
88
+ /** True if the fired cron matched no declared schedule. */
89
+ unmatched: boolean;
90
+ }
91
+ /**
92
+ * Dispatch one fired cron expression to the plugins that declared it.
93
+ *
94
+ * For each plugin whose `crons[]` contains a declaration with `schedule === cron`,
95
+ * its `onCronTick` is invoked once per matching declaration, with the event's
96
+ * `hookFamily` set to that declaration's family. Errors are isolated per plugin.
97
+ */
98
+ declare function dispatchCronTick(plugins: Array<CronablePlugin | undefined | null>, cron: string, scheduledTime: number, context: CronContext): Promise<CronDispatchResult>;
99
+ /** Minimal shape of a Cloudflare `ScheduledController`. */
100
+ interface ScheduledControllerLike {
101
+ cron: string;
102
+ scheduledTime: number;
103
+ }
104
+ /** Minimal shape of a Cloudflare `ExecutionContext`. */
105
+ interface ExecutionContextLike {
106
+ waitUntil?(promise: Promise<unknown>): void;
107
+ }
108
+ interface CreateScheduledHandlerOptions {
109
+ /** Plugins to consider (evaluated lazily at fire time). */
110
+ plugins: Array<CronablePlugin | undefined | null> | (() => Array<CronablePlugin | undefined | null>);
111
+ /** Provides the hook system (e.g. the singleton getter). */
112
+ getHooks: () => HookSystemLike;
113
+ /** Optional: when true, skip dispatch entirely (mirrors plugins.disableAll). */
114
+ disabled?: boolean;
115
+ /**
116
+ * Boot function from {@link SonicJSApp.boot}. Called before the first cron
117
+ * dispatch so a cron-first cold isolate (one that never handled an HTTP
118
+ * request) still has a wired hook bus and reachable email service.
119
+ * Without this, `getHookSystem()` may throw in cron handlers.
120
+ */
121
+ boot?: (env: Record<string, unknown>) => Promise<void>;
122
+ }
123
+ /**
124
+ * Build a Cloudflare `scheduled(controller, env, ctx)` handler that fans cron
125
+ * triggers out to plugins.
126
+ *
127
+ * Usage in a Worker entry:
128
+ * export default {
129
+ * fetch: app.fetch,
130
+ * scheduled: createScheduledHandler({ plugins, getHooks: getHookSystem }),
131
+ * }
132
+ */
133
+ declare function createScheduledHandler(options: CreateScheduledHandlerOptions): (controller: ScheduledControllerLike, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<CronDispatchResult>;
134
+
135
+ /**
136
+ * Hook-system singleton
137
+ *
138
+ * Gives env-independent access to the app's hook system. Code that runs outside
139
+ * the HTTP request context — most importantly scheduled (cron) handlers, which
140
+ * have no per-request `c.env` — needs a way to reach the hook system without
141
+ * threading it through every call. The app sets the singleton eagerly at
142
+ * construction; everything else reads it.
143
+ *
144
+ * Contract: `getHookSystem()` throws if read before the app has set one
145
+ * (throw-before-get), which surfaces wiring-order bugs loudly instead of
146
+ * silently no-oping. `setHookSystem()` is idempotent (last write wins) so that
147
+ * constructing multiple apps in one process — e.g. across tests — does not
148
+ * throw; call `resetHookSystem()` in test teardown for isolation.
149
+ */
150
+
151
+ /** Set the process-wide hook system. Last write wins. */
152
+ declare function setHookSystem(hookSystem: HookSystemLike): void;
153
+ /**
154
+ * Get the process-wide hook system.
155
+ * @throws if no hook system has been set yet.
156
+ */
157
+ declare function getHookSystem(): HookSystemLike;
158
+ /** True if a hook system has been set. */
159
+ declare function hasHookSystem(): boolean;
160
+ /** Clear the singleton. Intended for test isolation. */
161
+ declare function resetHookSystem(): void;
162
+ /** Convenience: a typed facade over the current singleton hook system. */
163
+ declare function getTypedHooks(): TypedHooks;
164
+
165
+ /**
166
+ * Plugin route mounting primitive
167
+ *
168
+ * This is the shared, position-aware primitive that mounts plugin routes into
169
+ * the Hono app. It replaces the hand-wired, copy-pasted
170
+ * `if (plugin.routes) { for (...) app.route(...) }` blocks that previously lived
171
+ * in `app.ts`.
172
+ *
173
+ * ## Why synchronous
174
+ *
175
+ * Hono's `SmartRouter` builds (and then locks) its match tree on the first
176
+ * request. Calling `app.route()` after that throws:
177
+ *
178
+ * `Error: Can not add a route since the matcher is already built`
179
+ *
180
+ * Therefore route mounting MUST happen synchronously at app-construction time,
181
+ * before any request is served. A plugin's imperative `register(app)` hook is
182
+ * held to the same contract: returning a Promise is a hard error
183
+ * (`PluginRegisterMustBeSyncError`) rather than a silent hang. Asynchronous,
184
+ * env-dependent work (hook subscriptions, services, crons) belongs in a later
185
+ * lazy "wire" phase — not here.
186
+ *
187
+ * ## Position-awareness
188
+ *
189
+ * Plugin routes must be mounted BEFORE the bare `/admin` catch-all so that a
190
+ * plugin's own `/admin/<x>` pages are not shadowed. Callers are responsible for
191
+ * invoking `registerPluginRoutes()` at the correct position in `app.ts`; this
192
+ * module just performs the mounting deterministically.
193
+ */
194
+
195
+ /**
196
+ * Minimal structural contract this module needs to mount a plugin.
197
+ *
198
+ * Deliberately NOT the full `Plugin` interface: the core plugins are typed
199
+ * against the built `@sonicjs-cms/core` `dist` declarations, while this file
200
+ * lives in `src`, and TypeScript treats those two `Plugin` types as distinct
201
+ * nominal identities. Accepting a structural subset lets both the `src` and
202
+ * `dist` `Plugin` shapes (and user-supplied plugins) be mounted without casts,
203
+ * and keeps `mount.ts` honest about what it actually reads.
204
+ */
205
+ interface MountableRoute {
206
+ path: string;
207
+ handler: unknown;
208
+ priority?: number;
209
+ }
210
+ interface MountablePlugin {
211
+ /** Stable unique id (used by topo-sort). Falls back to `name`. */
212
+ id?: string;
213
+ name?: string;
214
+ /** IDs of plugins that must be mounted before this one. */
215
+ dependencies?: string[];
216
+ routes?: MountableRoute[];
217
+ /** Synchronous imperative route registration. A Promise return is rejected. */
218
+ register?: (app: any) => unknown;
219
+ }
220
+ /**
221
+ * Thrown when a plugin's `register(app)` hook returns a Promise.
222
+ *
223
+ * `register()` runs synchronously at construction time (see module docs). Move
224
+ * any async work to a lifecycle hook (`install`/`activate`) or the async wiring
225
+ * phase instead.
226
+ */
227
+ declare class PluginRegisterMustBeSyncError extends Error {
228
+ constructor(pluginName: string);
229
+ }
230
+ /** A single mounted route, for diagnostics/introspection. */
231
+ interface MountedRoute {
232
+ plugin: string;
233
+ path: string;
234
+ }
235
+ /** Outcome of a mount pass. */
236
+ interface MountResult {
237
+ /** Routes successfully mounted, in mount order. */
238
+ mounted: MountedRoute[];
239
+ /** Plugins (or routes) skipped, with a human-readable reason. */
240
+ skipped: Array<{
241
+ plugin: string;
242
+ reason: string;
243
+ }>;
244
+ }
245
+ type AnyHono = Hono<any, any, any>;
246
+ /**
247
+ * Mount a single plugin's routes into `app`.
248
+ *
249
+ * Applies, in order:
250
+ * 1. The plugin's declarative `routes[]` (sorted by priority), and
251
+ * 2. The plugin's imperative `register(app)` hook, if present (sync-guarded).
252
+ *
253
+ * @param app Host Hono app (mount BEFORE the `/admin` catch-all).
254
+ * @param plugin Plugin to mount.
255
+ * @param result Optional accumulator for diagnostics.
256
+ */
257
+ declare function mountPlugin(app: AnyHono, plugin: MountablePlugin, result?: MountResult): void;
258
+ /** Options for {@link registerPluginRoutes}. */
259
+ interface RegisterPluginRoutesOptions {
260
+ /**
261
+ * Label used in dev warnings (e.g. `'core'` or `'user'`). Purely cosmetic.
262
+ */
263
+ source?: string;
264
+ /**
265
+ * When true, log a warning if two plugins mount the exact same route path
266
+ * (later registration is shadowed by Hono's first-match semantics). Defaults
267
+ * to true in non-production.
268
+ */
269
+ warnOnDuplicatePath?: boolean;
270
+ /**
271
+ * When true, sort plugins by their `dependencies` field before mounting
272
+ * (dependency-first order). Defaults to true.
273
+ */
274
+ sortByDependencies?: boolean;
275
+ /**
276
+ * Forwarded to `topoSort` — unknown dependency id throws when true, warns
277
+ * when false (default).
278
+ */
279
+ strict?: boolean;
280
+ }
281
+ /**
282
+ * Mount a list of plugins into `app`, in array order.
283
+ *
284
+ * This is the generic replacement for the hand-wired plugin blocks. Plugins are
285
+ * mounted in the order given (the caller decides ordering — typically core
286
+ * plugins first, then user `plugins.register` plugins), each before the bare
287
+ * `/admin` catch-all.
288
+ *
289
+ * Invalid entries are skipped (recorded in the result) rather than throwing, so
290
+ * one malformed plugin can't take down the whole app. The one hard error is a
291
+ * plugin whose `register()` is asynchronous — that is a contract violation that
292
+ * must surface loudly.
293
+ *
294
+ * @returns A {@link MountResult} describing what was mounted/skipped.
295
+ */
296
+ declare function registerPluginRoutes(app: AnyHono, plugins: Array<MountablePlugin | undefined | null>, options?: RegisterPluginRoutesOptions): MountResult;
297
+
298
+ /**
299
+ * Plugin wiring phase (the async half of two-phase boot)
300
+ *
301
+ * Route mounting is synchronous and happens at construction (see `mount.ts`).
302
+ * Everything that needs the runtime environment — hook subscriptions and the
303
+ * `onBoot` lifecycle — happens here, lazily, on the first request, after every
304
+ * plugin has registered. Splitting the two avoids Hono's "matcher already built"
305
+ * lock (routes must mount before the first request; env-dependent wiring can
306
+ * only run once a request supplies `c.env`).
307
+ *
308
+ * This module is the wiring mechanism. Where/when it is invoked (a once-guarded
309
+ * first-request step) is the caller's concern; `createPluginWirer()` provides
310
+ * the once-guard.
311
+ */
312
+
313
+ /** A hook subscription declared by a plugin (the legacy `hooks[]` shape). */
314
+ interface WirableHook {
315
+ name: string;
316
+ handler: (data: any, context: any) => any;
317
+ priority?: number;
318
+ }
319
+ /**
320
+ * Structural contract this module needs from a plugin. Deliberately minimal (and
321
+ * not the full `Plugin` interface) so the `src`/`dist` duplicate `Plugin`
322
+ * identities and user-supplied plugins all satisfy it without casts.
323
+ */
324
+ interface WirablePlugin {
325
+ /** Stable unique id (used by topo-sort). Falls back to `name`. */
326
+ id?: string;
327
+ name?: string;
328
+ /** IDs of plugins that must be wired before this one. */
329
+ dependencies?: string[];
330
+ /**
331
+ * When true, the plugin is inserted as 'active' on first install.
332
+ * Subsequent boots leave the admin-managed status untouched (the ON CONFLICT
333
+ * clause does not update `status`), so admins can still deactivate it.
334
+ */
335
+ defaultActive?: boolean;
336
+ /**
337
+ * Capabilities declared by the plugin. When present (even as an empty array),
338
+ * the wire phase enforces that each declarative hook subscription is covered by
339
+ * a matching capability in {@link HOOK_CAPABILITY_MAP}. Absent on old-style
340
+ * `PluginBuilder` plugins — the gate is skipped for backwards compatibility.
341
+ */
342
+ capabilities?: readonly string[];
343
+ hooks?: WirableHook[];
344
+ /**
345
+ * Async lifecycle hook run once, after all plugins have registered and their
346
+ * hooks are subscribed. The place for env-dependent setup (services, seeding,
347
+ * cron registration). Errors are isolated per-plugin.
348
+ */
349
+ onBoot?: (context: PluginBootContext) => void | Promise<void>;
350
+ }
351
+ /** Options forwarded to {@link wireRegisteredPlugins}. */
352
+ interface WireOptions {
353
+ /**
354
+ * Strict mode: capability violations are captured as errors in `WireResult`
355
+ * (and the hook is skipped) instead of only logging a warning.
356
+ * Also makes unknown dependency ids hard errors instead of warnings.
357
+ * Enable in CI or development; leave off in production for resilience.
358
+ */
359
+ strict?: boolean;
360
+ /**
361
+ * When false, skip dependency-aware ordering. Defaults to true (plugins with
362
+ * `dependencies` are wired after their declared dependencies).
363
+ */
364
+ sortByDependencies?: boolean;
365
+ }
366
+ /** Context handed to `onBoot`. Kept loose; concrete bindings are filled by the host. */
367
+ interface PluginBootContext {
368
+ /** The live hook system (already carrying every plugin's subscriptions). */
369
+ hooks: HookSystemLike;
370
+ /** Runtime bindings, when available. */
371
+ env?: Record<string, unknown>;
372
+ [key: string]: unknown;
373
+ }
374
+ /** Outcome of a wiring pass. */
375
+ interface WireResult {
376
+ /** Total hook subscriptions registered. */
377
+ subscribed: number;
378
+ /** Names of plugins whose `onBoot` completed successfully. */
379
+ booted: string[];
380
+ /** Per-plugin errors (wiring never throws; one bad plugin can't break boot). */
381
+ errors: Array<{
382
+ plugin: string;
383
+ phase: 'subscribe' | 'onBoot';
384
+ error: unknown;
385
+ }>;
386
+ }
387
+ /**
388
+ * Subscribe every plugin's declarative `hooks[]` to the live hook system, then
389
+ * run each plugin's `onBoot`, in array order.
390
+ *
391
+ * Subscriptions happen for ALL plugins first, so that one plugin's `onBoot` can
392
+ * rely on another plugin's hooks already being registered. Per-plugin errors are
393
+ * captured in the result rather than thrown.
394
+ */
395
+ declare function wireRegisteredPlugins(plugins: Array<WirablePlugin | undefined | null>, context: PluginBootContext, options?: WireOptions): Promise<WireResult>;
396
+ /**
397
+ * Wrap {@link wireRegisteredPlugins} in a once-guard.
398
+ *
399
+ * The returned function runs the wiring at most once per process: the first call
400
+ * starts it and caches the promise; every later call returns that same promise.
401
+ * This is the first-request trigger — many concurrent first requests all await
402
+ * one wiring pass.
403
+ *
404
+ * @param plugins Plugins to wire (evaluated lazily at first call).
405
+ * @param ctxFactory Builds the boot context at first-call time (so it can read
406
+ * per-request env).
407
+ */
408
+ declare function createPluginWirer(plugins: Array<WirablePlugin | undefined | null> | (() => Array<WirablePlugin | undefined | null>), ctxFactory: () => PluginBootContext): () => Promise<WireResult>;
409
+
410
+ /**
411
+ * Plugin admin-sidebar menu registry.
412
+ *
413
+ * Plugins declare `menu: PluginMenuEntry[]` on definePlugin. registerPlugins
414
+ * collects every entry into this module singleton; the admin layout reads via
415
+ * `resolvePluginMenuItems(user)` to render the sidebar.
416
+ *
417
+ * Phase 1 — strings + paths only. No custom React/HTMX components in menu
418
+ * entries (icons are name strings looked up by the sidebar's icon map).
419
+ */
420
+ interface PluginMenuEntry {
421
+ /** Display label. */
422
+ label: string;
423
+ /** Admin URL the entry navigates to (e.g. `/admin/email`). */
424
+ path: string;
425
+ /** Icon name from the admin icon map. Default: `puzzle-piece`. */
426
+ icon?: string;
427
+ /** Sort key (ASC). Default 100. */
428
+ order?: number;
429
+ /** Required permission slugs; if any matches the user's permissions, the entry is shown. */
430
+ permissions?: readonly string[];
431
+ }
432
+ /**
433
+ * Resolved entry — guaranteed `icon` + `order`, ready to render. Same shape
434
+ * the catalyst sidebar consumes via `dynamicMenuItems`. `icon` is the rendered
435
+ * SVG HTML string (resolved via {@link MENU_ICON_MAP} when the entry supplied
436
+ * a name; passed through verbatim when the entry supplied raw SVG/HTML).
437
+ */
438
+ interface ResolvedPluginMenuEntry {
439
+ label: string;
440
+ path: string;
441
+ icon: string;
442
+ order: number;
443
+ }
444
+ declare function setPluginMenu(items: readonly PluginMenuEntry[]): void;
445
+ declare function getPluginMenu(): readonly PluginMenuEntry[];
446
+ declare function resetPluginMenu(): void;
447
+ /**
448
+ * Filter entries by user permissions, sort by `order` ASC, project to render-ready
449
+ * shape with default icon. Pure function — same inputs, same output.
450
+ *
451
+ * Permission semantics: an entry with no `permissions` is always visible. An entry
452
+ * with `permissions: ['x','y']` is visible if the user has ANY of `x` or `y`.
453
+ * Users with `role: 'admin'` bypass all permission checks (admins see everything).
454
+ */
455
+ declare function resolvePluginMenuItems(user?: {
456
+ permissions?: readonly string[];
457
+ role?: string;
458
+ }): ResolvedPluginMenuEntry[];
459
+
460
+ /**
461
+ * Schema-driven plugin settings.
462
+ *
463
+ * Plugins declare `configSchema: { key: ConfigSchemaField }` on definePlugin.
464
+ * The host renders the admin settings UI from the schema, parses FormData back
465
+ * into typed values, and exposes the resulting record via `ctx.settings.load()`.
466
+ *
467
+ * Field kinds: 'string' | 'number' | 'boolean' | 'select'. New kinds get added
468
+ * here once and every consumer picks them up.
469
+ *
470
+ * Phase 1 — settings UI only. The renderer emits plain HTML strings designed to
471
+ * compose into the existing admin layout (no client-side JS). FormData parsing
472
+ * coalesces unchecked-checkbox omission into `false` (the only browser quirk
473
+ * the renderer must account for).
474
+ */
475
+ interface BaseField {
476
+ label: string;
477
+ description?: string;
478
+ required?: boolean;
479
+ }
480
+ interface StringField extends BaseField {
481
+ type: 'string';
482
+ default?: string;
483
+ format?: 'email' | 'url' | 'password';
484
+ /** Render as `<input type="password">`. Implied when `format === 'password'`. */
485
+ sensitive?: boolean;
486
+ placeholder?: string;
487
+ minLength?: number;
488
+ maxLength?: number;
489
+ }
490
+ interface NumberField extends BaseField {
491
+ type: 'number';
492
+ default?: number;
493
+ min?: number;
494
+ max?: number;
495
+ step?: number;
496
+ }
497
+ interface BooleanField extends BaseField {
498
+ type: 'boolean';
499
+ default?: boolean;
500
+ }
501
+ interface SelectField extends BaseField {
502
+ type: 'select';
503
+ default?: string;
504
+ /** Either `['us','eu']` shorthand or `[{ value, label }]` for distinct display strings. */
505
+ options: readonly string[] | readonly {
506
+ value: string;
507
+ label: string;
508
+ }[];
509
+ }
510
+ type ConfigSchemaField = StringField | NumberField | BooleanField | SelectField;
511
+ type ConfigSchema = Record<string, ConfigSchemaField>;
512
+ /** Parsed shape — typed record inferred from the schema. */
513
+ type SettingsFor<S extends ConfigSchema> = {
514
+ [K in keyof S]: S[K] extends StringField ? string : S[K] extends NumberField ? number : S[K] extends BooleanField ? boolean : S[K] extends SelectField ? string : never;
515
+ };
516
+ interface ParsedField {
517
+ key: string;
518
+ field: ConfigSchemaField;
519
+ }
520
+ /** Stable, ordered field list (preserves declaration order). */
521
+ declare function parseConfigSchema(schema: ConfigSchema): ParsedField[];
522
+ /**
523
+ * Render all fields as HTML controls. Output is a sequence of `<div class="field">…</div>`
524
+ * blocks designed to drop into the admin form template (no surrounding `<form>` or
525
+ * submit button — those belong to the page that calls this).
526
+ */
527
+ declare function renderSchemaFields(schema: ConfigSchema, currentValues?: Record<string, unknown>): string;
528
+ /**
529
+ * Coerce FormData entries into typed settings per the schema. Notably:
530
+ * - Unchecked checkboxes are omitted by the browser; we coalesce to `false`.
531
+ * - Numbers parse via `Number()` and fall back to the field's default if invalid.
532
+ * - Strings preserve empty string (NOT default) so an admin can intentionally clear.
533
+ */
534
+ declare function parseFormDataToSettings(schema: ConfigSchema, form: FormData): Record<string, unknown>;
535
+ /** Fill missing keys with field defaults. Existing keys (incl. `false`/`0`/'') win. */
536
+ declare function applySchemaDefaults<S extends ConfigSchema>(schema: S, stored: Record<string, unknown>): Record<string, unknown>;
537
+
538
+ /**
539
+ * registerPlugins — single chokepoint for plugin registration.
540
+ *
541
+ * Replaces the historical pattern of calling `registerPluginRoutes` + scheduling
542
+ * `wireRegisteredPlugins` + manually wiring `addMenuItem` calls + per-plugin
543
+ * settings-route handlers.
544
+ *
545
+ * Authors write:
546
+ * const myPlugin = definePlugin({ id, version, capabilities, register, hooks, menu, configSchema, ... })
547
+ *
548
+ * Hosts call:
549
+ * const reg = registerPlugins(app, [pluginA, pluginB], { hookSystem, env, ... })
550
+ * // ...
551
+ * await reg.boot(env) // first-request lazy wire
552
+ *
553
+ * What this fn does, in order:
554
+ * 1. Validate each plugin (id, semver, dep references) — throws on hard errors
555
+ * 2. Topologically order by `dependencies`
556
+ * 3. MOUNT pass — invoke `registerPluginRoutes()` (sync, routes only)
557
+ * 4. Collect declarative `menu[]` entries → setPluginMenu (overwrites prior)
558
+ * 5. Collect declarative `crons[]` for the host scheduled() handler
559
+ * 6. Return a registry + a one-shot `boot(env)` that lazily runs the WIRE pass
560
+ * (subscribe hooks + run onBoot + DB-reflect) on first request
561
+ *
562
+ * The mount/wire split is preserved (Hono's router locks after first request, so
563
+ * routes MUST mount synchronously at construction; env-dependent setup is lazy).
564
+ * That split is now an implementation detail — authors see ONE function.
565
+ */
566
+
567
+ type RegisterPluginsErrorReason = 'invalid_id' | 'invalid_semver' | 'duplicate_id' | 'register_returned_promise';
568
+ declare class RegisterPluginsError extends Error {
569
+ readonly reason: RegisterPluginsErrorReason;
570
+ readonly details: Readonly<Record<string, unknown>>;
571
+ constructor(reason: RegisterPluginsErrorReason, details: Readonly<Record<string, unknown>>);
572
+ }
573
+ /**
574
+ * Structural contract: the union of MountablePlugin + WirablePlugin + the
575
+ * declarative admin surface. Plugins built via `definePlugin()` satisfy this
576
+ * automatically; hand-rolled objects need at minimum `id`, `version`.
577
+ */
578
+ interface RegisterablePlugin extends MountablePlugin, WirablePlugin {
579
+ id: string;
580
+ version: string;
581
+ displayName?: string;
582
+ sonicjsVersionRange?: string;
583
+ menu?: PluginMenuEntry[];
584
+ crons?: CronDeclaration[];
585
+ /** Schema-driven settings — the admin route reads this via getPluginDefinition(id). */
586
+ configSchema?: ConfigSchema;
587
+ }
588
+ interface RegisterPluginsHostContext {
589
+ /** Hook system the wire phase subscribes to. */
590
+ hookSystem: PluginBootContext['hooks'];
591
+ /** Runtime bindings (env.DB, env.KV, env.R2, etc.). Used by the wire phase. */
592
+ env?: Record<string, unknown>;
593
+ /** Forwarded to the mount pass as the `source` label in dev warnings. */
594
+ source?: string;
595
+ /** Strict-mode: cycle / unknown-dep / capability errors throw instead of warn. */
596
+ strict?: boolean;
597
+ /** Forwarded to registerPluginRoutes. */
598
+ mountOptions?: Omit<RegisterPluginRoutesOptions, 'source' | 'strict'>;
599
+ }
600
+ interface RegistryEntry {
601
+ id: string;
602
+ displayName: string;
603
+ version: string;
604
+ capabilities: readonly string[];
605
+ }
606
+ interface PluginsRegistry {
607
+ /** Plugins indexed by id, in topo-sorted order. */
608
+ readonly byId: ReadonlyMap<string, RegistryEntry>;
609
+ /** Plugin ids in registration order (post topo-sort). */
610
+ readonly order: readonly string[];
611
+ /** Aggregated menu entries (also pushed to the menu singleton). */
612
+ readonly menu: readonly PluginMenuEntry[];
613
+ /** Aggregated cron declarations. */
614
+ readonly crons: readonly CronDeclaration[];
615
+ /** Aggregated cron schedule expressions (deduped) — useful for wrangler.toml codegen. */
616
+ readonly cronSchedules: readonly string[];
617
+ /** Mount-pass diagnostics. */
618
+ readonly mountResult: MountResult;
619
+ /**
620
+ * Lazy wire pass — call from the first-request middleware. Idempotent
621
+ * (subsequent calls return the cached result of the first run).
622
+ */
623
+ boot(env?: Record<string, unknown>): Promise<WireResult>;
624
+ }
625
+ declare function registerPlugins(app: Hono<any, any, any>, plugins: ReadonlyArray<RegisterablePlugin | undefined | null>, host: RegisterPluginsHostContext): PluginsRegistry;
626
+
627
+ /**
628
+ * Hook System Implementation
629
+ *
630
+ * Provides event-driven extensibility for plugins
631
+ */
632
+
633
+ declare class HookSystemImpl implements HookSystem {
634
+ private hooks;
635
+ private executing;
636
+ /**
637
+ * Register a hook handler
638
+ */
639
+ register(hookName: string, handler: HookHandler, priority?: number): void;
640
+ /**
641
+ * Execute all handlers for a hook
642
+ */
643
+ execute(hookName: string, data: any, context?: any): Promise<any>;
644
+ /**
645
+ * Remove a hook handler
646
+ */
647
+ unregister(hookName: string, handler: HookHandler): void;
648
+ /**
649
+ * Get all registered hooks for a name
650
+ */
651
+ getHooks(hookName: string): PluginHook[];
652
+ /**
653
+ * Get all registered hook names
654
+ */
655
+ getHookNames(): string[];
656
+ /**
657
+ * Get hook statistics
658
+ */
659
+ getStats(): {
660
+ hookName: string;
661
+ handlerCount: number;
662
+ }[];
663
+ /**
664
+ * Clear all hooks (useful for testing)
665
+ */
666
+ clear(): void;
667
+ /**
668
+ * Create a scoped hook system for a plugin
669
+ */
670
+ createScope(_pluginName: string): ScopedHookSystem;
671
+ }
672
+ /**
673
+ * Scoped hook system for individual plugins
674
+ */
675
+ declare class ScopedHookSystem {
676
+ private parent;
677
+ private registeredHooks;
678
+ constructor(parent: HookSystemImpl);
679
+ /**
680
+ * Register a hook (scoped to this plugin)
681
+ */
682
+ register(hookName: string, handler: HookHandler, priority?: number): void;
683
+ /**
684
+ * Execute a hook
685
+ */
686
+ execute(hookName: string, data: any, context?: any): Promise<any>;
687
+ /**
688
+ * Unregister a specific hook
689
+ */
690
+ unregister(hookName: string, handler: HookHandler): void;
691
+ /**
692
+ * Unregister all hooks for this plugin
693
+ */
694
+ unregisterAll(): void;
695
+ /**
696
+ * Get hooks registered by this plugin
697
+ */
698
+ getRegisteredHooks(): {
699
+ hookName: string;
700
+ handler: HookHandler;
701
+ }[];
702
+ }
703
+ /**
704
+ * Hook utilities
705
+ */
706
+ declare class HookUtils {
707
+ /**
708
+ * Create a hook name with namespace
709
+ */
710
+ static createHookName(namespace: string, event: string): string;
711
+ /**
712
+ * Parse a hook name to extract namespace and event
713
+ */
714
+ static parseHookName(hookName: string): {
715
+ namespace: string;
716
+ event: string;
717
+ };
718
+ /**
719
+ * Create a middleware that executes hooks
720
+ */
721
+ static createHookMiddleware(hookSystem: HookSystem, beforeHook?: string, afterHook?: string): (c: any, next: any) => Promise<void>;
722
+ /**
723
+ * Create a debounced hook handler
724
+ */
725
+ static debounce(handler: HookHandler, delay: number): HookHandler;
726
+ /**
727
+ * Create a throttled hook handler
728
+ */
729
+ static throttle(handler: HookHandler, limit: number): HookHandler;
730
+ }
731
+
732
+ /**
733
+ * Plugin Validator
734
+ *
735
+ * Validates plugin definitions, dependencies, and compatibility
736
+ */
737
+
738
+ declare class PluginValidator implements PluginValidator$1 {
739
+ private static readonly RESERVED_NAMES;
740
+ private static readonly RESERVED_PATHS;
741
+ /**
742
+ * Validate plugin definition
743
+ */
744
+ validate(plugin: Plugin): PluginValidationResult;
745
+ /**
746
+ * Validate plugin dependencies
747
+ */
748
+ validateDependencies(plugin: Plugin, registry: PluginRegistry): PluginValidationResult;
749
+ /**
750
+ * Validate plugin compatibility with SonicJS version
751
+ */
752
+ validateCompatibility(plugin: Plugin, sonicVersion: string): PluginValidationResult;
753
+ /**
754
+ * Check if two version ranges are compatible
755
+ */
756
+ private isCompatible;
757
+ /**
758
+ * Validate plugin security constraints
759
+ */
760
+ validateSecurity(plugin: Plugin): PluginValidationResult;
761
+ }
762
+
763
+ /**
764
+ * Plugin Registry Implementation
765
+ *
766
+ * Manages plugin registration, activation, and lifecycle
767
+ */
768
+
769
+ declare class PluginRegistryImpl implements PluginRegistry {
770
+ private plugins;
771
+ private configs;
772
+ private statuses;
773
+ private validator;
774
+ constructor(validator?: PluginValidator);
775
+ /**
776
+ * Get plugin by name
777
+ */
778
+ get(name: string): Plugin | undefined;
779
+ /**
780
+ * Get all registered plugins
781
+ */
782
+ getAll(): Plugin[];
783
+ /**
784
+ * Get active plugins
785
+ */
786
+ getActive(): Plugin[];
787
+ /**
788
+ * Register a plugin
789
+ */
790
+ register(plugin: Plugin): Promise<void>;
791
+ /**
792
+ * Unregister a plugin
793
+ */
794
+ unregister(name: string): Promise<void>;
795
+ /**
796
+ * Check if plugin is registered
797
+ */
798
+ has(name: string): boolean;
799
+ /**
800
+ * Activate a plugin
801
+ */
802
+ activate(name: string): Promise<void>;
803
+ /**
804
+ * Deactivate a plugin
805
+ */
806
+ deactivate(name: string): Promise<void>;
807
+ /**
808
+ * Get plugin configuration
809
+ */
810
+ getConfig(name: string): PluginConfig | undefined;
811
+ /**
812
+ * Set plugin configuration
813
+ */
814
+ setConfig(name: string, config: PluginConfig): void;
815
+ /**
816
+ * Get plugin status
817
+ */
818
+ getStatus(name: string): PluginStatus | undefined;
819
+ /**
820
+ * Get all plugin statuses
821
+ */
822
+ getAllStatuses(): Map<string, PluginStatus>;
823
+ /**
824
+ * Update plugin status
825
+ */
826
+ private updateStatus;
827
+ /**
828
+ * Get plugins that depend on the specified plugin
829
+ */
830
+ private getDependents;
831
+ /**
832
+ * Get dependency graph
833
+ */
834
+ getDependencyGraph(): Map<string, string[]>;
835
+ /**
836
+ * Resolve plugin load order based on dependencies
837
+ */
838
+ resolveLoadOrder(): string[];
839
+ /**
840
+ * Export plugin configuration
841
+ */
842
+ exportConfig(): {
843
+ plugins: PluginConfig[];
844
+ };
845
+ /**
846
+ * Import plugin configuration
847
+ */
848
+ importConfig(config: {
849
+ plugins: PluginConfig[];
850
+ }): void;
851
+ /**
852
+ * Clear all plugins (useful for testing)
853
+ */
854
+ clear(): void;
855
+ /**
856
+ * Get registry statistics
857
+ */
858
+ getStats(): {
859
+ total: number;
860
+ active: number;
861
+ inactive: number;
862
+ withErrors: number;
863
+ };
864
+ }
865
+
866
+ /**
867
+ * Plugin Manager
868
+ *
869
+ * Central orchestrator for the plugin system
870
+ */
871
+
872
+ declare class PluginManager implements PluginManager$1 {
873
+ readonly registry: PluginRegistry;
874
+ readonly hooks: HookSystem;
875
+ private validator;
876
+ private context?;
877
+ private scopedHooks;
878
+ private pluginRoutes;
879
+ constructor();
880
+ /**
881
+ * Initialize plugin system
882
+ */
883
+ initialize(context: PluginContext): Promise<void>;
884
+ /**
885
+ * Load plugins from configuration
886
+ */
887
+ loadPlugins(configs: PluginConfig[]): Promise<void>;
888
+ /**
889
+ * Install a plugin
890
+ */
891
+ install(plugin: Plugin, config?: PluginConfig): Promise<void>;
892
+ /**
893
+ * Uninstall a plugin
894
+ */
895
+ uninstall(name: string): Promise<void>;
896
+ /**
897
+ * Get plugin status
898
+ */
899
+ getStatus(name: string): PluginStatus;
900
+ /**
901
+ * Get all plugin statuses
902
+ */
903
+ getAllStatuses(): PluginStatus[];
904
+ /**
905
+ * Register plugin extensions (routes, middleware, etc.)
906
+ */
907
+ private registerPluginExtensions;
908
+ /**
909
+ * Unregister plugin extensions
910
+ */
911
+ private unregisterPluginExtensions;
912
+ /**
913
+ * Update plugin status
914
+ */
915
+ private updatePluginStatus;
916
+ /**
917
+ * Create a logger for a plugin
918
+ */
919
+ private createLogger;
920
+ /**
921
+ * Get plugin routes for mounting in main app
922
+ */
923
+ getPluginRoutes(): Map<string, Hono>;
924
+ /**
925
+ * Get plugin middleware for main app
926
+ */
927
+ getPluginMiddleware(): Array<{
928
+ name: string;
929
+ handler: any;
930
+ priority: number;
931
+ global: boolean;
932
+ }>;
933
+ /**
934
+ * Execute shutdown procedures
935
+ */
936
+ shutdown(): Promise<void>;
937
+ /**
938
+ * Get plugin system statistics
939
+ */
940
+ getStats(): {
941
+ registry: ReturnType<PluginRegistryImpl['getStats']>;
942
+ hooks: Array<{
943
+ hookName: string;
944
+ handlerCount: number;
945
+ }>;
946
+ routes: number;
947
+ middleware: number;
948
+ };
949
+ }
950
+
951
+ /**
952
+ * Minimal structural D1 surface needed for logging. Avoids importing the full
953
+ * `D1Database` type and keeps the service trivially fakeable in tests.
954
+ */
955
+ interface EmailLogDb {
956
+ prepare(query: string): {
957
+ bind(...values: unknown[]): {
958
+ run(): Promise<unknown>;
959
+ };
960
+ };
961
+ }
962
+ interface EmailServiceOptions {
963
+ /** The transport. */
964
+ provider: EmailProvider;
965
+ /** Default from-address used when a message omits `from`. */
966
+ defaultFrom: string;
967
+ /** Default reply-to applied when a message omits `replyTo`. */
968
+ defaultReplyTo?: string;
969
+ /** When provided, every send writes an email_log document to the documents table. */
970
+ db?: EmailLogDb;
971
+ /** Injectable clock (tests). Defaults to `Date.now`. */
972
+ now?: () => number;
973
+ /** Injectable id factory (tests). Defaults to `crypto.randomUUID`. */
974
+ idFactory?: () => string;
975
+ }
976
+ declare class EmailService {
977
+ private readonly provider;
978
+ private readonly defaultFrom;
979
+ private readonly defaultReplyTo?;
980
+ private readonly db?;
981
+ private readonly now;
982
+ private readonly idFactory;
983
+ constructor(options: EmailServiceOptions);
984
+ /** Name of the active transport (e.g. `'resend'`). */
985
+ getProviderName(): string;
986
+ /** True if the active transport is ready to send. */
987
+ isConfigured(): boolean;
988
+ /**
989
+ * Ask the active provider to reconcile delivery state for a batch of
990
+ * recently-sent email_log document rows. Returns empty array when the provider
991
+ * does not implement `reconcile()` (Resend, SendGrid, Console).
992
+ *
993
+ * Called by the `email-reconciliation` cron plugin.
994
+ */
995
+ reconcileDelivery(rows: EmailLogRow[]): Promise<Array<{
996
+ id: string;
997
+ delivery_state: string;
998
+ }>>;
999
+ /** Normalize, send, and log. Never throws for ordinary delivery failures. */
1000
+ send(message: EmailMessage): Promise<SendResult>;
1001
+ /**
1002
+ * Best-effort write of an email_log document into the `documents` table.
1003
+ * Documents use SECONDS for timestamps (not ms). Email log entries are
1004
+ * draft-only (is_current_draft=1, is_published=0, maxVersionsPerRoot=1).
1005
+ * Returns the new document's root_id, or undefined if logging is disabled / fails.
1006
+ */
1007
+ private writeLog;
1008
+ }
1009
+
1010
+ /**
1011
+ * Plugin capabilities
1012
+ *
1013
+ * A plugin declares the capabilities it needs (`capabilities: ['email:send']`).
1014
+ * The host then hands it a context whose powerful accessors are *gated* by those
1015
+ * declarations: reaching `ctx.email` without having declared `email:send` throws
1016
+ * `SonicCapabilityError` immediately, instead of failing deep inside a send.
1017
+ *
1018
+ * This is the isolation boundary that Strapi (namespacing only) and Payload (full
1019
+ * config access) don't have: capabilities make a plugin's blast radius explicit
1020
+ * and enforceable, and double as documentation of what a plugin can touch.
1021
+ *
1022
+ * Phase 1 vocabulary. Payment/queue/scheduled-fetch capabilities are intentionally
1023
+ * deferred to Phase 2 (see the overhaul plan §8.4 / open question 2).
1024
+ */
1025
+
1026
+ /**
1027
+ * Fixed capability names. `db:<table>` is parameterized (a plugin owns specific
1028
+ * tables), so it is matched by pattern rather than listed here.
1029
+ */
1030
+ declare const FIXED_CAPABILITIES: readonly ["email:send", "cache:read", "cache:write", "media:read", "media:write", "http:fetch", "cron:register", "admin:menu", "hooks.auth:subscribe", "hooks.content:subscribe", "hooks.email:subscribe"];
1031
+ type FixedCapability = (typeof FIXED_CAPABILITIES)[number];
1032
+ /** A scoped database capability, e.g. `db:email_log`. */
1033
+ type DbCapability = `db:${string}`;
1034
+ /** Any declarable capability. */
1035
+ type Capability = FixedCapability | DbCapability;
1036
+ /** True if `name` is a recognized capability (fixed name or a valid `db:<table>`). */
1037
+ declare function isKnownCapability(name: string): name is Capability;
1038
+ /**
1039
+ * Thrown when a plugin uses a capability it did not declare.
1040
+ */
1041
+ declare class SonicCapabilityError extends Error {
1042
+ readonly capability: string;
1043
+ readonly plugin?: string;
1044
+ /** The API surface the plugin tried to access (e.g. 'ctx.cap.email'). Optional. */
1045
+ readonly accessedApi?: string;
1046
+ constructor(capability: string, plugin?: string, accessedApi?: string);
1047
+ }
1048
+ /** True if `capability` is in the granted set. */
1049
+ declare function hasCapability(granted: readonly string[], capability: string): boolean;
1050
+ /**
1051
+ * Assert a capability is granted, throwing {@link SonicCapabilityError} otherwise.
1052
+ */
1053
+ declare function assertCapability(granted: readonly string[], capability: string, plugin?: string): void;
1054
+ /**
1055
+ * Validate a plugin's declared capability list. Returns the unknown entries (empty
1056
+ * array = all valid). Callers decide whether to warn or hard-fail.
1057
+ *
1058
+ * Apply {@link normalizeCapabilities} first if the input may contain deprecated
1059
+ * spellings (e.g. from an older SDK or a sibling fork's manifest).
1060
+ */
1061
+ declare function validateCapabilities(declared: readonly string[]): string[];
1062
+ declare const CAPABILITY_RENAMES: {
1063
+ readonly 'storage:read': "media:read";
1064
+ readonly 'storage:write': "media:write";
1065
+ readonly 'hooks.cron:register': "cron:register";
1066
+ readonly 'hooks.auth:register': "hooks.auth:subscribe";
1067
+ readonly 'hooks.content-read:register': "hooks.content:subscribe";
1068
+ readonly 'hooks.content-write:register': "hooks.content:subscribe";
1069
+ readonly 'hooks.email-events:register': "hooks.email:subscribe";
1070
+ };
1071
+ /**
1072
+ * Resolve a (possibly deprecated) capability string to its canonical form, or
1073
+ * `null` if it is unknown after rename resolution. Renames apply first, then the
1074
+ * result is checked against the known vocabulary.
1075
+ */
1076
+ declare function normalizeCapability(input: string): Capability | null;
1077
+ /**
1078
+ * Normalize a list of capability strings. Returns the canonical capabilities plus
1079
+ * the inputs that remained unknown after rename resolution, so the caller can warn
1080
+ * (production) or reject (strict).
1081
+ */
1082
+ declare function normalizeCapabilities(declared: readonly string[]): {
1083
+ capabilities: Capability[];
1084
+ unknown: string[];
1085
+ };
1086
+ /** Provider factories for capability-backed accessors. Each is called lazily. */
1087
+ interface CapabilityProviders {
1088
+ email?: () => EmailService;
1089
+ cache?: () => unknown;
1090
+ http?: () => typeof fetch;
1091
+ }
1092
+ declare const CAP_NOT_DECLARED: unique symbol;
1093
+ /**
1094
+ * The type of a capability accessor the plugin did NOT declare. Deliberately a
1095
+ * branded type (not `never`, which is assignable to everything): using it where a
1096
+ * service is expected is a compile error, so `ctx.cap.email` without `email:send`
1097
+ * fails to type-check instead of silently passing.
1098
+ */
1099
+ type CapabilityNotDeclared<C extends string = string> = {
1100
+ readonly [CAP_NOT_DECLARED]: C;
1101
+ };
1102
+ type WhenGranted<Caps extends readonly Capability[], C extends string, T> = C extends Caps[number] ? T : CapabilityNotDeclared<C>;
1103
+ type WhenGrantedAny<Caps extends readonly Capability[], C extends string, T> = Extract<Caps[number], C> extends never ? CapabilityNotDeclared<C> : T;
1104
+ /**
1105
+ * The gated context handed to a plugin. Accessors throw at runtime unless the
1106
+ * backing capability was declared; with a const-narrowed `Caps` tuple they are
1107
+ * also *typed* to the service (or `never`) at compile time, so `ctx.cap.email`
1108
+ * is `EmailService` only when `'email:send'` was declared.
1109
+ */
1110
+ interface CapabilityContext<Caps extends readonly Capability[] = readonly Capability[]> {
1111
+ /** The capabilities granted to this plugin. */
1112
+ readonly capabilities: readonly string[];
1113
+ /** True if the plugin declared `capability`. */
1114
+ has(capability: string): boolean;
1115
+ /** Throw {@link SonicCapabilityError} unless `capability` was declared. */
1116
+ require(capability: string): void;
1117
+ /** Email service. `EmailService` when `email:send` is declared, else `never`. */
1118
+ readonly email: WhenGranted<Caps, 'email:send', EmailService>;
1119
+ /** Cache service. Present when `cache:read` or `cache:write` is declared. */
1120
+ readonly cache: WhenGrantedAny<Caps, 'cache:read' | 'cache:write', unknown>;
1121
+ /** Outbound fetch. Present when `http:fetch` is declared. */
1122
+ readonly http: WhenGranted<Caps, 'http:fetch', typeof fetch>;
1123
+ }
1124
+ /** The un-narrowed gated context (every accessor typed as its service). */
1125
+ type PluginCapabilityContext = CapabilityContext</** all */ readonly Capability[]>;
1126
+ /**
1127
+ * Build a capability-gated context.
1128
+ *
1129
+ * Accessors are lazy getters: they check the grant (and that a provider was
1130
+ * supplied) at access time, so merely constructing the context is cheap and
1131
+ * holding a reference to a capability you never use costs nothing.
1132
+ *
1133
+ * @param granted Capabilities the plugin declared.
1134
+ * @param providers Backing service factories (only the granted ones get called).
1135
+ * @param plugin Plugin name, for clearer errors.
1136
+ */
1137
+ declare function createCapabilityContext<const Caps extends readonly Capability[]>(granted: Caps, providers?: CapabilityProviders, plugin?: string): CapabilityContext<Caps>;
1138
+
1139
+ /**
1140
+ * definePlugin() — the v3 plugin authoring entry point
1141
+ *
1142
+ * A plugin is authored as a single typed declaration and consumed, unchanged, by
1143
+ * every part of the runtime:
1144
+ *
1145
+ * export const emailPlugin = definePlugin({
1146
+ * id: 'email',
1147
+ * version: '3.0.0',
1148
+ * capabilities: ['email:send', 'hooks.auth:subscribe', 'cron:register'],
1149
+ * register(app) { app.route('/admin/plugins/email', emailRoutes) }, // SYNC
1150
+ * async onBoot(ctx) { // ASYNC
1151
+ * ctx.hooks.on('auth:registration:completed', (p) => { ... }) // typed
1152
+ * ctx.cap.email // gated
1153
+ * },
1154
+ * crons: [{ schedule: '*\/15 * * * *', hookFamily: 'email-reconciliation' }],
1155
+ * async onCronTick(event, ctx) { ... },
1156
+ * })
1157
+ *
1158
+ * The object it returns satisfies the structural contracts the runtime already
1159
+ * uses — `MountablePlugin` (mount.ts), `WirablePlugin` (wire.ts), `CronablePlugin`
1160
+ * (cron.ts) — plus the legacy `Plugin` metadata fields the admin/registry read.
1161
+ * No adapters: a defined plugin drops straight into `plugins.register` or the core
1162
+ * plugin list.
1163
+ *
1164
+ * The value definePlugin adds over a hand-written object is the enriched context:
1165
+ * inside `onBoot`/`onCronTick` the author gets a *typed* hook facade (`ctx.hooks`)
1166
+ * and the *capability-gated* service context (`ctx.cap`), instead of the raw
1167
+ * string-keyed hook system. See the two-phase boot contract: `register` is
1168
+ * synchronous (routes only); everything env-dependent lives in `onBoot`.
1169
+ */
1170
+
1171
+ /**
1172
+ * Declarative typed hook subscriptions: a map of canonical event name → handler,
1173
+ * each narrowed to that event's payload. Flattened into the plugin's `hooks[]`
1174
+ * and subscribed during the wire phase. Use `onBoot`'s `ctx.hooks.on()` instead
1175
+ * for dynamic/conditional subscriptions.
1176
+ */
1177
+ type DeclarativeHooks = {
1178
+ [E in HookEventName]?: TypedHookHandler<E>;
1179
+ };
1180
+ /**
1181
+ * Context handed to a defined plugin's `onBoot` / `onCronTick`.
1182
+ *
1183
+ * Enriches the raw boot/cron context with a typed hook facade and the gated
1184
+ * capability context, while keeping `raw` and `env` available as an escape hatch.
1185
+ */
1186
+ interface DefinedPluginContext<Caps extends readonly Capability[] = readonly Capability[]> {
1187
+ /** Typed hook facade — `ctx.hooks.on('auth:registration:completed', …)`. */
1188
+ hooks: TypedHooks;
1189
+ /**
1190
+ * Capability-gated services. With a const-narrowed `Caps`, `ctx.cap.email` is
1191
+ * typed `EmailService` only when `'email:send'` was declared (else `never`),
1192
+ * and throws `SonicCapabilityError` at runtime if accessed undeclared.
1193
+ */
1194
+ cap: CapabilityContext<Caps>;
1195
+ /** Runtime bindings, when available (absent during construction). */
1196
+ env?: Record<string, unknown>;
1197
+ /** The unwrapped context the runtime passed. */
1198
+ raw: PluginBootContext | CronContext;
1199
+ }
1200
+ /** Input to {@link definePlugin}. */
1201
+ interface DefinePluginInput<Caps extends readonly Capability[] = readonly []> {
1202
+ /** Unique, stable plugin id (kebab-case). Becomes the plugin `name`. */
1203
+ id: string;
1204
+ /** Human-readable display name. Defaults to `id`. */
1205
+ name?: string;
1206
+ /**
1207
+ * Semantic version of this plugin (e.g. `'1.2.3'`). Must be a valid semver
1208
+ * string — invalid values emit a console.warn at definition time.
1209
+ */
1210
+ version: string;
1211
+ /**
1212
+ * A semver range expressing which SonicJS core versions this plugin supports
1213
+ * (e.g. `'^3.0.0'` or `'>=3.1.0 <4.0.0'`). Checked against the running
1214
+ * core version at registration; a mismatch logs a compatibility warning but
1215
+ * does not block activation (plugins remain resilient by default).
1216
+ */
1217
+ sonicjsVersionRange?: string;
1218
+ description?: string;
1219
+ author?: {
1220
+ name: string;
1221
+ email?: string;
1222
+ url?: string;
1223
+ };
1224
+ /** Other plugin ids this one needs (load-order / activation). */
1225
+ dependencies?: string[];
1226
+ /**
1227
+ * Capabilities this plugin declares. The gated `ctx.cap` accessors throw for
1228
+ * anything not listed here. Pass as a literal (`['email:send'] as const` is not
1229
+ * needed — the `const` type param infers the tuple) to get `ctx.cap` narrowed.
1230
+ * Unknown/deprecated names are normalized then warned about at definition.
1231
+ */
1232
+ capabilities?: Caps;
1233
+ /** Declarative routes (mounted before the /admin catch-all). */
1234
+ routes?: MountableRoute[];
1235
+ /**
1236
+ * Imperative route registration. MUST be synchronous (Hono's router locks after
1237
+ * the first request). Async work belongs in `onBoot`.
1238
+ */
1239
+ register?: (app: Hono) => void;
1240
+ /**
1241
+ * Declarative typed hook subscriptions (`{ 'content:after:create': (p) => … }`).
1242
+ * Subscribed during the wire phase; each handler is narrowed to its event payload.
1243
+ */
1244
+ hooks?: DeclarativeHooks;
1245
+ /**
1246
+ * Run once on first request, after every plugin has registered. The place for
1247
+ * dynamic hook subscriptions (`ctx.hooks.on(...)`) and env-dependent setup.
1248
+ */
1249
+ onBoot?: (context: DefinedPluginContext<Caps>) => void | Promise<void>;
1250
+ /** Scheduled-work declarations (also list the expressions in wrangler.toml). */
1251
+ crons?: CronDeclaration[];
1252
+ /** Handler for a fired cron; branch on `event.hookFamily`. */
1253
+ onCronTick?: (event: CronTickEvent, context: DefinedPluginContext<Caps>) => void | Promise<void>;
1254
+ install?: (context: unknown) => void | Promise<void>;
1255
+ uninstall?: (context: unknown) => void | Promise<void>;
1256
+ activate?: (context: unknown) => void | Promise<void>;
1257
+ deactivate?: (context: unknown) => void | Promise<void>;
1258
+ /**
1259
+ * Declarative admin-sidebar entries collected by registerPlugins. The catalyst
1260
+ * sidebar renders them automatically; no per-plugin `addMenuItem(...)` call
1261
+ * required.
1262
+ */
1263
+ menu?: PluginMenuEntry[];
1264
+ /**
1265
+ * Schema-driven settings. Declaring this auto-renders the admin form at
1266
+ * `/admin/settings/plugins/:id`, parses FormData back into typed values, and
1267
+ * persists them via the plugin-service. Authors no longer hand-roll settings
1268
+ * routes/templates.
1269
+ */
1270
+ configSchema?: ConfigSchema;
1271
+ }
1272
+ /**
1273
+ * The runtime shape produced by {@link definePlugin}. Carries the legacy metadata
1274
+ * fields plus the v3 surfaces; structurally satisfies `MountablePlugin`,
1275
+ * `WirablePlugin`, and `CronablePlugin`.
1276
+ */
1277
+ interface DefinedPlugin {
1278
+ id: string;
1279
+ name: string;
1280
+ version: string;
1281
+ /** The semver range for SonicJS core compatibility declared by the author. */
1282
+ sonicjsVersionRange?: string;
1283
+ description?: string;
1284
+ author?: {
1285
+ name: string;
1286
+ email?: string;
1287
+ url?: string;
1288
+ };
1289
+ dependencies?: string[];
1290
+ capabilities: Capability[];
1291
+ routes?: MountableRoute[];
1292
+ register?: (app: Hono) => void;
1293
+ /** Declarative hook subscriptions, flattened for the wire phase. */
1294
+ hooks?: WirableHook[];
1295
+ /** Wrapped onBoot accepting the runtime's raw boot context. */
1296
+ onBoot?: (context: PluginBootContext) => void | Promise<void>;
1297
+ crons?: CronDeclaration[];
1298
+ /** Wrapped onCronTick accepting the runtime's raw cron context. */
1299
+ onCronTick?: (event: CronTickEvent, context: CronContext) => void | Promise<void>;
1300
+ install?: (context: unknown) => void | Promise<void>;
1301
+ uninstall?: (context: unknown) => void | Promise<void>;
1302
+ activate?: (context: unknown) => void | Promise<void>;
1303
+ deactivate?: (context: unknown) => void | Promise<void>;
1304
+ /** Declarative admin sidebar entries. registerPlugins collects + sets the menu singleton. */
1305
+ menu?: PluginMenuEntry[];
1306
+ /** Schema-driven settings. Renders the admin settings form for this plugin. */
1307
+ configSchema?: ConfigSchema;
1308
+ /** Marker so tooling/tests can detect a v3-defined plugin. */
1309
+ readonly __sonicV3: true;
1310
+ }
1311
+ /**
1312
+ * Define a v3 plugin. Validates declared capabilities (warns on unknown), then
1313
+ * returns a runtime-ready plugin whose `onBoot`/`onCronTick` receive the enriched,
1314
+ * typed, capability-gated context. The `const Caps` type parameter captures the
1315
+ * declared capability tuple so `ctx.cap` is narrowed at the author's call site.
1316
+ */
1317
+ declare function definePlugin<const Caps extends readonly Capability[] = readonly []>(input: DefinePluginInput<Caps>): DefinedPlugin;
1318
+ /** True if `plugin` was produced by {@link definePlugin}. */
1319
+ declare function isDefinedPlugin(plugin: unknown): plugin is DefinedPlugin;
1320
+
1321
+ export { collectCrons as $, RegisterPluginsError as A, type BooleanField as B, CAPABILITY_RENAMES as C, type DbCapability as D, type ExecutionContextLike as E, FIXED_CAPABILITIES as F, type RegisterPluginsErrorReason as G, HookSystemImpl as H, type RegisterPluginsHostContext as I, type RegisterablePlugin as J, type RegistryEntry as K, ScopedHookSystem as L, type MountResult as M, type NumberField as N, type SelectField as O, type ParsedField as P, type SettingsFor as Q, type RegisterPluginRoutesOptions as R, type ScheduledControllerLike as S, SonicCapabilityError as T, type StringField as U, type WirablePlugin as V, type WirableHook as W, type WireResult as X, applySchemaDefaults as Y, assertCapability as Z, collectCronSchedules as _, type Capability as a, createCapabilityContext as a0, createPluginWirer as a1, createScheduledHandler as a2, definePlugin as a3, dispatchCronTick as a4, getHookSystem as a5, getTypedHooks as a6, hasCapability as a7, hasHookSystem as a8, isDefinedPlugin as a9, isKnownCapability as aa, mountPlugin as ab, normalizeCapabilities as ac, normalizeCapability as ad, parseConfigSchema as ae, parseFormDataToSettings as af, registerPluginRoutes as ag, registerPlugins as ah, renderSchemaFields as ai, resetHookSystem as aj, setHookSystem as ak, validateCapabilities as al, wireRegisteredPlugins as am, type PluginMenuEntry as an, type ResolvedPluginMenuEntry as ao, getPluginMenu as ap, resetPluginMenu as aq, resolvePluginMenuItems as ar, setPluginMenu as as, type CapabilityContext as b, type CapabilityProviders as c, type CollectedCron as d, type ConfigSchema as e, type ConfigSchemaField as f, type CreateScheduledHandlerOptions as g, type CronContext as h, type CronDeclaration as i, type CronDispatchResult as j, type CronTickEvent as k, type CronablePlugin as l, type DeclarativeHooks as m, type DefinePluginInput as n, type DefinedPlugin as o, type DefinedPluginContext as p, type FixedCapability as q, HookUtils as r, type MountedRoute as s, type PluginBootContext as t, type PluginCapabilityContext as u, PluginManager as v, PluginRegisterMustBeSyncError as w, PluginRegistryImpl as x, PluginValidator as y, type PluginsRegistry as z };