@terreno/api 0.0.18 → 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/CLAUDE.local.md +204 -0
- package/.cursor/rules/00-root.mdc +338 -0
- package/.github/copilot-instructions.md +333 -0
- package/AGENTS.md +23 -3
- package/README.md +73 -3
- package/dist/api.d.ts +68 -1
- package/dist/api.js +139 -4
- package/dist/api.test.js +906 -2
- package/dist/auth.js +3 -1
- package/dist/errors.js +14 -11
- package/dist/example.js +7 -7
- package/dist/githubAuth.test.js +3 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/openApi.test.js +8 -5
- package/dist/openApiBuilder.d.ts +69 -1
- package/dist/openApiBuilder.js +109 -5
- package/dist/openApiValidator.d.ts +296 -0
- package/dist/openApiValidator.js +698 -0
- package/dist/openApiValidator.test.d.ts +1 -0
- package/dist/openApiValidator.test.js +346 -0
- package/dist/plugins.test.js +3 -3
- package/dist/terrenoPlugin.d.ts +4 -0
- package/dist/terrenoPlugin.js +2 -0
- package/dist/tests.js +34 -24
- package/package.json +4 -1
- package/src/__snapshots__/openApi.test.ts.snap +399 -0
- package/src/__snapshots__/openApiBuilder.test.ts.snap +108 -0
- package/src/api.test.ts +743 -2
- package/src/api.ts +209 -3
- package/src/auth.ts +3 -1
- package/src/errors.ts +14 -11
- package/src/example.ts +7 -7
- package/src/githubAuth.test.ts +3 -3
- package/src/index.ts +2 -0
- package/src/openApi.test.ts +8 -5
- package/src/openApiBuilder.ts +188 -15
- package/src/openApiValidator.test.ts +241 -0
- package/src/openApiValidator.ts +860 -0
- package/src/plugins.test.ts +3 -3
- package/src/terrenoPlugin.ts +5 -0
- package/src/tests.ts +34 -24
- package/.cursorrules +0 -107
- package/.windsurfrules +0 -107
- package/dist/response.d.ts +0 -0
- package/dist/response.js +0 -1
- package/index.ts +0 -1
- package/src/response.ts +0 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# Terreno
|
|
2
|
+
|
|
3
|
+
A monorepo containing shared packages for building full-stack applications with React Native and Express/Mongoose.
|
|
4
|
+
|
|
5
|
+
## Packages
|
|
6
|
+
|
|
7
|
+
- **api/** - REST API framework built on Express/Mongoose (`@terreno/api`)
|
|
8
|
+
- **ui/** - React Native UI component library (`@terreno/ui`)
|
|
9
|
+
- **rtk/** - Redux Toolkit Query utilities for API backends (`@terreno/rtk`)
|
|
10
|
+
- **mcp-server/** - MCP server for AI assistant integration (`@terreno/mcp-server`)
|
|
11
|
+
- **demo/** - Demo app for showcasing and testing UI components
|
|
12
|
+
- **example-frontend/** - Example Expo app demonstrating full stack usage
|
|
13
|
+
- **example-backend/** - Example Express backend using @terreno/api
|
|
14
|
+
|
|
15
|
+
## Development
|
|
16
|
+
|
|
17
|
+
Uses [Bun](https://bun.sh/) as the package manager.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bun install # Install dependencies
|
|
21
|
+
bun run compile # Compile all packages
|
|
22
|
+
bun run lint # Lint all packages
|
|
23
|
+
bun run lint:fix # Fix lint issues
|
|
24
|
+
bun run test # Run tests in api and ui
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Package-specific commands
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bun run api:test # Test API package
|
|
31
|
+
bun run ui:test # Test UI package
|
|
32
|
+
bun run demo:start # Start demo app
|
|
33
|
+
bun run frontend:web # Start frontend example
|
|
34
|
+
bun run backend:dev # Start backend example
|
|
35
|
+
bun run mcp:build # Build MCP server
|
|
36
|
+
bun run mcp:start # Start MCP server
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## How the Packages Work Together
|
|
40
|
+
|
|
41
|
+
The three core packages form a complete full-stack framework:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
BACKEND
|
|
45
|
+
@terreno/api
|
|
46
|
+
- Mongoose models with modelRouter -> CRUD endpoints
|
|
47
|
+
- Built-in auth (JWT + Passport)
|
|
48
|
+
- Automatic OpenAPI spec generation
|
|
49
|
+
|
|
|
50
|
+
/openapi.json
|
|
51
|
+
|
|
|
52
|
+
RTK Query SDK Codegen
|
|
53
|
+
|
|
|
54
|
+
FRONTEND
|
|
55
|
+
@terreno/rtk
|
|
56
|
+
- Generated hooks from OpenAPI spec
|
|
57
|
+
- Auth slice with JWT token management
|
|
58
|
+
- Automatic token refresh
|
|
59
|
+
+
|
|
60
|
+
@terreno/ui
|
|
61
|
+
- React Native components (Box, Button, TextField, etc.)
|
|
62
|
+
- TerrenoProvider for theming
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Integration Flow
|
|
66
|
+
|
|
67
|
+
1. **Backend (api)**: Define Mongoose models, use `modelRouter` to create CRUD endpoints with permissions
|
|
68
|
+
2. **OpenAPI Generation**: `setupServer` automatically generates `/openapi.json`
|
|
69
|
+
3. **SDK Codegen**: Frontend runs `bun run sdk` to generate RTK Query hooks from OpenAPI spec
|
|
70
|
+
4. **Frontend (rtk + ui)**: Use generated hooks with UI components for type-safe API calls
|
|
71
|
+
|
|
72
|
+
## Example Apps (Keep These Updated!)
|
|
73
|
+
|
|
74
|
+
The `example-frontend/` and `example-backend/` directories serve as both documentation and integration tests. When adding features to api, ui, or rtk:
|
|
75
|
+
|
|
76
|
+
1. **Add examples** demonstrating new features
|
|
77
|
+
2. **Update SDK** after backend changes: `cd example-frontend && bun run sdk`
|
|
78
|
+
3. **Verify integration** by running both examples together
|
|
79
|
+
|
|
80
|
+
### Running the Full Stack
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Terminal 1: Start backend
|
|
84
|
+
bun run backend:dev
|
|
85
|
+
|
|
86
|
+
# Terminal 2: Start frontend
|
|
87
|
+
bun run frontend:web
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Code Style
|
|
91
|
+
|
|
92
|
+
### TypeScript/JavaScript
|
|
93
|
+
- Use ES module syntax and TypeScript for all code
|
|
94
|
+
- Prefer interfaces over types; avoid enums, use maps
|
|
95
|
+
- Prefer const arrow functions over `function` keyword
|
|
96
|
+
- Use descriptive variable names with auxiliary verbs (e.g., `isLoading`)
|
|
97
|
+
- Use camelCase directories (e.g., `components/authWizard`)
|
|
98
|
+
- Favor named exports
|
|
99
|
+
- Use the RORO pattern (Receive an Object, Return an Object)
|
|
100
|
+
|
|
101
|
+
### Dates and Time
|
|
102
|
+
- Always use Luxon instead of Date or dayjs
|
|
103
|
+
|
|
104
|
+
### Error Handling
|
|
105
|
+
- Check error conditions at start of functions and return early
|
|
106
|
+
- Limit nested if statements
|
|
107
|
+
- Use multiline syntax with curly braces for all conditionals
|
|
108
|
+
|
|
109
|
+
### Testing
|
|
110
|
+
- Use bun test with expect for testing
|
|
111
|
+
|
|
112
|
+
### Logging
|
|
113
|
+
- Frontend: Use `console.info`, `console.debug`, `console.warn`, or `console.error` for permanent logs
|
|
114
|
+
- Backend: Use `logger.info/warn/error/debug` for permanent logs
|
|
115
|
+
- Use `console.log` only for debugging (to be removed)
|
|
116
|
+
|
|
117
|
+
### Development Practices
|
|
118
|
+
- Don't apologize for errors: fix them
|
|
119
|
+
- Prioritize modularity, DRY, performance, and security
|
|
120
|
+
- Focus on readability over performance
|
|
121
|
+
- Write complete, functional code without TODOs when possible
|
|
122
|
+
- Comments should describe purpose, not effect
|
|
123
|
+
|
|
124
|
+
## Package Reference
|
|
125
|
+
|
|
126
|
+
### @terreno/api
|
|
127
|
+
|
|
128
|
+
REST API framework providing:
|
|
129
|
+
|
|
130
|
+
- **modelRouter**: Auto-generates CRUD endpoints for Mongoose models
|
|
131
|
+
- **Permissions**: `IsAuthenticated`, `IsOwner`, `IsAdmin`, `IsAuthenticatedOrReadOnly`
|
|
132
|
+
- **Query Filters**: `OwnerQueryFilter` for filtering list queries by owner
|
|
133
|
+
- **setupServer**: Express server setup with auth, OpenAPI, and middleware
|
|
134
|
+
- **APIError**: Standardized error handling
|
|
135
|
+
- **logger**: Winston-based logging
|
|
136
|
+
|
|
137
|
+
Key imports:
|
|
138
|
+
```typescript
|
|
139
|
+
import {
|
|
140
|
+
modelRouter,
|
|
141
|
+
setupServer,
|
|
142
|
+
Permissions,
|
|
143
|
+
OwnerQueryFilter,
|
|
144
|
+
APIError,
|
|
145
|
+
logger,
|
|
146
|
+
asyncHandler,
|
|
147
|
+
authenticateMiddleware,
|
|
148
|
+
} from "@terreno/api";
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### modelRouter Usage
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import {modelRouter, modelRouterOptions, Permissions} from "@terreno/api";
|
|
155
|
+
|
|
156
|
+
const router = modelRouter(YourModel, {
|
|
157
|
+
permissions: {
|
|
158
|
+
list: [Permissions.IsAuthenticated],
|
|
159
|
+
create: [Permissions.IsAuthenticated],
|
|
160
|
+
read: [Permissions.IsOwner],
|
|
161
|
+
update: [Permissions.IsOwner],
|
|
162
|
+
delete: [], // Disabled
|
|
163
|
+
},
|
|
164
|
+
sort: "-created",
|
|
165
|
+
queryFields: ["_id", "type", "name"],
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### Custom Routes
|
|
170
|
+
|
|
171
|
+
For non-CRUD endpoints, use the OpenAPI builder:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import {asyncHandler, authenticateMiddleware, createOpenApiBuilder} from "@terreno/api";
|
|
175
|
+
|
|
176
|
+
router.get("/yourRoute/:id", [
|
|
177
|
+
authenticateMiddleware(),
|
|
178
|
+
createOpenApiBuilder(options)
|
|
179
|
+
.withTags(["yourTag"])
|
|
180
|
+
.withSummary("Brief summary")
|
|
181
|
+
.withPathParameter("id", {type: "string"})
|
|
182
|
+
.withResponse(200, {data: {type: "object"}})
|
|
183
|
+
.build(),
|
|
184
|
+
], asyncHandler(async (req, res) => {
|
|
185
|
+
return res.json({data: result});
|
|
186
|
+
}));
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### API Conventions
|
|
190
|
+
|
|
191
|
+
- Throw `APIError` with appropriate status codes: `throw new APIError({status: 400, title: "Message"})`
|
|
192
|
+
- Do not use `Model.findOne` - use `Model.findExactlyOne` or `Model.findOneOrThrow`
|
|
193
|
+
- Define statics/methods by direct assignment: `schema.methods = {bar() {}}`
|
|
194
|
+
- All model types live in `src/modelInterfaces.ts`
|
|
195
|
+
- In routes: `req.user` is `UserDocument | undefined`
|
|
196
|
+
- In @terreno/api callbacks: cast with `const user = u as unknown as UserDocument`
|
|
197
|
+
|
|
198
|
+
### @terreno/ui
|
|
199
|
+
|
|
200
|
+
React Native component library with 88+ components:
|
|
201
|
+
|
|
202
|
+
- **Layout**: Box, Page, SplitPage, Card
|
|
203
|
+
- **Forms**: TextField, SelectField, DateTimeField, CheckBox
|
|
204
|
+
- **Display**: Text, Heading, Badge, DataTable
|
|
205
|
+
- **Actions**: Button, IconButton, Link
|
|
206
|
+
- **Feedback**: Spinner, Modal, Toast
|
|
207
|
+
- **Theming**: TerrenoProvider, useTheme
|
|
208
|
+
|
|
209
|
+
Key imports:
|
|
210
|
+
```typescript
|
|
211
|
+
import {
|
|
212
|
+
Box,
|
|
213
|
+
Button,
|
|
214
|
+
Card,
|
|
215
|
+
Page,
|
|
216
|
+
Text,
|
|
217
|
+
TextField,
|
|
218
|
+
TerrenoProvider,
|
|
219
|
+
} from "@terreno/ui";
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### UI Component Examples
|
|
223
|
+
|
|
224
|
+
Layout with Box:
|
|
225
|
+
```typescript
|
|
226
|
+
<Box direction="row" padding={4} gap={2} alignItems="center">
|
|
227
|
+
<Text>Content</Text>
|
|
228
|
+
<Button text="Action" />
|
|
229
|
+
</Box>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Buttons:
|
|
233
|
+
```typescript
|
|
234
|
+
<Button
|
|
235
|
+
text="Submit"
|
|
236
|
+
variant="primary" // 'primary' | 'secondary' | 'outline' | 'ghost'
|
|
237
|
+
onClick={handleSubmit}
|
|
238
|
+
loading={isLoading}
|
|
239
|
+
iconName="check"
|
|
240
|
+
/>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Forms:
|
|
244
|
+
```typescript
|
|
245
|
+
<TextField
|
|
246
|
+
label="Email"
|
|
247
|
+
value={email}
|
|
248
|
+
onChangeText={setEmail}
|
|
249
|
+
error={emailError}
|
|
250
|
+
helperText="Enter a valid email"
|
|
251
|
+
/>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Modals:
|
|
255
|
+
```typescript
|
|
256
|
+
<Modal
|
|
257
|
+
title="Confirm Action"
|
|
258
|
+
visible={isVisible}
|
|
259
|
+
primaryButtonText="Confirm"
|
|
260
|
+
secondaryButtonText="Cancel"
|
|
261
|
+
onDismiss={() => setIsVisible(false)}
|
|
262
|
+
onPrimaryAction={handleConfirm}
|
|
263
|
+
>
|
|
264
|
+
<Text>Are you sure?</Text>
|
|
265
|
+
</Modal>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### UI Common Pitfalls
|
|
269
|
+
|
|
270
|
+
- Don't use inline styles when theme values are available
|
|
271
|
+
- Don't use raw `View`/`Text` when `Box`/@terreno/ui `Text` are available
|
|
272
|
+
- Don't forget loading and error states
|
|
273
|
+
- Don't use `style` prop when equivalent props exist (`padding`, `margin`)
|
|
274
|
+
- Never modify `openApiSdk.ts` manually
|
|
275
|
+
|
|
276
|
+
### @terreno/rtk
|
|
277
|
+
|
|
278
|
+
Redux Toolkit Query integration:
|
|
279
|
+
|
|
280
|
+
- **generateAuthSlice**: Creates auth reducer and middleware with JWT handling
|
|
281
|
+
- **emptyApi**: Base RTK Query API for code generation
|
|
282
|
+
- **Platform utilities**: Secure token storage (expo-secure-store for native, AsyncStorage for web)
|
|
283
|
+
|
|
284
|
+
Key imports:
|
|
285
|
+
```typescript
|
|
286
|
+
import {generateAuthSlice} from "@terreno/rtk";
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Always use generated SDK hooks - never use `axios` or `request` directly:
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// Correct
|
|
293
|
+
import {useGetYourRouteQuery} from "@/store/openApiSdk";
|
|
294
|
+
const {data, isLoading, error} = useGetYourRouteQuery({id: "value"});
|
|
295
|
+
|
|
296
|
+
// Wrong - don't use axios directly
|
|
297
|
+
// const result = await axios.get("/api/yourRoute/value");
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## React Best Practices (Frontend Packages)
|
|
301
|
+
|
|
302
|
+
- Use functional components with `React.FC` type
|
|
303
|
+
- Import hooks directly: `import {useEffect, useMemo} from 'react'`
|
|
304
|
+
- Always provide return types for functions
|
|
305
|
+
- Add explanatory comment above each `useEffect`
|
|
306
|
+
- Wrap callbacks in `useCallback`
|
|
307
|
+
- Prefer const arrow functions
|
|
308
|
+
- Use inline styles over `StyleSheet.create`
|
|
309
|
+
- Use Luxon for date operations
|
|
310
|
+
- Place static content and interfaces at beginning of file
|
|
311
|
+
- Minimize `use client`, `useEffect`, and `setState`
|
|
312
|
+
- Always support React-Native Web
|
|
313
|
+
|
|
314
|
+
## CI/CD Workflows
|
|
315
|
+
|
|
316
|
+
### Required Secret Validation
|
|
317
|
+
|
|
318
|
+
GitHub Actions workflows that use secrets or environment variables must validate all required variables are set before using them. Add a validation step early in the job that fails fast with a clear error message listing any missing variables.
|
|
319
|
+
|
|
320
|
+
```yaml
|
|
321
|
+
- name: Validate required secrets
|
|
322
|
+
run: |
|
|
323
|
+
missing=()
|
|
324
|
+
if [ -z "$VAR_NAME" ]; then missing+=("VAR_NAME"); fi
|
|
325
|
+
if [ ${#missing[@]} -ne 0 ]; then
|
|
326
|
+
echo "::error::Missing required secrets: ${missing[*]}"
|
|
327
|
+
exit 1
|
|
328
|
+
fi
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Dependency Management
|
|
332
|
+
|
|
333
|
+
Uses [Bun Catalogs](https://bun.sh/docs/install/catalogs) - shared versions defined in root `package.json` under `catalog`. Reference with `catalog:` in workspace packages.
|
package/AGENTS.md
CHANGED
|
@@ -7,13 +7,14 @@ A monorepo containing shared packages for building full-stack applications with
|
|
|
7
7
|
- **api/** - REST API framework built on Express/Mongoose (`@terreno/api`)
|
|
8
8
|
- **ui/** - React Native UI component library (`@terreno/ui`)
|
|
9
9
|
- **rtk/** - Redux Toolkit Query utilities for API backends (`@terreno/rtk`)
|
|
10
|
+
- **mcp-server/** - MCP server for AI assistant integration (`@terreno/mcp-server`)
|
|
10
11
|
- **demo/** - Demo app for showcasing and testing UI components
|
|
11
12
|
- **example-frontend/** - Example Expo app demonstrating full stack usage
|
|
12
13
|
- **example-backend/** - Example Express backend using @terreno/api
|
|
13
14
|
|
|
14
15
|
## Development
|
|
15
16
|
|
|
16
|
-
Uses [Bun](https://bun.sh/) as the package manager.
|
|
17
|
+
Uses [Bun](https://bun.sh/) as the package manager.
|
|
17
18
|
|
|
18
19
|
```bash
|
|
19
20
|
bun install # Install dependencies
|
|
@@ -31,6 +32,8 @@ bun run ui:test # Test UI package
|
|
|
31
32
|
bun run demo:start # Start demo app
|
|
32
33
|
bun run frontend:web # Start frontend example
|
|
33
34
|
bun run backend:dev # Start backend example
|
|
35
|
+
bun run mcp:build # Build MCP server
|
|
36
|
+
bun run mcp:start # Start MCP server
|
|
34
37
|
```
|
|
35
38
|
|
|
36
39
|
## How the Packages Work Together
|
|
@@ -68,10 +71,10 @@ The three core packages form a complete full-stack framework:
|
|
|
68
71
|
|
|
69
72
|
## Example Apps (Keep These Updated!)
|
|
70
73
|
|
|
71
|
-
The `frontend
|
|
74
|
+
The `example-frontend/` and `example-backend/` directories serve as both documentation and integration tests. When adding features to api, ui, or rtk:
|
|
72
75
|
|
|
73
76
|
1. **Add examples** demonstrating new features
|
|
74
|
-
2. **Update SDK** after backend changes: `cd frontend
|
|
77
|
+
2. **Update SDK** after backend changes: `cd example-frontend && bun run sdk`
|
|
75
78
|
3. **Verify integration** by running both examples together
|
|
76
79
|
|
|
77
80
|
### Running the Full Stack
|
|
@@ -308,6 +311,23 @@ const {data, isLoading, error} = useGetYourRouteQuery({id: "value"});
|
|
|
308
311
|
- Minimize `use client`, `useEffect`, and `setState`
|
|
309
312
|
- Always support React-Native Web
|
|
310
313
|
|
|
314
|
+
## CI/CD Workflows
|
|
315
|
+
|
|
316
|
+
### Required Secret Validation
|
|
317
|
+
|
|
318
|
+
GitHub Actions workflows that use secrets or environment variables must validate all required variables are set before using them. Add a validation step early in the job that fails fast with a clear error message listing any missing variables.
|
|
319
|
+
|
|
320
|
+
```yaml
|
|
321
|
+
- name: Validate required secrets
|
|
322
|
+
run: |
|
|
323
|
+
missing=()
|
|
324
|
+
if [ -z "$VAR_NAME" ]; then missing+=("VAR_NAME"); fi
|
|
325
|
+
if [ ${#missing[@]} -ne 0 ]; then
|
|
326
|
+
echo "::error::Missing required secrets: ${missing[*]}"
|
|
327
|
+
exit 1
|
|
328
|
+
fi
|
|
329
|
+
```
|
|
330
|
+
|
|
311
331
|
## Dependency Management
|
|
312
332
|
|
|
313
333
|
Uses [Bun Catalogs](https://bun.sh/docs/install/catalogs) - shared versions defined in root `package.json` under `catalog`. Reference with `catalog:` in workspace packages.
|
package/README.md
CHANGED
|
@@ -9,6 +9,14 @@ These APIs integrate with @terreno/rtk to create consistent types on the fronten
|
|
|
9
9
|
and backend, and automatically generated React hooks to fetch, query, and modify
|
|
10
10
|
model instances.
|
|
11
11
|
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **modelRouter** — Automatic CRUD endpoints for Mongoose models
|
|
15
|
+
- **Authentication** — JWT with email/password and GitHub OAuth support
|
|
16
|
+
- **Permissions** — Fine-grained access control (IsAuthenticated, IsOwner, IsAdmin, etc.)
|
|
17
|
+
- **OpenAPI** — Automatic spec generation from models and routes
|
|
18
|
+
- **Logging** — Winston-based logging with Google Cloud and Sentry support
|
|
19
|
+
|
|
12
20
|
## Getting started
|
|
13
21
|
|
|
14
22
|
To install:
|
|
@@ -46,12 +54,25 @@ const eventSchema = new Schema({
|
|
|
46
54
|
Assuming we have a model:
|
|
47
55
|
|
|
48
56
|
const foodSchema = new Schema<Food>({
|
|
49
|
-
name:
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
name: {
|
|
58
|
+
description: "Name of the food item",
|
|
59
|
+
type: String,
|
|
60
|
+
},
|
|
61
|
+
hidden: {
|
|
62
|
+
description: "Whether the food is hidden from the list",
|
|
63
|
+
type: Boolean,
|
|
64
|
+
default: false,
|
|
65
|
+
},
|
|
66
|
+
ownerId: {
|
|
67
|
+
description: "The user who added this food",
|
|
68
|
+
type: "ObjectId",
|
|
69
|
+
ref: "User",
|
|
70
|
+
},
|
|
52
71
|
});
|
|
53
72
|
export const FoodModel = model("Food", foodSchema);
|
|
54
73
|
|
|
74
|
+
**Important:** Every field must include a `description` property. This requirement ensures that the auto-generated OpenAPI specification and SDK have meaningful documentation for all fields.
|
|
75
|
+
|
|
55
76
|
We can expose this model as an API like this:
|
|
56
77
|
|
|
57
78
|
import express from "express";
|
|
@@ -99,6 +120,55 @@ Now we can perform operations on the Food model in a standard REST way. We've al
|
|
|
99
120
|
|
|
100
121
|
You can create your own permissions functions. Check permissions.ts for some examples of how to write them.
|
|
101
122
|
|
|
123
|
+
## Authentication
|
|
124
|
+
|
|
125
|
+
@terreno/api includes built-in authentication with JWT and OAuth support.
|
|
126
|
+
|
|
127
|
+
### Email/Password Authentication
|
|
128
|
+
|
|
129
|
+
Built-in email/password authentication using `passport-local-mongoose`:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import {setupServer} from "@terreno/api";
|
|
133
|
+
import {User} from "./models/user";
|
|
134
|
+
|
|
135
|
+
setupServer({
|
|
136
|
+
userModel: User,
|
|
137
|
+
addRoutes: (router) => {
|
|
138
|
+
// Your routes here
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
This automatically adds:
|
|
144
|
+
- `POST /auth/signup` — User registration
|
|
145
|
+
- `POST /auth/login` — Authentication
|
|
146
|
+
- `POST /auth/refresh_token` — Token refresh
|
|
147
|
+
- `GET /auth/me` — Get current user
|
|
148
|
+
- `PATCH /auth/me` — Update current user
|
|
149
|
+
|
|
150
|
+
### GitHub OAuth
|
|
151
|
+
|
|
152
|
+
Add GitHub OAuth login to your API:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import {githubUserPlugin, setupServer} from "@terreno/api";
|
|
156
|
+
|
|
157
|
+
// Add GitHub fields to user schema
|
|
158
|
+
userSchema.plugin(githubUserPlugin);
|
|
159
|
+
|
|
160
|
+
setupServer({
|
|
161
|
+
userModel: User,
|
|
162
|
+
githubAuth: {
|
|
163
|
+
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
164
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
165
|
+
callbackURL: process.env.GITHUB_CALLBACK_URL!,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Learn more:** See the [GitHub OAuth how-to guide](../docs/how-to/add-github-oauth.md) for complete setup instructions.
|
|
171
|
+
|
|
102
172
|
## Sentry
|
|
103
173
|
To enable Sentry, create a "src/sentryInstrumment.ts" file in your project.
|
|
104
174
|
|
package/dist/api.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import express, { type NextFunction, type Request, type Response } from "express";
|
|
2
2
|
import mongoose, { type Document, type Model } from "mongoose";
|
|
3
3
|
import { type User } from "./auth";
|
|
4
|
+
import { type ModelRouterValidationOptions } from "./openApiValidator";
|
|
4
5
|
import { type RESTPermissions } from "./permissions";
|
|
5
6
|
import type { PopulatePath } from "./populate";
|
|
6
7
|
import { type TerrenoTransformer } from "./transformers";
|
|
@@ -203,6 +204,19 @@ export interface ModelRouterOptions<T> {
|
|
|
203
204
|
* that you want to be documented and typed in the SDK.
|
|
204
205
|
*/
|
|
205
206
|
openApiExtraModelProperties?: any;
|
|
207
|
+
/**
|
|
208
|
+
* Enable runtime validation of request bodies against the OpenAPI schema.
|
|
209
|
+
* When enabled, requests that don't match the documented schema will return 400 errors.
|
|
210
|
+
*
|
|
211
|
+
* Can be set to:
|
|
212
|
+
* - `true`: Enable validation for create and update operations
|
|
213
|
+
* - `false`: Disable validation (default)
|
|
214
|
+
* - Object with `validateCreate` and `validateUpdate` booleans for fine-grained control
|
|
215
|
+
*
|
|
216
|
+
* Note: Global validation can be enabled via `configureOpenApiValidator()`.
|
|
217
|
+
* This option overrides the global setting for this specific router.
|
|
218
|
+
*/
|
|
219
|
+
validation?: boolean | ModelRouterValidationOptions;
|
|
206
220
|
}
|
|
207
221
|
/**
|
|
208
222
|
* Create a set of CRUD routes given a Mongoose model and configuration options.
|
|
@@ -211,6 +225,59 @@ export interface ModelRouterOptions<T> {
|
|
|
211
225
|
* @param options Options for configuring the REST API, such as permissions, transformers, and hooks.
|
|
212
226
|
*/
|
|
213
227
|
export declare function modelRouter<T>(model: Model<T>, options: ModelRouterOptions<T>): express.Router;
|
|
214
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Options for the asyncHandler function.
|
|
230
|
+
*/
|
|
231
|
+
export interface AsyncHandlerOptions {
|
|
232
|
+
/**
|
|
233
|
+
* Schema for validating request body.
|
|
234
|
+
* When provided and validation is enabled, the request body will be validated
|
|
235
|
+
* against this schema before the handler runs.
|
|
236
|
+
*/
|
|
237
|
+
bodySchema?: Record<string, import("./openApiBuilder").OpenApiSchemaProperty>;
|
|
238
|
+
/**
|
|
239
|
+
* Schema for validating query parameters.
|
|
240
|
+
* When provided and validation is enabled, query params will be validated
|
|
241
|
+
* against this schema before the handler runs.
|
|
242
|
+
*/
|
|
243
|
+
querySchema?: Record<string, import("./openApiBuilder").OpenApiSchemaProperty>;
|
|
244
|
+
/**
|
|
245
|
+
* Override global validation setting for this handler.
|
|
246
|
+
* - `true`: Enable validation regardless of global setting
|
|
247
|
+
* - `false`: Disable validation regardless of global setting
|
|
248
|
+
* - `undefined`: Use global setting
|
|
249
|
+
*/
|
|
250
|
+
validate?: boolean;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Wraps async route handlers to properly catch and forward errors.
|
|
254
|
+
*
|
|
255
|
+
* Since Express doesn't handle async routes well, wrap them with this function.
|
|
256
|
+
* Optionally supports integrated request validation.
|
|
257
|
+
*
|
|
258
|
+
* @param fn - The async route handler function
|
|
259
|
+
* @param options - Optional configuration for validation
|
|
260
|
+
* @returns Express middleware function
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```typescript
|
|
264
|
+
* // Basic usage without validation
|
|
265
|
+
* router.post("/users", asyncHandler(async (req, res) => {
|
|
266
|
+
* // handler code
|
|
267
|
+
* }));
|
|
268
|
+
*
|
|
269
|
+
* // With integrated validation
|
|
270
|
+
* router.post("/users", asyncHandler(async (req, res) => {
|
|
271
|
+
* // handler code - body is already validated
|
|
272
|
+
* }, {
|
|
273
|
+
* bodySchema: {
|
|
274
|
+
* name: {type: "string", required: true},
|
|
275
|
+
* email: {type: "string", format: "email", required: true},
|
|
276
|
+
* },
|
|
277
|
+
* validate: true,
|
|
278
|
+
* }));
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
export declare const asyncHandler: (fn: any, options?: AsyncHandlerOptions) => (req: Request, res: Response, next: NextFunction) => void;
|
|
215
282
|
export declare const gooseRestRouter: typeof modelRouter;
|
|
216
283
|
export type GooseRESTOptions<T> = ModelRouterOptions<T>;
|