@claudetools/tools 0.4.0 → 0.5.0
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 +60 -4
- package/dist/cli.js +0 -0
- package/dist/codedna/parser.d.ts +40 -3
- package/dist/codedna/parser.js +65 -8
- package/dist/codedna/registry.js +4 -1
- package/dist/codedna/template-engine.js +66 -32
- package/dist/handlers/codedna-handlers.d.ts +1 -1
- package/dist/handlers/codedna-handlers.js +27 -0
- package/dist/helpers/api-client.js +7 -0
- package/dist/helpers/codedna-monitoring.d.ts +34 -0
- package/dist/helpers/codedna-monitoring.js +159 -0
- package/dist/helpers/error-tracking.d.ts +73 -0
- package/dist/helpers/error-tracking.js +164 -0
- package/dist/helpers/usage-analytics.d.ts +91 -0
- package/dist/helpers/usage-analytics.js +256 -0
- package/docs/AUTO-REGISTRATION.md +353 -0
- package/docs/CLAUDE4_PROMPT_ANALYSIS.md +589 -0
- package/docs/ENTITY_DSL_REFERENCE.md +685 -0
- package/docs/MODERN_STACK_COMPLETE_GUIDE.md +706 -0
- package/docs/PROMPT_STANDARDIZATION_RESULTS.md +324 -0
- package/docs/PROMPT_TIER_TEMPLATES.md +787 -0
- package/docs/RESEARCH_METHODOLOGY_EXTRACTION.md +336 -0
- package/package.json +12 -3
- package/scripts/verify-prompt-compliance.sh +197 -0
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
# CodeDNA Modern Stack - Complete Implementation Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
CodeDNA now supports complete modern full-stack development with:
|
|
6
|
+
- **Tanstack Start + Drizzle** - Full-stack framework with type-safe ORM
|
|
7
|
+
- **React 19 + ShadcnUI** - Modern components with latest hooks
|
|
8
|
+
- **Tanstack Router + Query** - Type-safe routing and data fetching
|
|
9
|
+
- **Resend + React Email** - Transactional email system
|
|
10
|
+
- **Cloudflare** - Edge deployment (Pages, Workers, D1, KV, R2)
|
|
11
|
+
- **Code Quality** - ESLint, Prettier, TypeScript strict mode
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { EntityParser } from '@claudetools/tools/codedna';
|
|
17
|
+
import { TanstackStartDrizzleGenerator } from '@claudetools/tools/codedna/generators/tanstack-start-drizzle';
|
|
18
|
+
import { React19ShadcnGenerator } from '@claudetools/tools/codedna/generators/react19-shadcn';
|
|
19
|
+
|
|
20
|
+
// Parse entity
|
|
21
|
+
const parser = new EntityParser();
|
|
22
|
+
const entity = parser.parse(`
|
|
23
|
+
Product(
|
|
24
|
+
name:string:required:length(3,200),
|
|
25
|
+
sku:string:unique:required:pattern(/^[A-Z]{3}-\\d{6}$/),
|
|
26
|
+
price:decimal:required:min(0),
|
|
27
|
+
stock:integer:min(0):default(0),
|
|
28
|
+
category:refOne(Category),
|
|
29
|
+
tags:refMany(Tag),
|
|
30
|
+
description:string:nullable,
|
|
31
|
+
images:array(string),
|
|
32
|
+
metadata:json:nullable,
|
|
33
|
+
status:enum(active,inactive,discontinued):default(active)
|
|
34
|
+
)
|
|
35
|
+
`);
|
|
36
|
+
|
|
37
|
+
// Generate full-stack app
|
|
38
|
+
const fullStackGen = new TanstackStartDrizzleGenerator({
|
|
39
|
+
database: 'postgresql',
|
|
40
|
+
deployment: 'cloudflare',
|
|
41
|
+
ssr: true,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const componentsGen = new React19ShadcnGenerator({
|
|
45
|
+
componentPattern: 'compound',
|
|
46
|
+
includeTable: true,
|
|
47
|
+
includeModal: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const fullStackFiles = await fullStackGen.generate(entity);
|
|
51
|
+
const componentFiles = await componentsGen.generate(entity);
|
|
52
|
+
|
|
53
|
+
// Result: 20+ production-ready files
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Generated Architecture
|
|
57
|
+
|
|
58
|
+
### File Structure
|
|
59
|
+
```
|
|
60
|
+
my-app/
|
|
61
|
+
├── src/
|
|
62
|
+
│ ├── db/
|
|
63
|
+
│ │ ├── schema/
|
|
64
|
+
│ │ │ └── product.ts # Drizzle schema
|
|
65
|
+
│ │ └── client.ts # DB connection
|
|
66
|
+
│ ├── queries/
|
|
67
|
+
│ │ └── product.ts # Type-safe queries
|
|
68
|
+
│ ├── mutations/
|
|
69
|
+
│ │ └── product.ts # Server actions
|
|
70
|
+
│ ├── routes/
|
|
71
|
+
│ │ └── product/
|
|
72
|
+
│ │ ├── index.tsx # List (SSR)
|
|
73
|
+
│ │ ├── $id.tsx # Detail (SSR)
|
|
74
|
+
│ │ ├── create.tsx # Create form
|
|
75
|
+
│ │ └── $id/
|
|
76
|
+
│ │ └── edit.tsx # Edit form
|
|
77
|
+
│ ├── components/
|
|
78
|
+
│ │ ├── ProductForm.tsx # React 19 form
|
|
79
|
+
│ │ ├── ProductTable.tsx # Sortable table
|
|
80
|
+
│ │ ├── ProductModal.tsx # Dialog
|
|
81
|
+
│ │ └── ProductCard.tsx # Card
|
|
82
|
+
│ └── types/
|
|
83
|
+
│ └── product.ts # TypeScript types
|
|
84
|
+
├── drizzle/
|
|
85
|
+
│ └── migrations/ # SQL migrations
|
|
86
|
+
├── wrangler.toml # Cloudflare config
|
|
87
|
+
├── drizzle.config.ts # Drizzle config
|
|
88
|
+
└── package.json # Dependencies
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 1. Tanstack Router + Query Integration
|
|
92
|
+
|
|
93
|
+
### Route with Data Loading
|
|
94
|
+
```typescript
|
|
95
|
+
// src/routes/product/index.tsx
|
|
96
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
97
|
+
import { useQuery } from '@tanstack/react-query';
|
|
98
|
+
import { getAllProducts } from '@/queries/product';
|
|
99
|
+
|
|
100
|
+
export const Route = createFileRoute('/product/')({
|
|
101
|
+
// Server-side loader
|
|
102
|
+
loader: async () => {
|
|
103
|
+
const products = await getAllProducts();
|
|
104
|
+
return { products };
|
|
105
|
+
},
|
|
106
|
+
component: ProductsPage,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
function ProductsPage() {
|
|
110
|
+
const { products } = Route.useLoaderData();
|
|
111
|
+
|
|
112
|
+
// Client-side query (optional, for real-time updates)
|
|
113
|
+
const { data, refetch } = useQuery({
|
|
114
|
+
queryKey: ['products'],
|
|
115
|
+
queryFn: getAllProducts,
|
|
116
|
+
initialData: products,
|
|
117
|
+
staleTime: 5000,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return <ProductTable data={data} />;
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Mutations with Optimistic Updates
|
|
125
|
+
```typescript
|
|
126
|
+
// src/hooks/useProductMutations.ts
|
|
127
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
128
|
+
import { createProduct, updateProduct, deleteProduct } from '@/mutations/product';
|
|
129
|
+
|
|
130
|
+
export function useProductMutations() {
|
|
131
|
+
const queryClient = useQueryClient();
|
|
132
|
+
|
|
133
|
+
const create = useMutation({
|
|
134
|
+
mutationFn: createProduct,
|
|
135
|
+
onMutate: async (newProduct) => {
|
|
136
|
+
// Cancel outgoing refetches
|
|
137
|
+
await queryClient.cancelQueries({ queryKey: ['products'] });
|
|
138
|
+
|
|
139
|
+
// Snapshot previous value
|
|
140
|
+
const previous = queryClient.getQueryData(['products']);
|
|
141
|
+
|
|
142
|
+
// Optimistically update
|
|
143
|
+
queryClient.setQueryData(['products'], (old: any) => [...old, newProduct]);
|
|
144
|
+
|
|
145
|
+
return { previous };
|
|
146
|
+
},
|
|
147
|
+
onError: (err, newProduct, context) => {
|
|
148
|
+
// Rollback on error
|
|
149
|
+
queryClient.setQueryData(['products'], context?.previous);
|
|
150
|
+
},
|
|
151
|
+
onSettled: () => {
|
|
152
|
+
// Refetch after mutation
|
|
153
|
+
queryClient.invalidateQueries({ queryKey: ['products'] });
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const update = useMutation({
|
|
158
|
+
mutationFn: ({ id, data }: { id: number; data: any }) => updateProduct(id, data),
|
|
159
|
+
onSuccess: () => {
|
|
160
|
+
queryClient.invalidateQueries({ queryKey: ['products'] });
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const remove = useMutation({
|
|
165
|
+
mutationFn: deleteProduct,
|
|
166
|
+
onSuccess: () => {
|
|
167
|
+
queryClient.invalidateQueries({ queryKey: ['products'] });
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return { create, update, remove };
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Prefetching
|
|
176
|
+
```typescript
|
|
177
|
+
// Prefetch on hover
|
|
178
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
179
|
+
import { getProductById } from '@/queries/product';
|
|
180
|
+
|
|
181
|
+
function ProductLink({ id }: { id: number }) {
|
|
182
|
+
const queryClient = useQueryClient();
|
|
183
|
+
|
|
184
|
+
const prefetch = () => {
|
|
185
|
+
queryClient.prefetchQuery({
|
|
186
|
+
queryKey: ['product', id],
|
|
187
|
+
queryFn: () => getProductById(id),
|
|
188
|
+
staleTime: 5000,
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<a
|
|
194
|
+
href={`/product/${id}`}
|
|
195
|
+
onMouseEnter={prefetch}
|
|
196
|
+
onFocus={prefetch}
|
|
197
|
+
>
|
|
198
|
+
View Product
|
|
199
|
+
</a>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## 2. Email System (Resend + React Email)
|
|
205
|
+
|
|
206
|
+
### Email Templates
|
|
207
|
+
```typescript
|
|
208
|
+
// src/emails/WelcomeEmail.tsx
|
|
209
|
+
import { Html, Head, Body, Container, Text, Button } from '@react-email/components';
|
|
210
|
+
|
|
211
|
+
interface WelcomeEmailProps {
|
|
212
|
+
name: string;
|
|
213
|
+
loginUrl: string;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function WelcomeEmail({ name, loginUrl }: WelcomeEmailProps) {
|
|
217
|
+
return (
|
|
218
|
+
<Html>
|
|
219
|
+
<Head />
|
|
220
|
+
<Body style={{ backgroundColor: '#f6f9fc' }}>
|
|
221
|
+
<Container style={{ padding: '20px' }}>
|
|
222
|
+
<Text style={{ fontSize: '24px', fontWeight: 'bold' }}>
|
|
223
|
+
Welcome, {name}!
|
|
224
|
+
</Text>
|
|
225
|
+
<Text>
|
|
226
|
+
Thanks for signing up. Get started by logging in:
|
|
227
|
+
</Text>
|
|
228
|
+
<Button
|
|
229
|
+
href={loginUrl}
|
|
230
|
+
style={{
|
|
231
|
+
backgroundColor: '#007bff',
|
|
232
|
+
color: '#fff',
|
|
233
|
+
padding: '12px 20px',
|
|
234
|
+
borderRadius: '4px',
|
|
235
|
+
}}
|
|
236
|
+
>
|
|
237
|
+
Log In
|
|
238
|
+
</Button>
|
|
239
|
+
</Container>
|
|
240
|
+
</Body>
|
|
241
|
+
</Html>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Resend Integration
|
|
247
|
+
```typescript
|
|
248
|
+
// src/lib/email.ts
|
|
249
|
+
import { Resend } from 'resend';
|
|
250
|
+
import { WelcomeEmail } from '@/emails/WelcomeEmail';
|
|
251
|
+
import { render } from '@react-email/render';
|
|
252
|
+
|
|
253
|
+
const resend = new Resend(process.env.RESEND_API_KEY);
|
|
254
|
+
|
|
255
|
+
export async function sendWelcomeEmail(to: string, name: string) {
|
|
256
|
+
const html = render(<WelcomeEmail name={name} loginUrl="https://app.example.com" />);
|
|
257
|
+
|
|
258
|
+
await resend.emails.send({
|
|
259
|
+
from: 'noreply@example.com',
|
|
260
|
+
to,
|
|
261
|
+
subject: 'Welcome to Our Platform',
|
|
262
|
+
html,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Development preview
|
|
267
|
+
export async function previewEmail() {
|
|
268
|
+
const html = render(<WelcomeEmail name="John Doe" loginUrl="https://localhost:3000" />);
|
|
269
|
+
return html;
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Generated Email Templates (3)
|
|
274
|
+
1. **Welcome Email** - User registration confirmation
|
|
275
|
+
2. **Password Reset** - Secure reset link with expiry
|
|
276
|
+
3. **Notification Email** - Generic transactional notifications
|
|
277
|
+
|
|
278
|
+
## 3. Cloudflare Deployment
|
|
279
|
+
|
|
280
|
+
### Wrangler Configuration
|
|
281
|
+
```toml
|
|
282
|
+
# wrangler.toml
|
|
283
|
+
name = "my-app"
|
|
284
|
+
main = "src/index.ts"
|
|
285
|
+
compatibility_date = "2024-12-01"
|
|
286
|
+
|
|
287
|
+
# D1 Database
|
|
288
|
+
[[ d1_databases ]]
|
|
289
|
+
binding = "DB"
|
|
290
|
+
database_name = "production-db"
|
|
291
|
+
database_id = "YOUR_D1_DATABASE_ID"
|
|
292
|
+
|
|
293
|
+
# KV Storage (sessions, cache)
|
|
294
|
+
[[ kv_namespaces ]]
|
|
295
|
+
binding = "KV"
|
|
296
|
+
id = "YOUR_KV_ID"
|
|
297
|
+
|
|
298
|
+
# R2 Storage (file uploads)
|
|
299
|
+
[[ r2_buckets ]]
|
|
300
|
+
binding = "UPLOADS"
|
|
301
|
+
bucket_name = "uploads"
|
|
302
|
+
|
|
303
|
+
# Environment Variables
|
|
304
|
+
[vars]
|
|
305
|
+
ENVIRONMENT = "production"
|
|
306
|
+
|
|
307
|
+
# Secrets (set via: wrangler secret put RESEND_API_KEY)
|
|
308
|
+
# RESEND_API_KEY
|
|
309
|
+
# DATABASE_URL
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### GitHub Actions CI/CD
|
|
313
|
+
```yaml
|
|
314
|
+
# .github/workflows/deploy.yml
|
|
315
|
+
name: Deploy to Cloudflare
|
|
316
|
+
|
|
317
|
+
on:
|
|
318
|
+
push:
|
|
319
|
+
branches: [main]
|
|
320
|
+
|
|
321
|
+
jobs:
|
|
322
|
+
deploy:
|
|
323
|
+
runs-on: ubuntu-latest
|
|
324
|
+
steps:
|
|
325
|
+
- uses: actions/checkout@v4
|
|
326
|
+
|
|
327
|
+
- uses: actions/setup-node@v4
|
|
328
|
+
with:
|
|
329
|
+
node-version: '20'
|
|
330
|
+
|
|
331
|
+
- name: Install dependencies
|
|
332
|
+
run: npm ci
|
|
333
|
+
|
|
334
|
+
- name: Build
|
|
335
|
+
run: npm run build
|
|
336
|
+
|
|
337
|
+
- name: Run migrations
|
|
338
|
+
run: npx wrangler d1 migrations apply production-db --remote
|
|
339
|
+
env:
|
|
340
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
341
|
+
|
|
342
|
+
- name: Deploy
|
|
343
|
+
run: npx wrangler deploy
|
|
344
|
+
env:
|
|
345
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Deployment Scripts
|
|
349
|
+
```json
|
|
350
|
+
{
|
|
351
|
+
"scripts": {
|
|
352
|
+
"dev": "wrangler dev",
|
|
353
|
+
"build": "vinxi build",
|
|
354
|
+
"deploy": "npm run build && wrangler deploy",
|
|
355
|
+
"deploy:preview": "npm run build && wrangler deploy --env preview",
|
|
356
|
+
"db:migrate": "wrangler d1 migrations apply production-db --remote",
|
|
357
|
+
"db:migrate:local": "wrangler d1 migrations apply production-db --local"
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## 4. Code Quality & Patterns Library
|
|
363
|
+
|
|
364
|
+
### ESLint Configuration (Strict)
|
|
365
|
+
```javascript
|
|
366
|
+
// eslint.config.js
|
|
367
|
+
export default {
|
|
368
|
+
extends: [
|
|
369
|
+
'eslint:recommended',
|
|
370
|
+
'plugin:@typescript-eslint/strict',
|
|
371
|
+
'plugin:react/recommended',
|
|
372
|
+
'plugin:react-hooks/recommended',
|
|
373
|
+
],
|
|
374
|
+
rules: {
|
|
375
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
376
|
+
'@typescript-eslint/no-unused-vars': 'error',
|
|
377
|
+
'react/prop-types': 'off', // Using TypeScript
|
|
378
|
+
'react/react-in-jsx-scope': 'off', // React 19
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### TypeScript Strict Mode
|
|
384
|
+
```json
|
|
385
|
+
{
|
|
386
|
+
"compilerOptions": {
|
|
387
|
+
"strict": true,
|
|
388
|
+
"noUncheckedIndexedAccess": true,
|
|
389
|
+
"noImplicitOverride": true,
|
|
390
|
+
"exactOptionalPropertyTypes": true,
|
|
391
|
+
"noFallthroughCasesInSwitch": true,
|
|
392
|
+
"noImplicitReturns": true,
|
|
393
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
394
|
+
"noUnusedLocals": true,
|
|
395
|
+
"noUnusedParameters": true
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Pattern: Compound Components
|
|
401
|
+
```typescript
|
|
402
|
+
// ✅ GOOD: Compound component pattern
|
|
403
|
+
import { createContext, useContext } from 'react';
|
|
404
|
+
|
|
405
|
+
interface AccordionContextValue {
|
|
406
|
+
openItem: string | null;
|
|
407
|
+
setOpenItem: (id: string | null) => void;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const AccordionContext = createContext<AccordionContextValue | null>(null);
|
|
411
|
+
|
|
412
|
+
export function Accordion({ children }: { children: React.ReactNode }) {
|
|
413
|
+
const [openItem, setOpenItem] = useState<string | null>(null);
|
|
414
|
+
|
|
415
|
+
return (
|
|
416
|
+
<AccordionContext.Provider value={{ openItem, setOpenItem }}>
|
|
417
|
+
<div className="accordion">{children}</div>
|
|
418
|
+
</AccordionContext.Provider>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
Accordion.Item = function AccordionItem({
|
|
423
|
+
id,
|
|
424
|
+
children
|
|
425
|
+
}: {
|
|
426
|
+
id: string;
|
|
427
|
+
children: React.ReactNode
|
|
428
|
+
}) {
|
|
429
|
+
return <div data-item-id={id}>{children}</div>;
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
Accordion.Trigger = function AccordionTrigger({
|
|
433
|
+
id,
|
|
434
|
+
children
|
|
435
|
+
}: {
|
|
436
|
+
id: string;
|
|
437
|
+
children: React.ReactNode
|
|
438
|
+
}) {
|
|
439
|
+
const context = useContext(AccordionContext);
|
|
440
|
+
if (!context) throw new Error('Trigger must be used within Accordion');
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<button onClick={() => context.setOpenItem(context.openItem === id ? null : id)}>
|
|
444
|
+
{children}
|
|
445
|
+
</button>
|
|
446
|
+
);
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
Accordion.Content = function AccordionContent({
|
|
450
|
+
id,
|
|
451
|
+
children
|
|
452
|
+
}: {
|
|
453
|
+
id: string;
|
|
454
|
+
children: React.ReactNode
|
|
455
|
+
}) {
|
|
456
|
+
const context = useContext(AccordionContext);
|
|
457
|
+
if (!context) throw new Error('Content must be used within Accordion');
|
|
458
|
+
|
|
459
|
+
if (context.openItem !== id) return null;
|
|
460
|
+
return <div>{children}</div>;
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// Usage
|
|
464
|
+
<Accordion>
|
|
465
|
+
<Accordion.Item id="1">
|
|
466
|
+
<Accordion.Trigger id="1">Section 1</Accordion.Trigger>
|
|
467
|
+
<Accordion.Content id="1">Content 1</Accordion.Content>
|
|
468
|
+
</Accordion.Item>
|
|
469
|
+
</Accordion>
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Anti-Pattern: AI Slop
|
|
473
|
+
```typescript
|
|
474
|
+
// ❌ BAD: AI-generated slop with unnecessary complexity
|
|
475
|
+
export const createUserWithValidationAndErrorHandlingAndLogging = async (
|
|
476
|
+
userData: Partial<User> | undefined | null,
|
|
477
|
+
options?: {
|
|
478
|
+
validate?: boolean;
|
|
479
|
+
log?: boolean;
|
|
480
|
+
retry?: number;
|
|
481
|
+
timeout?: number;
|
|
482
|
+
fallback?: User;
|
|
483
|
+
}
|
|
484
|
+
): Promise<User | null | undefined> => {
|
|
485
|
+
try {
|
|
486
|
+
if (!userData) {
|
|
487
|
+
console.log('No user data provided');
|
|
488
|
+
return options?.fallback ?? null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (options?.validate !== false) {
|
|
492
|
+
// Over-engineered validation
|
|
493
|
+
const isValid = validateUserData(userData);
|
|
494
|
+
if (!isValid) {
|
|
495
|
+
throw new Error('Validation failed');
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Unnecessary retry logic for simple operation
|
|
500
|
+
let attempts = 0;
|
|
501
|
+
while (attempts < (options?.retry ?? 3)) {
|
|
502
|
+
try {
|
|
503
|
+
const user = await db.insert(users).values(userData);
|
|
504
|
+
if (options?.log !== false) {
|
|
505
|
+
console.log('User created:', user.id);
|
|
506
|
+
}
|
|
507
|
+
return user;
|
|
508
|
+
} catch (err) {
|
|
509
|
+
attempts++;
|
|
510
|
+
if (attempts >= (options?.retry ?? 3)) throw err;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
} catch (error) {
|
|
514
|
+
console.error('Error creating user:', error);
|
|
515
|
+
return options?.fallback ?? null;
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
// ✅ GOOD: Clean, maintainable code
|
|
520
|
+
export async function createUser(data: CreateUserInput): Promise<User> {
|
|
521
|
+
const user = await db.insert(users).values(data).returning();
|
|
522
|
+
return user[0];
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
## 5. Complete Example Applications
|
|
527
|
+
|
|
528
|
+
### Blog Platform
|
|
529
|
+
```typescript
|
|
530
|
+
// Entity DSL
|
|
531
|
+
const blogEntities = `
|
|
532
|
+
User(
|
|
533
|
+
email:email:unique:required,
|
|
534
|
+
username:string:unique:required:length(3,20),
|
|
535
|
+
password:string:hashed:required,
|
|
536
|
+
bio:string:length(10,500):nullable,
|
|
537
|
+
avatar:url:nullable
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
Post(
|
|
541
|
+
title:string:required:length(3,200),
|
|
542
|
+
slug:string:unique:required:index,
|
|
543
|
+
content:string:required,
|
|
544
|
+
excerpt:string:length(10,500):nullable,
|
|
545
|
+
author:refOne(User),
|
|
546
|
+
category:refOne(Category),
|
|
547
|
+
tags:refMany(Tag),
|
|
548
|
+
status:enum(draft,published,archived):default(draft),
|
|
549
|
+
publishedAt:datetime:nullable,
|
|
550
|
+
viewCount:integer:default(0)
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
Category(
|
|
554
|
+
name:string:unique:required,
|
|
555
|
+
slug:string:unique:required:index,
|
|
556
|
+
parent:ref(Category)
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
Tag(
|
|
560
|
+
name:string:unique:required,
|
|
561
|
+
slug:string:unique:required:index
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
Comment(
|
|
565
|
+
content:string:required:length(1,1000),
|
|
566
|
+
author:refOne(User),
|
|
567
|
+
post:refOne(Post),
|
|
568
|
+
replyTo:ref(Comment),
|
|
569
|
+
status:enum(pending,approved,spam):default(pending)
|
|
570
|
+
)
|
|
571
|
+
`;
|
|
572
|
+
|
|
573
|
+
// Generate: 5 entities × 15 files = 75 files
|
|
574
|
+
// Token savings: 5 × 11,300 = 56,500 tokens (~$0.85 saved)
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### E-Commerce Store
|
|
578
|
+
```typescript
|
|
579
|
+
const ecommerceEntities = `
|
|
580
|
+
Product(
|
|
581
|
+
name:string:required,
|
|
582
|
+
sku:string:unique:required:pattern(/^[A-Z]{3}-\\d{6}$/),
|
|
583
|
+
price:decimal:required:min(0),
|
|
584
|
+
compareAtPrice:decimal:min(0):nullable,
|
|
585
|
+
stock:integer:min(0):default(0),
|
|
586
|
+
category:refOne(Category),
|
|
587
|
+
images:array(string),
|
|
588
|
+
metadata:json:nullable,
|
|
589
|
+
status:enum(active,inactive,discontinued):default(active)
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
Order(
|
|
593
|
+
orderNumber:string:unique:required:immutable,
|
|
594
|
+
customer:refOne(Customer),
|
|
595
|
+
items:refMany(Product),
|
|
596
|
+
subtotal:decimal:required:min(0),
|
|
597
|
+
tax:decimal:required:min(0),
|
|
598
|
+
shipping:decimal:required:min(0),
|
|
599
|
+
total:computed(subtotal + tax + shipping),
|
|
600
|
+
status:enum(pending,paid,processing,shipped,delivered):default(pending)
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
Customer(
|
|
604
|
+
email:email:unique:required,
|
|
605
|
+
firstName:string:required,
|
|
606
|
+
lastName:string:required,
|
|
607
|
+
phone:string:pattern(/^\\+1\\d{10}$/):nullable,
|
|
608
|
+
addresses:json:nullable
|
|
609
|
+
)
|
|
610
|
+
`;
|
|
611
|
+
|
|
612
|
+
// Generate: 3 entities × 15 files = 45 files
|
|
613
|
+
// Includes: Stripe integration, order tracking emails, admin dashboard
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### SaaS Dashboard
|
|
617
|
+
```typescript
|
|
618
|
+
const saasEntities = `
|
|
619
|
+
Organization(
|
|
620
|
+
name:string:required:unique,
|
|
621
|
+
slug:string:unique:required:pattern(/^[a-z0-9-]+$/),
|
|
622
|
+
plan:enum(free,starter,pro,enterprise):default(free),
|
|
623
|
+
settings:json:nullable
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
User(
|
|
627
|
+
email:email:unique:required,
|
|
628
|
+
password:string:hashed:required,
|
|
629
|
+
organization:refOne(Organization),
|
|
630
|
+
role:enum(owner,admin,member,guest):default(member),
|
|
631
|
+
permissions:array(string)
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
Project(
|
|
635
|
+
name:string:required,
|
|
636
|
+
organization:refOne(Organization),
|
|
637
|
+
owner:refOne(User),
|
|
638
|
+
members:refMany(User),
|
|
639
|
+
status:enum(active,archived):default(active)
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
Task(
|
|
643
|
+
title:string:required,
|
|
644
|
+
project:refOne(Project),
|
|
645
|
+
assignee:refOne(User),
|
|
646
|
+
status:enum(todo,in_progress,review,done):default(todo),
|
|
647
|
+
priority:enum(low,medium,high,critical):default(medium),
|
|
648
|
+
dueDate:datetime:nullable
|
|
649
|
+
)
|
|
650
|
+
`;
|
|
651
|
+
|
|
652
|
+
// Generate: 4 entities × 15 files = 60 files
|
|
653
|
+
// Includes: Team management, billing, analytics, notifications
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
## Token Savings Summary
|
|
657
|
+
|
|
658
|
+
| Feature | Traditional | CodeDNA | Savings |
|
|
659
|
+
|---------|------------|---------|---------|
|
|
660
|
+
| Full-stack app (1 entity) | ~11,500 | ~200 | 98% |
|
|
661
|
+
| Components (1 entity) | ~2,500 | ~50 | 98% |
|
|
662
|
+
| Email templates (3) | ~1,500 | ~75 | 95% |
|
|
663
|
+
| Deployment config | ~800 | ~25 | 97% |
|
|
664
|
+
| **Blog (5 entities)** | **~80,000** | **~1,500** | **98%** |
|
|
665
|
+
| **E-commerce (3 entities)** | **~48,000** | **~900** | **98%** |
|
|
666
|
+
| **SaaS (4 entities)** | **~64,000** | **~1,200** | **98%** |
|
|
667
|
+
|
|
668
|
+
## Cost Savings (Claude Sonnet)
|
|
669
|
+
|
|
670
|
+
**Blog Platform:**
|
|
671
|
+
- Traditional: ~$1.20
|
|
672
|
+
- CodeDNA: ~$0.02
|
|
673
|
+
- **Saved: $1.18 (98%)**
|
|
674
|
+
|
|
675
|
+
**E-Commerce:**
|
|
676
|
+
- Traditional: ~$0.72
|
|
677
|
+
- CodeDNA: ~$0.01
|
|
678
|
+
- **Saved: $0.71 (98%)**
|
|
679
|
+
|
|
680
|
+
**SaaS Dashboard:**
|
|
681
|
+
- Traditional: ~$0.96
|
|
682
|
+
- CodeDNA: ~$0.02
|
|
683
|
+
- **Saved: $0.94 (98%)**
|
|
684
|
+
|
|
685
|
+
## Best Practices
|
|
686
|
+
|
|
687
|
+
1. **Start with Entity DSL** - Design your data model first
|
|
688
|
+
2. **Use Type Safety** - Enable TypeScript strict mode
|
|
689
|
+
3. **Leverage Relations** - Use refOne/refMany for clean schemas
|
|
690
|
+
4. **Add Validation** - Use pattern, length, min/max constraints
|
|
691
|
+
5. **Deploy to Edge** - Cloudflare for global performance
|
|
692
|
+
6. **Monitor Errors** - Use error boundaries and logging
|
|
693
|
+
7. **Optimize Queries** - Use Tanstack Query caching
|
|
694
|
+
8. **Email Responsively** - Test emails in all clients
|
|
695
|
+
|
|
696
|
+
## Next Steps
|
|
697
|
+
|
|
698
|
+
1. Generate your first entity
|
|
699
|
+
2. Review generated code
|
|
700
|
+
3. Customize as needed
|
|
701
|
+
4. Deploy to Cloudflare
|
|
702
|
+
5. Monitor and iterate
|
|
703
|
+
|
|
704
|
+
---
|
|
705
|
+
|
|
706
|
+
**CodeDNA Modern Stack: Build production apps in minutes, not weeks.**
|