@nextsparkjs/ai-workflow 0.1.0-beta.100
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/LICENSE +21 -0
- package/README.md +115 -0
- package/claude/_docs/workflows-optimizations.md +359 -0
- package/claude/agents/api-tester.md +634 -0
- package/claude/agents/architecture-supervisor.md +1351 -0
- package/claude/agents/backend-developer.md +997 -0
- package/claude/agents/backend-validator.md +417 -0
- package/claude/agents/bdd-docs-writer.md +737 -0
- package/claude/agents/block-developer.md +677 -0
- package/claude/agents/code-reviewer.md +1432 -0
- package/claude/agents/db-developer.md +721 -0
- package/claude/agents/db-validator.md +407 -0
- package/claude/agents/demo-video-generator.md +493 -0
- package/claude/agents/documentation-writer.md +1268 -0
- package/claude/agents/frontend-developer.md +1234 -0
- package/claude/agents/frontend-validator.md +777 -0
- package/claude/agents/functional-validator.md +630 -0
- package/claude/agents/mock-analyst.md +387 -0
- package/claude/agents/product-manager.md +963 -0
- package/claude/agents/qa-automation.md +1762 -0
- package/claude/agents/release-manager.md +634 -0
- package/claude/agents/selectors-translator.md +262 -0
- package/claude/agents/unit-test-writer.md +785 -0
- package/claude/agents/visual-comparator.md +329 -0
- package/claude/agents/workflow-maintainer.md +352 -0
- package/claude/commands/do/README.md +88 -0
- package/claude/commands/do/create-api.md +64 -0
- package/claude/commands/do/create-entity.md +66 -0
- package/claude/commands/do/create-migration.md +64 -0
- package/claude/commands/do/create-plugin.md +56 -0
- package/claude/commands/do/create-theme.md +70 -0
- package/claude/commands/do/mock-data.md +67 -0
- package/claude/commands/do/reset-db.md +71 -0
- package/claude/commands/do/setup-scheduled-action.md +75 -0
- package/claude/commands/do/sync-code-review.md +117 -0
- package/claude/commands/do/update-selectors.md +112 -0
- package/claude/commands/do/use-skills.md +90 -0
- package/claude/commands/do/validate-blocks.md +69 -0
- package/claude/commands/how-to/README.md +261 -0
- package/claude/commands/how-to/add-metadata.md +692 -0
- package/claude/commands/how-to/add-taxonomies.md +806 -0
- package/claude/commands/how-to/add-translations.md +571 -0
- package/claude/commands/how-to/create-api.md +577 -0
- package/claude/commands/how-to/create-block.md +575 -0
- package/claude/commands/how-to/create-child-entities.md +771 -0
- package/claude/commands/how-to/create-entity.md +597 -0
- package/claude/commands/how-to/create-migrations.md +605 -0
- package/claude/commands/how-to/create-plugin.md +654 -0
- package/claude/commands/how-to/customize-app.md +481 -0
- package/claude/commands/how-to/customize-dashboard.md +553 -0
- package/claude/commands/how-to/customize-theme.md +438 -0
- package/claude/commands/how-to/define-features-flows.md +632 -0
- package/claude/commands/how-to/deploy.md +507 -0
- package/claude/commands/how-to/handle-file-uploads.md +746 -0
- package/claude/commands/how-to/implement-search.md +1001 -0
- package/claude/commands/how-to/install-plugins.md +352 -0
- package/claude/commands/how-to/manage-test-coverage.md +984 -0
- package/claude/commands/how-to/run-tests.md +400 -0
- package/claude/commands/how-to/set-app-languages.md +601 -0
- package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
- package/claude/commands/how-to/set-scheduled-actions.md +527 -0
- package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
- package/claude/commands/how-to/setup-authentication.md +388 -0
- package/claude/commands/how-to/setup-claude-code.md +440 -0
- package/claude/commands/how-to/setup-database.md +274 -0
- package/claude/commands/how-to/setup-email-providers.md +598 -0
- package/claude/commands/how-to/setup-mobile-dev.md +627 -0
- package/claude/commands/how-to/start.md +500 -0
- package/claude/commands/how-to/use-devtools.md +639 -0
- package/claude/commands/how-to/use-superadmin.md +622 -0
- package/claude/commands/session/README.md +193 -0
- package/claude/commands/session/block-create.md +190 -0
- package/claude/commands/session/block-list.md +203 -0
- package/claude/commands/session/block-update.md +192 -0
- package/claude/commands/session/block-validate.md +218 -0
- package/claude/commands/session/changelog.md +115 -0
- package/claude/commands/session/close.md +225 -0
- package/claude/commands/session/commit.md +174 -0
- package/claude/commands/session/db-entity.md +206 -0
- package/claude/commands/session/db-fix.md +212 -0
- package/claude/commands/session/db-sample.md +206 -0
- package/claude/commands/session/demo.md +178 -0
- package/claude/commands/session/doc-bdd.md +207 -0
- package/claude/commands/session/doc-feature.md +218 -0
- package/claude/commands/session/doc-read.md +225 -0
- package/claude/commands/session/execute.md +204 -0
- package/claude/commands/session/explain.md +202 -0
- package/claude/commands/session/fix-bug.md +210 -0
- package/claude/commands/session/fix-build.md +182 -0
- package/claude/commands/session/fix-test.md +189 -0
- package/claude/commands/session/pending.md +232 -0
- package/claude/commands/session/refine.md +188 -0
- package/claude/commands/session/resume.md +192 -0
- package/claude/commands/session/review.md +192 -0
- package/claude/commands/session/scope-change.md +181 -0
- package/claude/commands/session/start-blocks.md +347 -0
- package/claude/commands/session/start.md +604 -0
- package/claude/commands/session/status.md +169 -0
- package/claude/commands/session/test-fix.md +221 -0
- package/claude/commands/session/test-run.md +203 -0
- package/claude/commands/session/test-write.md +242 -0
- package/claude/commands/session/validate.md +162 -0
- package/claude/config/context.json +40 -0
- package/claude/config/github.json +69 -0
- package/claude/config/github.schema.json +106 -0
- package/claude/config/team.json +46 -0
- package/claude/config/team.schema.json +106 -0
- package/claude/config/workspace.json +43 -0
- package/claude/config/workspace.schema.json +75 -0
- package/claude/skills/README.md +228 -0
- package/claude/skills/accessibility/SKILL.md +573 -0
- package/claude/skills/api-bypass-layers/SKILL.md +550 -0
- package/claude/skills/asana-integration/SKILL.md +499 -0
- package/claude/skills/better-auth/SKILL.md +666 -0
- package/claude/skills/billing-subscriptions/SKILL.md +660 -0
- package/claude/skills/block-decision-matrix/SKILL.md +359 -0
- package/claude/skills/clickup-integration/SKILL.md +434 -0
- package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
- package/claude/skills/create-plugin/SKILL.md +425 -0
- package/claude/skills/create-theme/SKILL.md +331 -0
- package/claude/skills/cypress-api/SKILL.md +511 -0
- package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
- package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
- package/claude/skills/cypress-e2e/SKILL.md +526 -0
- package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
- package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
- package/claude/skills/cypress-selectors/SKILL.md +309 -0
- package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
- package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
- package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
- package/claude/skills/database-migrations/SKILL.md +335 -0
- package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
- package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
- package/claude/skills/design-system/SKILL.md +682 -0
- package/claude/skills/documentation/SKILL.md +540 -0
- package/claude/skills/entity-api/SKILL.md +482 -0
- package/claude/skills/entity-system/SKILL.md +635 -0
- package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
- package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
- package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
- package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
- package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
- package/claude/skills/github/SKILL.md +467 -0
- package/claude/skills/i18n-nextintl/SKILL.md +302 -0
- package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
- package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
- package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
- package/claude/skills/impact-analysis/SKILL.md +203 -0
- package/claude/skills/jest-unit/SKILL.md +306 -0
- package/claude/skills/jest-unit/references/component-testing.md +371 -0
- package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
- package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
- package/claude/skills/jira-integration/SKILL.md +539 -0
- package/claude/skills/media-library/SKILL.md +743 -0
- package/claude/skills/mock-analysis/SKILL.md +276 -0
- package/claude/skills/monorepo-architecture/SKILL.md +162 -0
- package/claude/skills/nextjs-api-development/SKILL.md +364 -0
- package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
- package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
- package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
- package/claude/skills/notion-integration/SKILL.md +641 -0
- package/claude/skills/npm-development-workflow/SKILL.md +480 -0
- package/claude/skills/page-builder-blocks/SKILL.md +530 -0
- package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
- package/claude/skills/permissions-system/SKILL.md +619 -0
- package/claude/skills/plugins/SKILL.md +340 -0
- package/claude/skills/plugins/references/plugin-templates.md +414 -0
- package/claude/skills/plugins/references/plugin-testing.md +353 -0
- package/claude/skills/plugins/references/plugin-types.md +198 -0
- package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
- package/claude/skills/pom-patterns/SKILL.md +452 -0
- package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
- package/claude/skills/rate-limiting/SKILL.md +342 -0
- package/claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/claude/skills/react-best-practices/README.md +123 -0
- package/claude/skills/react-best-practices/SKILL.md +125 -0
- package/claude/skills/react-best-practices/metadata.json +15 -0
- package/claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/claude/skills/react-best-practices/rules/_template.md +28 -0
- package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/claude/skills/react-patterns/SKILL.md +688 -0
- package/claude/skills/registry-system/SKILL.md +331 -0
- package/claude/skills/scheduled-actions/SKILL.md +671 -0
- package/claude/skills/scope-enforcement/SKILL.md +542 -0
- package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
- package/claude/skills/server-actions/SKILL.md +493 -0
- package/claude/skills/service-layer/SKILL.md +587 -0
- package/claude/skills/session-management/SKILL.md +266 -0
- package/claude/skills/session-management/scripts/create-session.py +166 -0
- package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
- package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
- package/claude/skills/session-management/scripts/session-archive.sh +87 -0
- package/claude/skills/session-management/scripts/session-close.sh +133 -0
- package/claude/skills/session-management/scripts/session-init.sh +225 -0
- package/claude/skills/session-management/scripts/session-list.sh +163 -0
- package/claude/skills/session-management/scripts/split-plan.sh +116 -0
- package/claude/skills/shadcn-components/SKILL.md +586 -0
- package/claude/skills/shadcn-theming/SKILL.md +446 -0
- package/claude/skills/suspense-loading/SKILL.md +280 -0
- package/claude/skills/tailwind-theming/SKILL.md +507 -0
- package/claude/skills/tanstack-query/SKILL.md +608 -0
- package/claude/skills/test-coverage/SKILL.md +239 -0
- package/claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/claude/skills/zod-validation/SKILL.md +537 -0
- package/claude/templates/blocks/progress.md +86 -0
- package/claude/templates/iteration/changes.md +61 -0
- package/claude/templates/iteration/progress.md +55 -0
- package/claude/templates/log.md +31 -0
- package/claude/templates/story/context.md +77 -0
- package/claude/templates/story/pendings.md +37 -0
- package/claude/templates/story/plan.md +299 -0
- package/claude/templates/story/requirements.md +109 -0
- package/claude/templates/story/scope.json +10 -0
- package/claude/templates/story/tests.md +91 -0
- package/claude/templates/task/progress.md +58 -0
- package/claude/templates/task/requirements.md +54 -0
- package/claude/workflows/README.md +154 -0
- package/claude/workflows/blocks.md +614 -0
- package/claude/workflows/story.md +1207 -0
- package/claude/workflows/task.md +927 -0
- package/claude/workflows/tweak.md +527 -0
- package/cursor/.gitkeep +0 -0
- package/package.json +35 -0
- package/scripts/postinstall.mjs +198 -0
- package/scripts/setup.mjs +282 -0
- package/scripts/sync.mjs +209 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scheduled-actions
|
|
3
|
+
description: |
|
|
4
|
+
Scheduled Actions system for background task processing in this application.
|
|
5
|
+
Covers action scheduling, handler creation, webhook configuration, and cron processing.
|
|
6
|
+
Use this skill when creating, debugging, or configuring scheduled actions.
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Bash, Write, Edit
|
|
8
|
+
version: 1.1.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Scheduled Actions Skill
|
|
12
|
+
|
|
13
|
+
Patterns for background task processing and webhook systems.
|
|
14
|
+
|
|
15
|
+
## Architecture Overview
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
SCHEDULED ACTIONS SYSTEM:
|
|
19
|
+
|
|
20
|
+
Core Layer (core/lib/scheduled-actions/):
|
|
21
|
+
├── scheduler.ts # scheduleAction(), scheduleRecurringAction()
|
|
22
|
+
├── processor.ts # Cron processing logic
|
|
23
|
+
├── registry.ts # Handler registration
|
|
24
|
+
└── types.ts # TypeScript interfaces
|
|
25
|
+
|
|
26
|
+
Theme Layer (contents/themes/{theme}/lib/scheduled-actions/):
|
|
27
|
+
├── index.ts # Handler initialization + registerAllHandlers()
|
|
28
|
+
├── entity-hooks.ts # Entity event → action mapping
|
|
29
|
+
└── handlers/ # Handler implementations
|
|
30
|
+
├── webhook.ts # Webhook sender
|
|
31
|
+
├── email.ts # Email sender (if configured)
|
|
32
|
+
└── {custom}.ts # Custom handlers
|
|
33
|
+
|
|
34
|
+
Configuration (contents/themes/{theme}/config/app.config.ts):
|
|
35
|
+
└── scheduledActions: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
deduplication: { windowSeconds: 10 },
|
|
38
|
+
webhooks: { endpoints: {...}, patterns: {...} }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Flow:
|
|
42
|
+
Entity Event → Entity Hook → scheduleAction() → DB Table → Cron → Handler → Result
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
> **📍 Context-Aware Paths:** Core layer (`core/lib/scheduled-actions/`) is read-only in consumer projects.
|
|
46
|
+
> Create handlers in `contents/themes/{theme}/lib/scheduled-actions/handlers/`.
|
|
47
|
+
> See `core-theme-responsibilities` skill for complete rules.
|
|
48
|
+
|
|
49
|
+
## Initialization Flow
|
|
50
|
+
|
|
51
|
+
**CRITICAL:** Initialization happens in `instrumentation.ts` at server startup.
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
Server Start (instrumentation.ts)
|
|
55
|
+
│
|
|
56
|
+
├─ initializeScheduledActions() # Sync - registers handlers
|
|
57
|
+
│ └─ Calls theme's registerAllHandlers()
|
|
58
|
+
│ ├─ Register action handlers (e.g., content:publish)
|
|
59
|
+
│ └─ Register entity hooks (e.g., on entity.contents.updated)
|
|
60
|
+
│
|
|
61
|
+
└─ initializeRecurringActions() # Async - creates DB rows (once per server)
|
|
62
|
+
└─ Calls theme's registerRecurringActions()
|
|
63
|
+
└─ Creates recurring action rows in DB if not exist
|
|
64
|
+
(e.g., social:refresh-tokens every 30 minutes)
|
|
65
|
+
|
|
66
|
+
Then...
|
|
67
|
+
|
|
68
|
+
Cron Endpoint (/api/v1/cron/process) - Called every ~1 minute
|
|
69
|
+
│
|
|
70
|
+
└─ processPendingActions() # Async - executes due actions
|
|
71
|
+
└─ Processes actions where scheduledAt <= now
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Server Initialization (instrumentation.ts)
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// instrumentation.ts (root of project)
|
|
78
|
+
export async function register() {
|
|
79
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
80
|
+
const {
|
|
81
|
+
initializeScheduledActions,
|
|
82
|
+
initializeRecurringActions,
|
|
83
|
+
} = await import('@nextsparkjs/core/lib/scheduled-actions')
|
|
84
|
+
|
|
85
|
+
console.log('[Instrumentation] Initializing scheduled actions...')
|
|
86
|
+
|
|
87
|
+
// 1. Register handlers + entity hooks (sync, idempotent)
|
|
88
|
+
initializeScheduledActions()
|
|
89
|
+
|
|
90
|
+
// 2. Create recurring actions in DB (async, idempotent)
|
|
91
|
+
await initializeRecurringActions()
|
|
92
|
+
|
|
93
|
+
console.log('[Instrumentation] ✅ Scheduled actions initialized')
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Cron Endpoint (Simplified)
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// app/api/v1/cron/process/route.ts
|
|
102
|
+
import {
|
|
103
|
+
processPendingActions,
|
|
104
|
+
cleanupOldActions
|
|
105
|
+
} from '@nextsparkjs/core/lib/scheduled-actions'
|
|
106
|
+
|
|
107
|
+
export async function GET(request: NextRequest): Promise<NextResponse> {
|
|
108
|
+
// Handlers already registered via instrumentation.ts
|
|
109
|
+
|
|
110
|
+
// 1. Validate CRON_SECRET...
|
|
111
|
+
// 2. Process pending actions...
|
|
112
|
+
// 3. Cleanup old actions...
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Theme Registration Functions
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// contents/themes/{theme}/lib/scheduled-actions/index.ts
|
|
120
|
+
|
|
121
|
+
// Called by initializeScheduledActions() - registers handlers
|
|
122
|
+
export function registerAllHandlers() {
|
|
123
|
+
registerContentPublishHandler() // One-time actions
|
|
124
|
+
registerTokenRefreshHandler() // Handler for recurring action
|
|
125
|
+
registerEntityHooks() // Entity event → action mapping
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Called by initializeRecurringActions() - creates DB rows
|
|
129
|
+
export async function registerRecurringActions(): Promise<void> {
|
|
130
|
+
// Check if already exists to avoid duplicates
|
|
131
|
+
const existing = await queryWithRLS(
|
|
132
|
+
`SELECT id FROM "scheduled_actions" WHERE "actionType" = $1 AND "recurringInterval" IS NOT NULL`,
|
|
133
|
+
['social:refresh-tokens'],
|
|
134
|
+
null
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if (existing.length === 0) {
|
|
138
|
+
await scheduleRecurringAction('social:refresh-tokens', {}, 'every-30-minutes')
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Action Types
|
|
144
|
+
|
|
145
|
+
| Type | Trigger | Created By | Example |
|
|
146
|
+
|------|---------|------------|---------|
|
|
147
|
+
| **One-time** | Entity event | Entity hooks | `content:publish` when content.status='scheduled' |
|
|
148
|
+
| **Recurring** | Cron interval | `registerRecurringActions()` | `social:refresh-tokens` every 30 min |
|
|
149
|
+
|
|
150
|
+
> **✅ ALWAYS use `instrumentation.ts`** for scheduled actions initialization.
|
|
151
|
+
> This is the correct place because:
|
|
152
|
+
> - Runs ONCE at server startup (not on every cron request)
|
|
153
|
+
> - Entity hooks are registered before any API requests
|
|
154
|
+
> - Official Next.js 13+ pattern for global initialization
|
|
155
|
+
> - Idempotent functions handle edge cases safely
|
|
156
|
+
|
|
157
|
+
## When to Use This Skill
|
|
158
|
+
|
|
159
|
+
- Creating new action handlers
|
|
160
|
+
- Setting up webhooks for entity events
|
|
161
|
+
- Debugging pending/failed actions
|
|
162
|
+
- Configuring deduplication or batching
|
|
163
|
+
- Understanding the scheduled actions flow
|
|
164
|
+
|
|
165
|
+
## Key Files Reference
|
|
166
|
+
|
|
167
|
+
| File | Purpose |
|
|
168
|
+
|------|---------|
|
|
169
|
+
| `core/lib/scheduled-actions/scheduler.ts` | `scheduleAction()`, `scheduleRecurringAction()` |
|
|
170
|
+
| `core/lib/scheduled-actions/processor.ts` | Cron processing logic |
|
|
171
|
+
| `core/lib/scheduled-actions/registry.ts` | Handler registration |
|
|
172
|
+
| `contents/themes/{theme}/lib/scheduled-actions/index.ts` | Handler initialization |
|
|
173
|
+
| `contents/themes/{theme}/lib/scheduled-actions/handlers/` | Handler implementations |
|
|
174
|
+
| `contents/themes/{theme}/config/app.config.ts` | Configuration section |
|
|
175
|
+
|
|
176
|
+
## Creating Action Handlers
|
|
177
|
+
|
|
178
|
+
### Handler Template
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// contents/themes/{theme}/lib/scheduled-actions/handlers/{name}.ts
|
|
182
|
+
import { registerScheduledAction } from '@/core/lib/scheduled-actions'
|
|
183
|
+
|
|
184
|
+
interface MyPayload {
|
|
185
|
+
entityId: string
|
|
186
|
+
teamId: string
|
|
187
|
+
data: Record<string, unknown>
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function registerMyHandler() {
|
|
191
|
+
registerScheduledAction('my-action:type', async (payload, action) => {
|
|
192
|
+
const data = payload as MyPayload
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
// Implementation logic
|
|
196
|
+
await processMyAction(data)
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
message: 'Action completed successfully'
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
message: error instanceof Error ? error.message : 'Unknown error'
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Registering Handler
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// contents/themes/{theme}/lib/scheduled-actions/index.ts
|
|
216
|
+
import { registerMyHandler } from './handlers/my-handler'
|
|
217
|
+
|
|
218
|
+
let initialized = false
|
|
219
|
+
|
|
220
|
+
export function registerAllHandlers() {
|
|
221
|
+
if (initialized) return
|
|
222
|
+
initialized = true
|
|
223
|
+
|
|
224
|
+
// Register all handlers
|
|
225
|
+
registerWebhookHandler()
|
|
226
|
+
registerMyHandler() // Add new handler here
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Handler Types
|
|
231
|
+
|
|
232
|
+
| Type | Description | Use Case |
|
|
233
|
+
|------|-------------|----------|
|
|
234
|
+
| `webhook` | Send HTTP POST to external endpoint | Integrations, notifications |
|
|
235
|
+
| `email` | Send transactional emails | User notifications |
|
|
236
|
+
| `data-processor` | Process/transform data | ETL, aggregations |
|
|
237
|
+
| `cleanup` | Clean up old records | Maintenance tasks |
|
|
238
|
+
|
|
239
|
+
## Webhook Configuration
|
|
240
|
+
|
|
241
|
+
### Entity Hook Pattern
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// contents/themes/{theme}/lib/scheduled-actions/entity-hooks.ts
|
|
245
|
+
import { scheduleAction } from '@/core/lib/scheduled-actions'
|
|
246
|
+
import { hookSystem } from '@/core/lib/hooks'
|
|
247
|
+
|
|
248
|
+
export function registerEntityHooks() {
|
|
249
|
+
// Hook for task creation
|
|
250
|
+
hookSystem.register('entity.tasks.created', async ({ entity, teamId }) => {
|
|
251
|
+
await scheduleAction({
|
|
252
|
+
type: 'webhook:send',
|
|
253
|
+
payload: {
|
|
254
|
+
endpointKey: 'tasks',
|
|
255
|
+
event: 'task.created',
|
|
256
|
+
data: entity
|
|
257
|
+
},
|
|
258
|
+
scheduledFor: new Date(),
|
|
259
|
+
teamId
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
// Hook for task updates
|
|
264
|
+
hookSystem.register('entity.tasks.updated', async ({ entity, teamId }) => {
|
|
265
|
+
await scheduleAction({
|
|
266
|
+
type: 'webhook:send',
|
|
267
|
+
payload: {
|
|
268
|
+
endpointKey: 'tasks',
|
|
269
|
+
event: 'task.updated',
|
|
270
|
+
data: entity
|
|
271
|
+
},
|
|
272
|
+
scheduledFor: new Date(),
|
|
273
|
+
teamId
|
|
274
|
+
})
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Webhook Configuration in app.config.ts
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// contents/themes/{theme}/config/app.config.ts
|
|
283
|
+
export const appConfig = {
|
|
284
|
+
// ... other config
|
|
285
|
+
|
|
286
|
+
scheduledActions: {
|
|
287
|
+
enabled: true,
|
|
288
|
+
|
|
289
|
+
deduplication: {
|
|
290
|
+
windowSeconds: 10 // 0 to disable
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
webhooks: {
|
|
294
|
+
endpoints: {
|
|
295
|
+
// Key -> Environment variable mapping
|
|
296
|
+
tasks: 'WEBHOOK_URL_TASKS',
|
|
297
|
+
subscriptions: 'WEBHOOK_URL_SUBSCRIPTIONS',
|
|
298
|
+
default: 'WEBHOOK_URL_DEFAULT'
|
|
299
|
+
},
|
|
300
|
+
patterns: {
|
|
301
|
+
// Event pattern -> Endpoint key
|
|
302
|
+
'task.*': 'tasks',
|
|
303
|
+
'subscription.*': 'subscriptions',
|
|
304
|
+
'*': 'default' // Fallback
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Environment Variables
|
|
312
|
+
|
|
313
|
+
```env
|
|
314
|
+
# Required for cron processing
|
|
315
|
+
CRON_SECRET=your-secure-secret-min-32-chars
|
|
316
|
+
|
|
317
|
+
# Webhook URLs (one per endpoint key)
|
|
318
|
+
WEBHOOK_URL_TASKS=https://your-webhook-url/tasks
|
|
319
|
+
WEBHOOK_URL_SUBSCRIPTIONS=https://your-webhook-url/subs
|
|
320
|
+
WEBHOOK_URL_DEFAULT=https://fallback-url
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Scheduling Actions
|
|
324
|
+
|
|
325
|
+
### Immediate Action
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { scheduleAction } from '@/core/lib/scheduled-actions'
|
|
329
|
+
|
|
330
|
+
await scheduleAction({
|
|
331
|
+
type: 'my-action:type',
|
|
332
|
+
payload: {
|
|
333
|
+
entityId: 'abc123',
|
|
334
|
+
data: { field: 'value' }
|
|
335
|
+
},
|
|
336
|
+
scheduledFor: new Date(), // Now
|
|
337
|
+
teamId: 'team_123'
|
|
338
|
+
})
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Delayed Action
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
await scheduleAction({
|
|
345
|
+
type: 'reminder:send',
|
|
346
|
+
payload: { userId: 'user_123', message: 'Follow up' },
|
|
347
|
+
scheduledFor: new Date(Date.now() + 24 * 60 * 60 * 1000), // Tomorrow
|
|
348
|
+
teamId: 'team_123'
|
|
349
|
+
})
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Recurring Action
|
|
353
|
+
|
|
354
|
+
**IMPORTANT:** Recurring actions should be created in `registerRecurringActions()`, NOT ad-hoc.
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
// In theme's lib/scheduled-actions/index.ts
|
|
358
|
+
export async function registerRecurringActions(): Promise<void> {
|
|
359
|
+
const { scheduleRecurringAction } = await import('@nextsparkjs/core/lib/scheduled-actions')
|
|
360
|
+
|
|
361
|
+
// Check if already exists (idempotent)
|
|
362
|
+
const existing = await queryWithRLS(
|
|
363
|
+
`SELECT id FROM "scheduled_actions" WHERE "actionType" = $1 AND "recurringInterval" IS NOT NULL AND status = 'pending'`,
|
|
364
|
+
['report:generate'],
|
|
365
|
+
null
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
if (existing.length > 0) return
|
|
369
|
+
|
|
370
|
+
// Available intervals: 'every-minute', 'every-5-minutes', 'every-15-minutes',
|
|
371
|
+
// 'every-30-minutes', 'every-hour', 'every-6-hours', 'every-day'
|
|
372
|
+
await scheduleRecurringAction(
|
|
373
|
+
'report:generate',
|
|
374
|
+
{ reportType: 'daily-summary' },
|
|
375
|
+
'every-day'
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**Flow:** After processing a recurring action, it auto-reschedules for the next interval.
|
|
381
|
+
|
|
382
|
+
## Recurrence Types
|
|
383
|
+
|
|
384
|
+
For recurring actions, you can control how the next execution time is calculated using the `recurrenceType` parameter.
|
|
385
|
+
|
|
386
|
+
### Fixed Schedule (default)
|
|
387
|
+
|
|
388
|
+
Maintains exact schedule times, ignoring execution delays. Ideal for reports, batch jobs, or any task that should run at specific times.
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
await scheduleRecurringAction(
|
|
392
|
+
'report:daily',
|
|
393
|
+
{ type: 'sales' },
|
|
394
|
+
'daily',
|
|
395
|
+
{
|
|
396
|
+
scheduledAt: new Date('2026-02-05T12:00:00Z'),
|
|
397
|
+
recurrenceType: 'fixed' // or omit, defaults to 'fixed'
|
|
398
|
+
}
|
|
399
|
+
)
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Behavior:**
|
|
403
|
+
- Action scheduled: 12:00:00
|
|
404
|
+
- Actually runs: 12:05:00 (5 min delay due to cron or processing)
|
|
405
|
+
- **Next scheduled: 12:00:00 tomorrow** ✅ (maintains exact time)
|
|
406
|
+
|
|
407
|
+
### Rolling Interval
|
|
408
|
+
|
|
409
|
+
Calculates intervals from actual completion time. Ideal for token refreshes, polling, or tasks where consistent spacing matters more than exact timing.
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
await scheduleRecurringAction(
|
|
413
|
+
'social:refresh-tokens',
|
|
414
|
+
{},
|
|
415
|
+
'every-30-minutes',
|
|
416
|
+
{
|
|
417
|
+
recurrenceType: 'rolling'
|
|
418
|
+
}
|
|
419
|
+
)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Behavior:**
|
|
423
|
+
- Action scheduled: 12:00:00
|
|
424
|
+
- Actually runs: 12:15:00 (15 min delay)
|
|
425
|
+
- **Next scheduled: 12:45:00** ✅ (30 min from actual execution)
|
|
426
|
+
|
|
427
|
+
### Comparison
|
|
428
|
+
|
|
429
|
+
| Scenario | Fixed | Rolling |
|
|
430
|
+
|----------|-------|---------|
|
|
431
|
+
| Daily report at 9:00 AM | ✅ Runs at 9:00 daily (or as soon as possible) | ❌ Drifts if delayed |
|
|
432
|
+
| Token refresh every 30 min | ❌ Can stack up if delayed | ✅ Consistent 30 min gaps |
|
|
433
|
+
| Batch cleanup at midnight | ✅ Runs at midnight sharp | ❌ Drifts based on execution time |
|
|
434
|
+
| API polling every 5 min | ⚠️ Can create bursts if delayed | ✅ Steady 5 min spacing |
|
|
435
|
+
|
|
436
|
+
### Usage Examples
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
// Example 1: Fixed - Daily backup at 2:00 AM
|
|
440
|
+
await scheduleRecurringAction(
|
|
441
|
+
'backup:database',
|
|
442
|
+
{ retention: 30 },
|
|
443
|
+
'daily',
|
|
444
|
+
{
|
|
445
|
+
scheduledAt: new Date('2026-02-05T02:00:00Z'),
|
|
446
|
+
recurrenceType: 'fixed'
|
|
447
|
+
}
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
// Example 2: Rolling - Check external API every 15 minutes
|
|
451
|
+
await scheduleRecurringAction(
|
|
452
|
+
'external:sync',
|
|
453
|
+
{ endpoint: 'https://api.example.com' },
|
|
454
|
+
'every-15-minutes',
|
|
455
|
+
{
|
|
456
|
+
recurrenceType: 'rolling'
|
|
457
|
+
}
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
// Example 3: Fixed - Weekly report on Mondays at 8:00 AM
|
|
461
|
+
await scheduleRecurringAction(
|
|
462
|
+
'report:weekly',
|
|
463
|
+
{ format: 'pdf' },
|
|
464
|
+
'weekly',
|
|
465
|
+
{
|
|
466
|
+
scheduledAt: new Date('2026-02-10T08:00:00Z'), // Monday
|
|
467
|
+
recurrenceType: 'fixed'
|
|
468
|
+
}
|
|
469
|
+
)
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## Debugging Actions
|
|
473
|
+
|
|
474
|
+
### Check Pending Actions
|
|
475
|
+
|
|
476
|
+
```bash
|
|
477
|
+
curl "http://localhost:5173/api/v1/devtools/scheduled-actions?status=pending" \
|
|
478
|
+
-H "Authorization: Bearer API_KEY"
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Check Failed Actions
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
curl "http://localhost:5173/api/v1/devtools/scheduled-actions?status=failed" \
|
|
485
|
+
-H "Authorization: Bearer API_KEY"
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Manually Trigger Processing
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
curl "http://localhost:5173/api/v1/cron/process" \
|
|
492
|
+
-H "x-cron-secret: CRON_SECRET"
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Console Log Patterns
|
|
496
|
+
|
|
497
|
+
```
|
|
498
|
+
[ScheduledActions] Processing 5 pending actions
|
|
499
|
+
[ScheduledActions] Action abc123 completed successfully
|
|
500
|
+
[ScheduledActions] Action xyz789 failed: Connection timeout
|
|
501
|
+
[ScheduledActions] Handler not found for type: unknown:type
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## Deduplication
|
|
505
|
+
|
|
506
|
+
### Purpose
|
|
507
|
+
|
|
508
|
+
Prevents duplicate actions when the same event fires multiple times in quick succession.
|
|
509
|
+
|
|
510
|
+
### Configuration
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
scheduledActions: {
|
|
514
|
+
deduplication: {
|
|
515
|
+
windowSeconds: 10 // Actions with same type+payload within 10s are deduplicated
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### Behavior
|
|
521
|
+
|
|
522
|
+
1. When action is scheduled, system creates hash of `type + payload`
|
|
523
|
+
2. If action with same hash exists within window, new action is skipped
|
|
524
|
+
3. Window is reset when action is processed
|
|
525
|
+
|
|
526
|
+
### Disabling
|
|
527
|
+
|
|
528
|
+
Set `windowSeconds: 0` to disable deduplication entirely.
|
|
529
|
+
|
|
530
|
+
## API Endpoints
|
|
531
|
+
|
|
532
|
+
| Endpoint | Method | Auth | Purpose |
|
|
533
|
+
|----------|--------|------|---------|
|
|
534
|
+
| `/api/v1/cron/process` | POST | x-cron-secret | Trigger action processing |
|
|
535
|
+
| `/api/v1/devtools/scheduled-actions` | GET | API Key | List actions (debug) |
|
|
536
|
+
| `/api/v1/devtools/scheduled-actions/:id` | DELETE | API Key | Delete action (debug) |
|
|
537
|
+
|
|
538
|
+
## Troubleshooting
|
|
539
|
+
|
|
540
|
+
| Issue | Cause | Solution |
|
|
541
|
+
|-------|-------|----------|
|
|
542
|
+
| Handler not found | Not registered | Add to `index.ts` `registerAllHandlers()` |
|
|
543
|
+
| Webhook not sent | Pattern mismatch | Check patterns in `app.config.ts` |
|
|
544
|
+
| Duplicate actions | Dedup disabled | Set `windowSeconds > 0` |
|
|
545
|
+
| Actions stuck pending | Cron not running | Verify cron service and `CRON_SECRET` |
|
|
546
|
+
| 401 on cron endpoint | Wrong header | Use `x-cron-secret` (not `Authorization`) |
|
|
547
|
+
| Env variable undefined | Not set | Add to `.env` and restart server |
|
|
548
|
+
| Recurring action not created | Missing `registerRecurringActions()` | Export function from theme's `index.ts` |
|
|
549
|
+
| Recurring action not running | Handlers not initialized | Ensure `instrumentation.ts` exists and runs |
|
|
550
|
+
| Entity hooks not firing | Handlers not registered early | Use `instrumentation.ts`, not cron endpoint |
|
|
551
|
+
|
|
552
|
+
## Anti-Patterns
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
// NEVER: Process actions synchronously in API routes
|
|
556
|
+
// This blocks the response
|
|
557
|
+
app.post('/api/entity', async (req, res) => {
|
|
558
|
+
const entity = await createEntity(req.body)
|
|
559
|
+
await sendWebhook(entity) // WRONG - blocks response
|
|
560
|
+
res.json(entity)
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
// CORRECT: Schedule action for async processing
|
|
564
|
+
app.post('/api/entity', async (req, res) => {
|
|
565
|
+
const entity = await createEntity(req.body)
|
|
566
|
+
await scheduleAction({
|
|
567
|
+
type: 'webhook:send',
|
|
568
|
+
payload: { entity },
|
|
569
|
+
scheduledFor: new Date(),
|
|
570
|
+
teamId: req.teamId
|
|
571
|
+
})
|
|
572
|
+
res.json(entity)
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
// NEVER: Store sensitive data in payload
|
|
576
|
+
await scheduleAction({
|
|
577
|
+
type: 'email:send',
|
|
578
|
+
payload: {
|
|
579
|
+
password: 'secret123' // WRONG - stored in DB
|
|
580
|
+
}
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
// CORRECT: Store references only
|
|
584
|
+
await scheduleAction({
|
|
585
|
+
type: 'email:send',
|
|
586
|
+
payload: {
|
|
587
|
+
userId: 'user_123', // Lookup at processing time
|
|
588
|
+
templateKey: 'password-reset'
|
|
589
|
+
}
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
// NEVER: Forget to handle errors in handlers
|
|
593
|
+
registerScheduledAction('my:action', async (payload) => {
|
|
594
|
+
await riskyOperation(payload) // WRONG - unhandled rejection
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
// CORRECT: Always return success/failure
|
|
598
|
+
registerScheduledAction('my:action', async (payload) => {
|
|
599
|
+
try {
|
|
600
|
+
await riskyOperation(payload)
|
|
601
|
+
return { success: true, message: 'Done' }
|
|
602
|
+
} catch (error) {
|
|
603
|
+
return { success: false, message: error.message }
|
|
604
|
+
}
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
// NEVER: Initialize in API routes (adds overhead to every request)
|
|
608
|
+
// /api/v1/cron/process/route.ts
|
|
609
|
+
export async function GET(request: NextRequest) {
|
|
610
|
+
initializeScheduledActions() // WRONG - runs on every cron call
|
|
611
|
+
await initializeRecurringActions() // WRONG - unnecessary DB queries
|
|
612
|
+
// ...process actions
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// CORRECT: Initialize in instrumentation.ts (runs once at startup)
|
|
616
|
+
// instrumentation.ts
|
|
617
|
+
export async function register() {
|
|
618
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
619
|
+
const {
|
|
620
|
+
initializeScheduledActions,
|
|
621
|
+
initializeRecurringActions,
|
|
622
|
+
} = await import('@nextsparkjs/core/lib/scheduled-actions')
|
|
623
|
+
|
|
624
|
+
initializeScheduledActions() // ✅ Registers handlers + hooks
|
|
625
|
+
await initializeRecurringActions() // ✅ Creates recurring actions in DB
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
## Checklist
|
|
631
|
+
|
|
632
|
+
### Creating New Handler
|
|
633
|
+
|
|
634
|
+
- [ ] Handler file created in `handlers/` directory
|
|
635
|
+
- [ ] Handler registered in `index.ts` `registerAllHandlers()`
|
|
636
|
+
- [ ] Handler returns `{ success, message }` object
|
|
637
|
+
- [ ] Error handling with try/catch
|
|
638
|
+
- [ ] Registry rebuilt: `node core/scripts/build/registry.mjs`
|
|
639
|
+
|
|
640
|
+
### Adding Webhook
|
|
641
|
+
|
|
642
|
+
- [ ] Entity hook registered in `entity-hooks.ts`
|
|
643
|
+
- [ ] Endpoint key added to `webhooks.endpoints` config
|
|
644
|
+
- [ ] Pattern added to `webhooks.patterns` config
|
|
645
|
+
- [ ] Environment variable added to `.env`
|
|
646
|
+
- [ ] Environment variable documented in `.env.example`
|
|
647
|
+
|
|
648
|
+
### Debugging
|
|
649
|
+
|
|
650
|
+
- [ ] Check if `scheduledActions.enabled: true` in config
|
|
651
|
+
- [ ] Verify `CRON_SECRET` is set
|
|
652
|
+
- [ ] Check handler is registered (console log on startup)
|
|
653
|
+
- [ ] Query devtools endpoint for action status
|
|
654
|
+
- [ ] Check console for `[ScheduledActions]` logs
|
|
655
|
+
|
|
656
|
+
## Related Skills
|
|
657
|
+
|
|
658
|
+
- `entity-api` - API endpoints that trigger entity hooks
|
|
659
|
+
- `service-layer` - Service patterns for action processing
|
|
660
|
+
- `nextjs-api-development` - Cron endpoint patterns
|
|
661
|
+
- `database-migrations` - scheduled_actions table structure
|
|
662
|
+
|
|
663
|
+
## Documentation
|
|
664
|
+
|
|
665
|
+
Full documentation: `core/docs/20-scheduled-actions/`
|
|
666
|
+
- `01-overview.md` - System overview
|
|
667
|
+
- `02-scheduling.md` - Scheduling patterns
|
|
668
|
+
- `03-handlers.md` - Handler development
|
|
669
|
+
- `04-webhooks.md` - Webhook configuration
|
|
670
|
+
- `05-cron.md` - Cron processing
|
|
671
|
+
- `06-deduplication.md` - Deduplication system
|