invar-tools 1.8.0__py3-none-any.whl → 1.11.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.
Files changed (117) hide show
  1. invar/__init__.py +8 -0
  2. invar/core/doc_edit.py +187 -0
  3. invar/core/doc_parser.py +563 -0
  4. invar/core/language.py +88 -0
  5. invar/core/models.py +106 -0
  6. invar/core/patterns/detector.py +6 -1
  7. invar/core/patterns/p0_exhaustive.py +15 -3
  8. invar/core/patterns/p0_literal.py +15 -3
  9. invar/core/patterns/p0_newtype.py +15 -3
  10. invar/core/patterns/p0_nonempty.py +15 -3
  11. invar/core/patterns/p0_validation.py +15 -3
  12. invar/core/patterns/registry.py +5 -1
  13. invar/core/patterns/types.py +5 -1
  14. invar/core/property_gen.py +4 -0
  15. invar/core/rules.py +84 -18
  16. invar/core/sync_helpers.py +27 -1
  17. invar/core/ts_parsers.py +286 -0
  18. invar/core/ts_sig_parser.py +310 -0
  19. invar/mcp/handlers.py +408 -0
  20. invar/mcp/server.py +288 -143
  21. invar/node_tools/MANIFEST +7 -0
  22. invar/node_tools/__init__.py +51 -0
  23. invar/node_tools/fc-runner/cli.js +77 -0
  24. invar/node_tools/quick-check/cli.js +28 -0
  25. invar/node_tools/ts-analyzer/cli.js +480 -0
  26. invar/shell/claude_hooks.py +35 -12
  27. invar/shell/commands/doc.py +409 -0
  28. invar/shell/commands/guard.py +41 -1
  29. invar/shell/commands/init.py +154 -16
  30. invar/shell/commands/perception.py +157 -33
  31. invar/shell/commands/skill.py +187 -0
  32. invar/shell/commands/template_sync.py +65 -13
  33. invar/shell/commands/uninstall.py +60 -12
  34. invar/shell/commands/update.py +6 -14
  35. invar/shell/contract_coverage.py +1 -0
  36. invar/shell/doc_tools.py +459 -0
  37. invar/shell/fs.py +67 -13
  38. invar/shell/pi_hooks.py +6 -0
  39. invar/shell/prove/crosshair.py +3 -0
  40. invar/shell/prove/guard_ts.py +902 -0
  41. invar/shell/skill_manager.py +355 -0
  42. invar/shell/template_engine.py +28 -4
  43. invar/shell/templates.py +4 -4
  44. invar/templates/claude-md/python/critical-rules.md +33 -0
  45. invar/templates/claude-md/python/quick-reference.md +24 -0
  46. invar/templates/claude-md/typescript/critical-rules.md +40 -0
  47. invar/templates/claude-md/typescript/quick-reference.md +24 -0
  48. invar/templates/claude-md/universal/check-in.md +25 -0
  49. invar/templates/claude-md/universal/skills.md +73 -0
  50. invar/templates/claude-md/universal/workflow.md +55 -0
  51. invar/templates/commands/{audit.md → audit.md.jinja} +18 -1
  52. invar/templates/config/AGENT.md.jinja +58 -0
  53. invar/templates/config/CLAUDE.md.jinja +16 -209
  54. invar/templates/config/context.md.jinja +19 -0
  55. invar/templates/examples/{README.md → python/README.md} +2 -0
  56. invar/templates/examples/{conftest.py → python/conftest.py} +1 -1
  57. invar/templates/examples/{contracts.py → python/contracts.py} +81 -4
  58. invar/templates/examples/python/core_shell.py +227 -0
  59. invar/templates/examples/python/functional.py +613 -0
  60. invar/templates/examples/typescript/README.md +31 -0
  61. invar/templates/examples/typescript/contracts.ts +163 -0
  62. invar/templates/examples/typescript/core_shell.ts +374 -0
  63. invar/templates/examples/typescript/functional.ts +601 -0
  64. invar/templates/examples/typescript/workflow.md +95 -0
  65. invar/templates/hooks/PostToolUse.sh.jinja +10 -1
  66. invar/templates/hooks/PreToolUse.sh.jinja +38 -0
  67. invar/templates/hooks/Stop.sh.jinja +1 -1
  68. invar/templates/hooks/UserPromptSubmit.sh.jinja +7 -0
  69. invar/templates/hooks/pi/invar.ts.jinja +9 -0
  70. invar/templates/manifest.toml +7 -6
  71. invar/templates/onboard/assessment.md.jinja +214 -0
  72. invar/templates/onboard/patterns/python.md +347 -0
  73. invar/templates/onboard/patterns/typescript.md +452 -0
  74. invar/templates/onboard/roadmap.md.jinja +168 -0
  75. invar/templates/protocol/INVAR.md.jinja +51 -0
  76. invar/templates/protocol/python/architecture-examples.md +41 -0
  77. invar/templates/protocol/python/contracts-syntax.md +56 -0
  78. invar/templates/protocol/python/markers.md +44 -0
  79. invar/templates/protocol/python/tools.md +24 -0
  80. invar/templates/protocol/python/troubleshooting.md +38 -0
  81. invar/templates/protocol/typescript/architecture-examples.md +52 -0
  82. invar/templates/protocol/typescript/contracts-syntax.md +73 -0
  83. invar/templates/protocol/typescript/markers.md +48 -0
  84. invar/templates/protocol/typescript/tools.md +65 -0
  85. invar/templates/protocol/typescript/troubleshooting.md +104 -0
  86. invar/templates/protocol/universal/architecture.md +36 -0
  87. invar/templates/protocol/universal/completion.md +14 -0
  88. invar/templates/protocol/universal/contracts-concept.md +37 -0
  89. invar/templates/protocol/universal/header.md +17 -0
  90. invar/templates/protocol/universal/session.md +17 -0
  91. invar/templates/protocol/universal/six-laws.md +10 -0
  92. invar/templates/protocol/universal/usbv.md +14 -0
  93. invar/templates/protocol/universal/visible-workflow.md +25 -0
  94. invar/templates/skills/develop/SKILL.md.jinja +85 -3
  95. invar/templates/skills/extensions/_registry.yaml +93 -0
  96. invar/templates/skills/extensions/acceptance/SKILL.md +383 -0
  97. invar/templates/skills/extensions/invar-onboard/SKILL.md +448 -0
  98. invar/templates/skills/extensions/invar-onboard/patterns/python.md +347 -0
  99. invar/templates/skills/extensions/invar-onboard/patterns/typescript.md +452 -0
  100. invar/templates/skills/extensions/invar-onboard/templates/assessment.md.jinja +214 -0
  101. invar/templates/skills/extensions/invar-onboard/templates/roadmap.md.jinja +168 -0
  102. invar/templates/skills/extensions/security/SKILL.md +382 -0
  103. invar/templates/skills/extensions/security/patterns/_common.yaml +126 -0
  104. invar/templates/skills/extensions/security/patterns/python.yaml +155 -0
  105. invar/templates/skills/extensions/security/patterns/typescript.yaml +194 -0
  106. invar/templates/skills/review/SKILL.md.jinja +220 -248
  107. {invar_tools-1.8.0.dist-info → invar_tools-1.11.0.dist-info}/METADATA +336 -12
  108. invar_tools-1.11.0.dist-info/RECORD +178 -0
  109. invar/templates/examples/core_shell.py +0 -127
  110. invar/templates/protocol/INVAR.md +0 -310
  111. invar_tools-1.8.0.dist-info/RECORD +0 -116
  112. /invar/templates/examples/{workflow.md → python/workflow.md} +0 -0
  113. {invar_tools-1.8.0.dist-info → invar_tools-1.11.0.dist-info}/WHEEL +0 -0
  114. {invar_tools-1.8.0.dist-info → invar_tools-1.11.0.dist-info}/entry_points.txt +0 -0
  115. {invar_tools-1.8.0.dist-info → invar_tools-1.11.0.dist-info}/licenses/LICENSE +0 -0
  116. {invar_tools-1.8.0.dist-info → invar_tools-1.11.0.dist-info}/licenses/LICENSE-GPL +0 -0
  117. {invar_tools-1.8.0.dist-info → invar_tools-1.11.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Invar Contract Examples (TypeScript)
3
+ *
4
+ * Reference patterns for Zod schemas as contracts.
5
+ * Managed by Invar - do not edit directly.
6
+ */
7
+
8
+ import { z } from 'zod';
9
+
10
+ // =============================================================================
11
+ // GOOD: Complete Contract with Zod Schema
12
+ // =============================================================================
13
+
14
+ /**
15
+ * Precondition: price > 0, 0 <= discount <= 1
16
+ * Postcondition: result >= 0
17
+ */
18
+ const DiscountedPriceInput = z.object({
19
+ price: z.number().positive(),
20
+ discount: z.number().min(0).max(1),
21
+ });
22
+
23
+ const DiscountedPriceOutput = z.number().nonnegative();
24
+
25
+ /**
26
+ * Apply discount to price.
27
+ *
28
+ * @example
29
+ * discountedPrice({ price: 100.0, discount: 0.2 }) // => 80.0
30
+ *
31
+ * @example
32
+ * discountedPrice({ price: 100.0, discount: 0 }) // => 100.0 (no discount)
33
+ *
34
+ * @example
35
+ * discountedPrice({ price: 100.0, discount: 1 }) // => 0.0 (full discount)
36
+ */
37
+ export function discountedPrice(
38
+ input: z.infer<typeof DiscountedPriceInput>
39
+ ): number {
40
+ const { price, discount } = DiscountedPriceInput.parse(input);
41
+ const result = price * (1 - discount);
42
+ return DiscountedPriceOutput.parse(result);
43
+ }
44
+
45
+ // =============================================================================
46
+ // GOOD: List Processing with Length Constraint
47
+ // =============================================================================
48
+
49
+ /**
50
+ * Precondition: items.length > 0
51
+ * Postcondition: result is finite (no NaN/Infinity)
52
+ */
53
+ const AverageInput = z.array(z.number()).nonempty();
54
+ const AverageOutput = z.number().finite();
55
+
56
+ /**
57
+ * Calculate average of non-empty array.
58
+ *
59
+ * @example
60
+ * average([1, 2, 3]) // => 2.0
61
+ *
62
+ * @example
63
+ * average([5]) // => 5.0 (single element)
64
+ *
65
+ * @example
66
+ * average([0, 0, 0]) // => 0.0 (all zeros)
67
+ */
68
+ export function average(items: number[]): number {
69
+ const validated = AverageInput.parse(items);
70
+ const sum = validated.reduce((a, b) => a + b, 0);
71
+ const result = sum / validated.length;
72
+ return AverageOutput.parse(result);
73
+ }
74
+
75
+ // =============================================================================
76
+ // GOOD: Object Transformation
77
+ // =============================================================================
78
+
79
+ /**
80
+ * Precondition: Object.keys(data).length > 0
81
+ * Postcondition: Object.keys(result).length > 0
82
+ */
83
+ const NormalizeKeysInput = z.record(z.string(), z.number()).refine(
84
+ (obj) => Object.keys(obj).length > 0,
85
+ { message: 'Object must have at least one key' }
86
+ );
87
+
88
+ const NormalizeKeysOutput = z.record(z.string(), z.number()).refine(
89
+ (obj) => Object.keys(obj).length > 0,
90
+ { message: 'Result must have at least one key' }
91
+ );
92
+
93
+ /**
94
+ * Lowercase all keys.
95
+ *
96
+ * @example
97
+ * normalizeKeys({ A: 1, B: 2 }) // => { a: 1, b: 2 }
98
+ *
99
+ * @example
100
+ * normalizeKeys({ X: 10 }) // => { x: 10 }
101
+ */
102
+ export function normalizeKeys(
103
+ data: Record<string, number>
104
+ ): Record<string, number> {
105
+ const validated = NormalizeKeysInput.parse(data);
106
+ const result = Object.fromEntries(
107
+ Object.entries(validated).map(([k, v]) => [k.toLowerCase(), v])
108
+ );
109
+ return NormalizeKeysOutput.parse(result);
110
+ }
111
+
112
+ // =============================================================================
113
+ // BAD: Incomplete Contract (anti-pattern)
114
+ // =============================================================================
115
+
116
+ // DON'T: Schema that accepts anything
117
+ // const BadInput = z.any();
118
+ // function process(x: unknown) { ... }
119
+
120
+ // DON'T: Missing edge cases in examples
121
+ // function divide(a: number, b: number) {
122
+ // // @example divide(10, 2) // => 5.0
123
+ // // Missing: what about b=0?
124
+ // }
125
+
126
+ // =============================================================================
127
+ // GOOD: Multiple Preconditions
128
+ // =============================================================================
129
+
130
+ /**
131
+ * Precondition: start >= 0
132
+ * Precondition: end >= start
133
+ * Postcondition: result >= 0
134
+ */
135
+ const RangeSizeInput = z.object({
136
+ start: z.number().nonnegative(),
137
+ end: z.number(),
138
+ }).refine(
139
+ (data) => data.end >= data.start,
140
+ { message: 'end must be >= start' }
141
+ );
142
+
143
+ const RangeSizeOutput = z.number().nonnegative();
144
+
145
+ /**
146
+ * Calculate size of range [start, end).
147
+ *
148
+ * @example
149
+ * rangeSize({ start: 0, end: 10 }) // => 10
150
+ *
151
+ * @example
152
+ * rangeSize({ start: 5, end: 5 }) // => 0 (empty range)
153
+ *
154
+ * @example
155
+ * rangeSize({ start: 0, end: 1 }) // => 1 (single element)
156
+ */
157
+ export function rangeSize(
158
+ input: z.infer<typeof RangeSizeInput>
159
+ ): number {
160
+ const { start, end } = RangeSizeInput.parse(input);
161
+ const result = end - start;
162
+ return RangeSizeOutput.parse(result);
163
+ }
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Invar Core/Shell Separation Examples (TypeScript)
3
+ *
4
+ * Reference patterns for Core vs Shell architecture.
5
+ * Managed by Invar - do not edit directly.
6
+ */
7
+
8
+ import { z } from 'zod';
9
+ import { Result, ok, err } from 'neverthrow';
10
+ import * as fs from 'fs/promises';
11
+
12
+ // =============================================================================
13
+ // CORE: Pure Logic (no I/O)
14
+ // =============================================================================
15
+ // Location: src/*/core/
16
+ // Requirements: Zod schemas, pure functions, no I/O imports
17
+ // =============================================================================
18
+
19
+ /**
20
+ * Precondition: content is defined (can be empty string)
21
+ * Postcondition: all lines are trimmed and non-empty
22
+ */
23
+ const ParseLinesInput = z.string();
24
+ const ParseLinesOutput = z.array(z.string().min(1));
25
+
26
+ /**
27
+ * Parse content into non-empty lines.
28
+ *
29
+ * @example
30
+ * parseLines("a\nb\nc") // => ["a", "b", "c"]
31
+ *
32
+ * @example
33
+ * parseLines("") // => []
34
+ *
35
+ * @example
36
+ * parseLines(" \n ") // => [] (whitespace only)
37
+ */
38
+ export function parseLines(content: string): string[] {
39
+ const validated = ParseLinesInput.parse(content);
40
+ const result = validated
41
+ .split('\n')
42
+ .map(line => line.trim())
43
+ .filter(line => line.length > 0);
44
+ return ParseLinesOutput.parse(result);
45
+ }
46
+
47
+ /**
48
+ * Precondition: all items are strings
49
+ * Postcondition: all counts are positive
50
+ */
51
+ const CountItemsInput = z.array(z.string());
52
+ const CountItemsOutput = z.record(z.string(), z.number().positive());
53
+
54
+ /**
55
+ * Count occurrences of each item.
56
+ *
57
+ * @example
58
+ * countItems(["a", "b", "a"]) // => { a: 2, b: 1 }
59
+ *
60
+ * @example
61
+ * countItems([]) // => {}
62
+ */
63
+ export function countItems(items: string[]): Record<string, number> {
64
+ const validated = CountItemsInput.parse(items);
65
+ const counts: Record<string, number> = {};
66
+ for (const item of validated) {
67
+ counts[item] = (counts[item] ?? 0) + 1;
68
+ }
69
+ // Note: Output validation skipped for empty result (no positive numbers)
70
+ if (Object.keys(counts).length === 0) {
71
+ return counts;
72
+ }
73
+ return CountItemsOutput.parse(counts);
74
+ }
75
+
76
+ // =============================================================================
77
+ // SHELL: I/O Operations
78
+ // =============================================================================
79
+ // Location: src/*/shell/
80
+ // Requirements: Result<T, E> return type, calls Core for logic
81
+ // =============================================================================
82
+
83
+ /**
84
+ * Error types for file operations.
85
+ */
86
+ export class FileNotFoundError extends Error {
87
+ constructor(path: string) {
88
+ super(`File not found: ${path}`);
89
+ this.name = 'FileNotFoundError';
90
+ }
91
+ }
92
+
93
+ export class PermissionError extends Error {
94
+ constructor(path: string) {
95
+ super(`Permission denied: ${path}`);
96
+ this.name = 'PermissionError';
97
+ }
98
+ }
99
+
100
+ type FileError = FileNotFoundError | PermissionError | Error;
101
+
102
+ /**
103
+ * Read file content.
104
+ *
105
+ * Shell handles I/O, returns Result for error handling.
106
+ */
107
+ export async function readFile(
108
+ path: string
109
+ ): Promise<Result<string, FileError>> {
110
+ try {
111
+ const content = await fs.readFile(path, 'utf-8');
112
+ return ok(content);
113
+ } catch (error) {
114
+ if (error instanceof Error) {
115
+ if ('code' in error && error.code === 'ENOENT') {
116
+ return err(new FileNotFoundError(path));
117
+ }
118
+ if ('code' in error && error.code === 'EACCES') {
119
+ return err(new PermissionError(path));
120
+ }
121
+ return err(error);
122
+ }
123
+ return err(new Error(String(error)));
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Count lines in file - demonstrates Core/Shell integration.
129
+ *
130
+ * Shell reads file -> Core parses content -> Shell returns result.
131
+ */
132
+ export async function countLinesInFile(
133
+ path: string
134
+ ): Promise<Result<Record<string, number>, FileError>> {
135
+ // Shell: I/O operation
136
+ const contentResult = await readFile(path);
137
+
138
+ if (contentResult.isErr()) {
139
+ return err(contentResult.error);
140
+ }
141
+
142
+ const content = contentResult.value;
143
+
144
+ // Core: Pure logic (no I/O)
145
+ const lines = parseLines(content);
146
+ const counts = countItems(lines);
147
+
148
+ // Shell: Return result
149
+ return ok(counts);
150
+ }
151
+
152
+ // =============================================================================
153
+ // ANTI-PATTERNS
154
+ // =============================================================================
155
+
156
+ // DON'T: I/O in Core
157
+ // function parseFile(path: string) { // BAD: path in Core
158
+ // const content = fs.readFileSync(path); // BAD: I/O in Core
159
+ // return parseLines(content);
160
+ // }
161
+
162
+ // DO: Core receives content, not paths
163
+ // function parseContent(content: string) { // GOOD: receives data
164
+ // return parseLines(content);
165
+ // }
166
+
167
+
168
+ // DON'T: Throw exceptions in Shell
169
+ // async function loadConfig(path: string): Promise<Config> { // BAD: no Result
170
+ // return JSON.parse(await fs.readFile(path)); // Exceptions not handled
171
+ // }
172
+
173
+ // DO: Return Result<T, E>
174
+ // async function loadConfig(path: string): Promise<Result<Config, Error>> {
175
+ // try {
176
+ // const content = await fs.readFile(path, 'utf-8');
177
+ // return ok(JSON.parse(content));
178
+ // } catch (error) {
179
+ // return err(error instanceof Error ? error : new Error(String(error)));
180
+ // }
181
+ // }
182
+
183
+
184
+ // =============================================================================
185
+ // Next.js Integration Pattern
186
+ // =============================================================================
187
+ // Demonstrates how to use Result with Next.js API routes and Server Components.
188
+ // Shell (API handler) → Core (business logic) → Shell (HTTP response)
189
+
190
+ // NOTE: This is pseudocode - requires Next.js to be installed
191
+ // import { NextRequest, NextResponse } from 'next/server';
192
+ // import { ResultAsync } from 'neverthrow';
193
+
194
+ // -----------------------------------------------------------------------------
195
+ // Error Types
196
+ // -----------------------------------------------------------------------------
197
+
198
+ // interface ApiError {
199
+ // readonly code: string;
200
+ // readonly message: string;
201
+ // readonly status: number;
202
+ // }
203
+ //
204
+ // const NotFoundError = (id: string): ApiError => ({
205
+ // code: 'not_found',
206
+ // message: `Resource ${id} not found`,
207
+ // status: 404,
208
+ // });
209
+ //
210
+ // const ValidationError = (message: string): ApiError => ({
211
+ // code: 'validation_error',
212
+ // message,
213
+ // status: 400,
214
+ // });
215
+ //
216
+ // const InternalError = (message: string): ApiError => ({
217
+ // code: 'internal_error',
218
+ // message,
219
+ // status: 500,
220
+ // });
221
+
222
+ // -----------------------------------------------------------------------------
223
+ // CORE: Pure business logic (no Next.js imports)
224
+ // -----------------------------------------------------------------------------
225
+
226
+ // const UserSchema = z.object({
227
+ // id: z.string().min(1),
228
+ // name: z.string().min(1),
229
+ // email: z.string().email(),
230
+ // });
231
+ //
232
+ // type User = z.infer<typeof UserSchema>;
233
+ //
234
+ // /**
235
+ // * Core: Validate user data (pure, no I/O).
236
+ // */
237
+ // function validateUserData(data: unknown): Result<User, string> {
238
+ // const result = UserSchema.safeParse(data);
239
+ // if (!result.success) {
240
+ // return err(result.error.errors.map(e => e.message).join(', '));
241
+ // }
242
+ // return ok(result.data);
243
+ // }
244
+
245
+ // -----------------------------------------------------------------------------
246
+ // SHELL: Database I/O layer
247
+ // -----------------------------------------------------------------------------
248
+
249
+ // /**
250
+ // * Shell: Fetch user from database.
251
+ // */
252
+ // function fetchUserFromDb(id: string): ResultAsync<User, ApiError> {
253
+ // return ResultAsync.fromPromise(
254
+ // prisma.user.findUnique({ where: { id } }).then(user => {
255
+ // if (!user) throw new Error('not_found');
256
+ // return user;
257
+ // }),
258
+ // (error): ApiError => {
259
+ // if (error instanceof Error && error.message === 'not_found') {
260
+ // return NotFoundError(id);
261
+ // }
262
+ // return InternalError('Database error');
263
+ // }
264
+ // );
265
+ // }
266
+
267
+ // -----------------------------------------------------------------------------
268
+ // SHELL: Next.js API Route (App Router)
269
+ // -----------------------------------------------------------------------------
270
+
271
+ // /**
272
+ // * Shell: API route handler.
273
+ // *
274
+ // * Pattern: Shell → Core → Shell
275
+ // * 1. Shell receives HTTP request
276
+ // * 2. Core validates/processes
277
+ // * 3. Shell converts Result to HTTP response
278
+ // */
279
+ // export async function GET(
280
+ // request: NextRequest,
281
+ // { params }: { params: { id: string } }
282
+ // ) {
283
+ // const result = await fetchUserFromDb(params.id);
284
+ //
285
+ // // Convert Result to NextResponse
286
+ // return result.match(
287
+ // (user) => NextResponse.json(user),
288
+ // (error) => NextResponse.json(
289
+ // { error: error.message },
290
+ // { status: error.status }
291
+ // )
292
+ // );
293
+ // }
294
+ //
295
+ // export async function POST(request: NextRequest) {
296
+ // const body = await request.json();
297
+ //
298
+ // // Core: validate
299
+ // const validationResult = validateUserData(body);
300
+ // if (validationResult.isErr()) {
301
+ // return NextResponse.json(
302
+ // { error: validationResult.error },
303
+ // { status: 400 }
304
+ // );
305
+ // }
306
+ //
307
+ // // Shell: save to DB
308
+ // const saveResult = await saveUserToDb(validationResult.value);
309
+ //
310
+ // return saveResult.match(
311
+ // (user) => NextResponse.json(user, { status: 201 }),
312
+ // (error) => NextResponse.json(
313
+ // { error: error.message },
314
+ // { status: error.status }
315
+ // )
316
+ // );
317
+ // }
318
+
319
+ // -----------------------------------------------------------------------------
320
+ // SHELL: React Server Component
321
+ // -----------------------------------------------------------------------------
322
+
323
+ // /**
324
+ // * Shell: Server Component with Result handling.
325
+ // *
326
+ // * Server Components can call Shell functions directly.
327
+ // * Use .match() to handle success/error rendering.
328
+ // */
329
+ // export async function UserProfile({ userId }: { userId: string }) {
330
+ // const result = await fetchUserFromDb(userId);
331
+ //
332
+ // return result.match(
333
+ // (user) => (
334
+ // <div>
335
+ // <h1>{user.name}</h1>
336
+ // <p>{user.email}</p>
337
+ // </div>
338
+ // ),
339
+ // (error) => (
340
+ // <div className="error">
341
+ // {error.code === 'not_found'
342
+ // ? <p>User not found</p>
343
+ // : <p>Something went wrong</p>
344
+ // }
345
+ // </div>
346
+ // )
347
+ // );
348
+ // }
349
+
350
+ // =============================================================================
351
+ // Result → HTTP Response Mapping
352
+ // =============================================================================
353
+ // Common pattern for converting Result errors to HTTP status codes:
354
+ //
355
+ // | Error Type | HTTP Status | When to Use |
356
+ // |-----------------|-------------|--------------------------------|
357
+ // | NotFoundError | 404 | Resource doesn't exist |
358
+ // | ValidationError | 400 | Invalid input from client |
359
+ // | AuthError | 401/403 | Authentication/authorization |
360
+ // | ConflictError | 409 | Resource state conflict |
361
+ // | InternalError | 500 | Unexpected server error |
362
+ //
363
+ // Helper function:
364
+ // function resultToResponse<T>(
365
+ // result: Result<T, ApiError>
366
+ // ): NextResponse {
367
+ // return result.match(
368
+ // (value) => NextResponse.json(value),
369
+ // (error) => NextResponse.json(
370
+ // { code: error.code, message: error.message },
371
+ // { status: error.status }
372
+ // )
373
+ // );
374
+ // }