@sundaysf/cli-v3 0.0.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.
Files changed (80) hide show
  1. package/README.md +302 -0
  2. package/dist/cli.js +1327 -0
  3. package/package.json +55 -0
  4. package/templates/api/.claude/agents/knex-table-implementer.md +36 -0
  5. package/templates/api/.claude/agents/sundays-backend-builder.md +32 -0
  6. package/templates/api/.env.example +25 -0
  7. package/templates/api/.github/workflows/ci.yaml +45 -0
  8. package/templates/api/.github/workflows/deploy.yaml +72 -0
  9. package/templates/api/.prettierignore +5 -0
  10. package/templates/api/.prettierrc +9 -0
  11. package/templates/api/.sundaysrc +8 -0
  12. package/templates/api/CLAUDE.md +81 -0
  13. package/templates/api/Dockerfile +17 -0
  14. package/templates/api/README.md +164 -0
  15. package/templates/api/_dockerignore +8 -0
  16. package/templates/api/_gitignore +23 -0
  17. package/templates/api/_package.json +58 -0
  18. package/templates/api/docker-compose.yml +21 -0
  19. package/templates/api/eslint.config.js +27 -0
  20. package/templates/api/jest.config.js +33 -0
  21. package/templates/api/jest.setup.js +25 -0
  22. package/templates/api/knexfile.ts +5 -0
  23. package/templates/api/src/app.ts +48 -0
  24. package/templates/api/src/common/__tests__/common.test.ts +116 -0
  25. package/templates/api/src/common/config/env.ts +53 -0
  26. package/templates/api/src/common/errors/http.error.ts +30 -0
  27. package/templates/api/src/common/logger/index.ts +25 -0
  28. package/templates/api/src/common/utils/environment.resolver.ts +7 -0
  29. package/templates/api/src/common/utils/pagination.ts +25 -0
  30. package/templates/api/src/common/utils/version.resolver.ts +24 -0
  31. package/templates/api/src/common/validation/parse-dto.ts +20 -0
  32. package/templates/api/src/controllers/health/__tests__/health.controller.test.ts +52 -0
  33. package/templates/api/src/controllers/health/health.controller.ts +26 -0
  34. package/templates/api/src/db/BaseDAO.ts +92 -0
  35. package/templates/api/src/db/KnexConnection.ts +59 -0
  36. package/templates/api/src/db/__tests__/base-dao.test.ts +73 -0
  37. package/templates/api/src/db/__tests__/index.barrel.test.ts +10 -0
  38. package/templates/api/src/db/__tests__/knex-connection.test.ts +90 -0
  39. package/templates/api/src/db/d.types.ts +42 -0
  40. package/templates/api/src/db/dao/sundays-package-version/sundays-package-version.dao.ts +12 -0
  41. package/templates/api/src/db/index.ts +17 -0
  42. package/templates/api/src/db/interfaces/sundays-package-version/sundays-package-version.interfaces.ts +5 -0
  43. package/templates/api/src/db/knex.config.ts +46 -0
  44. package/templates/api/src/dto/input/.gitkeep +0 -0
  45. package/templates/api/src/jobs/.gitkeep +0 -0
  46. package/templates/api/src/middlewares/error/__tests__/error.middleware.test.ts +117 -0
  47. package/templates/api/src/middlewares/error/error.middleware.ts +70 -0
  48. package/templates/api/src/middlewares/not-found/__tests__/not-found.middleware.test.ts +54 -0
  49. package/templates/api/src/middlewares/not-found/not-found.middleware.ts +51 -0
  50. package/templates/api/src/middlewares/request-id/__tests__/request-id.middleware.test.ts +31 -0
  51. package/templates/api/src/middlewares/request-id/request-id.middleware.ts +20 -0
  52. package/templates/api/src/migrations/20240101000000_create_sundays_package_version.ts +15 -0
  53. package/templates/api/src/routes/__tests__/index-router.test.ts +61 -0
  54. package/templates/api/src/routes/health/__tests__/health.routes.test.ts +22 -0
  55. package/templates/api/src/routes/health/health.router.ts +18 -0
  56. package/templates/api/src/routes/index.ts +77 -0
  57. package/templates/api/src/seeds/001_sundays_package_version.ts +14 -0
  58. package/templates/api/src/server.ts +56 -0
  59. package/templates/api/src/services/.gitkeep +0 -0
  60. package/templates/api/tsconfig.json +20 -0
  61. package/templates/api/tsconfig.spec.json +10 -0
  62. package/templates/api-auth/overlay.json +95 -0
  63. package/templates/api-auth/src/controllers/auth/__tests__/auth.controller.test.ts +194 -0
  64. package/templates/api-auth/src/controllers/auth/auth.controller.ts +109 -0
  65. package/templates/api-auth/src/db/dao/auth/auth.dao.ts +21 -0
  66. package/templates/api-auth/src/db/dao/user/user.dao.ts +24 -0
  67. package/templates/api-auth/src/db/interfaces/auth/auth.interfaces.ts +8 -0
  68. package/templates/api-auth/src/db/interfaces/user/user.interfaces.ts +11 -0
  69. package/templates/api-auth/src/dto/input/auth/auth.login.dto.ts +14 -0
  70. package/templates/api-auth/src/dto/input/auth/auth.register.dto.ts +19 -0
  71. package/templates/api-auth/src/middlewares/auth/__tests__/auth.middleware.test.ts +52 -0
  72. package/templates/api-auth/src/middlewares/auth/auth.middleware.ts +56 -0
  73. package/templates/api-auth/src/migrations/20240101000001_create_user.ts +18 -0
  74. package/templates/api-auth/src/migrations/20240101000002_create_auth.ts +24 -0
  75. package/templates/api-auth/src/routes/auth/__tests__/auth.routes.test.ts +83 -0
  76. package/templates/api-auth/src/routes/auth/auth.router.ts +28 -0
  77. package/templates/api-auth/src/services/jwt/__tests__/jwt.service.test.ts +32 -0
  78. package/templates/api-auth/src/services/jwt/jwt.service.ts +36 -0
  79. package/templates/api-auth/src/services/password/__tests__/password.service.test.ts +13 -0
  80. package/templates/api-auth/src/services/password/password.service.ts +14 -0
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@sundaysf/cli-v3",
3
+ "version": "0.0.1",
4
+ "description": "Sundays Framework v3 CLI - scaffolds an Express 5 + Knex + Postgres API and generates entity verticals",
5
+ "type": "module",
6
+ "bin": {
7
+ "sundaysf": "./dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "templates",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=20"
16
+ },
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "dev": "tsx src/cli.ts",
20
+ "typecheck": "tsc --noEmit",
21
+ "test": "vitest run --exclude test/e2e",
22
+ "test:e2e": "SUNDAYS_E2E=1 vitest run test/e2e",
23
+ "lint": "eslint .",
24
+ "format": "prettier --write .",
25
+ "prepublishOnly": "npm run build && npm test"
26
+ },
27
+ "keywords": [
28
+ "sundays",
29
+ "scaffold",
30
+ "express",
31
+ "knex",
32
+ "cli"
33
+ ],
34
+ "author": "Pablo Dominguez",
35
+ "license": "MIT",
36
+ "dependencies": {
37
+ "@clack/prompts": "^1.8.1",
38
+ "commander": "^15.0.0",
39
+ "execa": "^10.0.1",
40
+ "fs-extra": "^11.4.0",
41
+ "picocolors": "^1.1.1"
42
+ },
43
+ "devDependencies": {
44
+ "@eslint/js": "^10.0.0",
45
+ "@types/fs-extra": "^11.0.4",
46
+ "@types/node": "^24.0.0",
47
+ "eslint": "^10.11.0",
48
+ "prettier": "^3.9.8",
49
+ "tsup": "^8.5.1",
50
+ "tsx": "^4.23.15",
51
+ "typescript": "^5.9.3",
52
+ "typescript-eslint": "^8.70.0",
53
+ "vitest": "^5.0.1"
54
+ }
55
+ }
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: knex-table-implementer
3
+ description: Use this agent to add or change database tables in this Sundays Framework v3 project - migrations, interfaces, DAOs and the src/db barrel - following the project's Knex conventions. Examples - "add an order_item table", "add a nullable deletedAt column to product", "write a DAO method that lists products with their category".
4
+ model: sonnet
5
+ color: red
6
+ ---
7
+
8
+ You are a database-layer specialist for a Sundays Framework v3 API (Knex 3 + PostgreSQL).
9
+ Read `CLAUDE.md` first.
10
+
11
+ ## New table
12
+
13
+ Run `sundaysf generate entity <name> <fields...>` (it writes the migration, interface, DAO, DTOs,
14
+ controller, router, tests and barrel exports). Then refine the generated migration if needed.
15
+
16
+ ## Changing an existing table
17
+
18
+ 1. `npm run db:make-migration -- <verb>_<table>` creates `src/migrations/<timestamp>_<verb>_<table>.ts`.
19
+ 2. Implement `up` and `down` with the knex schema builder. Columns camelCase, tables snake_case,
20
+ foreign keys `.references('id').inTable('<table>').onDelete('CASCADE')` plus `.index()`.
21
+ 3. Update `src/db/interfaces/<x>/<x>.interfaces.ts` (`interface IX extends IEntity`).
22
+ 4. Add finders to `src/db/dao/<x>/<x>.dao.ts`. DAOs extend `BaseDAO<IX>`; use `this.q(trx)` for
23
+ the query builder and accept an optional `trx?: Knex.Transaction` on every method.
24
+ Relations: `leftJoin` + `this._knex.raw('to_jsonb(r.*) as related')`.
25
+ 5. If you create a new DAO or interface by hand, export them in `src/db/index.ts` above the
26
+ `// @sundays:daos` / `// @sundays:interfaces` markers.
27
+ 6. `npm run db:migrate`, then `npm run db:rollback && npm run db:migrate` to prove `down` works.
28
+ 7. Add or update tests under `src/db/dao/<x>/__tests__/` (real Postgres, clean up in `afterAll`).
29
+
30
+ ## Rules
31
+
32
+ - Never edit an applied migration; write a new one.
33
+ - Every table has `id` (increments), `uuid` (unique), `createdAt`, `updatedAt`
34
+ (`timestamp(...).notNullable().defaultTo(knex.fn.now())`).
35
+ - Return `null` for not-found, never throw from a DAO for missing rows.
36
+ - Keep `knexfile.ts` untouched: configuration lives in `src/db/knex.config.ts`.
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: sundays-backend-builder
3
+ description: Use this agent to create or modify backend components (endpoints, controllers, routers, services, DTOs) in this Sundays Framework v3 project so they follow the project conventions exactly. Examples - "add a products API with CRUD", "add a POST /orders/:uuid/cancel endpoint", "this controller doesn't follow our standards, fix it".
4
+ model: sonnet
5
+ color: blue
6
+ ---
7
+
8
+ You are a senior backend developer working on a Sundays Framework v3 API (Express 5 + TypeScript +
9
+ Knex + PostgreSQL + Zod). Read `CLAUDE.md` first; it is the source of truth.
10
+
11
+ ## How you work
12
+
13
+ 1. **New entity?** Run `sundaysf generate entity <name> <fields...>` first, then adapt the output.
14
+ Never hand-write what the generator produces.
15
+ 2. **New endpoint on an existing entity?** Add the route in `src/routes/<x>/<x>.router.ts`
16
+ (`.bind()` the handler), the method in `src/controllers/<x>/<x>.controller.ts`, a DTO in
17
+ `src/dto/input/<x>/` when it takes a body, and DAO methods in `src/db/dao/<x>/<x>.dao.ts`.
18
+ 3. **Cross-cutting logic** (email, storage, external APIs) goes in `src/services/<x>/<x>.service.ts`.
19
+ 4. Write tests next to the code (`__tests__/`): unit test for the controller (mock `'../../../db'`),
20
+ route test with supertest when the endpoint touches the database.
21
+ 5. Finish with `npm run typecheck && npm run lint && npm test`.
22
+
23
+ ## Rules
24
+
25
+ - Response envelope `{ success, data }` / `{ success, message, errors? }`; paginated lists return
26
+ `dao.getAll(page, limit)` directly.
27
+ - Validation through Zod DTOs (`validateXCreate(req.body)`), never manual `if (!body.x)` checks.
28
+ - Errors through `HttpError` helpers (`notFound`, `conflict`, `badRequest`, `unauthorized`), never
29
+ `res.status(500)` by hand; unexpected errors go to `next(err)`.
30
+ - Public ids are `uuid`; resolve to numeric `id` before DAO writes.
31
+ - Express 5 routing rules (no `*`, `{/:id}` for optional params).
32
+ - No new dependencies without saying why.
@@ -0,0 +1,25 @@
1
+ # ------------------------------------------------------------------------------
2
+ # __SF_PROJECT_NAME__ - environment variables
3
+ # Copy to .env (sundaysf new already did) and adjust. Never commit .env.
4
+ # ------------------------------------------------------------------------------
5
+
6
+ # Server
7
+ PORT=__SF_PORT__
8
+ NODE_ENV=development
9
+ # pino level: fatal | error | warn | info | debug | trace | silent
10
+ LOG_LEVEL=info
11
+ # Comma separated list of allowed origins, or * for any
12
+ CORS_ORIGINS=*
13
+
14
+ # Database (matches docker-compose.yml)
15
+ SQL_HOST=localhost
16
+ SQL_PORT=5432
17
+ SQL_USER=postgres
18
+ SQL_PASSWORD=postgres
19
+ SQL_DB_NAME=__SF_DB_NAME__
20
+ # Set to false to accept self-signed certificates on remote hosts (SSL is off for localhost)
21
+ SQL_REJECT_UNAUTHORIZED=true
22
+ # Run pending migrations on boot (useful in containers). Default: false
23
+ RUN_MIGRATIONS=false
24
+
25
+ # @sundays:env
@@ -0,0 +1,45 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ jobs:
9
+ test:
10
+ name: Typecheck, lint and test
11
+ runs-on: ubuntu-latest
12
+ services:
13
+ postgres:
14
+ image: postgres:16-alpine
15
+ env:
16
+ POSTGRES_USER: postgres
17
+ POSTGRES_PASSWORD: postgres
18
+ POSTGRES_DB: __SF_DB_NAME__
19
+ ports:
20
+ - 5432:5432
21
+ options: >-
22
+ --health-cmd "pg_isready -U postgres"
23
+ --health-interval 5s
24
+ --health-timeout 3s
25
+ --health-retries 10
26
+ env:
27
+ NODE_ENV: test
28
+ SQL_HOST: localhost
29
+ SQL_PORT: 5432
30
+ SQL_USER: postgres
31
+ SQL_PASSWORD: postgres
32
+ SQL_DB_NAME: __SF_DB_NAME__
33
+ # @sundays:ci-env
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ - uses: actions/setup-node@v4
37
+ with:
38
+ node-version: 24
39
+ cache: npm
40
+ - run: npm ci
41
+ - run: npm run typecheck
42
+ - run: npm run lint
43
+ - run: npm run db:migrate
44
+ - run: npm test
45
+ - run: npm run build
@@ -0,0 +1,72 @@
1
+ name: Deploy to AWS ECS
2
+
3
+ # Manual deploy. Requires in the GitHub repo:
4
+ # secrets.AWS_ROLE_ARN - IAM role assumed via OIDC (needs ECR push + ECS deploy)
5
+ # vars.AWS_REGION - e.g. us-east-1
6
+ # and, already created in AWS: an ECR repository, an ECS cluster/service and a task
7
+ # definition, all named after the project (change the env block below if they differ).
8
+ on:
9
+ workflow_dispatch:
10
+
11
+ env:
12
+ AWS_REGION: ${{ vars.AWS_REGION || 'us-east-1' }}
13
+ ECR_REPOSITORY: __SF_PROJECT_SLUG__
14
+ ECS_CLUSTER: __SF_PROJECT_SLUG__
15
+ ECS_SERVICE: __SF_PROJECT_SLUG__
16
+ ECS_TASK_DEFINITION: __SF_PROJECT_SLUG__
17
+ CONTAINER_NAME: __SF_PROJECT_SLUG__
18
+
19
+ jobs:
20
+ deploy:
21
+ name: Build, push and deploy
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ id-token: write
25
+ contents: read
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+
29
+ - name: Read package version
30
+ id: pkg
31
+ run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
32
+
33
+ - name: Configure AWS credentials
34
+ uses: aws-actions/configure-aws-credentials@v4
35
+ with:
36
+ role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
37
+ aws-region: ${{ env.AWS_REGION }}
38
+
39
+ - name: Login to Amazon ECR
40
+ id: ecr
41
+ uses: aws-actions/amazon-ecr-login@v2
42
+
43
+ - name: Build, tag and push image
44
+ id: image
45
+ env:
46
+ REGISTRY: ${{ steps.ecr.outputs.registry }}
47
+ TAG: ${{ steps.pkg.outputs.version }}
48
+ run: |
49
+ docker build -t "$REGISTRY/$ECR_REPOSITORY:$TAG" .
50
+ docker tag "$REGISTRY/$ECR_REPOSITORY:$TAG" "$REGISTRY/$ECR_REPOSITORY:latest"
51
+ docker push "$REGISTRY/$ECR_REPOSITORY:$TAG"
52
+ docker push "$REGISTRY/$ECR_REPOSITORY:latest"
53
+ echo "image=$REGISTRY/$ECR_REPOSITORY:$TAG" >> "$GITHUB_OUTPUT"
54
+
55
+ - name: Download current task definition
56
+ run: aws ecs describe-task-definition --task-definition "$ECS_TASK_DEFINITION" --query taskDefinition > task-definition.json
57
+
58
+ - name: Render new task definition
59
+ id: taskdef
60
+ uses: aws-actions/amazon-ecs-render-task-definition@v1
61
+ with:
62
+ task-definition: task-definition.json
63
+ container-name: ${{ env.CONTAINER_NAME }}
64
+ image: ${{ steps.image.outputs.image }}
65
+
66
+ - name: Deploy
67
+ uses: aws-actions/amazon-ecs-deploy-task-definition@v2
68
+ with:
69
+ task-definition: ${{ steps.taskdef.outputs.task-definition }}
70
+ service: ${{ env.ECS_SERVICE }}
71
+ cluster: ${{ env.ECS_CLUSTER }}
72
+ wait-for-service-stability: true
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ dist/
3
+ coverage/
4
+ package-lock.json
5
+ pnpm-lock.yaml
@@ -0,0 +1,9 @@
1
+ {
2
+ "tabWidth": 2,
3
+ "semi": true,
4
+ "singleQuote": true,
5
+ "trailingComma": "es5",
6
+ "bracketSpacing": true,
7
+ "bracketSameLine": true,
8
+ "arrowParens": "always"
9
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "runtime": "node",
3
+ "install": "npm install",
4
+ "build": "npm run build",
5
+ "start": "npm start",
6
+ "port": __SF_PORT__,
7
+ "cli": "__SF_CLI_VERSION__"
8
+ }
@@ -0,0 +1,81 @@
1
+ # CLAUDE.md
2
+
3
+ Guidance for Claude Code when working in this repository (an API generated by Sundays
4
+ Framework v__SF_CLI_VERSION__).
5
+
6
+ ## Commands
7
+
8
+ ```bash
9
+ npm run start:dev # dev server with reload (tsx watch, no type-check)
10
+ npm run typecheck # tsc --noEmit (sources + tests)
11
+ npm test # jest with coverage; routes/ and db/ suites need Postgres (docker compose up -d)
12
+ npm run test:unit # jest without database suites
13
+ npm run lint # eslint
14
+ npm run format # prettier
15
+ npm run db:migrate # apply src/migrations
16
+ npm run db:rollback # undo last batch
17
+ npm run db:make-migration -- <name>
18
+ npm run db:seed
19
+ sundaysf generate entity <name> <fields...> # scaffold a full entity (see below)
20
+ ```
21
+
22
+ Before finishing any task run `npm run typecheck && npm run lint && npm test`.
23
+
24
+ ## Architecture
25
+
26
+ Express 5 + TypeScript (CommonJS, ES2022) + Knex 3 + PostgreSQL. Layers, top to bottom:
27
+
28
+ 1. `src/routes/<x>/<x>.router.ts` — class with `public router: Router`; auto-mounted at
29
+ `/api/<x>` by `src/routes/index.ts` (folder name = mount path, no manual registration).
30
+ 2. `src/controllers/<x>/<x>.controller.ts` — class; one `async method(req, res, next)` per
31
+ endpoint wrapped in `try { } catch (err) { next(err) }`; DAOs as `private _xDAO = new XDAO()`.
32
+ 3. `src/dto/input/<x>/<x>.{create,update}.dto.ts` — Zod schema + `validateXCreate(input)` which
33
+ throws `HttpError(400)` with field errors.
34
+ 4. `src/services/<x>/<x>.service.ts` — cross-cutting logic (email, storage, tokens...).
35
+ 5. `src/db/dao/<x>/<x>.dao.ts` — `class XDAO extends BaseDAO<IX> { protected readonly table = 'x' }`
36
+ plus custom finders; `src/db/interfaces/<x>/<x>.interfaces.ts` — `interface IX extends IEntity`.
37
+ 6. `src/db/index.ts` — barrel. Import DAOs/types from `'../../db'`. Keep the
38
+ `// @sundays:interfaces` and `// @sundays:daos` markers: generators append above them.
39
+
40
+ Boot: `src/server.ts` validates env (`src/common/config/env.ts`), connects `KnexManager`,
41
+ optionally runs migrations (`RUN_MIGRATIONS=true`), then imports `src/app.ts` and listens.
42
+ SIGTERM/SIGINT close the server and the pool.
43
+
44
+ ## Conventions (do not deviate)
45
+
46
+ - Response envelope: `{ success: true, data }` / `{ success: false, message, errors? }`.
47
+ Paginated lists return `IDataPaginator` from `dao.getAll(page, limit)` as-is (already has `success`).
48
+ - Public identifier is `uuid`; numeric `id` never leaves the API. Routes use `/:uuid`.
49
+ - Table names snake_case, columns camelCase, every table has `id`, `uuid`, `createdAt`, `updatedAt`.
50
+ - Migrations live in `src/migrations/` (TypeScript), named `<timestamp>_<verb>_<table>.ts`.
51
+ - Pagination params via `getPagination(req.query)` (`src/common/utils/pagination.ts`).
52
+ - Errors: `throw notFound('X not found')`, `conflict(...)`, `badRequest(...)`, `unauthorized(...)`
53
+ from `src/common/errors/http.error.ts`. Never `res.status(500)` by hand.
54
+ - Logging: `req.log` inside handlers, `logger` (`src/common/logger`) elsewhere. No `console.*`.
55
+ - New env vars go in the Zod schema in `src/common/config/env.ts`, `.env.example` and README.
56
+ - Express 5 routing: no `*` (use `/*splat`), optional params `{/:id}`, no regex strings,
57
+ `req.query` is read-only, `req.body` is `undefined` when no parser matched.
58
+ - Tests co-located in `__tests__/`: controller unit tests mock `'../../../db'` and use a
59
+ `mockRes()` helper; route tests use supertest + real Postgres and clean up in `afterAll`.
60
+ - Keep coverage above the threshold in `jest.config.js`.
61
+
62
+ ## Adding an entity
63
+
64
+ Always start with the generator, then adapt:
65
+
66
+ ```bash
67
+ sundaysf generate entity product name:string:unique price:decimal categoryId:category.id isActive:boolean=true
68
+ npm run db:migrate
69
+ ```
70
+
71
+ Field syntax `name:type[?][:unique][=default]`; types `string text integer decimal boolean date
72
+ datetime json uuid <entity>.id`. It creates migration, interface, DAO, DTOs, controller, router,
73
+ unit test and route test, and updates `src/db/index.ts`. Add custom endpoints to the generated
74
+ router/controller; add custom queries to the DAO using `this.q(trx)`.
75
+
76
+ Transactions: `KnexManager.getConnection().transaction(async (trx) => { await dao.create(x, trx); })`.
77
+ Relations: prefer `leftJoin` + `this._knex.raw('to_jsonb(r.*) as related')` inside the DAO.
78
+
79
+ ## Features in this project
80
+
81
+ <!-- @sundays:features -->
@@ -0,0 +1,17 @@
1
+ # ---- build stage ------------------------------------------------------------
2
+ FROM node:24-alpine AS builder
3
+ WORKDIR /var/api
4
+ COPY package*.json ./
5
+ RUN npm ci
6
+ COPY . .
7
+ RUN npm run build
8
+
9
+ # ---- runtime stage ----------------------------------------------------------
10
+ FROM node:24-alpine
11
+ ENV NODE_ENV=production
12
+ WORKDIR /var/api
13
+ COPY package*.json ./
14
+ RUN npm ci --omit=dev
15
+ COPY --from=builder /var/api/dist ./dist
16
+ EXPOSE __SF_PORT__
17
+ CMD ["node", "dist/server.js"]
@@ -0,0 +1,164 @@
1
+ # **SF_PROJECT_NAME**
2
+
3
+ REST API built with [Sundays Framework](https://github.com/sundaysf) v__SF_CLI_VERSION__:
4
+ Express 5 · TypeScript · Knex · PostgreSQL · Zod · pino · Jest.
5
+
6
+ ## Quick start
7
+
8
+ ```bash
9
+ npm install # once
10
+ docker compose up -d # local PostgreSQL 16 on port 5432
11
+ npm run db:migrate # apply migrations
12
+ npm run start:dev # http://localhost:__SF_PORT__/api/health (reloads on change)
13
+ ```
14
+
15
+ `sundaysf new` already created `.env` from `.env.example`. Add a new entity with:
16
+
17
+ ```bash
18
+ sundaysf generate entity product name:string:unique price:decimal isActive:boolean=true
19
+ npm run db:migrate
20
+ ```
21
+
22
+ ## Scripts
23
+
24
+ | Script | What it does |
25
+ | ------------------------------------- | ------------------------------------------------------------------------------------------------- |
26
+ | `npm run start:dev` | Dev server with reload (`tsx watch`). Does not type-check: run `typecheck` or rely on the editor. |
27
+ | `npm run build` | Compile `src/` to `dist/` with `tsc`. |
28
+ | `npm start` | Run the compiled server (`node dist/server.js`). |
29
+ | `npm run typecheck` | `tsc --noEmit` over sources and tests. |
30
+ | `npm test` | Jest with coverage. Route and DB suites need the local Postgres. |
31
+ | `npm run test:unit` | Jest without the `routes/` and `db/` suites (no database needed). |
32
+ | `npm run test:watch` | Jest in watch mode. |
33
+ | `npm run lint` / `npm run format` | ESLint / Prettier. |
34
+ | `npm run db:migrate` | Apply pending migrations from `src/migrations/`. |
35
+ | `npm run db:rollback` | Roll back the last migration batch. |
36
+ | `npm run db:status` | List applied and pending migrations. |
37
+ | `npm run db:seed` | Run seeds from `src/seeds/`. |
38
+ | `npm run db:make-migration -- <name>` | Create `src/migrations/<timestamp>_<name>.ts`. |
39
+ | `npm run db:make-seed -- <name>` | Create `src/seeds/<name>.ts`. |
40
+
41
+ ## Environment variables
42
+
43
+ Validated at boot by `src/common/config/env.ts` (Zod). A missing or malformed value stops the
44
+ process with a readable error. Add new variables to that schema, to `.env.example`, and here.
45
+
46
+ | Variable | Default | Description |
47
+ | ------------------------- | ------------- | ------------------------------------------------------------------------- |
48
+ | `PORT` | `__SF_PORT__` | HTTP port. |
49
+ | `NODE_ENV` | `development` | `development`, `test` or `production`. Pretty logs only in development. |
50
+ | `LOG_LEVEL` | `info` | pino level: `fatal`, `error`, `warn`, `info`, `debug`, `trace`, `silent`. |
51
+ | `CORS_ORIGINS` | `*` | Comma separated allowed origins, or `*`. |
52
+ | `SQL_HOST` | `localhost` | PostgreSQL host. SSL is disabled for localhost. |
53
+ | `SQL_PORT` | `5432` | PostgreSQL port. |
54
+ | `SQL_USER` | - | PostgreSQL user. |
55
+ | `SQL_PASSWORD` | - | PostgreSQL password. |
56
+ | `SQL_DB_NAME` | - | Database name. |
57
+ | `SQL_REJECT_UNAUTHORIZED` | `true` | Set `false` to accept self-signed certificates on remote hosts. |
58
+ | `RUN_MIGRATIONS` | `false` | Run pending migrations on boot (handy for containers). |
59
+
60
+ <!-- @sundays:readme-env -->
61
+
62
+ ## Project layout
63
+
64
+ ```
65
+ src/
66
+ server.ts boot: env -> db -> (migrations) -> app.listen, graceful shutdown
67
+ app.ts express: helmet, cors, request id, pino-http, parsers, /api, 404, errors
68
+ routes/
69
+ index.ts auto-discovery: routes/<x>/<x>.router.ts is mounted at /api/<x>
70
+ <entity>/<entity>.router.ts
71
+ controllers/<entity>/<entity>.controller.ts
72
+ dto/input/<entity>/<entity>.{create,update}.dto.ts Zod schemas + validate helpers
73
+ services/<name>/<name>.service.ts cross-cutting logic (email, s3, jwt...)
74
+ middlewares/<name>/<name>.middleware.ts
75
+ jobs/<name>.job.ts cron jobs (export run() and schedule(); register schedule() in server.ts)
76
+ db/
77
+ index.ts barrel with @sundays markers (generators append here)
78
+ BaseDAO.ts generic CRUD + pagination + optional transaction
79
+ KnexConnection.ts KnexManager singleton (connect / getConnection / disconnect)
80
+ knex.config.ts the one knex config (also used by knexfile.ts for the CLI)
81
+ dao/<entity>/<entity>.dao.ts
82
+ interfaces/<entity>/<entity>.interfaces.ts
83
+ migrations/ knex migrations (TypeScript, compiled with the app)
84
+ seeds/
85
+ common/
86
+ config/env.ts validated environment
87
+ logger/ pino
88
+ errors/http.error.ts HttpError + badRequest()/notFound()/... helpers
89
+ validation/parse-dto.ts parseDto(schema, input)
90
+ utils/ pagination, version/environment resolvers
91
+ ```
92
+
93
+ Every file has its tests next to it in a `__tests__/` folder.
94
+
95
+ ## Conventions
96
+
97
+ - **Response envelope**: `{ success: true, data }` on success, `{ success: false, message, errors? }`
98
+ on failure. Paginated lists return `IDataPaginator`:
99
+ `{ success, data, page, limit, count, totalCount, totalPages }`.
100
+ - **Ids**: tables have a numeric `id` (internal) and a `uuid` (public). Routes take `/:uuid`;
101
+ controllers resolve the row and use `id` for DAO writes.
102
+ - **Columns** are camelCase (`createdAt`, `categoryId`); **tables** are snake_case (`product_category`).
103
+ - **Controllers** are classes with `private _xDAO = new XDAO()` members and
104
+ `async method(req, res, next)` handlers wrapped in `try { } catch (err) { next(err) }`.
105
+ - **Routers** are classes exposing `public router: Router` and bind handlers with `.bind()`.
106
+ - **Validation**: `const input = validateProductCreate(req.body)` throws `HttpError(400)` with
107
+ per-field errors; the error middleware renders it.
108
+ - **Errors**: throw `notFound('Product not found')`, `conflict(...)`, etc. from
109
+ `src/common/errors/http.error.ts`, or `next(err)` for anything else.
110
+ - **Express 5**: no `*` wildcards (`/*splat`), optional params are `{/:id}`, `req.query` is read-only.
111
+
112
+ ## Adding an entity
113
+
114
+ ```bash
115
+ sundaysf generate entity product \
116
+ name:string:unique price:decimal categoryId:category.id isActive:boolean=true description:text?
117
+ ```
118
+
119
+ This writes the migration, interface, DAO, create/update DTOs, controller, router and tests, and
120
+ registers the DAO in `src/db/index.ts`. Then:
121
+
122
+ ```bash
123
+ npm run db:migrate
124
+ npm run start:dev
125
+ curl -X POST localhost:__SF_PORT__/api/product -H 'content-type: application/json' \
126
+ -d '{"name":"Widget","price":9.5,"categoryId":1}'
127
+ npm test
128
+ ```
129
+
130
+ Field syntax: `name:type[?][:unique][=default]` with types `string`, `text`, `integer`,
131
+ `decimal`, `boolean`, `date`, `datetime`, `json`, `uuid` or `<entity>.id` (foreign key).
132
+
133
+ ## Endpoints
134
+
135
+ ### Health
136
+
137
+ | Method | Path | Response |
138
+ | ------ | ------------- | ------------------------------------------------------------- |
139
+ | GET | `/api/health` | `{ success, health, version, environment, database, uptime }` |
140
+
141
+ <!-- @sundays:readme-endpoints -->
142
+
143
+ ## Testing
144
+
145
+ - `npm test` runs everything with coverage (threshold in `jest.config.js`). Route and DB suites
146
+ connect to the `SQL_*` database from `.env` and clean up what they create.
147
+ - `npm run test:unit` skips them so you can test without Postgres.
148
+ - `jest.setup.js` refuses to run against a non-local `SQL_HOST` unless `ALLOW_REMOTE_DB_TESTS=1`.
149
+ - Controller unit tests mock the `src/db` barrel with `jest.mock('../../../db', ...)`; route tests
150
+ use `supertest` against `app` with a real database.
151
+
152
+ ## Deploy
153
+
154
+ - `Dockerfile`: two-stage build on `node:24-alpine`, `EXPOSE __SF_PORT__`, runs `node dist/server.js`.
155
+ Set `RUN_MIGRATIONS=true` in the container to migrate on boot.
156
+ - `.github/workflows/ci.yaml`: on every PR and push to `main` runs typecheck, lint, migrations,
157
+ tests and build against a Postgres service.
158
+ - `.github/workflows/deploy.yaml`: manual deploy to AWS ECS (build → ECR → new task definition →
159
+ service update). Configure `secrets.AWS_ROLE_ARN` (OIDC) and `vars.AWS_REGION` in GitHub, and
160
+ adjust the resource names at the top of the file if they differ from `__SF_PROJECT_SLUG__`.
161
+
162
+ ## Claude Code
163
+
164
+ `CLAUDE.md` and `.claude/agents/` describe the conventions above for AI-assisted development.
@@ -0,0 +1,8 @@
1
+ node_modules
2
+ dist
3
+ coverage
4
+ .git
5
+ .env
6
+ .env.*
7
+ !.env.example
8
+ *.log
@@ -0,0 +1,23 @@
1
+ # Dependencies
2
+ node_modules/
3
+
4
+ # Build output
5
+ dist/
6
+ *.tsbuildinfo
7
+
8
+ # Environment (only .env.example is versioned)
9
+ .env
10
+ .env.*
11
+ !.env.example
12
+
13
+ # Tests
14
+ coverage/
15
+ src/routes/__tests__/_fixtures-*/
16
+
17
+ # IDE / OS
18
+ .vscode/
19
+ .idea/
20
+ .DS_Store
21
+
22
+ # Logs
23
+ *.log
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "__SF_PROJECT_SLUG__",
3
+ "version": "0.1.0",
4
+ "description": "__SF_PROJECT_NAME__ API - generated with Sundays Framework v__SF_CLI_VERSION__",
5
+ "private": true,
6
+ "main": "dist/server.js",
7
+ "scripts": {
8
+ "start:dev": "tsx watch src/server.ts",
9
+ "build": "tsc -p tsconfig.json",
10
+ "start": "node dist/server.js",
11
+ "typecheck": "tsc --noEmit -p tsconfig.spec.json",
12
+ "test": "jest",
13
+ "test:unit": "jest --testPathIgnorePatterns '/routes/' '/db/'",
14
+ "test:watch": "jest --watch",
15
+ "lint": "eslint .",
16
+ "format": "prettier --write .",
17
+ "db:migrate": "tsx node_modules/knex/bin/cli.js migrate:latest",
18
+ "db:rollback": "tsx node_modules/knex/bin/cli.js migrate:rollback",
19
+ "db:status": "tsx node_modules/knex/bin/cli.js migrate:status",
20
+ "db:seed": "tsx node_modules/knex/bin/cli.js seed:run",
21
+ "db:make-migration": "tsx node_modules/knex/bin/cli.js migrate:make -x ts",
22
+ "db:make-seed": "tsx node_modules/knex/bin/cli.js seed:make -x ts"
23
+ },
24
+ "dependencies": {
25
+ "cors": "^2.8.5",
26
+ "dotenv": "^18.0.1",
27
+ "express": "^5.2.1",
28
+ "helmet": "^8.3.0",
29
+ "knex": "^3.3.0",
30
+ "pg": "^8.23.0",
31
+ "pino": "^10.3.1",
32
+ "pino-http": "^11.0.0",
33
+ "zod": "^4.6.5"
34
+ },
35
+ "devDependencies": {
36
+ "@eslint/js": "^10.0.0",
37
+ "@types/cors": "^2.8.19",
38
+ "@types/express": "^5.0.6",
39
+ "@types/jest": "^30.0.0",
40
+ "@types/node": "^24.0.0",
41
+ "@types/pg": "^8.15.0",
42
+ "@types/supertest": "^7.0.0",
43
+ "eslint": "^10.11.0",
44
+ "globals": "^16.0.0",
45
+ "jest": "^30.5.2",
46
+ "pino-pretty": "^13.1.3",
47
+ "prettier": "^3.9.8",
48
+ "supertest": "^7.2.2",
49
+ "ts-jest": "^29.4.12",
50
+ "tsx": "^4.23.15",
51
+ "typescript": "~5.9.3",
52
+ "typescript-eslint": "^8.70.0"
53
+ },
54
+ "engines": {
55
+ "node": ">=22"
56
+ },
57
+ "license": "MIT"
58
+ }