@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,587 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: service-layer
|
|
3
|
+
description: |
|
|
4
|
+
Service layer patterns for this Next.js application.
|
|
5
|
+
Covers static class pattern, RLS integration, standard method signatures, and BaseEntityService.
|
|
6
|
+
Use this skill when implementing business logic or data access services.
|
|
7
|
+
allowed-tools: Read, Glob, Grep
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Service Layer Skill
|
|
12
|
+
|
|
13
|
+
Patterns for implementing business logic and data access services with RLS integration.
|
|
14
|
+
|
|
15
|
+
## Architecture Overview
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
core/lib/services/
|
|
19
|
+
├── base-entity.service.ts # Generic CRUD service base class
|
|
20
|
+
├── user.service.ts # User management
|
|
21
|
+
├── team.service.ts # Team CRUD and membership
|
|
22
|
+
├── team-member.service.ts # Team membership management
|
|
23
|
+
├── permission.service.ts # Permission checks (O(1))
|
|
24
|
+
├── membership.service.ts # Complete membership context
|
|
25
|
+
├── subscription.service.ts # Subscription lifecycle
|
|
26
|
+
├── plan.service.ts # Plan queries
|
|
27
|
+
├── usage.service.ts # Usage tracking and quotas
|
|
28
|
+
├── meta.service.ts # Flexible entity metadata
|
|
29
|
+
├── invoice.service.ts # Invoice management
|
|
30
|
+
├── theme.service.ts # Theme registry queries
|
|
31
|
+
├── entity-type.service.ts # Entity registry queries
|
|
32
|
+
└── ...
|
|
33
|
+
|
|
34
|
+
core/lib/db.ts # RLS query functions
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> **📍 Context-Aware Paths:** Paths shown assume monorepo development. In consumer projects,
|
|
38
|
+
> create in `contents/themes/{theme}/services/` instead. Core is read-only.
|
|
39
|
+
> See `core-theme-responsibilities` skill for complete rules.
|
|
40
|
+
|
|
41
|
+
## When to Use This Skill
|
|
42
|
+
|
|
43
|
+
- Implementing business logic services
|
|
44
|
+
- Creating data access layer for entities
|
|
45
|
+
- Working with RLS (Row Level Security)
|
|
46
|
+
- Implementing transactions
|
|
47
|
+
- Querying registries
|
|
48
|
+
|
|
49
|
+
## Static Class Pattern
|
|
50
|
+
|
|
51
|
+
**All services use static methods exclusively:**
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// core/lib/services/customer.service.ts
|
|
55
|
+
|
|
56
|
+
export class CustomerService {
|
|
57
|
+
// No constructor - all methods are static
|
|
58
|
+
// No instance state
|
|
59
|
+
|
|
60
|
+
static async getById(id: string, userId: string): Promise<Customer | null> {
|
|
61
|
+
// Implementation
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static async list(userId: string, options?: ListOptions): Promise<ListResult<Customer>> {
|
|
65
|
+
// Implementation
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static async create(userId: string, teamId: string, data: CreateCustomer): Promise<Customer> {
|
|
69
|
+
// Implementation
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static async update(id: string, userId: string, data: UpdateCustomer): Promise<Customer> {
|
|
73
|
+
// Implementation
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
static async delete(id: string, userId: string): Promise<boolean> {
|
|
77
|
+
// Implementation
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Usage (no instantiation):**
|
|
83
|
+
```typescript
|
|
84
|
+
import { CustomerService } from '@/core/lib/services/customer.service'
|
|
85
|
+
|
|
86
|
+
// Direct static method calls
|
|
87
|
+
const customer = await CustomerService.getById('cust-123', userId)
|
|
88
|
+
const customers = await CustomerService.list(userId, { limit: 10 })
|
|
89
|
+
const created = await CustomerService.create(userId, teamId, { name: 'Acme' })
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Standard Method Signatures
|
|
93
|
+
|
|
94
|
+
### Query Methods (Read Operations)
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// Get single entity by ID
|
|
98
|
+
static async getById(
|
|
99
|
+
id: string,
|
|
100
|
+
userId: string
|
|
101
|
+
): Promise<Entity | null>
|
|
102
|
+
|
|
103
|
+
// List with pagination, filtering, ordering
|
|
104
|
+
static async list(
|
|
105
|
+
userId: string,
|
|
106
|
+
options?: ListOptions
|
|
107
|
+
): Promise<ListResult<Entity>>
|
|
108
|
+
|
|
109
|
+
// Simple query (wrapper over list)
|
|
110
|
+
static async query(
|
|
111
|
+
userId: string,
|
|
112
|
+
options?: QueryOptions
|
|
113
|
+
): Promise<Entity[]>
|
|
114
|
+
|
|
115
|
+
// Check existence
|
|
116
|
+
static async exists(
|
|
117
|
+
id: string,
|
|
118
|
+
userId: string
|
|
119
|
+
): Promise<boolean>
|
|
120
|
+
|
|
121
|
+
// Count with optional filtering
|
|
122
|
+
static async count(
|
|
123
|
+
userId: string,
|
|
124
|
+
where?: Record<string, unknown>
|
|
125
|
+
): Promise<number>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Mutation Methods (Write Operations)
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// Create new entity
|
|
132
|
+
static async create(
|
|
133
|
+
userId: string,
|
|
134
|
+
teamId: string,
|
|
135
|
+
data: CreateEntity
|
|
136
|
+
): Promise<Entity>
|
|
137
|
+
|
|
138
|
+
// Update existing entity
|
|
139
|
+
static async update(
|
|
140
|
+
id: string,
|
|
141
|
+
userId: string,
|
|
142
|
+
data: UpdateEntity
|
|
143
|
+
): Promise<Entity>
|
|
144
|
+
|
|
145
|
+
// Delete entity
|
|
146
|
+
static async delete(
|
|
147
|
+
id: string,
|
|
148
|
+
userId: string
|
|
149
|
+
): Promise<boolean>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### ListOptions Interface
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
interface ListOptions {
|
|
156
|
+
limit?: number // Pagination limit (default: 20)
|
|
157
|
+
offset?: number // Pagination offset
|
|
158
|
+
orderBy?: string // Sort field (default: 'createdAt')
|
|
159
|
+
orderDir?: 'asc' | 'desc' // Sort direction (default: 'desc')
|
|
160
|
+
where?: Record<string, unknown> // Filters
|
|
161
|
+
search?: string // Text search
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface ListResult<T> {
|
|
165
|
+
data: T[]
|
|
166
|
+
total: number
|
|
167
|
+
limit: number
|
|
168
|
+
offset: number
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## RLS Integration
|
|
173
|
+
|
|
174
|
+
### Core RLS Functions
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// core/lib/db.ts
|
|
178
|
+
|
|
179
|
+
// Query with RLS context (SELECT)
|
|
180
|
+
export async function queryWithRLS<T>(
|
|
181
|
+
query: string,
|
|
182
|
+
params: unknown[] = [],
|
|
183
|
+
userId?: string | null
|
|
184
|
+
): Promise<T[]>
|
|
185
|
+
|
|
186
|
+
// Query single result with RLS
|
|
187
|
+
export async function queryOneWithRLS<T>(
|
|
188
|
+
query: string,
|
|
189
|
+
params: unknown[] = [],
|
|
190
|
+
userId?: string | null
|
|
191
|
+
): Promise<T | null>
|
|
192
|
+
|
|
193
|
+
// Mutation with RLS (INSERT, UPDATE, DELETE)
|
|
194
|
+
export async function mutateWithRLS<T>(
|
|
195
|
+
query: string,
|
|
196
|
+
params: unknown[] = [],
|
|
197
|
+
userId?: string | null
|
|
198
|
+
): Promise<{ rows: T[], rowCount: number }>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**RLS Context:**
|
|
202
|
+
- Functions set PostgreSQL `app.user_id` session variable
|
|
203
|
+
- RLS policies use this to filter/restrict data access
|
|
204
|
+
- Always pass `userId` parameter for proper isolation
|
|
205
|
+
|
|
206
|
+
### Service Implementation with RLS
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { queryOneWithRLS, queryWithRLS, mutateWithRLS } from '@/core/lib/db'
|
|
210
|
+
|
|
211
|
+
export class CustomerService {
|
|
212
|
+
static async getById(id: string, userId: string): Promise<Customer | null> {
|
|
213
|
+
// Input validation
|
|
214
|
+
if (!id?.trim()) throw new Error('Customer ID is required')
|
|
215
|
+
if (!userId?.trim()) throw new Error('User ID is required')
|
|
216
|
+
|
|
217
|
+
return queryOneWithRLS<Customer>(
|
|
218
|
+
'SELECT * FROM "customers" WHERE id = $1',
|
|
219
|
+
[id],
|
|
220
|
+
userId // Sets app.user_id for RLS
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
static async list(userId: string, options: ListOptions = {}): Promise<ListResult<Customer>> {
|
|
225
|
+
const { limit = 20, offset = 0, orderBy = 'createdAt', orderDir = 'desc' } = options
|
|
226
|
+
|
|
227
|
+
const data = await queryWithRLS<Customer>(
|
|
228
|
+
`SELECT * FROM "customers"
|
|
229
|
+
ORDER BY "${orderBy}" ${orderDir.toUpperCase()}
|
|
230
|
+
LIMIT $1 OFFSET $2`,
|
|
231
|
+
[limit, offset],
|
|
232
|
+
userId
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
const countResult = await queryOneWithRLS<{ count: number }>(
|
|
236
|
+
'SELECT COUNT(*) as count FROM "customers"',
|
|
237
|
+
[],
|
|
238
|
+
userId
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
data,
|
|
243
|
+
total: countResult?.count ?? 0,
|
|
244
|
+
limit,
|
|
245
|
+
offset
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static async create(userId: string, teamId: string, data: CreateCustomer): Promise<Customer> {
|
|
250
|
+
const id = generateId()
|
|
251
|
+
const now = new Date().toISOString()
|
|
252
|
+
|
|
253
|
+
const result = await mutateWithRLS<Customer>(
|
|
254
|
+
`INSERT INTO "customers" (id, "userId", "teamId", name, email, "createdAt", "updatedAt")
|
|
255
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
256
|
+
RETURNING *`,
|
|
257
|
+
[id, userId, teamId, data.name, data.email, now, now],
|
|
258
|
+
userId
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
return result.rows[0]
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
static async update(id: string, userId: string, data: UpdateCustomer): Promise<Customer> {
|
|
265
|
+
const setClauses: string[] = []
|
|
266
|
+
const params: unknown[] = []
|
|
267
|
+
let paramIndex = 1
|
|
268
|
+
|
|
269
|
+
if (data.name !== undefined) {
|
|
270
|
+
setClauses.push(`name = $${paramIndex++}`)
|
|
271
|
+
params.push(data.name)
|
|
272
|
+
}
|
|
273
|
+
if (data.email !== undefined) {
|
|
274
|
+
setClauses.push(`email = $${paramIndex++}`)
|
|
275
|
+
params.push(data.email)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
setClauses.push(`"updatedAt" = $${paramIndex++}`)
|
|
279
|
+
params.push(new Date().toISOString())
|
|
280
|
+
|
|
281
|
+
params.push(id) // WHERE id = $N
|
|
282
|
+
|
|
283
|
+
const result = await mutateWithRLS<Customer>(
|
|
284
|
+
`UPDATE "customers"
|
|
285
|
+
SET ${setClauses.join(', ')}
|
|
286
|
+
WHERE id = $${paramIndex}
|
|
287
|
+
RETURNING *`,
|
|
288
|
+
params,
|
|
289
|
+
userId
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if (result.rowCount === 0) {
|
|
293
|
+
throw new Error('Customer not found')
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return result.rows[0]
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
static async delete(id: string, userId: string): Promise<boolean> {
|
|
300
|
+
const result = await mutateWithRLS(
|
|
301
|
+
'DELETE FROM "customers" WHERE id = $1',
|
|
302
|
+
[id],
|
|
303
|
+
userId
|
|
304
|
+
)
|
|
305
|
+
return result.rowCount > 0
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Transaction Pattern
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { getTransactionClient } from '@/core/lib/db'
|
|
314
|
+
|
|
315
|
+
export class TeamService {
|
|
316
|
+
static async createWithOwner(
|
|
317
|
+
userId: string,
|
|
318
|
+
teamData: CreateTeam
|
|
319
|
+
): Promise<{ team: Team; membership: TeamMember }> {
|
|
320
|
+
const tx = await getTransactionClient(userId)
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
// Create team
|
|
324
|
+
const team = await tx.queryOne<Team>(
|
|
325
|
+
`INSERT INTO "teams" (id, name, slug, "createdAt")
|
|
326
|
+
VALUES ($1, $2, $3, $4)
|
|
327
|
+
RETURNING *`,
|
|
328
|
+
[generateId(), teamData.name, teamData.slug, new Date().toISOString()]
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
// Add owner membership
|
|
332
|
+
const membership = await tx.queryOne<TeamMember>(
|
|
333
|
+
`INSERT INTO "teamMembers" (id, "userId", "teamId", role, "createdAt")
|
|
334
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
335
|
+
RETURNING *`,
|
|
336
|
+
[generateId(), userId, team.id, 'owner', new Date().toISOString()]
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
// Commit transaction
|
|
340
|
+
await tx.commit()
|
|
341
|
+
|
|
342
|
+
return { team, membership }
|
|
343
|
+
} catch (error) {
|
|
344
|
+
// Rollback on error
|
|
345
|
+
await tx.rollback()
|
|
346
|
+
throw error
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## BaseEntityService
|
|
353
|
+
|
|
354
|
+
Generic base class for entity CRUD operations:
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
// core/lib/services/base-entity.service.ts
|
|
358
|
+
|
|
359
|
+
export interface EntityServiceConfig {
|
|
360
|
+
tableName: string // e.g., 'customers'
|
|
361
|
+
fields: string[] // Custom fields (excluding system fields)
|
|
362
|
+
searchableFields?: string[] // Fields for text search
|
|
363
|
+
defaultOrderBy?: string // Default: 'createdAt'
|
|
364
|
+
defaultOrderDir?: 'asc' | 'desc' // Default: 'desc'
|
|
365
|
+
defaultLimit?: number // Default: 20
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export class BaseEntityService<TEntity, TCreate, TUpdate> {
|
|
369
|
+
protected config: EntityServiceConfig
|
|
370
|
+
|
|
371
|
+
constructor(config: EntityServiceConfig) {
|
|
372
|
+
this.config = {
|
|
373
|
+
defaultOrderBy: 'createdAt',
|
|
374
|
+
defaultOrderDir: 'desc',
|
|
375
|
+
defaultLimit: 20,
|
|
376
|
+
...config
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async getById(id: string, userId: string): Promise<TEntity | null> { ... }
|
|
381
|
+
async list(userId: string, options?: ListOptions): Promise<ListResult<TEntity>> { ... }
|
|
382
|
+
async create(userId: string, teamId: string, data: TCreate): Promise<TEntity> { ... }
|
|
383
|
+
async update(id: string, userId: string, data: TUpdate): Promise<TEntity> { ... }
|
|
384
|
+
async delete(id: string, userId: string): Promise<boolean> { ... }
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Extending BaseEntityService:**
|
|
389
|
+
```typescript
|
|
390
|
+
class ProductsService extends BaseEntityService<Product, CreateProduct, UpdateProduct> {
|
|
391
|
+
constructor() {
|
|
392
|
+
super({
|
|
393
|
+
tableName: 'products',
|
|
394
|
+
fields: ['title', 'description', 'status', 'price'],
|
|
395
|
+
searchableFields: ['title', 'description'],
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Custom methods
|
|
400
|
+
async getByStatus(userId: string, status: string): Promise<Product[]> {
|
|
401
|
+
return this.query(userId, {
|
|
402
|
+
where: { status },
|
|
403
|
+
orderBy: 'createdAt'
|
|
404
|
+
})
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Usage
|
|
409
|
+
const service = new ProductsService()
|
|
410
|
+
const products = await service.list(userId, { search: 'laptop' })
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Service Categories
|
|
414
|
+
|
|
415
|
+
### Data Access Services
|
|
416
|
+
Use `queryWithRLS` and `mutateWithRLS` for database operations:
|
|
417
|
+
- UserService, TeamService, CustomerService
|
|
418
|
+
- SubscriptionService, InvoiceService
|
|
419
|
+
- Always accept `userId` parameter
|
|
420
|
+
|
|
421
|
+
### Registry Services
|
|
422
|
+
Query pre-generated registries (O(1) lookups):
|
|
423
|
+
- ThemeService, EntityTypeService
|
|
424
|
+
- NamespaceService, ScopeService
|
|
425
|
+
- Synchronous operations, no RLS needed
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
// Registry service example
|
|
429
|
+
export class ThemeService {
|
|
430
|
+
static getAll(): ThemeConfig[] {
|
|
431
|
+
return Object.values(THEME_REGISTRY)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
static getByName(name: string): ThemeConfig | undefined {
|
|
435
|
+
return THEME_REGISTRY[name]
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Composite Services
|
|
441
|
+
Combine multiple services for complex operations:
|
|
442
|
+
- MembershipService (TeamMember + Subscription + Permissions)
|
|
443
|
+
- Returns rich objects with helper methods
|
|
444
|
+
|
|
445
|
+
## Existing Services (24)
|
|
446
|
+
|
|
447
|
+
| Service | Category | Purpose |
|
|
448
|
+
|---------|----------|---------|
|
|
449
|
+
| `UserService` | Data | User management, profile |
|
|
450
|
+
| `TeamService` | Data | Team CRUD, slug management |
|
|
451
|
+
| `TeamMemberService` | Data | Membership, role management |
|
|
452
|
+
| `MetaService` | Data | Flexible entity metadata |
|
|
453
|
+
| `PermissionService` | Registry | O(1) permission checks |
|
|
454
|
+
| `MembershipService` | Composite | Complete membership context |
|
|
455
|
+
| `SubscriptionService` | Data | Subscription lifecycle |
|
|
456
|
+
| `PlanService` | Registry | Plan queries |
|
|
457
|
+
| `UsageService` | Data | Usage tracking, quotas |
|
|
458
|
+
| `InvoiceService` | Data | Invoice management |
|
|
459
|
+
| `ThemeService` | Registry | Theme configuration |
|
|
460
|
+
| `EntityTypeService` | Registry | Entity registry queries |
|
|
461
|
+
| `NamespaceService` | Registry | Route namespaces |
|
|
462
|
+
| `ScopeService` | Registry | API scopes |
|
|
463
|
+
| `MiddlewareService` | Registry | Middleware execution |
|
|
464
|
+
| `RouteHandlerService` | Registry | Route handlers |
|
|
465
|
+
| `PluginService` | Registry | Plugin configuration |
|
|
466
|
+
| `ApiRoutesService` | Registry | API route discovery |
|
|
467
|
+
| `BlockService` | Registry | Page builder blocks |
|
|
468
|
+
| `TemplateService` | Registry | Template management |
|
|
469
|
+
| `TranslationService` | Registry | I18n translations |
|
|
470
|
+
| `UserFlagsService` | Data | Feature flags |
|
|
471
|
+
|
|
472
|
+
## Error Handling
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
static async getById(id: string, userId: string): Promise<Entity | null> {
|
|
476
|
+
// Input validation
|
|
477
|
+
if (!id || id.trim() === '') {
|
|
478
|
+
throw new Error('Entity ID is required')
|
|
479
|
+
}
|
|
480
|
+
if (!userId || userId.trim() === '') {
|
|
481
|
+
throw new Error('User ID is required for authentication')
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
return await queryOneWithRLS<Entity>(
|
|
486
|
+
'SELECT * FROM "entities" WHERE id = $1',
|
|
487
|
+
[id],
|
|
488
|
+
userId
|
|
489
|
+
)
|
|
490
|
+
} catch (error) {
|
|
491
|
+
console.error('EntityService.getById error:', error)
|
|
492
|
+
throw new Error(
|
|
493
|
+
error instanceof Error ? error.message : 'Failed to fetch entity'
|
|
494
|
+
)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
## JSDoc Documentation
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
/**
|
|
503
|
+
* Get entity by ID
|
|
504
|
+
*
|
|
505
|
+
* @param id - Entity UUID
|
|
506
|
+
* @param userId - Current user ID for RLS context
|
|
507
|
+
* @returns Entity object or null if not found
|
|
508
|
+
* @throws Error if ID or userId is missing
|
|
509
|
+
*
|
|
510
|
+
* @example
|
|
511
|
+
* const customer = await CustomerService.getById('cust-123', userId)
|
|
512
|
+
* if (customer) {
|
|
513
|
+
* console.log(customer.name)
|
|
514
|
+
* }
|
|
515
|
+
*/
|
|
516
|
+
static async getById(id: string, userId: string): Promise<Customer | null> {
|
|
517
|
+
// Implementation
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## Anti-Patterns
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
// NEVER: Instance methods
|
|
525
|
+
class BadService {
|
|
526
|
+
async getById(id: string, userId: string) {} // Not static!
|
|
527
|
+
}
|
|
528
|
+
const service = new BadService() // Requires instantiation
|
|
529
|
+
|
|
530
|
+
// CORRECT: Static methods
|
|
531
|
+
class GoodService {
|
|
532
|
+
static async getById(id: string, userId: string) {}
|
|
533
|
+
}
|
|
534
|
+
GoodService.getById('id', userId) // No instantiation
|
|
535
|
+
|
|
536
|
+
// NEVER: Skip RLS
|
|
537
|
+
const customers = await query('SELECT * FROM customers') // No RLS!
|
|
538
|
+
|
|
539
|
+
// CORRECT: Use RLS functions
|
|
540
|
+
const customers = await queryWithRLS('SELECT * FROM customers', [], userId)
|
|
541
|
+
|
|
542
|
+
// NEVER: Build SQL with string interpolation
|
|
543
|
+
const query = `SELECT * FROM customers WHERE name = '${name}'` // SQL injection!
|
|
544
|
+
|
|
545
|
+
// CORRECT: Use parameterized queries
|
|
546
|
+
const query = 'SELECT * FROM customers WHERE name = $1'
|
|
547
|
+
await queryWithRLS(query, [name], userId)
|
|
548
|
+
|
|
549
|
+
// NEVER: Skip input validation
|
|
550
|
+
static async delete(id: string, userId: string) {
|
|
551
|
+
await mutateWithRLS('DELETE FROM customers WHERE id = $1', [id], userId)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// CORRECT: Validate inputs
|
|
555
|
+
static async delete(id: string, userId: string) {
|
|
556
|
+
if (!id?.trim()) throw new Error('ID is required')
|
|
557
|
+
if (!userId?.trim()) throw new Error('User ID is required')
|
|
558
|
+
await mutateWithRLS('DELETE FROM customers WHERE id = $1', [id], userId)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// NEVER: Forget to pass userId for RLS
|
|
562
|
+
await queryWithRLS(query, params) // Missing userId!
|
|
563
|
+
|
|
564
|
+
// CORRECT: Always pass userId
|
|
565
|
+
await queryWithRLS(query, params, userId)
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
## Checklist
|
|
569
|
+
|
|
570
|
+
Before finalizing service implementation:
|
|
571
|
+
|
|
572
|
+
- [ ] All methods are static
|
|
573
|
+
- [ ] Uses RLS functions (queryWithRLS, mutateWithRLS)
|
|
574
|
+
- [ ] userId parameter on all database operations
|
|
575
|
+
- [ ] Input validation at method start
|
|
576
|
+
- [ ] Parameterized queries (no string interpolation)
|
|
577
|
+
- [ ] Proper error handling with descriptive messages
|
|
578
|
+
- [ ] JSDoc documentation with @param, @returns, @example
|
|
579
|
+
- [ ] Transactions for multi-step operations
|
|
580
|
+
- [ ] Consistent method signatures
|
|
581
|
+
|
|
582
|
+
## Related Skills
|
|
583
|
+
|
|
584
|
+
- `entity-system` - Entity definition patterns
|
|
585
|
+
- `entity-api` - API endpoint patterns
|
|
586
|
+
- `permissions-system` - Permission checking
|
|
587
|
+
- `database-migrations` - Migration patterns
|