ebade 0.1.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/CHANGELOG.md +36 -0
- package/CONTRIBUTING.md +177 -0
- package/LICENSE +21 -0
- package/MANIFESTO.md +170 -0
- package/README.md +263 -0
- package/ROADMAP.md +119 -0
- package/SYNTAX.md +515 -0
- package/benchmarks/RESULTS.md +119 -0
- package/benchmarks/token-benchmark.js +197 -0
- package/cli/scaffold.js +706 -0
- package/docs/GREEN-AI.md +86 -0
- package/examples/ecommerce.ebade.yaml +192 -0
- package/landing/favicon.svg +6 -0
- package/landing/index.html +227 -0
- package/landing/main.js +147 -0
- package/landing/og-image.png +0 -0
- package/landing/style.css +616 -0
- package/package.json +43 -0
- package/packages/mcp-server/README.md +144 -0
- package/packages/mcp-server/package-lock.json +1178 -0
- package/packages/mcp-server/package.json +32 -0
- package/packages/mcp-server/src/index.ts +316 -0
- package/packages/mcp-server/src/tools/compile.ts +269 -0
- package/packages/mcp-server/src/tools/generate.ts +420 -0
- package/packages/mcp-server/src/tools/scaffold.ts +474 -0
- package/packages/mcp-server/src/tools/validate.ts +233 -0
- package/packages/mcp-server/tsconfig.json +16 -0
- package/schema/project.schema.json +195 -0
package/ROADMAP.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# ebade Roadmap πΊοΈ
|
|
2
|
+
|
|
3
|
+
> Building the first framework designed FOR AI agents - The Essence of Code.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## π― Vision
|
|
8
|
+
|
|
9
|
+
**ebade** becomes the native language for AI agents to build web applications.
|
|
10
|
+
When a user asks an AI to "build me an e-commerce site", the AI speaks ebade.
|
|
11
|
+
It's more efficient, deterministic, and KIND TO EARTH. π±
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Phase 1: Foundation (Week 1-2) β
IN PROGRESS
|
|
16
|
+
|
|
17
|
+
### 1.1 MCP Server
|
|
18
|
+
|
|
19
|
+
Make ebade usable by AI agents RIGHT NOW.
|
|
20
|
+
|
|
21
|
+
- [x] Create MCP server package
|
|
22
|
+
- [x] `ebade_scaffold` tool - generate project from ebade
|
|
23
|
+
- [x] `ebade_validate` tool - validate ebade file
|
|
24
|
+
- [x] `ebade_compile` tool - compile single ebade to code
|
|
25
|
+
- [x] `ebade_generate` tool - infer ebade from natural language
|
|
26
|
+
|
|
27
|
+
### 1.2 Real Compiler
|
|
28
|
+
|
|
29
|
+
Replace string templates with actual AST-based compilation.
|
|
30
|
+
|
|
31
|
+
- [ ] ebade parser (YAML/TS β AST)
|
|
32
|
+
- [ ] Code generator (AST β React/Next.js)
|
|
33
|
+
- [ ] Support for multiple framework targets
|
|
34
|
+
|
|
35
|
+
### 1.3 CLI Improvements
|
|
36
|
+
|
|
37
|
+
- [ ] `ebade init` - interactive project setup
|
|
38
|
+
- [ ] `ebade dev` - watch mode with local agent sync
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Phase 2: Developer Experience (Week 3-4)
|
|
43
|
+
|
|
44
|
+
### 2.1 VS Code Extension
|
|
45
|
+
|
|
46
|
+
- [ ] Syntax highlighting for ebade decorators
|
|
47
|
+
- [ ] Autocomplete for inferred patterns
|
|
48
|
+
- [ ] Inline token usage counter (Carbon counter! π±)
|
|
49
|
+
|
|
50
|
+
### 2.2 Playground Website
|
|
51
|
+
|
|
52
|
+
- [ ] Live editor (Monaco)
|
|
53
|
+
- [ ] Real-time compilation preview
|
|
54
|
+
- [ ] Share ebade via URL
|
|
55
|
+
|
|
56
|
+
### 2.3 Documentation Site
|
|
57
|
+
|
|
58
|
+
- [ ] "The Turkish Story" - The meaning of ebade
|
|
59
|
+
- [ ] Getting started for Agents
|
|
60
|
+
- [ ] Getting started for Humans
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Phase 3: Ecosystem (Month 2)
|
|
65
|
+
|
|
66
|
+
### 3.1 ebade Registry
|
|
67
|
+
|
|
68
|
+
- [ ] npm-like registry for reusable intents
|
|
69
|
+
- [ ] `ebade add auth/clerk`
|
|
70
|
+
- [ ] `ebade add blog/minimal`
|
|
71
|
+
|
|
72
|
+
### 3.2 Framework Targets
|
|
73
|
+
|
|
74
|
+
- [ ] Next.js App Router β
(v0.1)
|
|
75
|
+
- [ ] Vue 3 + Nuxt
|
|
76
|
+
- [ ] Svelte + SvelteKit
|
|
77
|
+
- [ ] Mobile (React Native / Flutter)
|
|
78
|
+
|
|
79
|
+
### 3.3 The "Badik" Mascot π£
|
|
80
|
+
|
|
81
|
+
- [ ] Community names
|
|
82
|
+
- [ ] Logo design
|
|
83
|
+
- [ ] Swag / Stickers
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Phase 4: AI Native Features (Month 3)
|
|
88
|
+
|
|
89
|
+
### 4.1 Green AI Optimization
|
|
90
|
+
|
|
91
|
+
- [ ] Auto-optimize ebade definitions for minimum token count.
|
|
92
|
+
- [ ] Carbon offset calculations per line of code.
|
|
93
|
+
|
|
94
|
+
### 4.2 Multi-Agent Orchestration
|
|
95
|
+
|
|
96
|
+
- [ ] System for multiple agents to collaborate on a single ebade file synchronously.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Success Metrics (3 Months)
|
|
101
|
+
|
|
102
|
+
| Metric | Target | Mission |
|
|
103
|
+
| :--- | :--- | :--- |
|
|
104
|
+
| Token Savings | >70% | Efficiency |
|
|
105
|
+
| Carbon Saved | >100kg | Sustainability |
|
|
106
|
+
| Framework Targets | 3 | Accessibility |
|
|
107
|
+
| Community Size | 1,000 "Badiks" | Impact |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Current Status
|
|
112
|
+
|
|
113
|
+
**Phase 1.1 - MCP Server** β WE ARE HERE
|
|
114
|
+
|
|
115
|
+
The MCP server is live. AI agents can now use ebade to scaffold full Next.js apps with 65% fewer tokens.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
*Last updated: 2026-01-07*
|
package/SYNTAX.md
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
# ebade Syntax Specification π
|
|
2
|
+
|
|
3
|
+
> **Code is a function of ebade.**
|
|
4
|
+
> `Code = f(ebade)`
|
|
5
|
+
|
|
6
|
+
This document defines the syntax and decorators used in ebade to define application intents.
|
|
7
|
+
|
|
8
|
+
## Core Principle: ebb-and-flow of intent
|
|
9
|
+
|
|
10
|
+
In ebade, you don't write implementation details. You define the **intent** of a component, page, or API. The ebade compiler then transforms this intent into framework-specific code (e.g., Next.js, Vue, Svelte).
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## File Extensions
|
|
15
|
+
|
|
16
|
+
- `.intent.js` / `.intent.ts` β ebade definitions
|
|
17
|
+
- `.intent.yaml` β Declarative ebade configs
|
|
18
|
+
- `ebade.config.js` β Project-level ebade configuration
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## ποΈ Project Intent (`project.intent.yaml`)
|
|
23
|
+
|
|
24
|
+
The project-level ebade defines the overall structure and features of the application.
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
name: my-app
|
|
28
|
+
type: saas-dashboard | e-commerce | blog | landing-page | portfolio
|
|
29
|
+
features:
|
|
30
|
+
- user-auth
|
|
31
|
+
- payments
|
|
32
|
+
- analytics
|
|
33
|
+
- search
|
|
34
|
+
- database
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Decorators
|
|
40
|
+
|
|
41
|
+
### @page(path)
|
|
42
|
+
|
|
43
|
+
Defines a page/route.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
@page('/products')
|
|
47
|
+
@page('/products/[id]')
|
|
48
|
+
@page('/users/[userId]/orders/[orderId]')
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### @intent(name)
|
|
52
|
+
|
|
53
|
+
Declares the purpose of this component/page.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
@intent('product-listing')
|
|
57
|
+
@intent('user-authentication')
|
|
58
|
+
@intent('checkout-flow')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### @requires(dependencies)
|
|
62
|
+
|
|
63
|
+
Specifies what this ebade needs to function.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
@requires({
|
|
67
|
+
data: ['products', 'categories'], // Data dependencies
|
|
68
|
+
auth: 'required' | 'optional' | 'none',
|
|
69
|
+
permissions: ['admin', 'editor'],
|
|
70
|
+
features: ['dark-mode', 'analytics']
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### @outcomes(results)
|
|
75
|
+
|
|
76
|
+
Defines possible outcomes and their handlers.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
@outcomes({
|
|
80
|
+
success: '/success-page',
|
|
81
|
+
success: { redirect: '/dashboard', toast: 'Welcome!' },
|
|
82
|
+
error: { show: 'inline', retry: true },
|
|
83
|
+
loading: { skeleton: true },
|
|
84
|
+
empty: { message: 'No items found', action: 'create-first' }
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### @data(fetcher)
|
|
89
|
+
|
|
90
|
+
Defines how to fetch data for this ebade.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
@data(async ({ params, query }) => {
|
|
94
|
+
return db.products.findMany({
|
|
95
|
+
where: { category: params.category },
|
|
96
|
+
limit: query.limit || 20
|
|
97
|
+
});
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### @validate(rules)
|
|
102
|
+
|
|
103
|
+
Input validation rules.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
@validate({
|
|
107
|
+
email: ['required', 'email'],
|
|
108
|
+
password: ['required', 'min:8', 'has-uppercase', 'has-number'],
|
|
109
|
+
age: ['required', 'number', 'min:18']
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### @style(design)
|
|
114
|
+
|
|
115
|
+
Visual styling from design system.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
@style('card/elevated')
|
|
119
|
+
@style({ variant: 'primary', size: 'lg', rounded: true })
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### @compose(intents)
|
|
123
|
+
|
|
124
|
+
Combines multiple ebade definitions.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
@compose(['header', 'sidebar', 'main-content', 'footer'])
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### @on(event, handler)
|
|
131
|
+
|
|
132
|
+
Event handlers.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
@on('submit', async (data) => createOrder(data))
|
|
136
|
+
@on('error', (e) => logError(e))
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### @expects(scenarios)
|
|
140
|
+
|
|
141
|
+
Defines expected behaviors for testing. **Tests become part of the ebade.**
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
@expects([
|
|
145
|
+
{
|
|
146
|
+
scenario: 'happy-path',
|
|
147
|
+
given: { productId: 1, quantity: 2 },
|
|
148
|
+
when: 'add-to-cart',
|
|
149
|
+
then: 'cart-updated',
|
|
150
|
+
assert: { cartCount: 2 }
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
scenario: 'out-of-stock',
|
|
154
|
+
given: { productId: 1, stock: 0 },
|
|
155
|
+
when: 'add-to-cart',
|
|
156
|
+
then: 'error',
|
|
157
|
+
error: 'Product is out of stock'
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
scenario: 'invalid-quantity',
|
|
161
|
+
given: { productId: 1, quantity: -1 },
|
|
162
|
+
when: 'add-to-cart',
|
|
163
|
+
then: 'error',
|
|
164
|
+
error: 'Quantity must be positive'
|
|
165
|
+
}
|
|
166
|
+
])
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
This automatically generates test files during compilation:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
// Generated: add-to-cart.test.ts
|
|
173
|
+
|
|
174
|
+
describe('add-to-cart', () => {
|
|
175
|
+
it('happy-path: should update cart', async () => {
|
|
176
|
+
const result = await addToCart({ productId: 1, quantity: 2 });
|
|
177
|
+
expect(result.outcome).toBe('cart-updated');
|
|
178
|
+
expect(result.cartCount).toBe(2);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('out-of-stock: should show error', async () => {
|
|
182
|
+
const result = await addToCart({ productId: 1, stock: 0 });
|
|
183
|
+
expect(result.outcome).toBe('error');
|
|
184
|
+
expect(result.error).toBe('Product is out of stock');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('invalid-quantity: should reject negative', async () => {
|
|
188
|
+
const result = await addToCart({ productId: 1, quantity: -1 });
|
|
189
|
+
expect(result.outcome).toBe('error');
|
|
190
|
+
expect(result.error).toBe('Quantity must be positive');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### Full Example with @expects
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// auth/login.intent.ts
|
|
199
|
+
|
|
200
|
+
@intent('user-login')
|
|
201
|
+
@inputs({
|
|
202
|
+
email: { type: 'email', required: true },
|
|
203
|
+
password: { type: 'password', required: true }
|
|
204
|
+
})
|
|
205
|
+
@validate({
|
|
206
|
+
email: ['required', 'email'],
|
|
207
|
+
password: ['required', 'min:8']
|
|
208
|
+
})
|
|
209
|
+
@expects([
|
|
210
|
+
{
|
|
211
|
+
scenario: 'valid-credentials',
|
|
212
|
+
given: { email: 'user@test.com', password: 'ValidPass123' },
|
|
213
|
+
mocks: { db: { user: { id: 1, verified: true } } },
|
|
214
|
+
then: 'success',
|
|
215
|
+
assert: { redirect: '/dashboard' }
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
scenario: 'invalid-password',
|
|
219
|
+
given: { email: 'user@test.com', password: 'wrong' },
|
|
220
|
+
mocks: { db: { user: null } },
|
|
221
|
+
then: 'error',
|
|
222
|
+
error: 'Invalid email or password'
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
scenario: 'unverified-email',
|
|
226
|
+
given: { email: 'new@test.com', password: 'ValidPass123' },
|
|
227
|
+
mocks: { db: { user: { id: 2, verified: false } } },
|
|
228
|
+
then: 'redirect',
|
|
229
|
+
assert: { redirect: '/verify-email' }
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
scenario: 'rate-limited',
|
|
233
|
+
given: { email: 'user@test.com', password: 'wrong' },
|
|
234
|
+
context: { failedAttempts: 5 },
|
|
235
|
+
then: 'error',
|
|
236
|
+
error: 'Too many attempts. Try again later.'
|
|
237
|
+
}
|
|
238
|
+
])
|
|
239
|
+
@outcomes({
|
|
240
|
+
success: { redirect: '/dashboard', toast: 'Welcome back!' },
|
|
241
|
+
error: { show: 'inline' },
|
|
242
|
+
redirect: { to: 'context.redirect' }
|
|
243
|
+
})
|
|
244
|
+
export function LoginForm({ onSubmit }) {
|
|
245
|
+
// Business logic only
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### Why Tests in ebade?
|
|
250
|
+
|
|
251
|
+
| Traditional | ebade |
|
|
252
|
+
|-------------|----------|
|
|
253
|
+
| Write code, then write tests | Define expectations upfront |
|
|
254
|
+
| Tests in separate files | Tests in same definition |
|
|
255
|
+
| Easy to forget edge cases | Edge cases are first-class |
|
|
256
|
+
| Tests as afterthought | Tests as specification |
|
|
257
|
+
|
|
258
|
+
> **ebade-Driven Testing**: You're not testing code, you're testing ebade.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Component Definition
|
|
263
|
+
|
|
264
|
+
### Basic Component
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// product-card.intent.ts
|
|
268
|
+
|
|
269
|
+
@intent('display-product')
|
|
270
|
+
@displays(['image', 'title', 'price', 'rating'])
|
|
271
|
+
@actions(['add-to-cart', 'add-to-wishlist', 'quick-view'])
|
|
272
|
+
@style('e-commerce/product-card')
|
|
273
|
+
export function ProductCard({ product }) {
|
|
274
|
+
// Only business logic, no boilerplate
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Page Definition
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
// checkout.intent.ts
|
|
282
|
+
|
|
283
|
+
@page('/checkout')
|
|
284
|
+
@intent('complete-purchase')
|
|
285
|
+
@requires({
|
|
286
|
+
data: ['cart', 'user'],
|
|
287
|
+
auth: 'required'
|
|
288
|
+
})
|
|
289
|
+
@outcomes({
|
|
290
|
+
success: '/orders/[orderId]',
|
|
291
|
+
paymentFailed: { show: 'error', retry: true },
|
|
292
|
+
cartEmpty: { redirect: '/products', message: 'Your cart is empty' }
|
|
293
|
+
})
|
|
294
|
+
@compose([
|
|
295
|
+
'order-summary',
|
|
296
|
+
'shipping-form',
|
|
297
|
+
'payment-form',
|
|
298
|
+
'place-order-button'
|
|
299
|
+
])
|
|
300
|
+
export function CheckoutPage({ cart, user }) {
|
|
301
|
+
return <CheckoutFlow cart={cart} user={user} />;
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Form Definition
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// contact-form.intent.ts
|
|
309
|
+
|
|
310
|
+
@intent('collect-contact-info')
|
|
311
|
+
@fields({
|
|
312
|
+
name: { type: 'text', required: true, label: 'Full Name' },
|
|
313
|
+
email: { type: 'email', required: true },
|
|
314
|
+
message: { type: 'textarea', required: true, rows: 5 },
|
|
315
|
+
category: {
|
|
316
|
+
type: 'select',
|
|
317
|
+
options: ['support', 'sales', 'feedback'],
|
|
318
|
+
default: 'support'
|
|
319
|
+
}
|
|
320
|
+
})
|
|
321
|
+
@validate({
|
|
322
|
+
name: ['required', 'min:2'],
|
|
323
|
+
email: ['required', 'email'],
|
|
324
|
+
message: ['required', 'min:10']
|
|
325
|
+
})
|
|
326
|
+
@on('submit', async (data) => sendContactForm(data))
|
|
327
|
+
@outcomes({
|
|
328
|
+
success: { show: 'toast', message: 'Message sent!' },
|
|
329
|
+
error: { show: 'inline' }
|
|
330
|
+
})
|
|
331
|
+
export function ContactForm() {}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Data Intents
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// data/products.intent.ts
|
|
340
|
+
|
|
341
|
+
@dataSource('products')
|
|
342
|
+
@provider('supabase')
|
|
343
|
+
@table('products')
|
|
344
|
+
@schema({
|
|
345
|
+
id: 'uuid',
|
|
346
|
+
name: 'string',
|
|
347
|
+
price: 'decimal',
|
|
348
|
+
category: 'string',
|
|
349
|
+
stock: 'integer',
|
|
350
|
+
images: 'array<string>',
|
|
351
|
+
createdAt: 'timestamp'
|
|
352
|
+
})
|
|
353
|
+
@queries({
|
|
354
|
+
list: { orderBy: 'createdAt', limit: 20 },
|
|
355
|
+
byCategory: (category) => ({ where: { category } }),
|
|
356
|
+
search: (term) => ({ where: { name: { contains: term } } })
|
|
357
|
+
})
|
|
358
|
+
export const ProductsData = defineData();
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## API Intents
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// api/orders.intent.ts
|
|
367
|
+
|
|
368
|
+
@api('/api/orders')
|
|
369
|
+
@intent('order-management')
|
|
370
|
+
@auth('required')
|
|
371
|
+
|
|
372
|
+
@get('/')
|
|
373
|
+
@returns('Order[]')
|
|
374
|
+
async function listOrders({ user }) {
|
|
375
|
+
return db.orders.findMany({ where: { userId: user.id } });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@post('/')
|
|
379
|
+
@validate({ items: 'required', shippingAddress: 'required' })
|
|
380
|
+
@returns('Order')
|
|
381
|
+
async function createOrder({ body, user }) {
|
|
382
|
+
return db.orders.create({ ...body, userId: user.id });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
@get('/[id]')
|
|
386
|
+
@returns('Order')
|
|
387
|
+
async function getOrder({ params, user }) {
|
|
388
|
+
return db.orders.findOne({ id: params.id, userId: user.id });
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Layout Intents
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
// layouts/main.intent.ts
|
|
398
|
+
|
|
399
|
+
@layout('main')
|
|
400
|
+
@compose([
|
|
401
|
+
{ slot: 'header', intent: 'navigation-header' },
|
|
402
|
+
{ slot: 'sidebar', intent: 'main-sidebar', show: 'desktop-only' },
|
|
403
|
+
{ slot: 'main', intent: 'page-content' },
|
|
404
|
+
{ slot: 'footer', intent: 'site-footer' }
|
|
405
|
+
])
|
|
406
|
+
@style('layout/dashboard')
|
|
407
|
+
export function MainLayout({ children }) {
|
|
408
|
+
return children;
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Full Example: E-commerce Product Page
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// products/[id].intent.ts
|
|
418
|
+
|
|
419
|
+
@page('/products/[id]')
|
|
420
|
+
@intent('view-product-details')
|
|
421
|
+
@data(async ({ params }) => ({
|
|
422
|
+
product: await db.products.findUnique({ id: params.id }),
|
|
423
|
+
reviews: await db.reviews.findMany({ productId: params.id }),
|
|
424
|
+
related: await db.products.findRelated(params.id, { limit: 4 })
|
|
425
|
+
}))
|
|
426
|
+
@outcomes({
|
|
427
|
+
notFound: '/404',
|
|
428
|
+
success: 'render'
|
|
429
|
+
})
|
|
430
|
+
@seo(({ product }) => ({
|
|
431
|
+
title: product.name,
|
|
432
|
+
description: product.description,
|
|
433
|
+
image: product.images[0]
|
|
434
|
+
}))
|
|
435
|
+
@compose([
|
|
436
|
+
'product-gallery',
|
|
437
|
+
'product-info',
|
|
438
|
+
'add-to-cart-section',
|
|
439
|
+
'product-tabs', // description, specs, reviews
|
|
440
|
+
'related-products'
|
|
441
|
+
])
|
|
442
|
+
export function ProductPage({ product, reviews, related }) {
|
|
443
|
+
return (
|
|
444
|
+
<ProductPageLayout>
|
|
445
|
+
<ProductGallery images={product.images} />
|
|
446
|
+
<ProductInfo product={product} />
|
|
447
|
+
<AddToCart product={product} />
|
|
448
|
+
<ProductTabs description={product.description} reviews={reviews} />
|
|
449
|
+
<RelatedProducts products={related} />
|
|
450
|
+
</ProductPageLayout>
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## Compiler Output
|
|
458
|
+
|
|
459
|
+
The above intent compiles to standard Next.js/React:
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
// Compiled: app/products/[id]/page.tsx
|
|
463
|
+
|
|
464
|
+
import { notFound } from 'next/navigation';
|
|
465
|
+
import { Metadata } from 'next';
|
|
466
|
+
// ... 50+ lines of imports
|
|
467
|
+
|
|
468
|
+
export async function generateMetadata({ params }): Promise<Metadata> {
|
|
469
|
+
const product = await db.products.findUnique({ id: params.id });
|
|
470
|
+
if (!product) return {};
|
|
471
|
+
return {
|
|
472
|
+
title: product.name,
|
|
473
|
+
description: product.description,
|
|
474
|
+
openGraph: { images: [product.images[0]] }
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export default async function ProductPage({ params }) {
|
|
479
|
+
const product = await db.products.findUnique({ id: params.id });
|
|
480
|
+
if (!product) notFound();
|
|
481
|
+
|
|
482
|
+
const reviews = await db.reviews.findMany({ productId: params.id });
|
|
483
|
+
const related = await db.products.findRelated(params.id, { limit: 4 });
|
|
484
|
+
|
|
485
|
+
return (
|
|
486
|
+
<ProductPageLayout>
|
|
487
|
+
{/* ... full implementation */}
|
|
488
|
+
</ProductPageLayout>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Design Tokens (from intent)
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
@style({
|
|
499
|
+
variant: 'primary',
|
|
500
|
+
size: 'lg',
|
|
501
|
+
rounded: true,
|
|
502
|
+
elevation: 'md'
|
|
503
|
+
})
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Resolves to:
|
|
507
|
+
|
|
508
|
+
```css
|
|
509
|
+
.component {
|
|
510
|
+
background-color: var(--color-primary);
|
|
511
|
+
padding: var(--spacing-lg);
|
|
512
|
+
border-radius: var(--radius-lg);
|
|
513
|
+
box-shadow: var(--shadow-md);
|
|
514
|
+
}
|
|
515
|
+
```
|