@beechcms/api 0.6.0-preview.1 → 0.6.0-preview.2

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 (222) hide show
  1. package/assets/dashboard/BeechLogo.svg +18 -18
  2. package/assets/dashboard/BeechLogoLIght.svg +48 -48
  3. package/assets/dashboard/assets/chart-BrJFdsGZ.js +39 -0
  4. package/assets/dashboard/assets/index-CIaoMxuB.css +1 -0
  5. package/assets/dashboard/assets/index-wTKgygsg.js +636 -0
  6. package/assets/dashboard/assets/pie-chart-recharts-BEaE1FOE.js +1 -0
  7. package/assets/dashboard/assets/timeseries-chart-recharts-DmWM6imF.js +1 -0
  8. package/assets/dashboard/beechLogoDark.svg +48 -48
  9. package/assets/dashboard/index.html +18 -18
  10. package/assets/dashboard/noResult.svg +42 -42
  11. package/assets/dashboard/sol.svg +3 -3
  12. package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
  13. package/migrations/0000_v040_base.sql +265 -265
  14. package/migrations/0029_automations.sql +14 -14
  15. package/migrations/0030_test_seeds.sql +125 -125
  16. package/package.json +2 -2
  17. package/src/auth/__fixtures__/in-memory-hash-provider.ts +17 -17
  18. package/src/auth/__fixtures__/static-token-service.ts +22 -22
  19. package/src/auth/bcrypt-hash-provider.ts +24 -24
  20. package/src/auth/constants.ts +14 -14
  21. package/src/auth/generate-refresh-token.test.ts +23 -23
  22. package/src/auth/hash-provider.test.ts +55 -55
  23. package/src/auth/jose-token-service.ts +62 -62
  24. package/src/auth/jwt-claims-passthrough.test.ts +87 -87
  25. package/src/auth/login.test.ts +100 -100
  26. package/src/auth/login.ts +78 -78
  27. package/src/auth/refresh.ts +11 -11
  28. package/src/auth/token-service.test.ts +90 -90
  29. package/src/factory.ts +370 -368
  30. package/src/features/automations/__tests__/action-executors.test.ts +255 -255
  31. package/src/features/automations/__tests__/automation-runner.test.ts +196 -196
  32. package/src/features/automations/__tests__/automation-runner.utils.test.ts +60 -60
  33. package/src/features/automations/__tests__/automations.handler.test.ts +264 -264
  34. package/src/features/automations/__tests__/automations.repository.test.ts +163 -163
  35. package/src/features/automations/__tests__/automations.schema.test.ts +224 -224
  36. package/src/features/automations/__tests__/context-resolver.test.ts +126 -126
  37. package/src/features/automations/__tests__/cron-runner.test.ts +267 -267
  38. package/src/features/automations/__tests__/cron-runner.utils.test.ts +144 -144
  39. package/src/features/automations/__tests__/set-variable.executor.test.ts +383 -383
  40. package/src/features/automations/__tests__/template-grammar.test.ts +308 -308
  41. package/src/features/automations/__tests__/webhook.executor.test.ts +142 -142
  42. package/src/features/automations/__tests__/when-evaluator.test.ts +274 -274
  43. package/src/features/automations/__tests__/when-pushdown.test.ts +281 -281
  44. package/src/features/automations/action-executors/create-entry.executor.ts +27 -27
  45. package/src/features/automations/action-executors/edit-field.executor.ts +26 -26
  46. package/src/features/automations/action-executors/index.ts +37 -37
  47. package/src/features/automations/action-executors/send-mail.executor.ts +53 -53
  48. package/src/features/automations/action-executors/set-variable.executor.ts +159 -159
  49. package/src/features/automations/action-executors/webhook.executor.ts +67 -67
  50. package/src/features/automations/automation-runner.ts +85 -85
  51. package/src/features/automations/automation-runner.utils.ts +47 -47
  52. package/src/features/automations/automations.handler.ts +197 -197
  53. package/src/features/automations/automations.schema.ts +177 -177
  54. package/src/features/automations/context-resolver.ts +152 -152
  55. package/src/features/automations/cron-runner.ts +140 -140
  56. package/src/features/automations/cron-runner.utils.ts +44 -44
  57. package/src/features/automations/filter-translation.ts +46 -46
  58. package/src/features/automations/index.ts +16 -16
  59. package/src/features/automations/template-grammar.ts +245 -245
  60. package/src/features/automations/var-access-resolver.ts +140 -140
  61. package/src/features/automations/when-evaluator.ts +87 -87
  62. package/src/features/automations/when-pushdown.ts +57 -57
  63. package/src/features/backrefs/__tests__/backrefs.handler.test.ts +359 -359
  64. package/src/features/backrefs/backrefs.handler.ts +168 -168
  65. package/src/features/backrefs/d1-backref.repository.ts +84 -84
  66. package/src/features/backrefs/index.ts +5 -5
  67. package/src/features/content/constants.ts +16 -16
  68. package/src/features/content/handlers/bulk.handler.ts +185 -185
  69. package/src/features/content/handlers/create.ts +129 -129
  70. package/src/features/content/handlers/delete.ts +62 -62
  71. package/src/features/content/handlers/facets.ts +49 -49
  72. package/src/features/content/handlers/get.ts +120 -120
  73. package/src/features/content/handlers/helpers.test.ts +180 -180
  74. package/src/features/content/handlers/helpers.ts +93 -93
  75. package/src/features/content/handlers/list.ts +174 -174
  76. package/src/features/content/handlers/update.ts +174 -174
  77. package/src/features/content/index.ts +26 -26
  78. package/src/features/dashboard-layout/__tests__/dashboard-layout.handler.test.ts +388 -0
  79. package/src/features/dashboard-layout/dashboard-layout.handler.ts +206 -0
  80. package/src/features/dashboard-layout/index.ts +5 -0
  81. package/src/features/draft/draft.handler.ts +195 -195
  82. package/src/features/draft/draft.middleware.ts +66 -66
  83. package/src/features/draft/index.ts +5 -5
  84. package/src/features/email/email.provider.ts +42 -42
  85. package/src/features/email/email.service.ts +98 -98
  86. package/src/features/email/email.types.ts +119 -119
  87. package/src/features/email/index.ts +33 -33
  88. package/src/features/email/providers/resend.ts +67 -67
  89. package/src/features/email/providers/smtp.ts +55 -55
  90. package/src/features/email/templates/automation-mail.ts +19 -19
  91. package/src/features/email/templates/password-changed.ts +63 -63
  92. package/src/features/email/templates/password-reset.ts +68 -68
  93. package/src/features/email/templates/shell.ts +96 -96
  94. package/src/features/notifications/index.ts +5 -5
  95. package/src/features/notifications/notifications.handler.ts +105 -105
  96. package/src/features/password-reset/index.ts +19 -19
  97. package/src/features/password-reset/request.ts +92 -92
  98. package/src/features/password-reset/reset.ts +102 -102
  99. package/src/features/rotate-field/index.ts +5 -5
  100. package/src/features/rotate-field/rotate-field.handler.ts +132 -132
  101. package/src/features/rotate-field/rotate-field.schema.ts +17 -17
  102. package/src/features/schema/schema.handler.ts +131 -131
  103. package/src/features/seeds/index.ts +5 -5
  104. package/src/features/seeds/seeds.handler.test.ts +1199 -632
  105. package/src/features/seeds/seeds.handler.ts +558 -693
  106. package/src/features/settings/__tests__/settings.handler.test.ts +555 -555
  107. package/src/features/settings/settings.handler.ts +392 -392
  108. package/src/features/setup/index.ts +288 -204
  109. package/src/features/stats/index.ts +5 -5
  110. package/src/features/stats/stats.handler.ts +420 -420
  111. package/src/features/webhooks/index.ts +63 -63
  112. package/src/index.ts +65 -65
  113. package/src/media-utils.ts +82 -82
  114. package/src/middleware/auth-providers.middleware.test.ts +85 -0
  115. package/src/middleware/auth-providers.middleware.ts +45 -45
  116. package/src/middleware/observability.middleware.test.ts +93 -0
  117. package/src/middleware/observability.middleware.ts +70 -70
  118. package/src/middleware/rate-limit.middleware.ts +45 -45
  119. package/src/middleware/repository.middleware.ts +117 -114
  120. package/src/middleware/seed-registry.middleware.test.ts +97 -97
  121. package/src/middleware/seed-registry.middleware.ts +21 -21
  122. package/src/middleware/storage.middleware.ts +25 -25
  123. package/src/middleware.ts +45 -45
  124. package/src/public/access-policy.ts +27 -27
  125. package/src/public/api-key-middleware.ts +57 -57
  126. package/src/public/cache-utils.ts +38 -38
  127. package/src/public/entry-projection.ts +46 -46
  128. package/src/public/idempotency.ts +23 -23
  129. package/src/public/index.ts +16 -16
  130. package/src/public/problem-details.ts +124 -124
  131. package/src/public/public-add.ts +114 -114
  132. package/src/public/public-edit.ts +163 -163
  133. package/src/public/public-errors.ts +19 -19
  134. package/src/public/public-read.ts +63 -63
  135. package/src/public/public-routes.ts +88 -88
  136. package/src/public/query-builder.test.ts +224 -224
  137. package/src/public/query-builder.ts +156 -156
  138. package/src/public/rate-limit-middleware.ts +34 -34
  139. package/src/public/read-list.ts +54 -54
  140. package/src/public/read-single.ts +48 -48
  141. package/src/public/response-builder.ts +30 -30
  142. package/src/public/sanitize.ts +69 -69
  143. package/src/public/slug-utils.ts +18 -18
  144. package/src/rate-limit/cloudflare-rate-limiter.test.ts +30 -30
  145. package/src/rate-limit/cloudflare-rate-limiter.ts +15 -15
  146. package/src/rate-limit/in-memory-rate-limiter.test.ts +37 -37
  147. package/src/rate-limit/in-memory-rate-limiter.ts +17 -17
  148. package/src/rate-limit/no-op-rate-limiter.test.ts +22 -22
  149. package/src/rate-limit/no-op-rate-limiter.ts +11 -11
  150. package/src/search-utils.test.ts +211 -211
  151. package/src/search-utils.ts +213 -213
  152. package/src/search.ts +65 -65
  153. package/src/shared/apply-policies.test.ts +81 -81
  154. package/src/shared/apply-policies.ts +67 -67
  155. package/src/shared/automations.repository.d1.ts +150 -150
  156. package/src/shared/background-notification-service.test.ts +62 -62
  157. package/src/shared/background-notification-service.ts +52 -52
  158. package/src/shared/base.repository.d1.ts +32 -32
  159. package/src/shared/content-utils.test.ts +165 -165
  160. package/src/shared/content-utils.ts +86 -86
  161. package/src/shared/content.repository.d1.test.ts +507 -458
  162. package/src/shared/content.repository.d1.ts +851 -851
  163. package/src/shared/d1-activity-log.repository.test.ts +140 -140
  164. package/src/shared/d1-activity-log.repository.ts +105 -105
  165. package/src/shared/d1-activity-logger.test.ts +86 -86
  166. package/src/shared/d1-activity-logger.ts +67 -67
  167. package/src/shared/d1-analytics.repository.test.ts +78 -78
  168. package/src/shared/d1-analytics.repository.ts +85 -85
  169. package/src/shared/d1-content-scan.repository.test.ts +80 -80
  170. package/src/shared/d1-content-scan.repository.ts +37 -37
  171. package/src/shared/d1-notification.repository.test.ts +128 -128
  172. package/src/shared/d1-notification.repository.ts +118 -118
  173. package/src/shared/d1-password-reset-token.repository.test.ts +81 -81
  174. package/src/shared/d1-password-reset-token.repository.ts +56 -56
  175. package/src/shared/d1-search.repository.test.ts +87 -87
  176. package/src/shared/d1-search.repository.ts +88 -88
  177. package/src/shared/d1-session.repository.test.ts +125 -125
  178. package/src/shared/d1-session.repository.ts +102 -102
  179. package/src/shared/d1-setup-checklist.repository.ts +36 -36
  180. package/src/shared/d1-user.repository.test.ts +151 -151
  181. package/src/shared/d1-user.repository.ts +119 -119
  182. package/src/shared/d1-widget.repository.test.ts +267 -221
  183. package/src/shared/d1-widget.repository.ts +385 -341
  184. package/src/shared/dashboard-layout.repository.d1.test.ts +135 -0
  185. package/src/shared/dashboard-layout.repository.d1.ts +58 -0
  186. package/src/shared/demo-data-sql.ts +216 -54
  187. package/src/shared/demo-data.repository.d1.ts +20 -20
  188. package/src/shared/execution-context-scheduler.ts +13 -13
  189. package/src/shared/fixed-clock.ts +25 -25
  190. package/src/shared/fts-sync.ts +8 -8
  191. package/src/shared/idempotency.repository.d1.test.ts +83 -83
  192. package/src/shared/idempotency.repository.d1.ts +60 -60
  193. package/src/shared/in-memory-activity-logger.ts +19 -19
  194. package/src/shared/in-memory-notification-service.ts +19 -19
  195. package/src/shared/in-memory-seed.repository.ts +58 -51
  196. package/src/shared/media.repository.d1.test.ts +107 -107
  197. package/src/shared/media.repository.d1.ts +68 -68
  198. package/src/shared/qstash-notification-service.test.ts +67 -67
  199. package/src/shared/qstash-notification-service.ts +55 -55
  200. package/src/shared/query-utils.ts +141 -141
  201. package/src/shared/request-utils.ts +26 -26
  202. package/src/shared/schema-mutator.d1.test.ts +142 -0
  203. package/src/shared/schema-mutator.d1.ts +64 -64
  204. package/src/shared/seed-layout.repository.d1.test.ts +114 -114
  205. package/src/shared/seed-layout.repository.d1.ts +62 -62
  206. package/src/shared/seed-registry-cache.test.ts +68 -68
  207. package/src/shared/seed-registry-cache.ts +49 -49
  208. package/src/shared/seed.repository.d1.test.ts +143 -143
  209. package/src/shared/seed.repository.d1.ts +98 -98
  210. package/src/shared/sequential-id-generator.ts +33 -33
  211. package/src/shared/site-settings.repository.d1.ts +53 -53
  212. package/src/shared/storage/factory.ts +41 -41
  213. package/src/shared/storage/s3-bucket.ts +195 -195
  214. package/src/shared/storage-utils.ts +40 -40
  215. package/src/shared/system-stats.repository.d1.test.ts +58 -58
  216. package/src/shared/system-stats.repository.d1.ts +48 -48
  217. package/src/types.ts +90 -87
  218. package/src/upload.ts +198 -198
  219. package/src/widget.test.ts +85 -0
  220. package/src/widget.ts +251 -212
  221. package/assets/dashboard/assets/index-BSjk0Sx9.js +0 -634
  222. package/assets/dashboard/assets/index-kRCYVN2B.css +0 -1
