invar-tools 1.8.0__py3-none-any.whl → 1.10.0__py3-none-any.whl
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.
- invar/__init__.py +8 -0
- invar/core/language.py +88 -0
- invar/core/models.py +106 -0
- invar/core/patterns/detector.py +6 -1
- invar/core/patterns/p0_exhaustive.py +15 -3
- invar/core/patterns/p0_literal.py +15 -3
- invar/core/patterns/p0_newtype.py +15 -3
- invar/core/patterns/p0_nonempty.py +15 -3
- invar/core/patterns/p0_validation.py +15 -3
- invar/core/patterns/registry.py +5 -1
- invar/core/patterns/types.py +5 -1
- invar/core/property_gen.py +4 -0
- invar/core/rules.py +84 -18
- invar/core/sync_helpers.py +27 -1
- invar/core/ts_parsers.py +286 -0
- invar/core/ts_sig_parser.py +307 -0
- invar/node_tools/MANIFEST +7 -0
- invar/node_tools/__init__.py +51 -0
- invar/node_tools/fc-runner/cli.js +77 -0
- invar/node_tools/quick-check/cli.js +28 -0
- invar/node_tools/ts-analyzer/cli.js +480 -0
- invar/shell/claude_hooks.py +35 -12
- invar/shell/commands/guard.py +36 -1
- invar/shell/commands/init.py +82 -3
- invar/shell/commands/perception.py +157 -33
- invar/shell/commands/skill.py +187 -0
- invar/shell/commands/template_sync.py +65 -13
- invar/shell/commands/uninstall.py +60 -12
- invar/shell/commands/update.py +6 -14
- invar/shell/contract_coverage.py +1 -0
- invar/shell/fs.py +66 -13
- invar/shell/pi_hooks.py +6 -0
- invar/shell/prove/guard_ts.py +899 -0
- invar/shell/skill_manager.py +353 -0
- invar/shell/template_engine.py +28 -4
- invar/shell/templates.py +4 -4
- invar/templates/claude-md/python/critical-rules.md +33 -0
- invar/templates/claude-md/python/quick-reference.md +24 -0
- invar/templates/claude-md/typescript/critical-rules.md +40 -0
- invar/templates/claude-md/typescript/quick-reference.md +24 -0
- invar/templates/claude-md/universal/check-in.md +25 -0
- invar/templates/claude-md/universal/skills.md +73 -0
- invar/templates/claude-md/universal/workflow.md +55 -0
- invar/templates/commands/{audit.md → audit.md.jinja} +18 -1
- invar/templates/config/AGENT.md.jinja +58 -0
- invar/templates/config/CLAUDE.md.jinja +16 -209
- invar/templates/config/context.md.jinja +19 -0
- invar/templates/examples/{README.md → python/README.md} +2 -0
- invar/templates/examples/{conftest.py → python/conftest.py} +1 -1
- invar/templates/examples/{contracts.py → python/contracts.py} +81 -4
- invar/templates/examples/python/core_shell.py +227 -0
- invar/templates/examples/python/functional.py +613 -0
- invar/templates/examples/typescript/README.md +31 -0
- invar/templates/examples/typescript/contracts.ts +163 -0
- invar/templates/examples/typescript/core_shell.ts +374 -0
- invar/templates/examples/typescript/functional.ts +601 -0
- invar/templates/examples/typescript/workflow.md +95 -0
- invar/templates/hooks/PostToolUse.sh.jinja +10 -1
- invar/templates/hooks/PreToolUse.sh.jinja +38 -0
- invar/templates/hooks/Stop.sh.jinja +1 -1
- invar/templates/hooks/UserPromptSubmit.sh.jinja +7 -0
- invar/templates/hooks/pi/invar.ts.jinja +9 -0
- invar/templates/manifest.toml +7 -6
- invar/templates/onboard/assessment.md.jinja +214 -0
- invar/templates/onboard/patterns/python.md +347 -0
- invar/templates/onboard/patterns/typescript.md +452 -0
- invar/templates/onboard/roadmap.md.jinja +168 -0
- invar/templates/protocol/INVAR.md.jinja +51 -0
- invar/templates/protocol/python/architecture-examples.md +41 -0
- invar/templates/protocol/python/contracts-syntax.md +56 -0
- invar/templates/protocol/python/markers.md +44 -0
- invar/templates/protocol/python/tools.md +24 -0
- invar/templates/protocol/python/troubleshooting.md +38 -0
- invar/templates/protocol/typescript/architecture-examples.md +52 -0
- invar/templates/protocol/typescript/contracts-syntax.md +73 -0
- invar/templates/protocol/typescript/markers.md +48 -0
- invar/templates/protocol/typescript/tools.md +65 -0
- invar/templates/protocol/typescript/troubleshooting.md +104 -0
- invar/templates/protocol/universal/architecture.md +36 -0
- invar/templates/protocol/universal/completion.md +14 -0
- invar/templates/protocol/universal/contracts-concept.md +37 -0
- invar/templates/protocol/universal/header.md +17 -0
- invar/templates/protocol/universal/session.md +17 -0
- invar/templates/protocol/universal/six-laws.md +10 -0
- invar/templates/protocol/universal/usbv.md +14 -0
- invar/templates/protocol/universal/visible-workflow.md +25 -0
- invar/templates/skills/develop/SKILL.md.jinja +39 -3
- invar/templates/skills/extensions/_registry.yaml +93 -0
- invar/templates/skills/extensions/acceptance/SKILL.md +383 -0
- invar/templates/skills/extensions/invar-onboard/SKILL.md +448 -0
- invar/templates/skills/extensions/invar-onboard/patterns/python.md +347 -0
- invar/templates/skills/extensions/invar-onboard/patterns/typescript.md +452 -0
- invar/templates/skills/extensions/invar-onboard/templates/assessment.md.jinja +214 -0
- invar/templates/skills/extensions/invar-onboard/templates/roadmap.md.jinja +168 -0
- invar/templates/skills/extensions/security/SKILL.md +382 -0
- invar/templates/skills/extensions/security/patterns/_common.yaml +126 -0
- invar/templates/skills/extensions/security/patterns/python.yaml +155 -0
- invar/templates/skills/extensions/security/patterns/typescript.yaml +194 -0
- invar/templates/skills/review/SKILL.md.jinja +331 -71
- {invar_tools-1.8.0.dist-info → invar_tools-1.10.0.dist-info}/METADATA +304 -12
- invar_tools-1.10.0.dist-info/RECORD +173 -0
- invar/templates/examples/core_shell.py +0 -127
- invar/templates/protocol/INVAR.md +0 -310
- invar_tools-1.8.0.dist-info/RECORD +0 -116
- /invar/templates/examples/{workflow.md → python/workflow.md} +0 -0
- {invar_tools-1.8.0.dist-info → invar_tools-1.10.0.dist-info}/WHEEL +0 -0
- {invar_tools-1.8.0.dist-info → invar_tools-1.10.0.dist-info}/entry_points.txt +0 -0
- {invar_tools-1.8.0.dist-info → invar_tools-1.10.0.dist-info}/licenses/LICENSE +0 -0
- {invar_tools-1.8.0.dist-info → invar_tools-1.10.0.dist-info}/licenses/LICENSE-GPL +0 -0
- {invar_tools-1.8.0.dist-info → invar_tools-1.10.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# TypeScript Onboarding Patterns
|
|
2
|
+
|
|
3
|
+
> Patterns for migrating TypeScript projects to Invar framework.
|
|
4
|
+
> Library: `neverthrow`
|
|
5
|
+
|
|
6
|
+
## 1. Overview
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
// Library: neverthrow
|
|
10
|
+
// Install: npm install neverthrow
|
|
11
|
+
|
|
12
|
+
import { Result, ResultAsync, ok, err, okAsync, errAsync } from 'neverthrow';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 2. Error Handling
|
|
18
|
+
|
|
19
|
+
### 2.1 Sync vs Async Result
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { Result, ResultAsync, ok, err, okAsync, errAsync } from 'neverthrow';
|
|
23
|
+
|
|
24
|
+
// Sync (Core layer)
|
|
25
|
+
function validateEmail(email: string): Result<string, ValidationError> {
|
|
26
|
+
if (!email.includes('@')) {
|
|
27
|
+
return err(new ValidationError('invalid_email'));
|
|
28
|
+
}
|
|
29
|
+
return ok(email);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Async (Shell layer)
|
|
33
|
+
function sendEmail(to: string): ResultAsync<void, EmailError> {
|
|
34
|
+
return ResultAsync.fromPromise(
|
|
35
|
+
emailClient.send({ to }),
|
|
36
|
+
(e) => new EmailError('send_failed', e)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2.2 Basic Transformation
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// Before: throw
|
|
45
|
+
async function getUser(id: string): Promise<User> {
|
|
46
|
+
const user = await db.user.findUnique({ where: { id } });
|
|
47
|
+
if (!user) throw new NotFoundError(`User ${id} not found`);
|
|
48
|
+
return user;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// After: ResultAsync
|
|
52
|
+
function getUser(id: string): ResultAsync<User, GetUserError> {
|
|
53
|
+
return ResultAsync.fromPromise(
|
|
54
|
+
db.user.findUnique({ where: { id } }),
|
|
55
|
+
() => new DbError('query_failed')
|
|
56
|
+
).andThen(user =>
|
|
57
|
+
user ? okAsync(user) : errAsync(new NotFoundError(`User ${id} not found`))
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2.3 Chaining (andThen, map)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
function processOrder(orderId: string): ResultAsync<Receipt, OrderError> {
|
|
66
|
+
return getOrder(orderId) // ResultAsync<Order, NotFoundError>
|
|
67
|
+
.andThen(validateOrder) // -> ResultAsync<Order, ValidationError>
|
|
68
|
+
.map(calculateTotal) // -> ResultAsync<number, ...>
|
|
69
|
+
.andThen(chargePayment) // -> ResultAsync<Payment, PaymentError>
|
|
70
|
+
.map(generateReceipt); // -> ResultAsync<Receipt, ...>
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2.4 Error Type Hierarchy (Discriminated Union)
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// Discriminated union for exhaustive checking
|
|
78
|
+
type OrderError =
|
|
79
|
+
| { type: 'NOT_FOUND'; orderId: string }
|
|
80
|
+
| { type: 'VALIDATION'; field: string; message: string }
|
|
81
|
+
| { type: 'PAYMENT'; code: string; retry: boolean };
|
|
82
|
+
|
|
83
|
+
function handleOrderError(error: OrderError): Response {
|
|
84
|
+
switch (error.type) {
|
|
85
|
+
case 'NOT_FOUND':
|
|
86
|
+
return notFound(`Order ${error.orderId} not found`);
|
|
87
|
+
case 'VALIDATION':
|
|
88
|
+
return badRequest(`${error.field}: ${error.message}`);
|
|
89
|
+
case 'PAYMENT':
|
|
90
|
+
return error.retry
|
|
91
|
+
? serviceUnavailable('Payment failed, please retry')
|
|
92
|
+
: badRequest(`Payment error: ${error.code}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 2.5 Safe Wrappers
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// Catch exceptions -> Result
|
|
101
|
+
function safeJsonParse<T>(json: string): Result<T, ParseError> {
|
|
102
|
+
return Result.fromThrowable(
|
|
103
|
+
() => JSON.parse(json) as T,
|
|
104
|
+
(e) => new ParseError('invalid_json', e)
|
|
105
|
+
)();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Wrap Promise -> ResultAsync
|
|
109
|
+
function safeFetch<T>(url: string): ResultAsync<T, FetchError> {
|
|
110
|
+
return ResultAsync.fromPromise(
|
|
111
|
+
fetch(url).then(r => r.json()),
|
|
112
|
+
(e) => new FetchError('fetch_failed', e)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 2.6 Combining Multiple Results
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { Result, ResultAsync } from 'neverthrow';
|
|
121
|
+
|
|
122
|
+
// Combine array of Results
|
|
123
|
+
function validateOrderItems(items: OrderItem[]): Result<OrderItem[], ValidationError[]> {
|
|
124
|
+
const results = items.map(validateItem);
|
|
125
|
+
return Result.combineWithAllErrors(results); // Collect all errors
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Combine multiple independent Results
|
|
129
|
+
const combined = Result.combine([
|
|
130
|
+
validateName(name),
|
|
131
|
+
validateEmail(email),
|
|
132
|
+
validateAge(age),
|
|
133
|
+
]);
|
|
134
|
+
// -> Result<[string, string, number], ValidationError>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 3. Contracts (Zod)
|
|
140
|
+
|
|
141
|
+
### 3.1 Schema Definition
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { z } from 'zod';
|
|
145
|
+
|
|
146
|
+
// Basic schemas
|
|
147
|
+
const UserIdSchema = z.string()
|
|
148
|
+
.min(1, 'ID is required')
|
|
149
|
+
.max(36, 'ID too long')
|
|
150
|
+
.regex(/^[a-zA-Z0-9-]+$/, 'Invalid ID format');
|
|
151
|
+
|
|
152
|
+
const EmailSchema = z.string()
|
|
153
|
+
.email('Invalid email format')
|
|
154
|
+
.transform(s => s.toLowerCase());
|
|
155
|
+
|
|
156
|
+
// Composite schema
|
|
157
|
+
const CreateUserSchema = z.object({
|
|
158
|
+
email: EmailSchema,
|
|
159
|
+
name: z.string().min(1).max(100),
|
|
160
|
+
role: z.enum(['admin', 'user', 'guest']).default('user'),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Schema with refinement
|
|
164
|
+
const OrderSchema = z.object({
|
|
165
|
+
items: z.array(z.object({
|
|
166
|
+
sku: z.string(),
|
|
167
|
+
qty: z.number().int().positive(),
|
|
168
|
+
price: z.number().positive(),
|
|
169
|
+
})).min(1, 'Order must have at least one item'),
|
|
170
|
+
}).refine(
|
|
171
|
+
(order) => order.items.reduce((sum, i) => sum + i.qty, 0) <= 100,
|
|
172
|
+
{ message: 'Order cannot exceed 100 items total' }
|
|
173
|
+
);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 3.2 Zod + Result Integration
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { z } from 'zod';
|
|
180
|
+
import { Result, ok, err } from 'neverthrow';
|
|
181
|
+
|
|
182
|
+
type ValidationError = {
|
|
183
|
+
type: 'VALIDATION';
|
|
184
|
+
issues: z.ZodIssue[];
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
function validate<T>(schema: z.ZodSchema<T>, data: unknown): Result<T, ValidationError> {
|
|
188
|
+
const result = schema.safeParse(data);
|
|
189
|
+
if (result.success) {
|
|
190
|
+
return ok(result.data);
|
|
191
|
+
}
|
|
192
|
+
return err({ type: 'VALIDATION', issues: result.error.issues });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Usage
|
|
196
|
+
function createUser(input: unknown): ResultAsync<User, CreateUserError> {
|
|
197
|
+
return validate(CreateUserSchema, input)
|
|
198
|
+
.asyncAndThen(validated =>
|
|
199
|
+
checkEmailUnique(validated.email)
|
|
200
|
+
.map(() => validated)
|
|
201
|
+
)
|
|
202
|
+
.andThen(saveUser);
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 3.3 Branded Types
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { z } from 'zod';
|
|
210
|
+
import { ResultAsync } from 'neverthrow';
|
|
211
|
+
|
|
212
|
+
// Type-safe IDs that can't be mixed up
|
|
213
|
+
const UserId = z.string().uuid().brand<'UserId'>();
|
|
214
|
+
type UserId = z.infer<typeof UserId>;
|
|
215
|
+
|
|
216
|
+
const OrderId = z.string().uuid().brand<'OrderId'>();
|
|
217
|
+
type OrderId = z.infer<typeof OrderId>;
|
|
218
|
+
|
|
219
|
+
function getUser(id: UserId): ResultAsync<User, UserError> { ... }
|
|
220
|
+
function getOrder(id: OrderId): ResultAsync<Order, OrderError> { ... }
|
|
221
|
+
|
|
222
|
+
// Compile error: OrderId cannot be assigned to UserId
|
|
223
|
+
// getUser(orderId);
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 3.4 JSDoc Contracts
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
/**
|
|
230
|
+
* Calculate order total with tax.
|
|
231
|
+
*
|
|
232
|
+
* @pre items.length > 0
|
|
233
|
+
* @pre taxRate >= 0 && taxRate <= 1
|
|
234
|
+
* @post result >= 0
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* const total = calculateTotal([{price: 100, qty: 2}], 0.1);
|
|
239
|
+
* assert(total === 220); // 200 + 20 tax
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
function calculateTotal(items: OrderItem[], taxRate: number): number {
|
|
243
|
+
const subtotal = items.reduce((sum, item) => sum + item.price * item.qty, 0);
|
|
244
|
+
return subtotal * (1 + taxRate);
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 4. Core/Shell Separation
|
|
251
|
+
|
|
252
|
+
### 4.1 Directory Structure
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
lib/
|
|
256
|
+
├── core/ # Pure functions, sync preferred
|
|
257
|
+
│ ├── order/
|
|
258
|
+
│ │ ├── validation.ts # Zod schemas + pure validation
|
|
259
|
+
│ │ ├── calculation.ts # Pure calculations
|
|
260
|
+
│ │ └── types.ts # Domain types
|
|
261
|
+
│ └── user/
|
|
262
|
+
│ └── ...
|
|
263
|
+
├── services/ # Shell: I/O orchestration
|
|
264
|
+
│ ├── order.service.ts
|
|
265
|
+
│ └── user.service.ts
|
|
266
|
+
├── repositories/ # Shell: Data access
|
|
267
|
+
│ ├── order.repository.ts
|
|
268
|
+
│ └── user.repository.ts
|
|
269
|
+
└── errors/ # Error type definitions
|
|
270
|
+
└── index.ts
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### 4.2 Core Layer Example
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// lib/core/order/validation.ts
|
|
277
|
+
import { z } from 'zod';
|
|
278
|
+
import { Result, ok, err } from 'neverthrow';
|
|
279
|
+
|
|
280
|
+
export const OrderItemSchema = z.object({
|
|
281
|
+
sku: z.string().min(1),
|
|
282
|
+
qty: z.number().int().positive(),
|
|
283
|
+
price: z.number().positive(),
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
export type OrderItem = z.infer<typeof OrderItemSchema>;
|
|
287
|
+
|
|
288
|
+
// Pure validation (sync Result)
|
|
289
|
+
export function validateOrder(order: unknown): Result<Order, ValidationError> {
|
|
290
|
+
const result = OrderSchema.safeParse(order);
|
|
291
|
+
if (!result.success) {
|
|
292
|
+
return err({ type: 'VALIDATION', issues: result.error.issues });
|
|
293
|
+
}
|
|
294
|
+
return ok(result.data);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Pure calculation
|
|
298
|
+
export function calculateSubtotal(items: OrderItem[]): number {
|
|
299
|
+
return items.reduce((sum, item) => sum + item.qty * item.price, 0);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function applyDiscount(amount: number, rate: number): number {
|
|
303
|
+
return amount * (1 - rate);
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### 4.3 Shell Layer Example
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
// lib/services/order.service.ts
|
|
311
|
+
import { ResultAsync } from 'neverthrow';
|
|
312
|
+
import { validateOrder, calculateSubtotal } from '../core/order/validation';
|
|
313
|
+
import { OrderRepository } from '../repositories/order.repository';
|
|
314
|
+
|
|
315
|
+
export class OrderService {
|
|
316
|
+
constructor(private readonly repo: OrderRepository) {}
|
|
317
|
+
|
|
318
|
+
processOrder(orderId: string): ResultAsync<Receipt, OrderError> {
|
|
319
|
+
return this.repo.findById(orderId) // Shell: I/O
|
|
320
|
+
.andThen(validateOrder) // Core: pure (sync->async)
|
|
321
|
+
.map(order => ({ // Core: pure transform
|
|
322
|
+
order,
|
|
323
|
+
subtotal: calculateSubtotal(order.items),
|
|
324
|
+
}))
|
|
325
|
+
.andThen(({ order, subtotal }) =>
|
|
326
|
+
this.getDiscount(order.id)
|
|
327
|
+
.map(discount => applyDiscount(subtotal, discount))
|
|
328
|
+
)
|
|
329
|
+
.andThen(total => this.chargePayment(total)) // Shell: I/O
|
|
330
|
+
.map(this.generateReceipt); // Core: pure
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 5. Next.js Integration
|
|
338
|
+
|
|
339
|
+
### 5.1 Server Actions + Result
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// app/actions/order.ts
|
|
343
|
+
'use server';
|
|
344
|
+
|
|
345
|
+
import { ResultAsync } from 'neverthrow';
|
|
346
|
+
import { orderService } from '@/lib/services';
|
|
347
|
+
|
|
348
|
+
type ActionResult<T> =
|
|
349
|
+
| { success: true; data: T }
|
|
350
|
+
| { success: false; error: { type: string; message: string } };
|
|
351
|
+
|
|
352
|
+
export async function createOrder(formData: FormData): Promise<ActionResult<Order>> {
|
|
353
|
+
const input = Object.fromEntries(formData);
|
|
354
|
+
const result = await orderService.createOrder(input);
|
|
355
|
+
|
|
356
|
+
return result.match(
|
|
357
|
+
(order) => ({ success: true, data: order }),
|
|
358
|
+
(error) => ({
|
|
359
|
+
success: false,
|
|
360
|
+
error: { type: error.type, message: formatError(error) }
|
|
361
|
+
})
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### 5.2 React Hook
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// hooks/useAction.ts
|
|
370
|
+
import { useState, useCallback } from 'react';
|
|
371
|
+
|
|
372
|
+
export function useAction<TInput, TOutput>(
|
|
373
|
+
action: (input: TInput) => Promise<ActionResult<TOutput>>
|
|
374
|
+
) {
|
|
375
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
376
|
+
const [error, setError] = useState<string | null>(null);
|
|
377
|
+
const [data, setData] = useState<TOutput | null>(null);
|
|
378
|
+
|
|
379
|
+
const execute = useCallback(async (input: TInput) => {
|
|
380
|
+
setIsLoading(true);
|
|
381
|
+
setError(null);
|
|
382
|
+
|
|
383
|
+
const result = await action(input);
|
|
384
|
+
|
|
385
|
+
if (result.success) {
|
|
386
|
+
setData(result.data);
|
|
387
|
+
} else {
|
|
388
|
+
setError(result.error.message);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
setIsLoading(false);
|
|
392
|
+
return result;
|
|
393
|
+
}, [action]);
|
|
394
|
+
|
|
395
|
+
return { execute, isLoading, error, data };
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## 6. Must Keep `throw` Scenarios
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
// 1. React Error Boundary (must throw)
|
|
405
|
+
async function OrderDetails({ id }: { id: string }) {
|
|
406
|
+
const result = await orderService.getOrder(id);
|
|
407
|
+
if (result.isErr()) {
|
|
408
|
+
throw new Error(result.error.message); // Let ErrorBoundary catch
|
|
409
|
+
}
|
|
410
|
+
return <OrderView order={result.value} />;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 2. Next.js redirect/notFound (must throw)
|
|
414
|
+
import { redirect, notFound } from 'next/navigation';
|
|
415
|
+
|
|
416
|
+
export default async function OrderPage({ params }: { params: { id: string } }) {
|
|
417
|
+
const result = await orderService.getOrder(params.id);
|
|
418
|
+
|
|
419
|
+
if (result.isErr()) {
|
|
420
|
+
if (result.error.type === 'NOT_FOUND') {
|
|
421
|
+
notFound(); // throws internally
|
|
422
|
+
}
|
|
423
|
+
if (result.error.type === 'UNAUTHORIZED') {
|
|
424
|
+
redirect('/login'); // throws internally
|
|
425
|
+
}
|
|
426
|
+
throw new Error(result.error.message);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return <OrderDetails order={result.value} />;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// 3. Constructors (cannot return Result)
|
|
433
|
+
// 4. Top-level try-catch in entry points
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## 7. Migration Checklist
|
|
439
|
+
|
|
440
|
+
- [ ] Install `neverthrow` library: `npm install neverthrow`
|
|
441
|
+
- [ ] Install `zod` for validation: `npm install zod`
|
|
442
|
+
- [ ] Define error type hierarchy (discriminated unions)
|
|
443
|
+
- [ ] Transform entry points to return `ResultAsync<T, E>`
|
|
444
|
+
- [ ] Extract pure functions to `lib/core/` directory
|
|
445
|
+
- [ ] Add Zod schemas for Core validation
|
|
446
|
+
- [ ] Add JSDoc `@pre/@post` comments to Core functions
|
|
447
|
+
- [ ] Run TypeScript compiler in strict mode
|
|
448
|
+
- [ ] Update API handlers to use `result.match()`
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
*Pattern Library v1.0 — LX-09*
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Expected variables:
|
|
3
|
+
- project_name: str
|
|
4
|
+
- timestamp: str
|
|
5
|
+
- invar_version: str
|
|
6
|
+
- language: str
|
|
7
|
+
- framework?: str (default "N/A")
|
|
8
|
+
- loc: int
|
|
9
|
+
- files: int
|
|
10
|
+
- test_type: str
|
|
11
|
+
- test_count: int
|
|
12
|
+
- compatibility: int (0-100)
|
|
13
|
+
- total_days: int
|
|
14
|
+
- risk_level: str ("Low" | "Medium" | "High")
|
|
15
|
+
- architecture_diagram: str
|
|
16
|
+
- dependency_map?: str
|
|
17
|
+
- current_error, gap_error: str
|
|
18
|
+
- current_validation, gap_validation: str
|
|
19
|
+
- current_separation, gap_separation: str
|
|
20
|
+
- current_test, gap_test: str
|
|
21
|
+
- error_patterns?: [{type: str, count: int, locations: [str]}]
|
|
22
|
+
- validation_patterns?: [{library: str, usage: str, files: [str]}]
|
|
23
|
+
- high_risk_areas?: [{name: str, reason: str, files: [str], impact: str}]
|
|
24
|
+
- blockers?: [{name: str, description: str, mitigation: str}]
|
|
25
|
+
- dependency_risks?: [{name: str, risk_level: str, reason: str}]
|
|
26
|
+
- phase1_days, phase2_days, phase3_days, phase4_days, phase5_days: int
|
|
27
|
+
- contract_type: str
|
|
28
|
+
- base_effort: float
|
|
29
|
+
- adjustments: [{factor: str, reason: str}]
|
|
30
|
+
- adjustment_formula: str
|
|
31
|
+
- recommendation: str
|
|
32
|
+
- result_library: str
|
|
33
|
+
- additional_prereqs?: [str]
|
|
34
|
+
- quick_wins?: [{name: str, description: str, effort: str, impact: str}]
|
|
35
|
+
- layers: [{name: str, files: int, loc: int, notes?: str}]
|
|
36
|
+
- priority_files: [{path: str, loc: int, current_state: str, target_state: str, dependencies?: [str]}]
|
|
37
|
+
#}
|
|
38
|
+
# Invar Onboarding Assessment
|
|
39
|
+
|
|
40
|
+
> Project: {{ project_name }}
|
|
41
|
+
> Assessed: {{ timestamp }}
|
|
42
|
+
> Invar Version: {{ invar_version }}
|
|
43
|
+
|
|
44
|
+
## 1. Summary
|
|
45
|
+
|
|
46
|
+
| Metric | Value |
|
|
47
|
+
|--------|-------|
|
|
48
|
+
| Primary Language | {{ language }} |
|
|
49
|
+
| Framework | {{ framework | default("N/A") }} |
|
|
50
|
+
| Code Size | {{ loc }} lines / {{ files }} files |
|
|
51
|
+
| Test Coverage | {{ test_type }}: {{ test_count }} tests |
|
|
52
|
+
| **Invar Compatibility** | **{{ compatibility }}%** |
|
|
53
|
+
| **Estimated Effort** | **{{ total_days }} days** |
|
|
54
|
+
| **Risk Level** | **{{ risk_level }}** |
|
|
55
|
+
|
|
56
|
+
## 2. Architecture Analysis
|
|
57
|
+
|
|
58
|
+
### 2.1 Layer Structure
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
{{ architecture_diagram }}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 2.2 Dependency Map
|
|
65
|
+
|
|
66
|
+
{% if dependency_map %}
|
|
67
|
+
{{ dependency_map }}
|
|
68
|
+
{% else %}
|
|
69
|
+
*No complex dependencies detected.*
|
|
70
|
+
{% endif %}
|
|
71
|
+
|
|
72
|
+
## 3. Pattern Analysis
|
|
73
|
+
|
|
74
|
+
| Dimension | Current | Invar Target | Gap |
|
|
75
|
+
|-----------|---------|--------------|-----|
|
|
76
|
+
| Error Handling | {{ current_error }} | Result[T, E] / Result<T, E> | {{ gap_error }} |
|
|
77
|
+
| Validation | {{ current_validation }} | @pre/@post / Zod | {{ gap_validation }} |
|
|
78
|
+
| Core/Shell | {{ current_separation }} | Explicit separation | {{ gap_separation }} |
|
|
79
|
+
| Testing | {{ current_test }} | Doctest + Property | {{ gap_test }} |
|
|
80
|
+
|
|
81
|
+
### 3.1 Error Handling Details
|
|
82
|
+
|
|
83
|
+
{% if error_patterns %}
|
|
84
|
+
| Pattern | Count | Location |
|
|
85
|
+
|---------|-------|----------|
|
|
86
|
+
{% for pattern in error_patterns %}
|
|
87
|
+
| {{ pattern.type }} | {{ pattern.count }} | {{ pattern.locations | join(", ") }} |
|
|
88
|
+
{% endfor %}
|
|
89
|
+
{% else %}
|
|
90
|
+
*No error handling patterns detected.*
|
|
91
|
+
{% endif %}
|
|
92
|
+
|
|
93
|
+
### 3.2 Validation Details
|
|
94
|
+
|
|
95
|
+
{% if validation_patterns %}
|
|
96
|
+
| Library | Usage | Files |
|
|
97
|
+
|---------|-------|-------|
|
|
98
|
+
{% for pattern in validation_patterns %}
|
|
99
|
+
| {{ pattern.library }} | {{ pattern.usage }} | {{ pattern.files | join(", ") }} |
|
|
100
|
+
{% endfor %}
|
|
101
|
+
{% else %}
|
|
102
|
+
*No validation libraries detected.*
|
|
103
|
+
{% endif %}
|
|
104
|
+
|
|
105
|
+
## 4. Risk Assessment
|
|
106
|
+
|
|
107
|
+
### 4.1 High Risk Areas
|
|
108
|
+
|
|
109
|
+
{% if high_risk_areas %}
|
|
110
|
+
{% for area in high_risk_areas %}
|
|
111
|
+
- **{{ area.name }}**: {{ area.reason }}
|
|
112
|
+
- Files: {{ area.files | join(", ") }}
|
|
113
|
+
- Impact: {{ area.impact }}
|
|
114
|
+
{% endfor %}
|
|
115
|
+
{% else %}
|
|
116
|
+
*No high risk areas identified.*
|
|
117
|
+
{% endif %}
|
|
118
|
+
|
|
119
|
+
### 4.2 Blockers
|
|
120
|
+
|
|
121
|
+
{% if blockers %}
|
|
122
|
+
{% for blocker in blockers %}
|
|
123
|
+
- [ ] **{{ blocker.name }}**: {{ blocker.description }}
|
|
124
|
+
- Mitigation: {{ blocker.mitigation }}
|
|
125
|
+
{% endfor %}
|
|
126
|
+
{% else %}
|
|
127
|
+
*No blockers identified.*
|
|
128
|
+
{% endif %}
|
|
129
|
+
|
|
130
|
+
### 4.3 Dependency Risks
|
|
131
|
+
|
|
132
|
+
{% if dependency_risks %}
|
|
133
|
+
| Dependency | Risk | Reason |
|
|
134
|
+
|------------|------|--------|
|
|
135
|
+
{% for dep in dependency_risks %}
|
|
136
|
+
| {{ dep.name }} | {{ dep.risk_level }} | {{ dep.reason }} |
|
|
137
|
+
{% endfor %}
|
|
138
|
+
{% else %}
|
|
139
|
+
*No dependency risks identified.*
|
|
140
|
+
{% endif %}
|
|
141
|
+
|
|
142
|
+
## 5. Effort Breakdown
|
|
143
|
+
|
|
144
|
+
| Phase | Scope | Estimate |
|
|
145
|
+
|-------|-------|----------|
|
|
146
|
+
| Foundation | Error types, Result infrastructure | {{ phase1_days }} days |
|
|
147
|
+
| Core Extraction | Pure function isolation | {{ phase2_days }} days |
|
|
148
|
+
| Shell Refactor | I/O layer Result conversion | {{ phase3_days }} days |
|
|
149
|
+
| Contracts | {{ contract_type }} | {{ phase4_days }} days |
|
|
150
|
+
| Validation | Guard integration, test coverage | {{ phase5_days }} days |
|
|
151
|
+
| **Total** | | **{{ total_days }} days** |
|
|
152
|
+
|
|
153
|
+
### 5.1 Estimation Factors
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
Base effort: {{ base_effort }} days ({{ loc }} LOC / 100)
|
|
157
|
+
|
|
158
|
+
Adjustments:
|
|
159
|
+
{% for adj in adjustments %}
|
|
160
|
+
{{ adj.factor }} {{ adj.reason }}
|
|
161
|
+
{% endfor %}
|
|
162
|
+
|
|
163
|
+
Final: {{ base_effort }} {{ adjustment_formula }} = {{ total_days }} days
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 6. Recommendations
|
|
167
|
+
|
|
168
|
+
### 6.1 Suggested Approach
|
|
169
|
+
|
|
170
|
+
{{ recommendation }}
|
|
171
|
+
|
|
172
|
+
### 6.2 Prerequisites
|
|
173
|
+
|
|
174
|
+
- [ ] E2E test coverage > 80% for critical paths
|
|
175
|
+
- [ ] Result library installed ({{ result_library }})
|
|
176
|
+
- [ ] Error type hierarchy defined
|
|
177
|
+
{% for prereq in additional_prereqs %}
|
|
178
|
+
- [ ] {{ prereq }}
|
|
179
|
+
{% endfor %}
|
|
180
|
+
|
|
181
|
+
### 6.3 Quick Wins
|
|
182
|
+
|
|
183
|
+
{% if quick_wins %}
|
|
184
|
+
{% for win in quick_wins %}
|
|
185
|
+
1. **{{ win.name }}**: {{ win.description }}
|
|
186
|
+
- Effort: {{ win.effort }}
|
|
187
|
+
- Impact: {{ win.impact }}
|
|
188
|
+
{% endfor %}
|
|
189
|
+
{% else %}
|
|
190
|
+
*No quick wins identified.*
|
|
191
|
+
{% endif %}
|
|
192
|
+
|
|
193
|
+
## 7. File Analysis
|
|
194
|
+
|
|
195
|
+
### 7.1 Files by Layer (Proposed)
|
|
196
|
+
|
|
197
|
+
| Layer | Files | LOC | Notes |
|
|
198
|
+
|-------|-------|-----|-------|
|
|
199
|
+
{% for layer in layers %}
|
|
200
|
+
| {{ layer.name }} | {{ layer.files }} | {{ layer.loc }} | {{ layer.notes | default("") }} |
|
|
201
|
+
{% endfor %}
|
|
202
|
+
|
|
203
|
+
### 7.2 Migration Priority
|
|
204
|
+
|
|
205
|
+
{% for file in priority_files %}
|
|
206
|
+
{{ loop.index }}. `{{ file.path }}` ({{ file.loc }} lines)
|
|
207
|
+
- Current: {{ file.current_state }}
|
|
208
|
+
- Target: {{ file.target_state }}
|
|
209
|
+
- Dependencies: {{ file.dependencies | join(", ") | default("None") }}
|
|
210
|
+
{% endfor %}
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
*Generated by /invar-onboard*
|