@tekyzinc/gsd-t 2.46.11 → 2.50.11
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/CHANGELOG.md +11 -0
- package/README.md +22 -2
- package/bin/debug-ledger.js +193 -0
- package/bin/gsd-t.js +259 -1
- package/commands/gsd-t-debug.md +64 -1
- package/commands/gsd-t-execute.md +79 -3
- package/commands/gsd-t-help.md +18 -2
- package/commands/gsd-t-init.md +7 -0
- package/commands/gsd-t-integrate.md +16 -0
- package/commands/gsd-t-quick.md +56 -1
- package/commands/gsd-t-test-sync.md +5 -1
- package/commands/gsd-t-verify.md +6 -1
- package/commands/gsd-t-wave.md +26 -0
- package/docs/GSD-T-README.md +83 -1
- package/docs/architecture.md +9 -1
- package/docs/requirements.md +30 -0
- package/package.json +1 -1
- package/templates/CLAUDE-global.md +19 -2
- package/templates/stacks/_security.md +243 -0
- package/templates/stacks/desktop.ini +2 -0
- package/templates/stacks/docker.md +202 -0
- package/templates/stacks/firebase.md +166 -0
- package/templates/stacks/flutter.md +205 -0
- package/templates/stacks/github-actions.md +201 -0
- package/templates/stacks/graphql.md +216 -0
- package/templates/stacks/neo4j.md +218 -0
- package/templates/stacks/nextjs.md +184 -0
- package/templates/stacks/node-api.md +196 -0
- package/templates/stacks/playwright.md +528 -0
- package/templates/stacks/postgresql.md +225 -0
- package/templates/stacks/python.md +243 -0
- package/templates/stacks/react-native.md +216 -0
- package/templates/stacks/react.md +293 -0
- package/templates/stacks/redux.md +193 -0
- package/templates/stacks/rest-api.md +202 -0
- package/templates/stacks/supabase.md +188 -0
- package/templates/stacks/tailwind.md +169 -0
- package/templates/stacks/typescript.md +176 -0
- package/templates/stacks/vite.md +176 -0
- package/templates/stacks/vue.md +189 -0
- package/templates/stacks/zustand.md +203 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Flutter Standards
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. State Management — Riverpod
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── Use Riverpod for state management — not setState for anything beyond local UI
|
|
12
|
+
├── Provider for each data source (API, local storage, computed)
|
|
13
|
+
├── AsyncNotifierProvider for server data with loading/error states
|
|
14
|
+
├── StateProvider for simple toggles/filters
|
|
15
|
+
├── NEVER store server data in StatefulWidget setState
|
|
16
|
+
└── ref.watch for reactive reads, ref.read for one-shot actions (onTap)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**BAD**
|
|
20
|
+
```dart
|
|
21
|
+
class _MyState extends State<MyWidget> {
|
|
22
|
+
List<User> users = [];
|
|
23
|
+
void initState() {
|
|
24
|
+
super.initState();
|
|
25
|
+
fetchUsers().then((u) => setState(() => users = u));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**GOOD**
|
|
31
|
+
```dart
|
|
32
|
+
@riverpod
|
|
33
|
+
class UsersNotifier extends _$UsersNotifier {
|
|
34
|
+
@override
|
|
35
|
+
Future<List<User>> build() => userRepository.getAll();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// In widget:
|
|
39
|
+
final users = ref.watch(usersNotifierProvider);
|
|
40
|
+
users.when(
|
|
41
|
+
data: (list) => UserListView(users: list),
|
|
42
|
+
loading: () => const LoadingSkeleton(),
|
|
43
|
+
error: (e, _) => ErrorView(message: e.toString()),
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. Widget Design
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
MANDATORY:
|
|
53
|
+
├── Max 150 lines per widget file — extract sub-widgets
|
|
54
|
+
├── Prefer StatelessWidget — use ConsumerWidget with Riverpod
|
|
55
|
+
├── Extract build method sub-trees into separate widgets (not methods)
|
|
56
|
+
├── One widget per file for major widgets
|
|
57
|
+
├── Use const constructors wherever possible
|
|
58
|
+
└── No business logic in build() — move to providers/notifiers
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**BAD** — `Widget _buildHeader()` methods that return widget trees.
|
|
62
|
+
|
|
63
|
+
**GOOD** — Extract `HeaderWidget` as its own `ConsumerWidget`.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. Navigation
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
MANDATORY:
|
|
71
|
+
├── Use go_router for declarative routing
|
|
72
|
+
├── Define all routes in a single router config file
|
|
73
|
+
├── Use named routes — NEVER hardcode path strings in widgets
|
|
74
|
+
├── Handle deep links in the router config
|
|
75
|
+
└── Redirect unauthenticated users in router guards — not in widgets
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**GOOD**
|
|
79
|
+
```dart
|
|
80
|
+
final router = GoRouter(
|
|
81
|
+
routes: [
|
|
82
|
+
GoRoute(path: '/', builder: (_, __) => const HomePage()),
|
|
83
|
+
GoRoute(path: '/users/:id', builder: (_, state) =>
|
|
84
|
+
UserDetailPage(id: state.pathParameters['id']!)),
|
|
85
|
+
],
|
|
86
|
+
redirect: (context, state) {
|
|
87
|
+
final isLoggedIn = ref.read(authProvider).isLoggedIn;
|
|
88
|
+
if (!isLoggedIn) return '/login';
|
|
89
|
+
return null;
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 4. Data Models
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
MANDATORY:
|
|
100
|
+
├── Use freezed for immutable data classes with copyWith, ==, and serialization
|
|
101
|
+
├── JSON serialization via json_serializable or freezed's fromJson/toJson
|
|
102
|
+
├── NEVER use raw Map<String, dynamic> between layers
|
|
103
|
+
├── Separate models: API DTO → Domain Model → UI ViewModel (if needed)
|
|
104
|
+
└── Use sealed classes (Dart 3) for state unions
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**GOOD**
|
|
108
|
+
```dart
|
|
109
|
+
@freezed
|
|
110
|
+
class User with _$User {
|
|
111
|
+
const factory User({
|
|
112
|
+
required String id,
|
|
113
|
+
required String name,
|
|
114
|
+
required String email,
|
|
115
|
+
required UserRole role,
|
|
116
|
+
}) = _User;
|
|
117
|
+
|
|
118
|
+
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 5. Repository Pattern
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
MANDATORY:
|
|
128
|
+
├── All API/DB calls go through repository classes — widgets never call HTTP directly
|
|
129
|
+
├── Repository returns domain models — not raw JSON or Response objects
|
|
130
|
+
├── Handle errors in repository — throw typed exceptions
|
|
131
|
+
├── Abstract repository interface for testability
|
|
132
|
+
└── Use dio or http package — configure interceptors for auth/logging
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 6. Platform-Specific Code
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
MANDATORY:
|
|
141
|
+
├── Use Platform.isAndroid/isIOS checks sparingly — prefer adaptive widgets
|
|
142
|
+
├── Platform channels: define a clear Dart interface, handle errors on both sides
|
|
143
|
+
├── Test on both platforms before marking task complete
|
|
144
|
+
├── Use device_info_plus for runtime capability detection
|
|
145
|
+
└── Handle permissions gracefully — explain why before requesting
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 7. Performance
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
MANDATORY:
|
|
154
|
+
├── Use const widgets wherever possible — prevents unnecessary rebuilds
|
|
155
|
+
├── Use ListView.builder for long lists — NEVER ListView with children for 20+ items
|
|
156
|
+
├── Cache images with CachedNetworkImage — not Image.network
|
|
157
|
+
├── Avoid rebuilding entire widget trees — use granular providers
|
|
158
|
+
├── Profile with Flutter DevTools before optimizing
|
|
159
|
+
└── Minimize widget depth — deep nesting kills readability and performance
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 8. Testing
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
MANDATORY:
|
|
168
|
+
├── Unit tests for providers, notifiers, and business logic
|
|
169
|
+
├── Widget tests for UI components with pumpWidget
|
|
170
|
+
├── Integration tests for critical user flows
|
|
171
|
+
├── Use mocktail for mocking — not mockito (null-safety friendly)
|
|
172
|
+
├── Test loading, error, and empty states — not just happy path
|
|
173
|
+
└── Golden tests for complex custom widgets (optional but recommended)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 9. Anti-Patterns
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
NEVER:
|
|
182
|
+
├── setState for server data (use Riverpod)
|
|
183
|
+
├── Raw Map<String, dynamic> between layers — use typed models
|
|
184
|
+
├── Build methods over 80 lines — extract widgets
|
|
185
|
+
├── Hardcoded strings — use constants or l10n
|
|
186
|
+
├── Nested callbacks deeper than 2 levels — use async/await
|
|
187
|
+
├── context.read in build() — use ref.watch
|
|
188
|
+
├── Ignoring dispose() — always clean up controllers and streams
|
|
189
|
+
└── print() for logging — use logger package
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Flutter Verification Checklist
|
|
195
|
+
|
|
196
|
+
- [ ] Riverpod for state management — no setState for server data
|
|
197
|
+
- [ ] Widgets under 150 lines with const constructors
|
|
198
|
+
- [ ] go_router with named routes — no hardcoded path strings
|
|
199
|
+
- [ ] freezed models for all data — no raw Maps
|
|
200
|
+
- [ ] Repository pattern — widgets never call HTTP
|
|
201
|
+
- [ ] Loading, error, and empty states handled in every async widget
|
|
202
|
+
- [ ] ListView.builder for dynamic lists
|
|
203
|
+
- [ ] Tests cover providers, widgets, and critical flows
|
|
204
|
+
- [ ] Tested on both iOS and Android
|
|
205
|
+
- [ ] No print() in committed code
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# GitHub Actions Standards
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Workflow Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── One workflow per concern: ci.yml, deploy.yml, release.yml
|
|
12
|
+
├── Name workflows clearly: name: "CI — Lint, Test, Build"
|
|
13
|
+
├── Trigger on specific events — NEVER use on: push without branch filters
|
|
14
|
+
├── Use workflow_dispatch for manual triggers on deploy/release workflows
|
|
15
|
+
└── Keep workflows under 200 lines — extract reusable logic to composite actions
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**GOOD**
|
|
19
|
+
```yaml
|
|
20
|
+
name: CI — Lint, Test, Build
|
|
21
|
+
on:
|
|
22
|
+
push:
|
|
23
|
+
branches: [main, develop]
|
|
24
|
+
pull_request:
|
|
25
|
+
branches: [main]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2. Job Design
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
MANDATORY:
|
|
34
|
+
├── Name jobs descriptively: jobs: lint:, jobs: test-unit:, jobs: build:
|
|
35
|
+
├── Use needs: for job dependencies — parallelize independent jobs
|
|
36
|
+
├── Set timeout-minutes on every job (default 360 is too long)
|
|
37
|
+
├── Use matrix strategy for multi-version/multi-platform testing
|
|
38
|
+
└── Fail fast by default — set fail-fast: false only when you need all results
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**GOOD**
|
|
42
|
+
```yaml
|
|
43
|
+
jobs:
|
|
44
|
+
lint:
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
timeout-minutes: 10
|
|
47
|
+
steps: ...
|
|
48
|
+
|
|
49
|
+
test:
|
|
50
|
+
needs: lint
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
timeout-minutes: 15
|
|
53
|
+
strategy:
|
|
54
|
+
matrix:
|
|
55
|
+
node-version: [18, 20]
|
|
56
|
+
steps: ...
|
|
57
|
+
|
|
58
|
+
build:
|
|
59
|
+
needs: test
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
timeout-minutes: 10
|
|
62
|
+
steps: ...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. Caching
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
MANDATORY:
|
|
71
|
+
├── Cache dependencies (node_modules, pip, gradle) — saves minutes per run
|
|
72
|
+
├── Use actions/cache or setup-node with cache: 'npm'
|
|
73
|
+
├── Cache key must include lock file hash — invalidates on dependency changes
|
|
74
|
+
├── Cache Playwright browsers separately (they're large)
|
|
75
|
+
└── Set restore-keys for partial cache hits
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**GOOD**
|
|
79
|
+
```yaml
|
|
80
|
+
- uses: actions/setup-node@v4
|
|
81
|
+
with:
|
|
82
|
+
node-version: 20
|
|
83
|
+
cache: 'npm'
|
|
84
|
+
|
|
85
|
+
# Or explicit caching:
|
|
86
|
+
- uses: actions/cache@v4
|
|
87
|
+
with:
|
|
88
|
+
path: ~/.npm
|
|
89
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
90
|
+
restore-keys: ${{ runner.os }}-npm-
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 4. Secrets Management
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
MANDATORY:
|
|
99
|
+
├── NEVER hardcode secrets, tokens, or API keys in workflow files
|
|
100
|
+
├── Use GitHub repository or environment secrets
|
|
101
|
+
├── Use environment-scoped secrets for deploy workflows (staging vs production)
|
|
102
|
+
├── Limit secret access: only jobs that need them should reference them
|
|
103
|
+
├── NEVER echo or log secrets — even accidentally via debug output
|
|
104
|
+
└── Rotate secrets periodically — set reminders
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**BAD**
|
|
108
|
+
```yaml
|
|
109
|
+
env:
|
|
110
|
+
API_KEY: sk-abc123 # NEVER DO THIS
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**GOOD**
|
|
114
|
+
```yaml
|
|
115
|
+
env:
|
|
116
|
+
API_KEY: ${{ secrets.API_KEY }}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 5. Actions Versioning
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
MANDATORY:
|
|
125
|
+
├── Pin actions to major version: uses: actions/checkout@v4
|
|
126
|
+
├── For security-critical workflows, pin to SHA: uses: actions/checkout@abc123
|
|
127
|
+
├── NEVER use @latest or @main for third-party actions
|
|
128
|
+
├── Review third-party actions before use — check the source
|
|
129
|
+
└── Prefer official actions (actions/*, github/*) over community ones
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 6. Deployment Workflows
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
MANDATORY:
|
|
138
|
+
├── Require manual approval for production deploys (environment protection rules)
|
|
139
|
+
├── Deploy to staging first — production only after staging passes
|
|
140
|
+
├── Include a rollback step or document the rollback procedure
|
|
141
|
+
├── Tag releases with version numbers after successful deploy
|
|
142
|
+
├── Use concurrency groups to prevent parallel deploys
|
|
143
|
+
└── Post-deploy: run smoke tests against the deployed environment
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**GOOD**
|
|
147
|
+
```yaml
|
|
148
|
+
deploy-production:
|
|
149
|
+
needs: deploy-staging
|
|
150
|
+
runs-on: ubuntu-latest
|
|
151
|
+
environment:
|
|
152
|
+
name: production
|
|
153
|
+
url: https://app.example.com
|
|
154
|
+
concurrency:
|
|
155
|
+
group: deploy-production
|
|
156
|
+
cancel-in-progress: false
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 7. Notifications and Artifacts
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
RECOMMENDED:
|
|
165
|
+
├── Upload test results and coverage as artifacts
|
|
166
|
+
├── Notify on failure (Slack, email) — don't notify on every success
|
|
167
|
+
├── Upload build artifacts for deploy jobs to download
|
|
168
|
+
├── Set artifact retention days (default 90 is often too long)
|
|
169
|
+
└── Use job summaries (echo >> $GITHUB_STEP_SUMMARY) for key metrics
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 8. Anti-Patterns
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
NEVER:
|
|
178
|
+
├── on: push without branch filters (triggers on every branch)
|
|
179
|
+
├── Hardcoded secrets in workflow files
|
|
180
|
+
├── @latest or @main for actions — pin versions
|
|
181
|
+
├── Jobs without timeout-minutes
|
|
182
|
+
├── Skipping cache — wastes minutes on every run
|
|
183
|
+
├── Deploy to production without staging gate
|
|
184
|
+
├── Single monolithic workflow file (500+ lines)
|
|
185
|
+
└── Running expensive jobs on PRs from forks without approval
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## GitHub Actions Verification Checklist
|
|
191
|
+
|
|
192
|
+
- [ ] Workflows named descriptively with specific triggers
|
|
193
|
+
- [ ] Branch filters on push triggers
|
|
194
|
+
- [ ] Jobs have timeout-minutes set
|
|
195
|
+
- [ ] Dependencies cached (npm, pip, etc.)
|
|
196
|
+
- [ ] Secrets in GitHub Secrets — never hardcoded
|
|
197
|
+
- [ ] Actions pinned to major version or SHA
|
|
198
|
+
- [ ] Deploy requires staging → production progression
|
|
199
|
+
- [ ] Production deploy has environment protection
|
|
200
|
+
- [ ] Concurrency groups prevent parallel deploys
|
|
201
|
+
- [ ] Test artifacts uploaded
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# GraphQL Standards
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Schema Design
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── Schema-first design — define the schema, then implement resolvers
|
|
12
|
+
├── Use PascalCase for types: User, OrderItem
|
|
13
|
+
├── Use camelCase for fields: firstName, createdAt
|
|
14
|
+
├── Use UPPER_SNAKE_CASE for enum values: ACTIVE, PENDING_REVIEW
|
|
15
|
+
├── Input types for mutations: CreateUserInput, UpdateOrderInput
|
|
16
|
+
├── Every mutation returns the affected type (not just Boolean)
|
|
17
|
+
└── Add descriptions to all types and fields — they power documentation
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**GOOD**
|
|
21
|
+
```graphql
|
|
22
|
+
"""A registered user in the system."""
|
|
23
|
+
type User {
|
|
24
|
+
id: ID!
|
|
25
|
+
"""User's display name."""
|
|
26
|
+
displayName: String!
|
|
27
|
+
email: String!
|
|
28
|
+
role: UserRole!
|
|
29
|
+
orders(first: Int = 20, after: String): OrderConnection!
|
|
30
|
+
createdAt: DateTime!
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
enum UserRole {
|
|
34
|
+
ADMIN
|
|
35
|
+
MEMBER
|
|
36
|
+
VIEWER
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
input CreateUserInput {
|
|
40
|
+
displayName: String!
|
|
41
|
+
email: String!
|
|
42
|
+
role: UserRole = MEMBER
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 2. Query Design
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
MANDATORY:
|
|
52
|
+
├── Singular for single resource: user(id: ID!): User
|
|
53
|
+
├── Plural for collections: users(first: Int, after: String, filter: UserFilter): UserConnection!
|
|
54
|
+
├── Use connection pattern (Relay) for paginated lists
|
|
55
|
+
├── Accept filter input types — not individual filter args
|
|
56
|
+
├── NEVER return unbounded lists — always require pagination args
|
|
57
|
+
└── Nullable return for single lookups (user may not exist)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**GOOD**
|
|
61
|
+
```graphql
|
|
62
|
+
type Query {
|
|
63
|
+
user(id: ID!): User
|
|
64
|
+
users(first: Int = 20, after: String, filter: UserFilter): UserConnection!
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
input UserFilter {
|
|
68
|
+
role: UserRole
|
|
69
|
+
isActive: Boolean
|
|
70
|
+
search: String
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type UserConnection {
|
|
74
|
+
edges: [UserEdge!]!
|
|
75
|
+
pageInfo: PageInfo!
|
|
76
|
+
totalCount: Int!
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type UserEdge {
|
|
80
|
+
node: User!
|
|
81
|
+
cursor: String!
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type PageInfo {
|
|
85
|
+
hasNextPage: Boolean!
|
|
86
|
+
endCursor: String
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 3. Mutation Design
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
MANDATORY:
|
|
96
|
+
├── Verb-first naming: createUser, updateOrder, deleteComment
|
|
97
|
+
├── Accept a single input argument: createUser(input: CreateUserInput!): User!
|
|
98
|
+
├── Return the mutated object — not Boolean or generic status
|
|
99
|
+
├── Use union types for error handling (preferred) or throw errors
|
|
100
|
+
├── Validate inputs in resolvers — schema types are not sufficient
|
|
101
|
+
└── Mutations must be idempotent where possible
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**GOOD**
|
|
105
|
+
```graphql
|
|
106
|
+
type Mutation {
|
|
107
|
+
createUser(input: CreateUserInput!): CreateUserPayload!
|
|
108
|
+
updateUser(id: ID!, input: UpdateUserInput!): User!
|
|
109
|
+
deleteUser(id: ID!): DeleteUserPayload!
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type CreateUserPayload {
|
|
113
|
+
user: User
|
|
114
|
+
errors: [ValidationError!]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
type ValidationError {
|
|
118
|
+
field: String!
|
|
119
|
+
message: String!
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 4. Resolver Patterns
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
MANDATORY:
|
|
129
|
+
├── Thin resolvers — delegate to service/data layer, don't put business logic in resolvers
|
|
130
|
+
├── Use DataLoader for N+1 prevention — batch and cache database lookups
|
|
131
|
+
├── Auth checks in resolvers or middleware — not in the service layer
|
|
132
|
+
├── Return null for not-found, throw for unauthorized/forbidden
|
|
133
|
+
└── Log errors with context (query name, variables, user ID)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**GOOD**
|
|
137
|
+
```typescript
|
|
138
|
+
const resolvers = {
|
|
139
|
+
Query: {
|
|
140
|
+
user: async (_, { id }, { dataSources, user }) => {
|
|
141
|
+
if (!user) throw new AuthenticationError('Must be logged in');
|
|
142
|
+
return dataSources.userService.getById(id);
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
User: {
|
|
146
|
+
orders: (parent, args, { dataSources }) =>
|
|
147
|
+
dataSources.orderLoader.load({ userId: parent.id, ...args }),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 5. N+1 Prevention — DataLoader
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
MANDATORY:
|
|
158
|
+
├── Use DataLoader for any field that resolves per-item in a list
|
|
159
|
+
├── Create loaders per request (new instance in context factory)
|
|
160
|
+
├── Batch function receives array of keys, returns array of results in same order
|
|
161
|
+
└── Cache is request-scoped — not shared between requests
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**GOOD**
|
|
165
|
+
```typescript
|
|
166
|
+
const userLoader = new DataLoader<string, User>(async (ids) => {
|
|
167
|
+
const users = await db.query('SELECT * FROM users WHERE id = ANY($1)', [ids]);
|
|
168
|
+
const userMap = new Map(users.map(u => [u.id, u]));
|
|
169
|
+
return ids.map(id => userMap.get(id) ?? new Error(`User ${id} not found`));
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 6. Client-Side Patterns
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
MANDATORY:
|
|
179
|
+
├── Co-locate queries with components — query file next to the component
|
|
180
|
+
├── Use fragments for shared field selections
|
|
181
|
+
├── Name all operations: query GetUser, mutation CreateOrder
|
|
182
|
+
├── Use generated types (graphql-codegen) — NEVER manually type query results
|
|
183
|
+
├── Handle loading, error, and empty states for every query
|
|
184
|
+
└── Cache update after mutations: refetch or update cache directly
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 7. Anti-Patterns
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
NEVER:
|
|
193
|
+
├── Unbounded list queries without pagination
|
|
194
|
+
├── Business logic in resolvers — delegate to services
|
|
195
|
+
├── N+1 queries — use DataLoader
|
|
196
|
+
├── Anonymous operations (unnamed queries/mutations)
|
|
197
|
+
├── Deeply nested queries without depth limiting
|
|
198
|
+
├── Returning Boolean from mutations — return the affected type
|
|
199
|
+
├── Over-fetching: requesting all fields when you need two
|
|
200
|
+
└── Manually typing query results — use codegen
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## GraphQL Verification Checklist
|
|
206
|
+
|
|
207
|
+
- [ ] Schema-first with descriptions on all types/fields
|
|
208
|
+
- [ ] Connection pattern for paginated lists
|
|
209
|
+
- [ ] All mutations return affected type (not Boolean)
|
|
210
|
+
- [ ] DataLoader used for N+1 prevention
|
|
211
|
+
- [ ] Input validation in resolvers
|
|
212
|
+
- [ ] Auth checks in resolvers or middleware
|
|
213
|
+
- [ ] Operations named (query GetUser, not anonymous)
|
|
214
|
+
- [ ] Generated types via codegen
|
|
215
|
+
- [ ] Loading, error, empty states handled client-side
|
|
216
|
+
- [ ] Query depth limiting configured
|