schematic-pg 0.1.14 → 0.1.16

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.
@@ -1,4 +1,4 @@
1
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
1
+ import { mkdir, readdir, readFile, unlink, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { parse } from '../schema-dsl/index.js';
4
4
  import { generateApiFiles } from '../api-generator/index.js';
@@ -38,10 +38,17 @@ export async function generateApi(schemaPath) {
38
38
  await writeFile(path.join(schemasDir, 'validation.ts'), files.validation, 'utf8');
39
39
  await writeFile(path.join(outputDir, 'openapi.ts'), files.openapiTs, 'utf8');
40
40
  await writeFile(path.join(outputDir, 'openapi.json'), files.openapiJson, 'utf8');
41
- for (const [fileName, content] of files.routes) {
41
+ await syncGeneratedRouteFiles(routesDir, files.routes);
42
+ console.log(`Generated API files in ${outputDir}`);
43
+ }
44
+ export async function syncGeneratedRouteFiles(routesDir, routes) {
45
+ for (const [fileName, content] of routes) {
42
46
  await writeFile(path.join(routesDir, fileName), content, 'utf8');
43
47
  }
44
- console.log(`Generated API files in ${outputDir}`);
48
+ const existing = await readdir(routesDir);
49
+ await Promise.all(existing
50
+ .filter((fileName) => fileName.endsWith('.ts') && !routes.has(fileName))
51
+ .map((fileName) => unlink(path.join(routesDir, fileName))));
45
52
  }
46
53
  export async function generateAll(schemaPath) {
47
54
  const resolvedSchemaPath = resolveSchemaPath(schemaPath);
@@ -77,7 +77,8 @@ models {
77
77
 
78
78
  orders: Order[]
79
79
 
80
- @policy(role: USER, allow: [select, update], where: "id = {{auth.user.id}}")
80
+ @rest(except: [create, update, delete])
81
+ @policy(role: USER, allow: [select], where: "id = {{auth.user.id}}")
81
82
  @policy(role: ADMIN, allow: all)
82
83
 
83
84
  @@index(fields: [role])
@@ -93,6 +94,7 @@ models {
93
94
  **Key concepts:**
94
95
 
95
96
  - **Relations:** Put `@relation(fields: [...], references: [...])` on the FK-owning side. The inverse side is inferred.
97
+ - **REST surface:** `@rest(only: [...])`, `@rest(except: [...])`, or `@rest(false)` controls which CRUD handlers are generated (`list`/`get`/`create`/`update`/`delete`).
96
98
  - **Policies:** `@policy(role: ..., allow: [select|insert|update|delete|all], where: "...")` — `where` supports `{{auth.user.id}}`.
97
99
  - **Validation:** `@regex(...)`, `@range(min: ..., max: ...)` flow into generated Zod schemas.
98
100
  - **Indexes / triggers:** `@@index(...)`, `@@trigger { timing, event, level, execute: """...""" }`.
@@ -225,8 +227,9 @@ Also available: `ForeignKeyConstraintError`, `DatabaseError`.
225
227
  | Location | `src/routes/**/*.ts` |
226
228
  | Export | `export default router` (`Hono<AppEnv>`) |
227
229
  | Mount path | File path relative to `src/routes/` |
230
+ | Same-path overlay | `src/routes/users.ts` merges into `generated/routes/users.ts` |
228
231
 
229
- `src/routes/health.ts` → `GET /health`. Regenerate after adding files.
232
+ `src/routes/health.ts` → `GET /health` (standalone). When the file name matches a model route (`users.ts`), it is imported into the generated model router after remaining `@rest` handlers. Regenerate after adding files.
230
233
 
231
234
  ```typescript
232
235
  import { Hono } from 'hono';
@@ -276,6 +279,7 @@ schematic-pg hooks:add [--model X] # scaffold src/hooks/{Model}.ts
276
279
  - Unauthenticated requests default to `{ role: 'PUBLIC' }`.
277
280
  - `@policy` `allow` maps to HTTP: GET→select, POST→insert, PUT→update, DELETE→delete.
278
281
  - Row-level `where` is injected on read/update/delete; POST checks permission only.
282
+ - `@rest` controls which handlers exist; disabled methods return `404` and are omitted from OpenAPI.
279
283
 
280
284
  ## Generated Outputs (Read-Only)
281
285
 
@@ -1,5 +1,5 @@
1
1
  export declare const AGENTS_TEMPLATE: string;
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 @policy(role: USER, allow: [select, update], where: \"id = {{auth.user.id}}\")\n @policy(role: ADMIN, allow: all)\n }\n}\n";
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
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\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";
@@ -21,7 +21,8 @@ models {
21
21
  passwordHash: VARCHAR(255)? @omit @unfilterable
22
22
  createdAt: TIMESTAMP @default(now())
23
23
 
24
- @policy(role: USER, allow: [select, update], where: "id = {{auth.user.id}}")
24
+ @rest(except: [create, update, delete])
25
+ @policy(role: USER, allow: [select], where: "id = {{auth.user.id}}")
25
26
  @policy(role: ADMIN, allow: all)
26
27
  }
27
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schematic-pg",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Single-file backend framework for PostgreSQL and Node.js",
5
5
  "type": "module",
6
6
  "license": "MIT",