@beechcms/api 0.6.0-preview.2 → 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/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/index.html +2 -2
- 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} +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 +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/{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/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/package.json +27 -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,851 +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 {
|
|
7
|
-
ContentRepository,
|
|
8
|
-
EntryNotFoundError,
|
|
9
|
-
RepositoryError,
|
|
10
|
-
SlugConflictError,
|
|
11
|
-
RelationTargetNotFoundError,
|
|
12
|
-
type BulkFieldUpdate,
|
|
13
|
-
type DraftSummary,
|
|
14
|
-
type Seed,
|
|
15
|
-
type Branch,
|
|
16
|
-
type SelectOptions,
|
|
17
|
-
buildSelectQuery,
|
|
18
|
-
deserializeFromDb,
|
|
19
|
-
serializeForDb,
|
|
20
|
-
} from '@beechcms/core'
|
|
21
|
-
import { BaseD1Repository } from './base.repository.d1'
|
|
22
|
-
|
|
23
|
-
// ── Private helpers ───────────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
function multiRelBranches(seed: Seed): Branch[] {
|
|
26
|
-
return seed.branches.filter(b => b.type === 'relation' && b.multiple === true)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function singleRelBranches(seed: Seed): Branch[] {
|
|
30
|
-
return seed.branches.filter(b => b.type === 'relation' && !b.multiple)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function jTable(seedSlug: string, branchAlias: string): string {
|
|
34
|
-
return `rel_${seedSlug}_${branchAlias}`
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function jDraftTable(seedSlug: string, branchAlias: string): string {
|
|
38
|
-
return `rel_${seedSlug}_${branchAlias}_drafts`
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export class D1ContentRepository extends BaseD1Repository implements ContentRepository {
|
|
42
|
-
/**
|
|
43
|
-
* Deserializes a DB row using the Seed's branch definitions.
|
|
44
|
-
* Skips multi-relation branches — their values come from junction tables.
|
|
45
|
-
*/
|
|
46
|
-
private rowToData(seed: Seed, row: any): Record<string, any> {
|
|
47
|
-
const data: Record<string, any> = {
|
|
48
|
-
id: row.id,
|
|
49
|
-
slug: row.slug,
|
|
50
|
-
status: row.status,
|
|
51
|
-
created_at: row.created_at,
|
|
52
|
-
updated_at: row.updated_at,
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
for (const branch of seed.branches) {
|
|
56
|
-
// Multi-relation values live in junction tables, not as columns on this table
|
|
57
|
-
if (branch.type === 'relation' && branch.multiple === true) continue
|
|
58
|
-
if (Object.hasOwn(row, branch.alias)) {
|
|
59
|
-
data[branch.alias] = deserializeFromDb(branch, row[branch.alias])
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return data
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Fetches multi-relation arrays for a single entry and attaches them to `data`.
|
|
68
|
-
*/
|
|
69
|
-
private async attachMultiRelations(
|
|
70
|
-
seed: Seed,
|
|
71
|
-
entryId: string,
|
|
72
|
-
data: Record<string, any>,
|
|
73
|
-
): Promise<void> {
|
|
74
|
-
const branches = multiRelBranches(seed)
|
|
75
|
-
if (branches.length === 0) return
|
|
76
|
-
|
|
77
|
-
const stmts = branches.map(b =>
|
|
78
|
-
this.database
|
|
79
|
-
.prepare(`SELECT target_id FROM ${jTable(seed.slug, b.alias)} WHERE parent_id = ? ORDER BY position ASC`)
|
|
80
|
-
.bind(entryId),
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
const results = await this.database.batch(stmts)
|
|
84
|
-
for (let i = 0; i < branches.length; i++) {
|
|
85
|
-
data[branches[i].alias] = (results[i].results ?? []).map((r: any) => r.target_id)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Fetches multi-relation arrays for a list of entries and attaches them.
|
|
91
|
-
* One query per multi-relation branch (O(R) queries, R = branch count).
|
|
92
|
-
*/
|
|
93
|
-
private async attachMultiRelationsMany(
|
|
94
|
-
seed: Seed,
|
|
95
|
-
entries: Record<string, any>[],
|
|
96
|
-
): Promise<void> {
|
|
97
|
-
const branches = multiRelBranches(seed)
|
|
98
|
-
if (branches.length === 0 || entries.length === 0) return
|
|
99
|
-
|
|
100
|
-
const ids = entries.map(e => e.id)
|
|
101
|
-
const placeholders = ids.map(() => '?').join(', ')
|
|
102
|
-
|
|
103
|
-
const stmts = branches.map(b =>
|
|
104
|
-
this.database
|
|
105
|
-
.prepare(
|
|
106
|
-
`SELECT parent_id, target_id FROM ${jTable(seed.slug, b.alias)}` +
|
|
107
|
-
` WHERE parent_id IN (${placeholders}) ORDER BY parent_id, position ASC`,
|
|
108
|
-
)
|
|
109
|
-
.bind(...ids),
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
const results = await this.database.batch(stmts)
|
|
113
|
-
for (let i = 0; i < branches.length; i++) {
|
|
114
|
-
const branch = branches[i]
|
|
115
|
-
const byParent = new Map<string, string[]>()
|
|
116
|
-
for (const row of results[i].results ?? []) {
|
|
117
|
-
const r = row as Record<string, unknown>
|
|
118
|
-
const pid = r.parent_id as string
|
|
119
|
-
if (!byParent.has(pid)) byParent.set(pid, [])
|
|
120
|
-
byParent.get(pid)!.push(r.target_id as string)
|
|
121
|
-
}
|
|
122
|
-
for (const entry of entries) {
|
|
123
|
-
entry[branch.alias] = byParent.get(entry.id) ?? []
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async findMany(
|
|
129
|
-
seed: Seed,
|
|
130
|
-
options: SelectOptions
|
|
131
|
-
): Promise<{ items: Record<string, any>[]; total: number }> {
|
|
132
|
-
try {
|
|
133
|
-
const { sql, bindings } = buildSelectQuery(seed, options)
|
|
134
|
-
|
|
135
|
-
const countSql = sql
|
|
136
|
-
.replace(/SELECT .* FROM/, 'SELECT COUNT(*) as total FROM')
|
|
137
|
-
.replace(/ ORDER BY .*$/, '')
|
|
138
|
-
.replace(/ LIMIT \? OFFSET \?$/, '')
|
|
139
|
-
|
|
140
|
-
const countBindings = bindings.slice(0, bindings.length - (options.pagination ? 2 : 0))
|
|
141
|
-
|
|
142
|
-
const [batchResults, totalCountResult] = await this.database.batch([
|
|
143
|
-
this.database.prepare(sql).bind(...bindings),
|
|
144
|
-
this.database.prepare(countSql).bind(...countBindings),
|
|
145
|
-
])
|
|
146
|
-
|
|
147
|
-
const contentEntries = (batchResults.results || []).map(row => this.rowToData(seed, row))
|
|
148
|
-
const totalEntriesCount = (totalCountResult.results?.[0] as any)?.total || 0
|
|
149
|
-
|
|
150
|
-
await this.attachMultiRelationsMany(seed, contentEntries)
|
|
151
|
-
|
|
152
|
-
return { items: contentEntries, total: totalEntriesCount }
|
|
153
|
-
} catch (error) {
|
|
154
|
-
throw this.mapError(error, `findMany(${seed.slug})`)
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async findById(seed: Seed, id: string): Promise<Record<string, any>> {
|
|
159
|
-
try {
|
|
160
|
-
const tableName = this.getTableName(seed.slug)
|
|
161
|
-
const entryRow = await this.database
|
|
162
|
-
.prepare(`SELECT * FROM ${tableName} WHERE id = ? LIMIT 1`)
|
|
163
|
-
.bind(id)
|
|
164
|
-
.first()
|
|
165
|
-
|
|
166
|
-
if (!entryRow) {
|
|
167
|
-
throw new EntryNotFoundError(`Entry ${id} not found in ${seed.slug}`)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const data = this.rowToData(seed, entryRow)
|
|
171
|
-
await this.attachMultiRelations(seed, id, data)
|
|
172
|
-
return data
|
|
173
|
-
} catch (error) {
|
|
174
|
-
if (error instanceof EntryNotFoundError) throw error
|
|
175
|
-
throw this.mapError(error, `findById(${seed.slug}, ${id})`)
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async findBySlug(seed: Seed, slug: string): Promise<Record<string, any>> {
|
|
180
|
-
try {
|
|
181
|
-
const tableName = this.getTableName(seed.slug)
|
|
182
|
-
const entryRow = await this.database
|
|
183
|
-
.prepare(`SELECT * FROM ${tableName} WHERE slug = ? LIMIT 1`)
|
|
184
|
-
.bind(slug)
|
|
185
|
-
.first()
|
|
186
|
-
|
|
187
|
-
if (!entryRow) {
|
|
188
|
-
throw new EntryNotFoundError(`Entry with slug "${slug}" not found in ${seed.slug}`)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const data = this.rowToData(seed, entryRow)
|
|
192
|
-
await this.attachMultiRelations(seed, entryRow.id as string, data)
|
|
193
|
-
return data
|
|
194
|
-
} catch (error) {
|
|
195
|
-
if (error instanceof EntryNotFoundError) throw error
|
|
196
|
-
throw this.mapError(error, `findBySlug(${seed.slug}, ${slug})`)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async getFacets(seed: Seed): Promise<{
|
|
201
|
-
statuses: Record<string, number>
|
|
202
|
-
tagsByColumn: Record<string, string[]>
|
|
203
|
-
}> {
|
|
204
|
-
try {
|
|
205
|
-
const tableName = this.getTableName(seed.slug)
|
|
206
|
-
|
|
207
|
-
const statusResults = await this.database
|
|
208
|
-
.prepare(`SELECT status, COUNT(*) as count FROM ${tableName} GROUP BY status`)
|
|
209
|
-
.all()
|
|
210
|
-
|
|
211
|
-
const statusesCount: Record<string, number> = {}
|
|
212
|
-
for (const statusRow of statusResults.results || []) {
|
|
213
|
-
statusesCount[statusRow.status as string] = statusRow.count as number
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const tagsByColumn: Record<string, string[]> = {}
|
|
217
|
-
const tagBranches = seed.branches.filter(branch => branch.type === 'tags')
|
|
218
|
-
|
|
219
|
-
for (const branch of tagBranches) {
|
|
220
|
-
const tagResults = await this.database
|
|
221
|
-
.prepare(`SELECT DISTINCT value FROM ${tableName}, json_each(${tableName}.${branch.alias}) WHERE value IS NOT NULL`)
|
|
222
|
-
.all()
|
|
223
|
-
tagsByColumn[branch.alias] = (tagResults.results || []).map(row => row.value as string)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return { statuses: statusesCount, tagsByColumn }
|
|
227
|
-
} catch (error) {
|
|
228
|
-
throw this.mapError(error, `getFacets(${seed.slug})`)
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async existsSlug(seed: Seed, slug: string, excludeId?: string): Promise<boolean> {
|
|
233
|
-
try {
|
|
234
|
-
const tableName = this.getTableName(seed.slug)
|
|
235
|
-
let sql = `SELECT 1 FROM ${tableName} WHERE slug = ?`
|
|
236
|
-
const queryBindings: any[] = [slug]
|
|
237
|
-
|
|
238
|
-
if (excludeId) {
|
|
239
|
-
sql += ` AND id != ?`
|
|
240
|
-
queryBindings.push(excludeId)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const entryExistsResult = await this.database.prepare(sql).bind(...queryBindings).first()
|
|
244
|
-
return entryExistsResult !== null
|
|
245
|
-
} catch (error) {
|
|
246
|
-
throw this.mapError(error, `existsSlug(${seed.slug}, ${slug})`)
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
private buildCreateMainStmt(
|
|
252
|
-
seed: Seed,
|
|
253
|
-
id: string,
|
|
254
|
-
slug: string,
|
|
255
|
-
status: string,
|
|
256
|
-
data: Record<string, any>
|
|
257
|
-
): D1PreparedStatement {
|
|
258
|
-
const tableName = this.getTableName(seed.slug)
|
|
259
|
-
const columnNames = ['id', 'slug', 'status']
|
|
260
|
-
const placeholders = ['?', '?', '?']
|
|
261
|
-
const queryBindings: any[] = [id, slug, status]
|
|
262
|
-
|
|
263
|
-
const mRelAliases = new Set(multiRelBranches(seed).map(b => b.alias))
|
|
264
|
-
|
|
265
|
-
for (const branch of seed.branches) {
|
|
266
|
-
if (mRelAliases.has(branch.alias)) continue
|
|
267
|
-
if (Object.hasOwn(data, branch.alias)) {
|
|
268
|
-
columnNames.push(branch.alias)
|
|
269
|
-
placeholders.push('?')
|
|
270
|
-
queryBindings.push(serializeForDb(branch, data[branch.alias]))
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const mainSql = `INSERT INTO ${tableName} (${columnNames.join(', ')}) VALUES (${placeholders.join(', ')})`
|
|
275
|
-
return this.database.prepare(mainSql).bind(...queryBindings)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
private buildUpdateMainStmt(
|
|
279
|
-
seed: Seed,
|
|
280
|
-
id: string,
|
|
281
|
-
data: Record<string, any>,
|
|
282
|
-
status?: string
|
|
283
|
-
): { stmt: D1PreparedStatement | null; junctionUpdates: any[] } {
|
|
284
|
-
const tableName = this.getTableName(seed.slug)
|
|
285
|
-
const updateClauses: string[] = []
|
|
286
|
-
const queryBindings: any[] = []
|
|
287
|
-
|
|
288
|
-
const mRelBranches = multiRelBranches(seed)
|
|
289
|
-
const mRelAliases = new Set(mRelBranches.map(b => b.alias))
|
|
290
|
-
|
|
291
|
-
if (status) {
|
|
292
|
-
updateClauses.push('status = ?')
|
|
293
|
-
queryBindings.push(status)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
for (const branch of seed.branches) {
|
|
297
|
-
if (mRelAliases.has(branch.alias)) continue
|
|
298
|
-
if (Object.hasOwn(data, branch.alias)) {
|
|
299
|
-
updateClauses.push(`${branch.alias} = ?`)
|
|
300
|
-
queryBindings.push(serializeForDb(branch, data[branch.alias]))
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const junctionUpdates = mRelBranches.filter(b => Object.hasOwn(data, b.alias))
|
|
305
|
-
|
|
306
|
-
if (updateClauses.length === 0 && junctionUpdates.length === 0) {
|
|
307
|
-
return { stmt: null, junctionUpdates: [] }
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
updateClauses.push('updated_at = (unixepoch())')
|
|
311
|
-
|
|
312
|
-
const mainSql = `UPDATE ${tableName} SET ${updateClauses.join(', ')} WHERE id = ?`
|
|
313
|
-
queryBindings.push(id)
|
|
314
|
-
|
|
315
|
-
return {
|
|
316
|
-
stmt: this.database.prepare(mainSql).bind(...queryBindings),
|
|
317
|
-
junctionUpdates
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
private buildJunctionInserts(seedSlug: string, parentId: string, branchAlias: string, targetIds: string[]): D1PreparedStatement[] {
|
|
322
|
-
const jt = jTable(seedSlug, branchAlias)
|
|
323
|
-
return targetIds.map((targetId, i) =>
|
|
324
|
-
this.database
|
|
325
|
-
.prepare(`INSERT INTO ${jt} (parent_id, target_id, position) VALUES (?, ?, ?)`)
|
|
326
|
-
.bind(parentId, targetId, i),
|
|
327
|
-
)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
private buildDraftJunctionInserts(seedSlug: string, entryId: string, branchAlias: string, targetIds: string[]): D1PreparedStatement[] {
|
|
331
|
-
const jdt = jDraftTable(seedSlug, branchAlias)
|
|
332
|
-
return targetIds.map((targetId, i) =>
|
|
333
|
-
this.database
|
|
334
|
-
.prepare(`INSERT INTO ${jdt} (entry_id, target_id, position) VALUES (?, ?, ?)`)
|
|
335
|
-
.bind(entryId, targetId, i),
|
|
336
|
-
)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
private async validatePublishDraftRelations(seed: Seed, draftRow: Record<string, unknown>, entryId: string) {
|
|
340
|
-
for (const branch of singleRelBranches(seed)) {
|
|
341
|
-
const value = draftRow[branch.alias]
|
|
342
|
-
if (value == null) continue
|
|
343
|
-
const exists = await this.database
|
|
344
|
-
.prepare(`SELECT 1 FROM content_${branch.targetSeed} WHERE id = ? LIMIT 1`)
|
|
345
|
-
.bind(value)
|
|
346
|
-
.first()
|
|
347
|
-
if (!exists) {
|
|
348
|
-
throw new RelationTargetNotFoundError({
|
|
349
|
-
alias: branch.alias,
|
|
350
|
-
targetSeed: branch.targetSeed!,
|
|
351
|
-
value: typeof value === 'string' ? value : (JSON.stringify(value) ?? 'undefined'),
|
|
352
|
-
})
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const mRelBranches = multiRelBranches(seed)
|
|
357
|
-
const mRelDraftIds = new Map<string, string[]>()
|
|
358
|
-
|
|
359
|
-
for (const branch of mRelBranches) {
|
|
360
|
-
const jdt = jDraftTable(seed.slug, branch.alias)
|
|
361
|
-
const rows = await this.database
|
|
362
|
-
.prepare(`SELECT target_id FROM ${jdt} WHERE entry_id = ? ORDER BY position ASC`)
|
|
363
|
-
.bind(entryId)
|
|
364
|
-
.all()
|
|
365
|
-
const targetIds = (rows.results ?? []).map((r: any) => String(r.target_id))
|
|
366
|
-
|
|
367
|
-
for (const targetId of targetIds) {
|
|
368
|
-
const exists = await this.database
|
|
369
|
-
.prepare(`SELECT 1 FROM content_${branch.targetSeed} WHERE id = ? LIMIT 1`)
|
|
370
|
-
.bind(targetId)
|
|
371
|
-
.first()
|
|
372
|
-
if (!exists) {
|
|
373
|
-
throw new RelationTargetNotFoundError({
|
|
374
|
-
alias: branch.alias,
|
|
375
|
-
targetSeed: branch.targetSeed!,
|
|
376
|
-
value: targetId,
|
|
377
|
-
})
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
mRelDraftIds.set(branch.alias, targetIds)
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
return { mRelBranches, mRelDraftIds }
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
private getBulkArrayUpdateStmts(seedSlug: string, id: string, alias: string, update: BulkFieldUpdate): D1PreparedStatement[] {
|
|
387
|
-
const jt = jTable(seedSlug, alias)
|
|
388
|
-
const stmts: D1PreparedStatement[] = []
|
|
389
|
-
|
|
390
|
-
if (update.kind === 'array_replace') {
|
|
391
|
-
const deleteStmt = this.database.prepare(`DELETE FROM ${jt} WHERE parent_id = ?`).bind(id)
|
|
392
|
-
const insertStmts = update.value.map((v, i) =>
|
|
393
|
-
this.database.prepare(`INSERT INTO ${jt} (parent_id, target_id, position) VALUES (?, ?, ?)`).bind(id, v, i)
|
|
394
|
-
)
|
|
395
|
-
stmts.push(deleteStmt, ...insertStmts)
|
|
396
|
-
} else if (update.kind === 'array_add') {
|
|
397
|
-
for (const targetId of update.value) {
|
|
398
|
-
stmts.push(
|
|
399
|
-
this.database
|
|
400
|
-
.prepare(
|
|
401
|
-
`INSERT OR IGNORE INTO ${jt} (parent_id, target_id, position) VALUES (?, ?, (SELECT COALESCE(MAX(position), -1) + 1 FROM ${jt} WHERE parent_id = ?))`,
|
|
402
|
-
)
|
|
403
|
-
.bind(id, targetId, id),
|
|
404
|
-
)
|
|
405
|
-
}
|
|
406
|
-
} else if (update.kind === 'array_remove' && update.value.length > 0) {
|
|
407
|
-
const placeholders = update.value.map(() => '?').join(', ')
|
|
408
|
-
stmts.push(
|
|
409
|
-
this.database
|
|
410
|
-
.prepare(`DELETE FROM ${jt} WHERE parent_id = ? AND target_id IN (${placeholders})`)
|
|
411
|
-
.bind(id, ...update.value),
|
|
412
|
-
)
|
|
413
|
-
}
|
|
414
|
-
return stmts
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
private async processBulkUpdateSingle(
|
|
418
|
-
seedSlug: string,
|
|
419
|
-
id: string,
|
|
420
|
-
fields: Record<string, BulkFieldUpdate>,
|
|
421
|
-
tableName: string
|
|
422
|
-
): Promise<void> {
|
|
423
|
-
const stmts: D1PreparedStatement[] = []
|
|
424
|
-
|
|
425
|
-
const setClauses: string[] = ['updated_at = (unixepoch())']
|
|
426
|
-
const setBindings: unknown[] = []
|
|
427
|
-
|
|
428
|
-
for (const [alias, update] of Object.entries(fields)) {
|
|
429
|
-
if (update.kind === 'set') {
|
|
430
|
-
setClauses.unshift(`${alias} = ?`)
|
|
431
|
-
setBindings.push(update.value)
|
|
432
|
-
} else {
|
|
433
|
-
stmts.push(...this.getBulkArrayUpdateStmts(seedSlug, id, alias, update))
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
stmts.unshift(
|
|
438
|
-
this.database
|
|
439
|
-
.prepare(`UPDATE ${tableName} SET ${setClauses.join(', ')} WHERE id = ?`)
|
|
440
|
-
.bind(...setBindings, id),
|
|
441
|
-
)
|
|
442
|
-
|
|
443
|
-
const results = await this.database.batch(stmts)
|
|
444
|
-
if ((results[0].meta?.changes ?? 0) === 0) {
|
|
445
|
-
throw new Error('not-found')
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
private async processBulkUpdateChunk(
|
|
450
|
-
seedSlug: string,
|
|
451
|
-
chunk: string[],
|
|
452
|
-
fields: Record<string, BulkFieldUpdate>,
|
|
453
|
-
tableName: string
|
|
454
|
-
): Promise<{ updated: number; failed: Array<{ id: string; reason: string }> }> {
|
|
455
|
-
let updated = 0
|
|
456
|
-
const failed: Array<{ id: string; reason: string }> = []
|
|
457
|
-
|
|
458
|
-
for (const id of chunk) {
|
|
459
|
-
try {
|
|
460
|
-
await this.processBulkUpdateSingle(seedSlug, id, fields, tableName)
|
|
461
|
-
updated++
|
|
462
|
-
} catch (err) {
|
|
463
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
464
|
-
if (msg === 'not-found') {
|
|
465
|
-
failed.push({ id, reason: 'not-found' })
|
|
466
|
-
} else {
|
|
467
|
-
const isFk = /FOREIGN KEY constraint failed/i.test(msg)
|
|
468
|
-
failed.push({ id, reason: isFk ? `relation-target-not-found:${id}` : `error:${msg}` })
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
return { updated, failed }
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
async create(
|
|
476
|
-
seed: Seed,
|
|
477
|
-
id: string,
|
|
478
|
-
slug: string,
|
|
479
|
-
status: string,
|
|
480
|
-
data: Record<string, any>
|
|
481
|
-
): Promise<void> {
|
|
482
|
-
try {
|
|
483
|
-
if (await this.existsSlug(seed, slug)) {
|
|
484
|
-
throw new SlugConflictError(`Slug "${slug}" already exists for ${seed.slug}`)
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
const batchStmts: D1PreparedStatement[] = [
|
|
488
|
-
this.buildCreateMainStmt(seed, id, slug, status, data),
|
|
489
|
-
]
|
|
490
|
-
|
|
491
|
-
for (const branch of multiRelBranches(seed)) {
|
|
492
|
-
const value = data[branch.alias]
|
|
493
|
-
if (!Array.isArray(value) || value.length === 0) continue
|
|
494
|
-
batchStmts.push(...this.buildJunctionInserts(seed.slug, id, branch.alias, value))
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
if (batchStmts.length === 1) {
|
|
498
|
-
await batchStmts[0].run()
|
|
499
|
-
} else {
|
|
500
|
-
await this.database.batch(batchStmts)
|
|
501
|
-
}
|
|
502
|
-
} catch (error) {
|
|
503
|
-
if (error instanceof SlugConflictError) throw error
|
|
504
|
-
throw this.mapError(error, `create(${seed.slug})`)
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
async update(
|
|
509
|
-
seed: Seed,
|
|
510
|
-
id: string,
|
|
511
|
-
data: Record<string, any>,
|
|
512
|
-
status?: string
|
|
513
|
-
): Promise<void> {
|
|
514
|
-
try {
|
|
515
|
-
const { stmt, junctionUpdates } = this.buildUpdateMainStmt(seed, id, data, status)
|
|
516
|
-
|
|
517
|
-
if (!stmt && junctionUpdates.length === 0) return
|
|
518
|
-
|
|
519
|
-
const batchStmts: D1PreparedStatement[] = []
|
|
520
|
-
if (stmt) batchStmts.push(stmt)
|
|
521
|
-
|
|
522
|
-
for (const branch of junctionUpdates) {
|
|
523
|
-
const jt = jTable(seed.slug, branch.alias)
|
|
524
|
-
const value = (data[branch.alias] ?? []) as string[]
|
|
525
|
-
const deleteStmt = this.database.prepare(`DELETE FROM ${jt} WHERE parent_id = ?`).bind(id)
|
|
526
|
-
batchStmts.push(deleteStmt, ...this.buildJunctionInserts(seed.slug, id, branch.alias, value))
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
if (batchStmts.length === 1) {
|
|
530
|
-
const updateResult = await batchStmts[0].run()
|
|
531
|
-
if (updateResult.meta.changes === 0) {
|
|
532
|
-
throw new EntryNotFoundError(`Entry ${id} not found in ${seed.slug}`)
|
|
533
|
-
}
|
|
534
|
-
} else if (batchStmts.length > 1) {
|
|
535
|
-
const results = await this.database.batch(batchStmts)
|
|
536
|
-
if (stmt && (results[0].meta?.changes ?? 0) === 0) {
|
|
537
|
-
throw new EntryNotFoundError(`Entry ${id} not found in ${seed.slug}`)
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
} catch (error) {
|
|
541
|
-
if (error instanceof EntryNotFoundError) throw error
|
|
542
|
-
throw this.mapError(error, `update(${seed.slug}, ${id})`)
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
async delete(seed: Seed, id: string): Promise<{ row: Record<string, any> }> {
|
|
547
|
-
try {
|
|
548
|
-
const tableName = this.getTableName(seed.slug)
|
|
549
|
-
|
|
550
|
-
const entryRow = await this.database
|
|
551
|
-
.prepare(`SELECT * FROM ${tableName} WHERE id = ?`)
|
|
552
|
-
.bind(id)
|
|
553
|
-
.first()
|
|
554
|
-
|
|
555
|
-
if (!entryRow) {
|
|
556
|
-
throw new EntryNotFoundError(`Entry ${id} not found in ${seed.slug}`)
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
await this.database.prepare(`DELETE FROM ${tableName} WHERE id = ?`).bind(id).run()
|
|
560
|
-
|
|
561
|
-
return { row: this.rowToData(seed, entryRow) }
|
|
562
|
-
} catch (error) {
|
|
563
|
-
if (error instanceof EntryNotFoundError) throw error
|
|
564
|
-
throw this.mapError(error, `delete(${seed.slug}, ${id})`)
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
async saveDraft(seed: Seed, entryId: string, data: Record<string, any>): Promise<void> {
|
|
569
|
-
try {
|
|
570
|
-
if (!seed.allowDrafts) {
|
|
571
|
-
throw new RepositoryError(`Drafts not allowed for ${seed.slug}`)
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
const draftTableName = this.getTableName(seed.slug, true)
|
|
575
|
-
const mRelBranches = multiRelBranches(seed)
|
|
576
|
-
const mRelAliases = new Set(mRelBranches.map(b => b.alias))
|
|
577
|
-
|
|
578
|
-
const columnNames = ['entry_id']
|
|
579
|
-
const placeholders = ['?']
|
|
580
|
-
const queryBindings: any[] = [entryId]
|
|
581
|
-
const updateClauses: string[] = []
|
|
582
|
-
|
|
583
|
-
for (const branch of seed.branches) {
|
|
584
|
-
if (mRelAliases.has(branch.alias)) continue
|
|
585
|
-
if (Object.hasOwn(data, branch.alias)) {
|
|
586
|
-
const serializedValue = serializeForDb(branch, data[branch.alias])
|
|
587
|
-
columnNames.push(branch.alias)
|
|
588
|
-
placeholders.push('?')
|
|
589
|
-
queryBindings.push(serializedValue)
|
|
590
|
-
updateClauses.push(`${branch.alias} = EXCLUDED.${branch.alias}`)
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
updateClauses.push('updated_at = (unixepoch())')
|
|
595
|
-
|
|
596
|
-
const sql = `
|
|
597
|
-
INSERT INTO ${draftTableName} (${columnNames.join(', ')})
|
|
598
|
-
VALUES (${placeholders.join(', ')})
|
|
599
|
-
ON CONFLICT(entry_id) DO UPDATE SET ${updateClauses.join(', ')}
|
|
600
|
-
`
|
|
601
|
-
|
|
602
|
-
const junctionUpdates = mRelBranches.filter(b => Object.hasOwn(data, b.alias))
|
|
603
|
-
|
|
604
|
-
if (junctionUpdates.length === 0) {
|
|
605
|
-
await this.database.prepare(sql).bind(...queryBindings).run()
|
|
606
|
-
return
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
const batchStmts: D1PreparedStatement[] = [
|
|
610
|
-
this.database.prepare(sql).bind(...queryBindings),
|
|
611
|
-
]
|
|
612
|
-
|
|
613
|
-
for (const branch of junctionUpdates) {
|
|
614
|
-
const jdt = jDraftTable(seed.slug, branch.alias)
|
|
615
|
-
const value = (data[branch.alias] ?? []) as string[]
|
|
616
|
-
batchStmts.push(
|
|
617
|
-
this.database.prepare(`DELETE FROM ${jdt} WHERE entry_id = ?`).bind(entryId),
|
|
618
|
-
)
|
|
619
|
-
for (let i = 0; i < value.length; i++) {
|
|
620
|
-
batchStmts.push(
|
|
621
|
-
this.database
|
|
622
|
-
.prepare(`INSERT INTO ${jdt} (entry_id, target_id, position) VALUES (?, ?, ?)`)
|
|
623
|
-
.bind(entryId, value[i], i),
|
|
624
|
-
)
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
await this.database.batch(batchStmts)
|
|
629
|
-
} catch (error) {
|
|
630
|
-
throw this.mapError(error, `saveDraft(${seed.slug}, ${entryId})`)
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
async getDraft(seed: Seed, entryId: string): Promise<Record<string, any> | null> {
|
|
635
|
-
try {
|
|
636
|
-
if (!seed.allowDrafts) return null
|
|
637
|
-
|
|
638
|
-
const draftTableName = this.getTableName(seed.slug, true)
|
|
639
|
-
const draftRow = await this.database
|
|
640
|
-
.prepare(`SELECT * FROM ${draftTableName} WHERE entry_id = ?`)
|
|
641
|
-
.bind(entryId)
|
|
642
|
-
.first()
|
|
643
|
-
|
|
644
|
-
if (!draftRow) return null
|
|
645
|
-
|
|
646
|
-
const mRelBranches = multiRelBranches(seed)
|
|
647
|
-
const mRelAliases = new Set(mRelBranches.map(b => b.alias))
|
|
648
|
-
|
|
649
|
-
const draftData: Record<string, any> = {}
|
|
650
|
-
for (const branch of seed.branches) {
|
|
651
|
-
if (mRelAliases.has(branch.alias)) continue
|
|
652
|
-
if (draftRow[branch.alias] !== null) {
|
|
653
|
-
draftData[branch.alias] = deserializeFromDb(branch, draftRow[branch.alias])
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// Fetch multi-relation draft arrays from junction draft tables
|
|
658
|
-
if (mRelBranches.length > 0) {
|
|
659
|
-
const stmts = mRelBranches.map(b =>
|
|
660
|
-
this.database
|
|
661
|
-
.prepare(`SELECT target_id FROM ${jDraftTable(seed.slug, b.alias)} WHERE entry_id = ? ORDER BY position ASC`)
|
|
662
|
-
.bind(entryId),
|
|
663
|
-
)
|
|
664
|
-
const results = await this.database.batch(stmts)
|
|
665
|
-
for (let i = 0; i < mRelBranches.length; i++) {
|
|
666
|
-
const ids = (results[i].results ?? []).map((r: any) => r.target_id as string)
|
|
667
|
-
if (ids.length > 0) {
|
|
668
|
-
draftData[mRelBranches[i].alias] = ids
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
return draftData
|
|
674
|
-
} catch (error) {
|
|
675
|
-
throw this.mapError(error, `getDraft(${seed.slug}, ${entryId})`)
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
async hasDraft(seed: Seed, entryId: string): Promise<boolean> {
|
|
680
|
-
try {
|
|
681
|
-
if (!seed.allowDrafts) return false
|
|
682
|
-
const draftTableName = this.getTableName(seed.slug, true)
|
|
683
|
-
const draftExistsResult = await this.database
|
|
684
|
-
.prepare(`SELECT 1 FROM ${draftTableName} WHERE entry_id = ? LIMIT 1`)
|
|
685
|
-
.bind(entryId)
|
|
686
|
-
.first()
|
|
687
|
-
return draftExistsResult !== null
|
|
688
|
-
} catch (error) {
|
|
689
|
-
throw this.mapError(error, `hasDraft(${seed.slug}, ${entryId})`)
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
async publishDraft(seed: Seed, entryId: string): Promise<void> {
|
|
694
|
-
try {
|
|
695
|
-
if (!seed.allowDrafts) return
|
|
696
|
-
|
|
697
|
-
const draftTableName = this.getTableName(seed.slug, true)
|
|
698
|
-
const liveTableName = this.getTableName(seed.slug)
|
|
699
|
-
|
|
700
|
-
const draftRow = await this.database
|
|
701
|
-
.prepare(`SELECT * FROM ${draftTableName} WHERE entry_id = ?`)
|
|
702
|
-
.bind(entryId)
|
|
703
|
-
.first<Record<string, unknown>>()
|
|
704
|
-
|
|
705
|
-
if (!draftRow) {
|
|
706
|
-
throw new EntryNotFoundError(`No draft found for ${entryId} in ${seed.slug}`)
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
const { mRelBranches, mRelDraftIds } = await this.validatePublishDraftRelations(seed, draftRow, entryId)
|
|
710
|
-
|
|
711
|
-
const mRelAliases = new Set(mRelBranches.map(b => b.alias))
|
|
712
|
-
const updateClauses: string[] = []
|
|
713
|
-
const queryBindings: any[] = []
|
|
714
|
-
|
|
715
|
-
for (const branch of seed.branches) {
|
|
716
|
-
if (mRelAliases.has(branch.alias)) continue
|
|
717
|
-
if (draftRow[branch.alias] !== null) {
|
|
718
|
-
updateClauses.push(`${branch.alias} = ?`)
|
|
719
|
-
queryBindings.push(draftRow[branch.alias])
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
updateClauses.push('updated_at = (unixepoch())')
|
|
724
|
-
const updateSql = `UPDATE ${liveTableName} SET ${updateClauses.join(', ')} WHERE id = ?`
|
|
725
|
-
queryBindings.push(entryId)
|
|
726
|
-
|
|
727
|
-
const batchStmts: D1PreparedStatement[] = [
|
|
728
|
-
this.database.prepare(updateSql).bind(...queryBindings),
|
|
729
|
-
this.database.prepare(`DELETE FROM ${draftTableName} WHERE entry_id = ?`).bind(entryId),
|
|
730
|
-
]
|
|
731
|
-
|
|
732
|
-
for (const branch of mRelBranches) {
|
|
733
|
-
const lt = jTable(seed.slug, branch.alias)
|
|
734
|
-
const jdt = jDraftTable(seed.slug, branch.alias)
|
|
735
|
-
const targetIds = mRelDraftIds.get(branch.alias) ?? []
|
|
736
|
-
|
|
737
|
-
batchStmts.push(
|
|
738
|
-
this.database.prepare(`DELETE FROM ${lt} WHERE parent_id = ?`).bind(entryId),
|
|
739
|
-
this.database.prepare(`DELETE FROM ${jdt} WHERE entry_id = ?`).bind(entryId),
|
|
740
|
-
...this.buildJunctionInserts(seed.slug, entryId, branch.alias, targetIds)
|
|
741
|
-
)
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
await this.database.batch(batchStmts)
|
|
745
|
-
} catch (error) {
|
|
746
|
-
if (error instanceof EntryNotFoundError) throw error
|
|
747
|
-
if (error instanceof RelationTargetNotFoundError) throw error
|
|
748
|
-
throw this.mapError(error, `publishDraft(${seed.slug}, ${entryId})`)
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
async bulkUpdate(
|
|
753
|
-
seedSlug: string,
|
|
754
|
-
ids: string[],
|
|
755
|
-
fields: Record<string, BulkFieldUpdate>,
|
|
756
|
-
): Promise<{ updated: number; failed: Array<{ id: string; reason: string }> }> {
|
|
757
|
-
const CHUNK = 50
|
|
758
|
-
const tableName = this.getTableName(seedSlug)
|
|
759
|
-
let updated = 0
|
|
760
|
-
const failed: Array<{ id: string; reason: string }> = []
|
|
761
|
-
|
|
762
|
-
for (let offset = 0; offset < ids.length; offset += CHUNK) {
|
|
763
|
-
const chunk = ids.slice(offset, offset + CHUNK)
|
|
764
|
-
const res = await this.processBulkUpdateChunk(seedSlug, chunk, fields, tableName)
|
|
765
|
-
updated += res.updated
|
|
766
|
-
failed.push(...res.failed)
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
return { updated, failed }
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
async deleteDraft(seed: Seed, entryId: string): Promise<void> {
|
|
773
|
-
try {
|
|
774
|
-
if (!seed.allowDrafts) return
|
|
775
|
-
const draftTableName = this.getTableName(seed.slug, true)
|
|
776
|
-
await this.database.prepare(`DELETE FROM ${draftTableName} WHERE entry_id = ?`).bind(entryId).run()
|
|
777
|
-
} catch (error) {
|
|
778
|
-
throw this.mapError(error, `deleteDraft(${seed.slug}, ${entryId})`)
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
async findPendingDrafts(seeds: Seed[]): Promise<DraftSummary[]> {
|
|
783
|
-
try {
|
|
784
|
-
if (seeds.length === 0) return []
|
|
785
|
-
|
|
786
|
-
const unionSelects: string[] = []
|
|
787
|
-
const bindings: (string | number)[] = []
|
|
788
|
-
|
|
789
|
-
for (const seed of seeds) {
|
|
790
|
-
const draftTable = `content_${seed.slug}_drafts`
|
|
791
|
-
const liveTable = `content_${seed.slug}`
|
|
792
|
-
const titleCol = seed.displayNameAlias
|
|
793
|
-
const seedLabel = seed.labelPlural ?? seed.label
|
|
794
|
-
|
|
795
|
-
unionSelects.push(`
|
|
796
|
-
SELECT
|
|
797
|
-
? AS seed_slug,
|
|
798
|
-
? AS seed_label,
|
|
799
|
-
d.entry_id AS id,
|
|
800
|
-
d.updated_at AS updated_at,
|
|
801
|
-
COALESCE(d.${titleCol}, l.${titleCol}) AS title
|
|
802
|
-
FROM ${draftTable} d
|
|
803
|
-
LEFT JOIN ${liveTable} l ON l.id = d.entry_id
|
|
804
|
-
`)
|
|
805
|
-
bindings.push(seed.slug, seedLabel)
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
const sql = `
|
|
809
|
-
WITH all_drafts AS (
|
|
810
|
-
${unionSelects.join('\nUNION ALL\n')}
|
|
811
|
-
)
|
|
812
|
-
SELECT
|
|
813
|
-
ad.seed_slug AS seedSlug,
|
|
814
|
-
ad.seed_label AS seedLabel,
|
|
815
|
-
ad.id AS id,
|
|
816
|
-
ad.updated_at AS updatedAt,
|
|
817
|
-
ad.title AS title,
|
|
818
|
-
al.user_name AS lastName,
|
|
819
|
-
al.user_email AS lastEmail
|
|
820
|
-
FROM all_drafts ad
|
|
821
|
-
LEFT JOIN activity_logs al
|
|
822
|
-
ON al.id = (
|
|
823
|
-
SELECT id FROM activity_logs
|
|
824
|
-
WHERE entity_id = ad.id
|
|
825
|
-
AND entity_slug = ad.seed_slug
|
|
826
|
-
AND action = 'update'
|
|
827
|
-
AND json_extract(details, '$.note') = 'draft saved'
|
|
828
|
-
ORDER BY created_at DESC
|
|
829
|
-
LIMIT 1
|
|
830
|
-
)
|
|
831
|
-
ORDER BY ad.updated_at DESC
|
|
832
|
-
`
|
|
833
|
-
|
|
834
|
-
const { results } = await this.database.prepare(sql).bind(...bindings).all()
|
|
835
|
-
|
|
836
|
-
return (results ?? []).map((row: any): DraftSummary => ({
|
|
837
|
-
id: String(row.id),
|
|
838
|
-
seedSlug: String(row.seedSlug),
|
|
839
|
-
seedLabel: String(row.seedLabel),
|
|
840
|
-
title: row.title != null && String(row.title).trim() ? String(row.title) : String(row.id),
|
|
841
|
-
updatedAt: Number(row.updatedAt),
|
|
842
|
-
lastModifiedBy: {
|
|
843
|
-
name: row.lastName ?? null,
|
|
844
|
-
email: row.lastEmail ?? '',
|
|
845
|
-
},
|
|
846
|
-
}))
|
|
847
|
-
} catch (error) {
|
|
848
|
-
throw this.mapError(error, 'findPendingDrafts')
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|