schematic-pg 0.1.17 → 0.1.18
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
CHANGED
|
@@ -456,11 +456,12 @@ Generated code imports the runtime from the `schematic-pg` package (`schematic-p
|
|
|
456
456
|
| `AUTH_ACCESS_TOKEN_TTL` | `1h` | Access token lifetime (`15m`, `1h`, or seconds) |
|
|
457
457
|
| `JWT_ROLE_CLAIM` | `role` | JWT claim mapped to `auth.role` |
|
|
458
458
|
| `JWT_USER_ID_CLAIM` | `sub` | JWT claim mapped to `auth.user.id` |
|
|
459
|
-
| `CORS_ORIGIN` | — (disabled) | Allowed browser origins. Unset disables CORS. Use `*` for any origin, or a comma-separated list (`http://localhost:5173,https://app.example.com`) |
|
|
459
|
+
| `CORS_ORIGIN` | — (disabled) | Allowed browser origins. Unset disables CORS. Use `*` for any origin (no cookies), or a comma-separated list (`http://localhost:5173,https://app.example.com`). Concrete origins enable credentialed CORS |
|
|
460
|
+
| `CORS_ALLOW_HEADERS` | — | Extra allowed request headers (comma-separated), merged with `Authorization`, `Content-Type`, `X-CSRF-Token` |
|
|
460
461
|
|
|
461
462
|
Set these in `.env` before running `dev`, `start`, or `db:bootstrap`.
|
|
462
463
|
|
|
463
|
-
Browser frontends on another origin need `CORS_ORIGIN`. The generated app reads it at runtime (no regenerate). Preflight `OPTIONS` is handled automatically;
|
|
464
|
+
Browser frontends on another origin need `CORS_ORIGIN`. The generated app reads it at runtime (no regenerate). Preflight `OPTIONS` is handled automatically; concrete origins enable cookies via `credentials: 'include'`. See [CORS](docs/rest-api.md#cors).
|
|
464
465
|
|
|
465
466
|
---
|
|
466
467
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { MiddlewareHandler } from 'hono';
|
|
2
2
|
import type { AppEnv } from '../types.js';
|
|
3
3
|
export declare function parseCorsOrigin(raw?: string | undefined): string | string[] | null;
|
|
4
|
+
/** Extra headers from `CORS_ALLOW_HEADERS` (comma-separated). Does not include defaults. */
|
|
5
|
+
export declare function parseCorsAllowHeaders(raw?: string | undefined): string[];
|
|
4
6
|
export declare function createCorsMiddleware(options?: {
|
|
5
7
|
origin?: string | string[] | null;
|
|
8
|
+
allowHeaders?: string[];
|
|
6
9
|
}): MiddlewareHandler<AppEnv>;
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { cors } from 'hono/cors';
|
|
2
|
-
const
|
|
2
|
+
const DEFAULT_CORS_ALLOW_HEADERS = ['Authorization', 'Content-Type', 'X-CSRF-Token'];
|
|
3
3
|
const CORS_ALLOW_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'];
|
|
4
4
|
const CORS_MAX_AGE_SECONDS = 86400;
|
|
5
|
+
function parseCommaSeparatedList(raw) {
|
|
6
|
+
const trimmed = raw?.trim() ?? '';
|
|
7
|
+
if (!trimmed) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
return trimmed
|
|
11
|
+
.split(',')
|
|
12
|
+
.map((item) => item.trim())
|
|
13
|
+
.filter((item) => item.length > 0);
|
|
14
|
+
}
|
|
5
15
|
export function parseCorsOrigin(raw = process.env.CORS_ORIGIN) {
|
|
6
16
|
const trimmed = raw?.trim() ?? '';
|
|
7
17
|
if (!trimmed) {
|
|
@@ -10,15 +20,36 @@ export function parseCorsOrigin(raw = process.env.CORS_ORIGIN) {
|
|
|
10
20
|
if (trimmed === '*') {
|
|
11
21
|
return '*';
|
|
12
22
|
}
|
|
13
|
-
const origins = trimmed
|
|
14
|
-
.split(',')
|
|
15
|
-
.map((origin) => origin.trim())
|
|
16
|
-
.filter((origin) => origin.length > 0);
|
|
23
|
+
const origins = parseCommaSeparatedList(trimmed);
|
|
17
24
|
if (origins.length === 0) {
|
|
18
25
|
return null;
|
|
19
26
|
}
|
|
20
27
|
return origins.length === 1 ? origins[0] : origins;
|
|
21
28
|
}
|
|
29
|
+
/** Extra headers from `CORS_ALLOW_HEADERS` (comma-separated). Does not include defaults. */
|
|
30
|
+
export function parseCorsAllowHeaders(raw = process.env.CORS_ALLOW_HEADERS) {
|
|
31
|
+
return parseCommaSeparatedList(raw);
|
|
32
|
+
}
|
|
33
|
+
function resolveAllowHeaders(optionsAllowHeaders) {
|
|
34
|
+
if (optionsAllowHeaders !== undefined) {
|
|
35
|
+
return optionsAllowHeaders;
|
|
36
|
+
}
|
|
37
|
+
const extras = parseCorsAllowHeaders();
|
|
38
|
+
if (extras.length === 0) {
|
|
39
|
+
return DEFAULT_CORS_ALLOW_HEADERS;
|
|
40
|
+
}
|
|
41
|
+
const seen = new Set(DEFAULT_CORS_ALLOW_HEADERS.map((header) => header.toLowerCase()));
|
|
42
|
+
const merged = [...DEFAULT_CORS_ALLOW_HEADERS];
|
|
43
|
+
for (const header of extras) {
|
|
44
|
+
const key = header.toLowerCase();
|
|
45
|
+
if (seen.has(key)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
seen.add(key);
|
|
49
|
+
merged.push(header);
|
|
50
|
+
}
|
|
51
|
+
return merged;
|
|
52
|
+
}
|
|
22
53
|
export function createCorsMiddleware(options = {}) {
|
|
23
54
|
const origin = options.origin !== undefined ? options.origin : parseCorsOrigin();
|
|
24
55
|
if (origin == null) {
|
|
@@ -26,9 +57,12 @@ export function createCorsMiddleware(options = {}) {
|
|
|
26
57
|
await next();
|
|
27
58
|
};
|
|
28
59
|
}
|
|
60
|
+
const allowHeaders = resolveAllowHeaders(options.allowHeaders);
|
|
61
|
+
const credentials = origin !== '*';
|
|
29
62
|
return cors({
|
|
30
63
|
origin,
|
|
31
|
-
|
|
64
|
+
credentials,
|
|
65
|
+
allowHeaders,
|
|
32
66
|
allowMethods: CORS_ALLOW_METHODS,
|
|
33
67
|
maxAge: CORS_MAX_AGE_SECONDS,
|
|
34
68
|
});
|
|
@@ -59,7 +59,8 @@ npx schematic-pg dev
|
|
|
59
59
|
| `JWT_SECRET` | HMAC secret for Bearer JWT auth |
|
|
60
60
|
| `JWT_ROLE_CLAIM` | JWT claim for role (default `role`) |
|
|
61
61
|
| `JWT_USER_ID_CLAIM` | JWT claim for user id (default `sub`) |
|
|
62
|
-
| `CORS_ORIGIN` | Allowed browser origins (`*` or comma-separated). Unset disables CORS |
|
|
62
|
+
| `CORS_ORIGIN` | Allowed browser origins (`*` or comma-separated). Unset disables CORS. Concrete origins enable credentialed CORS (cookies); `*` does not |
|
|
63
|
+
| `CORS_ALLOW_HEADERS` | Extra allowed request headers (comma-separated), merged with Authorization, Content-Type, X-CSRF-Token |
|
|
63
64
|
|
|
64
65
|
## Schema DSL Essentials
|
|
65
66
|
|
package/dist/cli/templates.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const AGENTS_TEMPLATE: string;
|
|
2
2
|
export declare const APP_SCHEMA_TEMPLATE = "extensions {\n\n}\n\nenums {\n UserRole { ADMIN, USER }\n}\n\nmodels {\n model User {\n id: UUID @id @default(gen_random_uuid())\n email: VARCHAR(255) @unique @regex(pattern: \"^[\\w.-]+@[\\w.-]+\\.\\w+$\", message: \"Invalid email address\")\n name: VARCHAR(150)?\n role: UserRole @default(USER)\n passwordHash: VARCHAR(255)? @omit @unfilterable\n createdAt: TIMESTAMP @default(now())\n\n @rest(except: [create, update, delete])\n @policy(role: USER, allow: [select], where: \"id = {{auth.user.id}}\")\n @policy(role: ADMIN, allow: all)\n }\n}\n";
|
|
3
|
-
export declare const ENV_TEMPLATE = "DATABASE_URL=postgresql://postgrest:postgrest@localhost:5432/postgrest\nJWT_SECRET=\nAUTH_PEPPER=\nAUTH_ACCESS_TOKEN_TTL=1h\nJWT_ROLE_CLAIM=role\nJWT_USER_ID_CLAIM=sub\nCORS_ORIGIN=\n";
|
|
3
|
+
export declare const ENV_TEMPLATE = "DATABASE_URL=postgresql://postgrest:postgrest@localhost:5432/postgrest\nJWT_SECRET=\nAUTH_PEPPER=\nAUTH_ACCESS_TOKEN_TTL=1h\nJWT_ROLE_CLAIM=role\nJWT_USER_ID_CLAIM=sub\nCORS_ORIGIN=\nCORS_ALLOW_HEADERS=\n";
|
|
4
4
|
export declare const GITIGNORE_TEMPLATE = "node_modules/\ndist/\n.env\ndocker_data/\n.DS_Store\n*.log\nnpm-debug.log*\n";
|
|
5
5
|
export declare const DOCKER_COMPOSE_TEMPLATE = "services:\n postgres:\n image: postgres:18.4-bookworm\n container_name: schematic-pg\n restart: unless-stopped\n ports:\n - \"5432:5432\"\n environment:\n POSTGRES_USER: postgrest\n POSTGRES_PASSWORD: postgrest\n POSTGRES_DB: postgrest\n volumes:\n - ./docker_data/postgres:/var/lib/postgresql\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U postgrest -d postgrest\"]\n interval: 5s\n timeout: 5s\n retries: 5\n";
|
|
6
6
|
export declare const TSCONFIG_TEMPLATE = "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"NodeNext\",\n \"moduleResolution\": \"NodeNext\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"outDir\": \"dist\",\n \"rootDir\": \".\"\n },\n \"include\": [\"generated/**/*\", \"src/**/*\"]\n}\n";
|
package/dist/cli/templates.js
CHANGED