@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,493 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: server-actions
|
|
3
|
+
description: |
|
|
4
|
+
Next.js Server Actions for mutations in this application.
|
|
5
|
+
Covers entity actions, user actions, team actions, and best practices.
|
|
6
|
+
Use this skill when implementing mutations from Client Components.
|
|
7
|
+
allowed-tools: Read, Glob, Grep
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Server Actions Skill
|
|
12
|
+
|
|
13
|
+
Patterns for using Next.js Server Actions to perform mutations from Client Components.
|
|
14
|
+
|
|
15
|
+
## Architecture Overview
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
core/lib/actions/
|
|
19
|
+
├── index.ts # Re-exports all actions
|
|
20
|
+
├── types.ts # Shared types (EntityActionResult, etc.)
|
|
21
|
+
├── entity.actions.ts # Generic CRUD for any registered entity
|
|
22
|
+
├── user.actions.ts # User profile management
|
|
23
|
+
└── team.actions.ts # Team and member management
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## When to Use This Skill
|
|
27
|
+
|
|
28
|
+
- Calling mutations from Client Components (`'use client'`)
|
|
29
|
+
- Form submissions without JavaScript
|
|
30
|
+
- Automatic cache revalidation after mutations
|
|
31
|
+
- Type-safe end-to-end mutations
|
|
32
|
+
|
|
33
|
+
## Import Pattern
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// From Client Components
|
|
37
|
+
'use client'
|
|
38
|
+
|
|
39
|
+
import {
|
|
40
|
+
// Entity actions
|
|
41
|
+
createEntity,
|
|
42
|
+
updateEntity,
|
|
43
|
+
deleteEntity,
|
|
44
|
+
getEntity,
|
|
45
|
+
listEntities,
|
|
46
|
+
deleteEntities,
|
|
47
|
+
entityExists,
|
|
48
|
+
countEntities,
|
|
49
|
+
// User actions
|
|
50
|
+
updateProfile,
|
|
51
|
+
updateAvatar,
|
|
52
|
+
deleteAccount,
|
|
53
|
+
// Team actions
|
|
54
|
+
updateTeam,
|
|
55
|
+
inviteMember,
|
|
56
|
+
removeMember,
|
|
57
|
+
updateMemberRole,
|
|
58
|
+
} from '@nextsparkjs/core/actions'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Security Model
|
|
62
|
+
|
|
63
|
+
**All actions follow the same security pattern:**
|
|
64
|
+
|
|
65
|
+
1. Auth is obtained from session/cookies (NEVER from client parameters)
|
|
66
|
+
2. `userId` comes from `getTypedSession()`
|
|
67
|
+
3. `teamId` comes from `httpOnly` cookie `activeTeamId`
|
|
68
|
+
4. Permissions are checked via `checkPermission()`
|
|
69
|
+
|
|
70
|
+
**Never pass userId/teamId as parameters:**
|
|
71
|
+
```typescript
|
|
72
|
+
// WRONG - Never trust client data for auth
|
|
73
|
+
export async function updateProfile(userId: string, data: ProfileData) { ... }
|
|
74
|
+
|
|
75
|
+
// CORRECT - Get auth from server context
|
|
76
|
+
export async function updateProfile(data: ProfileData) {
|
|
77
|
+
const session = await getTypedSession(await headers())
|
|
78
|
+
if (!session?.user?.id) {
|
|
79
|
+
return { success: false, error: 'Authentication required' }
|
|
80
|
+
}
|
|
81
|
+
const userId = session.user.id
|
|
82
|
+
// ...
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Entity Actions
|
|
87
|
+
|
|
88
|
+
### createEntity
|
|
89
|
+
|
|
90
|
+
Create a new entity record.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const result = await createEntity('products', {
|
|
94
|
+
name: 'New Product',
|
|
95
|
+
price: 99.99,
|
|
96
|
+
status: 'draft',
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
if (result.success) {
|
|
100
|
+
console.log('Created:', result.data)
|
|
101
|
+
} else {
|
|
102
|
+
console.error('Error:', result.error)
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### updateEntity
|
|
107
|
+
|
|
108
|
+
Update an existing entity.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const result = await updateEntity('products', productId, {
|
|
112
|
+
status: 'published',
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// With custom revalidation
|
|
116
|
+
await updateEntity('products', productId, data, {
|
|
117
|
+
revalidatePaths: ['/dashboard/overview'],
|
|
118
|
+
revalidateTags: ['product-stats'],
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### deleteEntity
|
|
123
|
+
|
|
124
|
+
Delete an entity by ID.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const result = await deleteEntity('products', productId)
|
|
128
|
+
|
|
129
|
+
// With redirect after delete
|
|
130
|
+
await deleteEntity('products', productId, {
|
|
131
|
+
redirectTo: '/dashboard/products',
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### deleteEntities (Batch)
|
|
136
|
+
|
|
137
|
+
Delete multiple entities at once.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const result = await deleteEntities('products', ['id1', 'id2', 'id3'])
|
|
141
|
+
|
|
142
|
+
if (result.success) {
|
|
143
|
+
console.log(`Deleted ${result.data.deletedCount} products`)
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### getEntity / listEntities
|
|
148
|
+
|
|
149
|
+
For reads from Client Components (prefer Server Components when possible).
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// Get single entity
|
|
153
|
+
const result = await getEntity('products', productId)
|
|
154
|
+
|
|
155
|
+
// List with pagination and filters
|
|
156
|
+
const result = await listEntities('products', {
|
|
157
|
+
limit: 20,
|
|
158
|
+
offset: 0,
|
|
159
|
+
where: { status: 'active' },
|
|
160
|
+
orderBy: 'createdAt',
|
|
161
|
+
orderDir: 'desc',
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### entityExists / countEntities
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// Check if entity exists
|
|
169
|
+
const result = await entityExists('products', productId)
|
|
170
|
+
|
|
171
|
+
// Count with filter
|
|
172
|
+
const result = await countEntities('products', { status: 'active' })
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## User Actions
|
|
176
|
+
|
|
177
|
+
### updateProfile
|
|
178
|
+
|
|
179
|
+
Update the current user's profile.
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const result = await updateProfile({
|
|
183
|
+
firstName: 'John',
|
|
184
|
+
lastName: 'Doe',
|
|
185
|
+
timezone: 'America/New_York',
|
|
186
|
+
language: 'en',
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
// Allowed fields: firstName, lastName, name, country, timezone, language
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### updateAvatar
|
|
193
|
+
|
|
194
|
+
Update the user's avatar (URL, not file upload).
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// After uploading file to storage and getting URL
|
|
198
|
+
const formData = new FormData()
|
|
199
|
+
formData.append('avatar', 'https://example.com/uploaded-avatar.jpg')
|
|
200
|
+
|
|
201
|
+
const result = await updateAvatar(formData)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### deleteAccount
|
|
205
|
+
|
|
206
|
+
Delete the current user's account.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// Check conditions first!
|
|
210
|
+
const result = await deleteAccount()
|
|
211
|
+
|
|
212
|
+
// Will fail if user owns teams
|
|
213
|
+
// Error: "Cannot delete account while owning teams. Transfer ownership first."
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Team Actions
|
|
217
|
+
|
|
218
|
+
### updateTeam
|
|
219
|
+
|
|
220
|
+
Update team information (requires owner or admin role).
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const result = await updateTeam(teamId, {
|
|
224
|
+
name: 'New Team Name',
|
|
225
|
+
description: 'Updated description',
|
|
226
|
+
slug: 'new-slug',
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
// Will check slug availability if changing
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### inviteMember
|
|
233
|
+
|
|
234
|
+
Add a user to the team (requires owner or admin role).
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
const result = await inviteMember(
|
|
238
|
+
teamId,
|
|
239
|
+
'user@example.com', // User must already exist
|
|
240
|
+
'member' // 'member' | 'admin' | 'viewer'
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
if (result.success) {
|
|
244
|
+
console.log('Invited:', result.data.memberId)
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### removeMember
|
|
249
|
+
|
|
250
|
+
Remove a member from the team.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
const result = await removeMember(teamId, userId)
|
|
254
|
+
|
|
255
|
+
// Cannot remove:
|
|
256
|
+
// - Team owner (must transfer ownership first)
|
|
257
|
+
// - Other admins (if requestor is admin, not owner)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### updateMemberRole
|
|
261
|
+
|
|
262
|
+
Change a member's role.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const result = await updateMemberRole(teamId, userId, 'admin')
|
|
266
|
+
|
|
267
|
+
// Restrictions:
|
|
268
|
+
// - Cannot set role to 'owner' (use transferOwnership)
|
|
269
|
+
// - Only owner can promote to admin
|
|
270
|
+
// - Only owner can demote admins
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Result Types
|
|
274
|
+
|
|
275
|
+
All actions return a discriminated union:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
type EntityActionResult<T> =
|
|
279
|
+
| { success: true; data: T }
|
|
280
|
+
| { success: false; error: string }
|
|
281
|
+
|
|
282
|
+
type EntityActionVoidResult =
|
|
283
|
+
| { success: true }
|
|
284
|
+
| { success: false; error: string }
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Usage with type guards:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
const result = await createEntity('products', data)
|
|
291
|
+
|
|
292
|
+
if (result.success) {
|
|
293
|
+
// TypeScript knows result.data exists
|
|
294
|
+
console.log(result.data.id)
|
|
295
|
+
} else {
|
|
296
|
+
// TypeScript knows result.error exists
|
|
297
|
+
toast.error(result.error)
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Revalidation Patterns
|
|
302
|
+
|
|
303
|
+
### Default Revalidation
|
|
304
|
+
|
|
305
|
+
Entity actions automatically revalidate:
|
|
306
|
+
- Create: `/dashboard/{entitySlug}`
|
|
307
|
+
- Update: `/dashboard/{entitySlug}` and `/dashboard/{entitySlug}/{id}`
|
|
308
|
+
- Delete: `/dashboard/{entitySlug}`
|
|
309
|
+
|
|
310
|
+
### Custom Revalidation
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
await createEntity('products', data, {
|
|
314
|
+
revalidatePaths: ['/dashboard/overview', '/public/catalog'],
|
|
315
|
+
revalidateTags: ['product-count', 'catalog'],
|
|
316
|
+
})
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Redirect After Mutation
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
await deleteEntity('products', id, {
|
|
323
|
+
redirectTo: '/dashboard/products',
|
|
324
|
+
})
|
|
325
|
+
// Note: redirect() throws NEXT_REDIRECT - action won't return data
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Form Integration
|
|
329
|
+
|
|
330
|
+
### With React Hook Form
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
'use client'
|
|
334
|
+
|
|
335
|
+
function ProductForm() {
|
|
336
|
+
const form = useForm()
|
|
337
|
+
|
|
338
|
+
async function onSubmit(data) {
|
|
339
|
+
const result = await createEntity('products', data)
|
|
340
|
+
if (result.success) {
|
|
341
|
+
router.push(`/products/${result.data.id}`)
|
|
342
|
+
} else {
|
|
343
|
+
form.setError('root', { message: result.error })
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### With Native Forms
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
'use client'
|
|
355
|
+
|
|
356
|
+
function ProfileForm() {
|
|
357
|
+
async function handleAction(formData: FormData) {
|
|
358
|
+
const result = await updateProfile({
|
|
359
|
+
firstName: formData.get('firstName') as string,
|
|
360
|
+
lastName: formData.get('lastName') as string,
|
|
361
|
+
})
|
|
362
|
+
// Handle result
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return (
|
|
366
|
+
<form action={handleAction}>
|
|
367
|
+
<input name="firstName" />
|
|
368
|
+
<input name="lastName" />
|
|
369
|
+
<button type="submit">Save</button>
|
|
370
|
+
</form>
|
|
371
|
+
)
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Creating New Server Actions
|
|
376
|
+
|
|
377
|
+
### Template
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// core/lib/actions/[domain].actions.ts
|
|
381
|
+
'use server'
|
|
382
|
+
|
|
383
|
+
import { revalidatePath } from 'next/cache'
|
|
384
|
+
import { headers } from 'next/headers'
|
|
385
|
+
import { getTypedSession } from '../auth'
|
|
386
|
+
import type { EntityActionResult, EntityActionVoidResult } from './types'
|
|
387
|
+
|
|
388
|
+
export async function myAction(data: MyInput): Promise<EntityActionResult<MyOutput>> {
|
|
389
|
+
try {
|
|
390
|
+
// 1. Get auth context
|
|
391
|
+
const headersList = await headers()
|
|
392
|
+
const session = await getTypedSession(headersList)
|
|
393
|
+
if (!session?.user?.id) {
|
|
394
|
+
return { success: false, error: 'Authentication required' }
|
|
395
|
+
}
|
|
396
|
+
const userId = session.user.id
|
|
397
|
+
|
|
398
|
+
// 2. Validate input
|
|
399
|
+
if (!data.requiredField) {
|
|
400
|
+
return { success: false, error: 'Required field is missing' }
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 3. Check permissions (if needed)
|
|
404
|
+
// const hasPermission = await checkPermission(userId, teamId, 'resource.action')
|
|
405
|
+
|
|
406
|
+
// 4. Execute business logic via Service
|
|
407
|
+
const result = await MyService.doSomething(userId, data)
|
|
408
|
+
|
|
409
|
+
// 5. Revalidate caches
|
|
410
|
+
revalidatePath('/dashboard/relevant-path')
|
|
411
|
+
|
|
412
|
+
return { success: true, data: result }
|
|
413
|
+
} catch (error) {
|
|
414
|
+
console.error('[myAction] Error:', error)
|
|
415
|
+
return {
|
|
416
|
+
success: false,
|
|
417
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Checklist for New Actions
|
|
424
|
+
|
|
425
|
+
- [ ] File marked with `'use server'` at top
|
|
426
|
+
- [ ] Auth obtained from session (not parameters)
|
|
427
|
+
- [ ] Input validation before service calls
|
|
428
|
+
- [ ] Permission checks where needed
|
|
429
|
+
- [ ] Business logic delegated to Services
|
|
430
|
+
- [ ] `revalidatePath` called for affected routes
|
|
431
|
+
- [ ] Error handling with try/catch
|
|
432
|
+
- [ ] Proper return type (`EntityActionResult` or `EntityActionVoidResult`)
|
|
433
|
+
- [ ] Export from `index.ts`
|
|
434
|
+
|
|
435
|
+
## Testing Server Actions
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
// tests/jest/lib/actions/my.actions.test.ts
|
|
439
|
+
|
|
440
|
+
import { myAction } from '@/core/lib/actions/my.actions'
|
|
441
|
+
|
|
442
|
+
// Mock dependencies
|
|
443
|
+
jest.mock('@/core/lib/auth', () => ({
|
|
444
|
+
getTypedSession: jest.fn(),
|
|
445
|
+
}))
|
|
446
|
+
|
|
447
|
+
jest.mock('next/cache', () => ({
|
|
448
|
+
revalidatePath: jest.fn(),
|
|
449
|
+
}))
|
|
450
|
+
|
|
451
|
+
jest.mock('next/headers', () => ({
|
|
452
|
+
headers: jest.fn().mockReturnValue(new Headers()),
|
|
453
|
+
}))
|
|
454
|
+
|
|
455
|
+
describe('myAction', () => {
|
|
456
|
+
beforeEach(() => {
|
|
457
|
+
jest.clearAllMocks()
|
|
458
|
+
// Setup authenticated user
|
|
459
|
+
const { getTypedSession } = require('@/core/lib/auth')
|
|
460
|
+
getTypedSession.mockResolvedValue({
|
|
461
|
+
user: { id: 'user-123' },
|
|
462
|
+
})
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
it('succeeds with valid data', async () => {
|
|
466
|
+
const result = await myAction({ name: 'Test' })
|
|
467
|
+
expect(result.success).toBe(true)
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
it('fails when not authenticated', async () => {
|
|
471
|
+
const { getTypedSession } = require('@/core/lib/auth')
|
|
472
|
+
getTypedSession.mockResolvedValue(null)
|
|
473
|
+
|
|
474
|
+
const result = await myAction({ name: 'Test' })
|
|
475
|
+
expect(result.success).toBe(false)
|
|
476
|
+
expect(result.error).toBe('Authentication required')
|
|
477
|
+
})
|
|
478
|
+
})
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Performance Considerations
|
|
482
|
+
|
|
483
|
+
1. **Prefer Server Components for reads** - Server Actions are for mutations
|
|
484
|
+
2. **Batch operations when possible** - Use `deleteEntities` instead of multiple `deleteEntity`
|
|
485
|
+
3. **Minimal revalidation** - Only revalidate affected paths
|
|
486
|
+
4. **Early validation** - Fail fast before expensive operations
|
|
487
|
+
|
|
488
|
+
## Related Skills
|
|
489
|
+
|
|
490
|
+
- `service-layer` - Business logic implementation
|
|
491
|
+
- `tanstack-query` - Optimistic updates and caching
|
|
492
|
+
- `react-patterns` - Form and component patterns
|
|
493
|
+
- `zod-validation` - Input validation schemas
|