@regardio/dev 1.23.0 → 2.0.1
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/README.md +2 -2
- package/dist/bin/ship/hotfix.bin.mjs +140 -0
- package/dist/bin/ship/production.bin.mjs +120 -0
- package/dist/bin/ship/staging.bin.mjs +70 -0
- package/dist/bin/ship/utils-BQ-JZ2D5.mjs +45 -0
- package/dist/playwright/index.d.mts +24 -0
- package/dist/playwright/index.mjs +61 -0
- package/dist/vitest/node.d.mts +22 -0
- package/dist/vitest/node.mjs +28 -0
- package/dist/vitest/react.d.mts +22 -0
- package/dist/vitest/react.mjs +28 -0
- package/docs/en/README.md +95 -0
- package/docs/en/agents.md +57 -0
- package/docs/en/standards/api.md +324 -0
- package/docs/en/standards/coding.md +144 -0
- package/docs/en/standards/commits.md +111 -0
- package/docs/en/standards/documentation.md +173 -0
- package/docs/en/standards/naming.md +180 -0
- package/docs/en/standards/principles.md +84 -0
- package/docs/en/standards/react.md +246 -0
- package/docs/en/standards/sql.md +258 -0
- package/docs/en/standards/testing.md +139 -0
- package/docs/en/standards/writing.md +119 -0
- package/docs/en/tools/biome.md +89 -0
- package/docs/en/tools/commitlint.md +92 -0
- package/docs/en/tools/dependencies.md +116 -0
- package/docs/en/tools/husky.md +90 -0
- package/docs/en/tools/markdownlint.md +84 -0
- package/docs/en/tools/playwright.md +117 -0
- package/docs/en/tools/releases.md +242 -0
- package/docs/en/tools/typescript.md +89 -0
- package/docs/en/tools/vitest.md +146 -0
- package/package.json +58 -70
- package/src/biome/preset.json +3 -0
- package/templates/changeset/README.md +14 -0
- package/templates/changeset/config.json +11 -0
- package/templates/github/release.yml +77 -0
- package/dist/bin/exec/clean.d.ts +0 -3
- package/dist/bin/exec/clean.d.ts.map +0 -1
- package/dist/bin/exec/clean.js +0 -25
- package/dist/bin/exec/clean.test.d.ts +0 -2
- package/dist/bin/exec/clean.test.d.ts.map +0 -1
- package/dist/bin/exec/clean.test.js +0 -45
- package/dist/bin/exec/husky.d.ts +0 -3
- package/dist/bin/exec/husky.d.ts.map +0 -1
- package/dist/bin/exec/husky.js +0 -9
- package/dist/bin/exec/p.d.ts +0 -3
- package/dist/bin/exec/p.d.ts.map +0 -1
- package/dist/bin/exec/p.js +0 -8
- package/dist/bin/exec/s.d.ts +0 -3
- package/dist/bin/exec/s.d.ts.map +0 -1
- package/dist/bin/exec/s.js +0 -8
- package/dist/bin/exec/tsc.d.ts +0 -3
- package/dist/bin/exec/tsc.d.ts.map +0 -1
- package/dist/bin/exec/tsc.js +0 -8
- package/dist/bin/lint/biome.d.ts +0 -3
- package/dist/bin/lint/biome.d.ts.map +0 -1
- package/dist/bin/lint/biome.js +0 -8
- package/dist/bin/lint/commit.d.ts +0 -3
- package/dist/bin/lint/commit.d.ts.map +0 -1
- package/dist/bin/lint/commit.js +0 -8
- package/dist/bin/lint/md.d.ts +0 -3
- package/dist/bin/lint/md.d.ts.map +0 -1
- package/dist/bin/lint/md.js +0 -16
- package/dist/bin/lint/package.d.ts +0 -4
- package/dist/bin/lint/package.d.ts.map +0 -1
- package/dist/bin/lint/package.js +0 -81
- package/dist/bin/lint/package.test.d.ts +0 -2
- package/dist/bin/lint/package.test.d.ts.map +0 -1
- package/dist/bin/lint/package.test.js +0 -65
- package/dist/bin/ship/hotfix.d.ts +0 -3
- package/dist/bin/ship/hotfix.d.ts.map +0 -1
- package/dist/bin/ship/hotfix.js +0 -141
- package/dist/bin/ship/production.d.ts +0 -3
- package/dist/bin/ship/production.d.ts.map +0 -1
- package/dist/bin/ship/production.js +0 -124
- package/dist/bin/ship/staging.d.ts +0 -3
- package/dist/bin/ship/staging.d.ts.map +0 -1
- package/dist/bin/ship/staging.js +0 -51
- package/dist/bin/ship/utils.d.ts +0 -9
- package/dist/bin/ship/utils.d.ts.map +0 -1
- package/dist/bin/ship/utils.js +0 -63
- package/dist/bin/ship/utils.test.d.ts +0 -2
- package/dist/bin/ship/utils.test.d.ts.map +0 -1
- package/dist/bin/ship/utils.test.js +0 -127
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -101
- package/dist/playwright/index.d.ts +0 -10
- package/dist/playwright/index.d.ts.map +0 -1
- package/dist/playwright/index.js +0 -42
- package/dist/playwright/index.test.d.ts +0 -2
- package/dist/playwright/index.test.d.ts.map +0 -1
- package/dist/playwright/index.test.js +0 -55
- package/dist/testing/setup-react.d.ts +0 -2
- package/dist/testing/setup-react.d.ts.map +0 -1
- package/dist/testing/setup-react.js +0 -1
- package/dist/vitest/node.d.ts +0 -22
- package/dist/vitest/node.d.ts.map +0 -1
- package/dist/vitest/node.js +0 -16
- package/dist/vitest/react.d.ts +0 -17
- package/dist/vitest/react.d.ts.map +0 -1
- package/dist/vitest/react.js +0 -12
- package/src/bin/exec/clean.test.ts +0 -63
- package/src/bin/exec/clean.ts +0 -36
- package/src/bin/exec/husky.ts +0 -14
- package/src/bin/exec/p.ts +0 -13
- package/src/bin/exec/s.ts +0 -13
- package/src/bin/exec/tsc.ts +0 -13
- package/src/bin/lint/biome.ts +0 -13
- package/src/bin/lint/commit.ts +0 -13
- package/src/bin/lint/md.ts +0 -28
- package/src/bin/lint/package.test.ts +0 -83
- package/src/bin/lint/package.ts +0 -108
- package/src/bin/ship/hotfix.ts +0 -241
- package/src/bin/ship/production.ts +0 -240
- package/src/bin/ship/staging.ts +0 -108
- package/src/bin/ship/utils.test.ts +0 -178
- package/src/bin/ship/utils.ts +0 -109
- package/src/config.test.ts +0 -129
- package/src/markdownlint/markdownlint-cli2.jsonc +0 -9
- package/src/playwright/index.test.ts +0 -73
- package/src/playwright/index.ts +0 -63
- package/src/templates/release.yml +0 -128
- package/src/testing/setup-react.ts +0 -8
- package/src/vitest/node.ts +0 -25
- package/src/vitest/react.ts +0 -19
- /package/{src → templates}/sqlfluff/setup.cfg +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
title: AI Agent Guidelines
|
|
4
|
+
type: guide
|
|
5
|
+
status: published
|
|
6
|
+
summary: Instructions for AI coding assistants working with Regardio projects
|
|
7
|
+
related: [coding-standards, react-standards, development-principles]
|
|
8
|
+
locale: en-US
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# AI Agent Guidelines
|
|
12
|
+
|
|
13
|
+
Baseline expectations for AI coding assistants working in Regardio projects. Follow the linked standards — they apply equally to agent and human contributions.
|
|
14
|
+
|
|
15
|
+
## How Agents Should Work
|
|
16
|
+
|
|
17
|
+
- **Scope changes tightly** - Only change what the task requires. Do not refactor adjacent code, add features, or reorganize files unless asked.
|
|
18
|
+
- **Read before writing** - Understand existing patterns in the file and its neighbors before editing. Match the style you find.
|
|
19
|
+
- **Avoid unnecessary complexity** - Only implement what is explicitly required. Do not introduce abstractions, helpers, or utilities speculatively.
|
|
20
|
+
- **No emojis** - Unless explicitly requested.
|
|
21
|
+
- **Preserve comments and documentation** - Do not add or remove comments unless the task calls for it.
|
|
22
|
+
- **Explain uncertainty** - If something is ambiguous, say so rather than guessing.
|
|
23
|
+
|
|
24
|
+
## Implementation Workflow
|
|
25
|
+
|
|
26
|
+
When working on any non-trivial task, follow this sequence:
|
|
27
|
+
|
|
28
|
+
1. **Understand the business logic** — Gather context before writing code. Read relevant domain documents. Know what is actually needed; do not implement what was not asked for.
|
|
29
|
+
2. **Evaluate existing solutions** — Check for well-maintained libraries or utilities before writing custom code. Do not reinvent; do not introduce a dependency without verifying it exists and is actively maintained.
|
|
30
|
+
3. **Define tests first** — Identify what tests describe the expected behavior before implementing. Tests are a contract. Do not modify tests to make them pass.
|
|
31
|
+
4. **Implement with reusability in mind** — Prefer simple, readable code. Duplicate until a clear pattern emerges, then extract. Avoid speculative abstractions.
|
|
32
|
+
5. **Stop if it gets complicated** — Growing complexity despite good preparation is a signal. Surface the difficulty, reconsider the approach, and back out rather than pushing through.
|
|
33
|
+
6. **Document intent, stay lean** — Comments explain *why*, not *what*. Check existing documentation and business logic context before starting work.
|
|
34
|
+
|
|
35
|
+
## Standards
|
|
36
|
+
|
|
37
|
+
- [Coding Standards](./standards/coding.md) — TypeScript, React, and general coding patterns
|
|
38
|
+
- [React and TypeScript Standards](./standards/react.md) — Component, hook, and state patterns
|
|
39
|
+
- [SQL Schema Standards](./standards/sql.md) — PostgreSQL naming, structure, and access control
|
|
40
|
+
- [Development Principles](./standards/principles.md) — Code quality, architecture, security
|
|
41
|
+
- [API Standards](./standards/api.md) — API design and implementation
|
|
42
|
+
- [Naming Conventions](./standards/naming.md) — Naming across TypeScript, SQL, CSS, Git
|
|
43
|
+
- [Testing Approach](./standards/testing.md) — Testing philosophy and patterns
|
|
44
|
+
- [Commit Conventions](./standards/commits.md) — Conventional commits and changelog generation
|
|
45
|
+
- [Documentation Standard](./standards/documentation.md) — Document structure and conventions
|
|
46
|
+
- [Writing](./standards/writing.md) — Voice, tone, and language
|
|
47
|
+
|
|
48
|
+
## Commands
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pnpm fix # Fix and lint everything
|
|
52
|
+
pnpm lint # Lint only
|
|
53
|
+
pnpm test # Run tests
|
|
54
|
+
pnpm typecheck # Type check
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Run `typecheck` and `lint` before marking a task complete.
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
title: "API"
|
|
4
|
+
description: "REST endpoint shape, error handling, security, and performance patterns for Regardio APIs."
|
|
5
|
+
publishedAt: 2026-04-17
|
|
6
|
+
order: 8
|
|
7
|
+
language: "en"
|
|
8
|
+
status: "published"
|
|
9
|
+
kind: "reference"
|
|
10
|
+
area: "dev"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Regardio APIs are resource-oriented, predictable, and consistent about errors. This page catalogues the shape of endpoints, the response envelope, and the patterns for authentication, validation, caching, and testing. The contract a client sees — URL, verb, status, envelope — stays stable; the implementation behind it is free to change.
|
|
14
|
+
|
|
15
|
+
## Design
|
|
16
|
+
|
|
17
|
+
### RESTful URLs
|
|
18
|
+
|
|
19
|
+
- One resource per URL
|
|
20
|
+
- Standard HTTP verbs carry the operation
|
|
21
|
+
- Status codes carry the outcome
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
GET /api/users List users
|
|
25
|
+
GET /api/users/:id Get a specific user
|
|
26
|
+
POST /api/users Create a user
|
|
27
|
+
PATCH /api/users/:id Update a user
|
|
28
|
+
DELETE /api/users/:id Delete a user
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Response envelope
|
|
32
|
+
|
|
33
|
+
Every response wraps its payload in a discriminated union so that a client never has to guess whether it received success or failure:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
interface SuccessResponse<T> {
|
|
37
|
+
success: true;
|
|
38
|
+
data: T;
|
|
39
|
+
meta?: { page?: number; limit?: number; total?: number };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface ErrorResponse {
|
|
43
|
+
success: false;
|
|
44
|
+
error: { code: string; message: string; details?: unknown };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Breaking changes go through versioning; consistency across versions keeps clients portable.
|
|
51
|
+
|
|
52
|
+
## Errors
|
|
53
|
+
|
|
54
|
+
### Categories
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
enum ApiErrorType {
|
|
58
|
+
VALIDATION = 'validation',
|
|
59
|
+
AUTHENTICATION = 'authentication',
|
|
60
|
+
PERMISSION = 'permission',
|
|
61
|
+
NOT_FOUND = 'not_found',
|
|
62
|
+
CONFLICT = 'conflict',
|
|
63
|
+
RATE_LIMIT = 'rate_limit',
|
|
64
|
+
SERVER = 'server',
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class ApiError extends Error {
|
|
68
|
+
constructor(
|
|
69
|
+
message: string,
|
|
70
|
+
public type: ApiErrorType,
|
|
71
|
+
public code: string,
|
|
72
|
+
public statusCode: number,
|
|
73
|
+
public details?: unknown,
|
|
74
|
+
) {
|
|
75
|
+
super(message);
|
|
76
|
+
this.name = 'ApiError';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Status codes
|
|
82
|
+
|
|
83
|
+
| Code | Use |
|
|
84
|
+
|------|-----|
|
|
85
|
+
| 200 | Successful GET, PATCH, DELETE |
|
|
86
|
+
| 201 | Successful POST |
|
|
87
|
+
| 400 | Invalid request |
|
|
88
|
+
| 401 | Missing or invalid authentication |
|
|
89
|
+
| 403 | Insufficient permissions |
|
|
90
|
+
| 404 | Resource not found |
|
|
91
|
+
| 409 | Resource conflict |
|
|
92
|
+
| 422 | Validation failure |
|
|
93
|
+
| 429 | Rate limit exceeded |
|
|
94
|
+
| 500 | Server error |
|
|
95
|
+
|
|
96
|
+
### Error payload
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"success": false,
|
|
101
|
+
"error": {
|
|
102
|
+
"code": "VALIDATION_ERROR",
|
|
103
|
+
"message": "Invalid input data",
|
|
104
|
+
"details": { "fields": { "email": "Invalid email format" } }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Client handling
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
async function fetchUser(id: string): Promise<User> {
|
|
113
|
+
const response = await fetch(`/api/users/${id}`);
|
|
114
|
+
const result: ApiResponse<User> = await response.json();
|
|
115
|
+
|
|
116
|
+
if (!result.success) {
|
|
117
|
+
throw new ApiError(
|
|
118
|
+
result.error.message,
|
|
119
|
+
mapErrorCode(result.error.code),
|
|
120
|
+
result.error.code,
|
|
121
|
+
response.status,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return result.data;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Security
|
|
130
|
+
|
|
131
|
+
### Authentication
|
|
132
|
+
|
|
133
|
+
- Token-based (JWT or equivalent)
|
|
134
|
+
- HTTPS in every environment that sees real traffic
|
|
135
|
+
- Tokens expire; refresh is explicit
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
interface AuthTokens {
|
|
139
|
+
accessToken: string;
|
|
140
|
+
refreshToken: string;
|
|
141
|
+
expiresAt: number;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Authorisation
|
|
146
|
+
|
|
147
|
+
- Minimum necessary permissions
|
|
148
|
+
- Role-based access for cross-resource actions
|
|
149
|
+
- Ownership verified on every resource-scoped action
|
|
150
|
+
- Server-side validation is the line; the client is untrusted
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
async function updateUser(id: string, data: Partial<User>): Promise<User> {
|
|
154
|
+
const currentUser = await getCurrentUser();
|
|
155
|
+
|
|
156
|
+
if (currentUser.id !== id && !currentUser.roles.includes('admin')) {
|
|
157
|
+
throw new ApiError('Permission denied', ApiErrorType.PERMISSION, 'PERMISSION_DENIED', 403);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const validatedData = validateUserUpdate(data);
|
|
161
|
+
return userRepository.update(id, validatedData);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Input validation
|
|
166
|
+
|
|
167
|
+
Types, ranges, and formats are validated on the server. A schema library carries the shape so the contract stays single-sourced:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { z } from 'zod';
|
|
171
|
+
|
|
172
|
+
const UserUpdateSchema = z.object({
|
|
173
|
+
email: z.string().email().optional(),
|
|
174
|
+
displayName: z.string().min(2).max(50).optional(),
|
|
175
|
+
age: z.number().int().min(18).max(120).optional(),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
function validateUserUpdate(data: unknown): Partial<User> {
|
|
179
|
+
try {
|
|
180
|
+
return UserUpdateSchema.parse(data);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error instanceof z.ZodError) {
|
|
183
|
+
throw new ApiError(
|
|
184
|
+
'Validation failed',
|
|
185
|
+
ApiErrorType.VALIDATION,
|
|
186
|
+
'VALIDATION_ERROR',
|
|
187
|
+
422,
|
|
188
|
+
{ fields: error.flatten().fieldErrors },
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Performance
|
|
197
|
+
|
|
198
|
+
### Pagination
|
|
199
|
+
|
|
200
|
+
List endpoints paginate; limits are capped so a client cannot ask for the world:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
interface ListParams {
|
|
204
|
+
page?: number;
|
|
205
|
+
limit?: number;
|
|
206
|
+
sortBy?: string;
|
|
207
|
+
sortOrder?: 'asc' | 'desc';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function listUsers(params: ListParams): Promise<ApiResponse<User[]>> {
|
|
211
|
+
const page = params.page ?? 1;
|
|
212
|
+
const limit = Math.min(params.limit ?? 20, 100);
|
|
213
|
+
const offset = (page - 1) * limit;
|
|
214
|
+
|
|
215
|
+
const [users, total] = await Promise.all([
|
|
216
|
+
userRepository.findMany({ offset, limit, sortBy: params.sortBy, sortOrder: params.sortOrder }),
|
|
217
|
+
userRepository.count(),
|
|
218
|
+
]);
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
success: true,
|
|
222
|
+
data: users,
|
|
223
|
+
meta: { page, limit, total, totalPages: Math.ceil(total / limit) },
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Caching and rate limiting
|
|
229
|
+
|
|
230
|
+
- `Cache-Control` and ETags where the resource tolerates it
|
|
231
|
+
- Rate limits protect against runaway clients and scraped keys
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
app.get('/api/users/:id', async (req, res) => {
|
|
235
|
+
const user = await userRepository.findById(req.params.id);
|
|
236
|
+
if (!user) {
|
|
237
|
+
return res.status(404).json({
|
|
238
|
+
success: false,
|
|
239
|
+
error: { code: 'NOT_FOUND', message: 'User not found' },
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
res.set('Cache-Control', 'private, max-age=300');
|
|
243
|
+
res.json({ success: true, data: user });
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const apiLimiter = rateLimit({
|
|
247
|
+
windowMs: 15 * 60 * 1000,
|
|
248
|
+
max: 100,
|
|
249
|
+
message: { success: false, error: { code: 'RATE_LIMIT_EXCEEDED', message: 'Too many requests' } },
|
|
250
|
+
});
|
|
251
|
+
app.use('/api/', apiLimiter);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Documentation
|
|
255
|
+
|
|
256
|
+
Each endpoint carries:
|
|
257
|
+
|
|
258
|
+
- Purpose and authentication requirement
|
|
259
|
+
- Parameters and response shape
|
|
260
|
+
- Error codes that can arise
|
|
261
|
+
- A request/response example
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
/**
|
|
265
|
+
* Update a user profile.
|
|
266
|
+
* @route PATCH /api/users/:id
|
|
267
|
+
* @auth Required
|
|
268
|
+
* @param id User ID
|
|
269
|
+
* @returns Updated user
|
|
270
|
+
* @throws 401 UNAUTHORIZED
|
|
271
|
+
* @throws 403 PERMISSION_DENIED
|
|
272
|
+
* @throws 422 VALIDATION_ERROR
|
|
273
|
+
*/
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
For large APIs, OpenAPI/Swagger carries the same contract in a form tooling can consume.
|
|
277
|
+
|
|
278
|
+
## Testing
|
|
279
|
+
|
|
280
|
+
- **Unit tests** — individual handlers and helpers
|
|
281
|
+
- **Integration tests** — endpoints against a real database
|
|
282
|
+
- **Contract tests** — request/response shape held to the documented contract
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
describe('User API', () => {
|
|
286
|
+
it('updates user profile successfully', async () => {
|
|
287
|
+
const user = await createTestUser();
|
|
288
|
+
const token = await generateAuthToken(user);
|
|
289
|
+
|
|
290
|
+
const response = await request(app)
|
|
291
|
+
.patch(`/api/users/${user.id}`)
|
|
292
|
+
.set('Authorization', `Bearer ${token}`)
|
|
293
|
+
.send({ displayName: 'Updated Name' });
|
|
294
|
+
|
|
295
|
+
expect(response.status).toBe(200);
|
|
296
|
+
expect(response.body.data.displayName).toBe('Updated Name');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('returns 403 when updating another user', async () => {
|
|
300
|
+
const user1 = await createTestUser();
|
|
301
|
+
const user2 = await createTestUser();
|
|
302
|
+
const token = await generateAuthToken(user1);
|
|
303
|
+
|
|
304
|
+
const response = await request(app)
|
|
305
|
+
.patch(`/api/users/${user2.id}`)
|
|
306
|
+
.set('Authorization', `Bearer ${token}`)
|
|
307
|
+
.send({ displayName: 'Hacked' });
|
|
308
|
+
|
|
309
|
+
expect(response.status).toBe(403);
|
|
310
|
+
expect(response.body.error.code).toBe('PERMISSION_DENIED');
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Related
|
|
316
|
+
|
|
317
|
+
- [React](./react.md) — Patterns for the clients that consume the API
|
|
318
|
+
- [SQL](./sql.md) — Naming, structure, and access on the database side
|
|
319
|
+
- [Testing](./testing.md) — Testing philosophy and layers
|
|
320
|
+
- [Principles](./principles.md) — Shared principles
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
**License**: [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) © Regardio
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
title: "Coding"
|
|
4
|
+
description: "TypeScript and general coding patterns Regardio projects hold to across packages and apps."
|
|
5
|
+
publishedAt: 2026-04-17
|
|
6
|
+
order: 5
|
|
7
|
+
language: "en"
|
|
8
|
+
status: "published"
|
|
9
|
+
kind: "reference"
|
|
10
|
+
area: "dev"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Regardio code is TypeScript across the board. What keeps the packages readable to each other is a small set of patterns held consistently: strict types, small modules, small functions, deliberate error handling. This page catalogues those patterns.
|
|
14
|
+
|
|
15
|
+
## TypeScript
|
|
16
|
+
|
|
17
|
+
### Type safety
|
|
18
|
+
|
|
19
|
+
- Strict TypeScript is on
|
|
20
|
+
- Data shapes are defined as `interface`; unions and aliases use `type`
|
|
21
|
+
- `any` stays rare and gets a comment explaining why
|
|
22
|
+
- Return types are explicit when inference leaves the reader guessing
|
|
23
|
+
|
|
24
|
+
### Modules and exports
|
|
25
|
+
|
|
26
|
+
- One responsibility per module
|
|
27
|
+
- No barrel files; `package.json` `exports` names the public surface
|
|
28
|
+
- Internal helpers stay internal, even when they look generally useful
|
|
29
|
+
|
|
30
|
+
### Function design
|
|
31
|
+
|
|
32
|
+
- Small, focused functions
|
|
33
|
+
- Parameters and non-obvious return types are typed explicitly
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
interface UserProfile {
|
|
37
|
+
id: string;
|
|
38
|
+
displayName: string;
|
|
39
|
+
createdAt: Date;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function fetchUserProfile(userId: string): Promise<UserProfile> {
|
|
43
|
+
const response = await api.get(`/users/${userId}`);
|
|
44
|
+
return response.data;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## React
|
|
49
|
+
|
|
50
|
+
### Components
|
|
51
|
+
|
|
52
|
+
- Functional components with hooks
|
|
53
|
+
- One responsibility per component
|
|
54
|
+
- Explicit props interfaces
|
|
55
|
+
- Composition over inheritance
|
|
56
|
+
|
|
57
|
+
### Hooks
|
|
58
|
+
|
|
59
|
+
- Dependencies declared in full for `useEffect`, `useMemo`, `useCallback`
|
|
60
|
+
- Reusable logic extracts into custom hooks (`use` prefix)
|
|
61
|
+
- Subscriptions, timers, and listeners are torn down in cleanup
|
|
62
|
+
- `useEffect` reads as a code smell; most cases have a better form elsewhere
|
|
63
|
+
|
|
64
|
+
### State
|
|
65
|
+
|
|
66
|
+
- Local state stays close to where it is used
|
|
67
|
+
- Related state collapses into one object or a `useReducer`
|
|
68
|
+
- Server state lives in a query layer, not in `useState`
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
interface ButtonProps {
|
|
72
|
+
variant: 'primary' | 'secondary';
|
|
73
|
+
isDisabled?: boolean;
|
|
74
|
+
onClick: () => void;
|
|
75
|
+
children: React.ReactNode;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function Button({ variant, isDisabled, onClick, children }: ButtonProps) {
|
|
79
|
+
return (
|
|
80
|
+
<button className={`btn btn--${variant}`} disabled={isDisabled} onClick={onClick}>
|
|
81
|
+
{children}
|
|
82
|
+
</button>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
See [React](./react.md) for the longer form.
|
|
88
|
+
|
|
89
|
+
## General
|
|
90
|
+
|
|
91
|
+
### Comments carry the *why*
|
|
92
|
+
|
|
93
|
+
A comment that restates the next line gets deleted. A comment that names the reason behind a non-obvious choice stays.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Reset to 1-based index for display
|
|
97
|
+
counter += 1;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Errors are designed, not caught and dropped
|
|
101
|
+
|
|
102
|
+
The paths that fail are known when the function is written. Result types are preferred at API boundaries; thrown errors inside a module where the flow is clearer. No `catch` block silently swallows.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
try {
|
|
106
|
+
const result = await riskyOperation();
|
|
107
|
+
return { success: true, data: result };
|
|
108
|
+
} catch (error) {
|
|
109
|
+
logger.error('Operation failed', { error });
|
|
110
|
+
return { success: false, error: 'Operation failed' };
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Inference where it reads, explicit where it doesn't
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const names = ['Alice', 'Bob']; // inferred as string[]
|
|
118
|
+
const config: AppConfig = loadConfig(); // explicit where the type isn't obvious
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Exceptions leave a reason
|
|
122
|
+
|
|
123
|
+
Lint suppressions and type escapes carry a comment:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// biome-ignore lint/complexity/noForEach: forEach is clearer for side effects here
|
|
127
|
+
items.forEach(sendNotification);
|
|
128
|
+
|
|
129
|
+
// @ts-expect-error: library types are incorrect for this overload
|
|
130
|
+
const result = legacyLib.process(data);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
An unjustified suppression is a regression.
|
|
134
|
+
|
|
135
|
+
## Related
|
|
136
|
+
|
|
137
|
+
- [Principles](./principles.md) — Shared principles the patterns build on
|
|
138
|
+
- [React](./react.md) — Component, hook, and state patterns
|
|
139
|
+
- [Naming](./naming.md) — Names across languages
|
|
140
|
+
- [Testing](./testing.md) — Testing philosophy
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
**License**: [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) © Regardio
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
title: "Commits"
|
|
4
|
+
description: "Conventional Commits format Regardio projects hold to, so history reads and changelogs generate."
|
|
5
|
+
publishedAt: 2026-04-17
|
|
6
|
+
order: 10
|
|
7
|
+
language: "en"
|
|
8
|
+
status: "published"
|
|
9
|
+
kind: "reference"
|
|
10
|
+
area: "dev"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Commit subjects feed the changelog the outside world reads, and the git history is what a future contributor reaches for when a line of code raises a question. Short, imperative subjects with a type and an optional scope keep both useful.
|
|
14
|
+
|
|
15
|
+
## Format
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
<type>(<scope>): <subject>
|
|
19
|
+
[optional body]
|
|
20
|
+
[optional footer]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **type** — category of change (required)
|
|
24
|
+
- **scope** — area affected (optional)
|
|
25
|
+
- **subject** — imperative mood, up to 100 characters (required)
|
|
26
|
+
|
|
27
|
+
## Types
|
|
28
|
+
|
|
29
|
+
| Type | Description | Changelog |
|
|
30
|
+
|------|-------------|-----------|
|
|
31
|
+
| `feat` | New feature | Features |
|
|
32
|
+
| `fix` | Bug fix | Bug Fixes |
|
|
33
|
+
| `docs` | Documentation only | — |
|
|
34
|
+
| `style` | Formatting | — |
|
|
35
|
+
| `refactor` | Code change with no fix or feature | — |
|
|
36
|
+
| `perf` | Performance improvement | Performance |
|
|
37
|
+
| `test` | Tests | — |
|
|
38
|
+
| `build` | Build or dependency change | — |
|
|
39
|
+
| `ci` | CI configuration | — |
|
|
40
|
+
| `chore` | Other changes | — |
|
|
41
|
+
| `BREAKING CHANGE` | Breaking API change | Breaking Changes |
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Simple
|
|
47
|
+
feat: add user authentication
|
|
48
|
+
fix: resolve login redirect loop
|
|
49
|
+
|
|
50
|
+
# With scope
|
|
51
|
+
feat(auth): implement OAuth2 flow
|
|
52
|
+
fix(api): handle null response gracefully
|
|
53
|
+
|
|
54
|
+
# With body
|
|
55
|
+
fix: prevent race condition in data sync
|
|
56
|
+
|
|
57
|
+
The previous implementation could cause data corruption.
|
|
58
|
+
A mutex now guards the sequential path.
|
|
59
|
+
|
|
60
|
+
Closes #123
|
|
61
|
+
|
|
62
|
+
# Breaking change
|
|
63
|
+
feat!: redesign authentication API
|
|
64
|
+
|
|
65
|
+
BREAKING CHANGE: auth.login() now returns a Promise.
|
|
66
|
+
Update call sites to use async/await.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Practices
|
|
70
|
+
|
|
71
|
+
### Imperative mood
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Good
|
|
75
|
+
feat: add validation for email field
|
|
76
|
+
|
|
77
|
+
# Not good
|
|
78
|
+
feat: added validation for email field
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Atomic commits
|
|
82
|
+
|
|
83
|
+
Each commit carries one logical change. When a branch has done several things, the history reflects them as separate commits.
|
|
84
|
+
|
|
85
|
+
### References to issues
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
fix: correct calculation of total price
|
|
89
|
+
|
|
90
|
+
Closes #456
|
|
91
|
+
Refs #123, #789
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Enforcement
|
|
95
|
+
|
|
96
|
+
Commit messages are validated by [Commitlint](../tools/commitlint.md) through git hooks.
|
|
97
|
+
|
|
98
|
+
## Changelog generation
|
|
99
|
+
|
|
100
|
+
Changelogs are generated from [Changesets](../tools/releases.md) — one-line descriptions authors write with `pnpm changeset` alongside their change. `ship-production` consumes pending changesets at ship time and writes them into each package's `CHANGELOG.md`. The changeset description is what users read, so write it for that reader from the start.
|
|
101
|
+
|
|
102
|
+
## Related
|
|
103
|
+
|
|
104
|
+
- [Commitlint](../tools/commitlint.md) — Commit message validation
|
|
105
|
+
- [Releases](../tools/releases.md) — Release workflow that consumes the commits
|
|
106
|
+
- [Naming](./naming.md) — Branch and package naming
|
|
107
|
+
- [Conventional Commits](https://www.conventionalcommits.org/) — The spec this follows
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
**License**: [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) © Regardio
|