@beechcms/api 0.6.0-preview.2 → 0.6.0-preview.4
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-BwlCt-IA.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-BcVN-DNy.js +642 -0
- package/assets/dashboard/assets/index-Cf1yRCqG.css +1 -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-BMWpjFzf.js +1 -0
- package/assets/dashboard/assets/timeseries-chart-recharts-8_GwAFR0.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 +45 -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} +19 -24
- 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} +3 -16
- 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 +7 -0
- package/dist/index.js +9732 -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/queue.middleware.d.ts +11 -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/{src/public/slug-utils.ts → dist/public/slug-utils.d.ts} +3 -10
- 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/cloudflare-queue-service.d.ts +11 -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-queue-service.d.ts +16 -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/queue-consumer.d.ts +9 -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 +87 -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 +28 -18
- package/assets/dashboard/assets/chart-BrJFdsGZ.js +0 -39
- package/assets/dashboard/assets/index-CIaoMxuB.css +0 -1
- package/assets/dashboard/assets/index-wTKgygsg.js +0 -636
- package/assets/dashboard/assets/pie-chart-recharts-BEaE1FOE.js +0 -1
- package/assets/dashboard/assets/timeseries-chart-recharts-DmWM6imF.js +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 -370
- 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/dashboard-layout/__tests__/dashboard-layout.handler.test.ts +0 -388
- package/src/features/dashboard-layout/dashboard-layout.handler.ts +0 -206
- package/src/features/dashboard-layout/index.ts +0 -5
- 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 -1199
- package/src/features/seeds/seeds.handler.ts +0 -558
- 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 -288
- 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.test.ts +0 -85
- package/src/middleware/auth-providers.middleware.ts +0 -45
- package/src/middleware/observability.middleware.test.ts +0 -93
- package/src/middleware/observability.middleware.ts +0 -70
- package/src/middleware/rate-limit.middleware.ts +0 -45
- package/src/middleware/repository.middleware.ts +0 -117
- 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/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 -507
- 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 -267
- package/src/shared/d1-widget.repository.ts +0 -385
- package/src/shared/dashboard-layout.repository.d1.test.ts +0 -135
- package/src/shared/dashboard-layout.repository.d1.ts +0 -58
- package/src/shared/demo-data-sql.ts +0 -216
- 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 -58
- 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.test.ts +0 -142
- 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 -90
- package/src/upload.ts +0 -198
- package/src/widget.test.ts +0 -85
- package/src/widget.ts +0 -251
|
@@ -1,44 +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
|
-
export function cronMatches(expression: string | null, scheduledTime: number): boolean {
|
|
6
|
-
if (!expression) return false
|
|
7
|
-
const parts = expression.trim().split(/\s+/)
|
|
8
|
-
if (parts.length !== 5) return false
|
|
9
|
-
|
|
10
|
-
const d = new Date(scheduledTime)
|
|
11
|
-
const actual = [
|
|
12
|
-
d.getUTCMinutes(),
|
|
13
|
-
d.getUTCHours(),
|
|
14
|
-
d.getUTCDate(),
|
|
15
|
-
d.getUTCMonth() + 1,
|
|
16
|
-
d.getUTCDay(),
|
|
17
|
-
] as const
|
|
18
|
-
|
|
19
|
-
return parts.every((field, i) => matchField(field, actual[i]))
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function matchField(field: string, value: number): boolean {
|
|
23
|
-
if (field === '*') return true
|
|
24
|
-
if (field.includes(',')) {
|
|
25
|
-
return field.split(',').some((part) => matchField(part, value))
|
|
26
|
-
}
|
|
27
|
-
if (field.includes('/')) {
|
|
28
|
-
const [rangePart, stepPart] = field.split('/')
|
|
29
|
-
const step = Number(stepPart)
|
|
30
|
-
if (!Number.isFinite(step) || step <= 0) return false
|
|
31
|
-
if (rangePart === '*') return value % step === 0
|
|
32
|
-
if (rangePart.includes('-')) {
|
|
33
|
-
const [lo, hi] = rangePart.split('-').map(Number)
|
|
34
|
-
return value >= lo && value <= hi && (value - lo) % step === 0
|
|
35
|
-
}
|
|
36
|
-
return false
|
|
37
|
-
}
|
|
38
|
-
if (field.includes('-')) {
|
|
39
|
-
const [lo, hi] = field.split('-').map(Number)
|
|
40
|
-
return Number.isFinite(lo) && Number.isFinite(hi) && value >= lo && value <= hi
|
|
41
|
-
}
|
|
42
|
-
const n = Number(field)
|
|
43
|
-
return Number.isFinite(n) && n === value
|
|
44
|
-
}
|
|
@@ -1,46 +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
|
-
/**
|
|
6
|
-
* Shared helper: converts a TriggerCondition into a FilterGroup accepted by
|
|
7
|
-
* ContentRepository.findMany. Used by cron-runner and context-resolver.
|
|
8
|
-
*
|
|
9
|
-
* TODO Sprint 8 (Task 12): when-pushdown.ts will call extractPushdownFilters
|
|
10
|
-
* to convert safe WhenNode predicates to FilterGroup[] for SQL pre-filtering.
|
|
11
|
-
*/
|
|
12
|
-
import type { BranchType, FilterGroup, FilterOperator, FilterType, Seed, TriggerCondition } from '@beechcms/core'
|
|
13
|
-
|
|
14
|
-
const SYSTEM_COLUMNS = new Set(['id', 'slug', 'status', 'created_at', 'updated_at'])
|
|
15
|
-
|
|
16
|
-
function mapBranchTypeToFilterType(type: BranchType): FilterType {
|
|
17
|
-
switch (type) {
|
|
18
|
-
case 'number': return 'number'
|
|
19
|
-
case 'boolean': return 'boolean'
|
|
20
|
-
case 'date': return 'date'
|
|
21
|
-
case 'tags': return 'tags'
|
|
22
|
-
case 'json': return 'json'
|
|
23
|
-
default: return 'text'
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function mapOp(op: TriggerCondition['op']): FilterOperator {
|
|
28
|
-
switch (op) {
|
|
29
|
-
case 'isempty': return 'is_empty'
|
|
30
|
-
case 'isnotempty': return 'is_not_empty'
|
|
31
|
-
default: return op
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function conditionToFilterGroup(c: TriggerCondition, seed: Seed): FilterGroup {
|
|
36
|
-
const branch = seed.branches.find((b) => b.alias === c.field)
|
|
37
|
-
const type: FilterType = branch
|
|
38
|
-
? mapBranchTypeToFilterType(branch.type)
|
|
39
|
-
: SYSTEM_COLUMNS.has(c.field) ? 'system' : 'text'
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
column: c.field,
|
|
43
|
-
type,
|
|
44
|
-
conditions: [{ op: mapOp(c.op), value: c.value as string | number | boolean | null }],
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,16 +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
|
-
export { AutomationRunner } from './automation-runner'
|
|
6
|
-
export type { AutomationRunnerDeps } from './automation-runner'
|
|
7
|
-
export { runCronAutomations } from './cron-runner'
|
|
8
|
-
export type { CronRunnerDeps } from './cron-runner'
|
|
9
|
-
export { automationsApp } from './automations.handler'
|
|
10
|
-
export {
|
|
11
|
-
createAutomationSchema,
|
|
12
|
-
updateAutomationSchema,
|
|
13
|
-
toggleAutomationSchema,
|
|
14
|
-
automationActionSchema,
|
|
15
|
-
} from './automations.schema'
|
|
16
|
-
export type { CreateAutomationBody, UpdateAutomationBody } from './automations.schema'
|
|
@@ -1,245 +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
|
-
/**
|
|
6
|
-
* Pure template-key parser for the automation template engine.
|
|
7
|
-
*
|
|
8
|
-
* Grammar (Sprint 6):
|
|
9
|
-
* simple: {{title}}, {{author.name}}
|
|
10
|
-
* scoped: {{<scope>:<selector>:<field>}}
|
|
11
|
-
*
|
|
12
|
-
* Grammar (Sprint 7):
|
|
13
|
-
* var_access: {{varname.firstone.campo}}, {{varname.count.(status=paid)}}
|
|
14
|
-
* {{varname.array[id1,id2].count}}
|
|
15
|
-
*
|
|
16
|
-
* Scopes: this | batch | <seedSlug> | <contextKey>
|
|
17
|
-
* Selectors: lastone | firstone | all | byid(<id>) | where(<alias>=<value>)
|
|
18
|
-
* Fields: branch alias, system column, or aggregate (count/sum/avg/min/max/pluck)
|
|
19
|
-
*
|
|
20
|
-
* Sugar:
|
|
21
|
-
* {{batch:count}} → batch:all:count
|
|
22
|
-
* {{<scope>:<field>}} → <scope>:lastone:<field> (default selector)
|
|
23
|
-
*
|
|
24
|
-
* TODO Sprint 8: extend ParsedKey to support WhenOperand refs once the
|
|
25
|
-
* recursive when-group evaluator (Tasks 10-16) is implemented.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
export { AUTOMATION_RESERVED_WORDS } from '@beechcms/core'
|
|
29
|
-
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// Sprint 7 types: var_access parsed key
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
export type VarStep =
|
|
35
|
-
| { type: 'field'; name: string }
|
|
36
|
-
| { type: 'nav'; nav: 'firstone' | 'lastone' }
|
|
37
|
-
| { type: 'agg'; op: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'pluck'; field?: string }
|
|
38
|
-
| { type: 'array'; ids: string[] }
|
|
39
|
-
|
|
40
|
-
export interface InlineCondition {
|
|
41
|
-
column: string
|
|
42
|
-
op: '=' | '!=' | '<' | '>' | '<=' | '>='
|
|
43
|
-
value: string
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
export type AutomationContextSelector =
|
|
49
|
-
| { kind: 'lastone' }
|
|
50
|
-
| { kind: 'firstone' }
|
|
51
|
-
| { kind: 'all' }
|
|
52
|
-
| { kind: 'byid'; id: string }
|
|
53
|
-
| { kind: 'where'; alias: string; value: string }
|
|
54
|
-
|
|
55
|
-
export type AggregateOp = 'count' | 'sum' | 'avg' | 'min' | 'max' | 'pluck'
|
|
56
|
-
|
|
57
|
-
export type ParsedKey =
|
|
58
|
-
| { kind: 'simple'; path: string }
|
|
59
|
-
| {
|
|
60
|
-
kind: 'scoped'
|
|
61
|
-
scope: string
|
|
62
|
-
selector: AutomationContextSelector
|
|
63
|
-
op: 'field' | AggregateOp
|
|
64
|
-
field: string | null
|
|
65
|
-
}
|
|
66
|
-
| {
|
|
67
|
-
kind: 'var_access'
|
|
68
|
-
name: string
|
|
69
|
-
steps: VarStep[]
|
|
70
|
-
conditions: InlineCondition[]
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const SLUG_RE = /^[a-zA-Z0-9_-]+$/
|
|
74
|
-
const AGGREGATE_OPS = new Set<string>(['count', 'sum', 'avg', 'min', 'max', 'pluck'])
|
|
75
|
-
const NAV_OPS = new Set<string>(['firstone', 'lastone'])
|
|
76
|
-
const INLINE_COND_RE = /^\(([\w]+)\s*(=|!=|<=|>=|<|>)\s*([^)]+)\)$/
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Split `raw` on `:` but treat content inside `(...)` as atomic.
|
|
80
|
-
*/
|
|
81
|
-
function splitKey(raw: string): string[] {
|
|
82
|
-
const tokens: string[] = []
|
|
83
|
-
let depth = 0
|
|
84
|
-
let current = ''
|
|
85
|
-
|
|
86
|
-
for (const ch of raw) {
|
|
87
|
-
if (ch === '(') {
|
|
88
|
-
depth++
|
|
89
|
-
current += ch
|
|
90
|
-
} else if (ch === ')') {
|
|
91
|
-
depth--
|
|
92
|
-
current += ch
|
|
93
|
-
} else if (ch === ':' && depth === 0) {
|
|
94
|
-
tokens.push(current)
|
|
95
|
-
current = ''
|
|
96
|
-
} else {
|
|
97
|
-
current += ch
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
if (current) tokens.push(current)
|
|
101
|
-
return tokens
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Split `raw` on `.` treating `[...]` and `(...)` as atomic blocks.
|
|
106
|
-
*/
|
|
107
|
-
function splitDot(raw: string): string[] {
|
|
108
|
-
const tokens: string[] = []
|
|
109
|
-
let depth = 0
|
|
110
|
-
let current = ''
|
|
111
|
-
|
|
112
|
-
for (const ch of raw) {
|
|
113
|
-
if (ch === '[' || ch === '(') {
|
|
114
|
-
depth++
|
|
115
|
-
current += ch
|
|
116
|
-
} else if (ch === ']' || ch === ')') {
|
|
117
|
-
depth--
|
|
118
|
-
current += ch
|
|
119
|
-
} else if (ch === '.' && depth === 0) {
|
|
120
|
-
tokens.push(current)
|
|
121
|
-
current = ''
|
|
122
|
-
} else {
|
|
123
|
-
current += ch
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (current) tokens.push(current)
|
|
127
|
-
return tokens
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function parseSelector(token: string): AutomationContextSelector | null {
|
|
131
|
-
if (token === 'lastone') return { kind: 'lastone' }
|
|
132
|
-
if (token === 'firstone') return { kind: 'firstone' }
|
|
133
|
-
if (token === 'all') return { kind: 'all' }
|
|
134
|
-
|
|
135
|
-
const byidMatch = token.match(/^byid\((.+)\)$/)
|
|
136
|
-
if (byidMatch) return { kind: 'byid', id: byidMatch[1] }
|
|
137
|
-
|
|
138
|
-
const whereMatch = token.match(/^where\(([^=]+)=(.+)\)$/)
|
|
139
|
-
if (whereMatch) return { kind: 'where', alias: whereMatch[1], value: whereMatch[2] }
|
|
140
|
-
|
|
141
|
-
return null
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function isKnownScope(token: string): boolean {
|
|
145
|
-
return token === 'this' || token === 'batch' || SLUG_RE.test(token)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function parseTemplateKey(raw: string): ParsedKey | null {
|
|
149
|
-
if (!raw) return null
|
|
150
|
-
|
|
151
|
-
const trimmed = raw.trim()
|
|
152
|
-
|
|
153
|
-
// Step 1: if contains `:` at depth 0 → existing scoped / sugar logic
|
|
154
|
-
const colonTokens = splitKey(trimmed)
|
|
155
|
-
if (colonTokens.length > 1) {
|
|
156
|
-
const [scopeToken, ...rest] = colonTokens
|
|
157
|
-
if (!isKnownScope(scopeToken)) return null
|
|
158
|
-
|
|
159
|
-
if (rest.length === 1) {
|
|
160
|
-
const fieldOrAggregate = rest[0]
|
|
161
|
-
if (AGGREGATE_OPS.has(fieldOrAggregate)) {
|
|
162
|
-
return { kind: 'scoped', scope: scopeToken, selector: { kind: 'all' }, op: fieldOrAggregate as AggregateOp, field: null }
|
|
163
|
-
}
|
|
164
|
-
return { kind: 'scoped', scope: scopeToken, selector: { kind: 'lastone' }, op: 'field', field: fieldOrAggregate }
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const selectorToken = rest[0]
|
|
168
|
-
const selector = parseSelector(selectorToken)
|
|
169
|
-
if (!selector) return null
|
|
170
|
-
|
|
171
|
-
const fieldTokens = rest.slice(1)
|
|
172
|
-
if (fieldTokens.length === 0) return null
|
|
173
|
-
|
|
174
|
-
const opToken = fieldTokens[0]
|
|
175
|
-
if (AGGREGATE_OPS.has(opToken)) {
|
|
176
|
-
if (selector.kind !== 'all') return null
|
|
177
|
-
const subField = fieldTokens[1] ?? null
|
|
178
|
-
return { kind: 'scoped', scope: scopeToken, selector, op: opToken as AggregateOp, field: subField }
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return { kind: 'scoped', scope: scopeToken, selector, op: 'field', field: opToken }
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Step 2: tokenise on `.` at depth 0
|
|
185
|
-
const dotTokens = splitDot(trimmed)
|
|
186
|
-
|
|
187
|
-
// Step 3: if no token contains `[` or `(`, return simple (fast path)
|
|
188
|
-
if (!dotTokens.some((t) => t.includes('[') || t.includes('('))) {
|
|
189
|
-
return { kind: 'simple', path: trimmed }
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Step 4: build var_access
|
|
193
|
-
const [nameToken, ...stepTokens] = dotTokens
|
|
194
|
-
const steps: VarStep[] = []
|
|
195
|
-
const conditions: InlineCondition[] = []
|
|
196
|
-
|
|
197
|
-
let i = 0
|
|
198
|
-
while (i < stepTokens.length) {
|
|
199
|
-
const token = stepTokens[i]
|
|
200
|
-
|
|
201
|
-
// Inline condition: (col op val)
|
|
202
|
-
const condMatch = token.match(INLINE_COND_RE)
|
|
203
|
-
if (condMatch) {
|
|
204
|
-
conditions.push({ column: condMatch[1], op: condMatch[2] as InlineCondition['op'], value: condMatch[3].trim() })
|
|
205
|
-
i++
|
|
206
|
-
continue
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Array selector: array[id1,id2,...]
|
|
210
|
-
const arrayMatch = token.match(/^array\[([^\]]*)\]$/)
|
|
211
|
-
if (arrayMatch) {
|
|
212
|
-
const ids = arrayMatch[1].split(',').map((s) => s.trim()).filter(Boolean)
|
|
213
|
-
steps.push({ type: 'array', ids })
|
|
214
|
-
i++
|
|
215
|
-
continue
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Nav: firstone / lastone
|
|
219
|
-
if (NAV_OPS.has(token)) {
|
|
220
|
-
steps.push({ type: 'nav', nav: token as 'firstone' | 'lastone' })
|
|
221
|
-
i++
|
|
222
|
-
continue
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Aggregate: count | sum | avg | min | max | pluck
|
|
226
|
-
if (AGGREGATE_OPS.has(token)) {
|
|
227
|
-
const nextToken = stepTokens[i + 1]
|
|
228
|
-
// Consume the next token as field if it's a plain identifier (no special chars)
|
|
229
|
-
if (nextToken && !nextToken.includes('[') && !nextToken.includes('(') && !AGGREGATE_OPS.has(nextToken) && !NAV_OPS.has(nextToken)) {
|
|
230
|
-
steps.push({ type: 'agg', op: token as VarStep & { type: 'agg' } extends { op: infer O } ? O : never, field: nextToken })
|
|
231
|
-
i += 2
|
|
232
|
-
} else {
|
|
233
|
-
steps.push({ type: 'agg', op: token as any })
|
|
234
|
-
i++
|
|
235
|
-
}
|
|
236
|
-
continue
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Plain field
|
|
240
|
-
steps.push({ type: 'field', name: token })
|
|
241
|
-
i++
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return { kind: 'var_access', name: nameToken, steps, conditions }
|
|
245
|
-
}
|
|
@@ -1,140 +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 { Seed } from '@beechcms/core'
|
|
6
|
-
import type { ParsedKey, InlineCondition } from './template-grammar'
|
|
7
|
-
import { materializeCollection } from './action-executors/set-variable.executor'
|
|
8
|
-
|
|
9
|
-
type VarAccessParsed = Extract<ParsedKey, { kind: 'var_access' }>
|
|
10
|
-
|
|
11
|
-
function toNumeric(v: unknown): number {
|
|
12
|
-
const n = Number(v)
|
|
13
|
-
if (Number.isFinite(n)) return n
|
|
14
|
-
if (typeof v === 'string') {
|
|
15
|
-
const d = Date.parse(v)
|
|
16
|
-
if (!Number.isNaN(d)) return d / 1000
|
|
17
|
-
}
|
|
18
|
-
return NaN
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function numericCoerce(a: unknown, b: unknown): { left: unknown; right: unknown } {
|
|
22
|
-
const na = toNumeric(a)
|
|
23
|
-
const nb = toNumeric(b)
|
|
24
|
-
const numeric = Number.isFinite(na) || Number.isFinite(nb)
|
|
25
|
-
return { left: numeric ? na : a, right: numeric ? nb : b }
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function applyCondition(record: Record<string, unknown>, cond: InlineCondition): boolean {
|
|
29
|
-
const raw = record[cond.column]
|
|
30
|
-
const { left, right } = numericCoerce(raw, cond.value)
|
|
31
|
-
switch (cond.op) {
|
|
32
|
-
case '=': return left === right
|
|
33
|
-
case '!=': return left !== right
|
|
34
|
-
case '<': return (left as number) < (right as number)
|
|
35
|
-
case '>': return (left as number) > (right as number)
|
|
36
|
-
case '<=': return (left as number) <= (right as number)
|
|
37
|
-
case '>=': return (left as number) >= (right as number)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function applyConditions(
|
|
42
|
-
record: Record<string, unknown>,
|
|
43
|
-
conditions: InlineCondition[],
|
|
44
|
-
): boolean {
|
|
45
|
-
return conditions.every((c) => applyCondition(record, c))
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function filterItems(
|
|
49
|
-
items: Array<Record<string, unknown>>,
|
|
50
|
-
conditions: InlineCondition[],
|
|
51
|
-
): Array<Record<string, unknown>> {
|
|
52
|
-
if (conditions.length === 0) return items
|
|
53
|
-
return items.filter((r) => applyConditions(r, conditions))
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function getItems(value: unknown): Array<Record<string, unknown>> | null {
|
|
57
|
-
if (!value || typeof value !== 'object') return null
|
|
58
|
-
const items = (value as any)['_items']
|
|
59
|
-
return Array.isArray(items) ? items : null
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const EMPTY_SEED = { branches: [] } as unknown as Seed
|
|
63
|
-
|
|
64
|
-
export function resolveVarAccess(
|
|
65
|
-
parsed: VarAccessParsed,
|
|
66
|
-
variables: Record<string, unknown>,
|
|
67
|
-
onMissing?: (field: string) => void,
|
|
68
|
-
): unknown {
|
|
69
|
-
if (!(parsed.name in variables)) {
|
|
70
|
-
onMissing?.(parsed.name)
|
|
71
|
-
return undefined
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
let current: unknown = variables[parsed.name]
|
|
75
|
-
const { steps, conditions } = parsed
|
|
76
|
-
|
|
77
|
-
for (let i = 0; i < steps.length; i++) {
|
|
78
|
-
const step = steps[i]
|
|
79
|
-
|
|
80
|
-
// ── array selector ─────────────────────────────────────────────────────
|
|
81
|
-
if (step.type === 'array') {
|
|
82
|
-
const items = getItems(current) ?? (Array.isArray(current) ? current as Array<Record<string, unknown>> : null)
|
|
83
|
-
if (!items) return undefined
|
|
84
|
-
const idSet = new Set(step.ids)
|
|
85
|
-
const subset = items.filter((r) => idSet.has(String(r['id'] ?? '')))
|
|
86
|
-
// Apply conditions to the subset before building the collection
|
|
87
|
-
const filtered = filterItems(subset, conditions)
|
|
88
|
-
current = materializeCollection(EMPTY_SEED, filtered, null)
|
|
89
|
-
// Conditions consumed — no post-filter needed
|
|
90
|
-
continue
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ── nav: firstone / lastone ────────────────────────────────────────────
|
|
94
|
-
if (step.type === 'nav') {
|
|
95
|
-
// Nav moves us from a collection to a single record.
|
|
96
|
-
// Conditions are a guard on that record, NOT a pre-filter on _items.
|
|
97
|
-
const record = (current as any)?.[step.nav] as Record<string, unknown> | null | undefined
|
|
98
|
-
if (!record) { current = null; continue }
|
|
99
|
-
// Apply conditions to the single record (post-filter)
|
|
100
|
-
if (conditions.length > 0 && !applyConditions(record, conditions)) return undefined
|
|
101
|
-
current = record
|
|
102
|
-
continue
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ── aggregate ──────────────────────────────────────────────────────────
|
|
106
|
-
if (step.type === 'agg') {
|
|
107
|
-
// Apply conditions to _items before aggregating
|
|
108
|
-
const items = getItems(current)
|
|
109
|
-
if (items !== null) {
|
|
110
|
-
const filtered = filterItems(items, conditions)
|
|
111
|
-
current = materializeCollection(EMPTY_SEED, filtered, null)
|
|
112
|
-
}
|
|
113
|
-
if (step.op === 'count') {
|
|
114
|
-
current = (current as any)?.['count']
|
|
115
|
-
} else if (step.field) {
|
|
116
|
-
const sub = (current as any)?.[step.op]
|
|
117
|
-
current = typeof sub === 'object' && sub !== null ? sub[step.field] : sub
|
|
118
|
-
} else {
|
|
119
|
-
current = (current as any)?.[step.op]
|
|
120
|
-
}
|
|
121
|
-
// Conditions consumed by filtering _items
|
|
122
|
-
return current
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ── field access ───────────────────────────────────────────────────────
|
|
126
|
-
if (step.type === 'field') {
|
|
127
|
-
current = (current as any)?.[step.name]
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Post-filter on single record (conditions not consumed by agg or array)
|
|
132
|
-
if (conditions.length > 0 && current !== null && current !== undefined) {
|
|
133
|
-
if (typeof current === 'object' && !Array.isArray(current)) {
|
|
134
|
-
if (!applyConditions(current as Record<string, unknown>, conditions)) return undefined
|
|
135
|
-
}
|
|
136
|
-
// For scalar values: conditions cannot be checked — pass through
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return current
|
|
140
|
-
}
|
|
@@ -1,87 +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 { WhenNode, WhenPredicate, WhenGroup, WhenOperand } from '@beechcms/core'
|
|
6
|
-
import type { ResolvedContext } from './context-resolver'
|
|
7
|
-
import { parseTemplateKey } from './template-grammar'
|
|
8
|
-
|
|
9
|
-
export function evaluateWhen(
|
|
10
|
-
node: WhenNode | null,
|
|
11
|
-
context: ResolvedContext,
|
|
12
|
-
): boolean {
|
|
13
|
-
if (!node) return true
|
|
14
|
-
if (node.kind === 'predicate') return evalPredicate(node, context)
|
|
15
|
-
return evalGroup(node, context)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function evalGroup(group: WhenGroup, context: ResolvedContext): boolean {
|
|
19
|
-
if (group.children.length === 0) {
|
|
20
|
-
const empty = group.op === 'AND'
|
|
21
|
-
return group.negate ? !empty : empty
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
for (const child of group.children) {
|
|
25
|
-
const r = evaluateWhen(child, context)
|
|
26
|
-
if (group.op === 'AND' && !r) return group.negate ? true : false
|
|
27
|
-
if (group.op === 'OR' && r) return group.negate ? false : true
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const combined = group.op === 'AND'
|
|
31
|
-
return group.negate ? !combined : combined
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function resolveOperand(operand: WhenOperand, context: ResolvedContext): unknown {
|
|
35
|
-
if (operand.kind === 'literal') return operand.value
|
|
36
|
-
|
|
37
|
-
// this.field (dot notation from UI) → this:field (colon, parsed as scoped key)
|
|
38
|
-
const key = operand.key.startsWith('this.') ? 'this:' + operand.key.slice(5) : operand.key
|
|
39
|
-
const parsed = parseTemplateKey(key)
|
|
40
|
-
if (!parsed) return null
|
|
41
|
-
|
|
42
|
-
const val = context.lookup(parsed)
|
|
43
|
-
return val ?? null
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function evalPredicate(pred: WhenPredicate, context: ResolvedContext): boolean {
|
|
47
|
-
const left = resolveOperand(pred.left, context)
|
|
48
|
-
const right = pred.right !== undefined ? resolveOperand(pred.right, context) : undefined
|
|
49
|
-
return compare(pred.op, left, right)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function compare(op: string, left: unknown, right: unknown): boolean {
|
|
53
|
-
switch (op) {
|
|
54
|
-
case 'isempty': return left == null || left === ''
|
|
55
|
-
case 'isnotempty': return left != null && left !== ''
|
|
56
|
-
|
|
57
|
-
case 'eq': return coerceEqual(left, right)
|
|
58
|
-
case 'neq': return !coerceEqual(left, right)
|
|
59
|
-
|
|
60
|
-
case 'gt': return Number(left) > Number(right)
|
|
61
|
-
case 'gte': return Number(left) >= Number(right)
|
|
62
|
-
case 'lt': return Number(left) < Number(right)
|
|
63
|
-
case 'lte': return Number(left) <= Number(right)
|
|
64
|
-
|
|
65
|
-
case 'contains': return typeof left === 'string' && typeof right === 'string' && left.includes(right)
|
|
66
|
-
case 'startswith': return typeof left === 'string' && typeof right === 'string' && left.startsWith(right)
|
|
67
|
-
case 'endswith': return typeof left === 'string' && typeof right === 'string' && left.endsWith(right)
|
|
68
|
-
|
|
69
|
-
case 'in': return Array.isArray(right) && right.some((r) => coerceEqual(left, r))
|
|
70
|
-
case 'notin': return !Array.isArray(right) || !right.some((r) => coerceEqual(left, r))
|
|
71
|
-
|
|
72
|
-
case 'matches': {
|
|
73
|
-
if (typeof left !== 'string' || typeof right !== 'string') return false
|
|
74
|
-
try { return new RegExp(right).test(left) } catch { return false }
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
default: return false
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function coerceEqual(a: unknown, b: unknown): boolean {
|
|
82
|
-
if (a === null && b === null) return true
|
|
83
|
-
if (a === null || b === null) return false
|
|
84
|
-
if (typeof a === 'number' && typeof b === 'string' && !Number.isNaN(Number(b))) return a === Number(b)
|
|
85
|
-
if (typeof b === 'number' && typeof a === 'string' && !Number.isNaN(Number(a))) return Number(a) === b
|
|
86
|
-
return a === b
|
|
87
|
-
}
|
|
@@ -1,57 +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 { WhenNode, WhenPredicate, TriggerCondition, FilterGroup, Seed } from '@beechcms/core'
|
|
6
|
-
import { conditionToFilterGroup } from './filter-translation'
|
|
7
|
-
|
|
8
|
-
const PUSHDOWN_OPS = new Set<string>(['eq', 'neq', 'contains', 'gt', 'lt', 'isempty', 'isnotempty'])
|
|
9
|
-
|
|
10
|
-
export function extractPushdownFilters(
|
|
11
|
-
node: WhenNode | null,
|
|
12
|
-
seed: Seed,
|
|
13
|
-
): FilterGroup[] {
|
|
14
|
-
if (!node) return []
|
|
15
|
-
|
|
16
|
-
// Single predicate at root
|
|
17
|
-
if (node.kind === 'predicate') {
|
|
18
|
-
const f = tryPushdown(node, seed)
|
|
19
|
-
return f ? [f] : []
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Only extract direct predicate children of the outermost AND group (non-negated)
|
|
23
|
-
if (node.kind === 'group' && node.op === 'AND' && !node.negate) {
|
|
24
|
-
return node.children.flatMap((child) => {
|
|
25
|
-
if (child.kind !== 'predicate') return []
|
|
26
|
-
const f = tryPushdown(child, seed)
|
|
27
|
-
return f ? [f] : []
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return []
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function tryPushdown(pred: WhenPredicate, seed: Seed): FilterGroup | null {
|
|
35
|
-
if (pred.left.kind !== 'ref') return null
|
|
36
|
-
|
|
37
|
-
const key = pred.left.key
|
|
38
|
-
if (!key.startsWith('this.')) return null
|
|
39
|
-
const field = key.slice(5)
|
|
40
|
-
if (!field) return null
|
|
41
|
-
|
|
42
|
-
if (!PUSHDOWN_OPS.has(pred.op)) return null
|
|
43
|
-
|
|
44
|
-
if (pred.op === 'isempty' || pred.op === 'isnotempty') {
|
|
45
|
-
return conditionToFilterGroup(
|
|
46
|
-
{ field, op: pred.op as TriggerCondition['op'], value: null },
|
|
47
|
-
seed,
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!pred.right || pred.right.kind !== 'literal') return null
|
|
52
|
-
|
|
53
|
-
return conditionToFilterGroup(
|
|
54
|
-
{ field, op: pred.op as TriggerCondition['op'], value: pred.right.value },
|
|
55
|
-
seed,
|
|
56
|
-
)
|
|
57
|
-
}
|