kata-context 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/.claude/settings.json +6 -0
- package/.github/dependabot.yml +13 -0
- package/.github/workflows/release.yml +68 -0
- package/.planning/PROJECT.md +103 -0
- package/.planning/REQUIREMENTS.md +61 -0
- package/.planning/ROADMAP.md +59 -0
- package/.planning/STATE.md +45 -0
- package/.planning/config.json +20 -0
- package/.planning/phases/01-foundation/01-01-PLAN.md +158 -0
- package/.planning/phases/01-foundation/01-01-SUMMARY.md +123 -0
- package/.planning/phases/01-foundation/01-02-PLAN.md +262 -0
- package/.planning/phases/01-foundation/01-02-SUMMARY.md +153 -0
- package/.planning/phases/01-foundation/01-RESEARCH.md +380 -0
- package/.planning/phases/01-foundation/01-foundation-VERIFICATION.md +232 -0
- package/.planning/research/ARCHITECTURE.md +601 -0
- package/.planning/research/FEATURES.md +242 -0
- package/.planning/research/PITFALLS.md +436 -0
- package/.planning/research/STACK.md +299 -0
- package/.planning/research/SUMMARY.md +409 -0
- package/README.md +82 -0
- package/biome.json +42 -0
- package/package.json +28 -0
- package/scripts/rescue-main.sh +79 -0
- package/src/index.ts +6 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
# Architecture Patterns
|
|
2
|
+
|
|
3
|
+
**Project:** Kata Context - Context Policy Engine
|
|
4
|
+
**Domain:** REST API with TypeScript/Python SDKs
|
|
5
|
+
**Researched:** 2026-01-29
|
|
6
|
+
|
|
7
|
+
## Executive Summary
|
|
8
|
+
|
|
9
|
+
Based on research into Vercel serverless architecture, TypeScript project organization, and multi-language SDK management, I recommend:
|
|
10
|
+
|
|
11
|
+
1. **Monorepo structure** using Turborepo + pnpm workspaces
|
|
12
|
+
2. **Pure serverless API** using Vercel Functions (not Next.js)
|
|
13
|
+
3. **Feature-first organization** within the API package
|
|
14
|
+
4. **Separate SDK packages** co-located in the monorepo
|
|
15
|
+
|
|
16
|
+
**Confidence:** HIGH for Vercel patterns, MEDIUM for SDK organization (based on industry examples)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Recommended Architecture
|
|
21
|
+
|
|
22
|
+
### Decision: Monorepo (Not Polyrepo)
|
|
23
|
+
|
|
24
|
+
**Recommendation:** Monorepo with Turborepo + pnpm workspaces
|
|
25
|
+
|
|
26
|
+
**Rationale:**
|
|
27
|
+
- Single repository enables coordinated releases between API and SDKs
|
|
28
|
+
- Shared TypeScript types between API and TypeScript SDK
|
|
29
|
+
- Unified CI/CD pipeline
|
|
30
|
+
- AI tooling benefits from full context visibility
|
|
31
|
+
- Apache 2.0 licensing applies uniformly
|
|
32
|
+
|
|
33
|
+
**Trade-off accepted:** Python SDK will need its own tooling (Poetry/pip) within the monorepo, but Turborepo can orchestrate Python builds.
|
|
34
|
+
|
|
35
|
+
**Sources:**
|
|
36
|
+
- [Turborepo Repository Structure](https://turborepo.dev/docs/crafting-your-repository/structuring-a-repository)
|
|
37
|
+
- [Monorepo vs Polyrepo Analysis](https://www.aviator.co/blog/monorepo-vs-polyrepo/)
|
|
38
|
+
|
|
39
|
+
### Decision: Pure Vercel Functions (Not Next.js)
|
|
40
|
+
|
|
41
|
+
**Recommendation:** Use Vercel Functions directly with the `/api` directory pattern
|
|
42
|
+
|
|
43
|
+
**Rationale:**
|
|
44
|
+
- Kata Context is a REST API, not a web application
|
|
45
|
+
- No need for React, SSR, or frontend routing
|
|
46
|
+
- Zero-configuration deployment with `/api` directory
|
|
47
|
+
- Lower complexity and smaller bundle sizes
|
|
48
|
+
- Direct access to Vercel's Fluid Compute features
|
|
49
|
+
|
|
50
|
+
**What we avoid:**
|
|
51
|
+
- Next.js App Router vs Pages Router complexity
|
|
52
|
+
- Unnecessary React Server Components
|
|
53
|
+
- Frontend build overhead
|
|
54
|
+
|
|
55
|
+
**Sources:**
|
|
56
|
+
- [Vercel Functions Documentation](https://vercel.com/docs/functions)
|
|
57
|
+
- [Hosting Backend APIs on Vercel](https://vercel.com/kb/guide/hosting-backend-apis)
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Directory Structure
|
|
62
|
+
|
|
63
|
+
### Complete Monorepo Layout
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
kata-context/
|
|
67
|
+
├── .github/
|
|
68
|
+
│ └── workflows/
|
|
69
|
+
│ ├── ci.yml # Unified CI pipeline
|
|
70
|
+
│ ├── release-api.yml # API deployment
|
|
71
|
+
│ ├── release-sdk-ts.yml # npm publish
|
|
72
|
+
│ └── release-sdk-python.yml # PyPI publish
|
|
73
|
+
│
|
|
74
|
+
├── .planning/ # Kata planning artifacts
|
|
75
|
+
│ ├── PROJECT.md
|
|
76
|
+
│ ├── ROADMAP.md
|
|
77
|
+
│ └── research/
|
|
78
|
+
│
|
|
79
|
+
├── apps/
|
|
80
|
+
│ └── api/ # Vercel serverless API
|
|
81
|
+
│ ├── api/ # Vercel Functions (route handlers)
|
|
82
|
+
│ │ ├── v1/
|
|
83
|
+
│ │ │ ├── policies/
|
|
84
|
+
│ │ │ │ ├── index.ts # GET /api/v1/policies
|
|
85
|
+
│ │ │ │ ├── [id].ts # GET/PUT/DELETE /api/v1/policies/:id
|
|
86
|
+
│ │ │ │ └── evaluate.ts # POST /api/v1/policies/evaluate
|
|
87
|
+
│ │ │ ├── contexts/
|
|
88
|
+
│ │ │ │ ├── index.ts
|
|
89
|
+
│ │ │ │ └── [id].ts
|
|
90
|
+
│ │ │ └── health.ts # GET /api/v1/health
|
|
91
|
+
│ │ └── health.ts # GET /api/health (root health check)
|
|
92
|
+
│ │
|
|
93
|
+
│ ├── src/ # Application source code
|
|
94
|
+
│ │ ├── domain/ # Core business logic
|
|
95
|
+
│ │ │ ├── policy/
|
|
96
|
+
│ │ │ │ ├── policy.ts
|
|
97
|
+
│ │ │ │ ├── policy.service.ts
|
|
98
|
+
│ │ │ │ └── policy.repository.ts
|
|
99
|
+
│ │ │ └── context/
|
|
100
|
+
│ │ │ ├── context.ts
|
|
101
|
+
│ │ │ ├── context.service.ts
|
|
102
|
+
│ │ │ └── context.repository.ts
|
|
103
|
+
│ │ │
|
|
104
|
+
│ │ ├── infrastructure/ # External integrations
|
|
105
|
+
│ │ │ ├── database/
|
|
106
|
+
│ │ │ │ └── client.ts
|
|
107
|
+
│ │ │ └── cache/
|
|
108
|
+
│ │ │ └── client.ts
|
|
109
|
+
│ │ │
|
|
110
|
+
│ │ ├── shared/ # Cross-cutting concerns
|
|
111
|
+
│ │ │ ├── errors/
|
|
112
|
+
│ │ │ │ └── api-error.ts
|
|
113
|
+
│ │ │ ├── middleware/
|
|
114
|
+
│ │ │ │ ├── auth.ts
|
|
115
|
+
│ │ │ │ └── validation.ts
|
|
116
|
+
│ │ │ └── utils/
|
|
117
|
+
│ │ │ └── response.ts
|
|
118
|
+
│ │ │
|
|
119
|
+
│ │ └── types/ # TypeScript type definitions
|
|
120
|
+
│ │ └── index.ts
|
|
121
|
+
│ │
|
|
122
|
+
│ ├── package.json
|
|
123
|
+
│ ├── tsconfig.json
|
|
124
|
+
│ └── vercel.ts # Vercel configuration
|
|
125
|
+
│
|
|
126
|
+
├── packages/
|
|
127
|
+
│ ├── typescript-sdk/ # @kata/context TypeScript SDK
|
|
128
|
+
│ │ ├── src/
|
|
129
|
+
│ │ │ ├── index.ts
|
|
130
|
+
│ │ │ ├── client.ts
|
|
131
|
+
│ │ │ ├── types.ts
|
|
132
|
+
│ │ │ └── errors.ts
|
|
133
|
+
│ │ ├── package.json
|
|
134
|
+
│ │ ├── tsconfig.json
|
|
135
|
+
│ │ └── README.md
|
|
136
|
+
│ │
|
|
137
|
+
│ ├── python-sdk/ # kata-context Python SDK
|
|
138
|
+
│ │ ├── kata_context/
|
|
139
|
+
│ │ │ ├── __init__.py
|
|
140
|
+
│ │ │ ├── client.py
|
|
141
|
+
│ │ │ ├── types.py
|
|
142
|
+
│ │ │ └── errors.py
|
|
143
|
+
│ │ ├── tests/
|
|
144
|
+
│ │ ├── pyproject.toml
|
|
145
|
+
│ │ └── README.md
|
|
146
|
+
│ │
|
|
147
|
+
│ └── shared-types/ # Shared TypeScript types (optional)
|
|
148
|
+
│ ├── src/
|
|
149
|
+
│ │ └── index.ts
|
|
150
|
+
│ ├── package.json
|
|
151
|
+
│ └── tsconfig.json
|
|
152
|
+
│
|
|
153
|
+
├── turbo.json # Turborepo configuration
|
|
154
|
+
├── pnpm-workspace.yaml # pnpm workspace definition
|
|
155
|
+
├── package.json # Root package.json
|
|
156
|
+
├── tsconfig.base.json # Shared TypeScript config
|
|
157
|
+
├── .eslintrc.js # Shared ESLint config
|
|
158
|
+
├── .prettierrc # Shared Prettier config
|
|
159
|
+
└── README.md
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Key Structural Decisions
|
|
163
|
+
|
|
164
|
+
#### 1. API Routes in `/api` Directory
|
|
165
|
+
|
|
166
|
+
Vercel automatically creates serverless functions from files in the `/api` directory. Each file becomes an endpoint:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
api/v1/policies/index.ts -> GET/POST /api/v1/policies
|
|
170
|
+
api/v1/policies/[id].ts -> GET/PUT/DELETE /api/v1/policies/:id
|
|
171
|
+
api/v1/policies/evaluate.ts -> POST /api/v1/policies/evaluate
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Why this pattern:**
|
|
175
|
+
- Zero-configuration routing
|
|
176
|
+
- File-based routing is explicit and discoverable
|
|
177
|
+
- Dynamic routes via `[param].ts` syntax
|
|
178
|
+
- Vercel handles function bundling automatically
|
|
179
|
+
|
|
180
|
+
#### 2. Feature-First Domain Organization
|
|
181
|
+
|
|
182
|
+
Within `/src/domain/`, organize by business domain, not by technical layer:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
domain/
|
|
186
|
+
├── policy/ # Everything about policies
|
|
187
|
+
│ ├── policy.ts # Domain entity
|
|
188
|
+
│ ├── policy.service.ts # Business logic
|
|
189
|
+
│ └── policy.repository.ts # Data access
|
|
190
|
+
└── context/ # Everything about contexts
|
|
191
|
+
├── context.ts
|
|
192
|
+
├── context.service.ts
|
|
193
|
+
└── context.repository.ts
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Why this pattern:**
|
|
197
|
+
- Co-locates related code
|
|
198
|
+
- Scales better than layer-first (controllers/, services/, models/)
|
|
199
|
+
- Easier to reason about a single feature
|
|
200
|
+
- Supports future microservice extraction if needed
|
|
201
|
+
|
|
202
|
+
#### 3. SDK Package Naming
|
|
203
|
+
|
|
204
|
+
| Package | npm/PyPI Name | Import |
|
|
205
|
+
|---------|---------------|--------|
|
|
206
|
+
| TypeScript SDK | `@kata/context` | `import { KataContext } from '@kata/context'` |
|
|
207
|
+
| Python SDK | `kata-context` | `from kata_context import KataContext` |
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Configuration Files
|
|
212
|
+
|
|
213
|
+
### Root package.json
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"name": "kata-context",
|
|
218
|
+
"private": true,
|
|
219
|
+
"scripts": {
|
|
220
|
+
"build": "turbo run build",
|
|
221
|
+
"dev": "turbo run dev",
|
|
222
|
+
"lint": "turbo run lint",
|
|
223
|
+
"test": "turbo run test",
|
|
224
|
+
"format": "prettier --write \"**/*.{ts,tsx,md}\""
|
|
225
|
+
},
|
|
226
|
+
"devDependencies": {
|
|
227
|
+
"turbo": "^2.x",
|
|
228
|
+
"prettier": "^3.x",
|
|
229
|
+
"eslint": "^9.x",
|
|
230
|
+
"typescript": "^5.x"
|
|
231
|
+
},
|
|
232
|
+
"packageManager": "pnpm@9.x"
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### pnpm-workspace.yaml
|
|
237
|
+
|
|
238
|
+
```yaml
|
|
239
|
+
packages:
|
|
240
|
+
- "apps/*"
|
|
241
|
+
- "packages/*"
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### turbo.json
|
|
245
|
+
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"$schema": "https://turbo.build/schema.json",
|
|
249
|
+
"tasks": {
|
|
250
|
+
"build": {
|
|
251
|
+
"dependsOn": ["^build"],
|
|
252
|
+
"outputs": ["dist/**", ".next/**", ".vercel/**"]
|
|
253
|
+
},
|
|
254
|
+
"dev": {
|
|
255
|
+
"cache": false,
|
|
256
|
+
"persistent": true
|
|
257
|
+
},
|
|
258
|
+
"lint": {},
|
|
259
|
+
"test": {
|
|
260
|
+
"dependsOn": ["build"]
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### apps/api/vercel.ts
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { routes, type VercelConfig } from '@vercel/config/v1';
|
|
270
|
+
|
|
271
|
+
export const config: VercelConfig = {
|
|
272
|
+
// Enable Fluid Compute for better concurrency
|
|
273
|
+
fluid: true,
|
|
274
|
+
|
|
275
|
+
// Clean URLs (no .ts/.js extensions)
|
|
276
|
+
cleanUrls: true,
|
|
277
|
+
|
|
278
|
+
// Trailing slash handling
|
|
279
|
+
trailingSlash: false,
|
|
280
|
+
|
|
281
|
+
// API versioning via rewrites (optional, for future versions)
|
|
282
|
+
rewrites: [
|
|
283
|
+
// Latest version alias
|
|
284
|
+
routes.rewrite('/api/policies/(.*)', '/api/v1/policies/$1'),
|
|
285
|
+
routes.rewrite('/api/contexts/(.*)', '/api/v1/contexts/$1'),
|
|
286
|
+
],
|
|
287
|
+
|
|
288
|
+
// CORS headers for SDK access
|
|
289
|
+
headers: [
|
|
290
|
+
routes.header('/api/(.*)', [
|
|
291
|
+
{ key: 'Access-Control-Allow-Origin', value: '*' },
|
|
292
|
+
{ key: 'Access-Control-Allow-Methods', value: 'GET, POST, PUT, DELETE, OPTIONS' },
|
|
293
|
+
{ key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization' },
|
|
294
|
+
]),
|
|
295
|
+
],
|
|
296
|
+
|
|
297
|
+
// Function configuration
|
|
298
|
+
functions: {
|
|
299
|
+
'api/**/*.ts': {
|
|
300
|
+
maxDuration: 30,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### apps/api/tsconfig.json
|
|
307
|
+
|
|
308
|
+
```json
|
|
309
|
+
{
|
|
310
|
+
"extends": "../../tsconfig.base.json",
|
|
311
|
+
"compilerOptions": {
|
|
312
|
+
"target": "ES2022",
|
|
313
|
+
"module": "ESNext",
|
|
314
|
+
"moduleResolution": "bundler",
|
|
315
|
+
"strict": true,
|
|
316
|
+
"esModuleInterop": true,
|
|
317
|
+
"skipLibCheck": true,
|
|
318
|
+
"forceConsistentCasingInFileNames": true,
|
|
319
|
+
"outDir": "dist",
|
|
320
|
+
"rootDir": ".",
|
|
321
|
+
"baseUrl": ".",
|
|
322
|
+
"paths": {
|
|
323
|
+
"@/*": ["src/*"]
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
"include": ["src/**/*", "api/**/*"],
|
|
327
|
+
"exclude": ["node_modules", "dist"]
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Component Boundaries
|
|
334
|
+
|
|
335
|
+
### API Layer Responsibilities
|
|
336
|
+
|
|
337
|
+
| Component | Responsibility | Communicates With |
|
|
338
|
+
|-----------|----------------|-------------------|
|
|
339
|
+
| Route Handlers (`api/`) | HTTP request/response, validation | Domain Services |
|
|
340
|
+
| Domain Services (`src/domain/*/service.ts`) | Business logic, orchestration | Repositories, other Services |
|
|
341
|
+
| Repositories (`src/domain/*/repository.ts`) | Data access abstraction | Infrastructure (Database) |
|
|
342
|
+
| Infrastructure (`src/infrastructure/`) | External service clients | External APIs, databases |
|
|
343
|
+
|
|
344
|
+
### Data Flow
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
Request → Route Handler → Validation → Service → Repository → Database
|
|
348
|
+
↓
|
|
349
|
+
Response ← Route Handler ← Service ← Domain Entity
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### SDK Layer Responsibilities
|
|
353
|
+
|
|
354
|
+
| Component | Responsibility |
|
|
355
|
+
|-----------|----------------|
|
|
356
|
+
| Client | HTTP client wrapper, authentication |
|
|
357
|
+
| Types | TypeScript/Python type definitions matching API |
|
|
358
|
+
| Errors | Domain-specific error handling |
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Patterns to Follow
|
|
363
|
+
|
|
364
|
+
### Pattern 1: Vercel Function Handler
|
|
365
|
+
|
|
366
|
+
**What:** Standard pattern for Vercel serverless functions with typed request/response.
|
|
367
|
+
|
|
368
|
+
**When:** Every API route handler.
|
|
369
|
+
|
|
370
|
+
**Example:**
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
// api/v1/policies/[id].ts
|
|
374
|
+
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
|
375
|
+
import { PolicyService } from '@/domain/policy/policy.service';
|
|
376
|
+
import { ApiError } from '@/shared/errors/api-error';
|
|
377
|
+
|
|
378
|
+
export default async function handler(
|
|
379
|
+
request: VercelRequest,
|
|
380
|
+
response: VercelResponse
|
|
381
|
+
) {
|
|
382
|
+
const { id } = request.query;
|
|
383
|
+
|
|
384
|
+
if (typeof id !== 'string') {
|
|
385
|
+
return response.status(400).json({ error: 'Invalid policy ID' });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
switch (request.method) {
|
|
390
|
+
case 'GET':
|
|
391
|
+
const policy = await PolicyService.getById(id);
|
|
392
|
+
if (!policy) {
|
|
393
|
+
return response.status(404).json({ error: 'Policy not found' });
|
|
394
|
+
}
|
|
395
|
+
return response.status(200).json(policy);
|
|
396
|
+
|
|
397
|
+
case 'PUT':
|
|
398
|
+
const updated = await PolicyService.update(id, request.body);
|
|
399
|
+
return response.status(200).json(updated);
|
|
400
|
+
|
|
401
|
+
case 'DELETE':
|
|
402
|
+
await PolicyService.delete(id);
|
|
403
|
+
return response.status(204).end();
|
|
404
|
+
|
|
405
|
+
default:
|
|
406
|
+
response.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
|
|
407
|
+
return response.status(405).json({ error: 'Method not allowed' });
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
if (error instanceof ApiError) {
|
|
411
|
+
return response.status(error.statusCode).json({ error: error.message });
|
|
412
|
+
}
|
|
413
|
+
console.error('Unexpected error:', error);
|
|
414
|
+
return response.status(500).json({ error: 'Internal server error' });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Pattern 2: Service Layer Abstraction
|
|
420
|
+
|
|
421
|
+
**What:** Business logic isolated from HTTP concerns.
|
|
422
|
+
|
|
423
|
+
**When:** Any non-trivial business logic.
|
|
424
|
+
|
|
425
|
+
**Example:**
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
// src/domain/policy/policy.service.ts
|
|
429
|
+
import { PolicyRepository } from './policy.repository';
|
|
430
|
+
import type { Policy, CreatePolicyInput, UpdatePolicyInput } from './policy';
|
|
431
|
+
|
|
432
|
+
export class PolicyService {
|
|
433
|
+
static async getById(id: string): Promise<Policy | null> {
|
|
434
|
+
return PolicyRepository.findById(id);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
static async create(input: CreatePolicyInput): Promise<Policy> {
|
|
438
|
+
// Business validation
|
|
439
|
+
this.validatePolicyRules(input.rules);
|
|
440
|
+
|
|
441
|
+
return PolicyRepository.create(input);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
static async evaluate(policyId: string, context: Record<string, unknown>): Promise<boolean> {
|
|
445
|
+
const policy = await this.getById(policyId);
|
|
446
|
+
if (!policy) {
|
|
447
|
+
throw new NotFoundError('Policy not found');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Core evaluation logic
|
|
451
|
+
return this.evaluateRules(policy.rules, context);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private static validatePolicyRules(rules: unknown): void {
|
|
455
|
+
// Validation logic
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private static evaluateRules(rules: PolicyRule[], context: Record<string, unknown>): boolean {
|
|
459
|
+
// Evaluation logic
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Pattern 3: Shared Types Between API and TypeScript SDK
|
|
465
|
+
|
|
466
|
+
**What:** Single source of truth for TypeScript types.
|
|
467
|
+
|
|
468
|
+
**When:** API response shapes, request bodies.
|
|
469
|
+
|
|
470
|
+
**Example:**
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
// packages/shared-types/src/index.ts
|
|
474
|
+
export interface Policy {
|
|
475
|
+
id: string;
|
|
476
|
+
name: string;
|
|
477
|
+
description?: string;
|
|
478
|
+
rules: PolicyRule[];
|
|
479
|
+
createdAt: string;
|
|
480
|
+
updatedAt: string;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export interface PolicyRule {
|
|
484
|
+
field: string;
|
|
485
|
+
operator: 'equals' | 'contains' | 'gt' | 'lt' | 'in';
|
|
486
|
+
value: unknown;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export interface EvaluationResult {
|
|
490
|
+
allowed: boolean;
|
|
491
|
+
policyId: string;
|
|
492
|
+
matchedRules: string[];
|
|
493
|
+
evaluatedAt: string;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export interface ApiError {
|
|
497
|
+
error: string;
|
|
498
|
+
code?: string;
|
|
499
|
+
details?: Record<string, unknown>;
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## Anti-Patterns to Avoid
|
|
506
|
+
|
|
507
|
+
### Anti-Pattern 1: Fat Route Handlers
|
|
508
|
+
|
|
509
|
+
**What:** Putting business logic directly in route handlers.
|
|
510
|
+
|
|
511
|
+
**Why bad:**
|
|
512
|
+
- Untestable without HTTP mocking
|
|
513
|
+
- Logic cannot be reused
|
|
514
|
+
- Handlers become unmaintainable
|
|
515
|
+
|
|
516
|
+
**Instead:**
|
|
517
|
+
- Route handlers only handle HTTP concerns
|
|
518
|
+
- Delegate to service layer for business logic
|
|
519
|
+
- Services are pure functions, easily testable
|
|
520
|
+
|
|
521
|
+
### Anti-Pattern 2: Layer-First Organization
|
|
522
|
+
|
|
523
|
+
**What:** Organizing by technical layer (`controllers/`, `services/`, `models/`).
|
|
524
|
+
|
|
525
|
+
**Why bad:**
|
|
526
|
+
- Related code scattered across directories
|
|
527
|
+
- Adding a feature touches many directories
|
|
528
|
+
- Harder to understand feature boundaries
|
|
529
|
+
|
|
530
|
+
**Instead:**
|
|
531
|
+
- Feature-first organization (`policy/`, `context/`)
|
|
532
|
+
- Each feature contains its controller, service, repository
|
|
533
|
+
- Cross-cutting concerns in `shared/`
|
|
534
|
+
|
|
535
|
+
### Anti-Pattern 3: Next.js for Pure APIs
|
|
536
|
+
|
|
537
|
+
**What:** Using Next.js when you only need an API.
|
|
538
|
+
|
|
539
|
+
**Why bad:**
|
|
540
|
+
- Unnecessary React/frontend overhead
|
|
541
|
+
- App Router vs Pages Router complexity
|
|
542
|
+
- Larger bundle sizes
|
|
543
|
+
- Build time overhead
|
|
544
|
+
|
|
545
|
+
**Instead:**
|
|
546
|
+
- Pure Vercel Functions with `/api` directory
|
|
547
|
+
- Only add Next.js if you need a frontend
|
|
548
|
+
|
|
549
|
+
### Anti-Pattern 4: Polyrepo for Tightly Coupled Components
|
|
550
|
+
|
|
551
|
+
**What:** Separate repositories for API and SDKs when they share types/releases.
|
|
552
|
+
|
|
553
|
+
**Why bad:**
|
|
554
|
+
- Version coordination complexity
|
|
555
|
+
- Type drift between API and SDKs
|
|
556
|
+
- Multiple CI/CD pipelines to maintain
|
|
557
|
+
- Harder for contributors
|
|
558
|
+
|
|
559
|
+
**Instead:**
|
|
560
|
+
- Monorepo with Turborepo
|
|
561
|
+
- Shared types package
|
|
562
|
+
- Coordinated releases
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## Scalability Considerations
|
|
567
|
+
|
|
568
|
+
| Concern | At 100 users | At 10K users | At 1M users |
|
|
569
|
+
|---------|--------------|--------------|-------------|
|
|
570
|
+
| **Request handling** | Single region Vercel Functions | Multi-region deployment | Multi-region + edge caching |
|
|
571
|
+
| **Database** | Serverless DB (Turso, Neon) | Serverless DB with read replicas | Dedicated database + caching layer |
|
|
572
|
+
| **Caching** | None needed | Vercel Edge Cache | Redis/Upstash + Edge Cache |
|
|
573
|
+
| **Rate limiting** | Basic (per-IP) | API key based | Token bucket with Redis |
|
|
574
|
+
| **SDK distribution** | npm/PyPI | npm/PyPI + CDN | npm/PyPI + CDN + enterprise support |
|
|
575
|
+
|
|
576
|
+
### Vercel Function Limits to Consider
|
|
577
|
+
|
|
578
|
+
| Limit | Hobby | Pro | Enterprise |
|
|
579
|
+
|-------|-------|-----|------------|
|
|
580
|
+
| Duration | 10s | 60s (default 15s) | 900s (default 15s) |
|
|
581
|
+
| Memory | 1024MB | Configurable | Configurable |
|
|
582
|
+
| Payload | 4.5MB | 4.5MB | 4.5MB |
|
|
583
|
+
|
|
584
|
+
**Source:** [Vercel Functions Documentation](https://vercel.com/docs/functions)
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## Sources
|
|
589
|
+
|
|
590
|
+
### HIGH Confidence (Official Documentation)
|
|
591
|
+
- [Vercel Functions](https://vercel.com/docs/functions) - Serverless function patterns
|
|
592
|
+
- [Vercel vercel.ts Configuration](https://vercel.com/docs/project-configuration/vercel-ts) - Programmatic configuration
|
|
593
|
+
- [Turborepo Repository Structure](https://turborepo.dev/docs/crafting-your-repository/structuring-a-repository) - Monorepo patterns
|
|
594
|
+
|
|
595
|
+
### MEDIUM Confidence (Verified Patterns)
|
|
596
|
+
- [Hosting Backend APIs on Vercel](https://vercel.com/kb/guide/hosting-backend-apis) - Pure API deployment
|
|
597
|
+
- [Monorepo Tools Comparison](https://monorepo.tools/) - Tool ecosystem overview
|
|
598
|
+
|
|
599
|
+
### LOW Confidence (Community Patterns)
|
|
600
|
+
- [Feature-First Organization](https://dev.to/pramod_boda/recommended-folder-structure-for-nodets-2025-39jl) - Modern TypeScript organization
|
|
601
|
+
- [Multi-Language Monorepo Examples](https://github.com/palmerhq/monorepo-starter) - Polyglot monorepo patterns
|