@beechcms/api 0.6.0-preview.1 → 0.6.0-preview.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.
- package/README.md +4 -4
- package/assets/dashboard/BeechLogo.svg +18 -18
- package/assets/dashboard/BeechLogoLIght.svg +48 -48
- package/assets/dashboard/assets/chart-Cws4Yj6K.js +39 -0
- package/assets/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/assets/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/assets/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/assets/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/assets/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/assets/dashboard/assets/index-BjPpppHe.css +1 -0
- package/assets/dashboard/assets/index-CrW-zhkA.js +642 -0
- package/assets/dashboard/assets/lora-cyrillic-ext-wght-normal-YcVxMP-u.woff2 +0 -0
- package/assets/dashboard/assets/lora-cyrillic-wght-normal-UWPuZLjM.woff2 +0 -0
- package/assets/dashboard/assets/lora-latin-ext-wght-normal-C2Wlntb9.woff2 +0 -0
- package/assets/dashboard/assets/lora-latin-wght-normal-BiLcIKcI.woff2 +0 -0
- package/assets/dashboard/assets/lora-math-wght-normal-sOy7rEfW.woff2 +0 -0
- package/assets/dashboard/assets/lora-symbols-wght-normal-DQ5VrUkH.woff2 +0 -0
- package/assets/dashboard/assets/lora-vietnamese-wght-normal-CfJ7gtf3.woff2 +0 -0
- package/assets/dashboard/assets/pie-chart-recharts-CAxoDQp2.js +1 -0
- package/assets/dashboard/assets/timeseries-chart-recharts-CixDW-LM.js +1 -0
- package/assets/dashboard/beechLogoDark.svg +48 -48
- package/assets/dashboard/index.html +18 -18
- package/assets/dashboard/noResult.svg +42 -42
- package/assets/dashboard/sol.svg +3 -3
- package/assets/dashboard/undraw_enter_nwx3.svg +36 -36
- package/dist/auth/__fixtures__/in-memory-hash-provider.d.ts +5 -0
- package/dist/auth/__fixtures__/static-token-service.d.ts +6 -0
- package/dist/auth/bcrypt-hash-provider.d.ts +8 -0
- package/dist/auth/constants.d.ts +10 -0
- package/dist/auth/jose-token-service.d.ts +14 -0
- package/dist/auth/login.d.ts +32 -0
- package/dist/auth/refresh.d.ts +1 -0
- package/dist/factory.d.ts +39 -0
- package/dist/features/automations/action-executors/create-entry.executor.d.ts +7 -0
- package/dist/features/automations/action-executors/edit-field.executor.d.ts +7 -0
- package/dist/features/automations/action-executors/index.d.ts +13 -0
- package/dist/features/automations/action-executors/send-mail.executor.d.ts +7 -0
- package/dist/features/automations/action-executors/set-variable.executor.d.ts +15 -0
- package/dist/features/automations/action-executors/webhook.executor.d.ts +7 -0
- package/dist/features/automations/automation-runner.d.ts +13 -0
- package/dist/features/automations/automation-runner.utils.d.ts +3 -0
- package/dist/features/automations/automations.handler.d.ts +7 -0
- package/dist/features/automations/automations.schema.d.ts +191 -0
- package/dist/features/automations/context-resolver.d.ts +8 -0
- package/dist/features/automations/cron-runner.d.ts +9 -0
- package/dist/features/automations/cron-runner.utils.d.ts +1 -0
- package/dist/features/automations/filter-translation.d.ts +9 -0
- package/dist/features/automations/index.d.ts +7 -0
- package/dist/features/automations/template-grammar.d.ts +73 -0
- package/dist/features/automations/var-access-resolver.d.ts +6 -0
- package/dist/features/automations/when-evaluator.d.ts +3 -0
- package/dist/features/automations/when-pushdown.d.ts +2 -0
- package/dist/features/backrefs/backrefs.handler.d.ts +3 -0
- package/dist/features/backrefs/d1-backref.repository.d.ts +17 -0
- package/dist/features/backrefs/index.d.ts +1 -0
- package/dist/features/content/constants.d.ts +12 -0
- package/dist/features/content/handlers/bulk.handler.d.ts +3 -0
- package/dist/features/content/handlers/create.d.ts +3 -0
- package/dist/features/content/handlers/delete.d.ts +3 -0
- package/dist/features/content/handlers/facets.d.ts +3 -0
- package/dist/features/content/handlers/get.d.ts +4 -0
- package/dist/features/content/handlers/helpers.d.ts +12 -0
- package/dist/features/content/handlers/list.d.ts +3 -0
- package/dist/features/content/handlers/update.d.ts +3 -0
- package/dist/features/content/index.d.ts +4 -0
- package/dist/features/dashboard-layout/dashboard-layout.handler.d.ts +7 -0
- package/dist/features/dashboard-layout/index.d.ts +1 -0
- package/dist/features/draft/draft.handler.d.ts +4 -0
- package/dist/features/draft/draft.middleware.d.ts +9 -0
- package/dist/features/draft/index.d.ts +1 -0
- package/{src/features/email/email.provider.ts → dist/features/email/email.provider.d.ts} +37 -42
- package/dist/features/email/email.service.d.ts +13 -0
- package/dist/features/email/email.types.d.ts +94 -0
- package/{src/features/email/index.ts → dist/features/email/index.d.ts} +20 -33
- package/dist/features/email/providers/resend.d.ts +36 -0
- package/dist/features/email/providers/smtp.d.ts +11 -0
- package/dist/features/email/templates/automation-mail.d.ts +8 -0
- package/dist/features/email/templates/password-changed.d.ts +15 -0
- package/dist/features/email/templates/password-reset.d.ts +18 -0
- package/dist/features/email/templates/shell.d.ts +58 -0
- package/dist/features/notifications/index.d.ts +1 -0
- package/dist/features/notifications/notifications.handler.d.ts +15 -0
- package/dist/features/password-reset/index.d.ts +6 -0
- package/dist/features/password-reset/request.d.ts +10 -0
- package/dist/features/password-reset/reset.d.ts +9 -0
- package/dist/features/rotate-field/index.d.ts +1 -0
- package/dist/features/rotate-field/rotate-field.handler.d.ts +7 -0
- package/dist/features/rotate-field/rotate-field.schema.d.ts +11 -0
- package/dist/features/schema/schema.handler.d.ts +7 -0
- package/dist/features/seeds/index.d.ts +1 -0
- package/dist/features/seeds/seeds.handler.d.ts +6 -0
- package/dist/features/settings/settings.handler.d.ts +7 -0
- package/dist/features/setup/index.d.ts +7 -0
- package/dist/features/stats/index.d.ts +1 -0
- package/dist/features/stats/stats.handler.d.ts +7 -0
- package/dist/features/webhooks/index.d.ts +3 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +9666 -0
- package/dist/media-utils.d.ts +25 -0
- package/dist/middleware/auth-providers.middleware.d.ts +8 -0
- package/dist/middleware/observability.middleware.d.ts +20 -0
- package/dist/middleware/rate-limit.middleware.d.ts +9 -0
- package/dist/middleware/repository.middleware.d.ts +34 -0
- package/dist/middleware/seed-registry.middleware.d.ts +10 -0
- package/dist/middleware/storage.middleware.d.ts +7 -0
- package/dist/middleware.d.ts +11 -0
- package/dist/public/access-policy.d.ts +12 -0
- package/dist/public/api-key-middleware.d.ts +6 -0
- package/dist/public/cache-utils.d.ts +10 -0
- package/dist/public/entry-projection.d.ts +2 -0
- package/dist/public/idempotency.d.ts +9 -0
- package/dist/public/index.d.ts +11 -0
- package/dist/public/problem-details.d.ts +31 -0
- package/dist/public/public-add.d.ts +3 -0
- package/dist/public/public-edit.d.ts +5 -0
- package/dist/public/public-errors.d.ts +13 -0
- package/dist/public/public-read.d.ts +3 -0
- package/dist/public/public-routes.d.ts +3 -0
- package/dist/public/query-builder.d.ts +28 -0
- package/dist/public/rate-limit-middleware.d.ts +3 -0
- package/dist/public/read-list.d.ts +26 -0
- package/dist/public/read-single.d.ts +22 -0
- package/dist/public/response-builder.d.ts +22 -0
- package/dist/public/sanitize.d.ts +23 -0
- package/dist/public/slug-utils.d.ts +11 -0
- package/dist/rate-limit/cloudflare-rate-limiter.d.ts +6 -0
- package/dist/rate-limit/in-memory-rate-limiter.d.ts +7 -0
- package/dist/rate-limit/no-op-rate-limiter.d.ts +4 -0
- package/dist/search-utils.d.ts +54 -0
- package/dist/search.d.ts +6 -0
- package/dist/shared/apply-policies.d.ts +10 -0
- package/dist/shared/automations.repository.d1.d.ts +12 -0
- package/dist/shared/background-notification-service.d.ts +18 -0
- package/dist/shared/base.repository.d1.d.ts +18 -0
- package/dist/shared/content-utils.d.ts +21 -0
- package/dist/shared/content.repository.d1.d.ts +68 -0
- package/dist/shared/d1-activity-log.repository.d.ts +13 -0
- package/dist/shared/d1-activity-logger.d.ts +20 -0
- package/dist/shared/d1-analytics.repository.d.ts +20 -0
- package/dist/shared/d1-content-scan.repository.d.ts +7 -0
- package/dist/shared/d1-notification.repository.d.ts +17 -0
- package/dist/shared/d1-password-reset-token.repository.d.ts +10 -0
- package/dist/shared/d1-search.repository.d.ts +14 -0
- package/dist/shared/d1-session.repository.d.ts +12 -0
- package/dist/shared/d1-setup-checklist.repository.d.ts +12 -0
- package/dist/shared/d1-user.repository.d.ts +18 -0
- package/dist/shared/d1-widget.repository.d.ts +31 -0
- package/dist/shared/dashboard-layout.repository.d1.d.ts +9 -0
- package/dist/shared/demo-data-sql.d.ts +1 -0
- package/dist/shared/demo-data.repository.d1.d.ts +6 -0
- package/dist/shared/execution-context-scheduler.d.ts +6 -0
- package/dist/shared/fixed-clock.d.ts +13 -0
- package/dist/shared/fts-sync.d.ts +1 -0
- package/dist/shared/idempotency.repository.d1.d.ts +16 -0
- package/dist/shared/in-memory-activity-logger.d.ts +11 -0
- package/dist/shared/in-memory-notification-service.d.ts +11 -0
- package/dist/shared/in-memory-seed.repository.d.ts +18 -0
- package/dist/shared/media.repository.d1.d.ts +35 -0
- package/dist/shared/qstash-notification-service.d.ts +18 -0
- package/dist/shared/query-utils.d.ts +36 -0
- package/dist/shared/request-utils.d.ts +13 -0
- package/dist/shared/schema-mutator.d1.d.ts +12 -0
- package/dist/shared/seed-layout.repository.d1.d.ts +9 -0
- package/dist/shared/seed-registry-cache.d.ts +12 -0
- package/dist/shared/seed.repository.d1.d.ts +13 -0
- package/dist/shared/sequential-id-generator.d.ts +13 -0
- package/dist/shared/site-settings.repository.d1.d.ts +7 -0
- package/dist/shared/storage/factory.d.ts +8 -0
- package/dist/shared/storage/s3-bucket.d.ts +43 -0
- package/dist/shared/storage-utils.d.ts +7 -0
- package/dist/shared/system-stats.repository.d1.d.ts +25 -0
- package/dist/types.d.ts +85 -0
- package/dist/upload.d.ts +16 -0
- package/dist/widget.d.ts +7 -0
- package/migrations/0000_v040_base.sql +265 -265
- package/migrations/0029_automations.sql +14 -14
- package/migrations/0030_test_seeds.sql +125 -125
- package/package.json +27 -18
- package/assets/dashboard/assets/index-BSjk0Sx9.js +0 -634
- package/assets/dashboard/assets/index-kRCYVN2B.css +0 -1
- package/src/auth/__fixtures__/in-memory-hash-provider.ts +0 -17
- package/src/auth/__fixtures__/static-token-service.ts +0 -22
- package/src/auth/bcrypt-hash-provider.ts +0 -24
- package/src/auth/constants.ts +0 -14
- package/src/auth/generate-refresh-token.test.ts +0 -23
- package/src/auth/hash-provider.test.ts +0 -55
- package/src/auth/jose-token-service.ts +0 -62
- package/src/auth/jwt-claims-passthrough.test.ts +0 -87
- package/src/auth/login.test.ts +0 -100
- package/src/auth/login.ts +0 -78
- package/src/auth/refresh.ts +0 -11
- package/src/auth/token-service.test.ts +0 -90
- package/src/factory.ts +0 -368
- package/src/features/automations/__tests__/action-executors.test.ts +0 -255
- package/src/features/automations/__tests__/automation-runner.test.ts +0 -196
- package/src/features/automations/__tests__/automation-runner.utils.test.ts +0 -60
- package/src/features/automations/__tests__/automations.handler.test.ts +0 -264
- package/src/features/automations/__tests__/automations.repository.test.ts +0 -163
- package/src/features/automations/__tests__/automations.schema.test.ts +0 -224
- package/src/features/automations/__tests__/context-resolver.test.ts +0 -126
- package/src/features/automations/__tests__/cron-runner.test.ts +0 -267
- package/src/features/automations/__tests__/cron-runner.utils.test.ts +0 -144
- package/src/features/automations/__tests__/set-variable.executor.test.ts +0 -383
- package/src/features/automations/__tests__/template-grammar.test.ts +0 -308
- package/src/features/automations/__tests__/webhook.executor.test.ts +0 -142
- package/src/features/automations/__tests__/when-evaluator.test.ts +0 -274
- package/src/features/automations/__tests__/when-pushdown.test.ts +0 -281
- package/src/features/automations/action-executors/create-entry.executor.ts +0 -27
- package/src/features/automations/action-executors/edit-field.executor.ts +0 -26
- package/src/features/automations/action-executors/index.ts +0 -37
- package/src/features/automations/action-executors/send-mail.executor.ts +0 -53
- package/src/features/automations/action-executors/set-variable.executor.ts +0 -159
- package/src/features/automations/action-executors/webhook.executor.ts +0 -67
- package/src/features/automations/automation-runner.ts +0 -85
- package/src/features/automations/automation-runner.utils.ts +0 -47
- package/src/features/automations/automations.handler.ts +0 -197
- package/src/features/automations/automations.schema.ts +0 -177
- package/src/features/automations/context-resolver.ts +0 -152
- package/src/features/automations/cron-runner.ts +0 -140
- package/src/features/automations/cron-runner.utils.ts +0 -44
- package/src/features/automations/filter-translation.ts +0 -46
- package/src/features/automations/index.ts +0 -16
- package/src/features/automations/template-grammar.ts +0 -245
- package/src/features/automations/var-access-resolver.ts +0 -140
- package/src/features/automations/when-evaluator.ts +0 -87
- package/src/features/automations/when-pushdown.ts +0 -57
- package/src/features/backrefs/__tests__/backrefs.handler.test.ts +0 -359
- package/src/features/backrefs/backrefs.handler.ts +0 -168
- package/src/features/backrefs/d1-backref.repository.ts +0 -84
- package/src/features/backrefs/index.ts +0 -5
- package/src/features/content/constants.ts +0 -16
- package/src/features/content/handlers/bulk.handler.ts +0 -185
- package/src/features/content/handlers/create.ts +0 -129
- package/src/features/content/handlers/delete.ts +0 -62
- package/src/features/content/handlers/facets.ts +0 -49
- package/src/features/content/handlers/get.ts +0 -120
- package/src/features/content/handlers/helpers.test.ts +0 -180
- package/src/features/content/handlers/helpers.ts +0 -93
- package/src/features/content/handlers/list.ts +0 -174
- package/src/features/content/handlers/update.ts +0 -174
- package/src/features/content/index.ts +0 -26
- package/src/features/draft/draft.handler.ts +0 -195
- package/src/features/draft/draft.middleware.ts +0 -66
- package/src/features/draft/index.ts +0 -5
- package/src/features/email/email.service.ts +0 -98
- package/src/features/email/email.types.ts +0 -119
- package/src/features/email/providers/resend.ts +0 -67
- package/src/features/email/providers/smtp.ts +0 -55
- package/src/features/email/templates/automation-mail.ts +0 -19
- package/src/features/email/templates/password-changed.ts +0 -63
- package/src/features/email/templates/password-reset.ts +0 -68
- package/src/features/email/templates/shell.ts +0 -96
- package/src/features/notifications/index.ts +0 -5
- package/src/features/notifications/notifications.handler.ts +0 -105
- package/src/features/password-reset/index.ts +0 -19
- package/src/features/password-reset/request.ts +0 -92
- package/src/features/password-reset/reset.ts +0 -102
- package/src/features/rotate-field/index.ts +0 -5
- package/src/features/rotate-field/rotate-field.handler.ts +0 -132
- package/src/features/rotate-field/rotate-field.schema.ts +0 -17
- package/src/features/schema/schema.handler.ts +0 -131
- package/src/features/seeds/index.ts +0 -5
- package/src/features/seeds/seeds.handler.test.ts +0 -632
- package/src/features/seeds/seeds.handler.ts +0 -693
- package/src/features/settings/__tests__/settings.handler.test.ts +0 -555
- package/src/features/settings/settings.handler.ts +0 -392
- package/src/features/setup/index.ts +0 -204
- package/src/features/stats/index.ts +0 -5
- package/src/features/stats/stats.handler.ts +0 -420
- package/src/features/webhooks/index.ts +0 -63
- package/src/index.ts +0 -65
- package/src/media-utils.ts +0 -82
- package/src/middleware/auth-providers.middleware.ts +0 -45
- package/src/middleware/observability.middleware.ts +0 -70
- package/src/middleware/rate-limit.middleware.ts +0 -45
- package/src/middleware/repository.middleware.ts +0 -114
- package/src/middleware/seed-registry.middleware.test.ts +0 -97
- package/src/middleware/seed-registry.middleware.ts +0 -21
- package/src/middleware/storage.middleware.ts +0 -25
- package/src/middleware.ts +0 -45
- package/src/public/access-policy.ts +0 -27
- package/src/public/api-key-middleware.ts +0 -57
- package/src/public/cache-utils.ts +0 -38
- package/src/public/entry-projection.ts +0 -46
- package/src/public/idempotency.ts +0 -23
- package/src/public/index.ts +0 -16
- package/src/public/problem-details.ts +0 -124
- package/src/public/public-add.ts +0 -114
- package/src/public/public-edit.ts +0 -163
- package/src/public/public-errors.ts +0 -19
- package/src/public/public-read.ts +0 -63
- package/src/public/public-routes.ts +0 -88
- package/src/public/query-builder.test.ts +0 -224
- package/src/public/query-builder.ts +0 -156
- package/src/public/rate-limit-middleware.ts +0 -34
- package/src/public/read-list.ts +0 -54
- package/src/public/read-single.ts +0 -48
- package/src/public/response-builder.ts +0 -30
- package/src/public/sanitize.ts +0 -69
- package/src/public/slug-utils.ts +0 -18
- package/src/rate-limit/cloudflare-rate-limiter.test.ts +0 -30
- package/src/rate-limit/cloudflare-rate-limiter.ts +0 -15
- package/src/rate-limit/in-memory-rate-limiter.test.ts +0 -37
- package/src/rate-limit/in-memory-rate-limiter.ts +0 -17
- package/src/rate-limit/no-op-rate-limiter.test.ts +0 -22
- package/src/rate-limit/no-op-rate-limiter.ts +0 -11
- package/src/search-utils.test.ts +0 -211
- package/src/search-utils.ts +0 -213
- package/src/search.ts +0 -65
- package/src/shared/apply-policies.test.ts +0 -81
- package/src/shared/apply-policies.ts +0 -67
- package/src/shared/automations.repository.d1.ts +0 -150
- package/src/shared/background-notification-service.test.ts +0 -62
- package/src/shared/background-notification-service.ts +0 -52
- package/src/shared/base.repository.d1.ts +0 -32
- package/src/shared/content-utils.test.ts +0 -165
- package/src/shared/content-utils.ts +0 -86
- package/src/shared/content.repository.d1.test.ts +0 -458
- package/src/shared/content.repository.d1.ts +0 -851
- package/src/shared/d1-activity-log.repository.test.ts +0 -140
- package/src/shared/d1-activity-log.repository.ts +0 -105
- package/src/shared/d1-activity-logger.test.ts +0 -86
- package/src/shared/d1-activity-logger.ts +0 -67
- package/src/shared/d1-analytics.repository.test.ts +0 -78
- package/src/shared/d1-analytics.repository.ts +0 -85
- package/src/shared/d1-content-scan.repository.test.ts +0 -80
- package/src/shared/d1-content-scan.repository.ts +0 -37
- package/src/shared/d1-notification.repository.test.ts +0 -128
- package/src/shared/d1-notification.repository.ts +0 -118
- package/src/shared/d1-password-reset-token.repository.test.ts +0 -81
- package/src/shared/d1-password-reset-token.repository.ts +0 -56
- package/src/shared/d1-search.repository.test.ts +0 -87
- package/src/shared/d1-search.repository.ts +0 -88
- package/src/shared/d1-session.repository.test.ts +0 -125
- package/src/shared/d1-session.repository.ts +0 -102
- package/src/shared/d1-setup-checklist.repository.ts +0 -36
- package/src/shared/d1-user.repository.test.ts +0 -151
- package/src/shared/d1-user.repository.ts +0 -119
- package/src/shared/d1-widget.repository.test.ts +0 -221
- package/src/shared/d1-widget.repository.ts +0 -341
- package/src/shared/demo-data-sql.ts +0 -54
- package/src/shared/demo-data.repository.d1.ts +0 -20
- package/src/shared/execution-context-scheduler.ts +0 -13
- package/src/shared/fixed-clock.ts +0 -25
- package/src/shared/fts-sync.ts +0 -8
- package/src/shared/idempotency.repository.d1.test.ts +0 -83
- package/src/shared/idempotency.repository.d1.ts +0 -60
- package/src/shared/in-memory-activity-logger.ts +0 -19
- package/src/shared/in-memory-notification-service.ts +0 -19
- package/src/shared/in-memory-seed.repository.ts +0 -51
- package/src/shared/media.repository.d1.test.ts +0 -107
- package/src/shared/media.repository.d1.ts +0 -68
- package/src/shared/qstash-notification-service.test.ts +0 -67
- package/src/shared/qstash-notification-service.ts +0 -55
- package/src/shared/query-utils.ts +0 -141
- package/src/shared/request-utils.ts +0 -26
- package/src/shared/schema-mutator.d1.ts +0 -64
- package/src/shared/seed-layout.repository.d1.test.ts +0 -114
- package/src/shared/seed-layout.repository.d1.ts +0 -62
- package/src/shared/seed-registry-cache.test.ts +0 -68
- package/src/shared/seed-registry-cache.ts +0 -49
- package/src/shared/seed.repository.d1.test.ts +0 -143
- package/src/shared/seed.repository.d1.ts +0 -98
- package/src/shared/sequential-id-generator.ts +0 -33
- package/src/shared/site-settings.repository.d1.ts +0 -53
- package/src/shared/storage/factory.ts +0 -41
- package/src/shared/storage/s3-bucket.ts +0 -195
- package/src/shared/storage-utils.ts +0 -40
- package/src/shared/system-stats.repository.d1.test.ts +0 -58
- package/src/shared/system-stats.repository.d1.ts +0 -48
- package/src/types.ts +0 -87
- package/src/upload.ts +0 -198
- package/src/widget.ts +0 -212
|
@@ -1,341 +0,0 @@
|
|
|
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 {
|
|
7
|
-
Seed,
|
|
8
|
-
IWidgetRepository,
|
|
9
|
-
AggregateFormula,
|
|
10
|
-
TimeWindow,
|
|
11
|
-
LeaderboardEntry,
|
|
12
|
-
LeaderboardOptions,
|
|
13
|
-
TimeseriesPoint,
|
|
14
|
-
WidgetListOptions,
|
|
15
|
-
WidgetListResult,
|
|
16
|
-
GrowthResult,
|
|
17
|
-
} from '@beechcms/core'
|
|
18
|
-
|
|
19
|
-
const SYSTEM_COLUMNS: ReadonlySet<string> = new Set([
|
|
20
|
-
'id',
|
|
21
|
-
'slug',
|
|
22
|
-
'status',
|
|
23
|
-
'created_at',
|
|
24
|
-
'updated_at',
|
|
25
|
-
])
|
|
26
|
-
|
|
27
|
-
const ALLOWED_FILTER_OPERATORS: ReadonlySet<string> = new Set([
|
|
28
|
-
'eq',
|
|
29
|
-
'=',
|
|
30
|
-
'neq',
|
|
31
|
-
'!=',
|
|
32
|
-
'like',
|
|
33
|
-
'gt',
|
|
34
|
-
'>',
|
|
35
|
-
'lt',
|
|
36
|
-
'<',
|
|
37
|
-
])
|
|
38
|
-
|
|
39
|
-
const UNSAFE_COLUMN_ERROR = 'UNSAFE_COLUMN'
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* D1-backed implementation of {@link IWidgetRepository}.
|
|
43
|
-
*
|
|
44
|
-
* All user-supplied values are bound via parameterised statements. Column
|
|
45
|
-
* aliases originating from query strings are validated against the seed
|
|
46
|
-
* before being composed into the SQL string, preventing column-name
|
|
47
|
-
* injection. SQL keywords (aggregate function names, ORDER direction) are
|
|
48
|
-
* selected via hardcoded branches.
|
|
49
|
-
*/
|
|
50
|
-
export class D1WidgetRepository implements IWidgetRepository {
|
|
51
|
-
constructor(private readonly database: D1Database) {}
|
|
52
|
-
|
|
53
|
-
async aggregate(seed: Seed, formula: AggregateFormula, window: TimeWindow): Promise<number> {
|
|
54
|
-
const aggregateExpression = this.buildAggregateExpression(seed, formula)
|
|
55
|
-
const timeWindowFilter = this.buildTimeWindowFilter(window)
|
|
56
|
-
const tableName = `content_${seed.slug}`
|
|
57
|
-
|
|
58
|
-
const sql =
|
|
59
|
-
`SELECT ${aggregateExpression} as computed_value
|
|
60
|
-
FROM ${tableName}
|
|
61
|
-
WHERE ${timeWindowFilter}`
|
|
62
|
-
|
|
63
|
-
const row = await this.database.prepare(sql).first<{ computed_value: number | null }>()
|
|
64
|
-
return row?.computed_value ?? 0
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async growth(
|
|
68
|
-
seed: Seed,
|
|
69
|
-
formula: AggregateFormula,
|
|
70
|
-
window: TimeWindow,
|
|
71
|
-
): Promise<GrowthResult> {
|
|
72
|
-
const aggregateExpression = this.buildAggregateExpression(seed, formula)
|
|
73
|
-
const { currentFilter, previousFilter } = this.buildPreviousWindowFilter(window)
|
|
74
|
-
const tableName = `content_${seed.slug}`
|
|
75
|
-
|
|
76
|
-
const [currentRow, previousRow] = await Promise.all([
|
|
77
|
-
this.database
|
|
78
|
-
.prepare(`SELECT ${aggregateExpression} as computed_value FROM ${tableName} WHERE ${currentFilter}`)
|
|
79
|
-
.first<{ computed_value: number | null }>(),
|
|
80
|
-
this.database
|
|
81
|
-
.prepare(`SELECT ${aggregateExpression} as computed_value FROM ${tableName} WHERE ${previousFilter}`)
|
|
82
|
-
.first<{ computed_value: number | null }>(),
|
|
83
|
-
])
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
currentValue: currentRow?.computed_value ?? 0,
|
|
87
|
-
previousValue: previousRow?.computed_value ?? 0,
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async leaderboard(seed: Seed, options: LeaderboardOptions): Promise<LeaderboardEntry[]> {
|
|
92
|
-
const scoreColumn = this.resolveColumnExpression(seed, options.scoreColumn)
|
|
93
|
-
const labelColumn = this.resolveColumnExpression(seed, seed.displayNameAlias)
|
|
94
|
-
const tableName = `content_${seed.slug}`
|
|
95
|
-
const orderClause = options.orderDirection === 'ASC'
|
|
96
|
-
? `ORDER BY CAST(${scoreColumn} AS REAL) ASC`
|
|
97
|
-
: `ORDER BY CAST(${scoreColumn} AS REAL) DESC`
|
|
98
|
-
|
|
99
|
-
const sql =
|
|
100
|
-
`SELECT id, ${labelColumn} as label, ${scoreColumn} as score
|
|
101
|
-
FROM ${tableName}
|
|
102
|
-
WHERE ${scoreColumn} IS NOT NULL
|
|
103
|
-
${orderClause}
|
|
104
|
-
LIMIT ?`
|
|
105
|
-
|
|
106
|
-
const rows = await this.database
|
|
107
|
-
.prepare(sql)
|
|
108
|
-
.bind(options.limit)
|
|
109
|
-
.all<{ id: string; label: string | null; score: number | string | null }>()
|
|
110
|
-
|
|
111
|
-
return (rows.results ?? []).map(row => ({
|
|
112
|
-
id: row.id,
|
|
113
|
-
label: row.label ?? row.id,
|
|
114
|
-
score: row.score ?? 0,
|
|
115
|
-
}))
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async list(seed: Seed, options: WidgetListOptions): Promise<WidgetListResult> {
|
|
119
|
-
const tableName = `content_${seed.slug}`
|
|
120
|
-
const conditions: string[] = []
|
|
121
|
-
const bindings: unknown[] = []
|
|
122
|
-
|
|
123
|
-
const trimmedSearch = options.search?.trim() ?? ''
|
|
124
|
-
if (trimmedSearch.length > 0) {
|
|
125
|
-
const displayColumn = this.resolveColumnExpression(seed, seed.displayNameAlias)
|
|
126
|
-
conditions.push(`${displayColumn} LIKE ?`)
|
|
127
|
-
bindings.push(`%${trimmedSearch}%`)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
for (const filter of options.filters ?? []) {
|
|
131
|
-
this.appendFilterCondition(seed, filter, conditions, bindings)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const whereSql = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
|
135
|
-
const orderClause = this.buildListOrderClause(seed, options)
|
|
136
|
-
const branchColumns = seed.branches.map(branch => branch.alias).join(', ')
|
|
137
|
-
const selectColumns = branchColumns.length > 0
|
|
138
|
-
? `id, slug, status, created_at, updated_at, ${branchColumns}`
|
|
139
|
-
: `id, slug, status, created_at, updated_at`
|
|
140
|
-
|
|
141
|
-
const dataSql =
|
|
142
|
-
`SELECT ${selectColumns}
|
|
143
|
-
FROM ${tableName}
|
|
144
|
-
${whereSql}
|
|
145
|
-
${orderClause}
|
|
146
|
-
LIMIT ? OFFSET ?`
|
|
147
|
-
|
|
148
|
-
const countSql = `SELECT COUNT(*) as total FROM ${tableName} ${whereSql}`
|
|
149
|
-
|
|
150
|
-
const [countRow, listRows] = await Promise.all([
|
|
151
|
-
this.database.prepare(countSql).bind(...bindings).first<{ total: number }>(),
|
|
152
|
-
this.database
|
|
153
|
-
.prepare(dataSql)
|
|
154
|
-
.bind(...bindings, options.limit, options.offset)
|
|
155
|
-
.all<Record<string, unknown>>(),
|
|
156
|
-
])
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
entries: listRows.results ?? [],
|
|
160
|
-
totalCount: countRow?.total ?? 0,
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async timeseries(
|
|
165
|
-
seed: Seed,
|
|
166
|
-
formula: AggregateFormula,
|
|
167
|
-
window: TimeWindow,
|
|
168
|
-
groupColumn: string,
|
|
169
|
-
): Promise<TimeseriesPoint[]> {
|
|
170
|
-
const groupColumnExpression = this.resolveColumnExpression(seed, groupColumn)
|
|
171
|
-
const aggregateExpression = this.buildAggregateExpression(seed, formula)
|
|
172
|
-
const timeWindowFilter = this.buildTimeWindowFilter(window)
|
|
173
|
-
const tableName = `content_${seed.slug}`
|
|
174
|
-
|
|
175
|
-
const dateBucketExpression = groupColumnExpression === 'created_at'
|
|
176
|
-
? `strftime('%Y-%m-%d', created_at, 'unixepoch')`
|
|
177
|
-
: `strftime('%Y-%m-%d', CAST(${groupColumnExpression} AS INTEGER), 'unixepoch')`
|
|
178
|
-
|
|
179
|
-
const sql =
|
|
180
|
-
`SELECT ${dateBucketExpression} as bucket_label,
|
|
181
|
-
${aggregateExpression} as bucket_value
|
|
182
|
-
FROM ${tableName}
|
|
183
|
-
WHERE ${timeWindowFilter}
|
|
184
|
-
GROUP BY bucket_label
|
|
185
|
-
ORDER BY bucket_label ASC`
|
|
186
|
-
|
|
187
|
-
const rows = await this.database
|
|
188
|
-
.prepare(sql)
|
|
189
|
-
.all<{ bucket_label: string | null; bucket_value: number | null }>()
|
|
190
|
-
|
|
191
|
-
return (rows.results ?? []).map(row => ({
|
|
192
|
-
label: row.bucket_label ?? '',
|
|
193
|
-
value: row.bucket_value ?? 0,
|
|
194
|
-
}))
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Throws UNSAFE_COLUMN if the alias is neither a system column nor a
|
|
199
|
-
* declared seed branch, preventing SQL injection via column names.
|
|
200
|
-
*/
|
|
201
|
-
private resolveColumnExpression(seed: Seed, alias: string): string {
|
|
202
|
-
if (SYSTEM_COLUMNS.has(alias)) return alias
|
|
203
|
-
const branch = seed.branches.find(candidate => candidate.alias === alias)
|
|
204
|
-
if (!branch) throw new Error(UNSAFE_COLUMN_ERROR)
|
|
205
|
-
return branch.alias
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
private buildAggregateExpression(seed: Seed, formula: AggregateFormula): string {
|
|
209
|
-
switch (formula.op) {
|
|
210
|
-
case 'count':
|
|
211
|
-
return 'COUNT(*)'
|
|
212
|
-
case 'sum': {
|
|
213
|
-
const column = this.resolveColumnExpression(seed, formula.column)
|
|
214
|
-
return `SUM(CAST(${column} AS REAL))`
|
|
215
|
-
}
|
|
216
|
-
case 'avg': {
|
|
217
|
-
const column = this.resolveColumnExpression(seed, formula.column)
|
|
218
|
-
return `AVG(CAST(${column} AS REAL))`
|
|
219
|
-
}
|
|
220
|
-
case 'min': {
|
|
221
|
-
const column = this.resolveColumnExpression(seed, formula.column)
|
|
222
|
-
return `MIN(CAST(${column} AS REAL))`
|
|
223
|
-
}
|
|
224
|
-
case 'max': {
|
|
225
|
-
const column = this.resolveColumnExpression(seed, formula.column)
|
|
226
|
-
return `MAX(CAST(${column} AS REAL))`
|
|
227
|
-
}
|
|
228
|
-
case 'countWhere':
|
|
229
|
-
return this.buildCountWhereExpression(seed, formula.column, formula.value)
|
|
230
|
-
case 'percentageOf': {
|
|
231
|
-
const numerator = this.resolveColumnExpression(seed, formula.numeratorColumn)
|
|
232
|
-
const denominator = this.resolveColumnExpression(seed, formula.denominatorColumn)
|
|
233
|
-
return `CASE WHEN SUM(CAST(${denominator} AS REAL)) = 0 THEN 0 ELSE (SUM(CAST(${numerator} AS REAL)) * 100.0 / SUM(CAST(${denominator} AS REAL))) END`
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private buildCountWhereExpression(seed: Seed, alias: string, value: unknown): string {
|
|
239
|
-
const column = this.resolveColumnExpression(seed, alias)
|
|
240
|
-
if (value === null) return `COUNT(CASE WHEN ${column} IS NULL THEN 1 END)`
|
|
241
|
-
if (typeof value === 'boolean') {
|
|
242
|
-
const numericValue = value ? 1 : 0
|
|
243
|
-
return `COUNT(CASE WHEN ${column} = ${numericValue} THEN 1 END)`
|
|
244
|
-
}
|
|
245
|
-
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
246
|
-
return `COUNT(CASE WHEN CAST(${column} AS REAL) = ${value} THEN 1 END)`
|
|
247
|
-
}
|
|
248
|
-
const escaped = String(value).replace(/'/g, "''")
|
|
249
|
-
return `COUNT(CASE WHEN ${column} = '${escaped}' THEN 1 END)`
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private buildTimeWindowFilter(window: TimeWindow): string {
|
|
253
|
-
switch (window) {
|
|
254
|
-
case 'week': return "created_at > unixepoch('now', '-7 days')"
|
|
255
|
-
case 'month': return "created_at > unixepoch('now', '-1 month')"
|
|
256
|
-
case 'year': return "created_at > unixepoch('now', '-1 year')"
|
|
257
|
-
case 'all': return '1=1'
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
private buildPreviousWindowFilter(
|
|
262
|
-
window: TimeWindow,
|
|
263
|
-
): { currentFilter: string; previousFilter: string } {
|
|
264
|
-
switch (window) {
|
|
265
|
-
case 'week':
|
|
266
|
-
return {
|
|
267
|
-
currentFilter: "created_at > unixepoch('now', '-7 days')",
|
|
268
|
-
previousFilter: "created_at > unixepoch('now', '-14 days') AND created_at <= unixepoch('now', '-7 days')",
|
|
269
|
-
}
|
|
270
|
-
case 'month':
|
|
271
|
-
return {
|
|
272
|
-
currentFilter: "created_at > unixepoch('now', '-1 month')",
|
|
273
|
-
previousFilter: "created_at > unixepoch('now', '-2 months') AND created_at <= unixepoch('now', '-1 month')",
|
|
274
|
-
}
|
|
275
|
-
case 'year':
|
|
276
|
-
return {
|
|
277
|
-
currentFilter: "created_at > unixepoch('now', '-1 year')",
|
|
278
|
-
previousFilter: "created_at > unixepoch('now', '-2 years') AND created_at <= unixepoch('now', '-1 year')",
|
|
279
|
-
}
|
|
280
|
-
case 'all':
|
|
281
|
-
return { currentFilter: '1=1', previousFilter: '1=0' }
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
private appendFilterCondition(
|
|
286
|
-
seed: Seed,
|
|
287
|
-
filter: { column: string; op: string; value: unknown },
|
|
288
|
-
conditions: string[],
|
|
289
|
-
bindings: unknown[],
|
|
290
|
-
): void {
|
|
291
|
-
if (!ALLOWED_FILTER_OPERATORS.has(filter.op)) return
|
|
292
|
-
|
|
293
|
-
let column: string
|
|
294
|
-
try {
|
|
295
|
-
column = this.resolveColumnExpression(seed, filter.column)
|
|
296
|
-
} catch {
|
|
297
|
-
return
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
switch (filter.op) {
|
|
301
|
-
case 'eq':
|
|
302
|
-
case '=':
|
|
303
|
-
conditions.push(`${column} = ?`)
|
|
304
|
-
bindings.push(filter.value)
|
|
305
|
-
return
|
|
306
|
-
case 'neq':
|
|
307
|
-
case '!=':
|
|
308
|
-
conditions.push(`${column} != ?`)
|
|
309
|
-
bindings.push(filter.value)
|
|
310
|
-
return
|
|
311
|
-
case 'like':
|
|
312
|
-
conditions.push(`${column} LIKE ?`)
|
|
313
|
-
bindings.push(filter.value)
|
|
314
|
-
return
|
|
315
|
-
case 'gt':
|
|
316
|
-
case '>':
|
|
317
|
-
conditions.push(`CAST(${column} AS REAL) > ?`)
|
|
318
|
-
bindings.push(filter.value)
|
|
319
|
-
return
|
|
320
|
-
case 'lt':
|
|
321
|
-
case '<':
|
|
322
|
-
conditions.push(`CAST(${column} AS REAL) < ?`)
|
|
323
|
-
bindings.push(filter.value)
|
|
324
|
-
return
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
private buildListOrderClause(seed: Seed, options: WidgetListOptions): string {
|
|
329
|
-
let orderColumn = 'created_at'
|
|
330
|
-
if (options.orderByColumn) {
|
|
331
|
-
try {
|
|
332
|
-
orderColumn = this.resolveColumnExpression(seed, options.orderByColumn)
|
|
333
|
-
} catch {
|
|
334
|
-
orderColumn = 'created_at'
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return options.orderDirection === 'DESC'
|
|
338
|
-
? `ORDER BY ${orderColumn} DESC`
|
|
339
|
-
: `ORDER BY ${orderColumn} ASC`
|
|
340
|
-
}
|
|
341
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
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
|
-
// Compiled-in demo dataset. Workers have no runtime FS so the SQL from
|
|
6
|
-
// migrations/0028_v040_seed_data.sql is bundled here as a string constant.
|
|
7
|
-
export const DEMO_DATA_SQL = `
|
|
8
|
-
INSERT OR IGNORE INTO content_team (id, slug, status, name, role, bio, photo, linkedIn, active, metaTitle, metaDescription, created_at, updated_at) VALUES
|
|
9
|
-
('tm-0001', 'flavio-de-musso', 'published', 'Flavio De Musso', 'Founder & Principal Engineer', 'Progetta architetture moderne per CMS headless e prodotti digitali edge-native.', 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&q=80', 'https://www.linkedin.com/in/flaviodemusso', 1, 'Flavio De Musso', 'Founder e creatore di Beech CMS.', unixepoch(), unixepoch()),
|
|
10
|
-
('tm-0002', 'laura-rossi', 'published', 'Laura Rossi', 'Product Designer', 'Cura la UX della dashboard e dei workflow editoriali. Specializzata in design system a scala.', 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400&q=80', 'https://www.linkedin.com/in/', 1, 'Laura Rossi – Designer', 'UX designer focalizzata su strumenti per content team.', unixepoch(), unixepoch()),
|
|
11
|
-
('tm-0003', 'marco-bianchi', 'published', 'Marco Bianchi', 'Frontend Engineer', 'Sviluppa i field renderer React e il registry della UI. Appassionato di performance web.', 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=400&q=80', 'https://www.linkedin.com/in/', 1, 'Marco Bianchi – Engineer', 'Sviluppatore frontend specializzato in React e UX.', unixepoch(), unixepoch()),
|
|
12
|
-
('tm-0004', 'giulia-verdi', 'published', 'Giulia Verdi', 'Developer Relations', 'Aiuta i team a integrare Beech CMS nelle loro pipeline di sviluppo. Developer Advocate.', 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&q=80', 'https://www.linkedin.com/in/', 1, 'Giulia Verdi – DevRel', 'Developer advocate per la community Beech.', unixepoch(), unixepoch()),
|
|
13
|
-
('tm-0005', 'andrea-neri', 'published', 'Andrea Neri', 'Support Specialist', 'Supporto di primo livello per clienti enterprise e gestione della knowledge base.', 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400&q=80', 'https://www.linkedin.com/in/', 1, 'Andrea Neri – Support', 'Supporto tecnico e customer success.', unixepoch(), unixepoch());
|
|
14
|
-
|
|
15
|
-
INSERT OR IGNORE INTO content_prodotti (id, slug, status, name, price, stock, active, coverImage, images, description, metaTitle, metaDescription, created_at, updated_at) VALUES
|
|
16
|
-
('prd-0001', 'beech-starter', 'published', 'Beech Starter', 490, 50, 1, 'https://images.unsplash.com/photo-1496181133206-80ce9b88a853?w=800&q=80', '[]', '<p>Piano entry-level per piccoli siti e landing page. Include il Botanical Engine, la dashboard, e 1GB di storage R2.</p>', 'Beech Starter', 'Piano starter per progetti piccoli e medi.', unixepoch(), unixepoch()),
|
|
17
|
-
('prd-0002', 'beech-pro', 'published', 'Beech Pro', 1290, 30, 1, 'https://images.unsplash.com/photo-1499951360447-b19be8fe80f5?w=800&q=80', '[]', '<p>Tutte le feature avanzate per team strutturati: draft workflow, analytics per-seed, FTS5 full-text search, 10GB storage R2.</p>', 'Beech Pro', 'Piano professionale per agenzie e team marketing.', unixepoch(), unixepoch()),
|
|
18
|
-
('prd-0003', 'beech-enterprise', 'published', 'Beech Enterprise', 3990, 10, 1, 'https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=800&q=80', '[]', '<p>Funzionalità enterprise, audit log, SLA garantito, multi-ambiente staging/production, e storage R2 illimitato.</p>', 'Beech Enterprise', 'Soluzione enterprise per grandi organizzazioni.', unixepoch(), unixepoch()),
|
|
19
|
-
('prd-0004', 'implementazione-custom', 'published', 'Implementazione custom', 15000, 3, 1, 'https://images.unsplash.com/photo-1593642532744-d377ab507dc8?w=800&q=80', '[]', '<p>Servizio di implementazione su misura di Beech CMS: analisi dei requisiti, definizione dei Seed, deploy su Cloudflare, formazione team.</p>', 'Implementazione custom Beech', 'Servizio di onboarding e integrazione personalizzata.', unixepoch(), unixepoch()),
|
|
20
|
-
('prd-0005', 'workshop-botanical-engine', 'published', 'Workshop Botanical Engine', 990, 20, 1, 'https://images.unsplash.com/photo-1561070791-2526d30994b5?w=800&q=80', '[]', '<p>Workshop avanzato sul Botanical Engine e sul registry dei seed. Impara a definire Seed ottimali e a usare beech seed:load per deployare schemi.</p>', 'Workshop Botanical Engine', 'Formazione per team tecnici su Beech CMS.', unixepoch(), unixepoch());
|
|
21
|
-
|
|
22
|
-
INSERT OR IGNORE INTO content_articoli (id, slug, status, title, publishedAt, coverImage, tags, body, metaTitle, metaDescription, created_at, updated_at) VALUES
|
|
23
|
-
('art-0001', 'benvenuto-nel-blog', 'published', 'Benvenuto nel blog Beech', 1736467200, 'https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=800&q=80', '["news","annuncio"]', '<p>Scopri come Beech CMS gestisce contenuti schema-driven con tabelle SQL dedicate per ogni tipo di contenuto.</p><h3>Architettura moderna</h3><p>Beech v0.4.0 elimina il JSON blob e usa colonne reali per ogni Branch del Seed. Il risultato è performance migliorate e query native SQLite.</p>', 'Benvenuto nel blog Beech', 'Introduzione al CMS headless schema-driven.', unixepoch(), unixepoch()),
|
|
24
|
-
('art-0002', 'roadmap-2026', 'published', 'Roadmap prodotto 2026', 1738713600, 'https://images.unsplash.com/photo-1557804506-669a67965ba0?w=800&q=80', '["release","aggiornamento"]', '<p>Tutte le novità in arrivo per Beech CMS nel 2026: Doctype Architecture, CLI seed:load, FTS5 per-seed, e molto altro.</p><h3>Fase 1 — Core</h3><p>Il Botanical Engine diventa un compilatore di schema che genera DDL SQL dalla definizione TypeScript dei Seed.</p>', 'Roadmap Beech CMS 2026', 'Piano di sviluppo delle funzionalità per il 2026.', unixepoch(), unixepoch()),
|
|
25
|
-
('art-0003', 'migrare-da-wordpress', 'published', 'Migrare da WordPress a Beech', 1740182400, 'https://images.unsplash.com/photo-1542831371-29b0f74f9713?w=800&q=80', '["guida","cms"]', '<p>Guida pratica per migrare contenuti da WordPress a Beech CMS senza downtime. Esporta, trasforma e importa usando la Public API.</p><h3>Step 1: Esporta da WordPress</h3><p>Usa il plugin WP All Export per ottenere CSV o JSON dai post. Poi mappali alle colonne del tuo Seed.</p>', 'Migrazione da WordPress a Beech', 'Strategie per migrare contenuti in sicurezza.', unixepoch(), unixepoch()),
|
|
26
|
-
('art-0004', 'pattern-registry-ui', 'published', 'Il Pattern Registry della UI', 1740873600, 'https://images.unsplash.com/photo-1561070791-2526d30994b5?w=800&q=80', '["tutorial","guida"]', '<p>Come il registry dei field renderer semplifica la dashboard: ogni Branch type ha un renderer React dedicato, registrato centralmente e richiamato automaticamente.</p>', 'Pattern Registry UI Beech', 'Architettura dei componenti field-driven.', unixepoch(), unixepoch()),
|
|
27
|
-
('art-0005', 'edge-first-architecture', 'published', 'Architettura edge-first con Cloudflare', 1741132800, 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=800&q=80', '["news","aggiornamento"]', '<p>Perché Beech nasce per l''edge: latenza minima, zero cold start, D1 SQLite replicato globalmente su Cloudflare. Il CMS arriva dove si trovano gli utenti.</p>', 'Architettura edge-first', 'Vantaggi di un CMS progettato per l''edge.', unixepoch(), unixepoch());
|
|
28
|
-
|
|
29
|
-
INSERT OR IGNORE INTO content_testimonianze (id, slug, status, author, company, quote, rating, date, photo, active, metaTitle, metaDescription, created_at, updated_at) VALUES
|
|
30
|
-
('tes-0001', 'acme-corp', 'published', 'Chiara Conti', 'Acme Corp', 'Con Beech CMS abbiamo ridotto del 60% il tempo di pubblicazione dei contenuti.', 5, 1737331200, 'https://images.unsplash.com/photo-1573497019940-1c28c88b4f3e?w=400&q=80', 1, 'Caso studio Acme', 'Come Acme ha accelerato i workflow editoriali.', unixepoch(), unixepoch()),
|
|
31
|
-
('tes-0002', 'studio-x', 'published', 'Luca Ferri', 'Studio X', 'La flessibilità degli schema ci ha permesso di iterare velocemente sul design del sito.', 4, 1738195200, 'https://images.unsplash.com/photo-1560250097-0b93528c311a?w=400&q=80', 1, 'Testimonianza Studio X', 'Feedback di un''agenzia digital sul CMS.', unixepoch(), unixepoch()),
|
|
32
|
-
('tes-0003', 'media-hub', 'published', 'Sara Galli', 'MediaHub', 'Finalmente un CMS pensato per il contenuto, non per il database. Team felicissimo.', 5, 1739404800, 'https://images.unsplash.com/photo-1580489944761-15a19d654956?w=400&q=80', 1, 'Testimonianza MediaHub', 'Perché i content team amano Beech.', unixepoch(), unixepoch()),
|
|
33
|
-
('tes-0004', 'shop-co', 'published', 'Davide Rizzi', 'ShopCo', 'Gestire landing e pagine prodotto è diventato molto più semplice. Alta raccomandazione.', 4, 1740268800, 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&q=80', 1, 'Testimonianza ShopCo', 'Beech CMS per e-commerce moderni.', unixepoch(), unixepoch()),
|
|
34
|
-
('tes-0005', 'fit-tech', 'published', 'Elena Santi', 'FitTech Inc', 'La dashboard è così intuitiva che il team marketing la usa senza training.', 5, 1741046400, 'https://images.unsplash.com/photo-1573497019940-1c28c88b4f3e?w=400&q=80', 1, 'Testimonianza FitTech', 'Esperienza di un team marketing su Beech CMS.', unixepoch(), unixepoch());
|
|
35
|
-
|
|
36
|
-
INSERT OR IGNORE INTO content_pagine (id, slug, status, title, coverImage, body, metaTitle, metaDescription, created_at, updated_at) VALUES
|
|
37
|
-
('pag-0001', 'home', 'published', 'Homepage', 'https://images.unsplash.com/photo-1517694712202-14dd9538aa97?w=1200&q=80', '<h1>Beech CMS</h1><p>Il CMS schema-driven per l''era edge. Definisci i tuoi Seed in TypeScript, deploya su Cloudflare, e inizia a pubblicare.</p>', 'Beech CMS - Homepage', 'CMS moderno schema-driven per deployment edge.', unixepoch(), unixepoch()),
|
|
38
|
-
('pag-0002', 'il-prodotto', 'published', 'Il prodotto', 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=1200&q=80', '<h1>Perché Beech</h1><p>Architettura ibrida con tabelle SQL dedicate per Seed, Botanical Engine come compilatore di schema, e CLI per il deploy.</p>', 'Perché scegliere Beech', 'Scopri le funzionalità chiave di Beech CMS.', unixepoch(), unixepoch()),
|
|
39
|
-
('pag-0003', 'casi-studio', 'published', 'Casi studio', 'https://images.unsplash.com/photo-1552664730-d307ca884978?w=1200&q=80', '<h1>Storie di successo</h1><p>Come i team usano Beech ogni giorno per pubblicare contenuti velocemente con la dashboard schema-driven.</p>', 'Casi studio Beech', 'Raccolta di case study su Beech CMS.', unixepoch(), unixepoch()),
|
|
40
|
-
('pag-0004', 'pricing', 'published', 'Pricing', 'https://images.unsplash.com/photo-1554224155-6726b3ff858f?w=1200&q=80', '<h1>Piani e prezzi</h1><p>Scegli il piano adatto al tuo team: Starter, Pro o Enterprise. Tutti includono il Botanical Engine e la dashboard.</p>', 'Pricing Beech CMS', 'Confronta i piani Starter, Pro ed Enterprise.', unixepoch(), unixepoch()),
|
|
41
|
-
('pag-0005', 'contatti', 'published', 'Contatti', 'https://images.unsplash.com/photo-1423666639041-f56000c27a9a?w=1200&q=80', '<h1>Parliamo del tuo progetto</h1><p>Compila il form per una demo gratuita. Il team risponde entro 24 ore lavorative.</p>', 'Contatta Beech', 'Richiedi una demo o una consulenza su Beech CMS.', unixepoch(), unixepoch());
|
|
42
|
-
|
|
43
|
-
INSERT OR IGNORE INTO content_clienti (id, slug, status, name, email, passwordHash, tier, phone, internalNote, registeredAt, active, created_at, updated_at) VALUES
|
|
44
|
-
('clt-0001', 'marco-rossi', 'published', 'Marco Rossi', 'marco.rossi@acmecorp.it', '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8', 'enterprise', '+39 02 1234 5678', 'Cliente storico. Sconto applicato del 20% su rinnovo annuale.', 1710460800, 1, unixepoch(), unixepoch()),
|
|
45
|
-
('clt-0002', 'laura-bianchi', 'published', 'Laura Bianchi', 'laura.bianchi@studiox.io', '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92', 'pro', '+39 06 9876 5432', 'Contattare prima del rinnovo per upsell a enterprise.', 1736294400, 1, unixepoch(), unixepoch()),
|
|
46
|
-
('clt-0003', 'davide-ferrari', 'published', 'Davide Ferrari', 'davide@mediahub.eu', '1c8bfe8f801d79745c4631d09fff36c82aa37fc4cce4fc946683d7b336b63032', 'starter', '+39 011 4567 890', 'Ha richiesto supporto per migrazione da WordPress.', 1750550400, 1, unixepoch(), unixepoch()),
|
|
47
|
-
('clt-0004', 'giulia-verdi', 'published', 'Giulia Verdi', 'giulia.verdi@personal.me', '6ca13d52ca70c883e0f0bb101e425a89e8624de51db2d2392593af6a84118090', 'free', '+39 081 3210 987', 'Account free scaduto. Inviare offerta upgrade.', 1756771200, 0, unixepoch(), unixepoch()),
|
|
48
|
-
('clt-0005', 'sofia-costa', 'published', 'Sofia Costa', 'sofia.costa@fittech.com', '65e84be33532fb784c48129675f9eff3a682b27168c0ea744b2cf58ee02337c5', 'pro', NULL, NULL, 1769731200, 1, unixepoch(), unixepoch());
|
|
49
|
-
|
|
50
|
-
INSERT OR IGNORE INTO content_messaggi (id, slug, status, name, email, subject, message, read, created_at, updated_at) VALUES
|
|
51
|
-
('msg-0001', 'msg-demo-1', 'published', 'Riccardo Esposito', 'riccardo@example.com', 'Richiesta demo Beech Pro', '<p>Buongiorno, siamo un''agenzia di 8 persone e vorremmo valutare Beech Pro per i nostri clienti. Possiamo fissare una call la prossima settimana?</p>', 0, unixepoch(), unixepoch()),
|
|
52
|
-
('msg-0002', 'msg-demo-2', 'published', 'Anna Marchetti', 'anna@startup.io', 'Domanda su FTS5 e ricerca full-text', '<p>Ciao team! Sto valutando Beech per un progetto di knowledge base. La ricerca full-text FTS5 funziona anche sulla versione Starter? Grazie.</p>', 1, unixepoch(), unixepoch()),
|
|
53
|
-
('msg-0003', 'msg-demo-3', 'published', 'Francesco Lombardi', 'f.lombardi@enterprise.it', 'Beech Enterprise — SLA e uptime', '<p>Siamo interessati al piano Enterprise. Quali SLA garantite? Abbiamo bisogno di 99.9% uptime per un''applicazione mission-critical.</p>', 0, unixepoch(), unixepoch());
|
|
54
|
-
`
|
|
@@ -1,20 +0,0 @@
|
|
|
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 { IDemoDataRepository } from '@beechcms/core'
|
|
7
|
-
import { DEMO_DATA_SQL } from './demo-data-sql'
|
|
8
|
-
|
|
9
|
-
export class D1DemoDataRepository implements IDemoDataRepository {
|
|
10
|
-
constructor(private readonly db: D1Database) {}
|
|
11
|
-
|
|
12
|
-
async loadDemoData(): Promise<void> {
|
|
13
|
-
const statements = DEMO_DATA_SQL
|
|
14
|
-
.split(';')
|
|
15
|
-
.map(s => s.trim())
|
|
16
|
-
.filter(s => s.length > 0)
|
|
17
|
-
.map(s => this.db.prepare(s))
|
|
18
|
-
await this.db.batch(statements)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
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 { IScheduler } from '@beechcms/core'
|
|
6
|
-
|
|
7
|
-
export class ExecutionContextScheduler implements IScheduler {
|
|
8
|
-
constructor(private readonly ctx: ExecutionContext) {}
|
|
9
|
-
|
|
10
|
-
waitUntil(promise: Promise<unknown>): void {
|
|
11
|
-
this.ctx.waitUntil(promise)
|
|
12
|
-
}
|
|
13
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
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 { IClock } from '@beechcms/core'
|
|
6
|
-
|
|
7
|
-
const MILLISECONDS_PER_SECOND = 1000
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Test-only IClock implementation that returns a frozen timestamp on every
|
|
11
|
-
* call. Lets specs assert exact values for `createdAt`, `iat`, and
|
|
12
|
-
* day-bucket computations without resorting to vi.useFakeTimers or
|
|
13
|
-
* patching the global Date constructor.
|
|
14
|
-
*/
|
|
15
|
-
export class FixedClock implements IClock {
|
|
16
|
-
constructor(private readonly fixedNowMs: number) {}
|
|
17
|
-
|
|
18
|
-
now(): number {
|
|
19
|
-
return this.fixedNowMs
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
nowSeconds(): number {
|
|
23
|
-
return Math.floor(this.fixedNowMs / MILLISECONDS_PER_SECOND)
|
|
24
|
-
}
|
|
25
|
-
}
|
package/src/shared/fts-sync.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
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
|
-
// v0.4.0: FTS sync handled automatically by SQL triggers (generateFtsTriggers).
|
|
6
|
-
// syncFts/deleteFts eliminated. This file kept as empty module to avoid import errors
|
|
7
|
-
// during the Phase 4 → Phase 6 transition.
|
|
8
|
-
export {}
|
|
@@ -1,83 +0,0 @@
|
|
|
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 { D1IdempotencyRepository } from './idempotency.repository.d1'
|
|
7
|
-
|
|
8
|
-
function makeMockDb(opts: { firstResult?: unknown } = {}) {
|
|
9
|
-
const { firstResult = null } = opts
|
|
10
|
-
const runMock = vi.fn().mockResolvedValue({ success: true })
|
|
11
|
-
const firstMock = vi.fn().mockResolvedValue(firstResult)
|
|
12
|
-
const bindMock = vi.fn(() => ({ run: runMock, first: firstMock }))
|
|
13
|
-
const prepareMock = vi.fn(() => ({ bind: bindMock }))
|
|
14
|
-
return { db: { prepare: prepareMock } as any, prepareMock, bindMock, runMock }
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe('D1IdempotencyRepository', () => {
|
|
18
|
-
describe('lookup', () => {
|
|
19
|
-
it('returns a mapped IdempotencyRecord when the key is found', async () => {
|
|
20
|
-
const row = {
|
|
21
|
-
idempotency_key: 'idem-1',
|
|
22
|
-
request_fingerprint: 'fp-abc',
|
|
23
|
-
response_status: 200,
|
|
24
|
-
response_body: '{"ok":true}',
|
|
25
|
-
expires_at: 9999,
|
|
26
|
-
}
|
|
27
|
-
const { db } = makeMockDb({ firstResult: row })
|
|
28
|
-
const result = await new D1IdempotencyRepository(db).lookup('idem-1')
|
|
29
|
-
expect(result).toEqual({
|
|
30
|
-
key: 'idem-1',
|
|
31
|
-
fingerprint: 'fp-abc',
|
|
32
|
-
responseStatus: 200,
|
|
33
|
-
responseBody: '{"ok":true}',
|
|
34
|
-
expiresAt: 9999,
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('returns null when the key is not found', async () => {
|
|
39
|
-
const { db } = makeMockDb({ firstResult: null })
|
|
40
|
-
expect(await new D1IdempotencyRepository(db).lookup('missing')).toBeNull()
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('queries the correct table', async () => {
|
|
44
|
-
const { db, prepareMock } = makeMockDb()
|
|
45
|
-
await new D1IdempotencyRepository(db).lookup('k')
|
|
46
|
-
expect(prepareMock).toHaveBeenCalledWith(expect.stringContaining('public_idempotency_keys'))
|
|
47
|
-
})
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
describe('store', () => {
|
|
51
|
-
it('calls INSERT INTO public_idempotency_keys with upsert on conflict', async () => {
|
|
52
|
-
const { db, prepareMock } = makeMockDb()
|
|
53
|
-
await new D1IdempotencyRepository(db).store({
|
|
54
|
-
key: 'idem-1', fingerprint: 'fp', responseStatus: 201, responseBody: '{}', expiresAt: 9999,
|
|
55
|
-
})
|
|
56
|
-
const sql = prepareMock.mock.calls[0][0] as string
|
|
57
|
-
expect(sql).toContain('INSERT INTO public_idempotency_keys')
|
|
58
|
-
expect(sql).toContain('ON CONFLICT')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('binds key, fingerprint, status, body, and expiresAt', async () => {
|
|
62
|
-
const { db, bindMock } = makeMockDb()
|
|
63
|
-
await new D1IdempotencyRepository(db).store({
|
|
64
|
-
key: 'idem-1', fingerprint: 'fp-x', responseStatus: 200, responseBody: '{"ok":1}', expiresAt: 8888,
|
|
65
|
-
})
|
|
66
|
-
const args = bindMock.mock.calls[0] as unknown[]
|
|
67
|
-
expect(args).toContain('idem-1')
|
|
68
|
-
expect(args).toContain('fp-x')
|
|
69
|
-
expect(args).toContain(200)
|
|
70
|
-
expect(args).toContain('{"ok":1}')
|
|
71
|
-
expect(args).toContain(8888)
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
describe('cleanup', () => {
|
|
76
|
-
it('calls DELETE with the expiration timestamp', async () => {
|
|
77
|
-
const { db, prepareMock, bindMock } = makeMockDb()
|
|
78
|
-
await new D1IdempotencyRepository(db).cleanup(12345)
|
|
79
|
-
expect(prepareMock).toHaveBeenCalledWith(expect.stringContaining('DELETE FROM public_idempotency_keys'))
|
|
80
|
-
expect(bindMock).toHaveBeenCalledWith(12345)
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
})
|
|
@@ -1,60 +0,0 @@
|
|
|
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 { IdempotencyRepository, IdempotencyRecord } from '@beechcms/core'
|
|
6
|
-
import { BaseD1Repository } from './base.repository.d1.js'
|
|
7
|
-
|
|
8
|
-
export class D1IdempotencyRepository extends BaseD1Repository implements IdempotencyRepository {
|
|
9
|
-
/**
|
|
10
|
-
* Looks up an idempotency record by its key.
|
|
11
|
-
*/
|
|
12
|
-
async lookup(idempotencyKey: string): Promise<IdempotencyRecord | null> {
|
|
13
|
-
const idempotencyRecordResult = await this.database.prepare(
|
|
14
|
-
`SELECT idempotency_key, request_fingerprint, response_status, response_body, expires_at
|
|
15
|
-
FROM public_idempotency_keys WHERE idempotency_key = ? LIMIT 1`
|
|
16
|
-
).bind(idempotencyKey).first<any>()
|
|
17
|
-
|
|
18
|
-
if (!idempotencyRecordResult) {
|
|
19
|
-
return null
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
key: idempotencyRecordResult.idempotency_key,
|
|
24
|
-
fingerprint: idempotencyRecordResult.request_fingerprint,
|
|
25
|
-
responseStatus: idempotencyRecordResult.response_status,
|
|
26
|
-
responseBody: idempotencyRecordResult.response_body,
|
|
27
|
-
expiresAt: idempotencyRecordResult.expires_at
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Stores or updates an idempotency record.
|
|
33
|
-
*/
|
|
34
|
-
async store(idempotencyData: IdempotencyRecord): Promise<void> {
|
|
35
|
-
await this.database.prepare(
|
|
36
|
-
`INSERT INTO public_idempotency_keys (idempotency_key, request_fingerprint, response_status, response_body, created_at, expires_at)
|
|
37
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
38
|
-
ON CONFLICT(idempotency_key) DO UPDATE SET
|
|
39
|
-
request_fingerprint = excluded.request_fingerprint,
|
|
40
|
-
response_status = excluded.response_status,
|
|
41
|
-
response_body = excluded.response_body,
|
|
42
|
-
created_at = excluded.created_at,
|
|
43
|
-
expires_at = excluded.expires_at`
|
|
44
|
-
).bind(
|
|
45
|
-
idempotencyData.key,
|
|
46
|
-
idempotencyData.fingerprint,
|
|
47
|
-
idempotencyData.responseStatus,
|
|
48
|
-
idempotencyData.responseBody,
|
|
49
|
-
Math.floor(Date.now() / 1000),
|
|
50
|
-
idempotencyData.expiresAt
|
|
51
|
-
).run()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Cleans up expired idempotency records.
|
|
56
|
-
*/
|
|
57
|
-
async cleanup(expirationTimestamp: number): Promise<void> {
|
|
58
|
-
await this.database.prepare(`DELETE FROM public_idempotency_keys WHERE expires_at < ?`).bind(expirationTimestamp).run()
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
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 { IActivityLogger, ActivityLogEntry } from '@beechcms/core'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Test double for {@link IActivityLogger}.
|
|
9
|
-
*
|
|
10
|
-
* Captures every call into a public array so test assertions can verify
|
|
11
|
-
* audit-trail side effects without touching D1. Preserves insertion order.
|
|
12
|
-
*/
|
|
13
|
-
export class InMemoryActivityLogger implements IActivityLogger {
|
|
14
|
-
public readonly entries: ActivityLogEntry[] = []
|
|
15
|
-
|
|
16
|
-
log(entry: ActivityLogEntry): void {
|
|
17
|
-
this.entries.push(entry)
|
|
18
|
-
}
|
|
19
|
-
}
|