@nextsparkjs/core 0.1.0-beta.91 → 0.1.0-beta.94
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/dist/components/dashboard/block-editor/array-field.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/array-field.js +55 -3
- package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/dynamic-form.js +82 -2
- package/dist/components/dashboard/navigation/DynamicNavigation.d.ts.map +1 -1
- package/dist/components/dashboard/navigation/DynamicNavigation.js +7 -1
- package/dist/components/devtools/scheduled-actions/actions-table.d.ts +1 -0
- package/dist/components/devtools/scheduled-actions/actions-table.d.ts.map +1 -1
- package/dist/components/devtools/scheduled-actions/actions-table.js +182 -46
- package/dist/components/devtools/scheduled-actions/types.d.ts +1 -0
- package/dist/components/devtools/scheduled-actions/types.d.ts.map +1 -1
- package/dist/components/media/MediaCard.d.ts +23 -0
- package/dist/components/media/MediaCard.d.ts.map +1 -0
- package/dist/components/media/MediaCard.js +154 -0
- package/dist/components/media/MediaDetailPanel.d.ts +17 -0
- package/dist/components/media/MediaDetailPanel.d.ts.map +1 -0
- package/dist/components/media/MediaDetailPanel.js +331 -0
- package/dist/components/media/MediaGrid.d.ts +26 -0
- package/dist/components/media/MediaGrid.d.ts.map +1 -0
- package/dist/components/media/MediaGrid.js +77 -0
- package/dist/components/media/MediaLibrary.d.ts +20 -0
- package/dist/components/media/MediaLibrary.d.ts.map +1 -0
- package/dist/components/media/MediaLibrary.js +229 -0
- package/dist/components/media/MediaList.d.ts +24 -0
- package/dist/components/media/MediaList.d.ts.map +1 -0
- package/dist/components/media/MediaList.js +181 -0
- package/dist/components/media/MediaSelector.d.ts +19 -0
- package/dist/components/media/MediaSelector.d.ts.map +1 -0
- package/dist/components/media/MediaSelector.js +145 -0
- package/dist/components/media/MediaTagFilter.d.ts +16 -0
- package/dist/components/media/MediaTagFilter.d.ts.map +1 -0
- package/dist/components/media/MediaTagFilter.js +122 -0
- package/dist/components/media/MediaToolbar.d.ts +25 -0
- package/dist/components/media/MediaToolbar.d.ts.map +1 -0
- package/dist/components/media/MediaToolbar.js +136 -0
- package/dist/components/media/MediaUploadZone.d.ts +19 -0
- package/dist/components/media/MediaUploadZone.d.ts.map +1 -0
- package/dist/components/media/MediaUploadZone.js +248 -0
- package/dist/components/media/index.d.ts +15 -0
- package/dist/components/media/index.d.ts.map +1 -0
- package/dist/components/media/index.js +20 -0
- package/dist/contexts/TeamContext.js +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/useEnsureUserMetadata.d.ts +4 -0
- package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
- package/dist/hooks/useEnsureUserMetadata.js +85 -60
- package/dist/hooks/useEntityMutations.d.ts.map +1 -1
- package/dist/hooks/useEntityMutations.js +5 -9
- package/dist/hooks/useMedia.d.ts +56 -0
- package/dist/hooks/useMedia.d.ts.map +1 -0
- package/dist/hooks/useMedia.js +181 -0
- package/dist/hooks/useMediaUpload.d.ts +27 -0
- package/dist/hooks/useMediaUpload.d.ts.map +1 -0
- package/dist/hooks/useMediaUpload.js +36 -0
- package/dist/hooks/useUserSettings.d.ts +5 -4
- package/dist/hooks/useUserSettings.d.ts.map +1 -1
- package/dist/hooks/useUserSettings.js +42 -40
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/lib/api/auth/dual-auth.d.ts +6 -2
- package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
- package/dist/lib/api/auth/dual-auth.js +5 -9
- package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +3 -3
- package/dist/lib/config/app.config.d.ts.map +1 -1
- package/dist/lib/config/app.config.js +37 -0
- package/dist/lib/config/config-sync.d.ts +1 -0
- package/dist/lib/config/config-sync.d.ts.map +1 -1
- package/dist/lib/config/config-sync.js +2 -0
- package/dist/lib/config/types.d.ts +29 -0
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/media/schemas.d.ts +39 -0
- package/dist/lib/media/schemas.d.ts.map +1 -0
- package/dist/lib/media/schemas.js +32 -0
- package/dist/lib/media/types.d.ts +69 -0
- package/dist/lib/media/types.d.ts.map +1 -0
- package/dist/lib/media/types.js +0 -0
- package/dist/lib/media/utils.d.ts +26 -0
- package/dist/lib/media/utils.d.ts.map +1 -0
- package/dist/lib/media/utils.js +33 -0
- package/dist/lib/rate-limit-redis.d.ts.map +1 -1
- package/dist/lib/rate-limit-redis.js +13 -4
- package/dist/lib/scheduled-actions/initializer.d.ts +6 -3
- package/dist/lib/scheduled-actions/initializer.d.ts.map +1 -1
- package/dist/lib/scheduled-actions/initializer.js +11 -6
- package/dist/lib/scheduled-actions/processor.d.ts +20 -4
- package/dist/lib/scheduled-actions/processor.d.ts.map +1 -1
- package/dist/lib/scheduled-actions/processor.js +128 -34
- package/dist/lib/scheduled-actions/registry.d.ts +3 -0
- package/dist/lib/scheduled-actions/registry.d.ts.map +1 -1
- package/dist/lib/scheduled-actions/registry.js +2 -1
- package/dist/lib/scheduled-actions/scheduler.d.ts +1 -1
- package/dist/lib/scheduled-actions/scheduler.d.ts.map +1 -1
- package/dist/lib/scheduled-actions/scheduler.js +76 -38
- package/dist/lib/scheduled-actions/types.d.ts +73 -0
- package/dist/lib/scheduled-actions/types.d.ts.map +1 -1
- package/dist/lib/selectors/core-selectors.d.ts +102 -0
- package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
- package/dist/lib/selectors/core-selectors.js +3 -1
- package/dist/lib/selectors/domains/block-editor.selectors.d.ts +8 -0
- package/dist/lib/selectors/domains/block-editor.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/block-editor.selectors.js +9 -0
- package/dist/lib/selectors/domains/devtools.selectors.d.ts +6 -0
- package/dist/lib/selectors/domains/devtools.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/devtools.selectors.js +6 -0
- package/dist/lib/selectors/domains/index.d.ts +1 -0
- package/dist/lib/selectors/domains/index.d.ts.map +1 -1
- package/dist/lib/selectors/domains/index.js +2 -0
- package/dist/lib/selectors/domains/media.selectors.d.ts +96 -0
- package/dist/lib/selectors/domains/media.selectors.d.ts.map +1 -0
- package/dist/lib/selectors/domains/media.selectors.js +103 -0
- package/dist/lib/selectors/selectors.d.ts +204 -0
- package/dist/lib/selectors/selectors.d.ts.map +1 -1
- package/dist/lib/services/index.d.ts +2 -0
- package/dist/lib/services/index.d.ts.map +1 -1
- package/dist/lib/services/index.js +2 -0
- package/dist/lib/services/media.service.d.ts +158 -0
- package/dist/lib/services/media.service.d.ts.map +1 -0
- package/dist/lib/services/media.service.js +410 -0
- package/dist/messages/de/devtools.json +16 -0
- package/dist/messages/de/index.d.ts +16 -0
- package/dist/messages/de/index.d.ts.map +1 -1
- package/dist/messages/en/admin.json +4 -1
- package/dist/messages/en/devtools.json +16 -0
- package/dist/messages/en/index.d.ts +167 -0
- package/dist/messages/en/index.d.ts.map +1 -1
- package/dist/messages/en/index.js +2 -0
- package/dist/messages/en/index.ts +2 -0
- package/dist/messages/en/media.json +147 -0
- package/dist/messages/en/navigation.json +1 -0
- package/dist/messages/es/admin.json +4 -1
- package/dist/messages/es/devtools.json +16 -0
- package/dist/messages/es/index.d.ts +167 -0
- package/dist/messages/es/index.d.ts.map +1 -1
- package/dist/messages/es/index.js +2 -0
- package/dist/messages/es/index.ts +2 -0
- package/dist/messages/es/media.json +147 -0
- package/dist/messages/es/navigation.json +1 -0
- package/dist/messages/fr/devtools.json +16 -0
- package/dist/messages/fr/index.d.ts +16 -0
- package/dist/messages/fr/index.d.ts.map +1 -1
- package/dist/messages/it/devtools.json +16 -0
- package/dist/messages/it/index.d.ts +16 -0
- package/dist/messages/it/index.d.ts.map +1 -1
- package/dist/messages/pt/devtools.json +16 -0
- package/dist/messages/pt/index.d.ts +16 -0
- package/dist/messages/pt/index.d.ts.map +1 -1
- package/dist/migrations/017_scheduled_actions_table.sql +21 -0
- package/dist/migrations/021_media.sql +154 -0
- package/dist/migrations/090_sample_data.sql +53 -0
- package/dist/styles/classes.json +36 -3
- package/dist/styles/ui.css +1 -1
- package/dist/templates/app/api/devtools/config/entities/route.ts +18 -11
- package/dist/templates/app/api/devtools/config/theme/route.ts +5 -4
- package/dist/templates/app/api/devtools/tests/[...path]/route.ts +6 -5
- package/dist/templates/app/api/devtools/tests/route.ts +5 -4
- package/dist/templates/app/api/health/route.ts +6 -4
- package/dist/templates/app/api/internal/user-metadata/route.ts +3 -2
- package/dist/templates/app/api/superadmin/subscriptions/route.ts +5 -6
- package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +6 -7
- package/dist/templates/app/api/superadmin/teams/route.ts +5 -6
- package/dist/templates/app/api/superadmin/users/[userId]/route.ts +11 -16
- package/dist/templates/app/api/superadmin/users/route.ts +9 -10
- package/dist/templates/app/api/user/delete-account/route.ts +3 -2
- package/dist/templates/app/api/user/plan-flags/route.ts +11 -24
- package/dist/templates/app/api/user/profile/route.ts +7 -6
- package/dist/templates/app/api/v1/[entity]/[id]/child/[childType]/[childId]/route.ts +16 -18
- package/dist/templates/app/api/v1/[entity]/[id]/child/[childType]/route.ts +17 -19
- package/dist/templates/app/api/v1/[entity]/[id]/route.ts +10 -12
- package/dist/templates/app/api/v1/[entity]/route.ts +9 -11
- package/dist/templates/app/api/v1/api-keys/[id]/route.ts +9 -8
- package/dist/templates/app/api/v1/api-keys/route.ts +7 -6
- package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +3 -2
- package/dist/templates/app/api/v1/billing/cancel/route.ts +15 -14
- package/dist/templates/app/api/v1/billing/change-plan/route.ts +10 -9
- package/dist/templates/app/api/v1/billing/check-action/route.ts +8 -7
- package/dist/templates/app/api/v1/billing/checkout/route.ts +10 -9
- package/dist/templates/app/api/v1/billing/plans/route.ts +5 -4
- package/dist/templates/app/api/v1/billing/portal/route.ts +9 -8
- package/dist/templates/app/api/v1/blocks/[slug]/route.ts +4 -3
- package/dist/templates/app/api/v1/blocks/route.ts +3 -2
- package/dist/templates/app/api/v1/blocks/validate/route.ts +5 -3
- package/dist/templates/app/api/v1/cron/process/route.ts +4 -6
- package/dist/templates/app/api/v1/devtools/blocks/route.ts +3 -2
- package/dist/templates/app/api/v1/devtools/docs/route.ts +3 -2
- package/dist/templates/app/api/v1/devtools/features/route.ts +3 -2
- package/dist/templates/app/api/v1/devtools/flows/route.ts +3 -2
- package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +125 -3
- package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +110 -0
- package/dist/templates/app/api/v1/devtools/testing/route.ts +3 -2
- package/dist/templates/app/api/v1/media/[id]/route.ts +144 -0
- package/dist/templates/app/api/v1/media/[id]/tags/route.ts +154 -0
- package/dist/templates/app/api/v1/media/check-duplicates/route.ts +56 -0
- package/dist/templates/app/api/v1/media/route.ts +56 -0
- package/dist/templates/app/api/v1/media/upload/route.ts +157 -33
- package/dist/templates/app/api/v1/media-tags/route.ts +65 -0
- package/dist/templates/app/api/v1/plugin/[...path]/route.ts +16 -15
- package/dist/templates/app/api/v1/plugin/route.ts +3 -2
- package/dist/templates/app/api/v1/post-categories/[id]/route.ts +10 -9
- package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
- package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +3 -3
- package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +3 -3
- package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +3 -2
- package/dist/templates/app/api/v1/team-invitations/route.ts +3 -2
- package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +5 -4
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +3 -2
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +3 -2
- package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +5 -4
- package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +5 -5
- package/dist/templates/app/api/v1/teams/[teamId]/route.ts +31 -58
- package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -2
- package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +5 -4
- package/dist/templates/app/api/v1/teams/route.ts +18 -17
- package/dist/templates/app/api/v1/teams/switch/route.ts +3 -2
- package/dist/templates/app/api/v1/theme/[...path]/route.ts +16 -15
- package/dist/templates/app/api/v1/theme/route.ts +3 -2
- package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +7 -6
- package/dist/templates/app/api/v1/users/[id]/route.ts +9 -8
- package/dist/templates/app/api/v1/users/route.ts +7 -6
- package/dist/templates/app/dashboard/(main)/media/page.tsx +607 -0
- package/dist/templates/contents/themes/starter/messages/de/dev.json +106 -0
- package/dist/templates/contents/themes/starter/messages/de/index.ts +2 -0
- package/dist/templates/contents/themes/starter/messages/en/dev.json +106 -0
- package/dist/templates/contents/themes/starter/messages/en/index.ts +2 -0
- package/dist/templates/contents/themes/starter/messages/es/dev.json +106 -0
- package/dist/templates/contents/themes/starter/messages/es/index.ts +2 -0
- package/dist/templates/contents/themes/starter/messages/fr/dev.json +106 -0
- package/dist/templates/contents/themes/starter/messages/fr/index.ts +2 -0
- package/dist/templates/contents/themes/starter/messages/it/dev.json +106 -0
- package/dist/templates/contents/themes/starter/messages/it/index.ts +2 -0
- package/dist/templates/contents/themes/starter/messages/pt/dev.json +106 -0
- package/dist/templates/contents/themes/starter/messages/pt/index.ts +2 -0
- package/dist/templates/contents/themes/starter/styles/globals.css +14 -0
- package/dist/templates/instrumentation.ts +33 -0
- package/dist/types/blocks.d.ts +1 -1
- package/dist/types/blocks.d.ts.map +1 -1
- package/migrations/017_scheduled_actions_table.sql +21 -0
- package/migrations/021_media.sql +154 -0
- package/migrations/090_sample_data.sql +53 -0
- package/package.json +3 -2
- package/scripts/build/registry/config.mjs +41 -0
- package/scripts/build/registry/discovery/templates.mjs +0 -1
- package/scripts/build/registry/generators/entity-registry.mjs +16 -6
- package/scripts/build/registry/generators/route-handlers.mjs +8 -2
- package/scripts/build/registry/generators/template-registry.mjs +16 -4
- package/scripts/build/registry/post-build/route-cleanup.mjs +0 -1
- package/scripts/build/registry/validate-env.test.mjs +92 -0
- package/scripts/build/registry.mjs +18 -1
- package/scripts/deploy/vercel-deploy.mjs +1 -1
- package/templates/app/api/devtools/config/entities/route.ts +18 -11
- package/templates/app/api/devtools/config/theme/route.ts +5 -4
- package/templates/app/api/devtools/tests/[...path]/route.ts +6 -5
- package/templates/app/api/devtools/tests/route.ts +5 -4
- package/templates/app/api/health/route.ts +6 -4
- package/templates/app/api/internal/user-metadata/route.ts +3 -2
- package/templates/app/api/superadmin/subscriptions/route.ts +5 -6
- package/templates/app/api/superadmin/teams/[teamId]/route.ts +6 -7
- package/templates/app/api/superadmin/teams/route.ts +5 -6
- package/templates/app/api/superadmin/users/[userId]/route.ts +11 -16
- package/templates/app/api/superadmin/users/route.ts +9 -10
- package/templates/app/api/user/delete-account/route.ts +3 -2
- package/templates/app/api/user/plan-flags/route.ts +11 -24
- package/templates/app/api/user/profile/route.ts +7 -6
- package/templates/app/api/v1/[entity]/[id]/child/[childType]/[childId]/route.ts +16 -18
- package/templates/app/api/v1/[entity]/[id]/child/[childType]/route.ts +17 -19
- package/templates/app/api/v1/[entity]/[id]/route.ts +10 -12
- package/templates/app/api/v1/[entity]/route.ts +9 -11
- package/templates/app/api/v1/api-keys/[id]/route.ts +9 -8
- package/templates/app/api/v1/api-keys/route.ts +7 -6
- package/templates/app/api/v1/auth/signup-with-invite/route.ts +3 -2
- package/templates/app/api/v1/billing/cancel/route.ts +15 -14
- package/templates/app/api/v1/billing/change-plan/route.ts +10 -9
- package/templates/app/api/v1/billing/check-action/route.ts +8 -7
- package/templates/app/api/v1/billing/checkout/route.ts +10 -9
- package/templates/app/api/v1/billing/plans/route.ts +5 -4
- package/templates/app/api/v1/billing/portal/route.ts +9 -8
- package/templates/app/api/v1/blocks/[slug]/route.ts +4 -3
- package/templates/app/api/v1/blocks/route.ts +3 -2
- package/templates/app/api/v1/blocks/validate/route.ts +5 -3
- package/templates/app/api/v1/cron/process/route.ts +4 -6
- package/templates/app/api/v1/devtools/blocks/route.ts +3 -2
- package/templates/app/api/v1/devtools/docs/route.ts +3 -2
- package/templates/app/api/v1/devtools/features/route.ts +3 -2
- package/templates/app/api/v1/devtools/flows/route.ts +3 -2
- package/templates/app/api/v1/devtools/scheduled-actions/route.ts +125 -3
- package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +110 -0
- package/templates/app/api/v1/devtools/testing/route.ts +3 -2
- package/templates/app/api/v1/media/[id]/route.ts +144 -0
- package/templates/app/api/v1/media/[id]/tags/route.ts +154 -0
- package/templates/app/api/v1/media/check-duplicates/route.ts +56 -0
- package/templates/app/api/v1/media/route.ts +56 -0
- package/templates/app/api/v1/media/upload/route.ts +157 -33
- package/templates/app/api/v1/media-tags/route.ts +65 -0
- package/templates/app/api/v1/plugin/[...path]/route.ts +16 -15
- package/templates/app/api/v1/plugin/route.ts +3 -2
- package/templates/app/api/v1/post-categories/[id]/route.ts +10 -9
- package/templates/app/api/v1/post-categories/route.ts +5 -4
- package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +3 -3
- package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +3 -3
- package/templates/app/api/v1/team-invitations/[token]/route.ts +3 -2
- package/templates/app/api/v1/team-invitations/route.ts +3 -2
- package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +5 -4
- package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +3 -2
- package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +3 -2
- package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +5 -4
- package/templates/app/api/v1/teams/[teamId]/members/route.ts +5 -5
- package/templates/app/api/v1/teams/[teamId]/route.ts +31 -58
- package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +3 -2
- package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +5 -4
- package/templates/app/api/v1/teams/route.ts +18 -17
- package/templates/app/api/v1/teams/switch/route.ts +3 -2
- package/templates/app/api/v1/theme/[...path]/route.ts +16 -15
- package/templates/app/api/v1/theme/route.ts +3 -2
- package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +7 -6
- package/templates/app/api/v1/users/[id]/route.ts +9 -8
- package/templates/app/api/v1/users/route.ts +7 -6
- package/templates/app/dashboard/(main)/media/page.tsx +607 -0
- package/templates/contents/themes/starter/messages/de/dev.json +106 -0
- package/templates/contents/themes/starter/messages/de/index.ts +2 -0
- package/templates/contents/themes/starter/messages/en/dev.json +106 -0
- package/templates/contents/themes/starter/messages/en/index.ts +2 -0
- package/templates/contents/themes/starter/messages/es/dev.json +106 -0
- package/templates/contents/themes/starter/messages/es/index.ts +2 -0
- package/templates/contents/themes/starter/messages/fr/dev.json +106 -0
- package/templates/contents/themes/starter/messages/fr/index.ts +2 -0
- package/templates/contents/themes/starter/messages/it/dev.json +106 -0
- package/templates/contents/themes/starter/messages/it/index.ts +2 -0
- package/templates/contents/themes/starter/messages/pt/dev.json +106 -0
- package/templates/contents/themes/starter/messages/pt/index.ts +2 -0
- package/templates/contents/themes/starter/styles/globals.css +14 -0
- package/templates/instrumentation.ts +33 -0
- package/dist/presets/plugin/.env.example.template +0 -19
- package/dist/presets/plugin/entities/.gitkeep +0 -18
- package/dist/presets/theme/blocks/.gitkeep +0 -17
- package/dist/presets/theme/public/brand/.gitkeep +0 -8
- package/dist/presets/theme/tests/cypress/.gitkeep +0 -10
- package/dist/templates/contents/plugins/starter/plugin/.env.example.template +0 -19
- package/templates/contents/plugins/starter/plugin/.env.example.template +0 -19
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scheduled Actions - Processor
|
|
3
|
-
* Processes pending actions and
|
|
3
|
+
* Processes pending actions with parallel execution and lock-group based locking
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Controlled parallelism with configurable concurrency limit
|
|
7
|
+
* - Lock-group based locking to prevent race conditions on same resource
|
|
8
|
+
* - SELECT FOR UPDATE SKIP LOCKED for safe concurrent processing
|
|
9
|
+
* - Timeout protection per action
|
|
4
10
|
*/
|
|
5
|
-
import type { ProcessResult } from './types';
|
|
11
|
+
import type { ScheduledAction, ProcessResult } from './types';
|
|
6
12
|
/**
|
|
7
|
-
* Process pending actions
|
|
8
|
-
*
|
|
13
|
+
* Process pending actions with parallel execution
|
|
14
|
+
* Uses SELECT FOR UPDATE SKIP LOCKED for safe concurrent processing
|
|
15
|
+
*
|
|
16
|
+
* Lock Group Behavior:
|
|
17
|
+
* - Actions with same lockGroup are processed sequentially
|
|
18
|
+
* - Actions with NULL lockGroup can run in parallel with any other action
|
|
19
|
+
* - Uses PostgreSQL row-level locking to prevent race conditions
|
|
9
20
|
*
|
|
10
21
|
* @param batchSize - Maximum number of actions to process (uses config default if not provided)
|
|
11
22
|
* @returns Result with counts and errors
|
|
@@ -15,4 +26,9 @@ import type { ProcessResult } from './types';
|
|
|
15
26
|
* console.log(`Processed ${result.processed}, succeeded ${result.succeeded}, failed ${result.failed}`)
|
|
16
27
|
*/
|
|
17
28
|
export declare function processPendingActions(batchSize?: number): Promise<ProcessResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Execute a single action
|
|
31
|
+
* Handles the full lifecycle of action execution
|
|
32
|
+
*/
|
|
33
|
+
export declare function executeAction(action: ScheduledAction): Promise<void>;
|
|
18
34
|
//# sourceMappingURL=processor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/processor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/processor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAO7D;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,CAAC,CAkExB;AAuFD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA4D1E"}
|
|
@@ -4,9 +4,11 @@ import { getActionHandler } from "./registry.js";
|
|
|
4
4
|
import { scheduleAction } from "./scheduler.js";
|
|
5
5
|
const DEFAULT_BATCH_SIZE = 10;
|
|
6
6
|
const DEFAULT_TIMEOUT = 3e4;
|
|
7
|
+
const DEFAULT_CONCURRENCY_LIMIT = 1;
|
|
7
8
|
async function processPendingActions(batchSize) {
|
|
8
|
-
var _a;
|
|
9
|
+
var _a, _b;
|
|
9
10
|
const effectiveBatchSize = batchSize ?? ((_a = APP_CONFIG_MERGED.scheduledActions) == null ? void 0 : _a.batchSize) ?? DEFAULT_BATCH_SIZE;
|
|
11
|
+
const concurrencyLimit = ((_b = APP_CONFIG_MERGED.scheduledActions) == null ? void 0 : _b.concurrencyLimit) ?? DEFAULT_CONCURRENCY_LIMIT;
|
|
10
12
|
const result = {
|
|
11
13
|
processed: 0,
|
|
12
14
|
succeeded: 0,
|
|
@@ -14,28 +16,39 @@ async function processPendingActions(batchSize) {
|
|
|
14
16
|
errors: []
|
|
15
17
|
};
|
|
16
18
|
try {
|
|
17
|
-
const actions = await
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
19
|
+
const actions = await fetchAndLockPendingActions(effectiveBatchSize);
|
|
20
|
+
if (actions.length === 0) {
|
|
21
|
+
console.log("[ScheduledActions] No pending actions to process");
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
console.log(`[ScheduledActions] Found ${actions.length} pending action(s) to process (concurrency: ${concurrencyLimit})`);
|
|
25
|
+
if (concurrencyLimit === 1) {
|
|
26
|
+
for (const action of actions) {
|
|
27
|
+
result.processed++;
|
|
28
|
+
try {
|
|
29
|
+
await executeAction(action);
|
|
30
|
+
result.succeeded++;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
result.failed++;
|
|
33
|
+
result.errors.push({
|
|
34
|
+
actionId: action.id,
|
|
35
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
const results = await processWithConcurrencyLimit(actions, concurrencyLimit);
|
|
41
|
+
for (const r of results) {
|
|
42
|
+
result.processed++;
|
|
43
|
+
if (r.status === "fulfilled") {
|
|
44
|
+
result.succeeded++;
|
|
45
|
+
} else {
|
|
46
|
+
result.failed++;
|
|
47
|
+
result.errors.push({
|
|
48
|
+
actionId: r.actionId,
|
|
49
|
+
error: r.reason instanceof Error ? r.reason.message : "Unknown error"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
39
52
|
}
|
|
40
53
|
}
|
|
41
54
|
console.log(`[ScheduledActions] Processing complete: ${result.succeeded} succeeded, ${result.failed} failed`);
|
|
@@ -45,11 +58,57 @@ async function processPendingActions(batchSize) {
|
|
|
45
58
|
}
|
|
46
59
|
return result;
|
|
47
60
|
}
|
|
61
|
+
async function fetchAndLockPendingActions(batchSize) {
|
|
62
|
+
const actions = await queryWithRLS(
|
|
63
|
+
`WITH ranked_actions AS (
|
|
64
|
+
SELECT *,
|
|
65
|
+
ROW_NUMBER() OVER (
|
|
66
|
+
PARTITION BY "lockGroup"
|
|
67
|
+
ORDER BY "scheduledAt" ASC
|
|
68
|
+
) as rn
|
|
69
|
+
FROM "scheduled_actions"
|
|
70
|
+
WHERE status = 'pending'
|
|
71
|
+
AND "scheduledAt" <= NOW()
|
|
72
|
+
)
|
|
73
|
+
SELECT id, "actionType", status, payload, "teamId", "scheduledAt",
|
|
74
|
+
"startedAt", "completedAt", "errorMessage", attempts,
|
|
75
|
+
"recurringInterval", "recurrenceType", "lockGroup", "maxRetries",
|
|
76
|
+
"createdAt", "updatedAt"
|
|
77
|
+
FROM ranked_actions
|
|
78
|
+
WHERE rn = 1 OR "lockGroup" IS NULL
|
|
79
|
+
ORDER BY "scheduledAt" ASC
|
|
80
|
+
LIMIT $1
|
|
81
|
+
FOR UPDATE SKIP LOCKED`,
|
|
82
|
+
[batchSize],
|
|
83
|
+
null
|
|
84
|
+
// System operation
|
|
85
|
+
);
|
|
86
|
+
return actions;
|
|
87
|
+
}
|
|
88
|
+
async function processWithConcurrencyLimit(actions, limit) {
|
|
89
|
+
const results = [];
|
|
90
|
+
const executing = /* @__PURE__ */ new Set();
|
|
91
|
+
for (const action of actions) {
|
|
92
|
+
const promise = executeAction(action).then(() => {
|
|
93
|
+
results.push({ actionId: action.id, status: "fulfilled" });
|
|
94
|
+
}).catch((error) => {
|
|
95
|
+
results.push({ actionId: action.id, status: "rejected", reason: error });
|
|
96
|
+
}).finally(() => {
|
|
97
|
+
executing.delete(promise);
|
|
98
|
+
});
|
|
99
|
+
executing.add(promise);
|
|
100
|
+
if (executing.size >= limit) {
|
|
101
|
+
await Promise.race(executing);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
await Promise.all(executing);
|
|
105
|
+
return results;
|
|
106
|
+
}
|
|
48
107
|
async function executeAction(action) {
|
|
49
108
|
var _a;
|
|
50
109
|
const startTime = Date.now();
|
|
51
110
|
const configDefaultTimeout = ((_a = APP_CONFIG_MERGED.scheduledActions) == null ? void 0 : _a.defaultTimeout) ?? DEFAULT_TIMEOUT;
|
|
52
|
-
console.log(`[ScheduledActions] Executing action ${action.id} (${action.actionType})`);
|
|
111
|
+
console.log(`[ScheduledActions] Executing action ${action.id} (${action.actionType})${action.lockGroup ? ` [lockGroup: ${action.lockGroup}]` : ""}`);
|
|
53
112
|
await markActionRunning(action.id);
|
|
54
113
|
try {
|
|
55
114
|
const actionDef = getActionHandler(action.actionType);
|
|
@@ -72,7 +131,14 @@ async function executeAction(action) {
|
|
|
72
131
|
const executionTime = Date.now() - startTime;
|
|
73
132
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
74
133
|
console.error(`[ScheduledActions] Action ${action.id} failed after ${executionTime}ms:`, errorMessage);
|
|
75
|
-
|
|
134
|
+
if (action.attempts < action.maxRetries) {
|
|
135
|
+
const retryDelayMinutes = action.attempts * 5;
|
|
136
|
+
console.log(`[ScheduledActions] Will retry action ${action.id} in ${retryDelayMinutes} minutes (attempt ${action.attempts}/${action.maxRetries})`);
|
|
137
|
+
await rescheduleFailedAction(action, errorMessage, retryDelayMinutes);
|
|
138
|
+
} else {
|
|
139
|
+
console.error(`[ScheduledActions] Max retries (${action.maxRetries}) reached for action ${action.id}`);
|
|
140
|
+
await markActionFailed(action.id, errorMessage);
|
|
141
|
+
}
|
|
76
142
|
throw error;
|
|
77
143
|
}
|
|
78
144
|
}
|
|
@@ -117,36 +183,64 @@ async function markActionFailed(actionId, errorMessage) {
|
|
|
117
183
|
null
|
|
118
184
|
);
|
|
119
185
|
}
|
|
186
|
+
async function rescheduleFailedAction(action, errorMessage, delayMinutes) {
|
|
187
|
+
const nextAttemptTime = new Date(Date.now() + delayMinutes * 60 * 1e3);
|
|
188
|
+
await mutateWithRLS(
|
|
189
|
+
`UPDATE "scheduled_actions"
|
|
190
|
+
SET status = 'pending',
|
|
191
|
+
"scheduledAt" = $2,
|
|
192
|
+
"errorMessage" = $3,
|
|
193
|
+
"startedAt" = NULL,
|
|
194
|
+
"completedAt" = NULL,
|
|
195
|
+
"updatedAt" = NOW()
|
|
196
|
+
WHERE id = $1`,
|
|
197
|
+
[action.id, nextAttemptTime, errorMessage],
|
|
198
|
+
null
|
|
199
|
+
);
|
|
200
|
+
console.log(`[ScheduledActions] Rescheduled action ${action.id} for ${nextAttemptTime.toISOString()}`);
|
|
201
|
+
}
|
|
120
202
|
async function rescheduleRecurringAction(action) {
|
|
121
203
|
if (!action.recurringInterval) {
|
|
122
204
|
return;
|
|
123
205
|
}
|
|
124
|
-
|
|
206
|
+
let nextScheduledAt;
|
|
207
|
+
if (action.recurrenceType === "rolling") {
|
|
208
|
+
nextScheduledAt = calculateNextScheduledTime(action.recurringInterval, /* @__PURE__ */ new Date());
|
|
209
|
+
} else {
|
|
210
|
+
nextScheduledAt = calculateNextScheduledTime(action.recurringInterval, action.scheduledAt);
|
|
211
|
+
}
|
|
125
212
|
await scheduleAction(
|
|
126
213
|
action.actionType,
|
|
127
214
|
action.payload,
|
|
128
215
|
{
|
|
129
216
|
scheduledAt: nextScheduledAt,
|
|
130
217
|
teamId: action.teamId ?? void 0,
|
|
131
|
-
recurringInterval: action.recurringInterval ?? void 0
|
|
218
|
+
recurringInterval: action.recurringInterval ?? void 0,
|
|
219
|
+
recurrenceType: action.recurrenceType ?? void 0,
|
|
220
|
+
lockGroup: action.lockGroup ?? void 0
|
|
132
221
|
}
|
|
133
222
|
);
|
|
134
|
-
console.log(
|
|
223
|
+
console.log(
|
|
224
|
+
`[ScheduledActions] Rescheduled recurring action ${action.actionType} for ${nextScheduledAt.toISOString()} (type: ${action.recurrenceType || "fixed"})`
|
|
225
|
+
);
|
|
135
226
|
}
|
|
136
|
-
function calculateNextScheduledTime(interval) {
|
|
137
|
-
const
|
|
227
|
+
function calculateNextScheduledTime(interval, baseTime) {
|
|
228
|
+
const base = new Date(baseTime);
|
|
138
229
|
switch (interval) {
|
|
230
|
+
case "every-30-minutes":
|
|
231
|
+
return new Date(base.getTime() + 30 * 60 * 1e3);
|
|
139
232
|
case "hourly":
|
|
140
|
-
return new Date(
|
|
233
|
+
return new Date(base.getTime() + 60 * 60 * 1e3);
|
|
141
234
|
case "daily":
|
|
142
|
-
return new Date(
|
|
235
|
+
return new Date(base.getTime() + 24 * 60 * 60 * 1e3);
|
|
143
236
|
case "weekly":
|
|
144
|
-
return new Date(
|
|
237
|
+
return new Date(base.getTime() + 7 * 24 * 60 * 60 * 1e3);
|
|
145
238
|
default:
|
|
146
239
|
console.warn(`[ScheduledActions] Unknown interval '${interval}', defaulting to 1 hour`);
|
|
147
|
-
return new Date(
|
|
240
|
+
return new Date(base.getTime() + 60 * 60 * 1e3);
|
|
148
241
|
}
|
|
149
242
|
}
|
|
150
243
|
export {
|
|
244
|
+
executeAction,
|
|
151
245
|
processPendingActions
|
|
152
246
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scheduled Actions - Registry
|
|
3
3
|
* Manages registration and lookup of action handlers
|
|
4
|
+
*
|
|
5
|
+
* Uses globalThis to persist across HMR in development mode.
|
|
6
|
+
* Same pattern as Next.js recommends for database connections.
|
|
4
7
|
*/
|
|
5
8
|
import type { ScheduledActionDefinition, ScheduledActionHandler } from './types';
|
|
6
9
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/registry.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AAehF;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,EAC/B,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,IAAI,CAaN;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,yBAAyB,GAAG,SAAS,CAEpF;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,EAAE,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const
|
|
1
|
+
const globalForScheduledActions = globalThis;
|
|
2
|
+
const actionRegistry = globalForScheduledActions.__scheduledActionsRegistry ??= /* @__PURE__ */ new Map();
|
|
2
3
|
function registerScheduledAction(name, handler, options) {
|
|
3
4
|
if (actionRegistry.has(name)) {
|
|
4
5
|
console.warn(`[ScheduledActions] Action '${name}' is already registered. Overwriting.`);
|
|
@@ -28,7 +28,7 @@ import type { ScheduleOptions } from './types';
|
|
|
28
28
|
* data: { title: 'New Task' }
|
|
29
29
|
* }, { teamId: 'team-456' })
|
|
30
30
|
*/
|
|
31
|
-
export declare function scheduleAction(actionType: string, payload: unknown, options?: ScheduleOptions): Promise<string
|
|
31
|
+
export declare function scheduleAction(actionType: string, payload: unknown, options?: ScheduleOptions): Promise<string>;
|
|
32
32
|
/**
|
|
33
33
|
* Schedule a recurring action
|
|
34
34
|
* Convenience function that sets recurringInterval
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAM9C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAM9C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,MAAM,CAAC,CAkIjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,mBAAmB,CAAC,GACnD,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAoB9E"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mutateWithRLS,
|
|
1
|
+
import { mutateWithRLS, getTransactionClient } from "../db.js";
|
|
2
2
|
import { APP_CONFIG_MERGED } from "../config/config-sync.js";
|
|
3
3
|
async function scheduleAction(actionType, payload, options) {
|
|
4
4
|
var _a;
|
|
@@ -8,41 +8,74 @@ async function scheduleAction(actionType, payload, options) {
|
|
|
8
8
|
const entityId = payloadObj == null ? void 0 : payloadObj.entityId;
|
|
9
9
|
const entityType = payloadObj == null ? void 0 : payloadObj.entityType;
|
|
10
10
|
const shouldDeduplicate = windowSeconds > 0 && entityId && !(options == null ? void 0 : options.recurringInterval);
|
|
11
|
+
const actionId = globalThis.crypto.randomUUID();
|
|
12
|
+
const scheduledAt = (options == null ? void 0 : options.scheduledAt) || /* @__PURE__ */ new Date();
|
|
13
|
+
const teamId = (options == null ? void 0 : options.teamId) || null;
|
|
14
|
+
const recurringInterval = (options == null ? void 0 : options.recurringInterval) || null;
|
|
15
|
+
const lockGroup = (options == null ? void 0 : options.lockGroup) || null;
|
|
16
|
+
const maxRetries = (options == null ? void 0 : options.maxRetries) ?? 3;
|
|
17
|
+
const recurrenceType = (options == null ? void 0 : options.recurrenceType) || null;
|
|
11
18
|
if (shouldDeduplicate) {
|
|
12
19
|
const dedupKey = `${actionType}:${entityId}:${entityType || ""}`;
|
|
13
|
-
await
|
|
14
|
-
|
|
15
|
-
[dedupKey]
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
20
|
+
const client = await getTransactionClient(null);
|
|
21
|
+
try {
|
|
22
|
+
await client.query(`SELECT pg_advisory_xact_lock(hashtext($1))`, [dedupKey]);
|
|
23
|
+
const existing = await client.query(
|
|
24
|
+
`SELECT id
|
|
25
|
+
FROM "scheduled_actions"
|
|
26
|
+
WHERE "actionType" = $1
|
|
27
|
+
AND status = 'pending'
|
|
28
|
+
AND payload->>'entityId' = $2
|
|
29
|
+
AND payload->>'entityType' = $3
|
|
30
|
+
AND "createdAt" > NOW() - INTERVAL '1 second' * $4
|
|
31
|
+
LIMIT 1`,
|
|
32
|
+
[actionType, entityId, entityType || "", windowSeconds]
|
|
33
|
+
);
|
|
34
|
+
if (existing.length > 0) {
|
|
35
|
+
await client.query(
|
|
36
|
+
`UPDATE "scheduled_actions"
|
|
37
|
+
SET payload = $1, "updatedAt" = NOW()
|
|
38
|
+
WHERE id = $2 AND status = 'pending'`,
|
|
39
|
+
[payload, existing[0].id]
|
|
40
|
+
);
|
|
41
|
+
await client.commit();
|
|
42
|
+
console.log(`[ScheduledActions] Duplicate detected, updated payload: ${existing[0].id}`);
|
|
43
|
+
return existing[0].id;
|
|
44
|
+
}
|
|
45
|
+
await client.query(
|
|
46
|
+
`INSERT INTO "scheduled_actions" (
|
|
47
|
+
id,
|
|
48
|
+
"actionType",
|
|
49
|
+
status,
|
|
50
|
+
payload,
|
|
51
|
+
"teamId",
|
|
52
|
+
"scheduledAt",
|
|
53
|
+
"recurringInterval",
|
|
54
|
+
"lockGroup",
|
|
55
|
+
"maxRetries",
|
|
56
|
+
"recurrenceType"
|
|
57
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
|
|
58
|
+
[
|
|
59
|
+
actionId,
|
|
60
|
+
actionType,
|
|
61
|
+
"pending",
|
|
62
|
+
payload,
|
|
63
|
+
teamId,
|
|
64
|
+
scheduledAt.toISOString(),
|
|
65
|
+
recurringInterval,
|
|
66
|
+
lockGroup,
|
|
67
|
+
maxRetries,
|
|
68
|
+
recurrenceType
|
|
69
|
+
]
|
|
37
70
|
);
|
|
38
|
-
|
|
39
|
-
|
|
71
|
+
await client.commit();
|
|
72
|
+
console.log(`[ScheduledActions] Scheduled action '${actionType}' with ID: ${actionId}${lockGroup ? ` (lockGroup: ${lockGroup})` : ""}`);
|
|
73
|
+
return actionId;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
await client.rollback();
|
|
76
|
+
throw error;
|
|
40
77
|
}
|
|
41
78
|
}
|
|
42
|
-
const actionId = globalThis.crypto.randomUUID();
|
|
43
|
-
const scheduledAt = (options == null ? void 0 : options.scheduledAt) || /* @__PURE__ */ new Date();
|
|
44
|
-
const teamId = (options == null ? void 0 : options.teamId) || null;
|
|
45
|
-
const recurringInterval = (options == null ? void 0 : options.recurringInterval) || null;
|
|
46
79
|
await mutateWithRLS(
|
|
47
80
|
`INSERT INTO "scheduled_actions" (
|
|
48
81
|
id,
|
|
@@ -51,29 +84,34 @@ async function scheduleAction(actionType, payload, options) {
|
|
|
51
84
|
payload,
|
|
52
85
|
"teamId",
|
|
53
86
|
"scheduledAt",
|
|
54
|
-
"recurringInterval"
|
|
55
|
-
|
|
87
|
+
"recurringInterval",
|
|
88
|
+
"lockGroup",
|
|
89
|
+
"maxRetries",
|
|
90
|
+
"recurrenceType"
|
|
91
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
|
|
56
92
|
[
|
|
57
93
|
actionId,
|
|
58
94
|
actionType,
|
|
59
95
|
"pending",
|
|
60
|
-
|
|
96
|
+
payload,
|
|
61
97
|
teamId,
|
|
62
98
|
scheduledAt.toISOString(),
|
|
63
|
-
recurringInterval
|
|
99
|
+
recurringInterval,
|
|
100
|
+
lockGroup,
|
|
101
|
+
maxRetries,
|
|
102
|
+
recurrenceType
|
|
64
103
|
],
|
|
65
104
|
null
|
|
66
105
|
// System operation, no RLS context needed
|
|
67
106
|
);
|
|
68
|
-
console.log(`[ScheduledActions] Scheduled action '${actionType}' with ID: ${actionId}`);
|
|
107
|
+
console.log(`[ScheduledActions] Scheduled action '${actionType}' with ID: ${actionId}${lockGroup ? ` (lockGroup: ${lockGroup})` : ""}`);
|
|
69
108
|
return actionId;
|
|
70
109
|
}
|
|
71
110
|
async function scheduleRecurringAction(actionType, payload, interval, options) {
|
|
72
|
-
|
|
111
|
+
return scheduleAction(actionType, payload, {
|
|
73
112
|
...options,
|
|
74
113
|
recurringInterval: interval
|
|
75
114
|
});
|
|
76
|
-
return result;
|
|
77
115
|
}
|
|
78
116
|
async function cancelScheduledAction(actionId) {
|
|
79
117
|
const result = await mutateWithRLS(
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
* Core types for the scheduled actions system
|
|
4
4
|
*/
|
|
5
5
|
export type ScheduledActionStatus = 'pending' | 'running' | 'completed' | 'failed';
|
|
6
|
+
/**
|
|
7
|
+
* Determines how next execution time is calculated for recurring actions
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* 'fixed' - Maintains exact schedule times (e.g., daily at 12:00)
|
|
11
|
+
* If action scheduled at 12:00 runs at 12:05, next is still 12:00 tomorrow
|
|
12
|
+
*
|
|
13
|
+
* 'rolling' - Intervals from actual completion time (e.g., 30min after execution finishes)
|
|
14
|
+
* If action scheduled at 12:00 runs at 12:15, next is 12:45
|
|
15
|
+
*/
|
|
16
|
+
export type RecurrenceType = 'fixed' | 'rolling';
|
|
6
17
|
/**
|
|
7
18
|
* Scheduled action record from database
|
|
8
19
|
* NOTE: Database uses camelCase (e.g., actionType, teamId, scheduledAt)
|
|
@@ -18,7 +29,34 @@ export interface ScheduledAction {
|
|
|
18
29
|
completedAt: Date | null;
|
|
19
30
|
errorMessage: string | null;
|
|
20
31
|
attempts: number;
|
|
32
|
+
/**
|
|
33
|
+
* Maximum value for the attempts counter before marking action as failed.
|
|
34
|
+
* Represents total attempts (initial execution + retries), not retry count.
|
|
35
|
+
* Default: 3
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* maxRetries = 0 -> Fail immediately, no retries (1 total attempt)
|
|
39
|
+
* maxRetries = 3 -> 3 total attempts (1 initial + 2 retries)
|
|
40
|
+
* maxRetries = 5 -> 5 total attempts (1 initial + 4 retries)
|
|
41
|
+
*/
|
|
42
|
+
maxRetries: number;
|
|
21
43
|
recurringInterval: string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Determines how next execution time is calculated for recurring actions.
|
|
46
|
+
* Only relevant when recurringInterval is set.
|
|
47
|
+
* Default: 'fixed'
|
|
48
|
+
*/
|
|
49
|
+
recurrenceType: RecurrenceType | null;
|
|
50
|
+
/**
|
|
51
|
+
* Lock group key for parallel execution control.
|
|
52
|
+
* Actions with the same lockGroup will be processed sequentially.
|
|
53
|
+
* Actions with NULL lockGroup can run in parallel with any other action.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* lockGroup = 'client:123' -> All actions for client 123 run sequentially
|
|
57
|
+
* lockGroup = 'content:456' -> All actions for content 456 run sequentially
|
|
58
|
+
*/
|
|
59
|
+
lockGroup: string | null;
|
|
22
60
|
createdAt: Date;
|
|
23
61
|
updatedAt: Date;
|
|
24
62
|
}
|
|
@@ -46,6 +84,41 @@ export interface ScheduleOptions {
|
|
|
46
84
|
scheduledAt?: Date;
|
|
47
85
|
teamId?: string;
|
|
48
86
|
recurringInterval?: 'hourly' | 'daily' | 'weekly' | string;
|
|
87
|
+
/**
|
|
88
|
+
* Maximum value for the attempts counter before marking action as failed.
|
|
89
|
+
* Represents total attempts (initial execution + retries), not retry count.
|
|
90
|
+
* Default: 3
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* { maxRetries: 0 } -> Fail immediately, no retries (1 total attempt)
|
|
94
|
+
* { maxRetries: 3 } -> 3 total attempts (1 initial + 2 retries)
|
|
95
|
+
* { maxRetries: 5 } -> 5 total attempts (1 initial + 4 retries) for critical operations
|
|
96
|
+
*/
|
|
97
|
+
maxRetries?: number;
|
|
98
|
+
/**
|
|
99
|
+
* Lock group key for parallel execution control.
|
|
100
|
+
* Actions with the same lockGroup will be processed sequentially.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* { lockGroup: 'client:123' } -> All actions for client 123 run sequentially
|
|
104
|
+
* { lockGroup: `content:${contentId}` } -> Prevents concurrent content publishing
|
|
105
|
+
*/
|
|
106
|
+
lockGroup?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Determines how next execution time is calculated for recurring actions.
|
|
109
|
+
* Only relevant when recurringInterval is set.
|
|
110
|
+
* Default: 'fixed'
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* // Daily report at 12:00 (fixed schedule)
|
|
114
|
+
* { recurringInterval: 'daily', recurrenceType: 'fixed' }
|
|
115
|
+
* // If runs at 12:05, next is still 12:00 tomorrow
|
|
116
|
+
*
|
|
117
|
+
* // Token refresh every 30 minutes (rolling interval)
|
|
118
|
+
* { recurringInterval: 'every-30-minutes', recurrenceType: 'rolling' }
|
|
119
|
+
* // If scheduled at 12:00 but runs at 12:15, next is 12:45
|
|
120
|
+
*/
|
|
121
|
+
recurrenceType?: RecurrenceType;
|
|
49
122
|
}
|
|
50
123
|
/**
|
|
51
124
|
* Result of processing actions
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAA;AAElF;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,qBAAqB,CAAA;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,WAAW,EAAE,IAAI,CAAA;IACjB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3D;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,sBAAsB,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,IAAI,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iBAAiB,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/scheduled-actions/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAA;AAElF;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,CAAA;AAEhD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,qBAAqB,CAAA;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,WAAW,EAAE,IAAI,CAAA;IACjB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;;;;OASG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC;;;;OAIG;IACH,cAAc,EAAE,cAAc,GAAG,IAAI,CAAA;IACrC;;;;;;;;OAQG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3D;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,sBAAsB,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,IAAI,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iBAAiB,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;IAC1D;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,cAAc,CAAA;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnD"}
|