Skip to content
This repository was archived by the owner on Mar 9, 2026. It is now read-only.

Commit 36d960f

Browse files
authored
refactor(skills): replace correct-pattern samples with source references in boundary-validator (#72)
* refactor(skills): replace correct-pattern samples with source references in boundary-validator Remove manually-maintained correct-pattern code blocks from references/ and replace with pointers to src/parse.ts and src/types/. Violation patterns, checklists, and grep commands are kept as they are unique to the skill and do not exist elsewhere. This eliminates the drift risk between reference docs and implementation without adding maintenance burden to contributors. * refactor(skills): hyperlink source references in boundary-validator references * refactor(skills): revert to plain path references in boundary-validator Relative hyperlinks caused 404 without branch/SHA context. Code spans (backticks) are sufficient for both AI agents and humans.
1 parent 9bc8a56 commit 36d960f

4 files changed

Lines changed: 75 additions & 683 deletions

File tree

skills/boundary-validator/references/api-contract.md

Lines changed: 22 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -8,280 +8,102 @@ These constraints ensure API stability and prevent breaking changes.
88

99
## Public API (Minimal and Stable)
1010

11-
The safe-formdata public API consists of a single function and utility types:
11+
Single function, no overloads, no options:
1212

1313
```typescript
1414
parse(formData: FormData): ParseResult
15+
```
16+
17+
Named utility types derived from `ParseResult`:
1518

16-
// Named utility types derived from ParseResult
19+
```typescript
1720
type SuccessResult = Extract<ParseResult, { data: Record<string, string | File> }>;
1821
type FailureResult = Extract<ParseResult, { data: null }>;
1922
```
2023

21-
### Constraints
22-
23-
- **No overloads**: The function has exactly one signature
24-
- **No options**: No optional parameters or configuration objects
25-
- **No framework-specific adapters**: Works with standard FormData only
24+
See implementation: `src/index.ts`, `src/parse.ts`
2625

27-
### Rationale
28-
29-
A minimal API surface reduces maintenance burden, prevents feature creep, and maintains the boundary-focused philosophy. Any additional functionality belongs in separate libraries that build on top of safe-formdata.
30-
31-
### Examples
32-
33-
**❌ Violations**:
26+
### Violations
3427

3528
```typescript
36-
// Adding overloads
29+
// Adding overloads
3730
function parse(formData: FormData): ParseResult;
3831
function parse(formData: FormData, options: ParseOptions): ParseResult;
3932

40-
// Adding options
33+
// Adding options
4134
function parse(formData: FormData, options?: { allowDuplicates?: boolean }): ParseResult;
4235

43-
// Adding framework adapters
36+
// ❌ Framework adapters
4437
function parseRequest(req: NextRequest): ParseResult;
45-
function parseFromExpress(req: express.Request): ParseResult;
46-
```
47-
48-
**✅ Correct**:
49-
50-
```typescript
51-
// Single, stable signature
52-
export function parse(formData: FormData): ParseResult {
53-
// Implementation
54-
}
5538
```
5639

5740
---
5841

5942
## Type Definitions
6043

61-
### ParseResult (Discriminated Union)
62-
63-
```typescript
64-
export type ParseResult =
65-
| {
66-
data: Record<string, string | File>;
67-
issues: [];
68-
}
69-
| {
70-
data: null;
71-
issues: [ParseIssue, ...ParseIssue[]];
72-
};
73-
```
74-
75-
#### Constraints
44+
See source: `src/types/ParseResult.ts`, `src/types/ParseIssue.ts`, `src/types/IssueCode.ts`
7645

77-
- **Must be a discriminated union**: Two distinct shapes based on success/failure
78-
- **Success state**: `data` is a Record, `issues` is an empty array (literal type `[]`)
79-
- **Failure state**: `data` is `null`, `issues` is a non-empty tuple (`[ParseIssue, ...ParseIssue[]]`)
80-
- **No intermediate states**: Partial success is not allowed
81-
82-
#### Type Narrowing Pattern
46+
### Type Narrowing Pattern
8347

8448
```typescript
8549
if (result.data !== null) {
86-
// TypeScript knows: data is Record<string, string | File>
87-
// TypeScript knows: issues is []
88-
console.log(result.data.username);
50+
// data is Record<string, string | File>, issues is []
8951
} else {
90-
// TypeScript knows: data is null
91-
// TypeScript knows: issues is [ParseIssue, ...ParseIssue[]]
92-
console.error(result.issues);
52+
// data is null, issues is [ParseIssue, ...ParseIssue[]]
9353
}
9454
```
9555

96-
#### Important: No `.ok` Property
97-
98-
Unlike some libraries (e.g., `Result<T, E>` types), `ParseResult` does **not** have a `.ok` property.
99-
100-
**❌ Wrong**:
56+
### No `.ok` Property
10157

10258
```typescript
59+
// ❌ Wrong
10360
if (result.ok) {
10461
/* ... */
10562
}
106-
```
10763

108-
**✅ Correct**:
109-
110-
```typescript
64+
// ✅ Correct
11165
if (result.data !== null) {
11266
/* ... */
11367
}
11468
```
11569

116-
---
117-
118-
### ParseIssue
119-
120-
```typescript
121-
export interface ParseIssue {
122-
code: IssueCode;
123-
key: string;
124-
}
125-
```
126-
127-
#### Constraints
128-
129-
- **`code`**: Must be one of the allowed IssueCode values
130-
- **`key`**: Must be the original FormData key that caused the issue, reported as-is without interpretation
131-
- **Issues are informational, not exceptions**
132-
133-
#### Future Considerations
134-
135-
In v1.0+, additional fields may be considered:
136-
137-
- `message?: string` - Human-readable error message
138-
- `meta?: Record<string, unknown>` - Additional metadata
139-
140-
**Any such changes would require a major version bump.**
141-
142-
---
143-
144-
### IssueCode
145-
146-
```typescript
147-
export type IssueCode = "invalid_key" | "forbidden_key" | "duplicate_key";
148-
```
149-
150-
#### Allowed Values (Fixed)
151-
152-
- `invalid_key` - Key is invalid (empty, wrong type, etc.)
153-
- `forbidden_key` - Key is forbidden (`__proto__`, `constructor`, `prototype`)
154-
- `duplicate_key` - Key appears multiple times in FormData
155-
156-
#### Constraint
70+
### IssueCode Constraint
15771

15872
**No additional IssueCode may be introduced without a major version bump.**
15973

160-
#### Examples
161-
162-
**❌ Violations** (Breaking changes):
163-
164-
```typescript
165-
// Adding new issue codes (requires major version bump)
166-
export type IssueCode =
167-
| "invalid_key"
168-
| "forbidden_key"
169-
| "duplicate_key"
170-
| "invalid_value" // NEW - Breaking change!
171-
| "too_many_fields"; // NEW - Breaking change!
172-
173-
// Removing existing codes (requires major version bump)
174-
export type IssueCode = "invalid_key" | "forbidden_key";
175-
// "duplicate_key" removed - Breaking change!
176-
177-
// Renaming codes (requires major version bump)
178-
export type IssueCode = "bad_key" | "forbidden_key" | "duplicate_key";
179-
// "invalid_key" → "bad_key" - Breaking change!
180-
```
181-
182-
**✅ Correct** (Non-breaking changes):
183-
18474
```typescript
185-
// v0.1.0 - Initial release
186-
export type IssueCode = "invalid_key" | "forbidden_key" | "duplicate_key";
187-
188-
// v0.2.0 - No changes to IssueCode (patch or minor bump allowed)
189-
export type IssueCode = "invalid_key" | "forbidden_key" | "duplicate_key";
190-
191-
// v1.0.0 - Major version allows changes
192-
export type IssueCode = "invalid_key" | "forbidden_key" | "duplicate_key" | "invalid_value"; // OK in major version
75+
// ❌ Adding codes — requires major version bump
76+
export type IssueCode = "invalid_key" | "forbidden_key" | "duplicate_key" | "invalid_value";
19377
```
19478

19579
---
19680

197-
## Type Documentation
198-
199-
All type definitions include comprehensive JSDoc comments for IDE integration.
200-
201-
See:
202-
203-
- `src/types/ParseResult.ts` - Discriminated union with type narrowing examples
204-
- `src/types/ParseIssue.ts` - Issue structure and property explanations
205-
- `src/types/IssueCode.ts` - Security-focused issue code definitions
206-
207-
### JSDoc Example
208-
209-
````typescript
210-
/**
211-
* Result of parsing FormData.
212-
*
213-
* This is a discriminated union:
214-
* - Success: `data` is a Record, `issues` is `[]`
215-
* - Failure: `data` is `null`, `issues` contains errors
216-
*
217-
* Use `data !== null` to narrow the type:
218-
*
219-
* @example
220-
* ```ts
221-
* const result = parse(formData);
222-
*
223-
* if (result.data !== null) {
224-
* // Success: result.data is Record<string, string | File>
225-
* console.log(result.data.username);
226-
* } else {
227-
* // Failure: result.issues is ParseIssue[]
228-
* console.error(result.issues);
229-
* }
230-
* ```
231-
*/
232-
export type ParseResult =
233-
| { data: Record<string, string | File>; issues: [] }
234-
| { data: null; issues: [ParseIssue, ...ParseIssue[]] };
235-
````
236-
237-
---
238-
23981
## Versioning Policy
24082

241-
For complete versioning policy, see README.md Versioning section.
242-
243-
### Key Points
244-
24583
- **Patch versions** (0.1.x): bugfixes, no API changes
24684
- **Minor versions** (0.x.0): Breaking changes allowed in 0.x (treated as effectively major)
24785
- **Major versions** (1.0.0+): Breaking changes allowed
24886

24987
### What Counts as Breaking
25088

251-
The following changes are **breaking** and require a major version bump:
252-
25389
- Adding/removing `IssueCode` values
25490
- Changing `ParseResult` structure
25591
- Adding required parameters to `parse()`
256-
- Changing return type of `parse()`
25792
- Removing or renaming exported types
25893

259-
### What Counts as Non-Breaking
260-
261-
The following changes are **non-breaking** and allowed in minor/patch versions:
262-
263-
- Bugfixes in parsing logic
264-
- Performance improvements
265-
- Internal refactoring
266-
- Documentation improvements
267-
- Adding optional JSDoc comments
268-
26994
---
27095

27196
## Review Checklist
27297

273-
When reviewing API changes:
274-
27598
- [ ] Public API remains `parse(formData): ParseResult` only
276-
- [ ] `SuccessResult` / `FailureResult` utility types are derived from `ParseResult` (not independently defined)
99+
- [ ] `SuccessResult` / `FailureResult` are derived from `ParseResult` (not independently defined)
277100
- [ ] No overloads added
278101
- [ ] No options parameters added
279102
- [ ] `ParseResult` structure unchanged
280103
- [ ] `IssueCode` values unchanged (or major version bump planned)
281104
- [ ] Type narrowing pattern still works (`data !== null`)
282-
- [ ] JSDoc comments updated (if applicable)
283105

284106
---
285107

286108
**Source**: AGENTS.md (lines 119-199)
287-
**Last updated**: 2026-03-02
109+
**Last updated**: 2026-03-06

0 commit comments

Comments
 (0)