@facetlayer/prism-framework 0.4.0 → 0.4.1
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 +176 -8
- package/dist/Errors.d.ts +38 -0
- package/dist/Errors.d.ts.map +1 -0
- package/dist/Metrics.d.ts +5 -0
- package/dist/Metrics.d.ts.map +1 -0
- package/dist/RequestContext.d.ts +17 -0
- package/dist/RequestContext.d.ts.map +1 -0
- package/dist/ServiceDefinition.d.ts +16 -0
- package/dist/ServiceDefinition.d.ts.map +1 -0
- package/dist/app/PrismApp.d.ts +31 -0
- package/dist/app/PrismApp.d.ts.map +1 -0
- package/dist/app/callEndpoint.d.ts +13 -0
- package/dist/app/callEndpoint.d.ts.map +1 -0
- package/dist/app/validateApp.d.ts +20 -0
- package/dist/app/validateApp.d.ts.map +1 -0
- package/dist/authorization/AuthSource.d.ts +8 -0
- package/dist/authorization/AuthSource.d.ts.map +1 -0
- package/dist/authorization/Authorization.d.ts +24 -0
- package/dist/authorization/Authorization.d.ts.map +1 -0
- package/dist/authorization/Resource.d.ts +5 -0
- package/dist/authorization/Resource.d.ts.map +1 -0
- package/dist/authorization/index.d.ts +5 -0
- package/dist/authorization/index.d.ts.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/databases/DatabaseInitializationOptions.d.ts +9 -0
- package/dist/databases/DatabaseInitializationOptions.d.ts.map +1 -0
- package/dist/databases/DatabaseSetup.d.ts +3 -0
- package/dist/databases/DatabaseSetup.d.ts.map +1 -0
- package/dist/endpoints/createEndpoint.d.ts +4 -0
- package/dist/endpoints/createEndpoint.d.ts.map +1 -0
- package/dist/endpoints/getEffectiveOperationId.d.ts +19 -0
- package/dist/endpoints/getEffectiveOperationId.d.ts.map +1 -0
- package/dist/env/Env.d.ts +2 -0
- package/dist/env/Env.d.ts.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1364 -0
- package/dist/launch/launchConfig.d.ts +18 -0
- package/dist/launch/launchConfig.d.ts.map +1 -0
- package/dist/logging/index.d.ts +9 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/sse/ConnectionManager.d.ts +23 -0
- package/dist/sse/ConnectionManager.d.ts.map +1 -0
- package/dist/stdin/StdinServer.d.ts +38 -0
- package/dist/stdin/StdinServer.d.ts.map +1 -0
- package/dist/web/EndpointListing.d.ts +3 -0
- package/dist/web/EndpointListing.d.ts.map +1 -0
- package/dist/web/ExpressAppSetup.d.ts +18 -0
- package/dist/web/ExpressAppSetup.d.ts.map +1 -0
- package/dist/web/ExpressEndpointSetup.d.ts +31 -0
- package/dist/web/ExpressEndpointSetup.d.ts.map +1 -0
- package/dist/web/SseResponse.d.ts +15 -0
- package/dist/web/SseResponse.d.ts.map +1 -0
- package/dist/web/ViteIntegration.d.ts +19 -0
- package/dist/web/ViteIntegration.d.ts.map +1 -0
- package/dist/web/corsMiddleware.d.ts +14 -0
- package/dist/web/corsMiddleware.d.ts.map +1 -0
- package/dist/web/localhostOnlyMiddleware.d.ts +3 -0
- package/dist/web/localhostOnlyMiddleware.d.ts.map +1 -0
- package/dist/web/openapi/OpenAPI.d.ts +37 -0
- package/dist/web/openapi/OpenAPI.d.ts.map +1 -0
- package/dist/web/openapi/validateServicesForOpenapi.d.ts +32 -0
- package/dist/web/openapi/validateServicesForOpenapi.d.ts.map +1 -0
- package/dist/web/requestContextMiddleware.d.ts +3 -0
- package/dist/web/requestContextMiddleware.d.ts.map +1 -0
- package/docs/authorization.md +281 -0
- package/docs/cors-setup.md +172 -0
- package/docs/creating-services.md +220 -0
- package/docs/database-setup.md +134 -0
- package/docs/endpoint-tools.md +1 -11
- package/docs/env-files.md +12 -1
- package/docs/error-handling.md +70 -0
- package/docs/getting-started.md +22 -12
- package/docs/launch-configuration.md +223 -0
- package/docs/overview.md +62 -0
- package/docs/server-setup.md +144 -0
- package/docs/source-directory-organization.md +115 -0
- package/docs/stdin-protocol.md +176 -0
- package/package.json +42 -9
- package/src/Errors.ts +120 -0
- package/src/Metrics.ts +53 -0
- package/src/RequestContext.ts +36 -0
- package/src/ServiceDefinition.ts +35 -0
- package/src/__tests__/Authorization.test.ts +350 -0
- package/src/__tests__/Errors.test.ts +378 -0
- package/src/__tests__/ListEndpoints.test.ts +98 -0
- package/src/__tests__/PrismApp.test.ts +274 -0
- package/src/__tests__/RequestContext.test.ts +295 -0
- package/src/__tests__/SseResponse.test.ts +189 -0
- package/src/__tests__/StdinServer.test.ts +304 -0
- package/src/__tests__/corsMiddleware.test.ts +293 -0
- package/src/__tests__/createEndpoint.test.ts +412 -0
- package/src/__tests__/validateApp.test.ts +206 -0
- package/src/app/PrismApp.ts +117 -0
- package/src/app/callEndpoint.ts +55 -0
- package/src/app/validateApp.ts +78 -0
- package/src/authorization/AuthSource.ts +14 -0
- package/src/authorization/Authorization.ts +78 -0
- package/src/authorization/Resource.ts +8 -0
- package/src/authorization/index.ts +4 -0
- package/src/databases/DatabaseInitializationOptions.ts +9 -0
- package/src/databases/DatabaseSetup.ts +19 -0
- package/src/endpoints/createEndpoint.ts +39 -0
- package/src/endpoints/getEffectiveOperationId.ts +90 -0
- package/src/env/Env.ts +23 -0
- package/src/index.ts +78 -0
- package/src/launch/launchConfig.ts +59 -0
- package/src/list-endpoints-command.ts +1 -1
- package/src/logging/index.ts +25 -0
- package/src/sse/ConnectionManager.ts +79 -0
- package/src/stdin/StdinServer.ts +129 -0
- package/src/web/EndpointListing.ts +166 -0
- package/src/web/ExpressAppSetup.ts +125 -0
- package/src/web/ExpressEndpointSetup.ts +178 -0
- package/src/web/SseResponse.ts +78 -0
- package/src/web/ViteIntegration.ts +72 -0
- package/src/web/__tests__/OpenAPI.invalidZodSchemas.test.ts +250 -0
- package/src/web/corsMiddleware.ts +63 -0
- package/src/web/localhostOnlyMiddleware.ts +19 -0
- package/src/web/openapi/OpenAPI.ts +248 -0
- package/src/web/openapi/validateServicesForOpenapi.ts +76 -0
- package/src/web/requestContextMiddleware.ts +25 -0
- package/.claude/settings.local.json +0 -20
- package/CHANGELOG +0 -28
- package/CLAUDE.md +0 -44
- package/build.mts +0 -8
- package/test/call-command.test.ts +0 -96
- package/test/generate-api-clients.test.ts +0 -33
- package/test/generate-api-clients.test.ts.disabled +0 -75
- package/tsconfig.json +0 -21
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cors-setup
|
|
3
|
+
description: How to configure CORS (Cross-Origin Resource Sharing) for your Prism API server
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# CORS Setup
|
|
7
|
+
|
|
8
|
+
This guide explains how to configure CORS (Cross-Origin Resource Sharing) for your Prism Framework API server.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
CORS controls which web origins can make requests to your API. The Prism Framework includes built-in CORS middleware that handles preflight requests and sets appropriate headers.
|
|
13
|
+
|
|
14
|
+
## Configuration Options
|
|
15
|
+
|
|
16
|
+
Pass a `corsConfig` object to `startServer` (see `server-setup` doc for full `startServer` details):
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
await startServer({
|
|
20
|
+
app,
|
|
21
|
+
port: 4000,
|
|
22
|
+
corsConfig: {
|
|
23
|
+
webBaseUrl: 'example.com',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### CorsConfig Interface
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
interface CorsConfig {
|
|
32
|
+
/** Base URL for web application (e.g., 'example.com' or 'https://example.com') */
|
|
33
|
+
webBaseUrl?: string;
|
|
34
|
+
|
|
35
|
+
/** Allow any localhost origin (http://localhost:*) for local development */
|
|
36
|
+
allowLocalhost?: boolean;
|
|
37
|
+
|
|
38
|
+
/** @deprecated Use `allowLocalhost` instead */
|
|
39
|
+
enableTestEndpoints?: boolean;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Option Details
|
|
44
|
+
|
|
45
|
+
### webBaseUrl
|
|
46
|
+
|
|
47
|
+
Specifies the production web domain that is allowed to make cross-origin requests.
|
|
48
|
+
|
|
49
|
+
- **Format**: Domain only (e.g., `'example.com'`) or full URL (e.g., `'https://example.com'`)
|
|
50
|
+
- **Behavior**: Allows requests from `https://{webBaseUrl}`
|
|
51
|
+
- **Typical usage**: Set via environment variable `WEB_BASE_URL`
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
corsConfig: {
|
|
55
|
+
webBaseUrl: process.env.WEB_BASE_URL, // e.g., 'myapp.example.com'
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### allowLocalhost
|
|
60
|
+
|
|
61
|
+
Enables CORS for any `http://localhost:*` origin (any port). This is the recommended setting for local development where the API server and frontend run on different ports.
|
|
62
|
+
|
|
63
|
+
- **Default**: `false`
|
|
64
|
+
- **When `true`**: Allows any localhost origin for development
|
|
65
|
+
- **When `false`**: Localhost origins are blocked
|
|
66
|
+
- **Typical usage**: Enable in development, disable in production
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
corsConfig: {
|
|
70
|
+
allowLocalhost: process.env.NODE_ENV !== 'production',
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### enableTestEndpoints (deprecated)
|
|
75
|
+
|
|
76
|
+
Use `allowLocalhost` instead. When both are set, `allowLocalhost` takes precedence.
|
|
77
|
+
|
|
78
|
+
## Environment Variable Pattern
|
|
79
|
+
|
|
80
|
+
A common pattern is to configure CORS via environment variables:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# .env (development)
|
|
84
|
+
WEB_BASE_URL=localhost:3000
|
|
85
|
+
ALLOW_LOCALHOST=true
|
|
86
|
+
|
|
87
|
+
# .env (production)
|
|
88
|
+
WEB_BASE_URL=myapp.example.com
|
|
89
|
+
ALLOW_LOCALHOST=false
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
corsConfig: {
|
|
94
|
+
webBaseUrl: process.env.WEB_BASE_URL,
|
|
95
|
+
allowLocalhost: process.env.ALLOW_LOCALHOST === 'true',
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## CORS Headers
|
|
100
|
+
|
|
101
|
+
The middleware automatically sets these headers on all responses:
|
|
102
|
+
|
|
103
|
+
| Header | Value |
|
|
104
|
+
|--------|-------|
|
|
105
|
+
| `Access-Control-Allow-Credentials` | `true` |
|
|
106
|
+
| `Access-Control-Allow-Methods` | `GET, POST, PUT, DELETE, OPTIONS, PATCH` |
|
|
107
|
+
| `Access-Control-Allow-Headers` | `Origin, X-Requested-With, Content-Type, Accept, Authorization, Cookie, Cache-Control` |
|
|
108
|
+
| `Access-Control-Max-Age` | `86400` (24 hours) |
|
|
109
|
+
|
|
110
|
+
## Preflight Requests
|
|
111
|
+
|
|
112
|
+
The middleware automatically handles OPTIONS preflight requests by returning HTTP 200 with the appropriate CORS headers.
|
|
113
|
+
|
|
114
|
+
## Security Notes
|
|
115
|
+
|
|
116
|
+
- **Whitelist-based**: Only explicitly allowed origins receive the `Access-Control-Allow-Origin` header
|
|
117
|
+
- **No wildcards**: The middleware uses specific domain matching instead of `*`
|
|
118
|
+
- **Credentials enabled**: Supports cookie-based authentication
|
|
119
|
+
- **Non-matching origins**: Requests from non-allowed origins do not receive CORS headers, causing the browser to block the response
|
|
120
|
+
|
|
121
|
+
## Examples
|
|
122
|
+
|
|
123
|
+
### Development Setup (localhost only)
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
await startServer({
|
|
127
|
+
app,
|
|
128
|
+
port: 4000,
|
|
129
|
+
corsConfig: {
|
|
130
|
+
allowLocalhost: true,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Production Setup
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
await startServer({
|
|
139
|
+
app,
|
|
140
|
+
port: 4000,
|
|
141
|
+
corsConfig: {
|
|
142
|
+
webBaseUrl: 'myapp.example.com',
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Combined Setup (development + production)
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
await startServer({
|
|
151
|
+
app,
|
|
152
|
+
port: parseInt(process.env.PRISM_API_PORT, 10),
|
|
153
|
+
corsConfig: {
|
|
154
|
+
webBaseUrl: process.env.WEB_BASE_URL,
|
|
155
|
+
allowLocalhost: process.env.ALLOW_LOCALHOST === 'true',
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Troubleshooting
|
|
161
|
+
|
|
162
|
+
### "No 'Access-Control-Allow-Origin' header" error
|
|
163
|
+
|
|
164
|
+
This means the requesting origin is not allowed. Check:
|
|
165
|
+
|
|
166
|
+
1. For localhost development: Ensure `allowLocalhost: true` is set
|
|
167
|
+
2. For production: Ensure `webBaseUrl` matches your frontend's domain
|
|
168
|
+
3. The request origin uses the correct protocol (https for production)
|
|
169
|
+
|
|
170
|
+
### Preflight requests failing
|
|
171
|
+
|
|
172
|
+
Ensure your server is running and the CORS middleware is configured. The middleware handles OPTIONS requests automatically.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: creating-services
|
|
3
|
+
description: How to create services with endpoints, middleware, and database schemas
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Creating Services
|
|
7
|
+
|
|
8
|
+
Services are the building blocks of a Prism Framework application. Each service is self-contained and can define endpoints, middleware, database schemas, and background jobs.
|
|
9
|
+
|
|
10
|
+
## ServiceDefinition Interface
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
interface ServiceDefinition {
|
|
14
|
+
/** Unique name identifying this service */
|
|
15
|
+
name: string;
|
|
16
|
+
|
|
17
|
+
/** API endpoints provided by this service */
|
|
18
|
+
endpoints?: EndpointDefinition[];
|
|
19
|
+
|
|
20
|
+
/** Express middleware scoped to specific paths */
|
|
21
|
+
middleware?: MiddlewareDefinition[];
|
|
22
|
+
|
|
23
|
+
/** SQLite database schemas, keyed by database name */
|
|
24
|
+
databases?: Record<string, {
|
|
25
|
+
statements: string[]; // SQL CREATE TABLE / CREATE INDEX statements
|
|
26
|
+
}>;
|
|
27
|
+
|
|
28
|
+
/** Async callback to start background jobs when the app initializes */
|
|
29
|
+
startJobs?: () => Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
All fields except `name` are optional. A service can provide any combination of endpoints, middleware, databases, and background jobs.
|
|
34
|
+
|
|
35
|
+
## Basic Service Structure
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { ServiceDefinition, createEndpoint } from '@facetlayer/prism-framework';
|
|
39
|
+
import { z } from 'zod';
|
|
40
|
+
|
|
41
|
+
export const definition: ServiceDefinition = {
|
|
42
|
+
name: 'my-service',
|
|
43
|
+
|
|
44
|
+
// API endpoints
|
|
45
|
+
endpoints: [
|
|
46
|
+
// ... endpoint definitions
|
|
47
|
+
],
|
|
48
|
+
|
|
49
|
+
// Optional middleware
|
|
50
|
+
middleware: [
|
|
51
|
+
// ... middleware definitions
|
|
52
|
+
],
|
|
53
|
+
|
|
54
|
+
// Optional database schemas
|
|
55
|
+
databases: {
|
|
56
|
+
user: {
|
|
57
|
+
statements: [
|
|
58
|
+
// SQL statements for user database
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Optional background jobs
|
|
64
|
+
startJobs: async () => {
|
|
65
|
+
// Initialize background tasks
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Defining Endpoints
|
|
71
|
+
|
|
72
|
+
Endpoints are defined with type safety using Zod schemas:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const GetUserRequest = z.object({
|
|
76
|
+
userId: z.string(),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const GetUserResponse = z.object({
|
|
80
|
+
id: z.string(),
|
|
81
|
+
email: z.string(),
|
|
82
|
+
name: z.string(),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const getUserEndpoint = createEndpoint({
|
|
86
|
+
method: 'GET',
|
|
87
|
+
path: '/users/:userId',
|
|
88
|
+
requestSchema: GetUserRequest,
|
|
89
|
+
responseSchema: GetUserResponse,
|
|
90
|
+
requires: ['authenticated-user'], // Optional requirements
|
|
91
|
+
handler: async (input) => {
|
|
92
|
+
// input is typed as z.infer<typeof GetUserRequest>
|
|
93
|
+
const user = await getUserById(input.userId);
|
|
94
|
+
return user; // Must match GetUserResponse schema
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Endpoint Methods
|
|
100
|
+
|
|
101
|
+
Supported HTTP methods:
|
|
102
|
+
- `GET`
|
|
103
|
+
- `POST`
|
|
104
|
+
- `PUT`
|
|
105
|
+
- `DELETE`
|
|
106
|
+
- `PATCH`
|
|
107
|
+
|
|
108
|
+
### Request Data
|
|
109
|
+
|
|
110
|
+
The framework automatically combines data from:
|
|
111
|
+
- Request body (`req.body`)
|
|
112
|
+
- URL parameters (`req.params`)
|
|
113
|
+
- Query parameters (`req.query`)
|
|
114
|
+
|
|
115
|
+
All are merged and validated against the `requestSchema`.
|
|
116
|
+
|
|
117
|
+
### Requirements
|
|
118
|
+
|
|
119
|
+
The `requires` array can specify:
|
|
120
|
+
- `'authenticated-user'` - Requires an authenticated user (checks for user resource in context)
|
|
121
|
+
|
|
122
|
+
Applications can extend this by providing custom middleware or handlers.
|
|
123
|
+
|
|
124
|
+
## Server-Sent Events (SSE)
|
|
125
|
+
|
|
126
|
+
For streaming responses, return an object with a `startSse` method:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
createEndpoint({
|
|
130
|
+
method: 'GET',
|
|
131
|
+
path: '/stream',
|
|
132
|
+
handler: async () => {
|
|
133
|
+
return {
|
|
134
|
+
startSse: (sse: SseResponse) => {
|
|
135
|
+
// Send events
|
|
136
|
+
sse.send({ message: 'Hello' });
|
|
137
|
+
sse.send({ message: 'World' });
|
|
138
|
+
|
|
139
|
+
// Close when done
|
|
140
|
+
sse.close();
|
|
141
|
+
|
|
142
|
+
// Or handle client disconnect
|
|
143
|
+
sse.onClose(() => {
|
|
144
|
+
console.log('Client disconnected');
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Adding Middleware
|
|
153
|
+
|
|
154
|
+
Middleware can be path-specific:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
export const definition: ServiceDefinition = {
|
|
158
|
+
name: 'my-service',
|
|
159
|
+
middleware: [
|
|
160
|
+
{
|
|
161
|
+
path: '/admin/*',
|
|
162
|
+
handler: (req, res, next) => {
|
|
163
|
+
// Check admin permissions
|
|
164
|
+
if (!isAdmin(req)) {
|
|
165
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
166
|
+
}
|
|
167
|
+
next();
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Database Schemas
|
|
175
|
+
|
|
176
|
+
Define database schemas per database:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
export const definition: ServiceDefinition = {
|
|
180
|
+
name: 'users',
|
|
181
|
+
databases: {
|
|
182
|
+
user: {
|
|
183
|
+
statements: [
|
|
184
|
+
`CREATE TABLE IF NOT EXISTS users (
|
|
185
|
+
id INTEGER PRIMARY KEY,
|
|
186
|
+
email TEXT UNIQUE NOT NULL,
|
|
187
|
+
name TEXT,
|
|
188
|
+
created_at DATETIME DEFAULT (datetime('now', 'utc') || 'Z')
|
|
189
|
+
)`,
|
|
190
|
+
`CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)`,
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
project: {
|
|
194
|
+
statements: [
|
|
195
|
+
// Project-specific tables
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Background Jobs
|
|
203
|
+
|
|
204
|
+
Services can start background jobs when the application initializes:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
export const definition: ServiceDefinition = {
|
|
208
|
+
name: 'cleanup',
|
|
209
|
+
startJobs: async () => {
|
|
210
|
+
// Run periodic cleanup
|
|
211
|
+
setInterval(async () => {
|
|
212
|
+
await cleanupOldData();
|
|
213
|
+
}, 60 * 60 * 1000); // Every hour
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Error Handling
|
|
219
|
+
|
|
220
|
+
Use the built-in HTTP error classes to return proper status codes from handlers. See the `error-handling` doc for the full list of available error classes and usage patterns.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: database-setup
|
|
3
|
+
description: How to set up a local SQLite database for your backend application
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Database Setup
|
|
7
|
+
|
|
8
|
+
This guide covers setting up a local SQLite database for your Prism Framework backend application using the `@facetlayer/sqlite-wrapper` library.
|
|
9
|
+
|
|
10
|
+
## Directory Structure
|
|
11
|
+
|
|
12
|
+
Set up the database as a backend "service" with its own folder inside the `./src` directory:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
src/
|
|
16
|
+
├── _main/
|
|
17
|
+
│ └── index.ts
|
|
18
|
+
├── user-database/
|
|
19
|
+
│ └── db.ts
|
|
20
|
+
└── other-services/
|
|
21
|
+
└── ...
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Install the required packages:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm add @facetlayer/sqlite-wrapper better-sqlite3
|
|
30
|
+
pnpm add -D @types/better-sqlite3
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Database Configuration
|
|
34
|
+
|
|
35
|
+
Create a database module at `src/user-database/db.ts`:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { DatabaseLoader, SqliteDatabase } from '@facetlayer/sqlite-wrapper';
|
|
39
|
+
import { toConsoleLog } from '@facetlayer/streams';
|
|
40
|
+
import BetterSqliteDatabase from 'better-sqlite3';
|
|
41
|
+
import Path from 'path';
|
|
42
|
+
|
|
43
|
+
let db: SqliteDatabase | null = null;
|
|
44
|
+
|
|
45
|
+
const schema = {
|
|
46
|
+
name: 'MyAppDatabase',
|
|
47
|
+
statements: [
|
|
48
|
+
`create table users (
|
|
49
|
+
id integer primary key autoincrement,
|
|
50
|
+
email text not null unique,
|
|
51
|
+
created_at integer not null default (strftime('%s', 'now'))
|
|
52
|
+
)`,
|
|
53
|
+
`create index idx_users_email on users(email)`,
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getDatabase(): SqliteDatabase {
|
|
58
|
+
const DATABASE_DIR = process.env.DATABASE_DIR;
|
|
59
|
+
if (!DATABASE_DIR) {
|
|
60
|
+
throw new Error('DATABASE_DIR is not set');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!db) {
|
|
64
|
+
const loader = new DatabaseLoader({
|
|
65
|
+
filename: Path.join(DATABASE_DIR, 'app.db'),
|
|
66
|
+
loadDatabase: (filename) => new BetterSqliteDatabase(filename),
|
|
67
|
+
migrationBehavior: 'safe-upgrades',
|
|
68
|
+
schema,
|
|
69
|
+
logs: toConsoleLog('[database]'),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
db = loader.load();
|
|
73
|
+
}
|
|
74
|
+
return db;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Environment Variable
|
|
79
|
+
|
|
80
|
+
The `DATABASE_DIR` environment variable must be set to specify where the database file will be stored.
|
|
81
|
+
|
|
82
|
+
For local development, create a `.env` file:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
DATABASE_DIR=./data
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Make sure the directory exists:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
mkdir -p ./data
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Using the Database
|
|
95
|
+
|
|
96
|
+
Import and use the database in your services:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { getDatabase } from '../user-database/db';
|
|
100
|
+
|
|
101
|
+
export async function createUser(email: string) {
|
|
102
|
+
const db = getDatabase();
|
|
103
|
+
const stmt = db.prepare('INSERT INTO users (email) VALUES (?)');
|
|
104
|
+
const result = stmt.run(email);
|
|
105
|
+
return result.lastInsertRowid;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function getUserByEmail(email: string) {
|
|
109
|
+
const db = getDatabase();
|
|
110
|
+
const stmt = db.prepare('SELECT * FROM users WHERE email = ?');
|
|
111
|
+
return stmt.get(email);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Schema Migrations
|
|
116
|
+
|
|
117
|
+
The `DatabaseLoader` handles schema migrations automatically with the `migrationBehavior` option:
|
|
118
|
+
|
|
119
|
+
- `'safe-upgrades'` - Automatically applies safe migrations (adding tables, columns, indexes)
|
|
120
|
+
- `'strict'` - Only creates schema on first run, no automatic migrations
|
|
121
|
+
|
|
122
|
+
For complex migrations, add new statements to the `statements` array. The loader will apply them in order.
|
|
123
|
+
|
|
124
|
+
## Multiple Databases
|
|
125
|
+
|
|
126
|
+
If your application needs multiple databases, create separate modules with different schemas and filenames:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// src/user-database/db.ts
|
|
130
|
+
export function getUserDatabase(): SqliteDatabase { ... }
|
|
131
|
+
|
|
132
|
+
// src/project-database/db.ts
|
|
133
|
+
export function getProjectDatabase(): SqliteDatabase { ... }
|
|
134
|
+
```
|
package/docs/endpoint-tools.md
CHANGED
|
@@ -43,8 +43,6 @@ Calls an endpoint on a running server.
|
|
|
43
43
|
|
|
44
44
|
### Basic Usage
|
|
45
45
|
|
|
46
|
-
**Important:** Do not use the `/api` prefix in endpoint paths. Use the path directly as defined in the endpoint.
|
|
47
|
-
|
|
48
46
|
```bash
|
|
49
47
|
# GET request
|
|
50
48
|
prism call /users
|
|
@@ -101,15 +99,7 @@ Response: {"id":"123","name":"John","email":"john@example.com"}
|
|
|
101
99
|
|
|
102
100
|
1. **Check the server is running** - Start your API server
|
|
103
101
|
2. **Verify PRISM_API_PORT** - Ensure `.env` contains `PRISM_API_PORT` matching your server port
|
|
104
|
-
3. **Check dotenv is loaded** - Your server must load the `.env` file
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
import { config } from 'dotenv';
|
|
108
|
-
config({ path: '.env' });
|
|
109
|
-
|
|
110
|
-
const PORT = parseInt(process.env.PRISM_API_PORT, 10);
|
|
111
|
-
await startServer({ app, port: PORT });
|
|
112
|
-
```
|
|
102
|
+
3. **Check dotenv is loaded** - Your server must load the `.env` file (see `env-files` doc)
|
|
113
103
|
|
|
114
104
|
### "Endpoint not found"
|
|
115
105
|
|
package/docs/env-files.md
CHANGED
|
@@ -53,6 +53,17 @@ const apiUrl = import.meta.env.VITE_API_URL;
|
|
|
53
53
|
|
|
54
54
|
See the `vite-setup` doc in `@facetlayer/prism-framework-ui` for more details.
|
|
55
55
|
|
|
56
|
+
## Loading .env in the Backend
|
|
57
|
+
|
|
58
|
+
Use `dotenv` to load your `.env` file early in your server entry point:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { config } from 'dotenv';
|
|
62
|
+
config({ path: '.env' });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This should be called before accessing any `process.env` values. The `dotenv` package is included when you install `@facetlayer/prism-framework`'s recommended dependencies (see `getting-started` doc).
|
|
66
|
+
|
|
56
67
|
# Port assignment
|
|
57
68
|
|
|
58
69
|
It's recommended to use the `@facetlayer/port-assignment` tool if you need to assign new unique port numbers.
|
|
@@ -61,4 +72,4 @@ Example:
|
|
|
61
72
|
|
|
62
73
|
npx @facetlayer/port-assignment claim --name <project name>
|
|
63
74
|
|
|
64
|
-
Run `npx @facetlayer/port-
|
|
75
|
+
Run `npx @facetlayer/port-assignment list-docs` for more documentation.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: error-handling
|
|
3
|
+
description: HTTP error classes for returning proper status codes from endpoint handlers
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Error Handling
|
|
7
|
+
|
|
8
|
+
Prism Framework provides built-in HTTP error classes. Throw these from endpoint handlers or middleware to return the appropriate status code and error message.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { NotFoundError, BadRequestError } from '@facetlayer/prism-framework';
|
|
14
|
+
|
|
15
|
+
handler: async (input) => {
|
|
16
|
+
if (!input.userId) {
|
|
17
|
+
throw new BadRequestError('User ID is required');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const user = await getUserById(input.userId);
|
|
21
|
+
if (!user) {
|
|
22
|
+
throw new NotFoundError('User not found');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return user;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Available Error Classes
|
|
30
|
+
|
|
31
|
+
| Class | Status Code |
|
|
32
|
+
|-------|-------------|
|
|
33
|
+
| `BadRequestError` | 400 |
|
|
34
|
+
| `UnauthorizedError` | 401 |
|
|
35
|
+
| `ForbiddenError` | 403 |
|
|
36
|
+
| `NotFoundError` | 404 |
|
|
37
|
+
| `ConflictError` | 409 |
|
|
38
|
+
| `ValidationError` | 422 |
|
|
39
|
+
| `NotImplementedError` | 501 |
|
|
40
|
+
| `ServiceUnavailableError` | 503 |
|
|
41
|
+
| `HttpError` | Custom status code |
|
|
42
|
+
|
|
43
|
+
## Custom Status Codes
|
|
44
|
+
|
|
45
|
+
Use `HttpError` for status codes not covered by the named classes:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { HttpError } from '@facetlayer/prism-framework';
|
|
49
|
+
|
|
50
|
+
throw new HttpError(429, 'Too many requests');
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Authorization Errors
|
|
54
|
+
|
|
55
|
+
For authorization-related errors, use `UnauthorizedError` (not logged in) and `ForbiddenError` (logged in but not allowed):
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { UnauthorizedError, ForbiddenError } from '@facetlayer/prism-framework';
|
|
59
|
+
|
|
60
|
+
const user = context.auth.getResource('user');
|
|
61
|
+
if (!user) {
|
|
62
|
+
throw new UnauthorizedError('Authentication required');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!context.auth.hasPermission('delete:projects')) {
|
|
66
|
+
throw new ForbiddenError('Insufficient permissions');
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
See the `authorization` doc for more on how auth and permissions work.
|
package/docs/getting-started.md
CHANGED
|
@@ -12,14 +12,11 @@ description: Guide for setting up a new Prism Framework project with type-safe A
|
|
|
12
12
|
This includes the following NPM libraries:
|
|
13
13
|
|
|
14
14
|
### `@facetlayer/prism-framework`
|
|
15
|
-
- Base library with
|
|
16
|
-
-
|
|
17
|
-
- First place to look for documentation (`prism list-docs`)
|
|
18
|
-
|
|
19
|
-
### `@facetlayer/prism-framework-api`
|
|
20
|
-
- Backend API framework
|
|
21
|
-
- Can be hosted on HTTP with Express.js
|
|
15
|
+
- Base library with backend API framework, CLI tooling, and development tools
|
|
16
|
+
- Includes the server framework (Express.js), endpoint definitions, authorization, and more
|
|
22
17
|
- Also supports other launch methods such as IPC for Electron
|
|
18
|
+
- Should be installed at the top level
|
|
19
|
+
- First place to look for documentation (`prism list-docs`)
|
|
23
20
|
|
|
24
21
|
### `@facetlayer/prism-framework-ui`
|
|
25
22
|
- Helpers for React-based frontend web apps
|
|
@@ -41,7 +38,7 @@ Some ways to set up the repo for a Prism project:
|
|
|
41
38
|
### Option 1: API in separate directory
|
|
42
39
|
|
|
43
40
|
- `./api` - Backend API
|
|
44
|
-
- `./api/package.json` - Contains prism-framework
|
|
41
|
+
- `./api/package.json` - Contains prism-framework
|
|
45
42
|
- `./api/src/` - Backend service implementation
|
|
46
43
|
- `./web` or `./ui` - Frontend web app
|
|
47
44
|
- `./web/package.json` - Contains Next.js or Vite, and prism-framework-ui
|
|
@@ -49,7 +46,7 @@ Some ways to set up the repo for a Prism project:
|
|
|
49
46
|
|
|
50
47
|
### Option 2: API in top level directory
|
|
51
48
|
|
|
52
|
-
- `./package.json` - Contains prism-framework
|
|
49
|
+
- `./package.json` - Contains prism-framework
|
|
53
50
|
- `./src` - Backend API source code
|
|
54
51
|
- `./web` or `./ui` - Frontend web app
|
|
55
52
|
- `./web/package.json` - Contains Next.js or Vite, and prism-framework-ui
|
|
@@ -69,7 +66,20 @@ Some ways to set up the repo for a Prism project:
|
|
|
69
66
|
|
|
70
67
|
The top level of the project should have these dependencies:
|
|
71
68
|
|
|
72
|
-
`pnpm add typescript dotenv @facetlayer/prism-framework`
|
|
69
|
+
`pnpm add typescript dotenv @facetlayer/prism-framework zod@^4`
|
|
70
|
+
|
|
71
|
+
## Environment setup
|
|
72
|
+
|
|
73
|
+
Create a `.env` file next to your backend code with at minimum:
|
|
74
|
+
|
|
75
|
+
PRISM_API_PORT=<port number>
|
|
76
|
+
DATABASE_DIR=data
|
|
77
|
+
|
|
78
|
+
Use `@facetlayer/port-assignment` to claim a unique port for your project:
|
|
79
|
+
|
|
80
|
+
npx @facetlayer/port-assignment claim --name <project name>
|
|
81
|
+
|
|
82
|
+
See the `env-files` doc (`prism get-doc env-files`) for the full list of recommended environment variables for both backend and frontend.
|
|
73
83
|
|
|
74
84
|
## Local service management
|
|
75
85
|
|
|
@@ -81,6 +91,6 @@ Examples:
|
|
|
81
91
|
`candle list-docs`
|
|
82
92
|
|
|
83
93
|
Set up services in the .candle.json file:
|
|
84
|
-
`candle add-service api "node --watch src/_main/api.ts" --root ./api
|
|
85
|
-
`candle add-service web "pnpm dev" --root ./web
|
|
94
|
+
`candle add-service api "node --watch src/_main/api.ts" --root ./api`
|
|
95
|
+
`candle add-service web "pnpm dev" --root ./web`
|
|
86
96
|
|