@beechcms/api 0.5.0 → 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 (225) hide show
  1. package/assets/dashboard/BeechLogo.svg +18 -18
  2. package/assets/dashboard/BeechLogoLIght.svg +48 -48
  3. package/assets/dashboard/Searching.svg +1 -0
  4. package/assets/dashboard/assets/chart-BrJFdsGZ.js +39 -0
  5. package/assets/dashboard/assets/index-CIaoMxuB.css +1 -0
  6. package/assets/dashboard/assets/index-wTKgygsg.js +636 -0
  7. package/assets/dashboard/assets/pie-chart-recharts-BEaE1FOE.js +1 -0
  8. package/assets/dashboard/assets/timeseries-chart-recharts-DmWM6imF.js +1 -0
  9. package/assets/dashboard/beechLogoDark.svg +48 -48
  10. package/assets/dashboard/index.html +18 -18
  11. package/assets/dashboard/noResult.svg +43 -0
  12. package/assets/dashboard/sol.svg +3 -3
  13. package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
  14. package/assets/dashboard/working.svg +1 -0
  15. package/migrations/0000_v040_base.sql +265 -238
  16. package/migrations/0029_automations.sql +14 -14
  17. package/migrations/0030_test_seeds.sql +125 -125
  18. package/package.json +13 -9
  19. package/src/auth/{in-memory-hash-provider.ts → __fixtures__/in-memory-hash-provider.ts} +17 -13
  20. package/src/auth/{static-token-service.ts → __fixtures__/static-token-service.ts} +22 -18
  21. package/src/auth/bcrypt-hash-provider.ts +24 -20
  22. package/src/auth/constants.ts +14 -10
  23. package/src/auth/generate-refresh-token.test.ts +23 -19
  24. package/src/auth/hash-provider.test.ts +55 -46
  25. package/src/auth/jose-token-service.ts +62 -55
  26. package/src/auth/jwt-claims-passthrough.test.ts +87 -0
  27. package/src/auth/login.test.ts +100 -92
  28. package/src/auth/login.ts +78 -74
  29. package/src/auth/refresh.ts +11 -5
  30. package/src/auth/token-service.test.ts +90 -82
  31. package/src/factory.ts +370 -347
  32. package/src/features/automations/__tests__/action-executors.test.ts +255 -268
  33. package/src/features/automations/__tests__/automation-runner.test.ts +196 -192
  34. package/src/features/automations/__tests__/automation-runner.utils.test.ts +60 -56
  35. package/src/features/automations/__tests__/automations.handler.test.ts +264 -260
  36. package/src/features/automations/__tests__/automations.repository.test.ts +163 -159
  37. package/src/features/automations/__tests__/automations.schema.test.ts +224 -134
  38. package/src/features/automations/__tests__/context-resolver.test.ts +126 -122
  39. package/src/features/automations/__tests__/cron-runner.test.ts +267 -263
  40. package/src/features/automations/__tests__/cron-runner.utils.test.ts +144 -140
  41. package/src/features/automations/__tests__/set-variable.executor.test.ts +383 -306
  42. package/src/features/automations/__tests__/template-grammar.test.ts +308 -304
  43. package/src/features/automations/__tests__/webhook.executor.test.ts +142 -0
  44. package/src/features/automations/__tests__/when-evaluator.test.ts +274 -270
  45. package/src/features/automations/__tests__/when-pushdown.test.ts +281 -277
  46. package/src/features/automations/action-executors/create-entry.executor.ts +27 -23
  47. package/src/features/automations/action-executors/edit-field.executor.ts +26 -22
  48. package/src/features/automations/action-executors/index.ts +37 -33
  49. package/src/features/automations/action-executors/send-mail.executor.ts +53 -42
  50. package/src/features/automations/action-executors/set-variable.executor.ts +159 -146
  51. package/src/features/automations/action-executors/webhook.executor.ts +67 -25
  52. package/src/features/automations/automation-runner.ts +85 -81
  53. package/src/features/automations/automation-runner.utils.ts +47 -43
  54. package/src/features/automations/automations.handler.ts +197 -193
  55. package/src/features/automations/automations.schema.ts +177 -160
  56. package/src/features/automations/context-resolver.ts +152 -148
  57. package/src/features/automations/cron-runner.ts +140 -136
  58. package/src/features/automations/cron-runner.utils.ts +44 -40
  59. package/src/features/automations/filter-translation.ts +46 -42
  60. package/src/features/automations/index.ts +16 -12
  61. package/src/features/automations/template-grammar.ts +245 -241
  62. package/src/features/automations/var-access-resolver.ts +140 -136
  63. package/src/features/automations/when-evaluator.ts +87 -83
  64. package/src/features/automations/when-pushdown.ts +57 -53
  65. package/src/features/backrefs/__tests__/backrefs.handler.test.ts +359 -0
  66. package/src/features/backrefs/backrefs.handler.ts +168 -0
  67. package/src/features/backrefs/d1-backref.repository.ts +84 -0
  68. package/src/features/backrefs/index.ts +5 -0
  69. package/src/features/content/constants.ts +16 -10
  70. package/src/features/content/handlers/bulk.handler.ts +185 -0
  71. package/src/features/content/handlers/create.ts +129 -165
  72. package/src/features/content/handlers/delete.ts +62 -87
  73. package/src/features/content/handlers/facets.ts +49 -45
  74. package/src/features/content/handlers/get.ts +120 -116
  75. package/src/features/content/handlers/helpers.test.ts +180 -0
  76. package/src/features/content/handlers/helpers.ts +93 -0
  77. package/src/features/content/handlers/list.ts +174 -88
  78. package/src/features/content/handlers/update.ts +174 -219
  79. package/src/features/content/index.ts +26 -20
  80. package/src/features/dashboard-layout/__tests__/dashboard-layout.handler.test.ts +388 -0
  81. package/src/features/dashboard-layout/dashboard-layout.handler.ts +206 -0
  82. package/src/features/dashboard-layout/index.ts +5 -0
  83. package/src/features/draft/draft.handler.ts +195 -170
  84. package/src/features/draft/draft.middleware.ts +66 -62
  85. package/src/features/draft/index.ts +5 -1
  86. package/src/features/email/email.provider.ts +42 -38
  87. package/src/features/email/email.service.ts +98 -93
  88. package/src/features/email/email.types.ts +119 -108
  89. package/src/features/email/index.ts +33 -29
  90. package/src/features/email/providers/resend.ts +67 -63
  91. package/src/features/email/providers/smtp.ts +55 -0
  92. package/src/features/email/templates/automation-mail.ts +19 -15
  93. package/src/features/email/templates/password-changed.ts +63 -59
  94. package/src/features/email/templates/password-reset.ts +68 -64
  95. package/src/features/email/templates/shell.ts +96 -92
  96. package/src/features/notifications/index.ts +5 -1
  97. package/src/features/notifications/notifications.handler.ts +105 -101
  98. package/src/features/password-reset/index.ts +19 -15
  99. package/src/features/password-reset/request.ts +92 -82
  100. package/src/features/password-reset/reset.ts +102 -92
  101. package/src/features/rotate-field/index.ts +5 -1
  102. package/src/features/rotate-field/rotate-field.handler.ts +132 -128
  103. package/src/features/rotate-field/rotate-field.schema.ts +17 -13
  104. package/src/features/schema/schema.handler.ts +131 -16
  105. package/src/features/seeds/index.ts +5 -0
  106. package/src/features/seeds/seeds.handler.test.ts +1199 -0
  107. package/src/features/seeds/seeds.handler.ts +558 -0
  108. package/src/features/settings/__tests__/settings.handler.test.ts +555 -0
  109. package/src/features/settings/settings.handler.ts +392 -302
  110. package/src/features/setup/index.ts +288 -91
  111. package/src/features/stats/index.ts +5 -1
  112. package/src/features/stats/stats.handler.ts +420 -430
  113. package/src/features/webhooks/index.ts +63 -0
  114. package/src/index.ts +65 -56
  115. package/src/media-utils.ts +82 -78
  116. package/src/middleware/auth-providers.middleware.test.ts +85 -0
  117. package/src/middleware/auth-providers.middleware.ts +45 -32
  118. package/src/middleware/observability.middleware.test.ts +93 -0
  119. package/src/middleware/observability.middleware.ts +70 -52
  120. package/src/middleware/rate-limit.middleware.ts +45 -41
  121. package/src/middleware/repository.middleware.ts +117 -91
  122. package/src/middleware/seed-registry.middleware.test.ts +97 -0
  123. package/src/middleware/seed-registry.middleware.ts +21 -0
  124. package/src/middleware/storage.middleware.ts +25 -21
  125. package/src/middleware.ts +45 -47
  126. package/src/public/access-policy.ts +27 -23
  127. package/src/public/api-key-middleware.ts +57 -53
  128. package/src/public/cache-utils.ts +38 -34
  129. package/src/public/entry-projection.ts +46 -42
  130. package/src/public/idempotency.ts +23 -19
  131. package/src/public/index.ts +16 -12
  132. package/src/public/problem-details.ts +124 -53
  133. package/src/public/public-add.ts +114 -110
  134. package/src/public/public-edit.ts +163 -159
  135. package/src/public/public-errors.ts +19 -15
  136. package/src/public/public-read.ts +63 -59
  137. package/src/public/public-routes.ts +88 -84
  138. package/src/public/query-builder.test.ts +224 -220
  139. package/src/public/query-builder.ts +156 -152
  140. package/src/public/rate-limit-middleware.ts +34 -30
  141. package/src/public/read-list.ts +54 -50
  142. package/src/public/read-single.ts +48 -44
  143. package/src/public/response-builder.ts +30 -26
  144. package/src/public/sanitize.ts +69 -65
  145. package/src/public/slug-utils.ts +18 -14
  146. package/src/rate-limit/cloudflare-rate-limiter.test.ts +30 -26
  147. package/src/rate-limit/cloudflare-rate-limiter.ts +15 -11
  148. package/src/rate-limit/in-memory-rate-limiter.test.ts +37 -33
  149. package/src/rate-limit/in-memory-rate-limiter.ts +17 -13
  150. package/src/rate-limit/no-op-rate-limiter.test.ts +22 -18
  151. package/src/rate-limit/no-op-rate-limiter.ts +11 -7
  152. package/src/search-utils.test.ts +211 -207
  153. package/src/search-utils.ts +213 -209
  154. package/src/search.ts +65 -61
  155. package/src/shared/apply-policies.test.ts +81 -77
  156. package/src/shared/apply-policies.ts +67 -63
  157. package/src/shared/automations.repository.d1.ts +150 -146
  158. package/src/shared/background-notification-service.test.ts +62 -58
  159. package/src/shared/background-notification-service.ts +52 -48
  160. package/src/shared/base.repository.d1.ts +32 -28
  161. package/src/shared/content-utils.test.ts +165 -161
  162. package/src/shared/content-utils.ts +86 -82
  163. package/src/shared/content.repository.d1.test.ts +507 -312
  164. package/src/shared/content.repository.d1.ts +851 -382
  165. package/src/shared/d1-activity-log.repository.test.ts +140 -136
  166. package/src/shared/d1-activity-log.repository.ts +105 -101
  167. package/src/shared/d1-activity-logger.test.ts +86 -82
  168. package/src/shared/d1-activity-logger.ts +67 -63
  169. package/src/shared/d1-analytics.repository.test.ts +78 -74
  170. package/src/shared/d1-analytics.repository.ts +85 -81
  171. package/src/shared/d1-content-scan.repository.test.ts +80 -76
  172. package/src/shared/d1-content-scan.repository.ts +37 -29
  173. package/src/shared/d1-notification.repository.test.ts +128 -124
  174. package/src/shared/d1-notification.repository.ts +118 -114
  175. package/src/shared/d1-password-reset-token.repository.test.ts +81 -77
  176. package/src/shared/d1-password-reset-token.repository.ts +56 -52
  177. package/src/shared/d1-search.repository.test.ts +87 -83
  178. package/src/shared/d1-search.repository.ts +88 -84
  179. package/src/shared/d1-session.repository.test.ts +125 -121
  180. package/src/shared/d1-session.repository.ts +102 -98
  181. package/src/shared/d1-setup-checklist.repository.ts +36 -0
  182. package/src/shared/d1-user.repository.test.ts +151 -147
  183. package/src/shared/d1-user.repository.ts +119 -109
  184. package/src/shared/d1-widget.repository.test.ts +267 -217
  185. package/src/shared/d1-widget.repository.ts +385 -337
  186. package/src/shared/dashboard-layout.repository.d1.test.ts +135 -0
  187. package/src/shared/dashboard-layout.repository.d1.ts +58 -0
  188. package/src/shared/demo-data-sql.ts +216 -0
  189. package/src/shared/demo-data.repository.d1.ts +20 -0
  190. package/src/shared/execution-context-scheduler.ts +13 -9
  191. package/src/shared/fixed-clock.ts +25 -21
  192. package/src/shared/fts-sync.ts +8 -4
  193. package/src/shared/idempotency.repository.d1.test.ts +83 -79
  194. package/src/shared/idempotency.repository.d1.ts +60 -56
  195. package/src/shared/in-memory-activity-logger.ts +19 -15
  196. package/src/shared/in-memory-notification-service.ts +19 -15
  197. package/src/shared/in-memory-seed.repository.ts +58 -0
  198. package/src/shared/media.repository.d1.test.ts +107 -103
  199. package/src/shared/media.repository.d1.ts +68 -64
  200. package/src/shared/qstash-notification-service.test.ts +67 -0
  201. package/src/shared/qstash-notification-service.ts +55 -0
  202. package/src/shared/query-utils.ts +141 -137
  203. package/src/shared/request-utils.ts +26 -22
  204. package/src/shared/schema-mutator.d1.test.ts +142 -0
  205. package/src/shared/schema-mutator.d1.ts +64 -0
  206. package/src/shared/seed-layout.repository.d1.test.ts +114 -0
  207. package/src/shared/seed-layout.repository.d1.ts +62 -0
  208. package/src/shared/seed-registry-cache.test.ts +68 -0
  209. package/src/shared/seed-registry-cache.ts +49 -0
  210. package/src/shared/seed.repository.d1.test.ts +143 -0
  211. package/src/shared/seed.repository.d1.ts +98 -0
  212. package/src/shared/sequential-id-generator.ts +33 -22
  213. package/src/shared/site-settings.repository.d1.ts +53 -0
  214. package/src/shared/storage/factory.ts +41 -40
  215. package/src/shared/storage/s3-bucket.ts +195 -163
  216. package/src/shared/storage-utils.ts +40 -36
  217. package/src/shared/system-stats.repository.d1.test.ts +58 -54
  218. package/src/shared/system-stats.repository.d1.ts +48 -44
  219. package/src/types.ts +90 -67
  220. package/src/upload.ts +198 -187
  221. package/src/widget.test.ts +85 -0
  222. package/src/widget.ts +251 -208
  223. package/assets/dashboard/assets/index-B1mUfgiK.css +0 -1
  224. package/assets/dashboard/assets/index-CHxxc_id.js +0 -629
  225. package/src/shared/storage/r2-binding-bucket.ts +0 -81
