schematic-pg 0.1.0 → 0.1.2

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
@@ -196,7 +196,6 @@ Install the CLI and scaffold a new project:
196
196
  ```bash
197
197
  npx schematic-pg init my-app
198
198
  cd my-app
199
- npm install
200
199
  ```
201
200
 
202
201
  Edit `app.schema`, then generate code and start the API:
@@ -261,7 +260,7 @@ The `schematic-pg` binary is the primary interface. Each command accepts an opti
261
260
  ### Project setup
262
261
 
263
262
  ```bash
264
- schematic-pg init [dir] # Scaffold a new project (default: current directory)
263
+ schematic-pg init [dir] [--skip-install] # Scaffold a new project (runs npm install by default)
265
264
  ```
266
265
 
267
266
  ### Code generation
@@ -0,0 +1,6 @@
1
+ export type PolicyOperation = 'select' | 'insert' | 'update' | 'delete';
2
+ export interface NormalizedPolicy {
3
+ role: string;
4
+ operations: PolicyOperation[] | 'all';
5
+ where?: string;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,9 @@
1
1
  import type { WhereInput } from '../../db/where-translator.js';
2
- import { type NormalizedPolicy, type PolicyOperation } from 'generated/policies.js';
2
+ import type { NormalizedPolicy, PolicyOperation } from './policy-types.js';
3
3
  import type { AuthContext } from './types.js';
4
+ export type { NormalizedPolicy, PolicyOperation } from './policy-types.js';
4
5
  export { ForbiddenError, UnauthorizedError } from './errors.js';
6
+ export declare function configurePolicies(next: Record<string, NormalizedPolicy[]>): void;
5
7
  export declare function assertPolicy(model: string, role: string, operation: PolicyOperation): NormalizedPolicy;
6
8
  export declare function resolvePolicyWhere(policy: NormalizedPolicy, auth: AuthContext): WhereInput | undefined;
7
9
  export declare function mergeWhere(primary: Record<string, unknown>, policyWhere?: WhereInput): Record<string, unknown>;
@@ -1,15 +1,18 @@
1
- import { POLICIES } from 'generated/policies.js';
2
1
  import { ForbiddenError } from './errors.js';
3
2
  import { interpolateTemplate } from './template.js';
4
3
  import { PUBLIC_ROLE } from './types.js';
5
- const SIMPLE_WHERE_PATTERN = /^(\w+)\s*(=|!=|<>|>=|<=|>|<)\s*(.+)$/;
6
4
  export { ForbiddenError, UnauthorizedError } from './errors.js';
5
+ const SIMPLE_WHERE_PATTERN = /^(\w+)\s*(=|!=|<>|>=|<=|>|<)\s*(.+)$/;
6
+ let policies = {};
7
+ export function configurePolicies(next) {
8
+ policies = next;
9
+ }
7
10
  export function assertPolicy(model, role, operation) {
8
- const policies = POLICIES[model];
9
- if (!policies || policies.length === 0) {
11
+ const modelPolicies = policies[model];
12
+ if (!modelPolicies || modelPolicies.length === 0) {
10
13
  throw new ForbiddenError(`No policies configured for model "${model}"`);
11
14
  }
12
- const policy = findPolicyForRole(policies, role);
15
+ const policy = findPolicyForRole(modelPolicies, role);
13
16
  if (!policy) {
14
17
  throw new ForbiddenError(`Role "${role}" is not allowed to ${operation} ${model}`);
15
18
  }
@@ -34,13 +37,13 @@ export function mergeWhere(primary, policyWhere) {
34
37
  }
35
38
  return { AND: [primary, policyWhere] };
36
39
  }
37
- function findPolicyForRole(policies, role) {
38
- const directMatch = policies.find((policy) => policy.role === role);
40
+ function findPolicyForRole(modelPolicies, role) {
41
+ const directMatch = modelPolicies.find((policy) => policy.role === role);
39
42
  if (directMatch) {
40
43
  return directMatch;
41
44
  }
42
45
  if (role !== PUBLIC_ROLE) {
43
- return policies.find((policy) => policy.role === PUBLIC_ROLE);
46
+ return modelPolicies.find((policy) => policy.role === PUBLIC_ROLE);
44
47
  }
45
48
  return undefined;
46
49
  }
@@ -1,8 +1,8 @@
1
1
  import { Pool } from 'pg';
2
2
  import type { MiddlewareHandler } from 'hono';
3
- import type { DbClient } from 'generated/db.js';
3
+ import type { DbClient } from '../../types/generated-db.stub.js';
4
4
  import type { AppEnv } from '../types.js';
5
- export declare const db: DbClient;
6
- export declare function createDbMiddleware(options?: {
5
+ export declare function createDbMiddleware(options: {
7
6
  pool?: Pool;
7
+ createDbClient: (pool: Pool) => DbClient;
8
8
  }): MiddlewareHandler<AppEnv>;
@@ -1,10 +1,8 @@
1
1
  import { Pool } from 'pg';
2
- import { createDbClient } from 'generated/db.js';
3
2
  import { getDatabaseUrl } from '../../db/config.js';
4
- const pool = new Pool({ connectionString: getDatabaseUrl() });
5
- export const db = createDbClient(pool);
6
- export function createDbMiddleware(options = {}) {
7
- const client = options.pool ? createDbClient(options.pool) : db;
3
+ export function createDbMiddleware(options) {
4
+ const pool = options.pool ?? new Pool({ connectionString: getDatabaseUrl() });
5
+ const client = options.createDbClient(pool);
8
6
  return async (c, next) => {
9
7
  c.set('db', client);
10
8
  await next();
@@ -37,6 +37,9 @@ export class AppGenerator {
37
37
  "import { prettyJSON } from 'hono/pretty-json';",
38
38
  "import { serve } from '@hono/node-server';",
39
39
  routeImports,
40
+ "import { createDbClient } from './db.js';",
41
+ "import { POLICIES } from './policies.js';",
42
+ `import { configurePolicies } from '${PACKAGE_NAME}/api/auth/policy';`,
40
43
  `import { createAuthMiddleware } from '${PACKAGE_NAME}/api/auth/middleware';`,
41
44
  `import { createJwtResolver } from '${PACKAGE_NAME}/api/auth/jwt-resolver';`,
42
45
  `import type { AuthResolver } from '${PACKAGE_NAME}/api/auth/types';`,
@@ -44,6 +47,8 @@ export class AppGenerator {
44
47
  `import { handleError } from '${PACKAGE_NAME}/api/middleware/errors';`,
45
48
  `import type { AppEnv } from '${PACKAGE_NAME}/api/types';`,
46
49
  '',
50
+ 'configurePolicies(POLICIES);',
51
+ '',
47
52
  'export interface CreateAppOptions {',
48
53
  ' pool?: Pool;',
49
54
  ' authResolver?: AuthResolver;',
@@ -53,7 +58,7 @@ export class AppGenerator {
53
58
  ' const app = new Hono<AppEnv>();',
54
59
  ' app.use(logger());',
55
60
  ' app.use(prettyJSON());',
56
- ' app.use(createDbMiddleware({ pool: options.pool }));',
61
+ ' app.use(createDbMiddleware({ pool: options.pool, createDbClient }));',
57
62
  ' app.use(createAuthMiddleware(options.authResolver ?? createJwtResolver()));',
58
63
  ' app.onError(handleError);',
59
64
  '',
@@ -1,3 +1,4 @@
1
+ import { PACKAGE_NAME } from '../constants.js';
1
2
  import { hasPolicies, normalizePolicies, serializePolicy, } from './utils/policy.js';
2
3
  export class PolicyGenerator {
3
4
  schema;
@@ -13,13 +14,7 @@ export class PolicyGenerator {
13
14
  });
14
15
  return [
15
16
  '// Auto-generated by PolicyGenerator. Do not edit manually.',
16
- "export type PolicyOperation = 'select' | 'insert' | 'update' | 'delete';",
17
- '',
18
- 'export interface NormalizedPolicy {',
19
- ' role: string;',
20
- " operations: PolicyOperation[] | 'all';",
21
- ' where?: string;',
22
- '}',
17
+ `import type { NormalizedPolicy } from '${PACKAGE_NAME}/api/auth/policy';`,
23
18
  '',
24
19
  'export const POLICIES: Record<string, NormalizedPolicy[]> = {',
25
20
  policyEntries.join(',\n'),
package/dist/cli/init.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { spawn } from 'node:child_process';
1
2
  import { PACKAGE_NAME } from '../constants.js';
2
3
  import { existsSync } from 'node:fs';
3
4
  import { mkdir, readdir, writeFile } from 'node:fs/promises';
@@ -17,7 +18,25 @@ function resolveTargetDir(args) {
17
18
  function resolveProjectName(targetDir) {
18
19
  return path.basename(targetDir) || `${PACKAGE_NAME}-app`;
19
20
  }
21
+ function runNpmInstall(cwd) {
22
+ return new Promise((resolve, reject) => {
23
+ const child = spawn('npm', ['install'], {
24
+ cwd,
25
+ stdio: 'inherit',
26
+ shell: process.platform === 'win32',
27
+ });
28
+ child.on('error', reject);
29
+ child.on('close', (code) => {
30
+ if (code === 0) {
31
+ resolve();
32
+ return;
33
+ }
34
+ reject(new Error(`npm install failed with exit code ${code}`));
35
+ });
36
+ });
37
+ }
20
38
  export async function runInit(args) {
39
+ const skipInstall = args.includes('--skip-install');
21
40
  const targetDir = resolveTargetDir(args);
22
41
  const projectName = resolveProjectName(targetDir);
23
42
  if (targetDir !== process.cwd() && existsSync(targetDir)) {
@@ -45,11 +64,17 @@ export async function runInit(args) {
45
64
  else {
46
65
  console.log('Skipped existing file: package.json');
47
66
  }
67
+ if (!skipInstall) {
68
+ console.log('\nRunning npm install...');
69
+ await runNpmInstall(targetDir);
70
+ }
48
71
  console.log('\nNext steps:');
49
72
  if (targetDir !== process.cwd()) {
50
73
  console.log(` cd ${targetDir}`);
51
74
  }
52
- console.log(' npm install');
75
+ if (skipInstall) {
76
+ console.log(' npm install');
77
+ }
53
78
  console.log(' docker compose up -d');
54
79
  console.log(` npx ${PACKAGE_NAME} generate`);
55
80
  console.log(` npx ${PACKAGE_NAME} db:bootstrap`);
@@ -59,10 +59,6 @@ export function createPackageJsonTemplate(projectName) {
59
59
  version: '0.1.0',
60
60
  private: true,
61
61
  type: 'module',
62
- imports: {
63
- 'generated/db.js': './generated/db.js',
64
- 'generated/policies.js': './generated/policies.js',
65
- },
66
62
  scripts: {
67
63
  dev: `${PACKAGE_NAME} dev`,
68
64
  generate: `${PACKAGE_NAME} generate`,
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { PACKAGE_NAME } from './constants.js';
7
7
  const USAGE = `Usage: ${PACKAGE_NAME} <command> [options]
8
8
 
9
9
  Commands:
10
- init [dir] Scaffold a new project
10
+ init [dir] [--skip-install] Scaffold a new project
11
11
  generate [schema] Generate schema.sql, db client, and API
12
12
  generate:sql [schema] Generate SQL DDL to stdout
13
13
  generate:client [schema] Generate db client files
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schematic-pg",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Single-file backend framework for PostgreSQL and Node.js",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,7 +14,7 @@
14
14
  "main": "./dist/index.js",
15
15
  "types": "./dist/index.d.ts",
16
16
  "bin": {
17
- "schematic-pg": "./dist/cli.js"
17
+ "schematic-pg": "dist/cli.js"
18
18
  },
19
19
  "files": [
20
20
  "dist/",
@@ -25,10 +25,6 @@
25
25
  "editors/vscode",
26
26
  "editors/language-server"
27
27
  ],
28
- "imports": {
29
- "generated/db.js": "./generated/db.js",
30
- "generated/policies.js": "./generated/policies.js"
31
- },
32
28
  "exports": {
33
29
  ".": {
34
30
  "types": "./dist/index.d.ts",