@@ -0,0 +1,93 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
+ // See LICENSE in the repository root for license terms.
4
+
5
+ import { describe, it, expect, vi } from 'vitest'
6
+ import { Hono } from 'hono'
7
+ import { observabilityMiddleware } from './observability.middleware'
8
+ import type { IClock, IIdGenerator } from '@beechcms/core'
9
+
10
+ describe('observabilityMiddleware', () => {
11
+ it('injects default activityLogger and background notification service when no overrides are given', async () => {
12
+ const app = new Hono()
13
+
14
+ app.use('*', async (c, next) => {
15
+ c.set('notificationRepository' as never, {} as any)
16
+ await next()
17
+ })
18
+
19
+ app.use('*', observabilityMiddleware())
20
+
21
+ app.get('/test', (c) => {
22
+ const logger = c.get('activityLogger' as never)
23
+ const notifications = c.get('notificationService' as never)
24
+ return c.json({
25
+ hasLogger: !!logger,
26
+ hasNotifications: !!notifications,
27
+ })
28
+ })
29
+
30
+ const executionCtx = {
31
+ waitUntil: vi.fn(),
32
+ passThroughOnException: vi.fn(),
33
+ }
34
+
35
+ // Mock env with DB
36
+ const res = await app.request('/test', undefined, { DB: {} }, executionCtx)
37
+ const body = await res.json() as any
38
+ expect(body.hasLogger).toBe(true)
39
+ expect(body.hasNotifications).toBe(true)
40
+ })
41
+
42
+ it('respects overrides when provided', async () => {
43
+ const app = new Hono()
44
+
45
+ const mockLogger = { log: vi.fn() }
46
+ const mockNotifications = { send: vi.fn() }
47
+ const mockClock: IClock = { now: () => 12345 }
48
+ const mockIdGen: IIdGenerator = { nextId: () => 'custom-id' }
49
+
50
+ app.use('*', observabilityMiddleware({
51
+ activityLogger: mockLogger as any,
52
+ notificationService: mockNotifications as any,
53
+ clock: mockClock,
54
+ idGenerator: mockIdGen,
55
+ }))
56
+
57
+ app.get('/test', (c) => {
58
+ const logger = c.get('activityLogger' as never)
59
+ const notifications = c.get('notificationService' as never)
60
+ return c.json({
61
+ loggerIsOverridden: logger === mockLogger,
62
+ notificationsAreOverridden: notifications === mockNotifications,
63
+ })
64
+ })
65
+
66
+ const res = await app.request('/test')
67
+ const body = await res.json() as any
68
+ expect(body.loggerIsOverridden).toBe(true)
69
+ expect(body.notificationsAreOverridden).toBe(true)
70
+ })
71
+
72
+ it('uses QStashNotificationService when QSTASH_TOKEN and APP_URL are in Env', async () => {
73
+ const app = new Hono()
74
+ app.use('*', observabilityMiddleware())
75
+
76
+ app.get('/test', (c) => {
77
+ const notifications = c.get('notificationService' as never) as any
78
+ return c.json({
79
+ type: notifications?.constructor?.name,
80
+ })
81
+ })
82
+
83
+ const env = {
84
+ QSTASH_TOKEN: 'token',
85
+ APP_URL: 'https://my-app.com',
86
+ DB: {},
87
+ }
88
+
89
+ const res = await app.request('/test', undefined, env)
90
+ const body = await res.json() as any
91
+ expect(body.type).toBe('QStashNotificationService')
92
+ })
93
+ })
@@ -1,70 +1,70 @@
1
- // SPDX-License-Identifier: BUSL-1.1
2
- // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
- // See LICENSE in the repository root for license terms.
4
-
5
- import { createMiddleware } from 'hono/factory'
6
- import type { IActivityLogger, INotificationService, IClock, IIdGenerator } from '@beechcms/core'
7
- import { SystemClock, SystemIdGenerator } from '@beechcms/core'
8
- import type { AppEnv } from '../types'
9
- import { D1ActivityLogger } from '../shared/d1-activity-logger'
10
- import { BackgroundNotificationService } from '../shared/background-notification-service'
11
- import { QStashNotificationService } from '../shared/qstash-notification-service'
12
-
13
- export interface ObservabilityOverrides {
14
- activityLogger?: IActivityLogger
15
- notificationService?: INotificationService
16
- clock?: IClock
17
- idGenerator?: IIdGenerator
18
- }
19
-
20
- /**
21
- * Observability middleware.
22
- *
23
- * Injects the activity logger and the notification service into the Hono
24
- * context. Both depend on `executionCtx.waitUntil` to fire-and-forget their
25
- * persistence work in production. The notification service additionally
26
- * depends on the notification repository — `repositoryMiddleware` MUST run
27
- * before this middleware in the factory pipeline.
28
- *
29
- * Tests can pass their own implementations via `overrides` to bypass D1.
30
- */
31
- export const observabilityMiddleware = (overrides?: ObservabilityOverrides) => {
32
- return createMiddleware<AppEnv>(async (context, next) => {
33
- let scheduleBackgroundTask: ((task: Promise<unknown>) => void) | undefined
34
- try {
35
- const executionContext = context.executionCtx
36
- scheduleBackgroundTask = executionContext.waitUntil.bind(executionContext)
37
- } catch {
38
- scheduleBackgroundTask = undefined
39
- }
40
-
41
- const resolvedClock = overrides?.clock ?? SystemClock
42
- const resolvedIdGenerator = overrides?.idGenerator ?? SystemIdGenerator
43
-
44
- const activityLogger =
45
- overrides?.activityLogger ??
46
- new D1ActivityLogger(context.env.DB, resolvedClock, resolvedIdGenerator, scheduleBackgroundTask)
47
-
48
- let notificationService: INotificationService
49
- if (overrides?.notificationService) {
50
- notificationService = overrides.notificationService
51
- } else if (context.env.QSTASH_TOKEN && context.env.APP_URL) {
52
- notificationService = new QStashNotificationService(
53
- context.env.QSTASH_TOKEN,
54
- context.env.APP_URL,
55
- scheduleBackgroundTask,
56
- context.env.QSTASH_URL
57
- )
58
- } else {
59
- notificationService = new BackgroundNotificationService(
60
- context.get('notificationRepository'),
61
- scheduleBackgroundTask
62
- )
63
- }
64
-
65
- context.set('activityLogger', activityLogger)
66
- context.set('notificationService', notificationService)
67
-
68
- await next()
69
- })
70
- }
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
+ // See LICENSE in the repository root for license terms.
4
+
5
+ import { createMiddleware } from 'hono/factory'
6
+ import type { IActivityLogger, INotificationService, IClock, IIdGenerator } from '@beechcms/core'
7
+ import { SystemClock, SystemIdGenerator } from '@beechcms/core'
8
+ import type { AppEnv } from '../types'
9
+ import { D1ActivityLogger } from '../shared/d1-activity-logger'
10
+ import { BackgroundNotificationService } from '../shared/background-notification-service'
11
+ import { QStashNotificationService } from '../shared/qstash-notification-service'
12
+
13
+ export interface ObservabilityOverrides {
14
+ activityLogger?: IActivityLogger
15
+ notificationService?: INotificationService
16
+ clock?: IClock
17
+ idGenerator?: IIdGenerator
18
+ }
19
+
20
+ /**
21
+ * Observability middleware.
22
+ *
23
+ * Injects the activity logger and the notification service into the Hono
24
+ * context. Both depend on `executionCtx.waitUntil` to fire-and-forget their
25
+ * persistence work in production. The notification service additionally
26
+ * depends on the notification repository — `repositoryMiddleware` MUST run
27
+ * before this middleware in the factory pipeline.
28
+ *
29
+ * Tests can pass their own implementations via `overrides` to bypass D1.
30
+ */
31
+ export const observabilityMiddleware = (overrides?: ObservabilityOverrides) => {
32
+ return createMiddleware<AppEnv>(async (context, next) => {
33
+ let scheduleBackgroundTask: ((task: Promise<unknown>) => void) | undefined
34
+ try {
35
+ const executionContext = context.executionCtx
36
+ scheduleBackgroundTask = executionContext.waitUntil.bind(executionContext)
37
+ } catch {
38
+ scheduleBackgroundTask = undefined
39
+ }
40
+
41
+ const resolvedClock = overrides?.clock ?? SystemClock
42
+ const resolvedIdGenerator = overrides?.idGenerator ?? SystemIdGenerator
43
+
44
+ const activityLogger =
45
+ overrides?.activityLogger ??
46
+ new D1ActivityLogger(context.env.DB, resolvedClock, resolvedIdGenerator, scheduleBackgroundTask)
47
+
48
+ let notificationService: INotificationService
49
+ if (overrides?.notificationService) {
50
+ notificationService = overrides.notificationService
51
+ } else if (context.env.QSTASH_TOKEN && (context.env.QSTASH_CALLBACK_URL || context.env.APP_URL)) {
52
+ notificationService = new QStashNotificationService(
53
+ context.env.QSTASH_TOKEN,
54
+ (context.env.QSTASH_CALLBACK_URL ?? context.env.APP_URL) as string,
55
+ scheduleBackgroundTask,
56
+ context.env.QSTASH_URL
57
+ )
58
+ } else {
59
+ notificationService = new BackgroundNotificationService(
60
+ context.get('notificationRepository'),
61
+ scheduleBackgroundTask
62
+ )
63
+ }
64
+
65
+ context.set('activityLogger', activityLogger)
66
+ context.set('notificationService', notificationService)
67
+
68
+ await next()
69
+ })
70
+ }
@@ -1,45 +1,45 @@
1
- // SPDX-License-Identifier: BUSL-1.1
2
- // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
- // See LICENSE in the repository root for license terms.
4
-
5
- /// <reference types="@cloudflare/workers-types" />
6
- import { createMiddleware } from 'hono/factory'
7
- import type { IRateLimiter } from '@beechcms/core'
8
- import type { AppEnv, Env } from '../types'
9
- import { CloudflareRateLimiter } from '../rate-limit/cloudflare-rate-limiter'
10
- import { NoOpRateLimiter } from '../rate-limit/no-op-rate-limiter'
11
-
12
- export type RateLimiterName =
13
- | 'login'
14
- | 'tokenRefresh'
15
- | 'forgotPassword'
16
- | 'resetPassword'
17
- | 'publicApiRead'
18
- | 'publicApiWrite'
19
-
20
- export interface IRateLimiterRegistry {
21
- getLimiter(name: RateLimiterName): IRateLimiter
22
- }
23
-
24
- const NO_OP = new NoOpRateLimiter()
25
-
26
- function buildDefaultRegistry(env: Env): IRateLimiterRegistry {
27
- const limiters: Record<RateLimiterName, IRateLimiter> = {
28
- login: env.LOGIN_RATE_LIMITER ? new CloudflareRateLimiter(env.LOGIN_RATE_LIMITER) : NO_OP,
29
- tokenRefresh: env.REFRESH_RATE_LIMITER ? new CloudflareRateLimiter(env.REFRESH_RATE_LIMITER) : NO_OP,
30
- forgotPassword: env.FORGOT_PASSWORD_RATE_LIMITER ? new CloudflareRateLimiter(env.FORGOT_PASSWORD_RATE_LIMITER) : NO_OP,
31
- resetPassword: env.RESET_PASSWORD_RATE_LIMITER ? new CloudflareRateLimiter(env.RESET_PASSWORD_RATE_LIMITER) : NO_OP,
32
- publicApiRead: env.PUBLIC_READ_RATE_LIMITER ? new CloudflareRateLimiter(env.PUBLIC_READ_RATE_LIMITER) : NO_OP,
33
- publicApiWrite: env.PUBLIC_WRITE_RATE_LIMITER ? new CloudflareRateLimiter(env.PUBLIC_WRITE_RATE_LIMITER) : NO_OP,
34
- }
35
-
36
- return { getLimiter: (name) => limiters[name] }
37
- }
38
-
39
- export const rateLimiterMiddleware = (overrides?: { registry?: IRateLimiterRegistry }) => {
40
- return createMiddleware<AppEnv>(async (context, next) => {
41
- const registry = overrides?.registry ?? buildDefaultRegistry(context.env)
42
- context.set('rateLimiters', registry)
43
- await next()
44
- })
45
- }
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
+ // See LICENSE in the repository root for license terms.
4
+
5
+ /// <reference types="@cloudflare/workers-types" />
6
+ import { createMiddleware } from 'hono/factory'
7
+ import type { IRateLimiter } from '@beechcms/core'
8
+ import type { AppEnv, Env } from '../types'
9
+ import { CloudflareRateLimiter } from '../rate-limit/cloudflare-rate-limiter'
10
+ import { NoOpRateLimiter } from '../rate-limit/no-op-rate-limiter'
11
+
12
+ export type RateLimiterName =
13
+ | 'login'
14
+ | 'tokenRefresh'
15
+ | 'forgotPassword'
16
+ | 'resetPassword'
17
+ | 'publicApiRead'
18
+ | 'publicApiWrite'
19
+
20
+ export interface IRateLimiterRegistry {
21
+ getLimiter(name: RateLimiterName): IRateLimiter
22
+ }
23
+
24
+ const NO_OP = new NoOpRateLimiter()
25
+
26
+ function buildDefaultRegistry(env: Env): IRateLimiterRegistry {
27
+ const limiters: Record<RateLimiterName, IRateLimiter> = {
28
+ login: env.LOGIN_RATE_LIMITER ? new CloudflareRateLimiter(env.LOGIN_RATE_LIMITER) : NO_OP,
29
+ tokenRefresh: env.REFRESH_RATE_LIMITER ? new CloudflareRateLimiter(env.REFRESH_RATE_LIMITER) : NO_OP,
30
+ forgotPassword: env.FORGOT_PASSWORD_RATE_LIMITER ? new CloudflareRateLimiter(env.FORGOT_PASSWORD_RATE_LIMITER) : NO_OP,
31
+ resetPassword: env.RESET_PASSWORD_RATE_LIMITER ? new CloudflareRateLimiter(env.RESET_PASSWORD_RATE_LIMITER) : NO_OP,
32
+ publicApiRead: env.PUBLIC_READ_RATE_LIMITER ? new CloudflareRateLimiter(env.PUBLIC_READ_RATE_LIMITER) : NO_OP,
33
+ publicApiWrite: env.PUBLIC_WRITE_RATE_LIMITER ? new CloudflareRateLimiter(env.PUBLIC_WRITE_RATE_LIMITER) : NO_OP,
34
+ }
35
+
36
+ return { getLimiter: (name) => limiters[name] }
37
+ }
38
+
39
+ export const rateLimiterMiddleware = (overrides?: { registry?: IRateLimiterRegistry }) => {
40
+ return createMiddleware<AppEnv>(async (context, next) => {
41
+ const registry = overrides?.registry ?? buildDefaultRegistry(context.env)
42
+ context.set('rateLimiters', registry)
43
+ await next()
44
+ })
45
+ }
@@ -1,114 +1,117 @@
1
- // SPDX-License-Identifier: BUSL-1.1
2
- // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
- // See LICENSE in the repository root for license terms.
4
-
5
- import { createMiddleware } from 'hono/factory'
6
- import type { Context } from 'hono'
7
- import { D1ContentRepository } from '../shared/content.repository.d1'
8
- import { D1IdempotencyRepository } from '../shared/idempotency.repository.d1'
9
- import { D1MediaRepository } from '../shared/media.repository.d1'
10
- import { D1SystemStatsRepository } from '../shared/system-stats.repository.d1'
11
- import { D1UserRepository } from '../shared/d1-user.repository'
12
- import { D1SessionRepository } from '../shared/d1-session.repository'
13
- import { D1PasswordResetTokenRepository } from '../shared/d1-password-reset-token.repository'
14
- import { D1ActivityLogRepository } from '../shared/d1-activity-log.repository'
15
- import { D1NotificationRepository } from '../shared/d1-notification.repository'
16
- import { D1WidgetRepository } from '../shared/d1-widget.repository'
17
- import { D1SearchRepository } from '../shared/d1-search.repository'
18
- import { D1AnalyticsRepository } from '../shared/d1-analytics.repository'
19
- import { D1ContentScanRepository } from '../shared/d1-content-scan.repository'
20
- import { D1SiteSettingsRepository } from '../shared/site-settings.repository.d1'
21
- import { D1DemoDataRepository } from '../shared/demo-data.repository.d1'
22
- import { D1SetupChecklistRepository } from '../shared/d1-setup-checklist.repository'
23
- import { D1SeedLayoutRepository } from '../shared/seed-layout.repository.d1'
24
- import { D1SeedRepository } from '../shared/seed.repository.d1'
25
- import { D1SchemaMutator } from '../shared/schema-mutator.d1'
26
- import { SystemClock, SystemIdGenerator } from '@beechcms/core'
27
- import type { ContentRepository, IdempotencyRepository, MediaRepository, SystemStatsRepository, IUserRepository, ISessionRepository, IPasswordResetTokenRepository, IActivityLogRepository, INotificationRepository, IWidgetRepository, ISearchRepository, IAnalyticsRepository, IContentScanRepository, IClock, IIdGenerator, IAutomationRunner, IAutomationRepository, IScheduler, ISiteSettingsRepository, IDemoDataRepository, ISeedLayoutRepository, ISeedRepository, ISchemaMutator } from '@beechcms/core'
28
- import { NoOpScheduler } from '@beechcms/core'
29
- import { AutomationRunner } from '../features/automations'
30
- import { D1AutomationRepository } from '../shared/automations.repository.d1'
31
- import { ExecutionContextScheduler } from '../shared/execution-context-scheduler'
32
- import type { Env, Variables } from '../types'
33
-
34
- interface RepositoryOverrides {
35
- repository?: ContentRepository
36
- idempotencyRepository?: IdempotencyRepository
37
- mediaRepository?: MediaRepository
38
- systemStatsRepository?: SystemStatsRepository
39
- userRepository?: IUserRepository
40
- sessionRepository?: ISessionRepository
41
- passwordResetTokenRepository?: IPasswordResetTokenRepository
42
- activityLogRepository?: IActivityLogRepository
43
- notificationRepository?: INotificationRepository
44
- widgetRepository?: IWidgetRepository
45
- searchRepository?: ISearchRepository
46
- analyticsRepository?: IAnalyticsRepository
47
- contentScanRepository?: IContentScanRepository
48
- clock?: IClock
49
- idGenerator?: IIdGenerator
50
- automationRepository?: IAutomationRepository
51
- automationRunner?: IAutomationRunner
52
- scheduler?: IScheduler
53
- siteSettingsRepository?: ISiteSettingsRepository
54
- demoDataRepository?: IDemoDataRepository
55
- seedLayoutRepository?: ISeedLayoutRepository
56
- seedRepository?: ISeedRepository
57
- schemaMutator?: ISchemaMutator
58
- }
59
-
60
- function buildScheduler(context: Context): IScheduler {
61
- try {
62
- return new ExecutionContextScheduler(context.executionCtx)
63
- } catch {
64
- return new NoOpScheduler()
65
- }
66
- }
67
-
68
- export const repositoryMiddleware = (overrides?: RepositoryOverrides) => {
69
- return createMiddleware<{ Bindings: Env; Variables: Variables }>(async (context, next) => {
70
- const resolvedClock = overrides?.clock ?? SystemClock
71
- const resolvedIdGenerator = overrides?.idGenerator ?? SystemIdGenerator
72
- const database = context.env.DB
73
-
74
- context.set('repository', overrides?.repository ?? new D1ContentRepository(database))
75
- context.set('idempotencyRepository', overrides?.idempotencyRepository ?? new D1IdempotencyRepository(database))
76
- context.set('mediaRepository', overrides?.mediaRepository ?? new D1MediaRepository(database))
77
- context.set('systemStatsRepository', overrides?.systemStatsRepository ?? new D1SystemStatsRepository(database))
78
- context.set('userRepository', overrides?.userRepository ?? new D1UserRepository(database))
79
- context.set('sessionRepository', overrides?.sessionRepository ?? new D1SessionRepository(database, resolvedClock))
80
- context.set('passwordResetTokenRepository', overrides?.passwordResetTokenRepository ?? new D1PasswordResetTokenRepository(database, resolvedIdGenerator))
81
- context.set('activityLogRepository', overrides?.activityLogRepository ?? new D1ActivityLogRepository(database))
82
- context.set('notificationRepository', overrides?.notificationRepository ?? new D1NotificationRepository(database, resolvedClock, resolvedIdGenerator))
83
- context.set('widgetRepository', overrides?.widgetRepository ?? new D1WidgetRepository(database))
84
- context.set('searchRepository', overrides?.searchRepository ?? new D1SearchRepository(database))
85
- context.set('analyticsRepository', overrides?.analyticsRepository ?? new D1AnalyticsRepository(database, resolvedClock))
86
- context.set('contentScanRepository', overrides?.contentScanRepository ?? new D1ContentScanRepository(database))
87
- context.set('clock', resolvedClock)
88
- context.set('idGenerator', resolvedIdGenerator)
89
- const automationRepository = overrides?.automationRepository
90
- ?? new D1AutomationRepository(database)
91
-
92
- context.set('automationRepository', automationRepository)
93
- // getSeed is set later by seedRegistryMiddleware — capture context by closure so the
94
- // runner reads the live value at call time, not at middleware creation time.
95
- context.set(
96
- 'automationRunner',
97
- overrides?.automationRunner ?? new AutomationRunner({
98
- automationRepository,
99
- contentRepository: context.get('repository'),
100
- getSeed: (slug: string) => context.get('getSeed')?.(slug) ?? null,
101
- idGenerator: resolvedIdGenerator,
102
- env: context.env as unknown as Record<string, string | undefined>,
103
- }),
104
- )
105
- context.set('scheduler', overrides?.scheduler ?? buildScheduler(context))
106
- context.set('siteSettingsRepository', overrides?.siteSettingsRepository ?? new D1SiteSettingsRepository(database))
107
- context.set('demoDataRepository', overrides?.demoDataRepository ?? new D1DemoDataRepository(database))
108
- context.set('setupChecklistRepository', new D1SetupChecklistRepository(database))
109
- context.set('seedLayoutRepository', overrides?.seedLayoutRepository ?? new D1SeedLayoutRepository(database))
110
- context.set('seedRepository', overrides?.seedRepository ?? new D1SeedRepository(database))
111
- context.set('schemaMutator', overrides?.schemaMutator ?? new D1SchemaMutator(database))
112
- await next()
113
- })
114
- }
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ // Copyright (c) 2024–2026 Flavio De Musso. All rights reserved.
3
+ // See LICENSE in the repository root for license terms.
4
+
5
+ import { createMiddleware } from 'hono/factory'
6
+ import type { Context } from 'hono'
7
+ import { D1ContentRepository } from '../shared/content.repository.d1'
8
+ import { D1IdempotencyRepository } from '../shared/idempotency.repository.d1'
9
+ import { D1MediaRepository } from '../shared/media.repository.d1'
10
+ import { D1SystemStatsRepository } from '../shared/system-stats.repository.d1'
11
+ import { D1UserRepository } from '../shared/d1-user.repository'
12
+ import { D1SessionRepository } from '../shared/d1-session.repository'
13
+ import { D1PasswordResetTokenRepository } from '../shared/d1-password-reset-token.repository'
14
+ import { D1ActivityLogRepository } from '../shared/d1-activity-log.repository'
15
+ import { D1NotificationRepository } from '../shared/d1-notification.repository'
16
+ import { D1WidgetRepository } from '../shared/d1-widget.repository'
17
+ import { D1SearchRepository } from '../shared/d1-search.repository'
18
+ import { D1AnalyticsRepository } from '../shared/d1-analytics.repository'
19
+ import { D1ContentScanRepository } from '../shared/d1-content-scan.repository'
20
+ import { D1SiteSettingsRepository } from '../shared/site-settings.repository.d1'
21
+ import { D1DemoDataRepository } from '../shared/demo-data.repository.d1'
22
+ import { D1SetupChecklistRepository } from '../shared/d1-setup-checklist.repository'
23
+ import { D1SeedLayoutRepository } from '../shared/seed-layout.repository.d1'
24
+ import { D1DashboardLayoutRepository } from '../shared/dashboard-layout.repository.d1'
25
+ import { D1SeedRepository } from '../shared/seed.repository.d1'
26
+ import { D1SchemaMutator } from '../shared/schema-mutator.d1'
27
+ import { SystemClock, SystemIdGenerator } from '@beechcms/core'
28
+ import type { ContentRepository, IdempotencyRepository, MediaRepository, SystemStatsRepository, IUserRepository, ISessionRepository, IPasswordResetTokenRepository, IActivityLogRepository, INotificationRepository, IWidgetRepository, ISearchRepository, IAnalyticsRepository, IContentScanRepository, IClock, IIdGenerator, IAutomationRunner, IAutomationRepository, IScheduler, ISiteSettingsRepository, IDemoDataRepository, ISeedLayoutRepository, ISeedRepository, ISchemaMutator, IDashboardLayoutRepository } from '@beechcms/core'
29
+ import { NoOpScheduler } from '@beechcms/core'
30
+ import { AutomationRunner } from '../features/automations'
31
+ import { D1AutomationRepository } from '../shared/automations.repository.d1'
32
+ import { ExecutionContextScheduler } from '../shared/execution-context-scheduler'
33
+ import type { Env, Variables } from '../types'
34
+
35
+ interface RepositoryOverrides {
36
+ repository?: ContentRepository
37
+ idempotencyRepository?: IdempotencyRepository
38
+ mediaRepository?: MediaRepository
39
+ systemStatsRepository?: SystemStatsRepository
40
+ userRepository?: IUserRepository
41
+ sessionRepository?: ISessionRepository
42
+ passwordResetTokenRepository?: IPasswordResetTokenRepository
43
+ activityLogRepository?: IActivityLogRepository
44
+ notificationRepository?: INotificationRepository
45
+ widgetRepository?: IWidgetRepository
46
+ searchRepository?: ISearchRepository
47
+ analyticsRepository?: IAnalyticsRepository
48
+ contentScanRepository?: IContentScanRepository
49
+ clock?: IClock
50
+ idGenerator?: IIdGenerator
51
+ automationRepository?: IAutomationRepository
52
+ automationRunner?: IAutomationRunner
53
+ scheduler?: IScheduler
54
+ siteSettingsRepository?: ISiteSettingsRepository
55
+ demoDataRepository?: IDemoDataRepository
56
+ seedLayoutRepository?: ISeedLayoutRepository
57
+ seedRepository?: ISeedRepository
58
+ schemaMutator?: ISchemaMutator
59
+ dashboardLayoutRepository?: IDashboardLayoutRepository
60
+ }
61
+
62
+ function buildScheduler(context: Context): IScheduler {
63
+ try {
64
+ return new ExecutionContextScheduler(context.executionCtx)
65
+ } catch {
66
+ return new NoOpScheduler()
67
+ }
68
+ }
69
+
70
+ export const repositoryMiddleware = (overrides?: RepositoryOverrides) => {
71
+ return createMiddleware<{ Bindings: Env; Variables: Variables }>(async (context, next) => {
72
+ const resolvedClock = overrides?.clock ?? SystemClock
73
+ const resolvedIdGenerator = overrides?.idGenerator ?? SystemIdGenerator
74
+ const database = context.env.DB
75
+
76
+ context.set('repository', overrides?.repository ?? new D1ContentRepository(database))
77
+ context.set('idempotencyRepository', overrides?.idempotencyRepository ?? new D1IdempotencyRepository(database))
78
+ context.set('mediaRepository', overrides?.mediaRepository ?? new D1MediaRepository(database))
79
+ context.set('systemStatsRepository', overrides?.systemStatsRepository ?? new D1SystemStatsRepository(database))
80
+ context.set('userRepository', overrides?.userRepository ?? new D1UserRepository(database))
81
+ context.set('sessionRepository', overrides?.sessionRepository ?? new D1SessionRepository(database, resolvedClock))
82
+ context.set('passwordResetTokenRepository', overrides?.passwordResetTokenRepository ?? new D1PasswordResetTokenRepository(database, resolvedIdGenerator))
83
+ context.set('activityLogRepository', overrides?.activityLogRepository ?? new D1ActivityLogRepository(database))
84
+ context.set('notificationRepository', overrides?.notificationRepository ?? new D1NotificationRepository(database, resolvedClock, resolvedIdGenerator))
85
+ context.set('widgetRepository', overrides?.widgetRepository ?? new D1WidgetRepository(database))
86
+ context.set('searchRepository', overrides?.searchRepository ?? new D1SearchRepository(database))
87
+ context.set('analyticsRepository', overrides?.analyticsRepository ?? new D1AnalyticsRepository(database, resolvedClock))
88
+ context.set('contentScanRepository', overrides?.contentScanRepository ?? new D1ContentScanRepository(database))
89
+ context.set('clock', resolvedClock)
90
+ context.set('idGenerator', resolvedIdGenerator)
91
+ const automationRepository = overrides?.automationRepository
92
+ ?? new D1AutomationRepository(database)
93
+
94
+ context.set('automationRepository', automationRepository)
95
+ // getSeed is set later by seedRegistryMiddleware — capture context by closure so the
96
+ // runner reads the live value at call time, not at middleware creation time.
97
+ context.set(
98
+ 'automationRunner',
99
+ overrides?.automationRunner ?? new AutomationRunner({
100
+ automationRepository,
101
+ contentRepository: context.get('repository'),
102
+ getSeed: (slug: string) => context.get('getSeed')?.(slug) ?? null,
103
+ idGenerator: resolvedIdGenerator,
104
+ env: context.env as unknown as Record<string, string | undefined>,
105
+ }),
106
+ )
107
+ context.set('scheduler', overrides?.scheduler ?? buildScheduler(context))
108
+ context.set('siteSettingsRepository', overrides?.siteSettingsRepository ?? new D1SiteSettingsRepository(database))
109
+ context.set('demoDataRepository', overrides?.demoDataRepository ?? new D1DemoDataRepository(database))
110
+ context.set('setupChecklistRepository', new D1SetupChecklistRepository(database))
111
+ context.set('seedLayoutRepository', overrides?.seedLayoutRepository ?? new D1SeedLayoutRepository(database))
112
+ context.set('seedRepository', overrides?.seedRepository ?? new D1SeedRepository(database))
113
+ context.set('schemaMutator', overrides?.schemaMutator ?? new D1SchemaMutator(database))
114
+ context.set('dashboardLayoutRepository', overrides?.dashboardLayoutRepository ?? new D1DashboardLayoutRepository(database))
115
+ await next()
116
+ })
117
+ }