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; `Authorization` and `Content-Type` are allowed. See [CORS](docs/rest-api.md#cors).
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 CORS_ALLOW_HEADERS = ['Authorization', 'Content-Type'];
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
- allowHeaders: CORS_ALLOW_HEADERS,
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
 
@@ -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";
@@ -34,6 +34,7 @@ AUTH_ACCESS_TOKEN_TTL=1h
34
34
  JWT_ROLE_CLAIM=role
35
35
  JWT_USER_ID_CLAIM=sub
36
36
  CORS_ORIGIN=
37
+ CORS_ALLOW_HEADERS=
37
38
  `;
38
39
  export const GITIGNORE_TEMPLATE = `node_modules/
39
40
  dist/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schematic-pg",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Single-file backend framework for PostgreSQL and Node.js",
5
5
  "type": "module",
6
6
  "license": "MIT",