package/src/middleware.ts CHANGED
@@ -1,47 +1,45 @@
1
- /// <reference types="@cloudflare/workers-types" />
2
- import type { Context, Next } from 'hono'
3
- import { HTTPException } from 'hono/http-exception'
4
- import type { Env, Variables } from './types'
5
-
6
- export type JwtPayload = {
7
- sub: string
8
- email?: string
9
- name?: string
10
- }
11
-
12
- const UNAUTHORIZED_JSON = JSON.stringify({ error: 'Unauthorized' })
13
-
14
- function unauthorizedResponse() {
15
- return new Response(UNAUTHORIZED_JSON, {
16
- status: 401,
17
- headers: { 'Content-Type': 'application/json' },
18
- })
19
- }
20
-
21
- /**
22
- * JWT authentication middleware. Reads the Bearer token from the Authorization
23
- * header and delegates verification to the ITokenService injected in context.
24
- * Returns 401 on any missing or invalid token; never exposes failure details.
25
- */
26
- export function authMiddleware() {
27
- return async (c: Context<{ Bindings: Env; Variables: Variables }>, next: Next): Promise<Response | void> => {
28
- const authHeader = c.req.header('Authorization')
29
- if (!authHeader?.startsWith('Bearer ')) {
30
- throw new HTTPException(401, { res: unauthorizedResponse() })
31
- }
32
-
33
- const token = authHeader.slice(7)
34
- if (!token) {
35
- throw new HTTPException(401, { res: unauthorizedResponse() })
36
- }
37
-
38
- const claims = await c.get('tokenService').verify(token)
39
-
40
- if (!claims) {
41
- throw new HTTPException(401, { res: unauthorizedResponse() })
42
- }
43
-
44
- c.set('jwtPayload', claims as JwtPayload)
45
- await next()
46
- }
47
- }
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 type { Context, Next } from 'hono'
7
+ import { HTTPException } from 'hono/http-exception'
8
+ import type { Env, Variables } from './types'
9
+
10
+ const UNAUTHORIZED_JSON = JSON.stringify({ error: 'Unauthorized' })
11
+
12
+ function unauthorizedResponse() {
13
+ return new Response(UNAUTHORIZED_JSON, {
14
+ status: 401,
15
+ headers: { 'Content-Type': 'application/json' },
16
+ })
17
+ }
18
+
19
+ /**
20
+ * JWT authentication middleware. Reads the Bearer token from the Authorization
21
+ * header and delegates verification to the ITokenService injected in context.
22
+ * Returns 401 on any missing or invalid token; never exposes failure details.
23
+ */
24
+ export function authMiddleware() {
25
+ return async (c: Context<{ Bindings: Env; Variables: Variables }>, next: Next): Promise<Response | void> => {
26
+ const authHeader = c.req.header('Authorization')
27
+ if (!authHeader?.startsWith('Bearer ')) {
28
+ throw new HTTPException(401, { res: unauthorizedResponse() })
29
+ }
30
+
31
+ const token = authHeader.slice(7)
32
+ if (!token) {
33
+ throw new HTTPException(401, { res: unauthorizedResponse() })
34
+ }
35
+
36
+ const claims = await c.get('tokenService').verify(token)
37
+
38
+ if (!claims) {
39
+ throw new HTTPException(401, { res: unauthorizedResponse() })
40
+ }
41
+
42
+ c.set('jwtPayload', claims)
43
+ await next()
44
+ }
45
+ }
@@ -1,23 +1,27 @@
1
- import type { Seed } from '@beechcms/core'
2
-
3
- export type PublicOperation = 'read' | 'add' | 'edit'
4
-
5
- function isAllowed(seed: Seed, operation: PublicOperation): boolean {
6
- if (operation === 'read') return seed.allowPublicRead === true
7
- if (operation === 'add') return seed.allowPublicPost === true
8
- return seed.allowPublicEdit === true
9
- }
10
-
11
- export function checkPublicOperation(seed: Seed, operation: PublicOperation) {
12
- if (isAllowed(seed, operation)) {
13
- return { ok: true } as const
14
- }
15
-
16
- return {
17
- ok: false,
18
- error: {
19
- error: 'Forbidden',
20
- message: `Public ${operation.toUpperCase()} is not allowed for content type '${seed.slug}'.`,
21
- },
22
- } as const
23
- }
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 type { Seed } from '@beechcms/core'
6
+
7
+ export type PublicOperation = 'read' | 'add' | 'edit'
8
+
9
+ function isAllowed(seed: Seed, operation: PublicOperation): boolean {
10
+ if (operation === 'read') return seed.allowPublicRead === true
11
+ if (operation === 'add') return seed.allowPublicPost === true
12
+ return seed.allowPublicEdit === true
13
+ }
14
+
15
+ export function checkPublicOperation(seed: Seed, operation: PublicOperation) {
16
+ if (isAllowed(seed, operation)) {
17
+ return { ok: true } as const
18
+ }
19
+
20
+ return {
21
+ ok: false,
22
+ error: {
23
+ error: 'Forbidden',
24
+ message: `Public ${operation.toUpperCase()} is not allowed for content type '${seed.slug}'.`,
25
+ },
26
+ } as const
27
+ }
@@ -1,53 +1,57 @@
1
- import type { Context, Next } from 'hono'
2
- import { PUBLIC_ERRORS } from './public-errors'
3
- import { publicProblem } from './problem-details'
4
-
5
- type PublicBindings = {
6
- PUBLIC_READ_API_KEY?: string
7
- PUBLIC_WRITE_API_KEY?: string
8
- }
9
-
10
- function isReadMethod(method: string): boolean {
11
- return method === 'GET' || method === 'HEAD' || method === 'OPTIONS'
12
- }
13
-
14
- function getConfiguredKey(env: PublicBindings, method: string): string | undefined {
15
- if (isReadMethod(method)) {
16
- return env.PUBLIC_READ_API_KEY
17
- }
18
- return env.PUBLIC_WRITE_API_KEY
19
- }
20
-
21
- /**
22
- * API key auth middleware for Public API routes.
23
- * Uses X-API-Key header only.
24
- */
25
- export function apiKeyMiddleware() {
26
- return async (c: Context, next: Next): Promise<Response | void> => {
27
- const env = c.env as PublicBindings
28
- const configuredKey = getConfiguredKey(env, c.req.method)
29
-
30
- if (!configuredKey) {
31
- return publicProblem(c, {
32
- type: 'public-api-not-configured',
33
- title: PUBLIC_ERRORS.API_KEY_FORBIDDEN.error,
34
- status: 403,
35
- detail: PUBLIC_ERRORS.API_KEY_FORBIDDEN.message,
36
- })
37
- }
38
-
39
- const providedKey = c.req.header('X-API-Key')
40
-
41
- if (!providedKey || providedKey !== configuredKey) {
42
- return publicProblem(c, {
43
- type: 'public-api-key-unauthorized',
44
- title: PUBLIC_ERRORS.API_KEY_UNAUTHORIZED.error,
45
- status: 401,
46
- detail: PUBLIC_ERRORS.API_KEY_UNAUTHORIZED.message,
47
- })
48
- }
49
-
50
- await next()
51
- }
52
- }
53
-
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 type { Context, Next } from 'hono'
6
+ import { PUBLIC_ERRORS } from './public-errors'
7
+ import { publicProblem } from './problem-details'
8
+
9
+ type PublicBindings = {
10
+ PUBLIC_READ_API_KEY?: string
11
+ PUBLIC_WRITE_API_KEY?: string
12
+ }
13
+
14
+ function isReadMethod(method: string): boolean {
15
+ return method === 'GET' || method === 'HEAD' || method === 'OPTIONS'
16
+ }
17
+
18
+ function getConfiguredKey(env: PublicBindings, method: string): string | undefined {
19
+ if (isReadMethod(method)) {
20
+ return env.PUBLIC_READ_API_KEY
21
+ }
22
+ return env.PUBLIC_WRITE_API_KEY
23
+ }
24
+
25
+ /**
26
+ * API key auth middleware for Public API routes.
27
+ * Uses X-API-Key header only.
28
+ */
29
+ export function apiKeyMiddleware() {
30
+ return async (c: Context, next: Next): Promise<Response | void> => {
31
+ const env = c.env as PublicBindings
32
+ const configuredKey = getConfiguredKey(env, c.req.method)
33
+
34
+ if (!configuredKey) {
35
+ return publicProblem(c, {
36
+ type: 'public-api-not-configured',
37
+ title: PUBLIC_ERRORS.API_KEY_FORBIDDEN.error,
38
+ status: 403,
39
+ detail: PUBLIC_ERRORS.API_KEY_FORBIDDEN.message,
40
+ })
41
+ }
42
+
43
+ const providedKey = c.req.header('X-API-Key')
44
+
45
+ if (!providedKey || providedKey !== configuredKey) {
46
+ return publicProblem(c, {
47
+ type: 'public-api-key-unauthorized',
48
+ title: PUBLIC_ERRORS.API_KEY_UNAUTHORIZED.error,
49
+ status: 401,
50
+ detail: PUBLIC_ERRORS.API_KEY_UNAUTHORIZED.message,
51
+ })
52
+ }
53
+
54
+ await next()
55
+ }
56
+ }
57
+
@@ -1,34 +1,38 @@
1
- import type { Context } from 'hono'
2
-
3
- type EdgeCache = {
4
- cache: Cache
5
- executionCtx: { waitUntil: (p: Promise<unknown>) => void }
6
- } | null
7
-
8
- export function resolveEdgeCache(c: Context): EdgeCache {
9
- try {
10
- const cache = caches.default
11
- let executionCtx: any
12
- try {
13
- executionCtx = c.executionCtx
14
- } catch {
15
- return null
16
- }
17
-
18
- if (!executionCtx?.waitUntil) return null
19
- return { cache, executionCtx: executionCtx as { waitUntil: (p: Promise<unknown>) => void } }
20
- } catch {
21
- return null
22
- }
23
- }
24
-
25
- export function withCachedResponse(edgeCache: EdgeCache, cacheKey: Request, response: Response): Response {
26
- if (!edgeCache) return response
27
- const cloned = response.clone()
28
- const headers = new Headers(cloned.headers)
29
- headers.set('Cache-Control', 'public, max-age=60')
30
- edgeCache.executionCtx.waitUntil(
31
- edgeCache.cache.put(cacheKey, new Response(cloned.body, { status: cloned.status, headers }))
32
- )
33
- return response
34
- }
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 type { Context } from 'hono'
6
+
7
+ type EdgeCache = {
8
+ cache: Cache
9
+ executionCtx: { waitUntil: (p: Promise<unknown>) => void }
10
+ } | null
11
+
12
+ export function resolveEdgeCache(c: Context): EdgeCache {
13
+ try {
14
+ const cache = caches.default
15
+ let executionCtx: any
16
+ try {
17
+ executionCtx = c.executionCtx
18
+ } catch {
19
+ return null
20
+ }
21
+
22
+ if (!executionCtx?.waitUntil) return null
23
+ return { cache, executionCtx: executionCtx as { waitUntil: (p: Promise<unknown>) => void } }
24
+ } catch {
25
+ return null
26
+ }
27
+ }
28
+
29
+ export function withCachedResponse(edgeCache: EdgeCache, cacheKey: Request, response: Response): Response {
30
+ if (!edgeCache) return response
31
+ const cloned = response.clone()
32
+ const headers = new Headers(cloned.headers)
33
+ headers.set('Cache-Control', 'public, max-age=60')
34
+ edgeCache.executionCtx.waitUntil(
35
+ edgeCache.cache.put(cacheKey, new Response(cloned.body, { status: cloned.status, headers }))
36
+ )
37
+ return response
38
+ }
@@ -1,42 +1,46 @@
1
- import { resolvePolicies } from '@beechcms/core'
2
- import type { Seed } from '@beechcms/core'
3
-
4
- const SYSTEM_FIELDS = ['id', 'slug', 'status', 'created_at', 'updated_at']
5
- const IDENTITY_FIELDS = ['id', 'slug']
6
-
7
- function applyPublicPolicies(data: Record<string, unknown>, seed: Seed): Record<string, unknown> {
8
- const result: Record<string, unknown> = {}
9
-
10
- for (const key of SYSTEM_FIELDS) {
11
- if (key in data) result[key] = data[key]
12
- }
13
-
14
- for (const branch of seed.branches) {
15
- const value = data[branch.alias]
16
- const { public: isPublic, visibility } = resolvePolicies(branch)
17
- if (!isPublic) continue
18
- if (visibility === 'hidden') continue
19
- result[branch.alias] = visibility === 'masked' && typeof value === 'string' && value.length > 0
20
- ? '••••••••'
21
- : value
22
- }
23
- return result
24
- }
25
-
26
- export function toFlatPublicEntry(data: Record<string, unknown>, seed: Seed, fieldsParam?: string): Record<string, unknown> {
27
- const projected = applyPublicPolicies(data, seed)
28
- const requestedFields = (fieldsParam ?? '').split(',').map(f => f.trim()).filter(Boolean)
29
-
30
- if (requestedFields.length === 0) return projected
31
-
32
- const filtered: Record<string, unknown> = {}
33
- for (const field of requestedFields) {
34
- if (field in projected) filtered[field] = projected[field]
35
- }
36
-
37
- for (const key of IDENTITY_FIELDS) {
38
- if (key in projected && !filtered[key]) filtered[key] = projected[key]
39
- }
40
-
41
- return filtered
42
- }
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 { resolvePolicies } from '@beechcms/core'
6
+ import type { Seed } from '@beechcms/core'
7
+
8
+ const SYSTEM_FIELDS = ['id', 'slug', 'status', 'created_at', 'updated_at']
9
+ const IDENTITY_FIELDS = ['id', 'slug']
10
+
11
+ function applyPublicPolicies(data: Record<string, unknown>, seed: Seed): Record<string, unknown> {
12
+ const result: Record<string, unknown> = {}
13
+
14
+ for (const key of SYSTEM_FIELDS) {
15
+ if (key in data) result[key] = data[key]
16
+ }
17
+
18
+ for (const branch of seed.branches) {
19
+ const value = data[branch.alias]
20
+ const { public: isPublic, visibility } = resolvePolicies(branch)
21
+ if (!isPublic) continue
22
+ if (visibility === 'hidden') continue
23
+ result[branch.alias] = visibility === 'masked' && typeof value === 'string' && value.length > 0
24
+ ? '••••••••'
25
+ : value
26
+ }
27
+ return result
28
+ }
29
+
30
+ export function toFlatPublicEntry(data: Record<string, unknown>, seed: Seed, fieldsParam?: string): Record<string, unknown> {
31
+ const projected = applyPublicPolicies(data, seed)
32
+ const requestedFields = (fieldsParam ?? '').split(',').map(f => f.trim()).filter(Boolean)
33
+
34
+ if (requestedFields.length === 0) return projected
35
+
36
+ const filtered: Record<string, unknown> = {}
37
+ for (const field of requestedFields) {
38
+ if (field in projected) filtered[field] = projected[field]
39
+ }
40
+
41
+ for (const key of IDENTITY_FIELDS) {
42
+ if (key in projected && !filtered[key]) filtered[key] = projected[key]
43
+ }
44
+
45
+ return filtered
46
+ }
@@ -1,19 +1,23 @@
1
- import { sha256hex } from '@beechcms/core'
2
-
3
- export function parseIdempotencyKey(rawValue: string | undefined): string | null {
4
- if (!rawValue) return null
5
- const key = rawValue.trim()
6
- if (!key || key.length > 128) return null
7
- return key
8
- }
9
-
10
- type FingerprintInput = {
11
- seedSlug: string
12
- statusValue: unknown
13
- slug: string | null
14
- data: Record<string, unknown>
15
- }
16
-
17
- export function buildRequestFingerprint(input: FingerprintInput): Promise<string> {
18
- return sha256hex(JSON.stringify({ seedSlug: input.seedSlug, statusValue: input.statusValue, slug: input.slug, data: input.data }))
19
- }
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 { sha256hex } from '@beechcms/core'
6
+
7
+ export function parseIdempotencyKey(rawValue: string | undefined): string | null {
8
+ if (!rawValue) return null
9
+ const key = rawValue.trim()
10
+ if (!key || key.length > 128) return null
11
+ return key
12
+ }
13
+
14
+ type FingerprintInput = {
15
+ seedSlug: string
16
+ statusValue: unknown
17
+ slug: string | null
18
+ data: Record<string, unknown>
19
+ }
20
+
21
+ export function buildRequestFingerprint(input: FingerprintInput): Promise<string> {
22
+ return sha256hex(JSON.stringify({ seedSlug: input.seedSlug, statusValue: input.statusValue, slug: input.slug, data: input.data }))
23
+ }
@@ -1,12 +1,16 @@
1
- export { apiKeyMiddleware } from './api-key-middleware'
2
- export { publicRateLimitMiddleware } from './rate-limit-middleware'
3
- export { publicRoutes } from './public-routes'
4
- export { PUBLIC_ERRORS } from './public-errors'
5
- export { sanitizePublicPayload } from './sanitize'
6
- export { generateEntrySlug, slugify } from './slug-utils'
7
- export { buildPublicListMeta, buildPublicSingleMeta } from './response-builder'
8
- export { parseLatestCount, parsePublicPagination } from './query-builder'
9
- export { publicReadHandler } from './public-read'
10
- export { publicAddHandler } from './public-add'
11
- export { publicEditHandler } from './public-edit'
12
-
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
+ export { apiKeyMiddleware } from './api-key-middleware'
6
+ export { publicRateLimitMiddleware } from './rate-limit-middleware'
7
+ export { publicRoutes } from './public-routes'
8
+ export { PUBLIC_ERRORS } from './public-errors'
9
+ export { sanitizePublicPayload } from './sanitize'
10
+ export { generateEntrySlug, slugify } from './slug-utils'
11
+ export { buildPublicListMeta, buildPublicSingleMeta } from './response-builder'
12
+ export { parseLatestCount, parsePublicPagination } from './query-builder'
13
+ export { publicReadHandler } from './public-read'
14
+ export { publicAddHandler } from './public-add'
15
+ export { publicEditHandler } from './public-edit'
16
+