Skip to content

Commit 101167a

Browse files
committed
update to modern variant
Signed-off-by: Kirill Mokevnin <[email protected]>
1 parent 18bdd7b commit 101167a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+3286
-3315
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,4 @@ profile-*
5656
profile*
5757
*clinic*
5858
*flamegraph*
59+
.env

AGENTS.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Repository Guidelines
2+
3+
## Project Structure & Module Organization
4+
5+
- `app.js`: Fastify entry; autoloads `plugins/` and `routes/`.
6+
- `routes/`: HTTP handlers.
7+
- `plugins/`: App plugins (JWT auth, DB, sensible errors, response validation, route glue).
8+
- `db/`: Drizzle ORM schema and seeds; `drizzle/` holds generated migrations.
9+
- `lib/`: Utilities and test data builders.
10+
- `test/`: Node tests; helpers in `test/helper.js`, route specs in `test/routes/*.test.js`.
11+
- `types/`: ts types
12+
- `main.tsp`, `tsp-output/`: TypeSpec and generated OpenAPI/handler typings.
13+
14+
## Build, Test, and Development Commands
15+
- `npm run dev`: Start Fastify with watch on http://localhost:3000.
16+
- `npm start`: Start in production mode.
17+
- `npm test` or `make test`: Run Node tests (`node --test`).
18+
- `make lint` / `make lint-fix`: Lint and autofix via ESLint.
19+
- `make check-types`: Type-check with `tsc` (JS + d.ts).
20+
- `make generate-types`: Compile TypeSpec and generate Fastify handler types.
21+
- `make migration-generate`: Generate Drizzle migrations.
22+
- `make mock`: Serve mocked API from generated OpenAPI.
23+
24+
## Coding Style & Naming Conventions
25+
- **Modules**: ESM only (`type: module`). Prefer named exports; keep JSDoc types consistent with `types/`.
26+
- **Formatting**: 2-space indent, no semicolons; follow ESLint + `@stylistic` rules. Run `make lint` before committing.
27+
- **Files**: Group endpoints by resource in `routes/api/` (e.g., `routes/api/books.js`). Co-locate validators and serializers by domain when present.
28+
29+
## Testing Guidelines
30+
- **Framework**: Node built-in `node:test` with `app.inject()`; see `test/helper.js` for server bootstrap.
31+
- **Naming**: Place specs under `test/routes/` as `*.test.js` (e.g., `test/routes/users.test.js`).
32+
- **Scope**: Add success tests for each new/changed route. No coverage gate enforced.
33+
34+
## Commit & Pull Request Guidelines
35+
- **Commits**: Use clear, imperative messages (optionally Conventional Commits). Reference issues when applicable.
36+
- **PRs**: Provide purpose, summary, linked issues, test plan, and example requests/responses (curl or HTTPie). Keep diffs focused.
37+
38+
## Security & Configuration Tips
39+
- **Secrets**: Move JWT secret to env (e.g., `JWT_SECRET`) rather than hardcoding; use `.env` locally and never commit secrets.
40+
- **DB**: Current DB is in-memory SQLite (`plugins/drizzle.js`). Switch to file/real DB for persistence before production.

Makefile

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ dev:
55
npm run dev
66

77
check-types:
8-
npx tsc
8+
npx tsc --noEmit
99

1010
routes:
11-
npx fastify print-routes routes/users.js
11+
npx fastify print-routes routes/api/users.js
1212

1313
migration-generate:
1414
npx drizzle-kit generate
@@ -19,13 +19,15 @@ lint:
1919
lint-fix:
2020
npx eslint --fix .
2121

22-
types-to-openapi:
22+
generate-openapi:
2323
npx tsp compile .
2424

25-
types-to-typebox:
26-
npx openapi-box ./tsp-output/@typespec/openapi3/openapi.v1.json
25+
generate-openapi-ts-types:
26+
# # npx openapi-box ./tsp-output/@typespec/openapi3/openapi.v1.json
27+
# npx openapi-typescript ./tsp-output/@typespec/openapi3/openapi.v1.json -o types/openapi.ts
28+
npx openapi-ts
2729

28-
types: types-to-openapi types-to-typebox
30+
generate-types: generate-openapi generate-openapi-ts-types
2931

3032
mock:
3133
npx prism mock ./tsp-output/@typespec/openapi3/openapi.v1.json

app.js

Lines changed: 0 additions & 53 deletions
This file was deleted.

app.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import path from 'node:path';
2+
import type { AutoloadPluginOptions } from '@fastify/autoload';
3+
import AutoLoad from '@fastify/autoload';
4+
import { errors } from '@vinejs/vine';
5+
import type { FastifyPluginAsync, FastifyServerOptions } from 'fastify';
6+
import glue from 'fastify-openapi-glue';
7+
import serviceHandlers from './routes/index.ts';
8+
9+
export interface AppOptions
10+
extends FastifyServerOptions,
11+
Partial<AutoloadPluginOptions> {}
12+
13+
// Pass --options via CLI arguments in command to enable these options.
14+
const options: AppOptions = {};
15+
16+
const app: FastifyPluginAsync<AppOptions> = async (
17+
fastify,
18+
opts,
19+
): Promise<void> => {
20+
fastify.setErrorHandler((error, _request, reply) => {
21+
if (error instanceof errors.E_VALIDATION_ERROR) {
22+
const errorDetail = {
23+
status: 422,
24+
title: 'Validation Error',
25+
detail: 'Errors related to business logic such as uniqueness',
26+
errors: error.messages,
27+
};
28+
reply.type('application/problem+json').code(422).send(errorDetail);
29+
} else {
30+
reply.send(error);
31+
}
32+
});
33+
34+
fastify.addContentTypeParser(
35+
'application/problem+json',
36+
{ parseAs: 'string' },
37+
fastify.getDefaultJsonParser('ignore', 'ignore'),
38+
);
39+
40+
// This loads all plugins defined in plugins
41+
// those should be support plugins that are reused
42+
// through your application
43+
fastify.register(AutoLoad, {
44+
dir: path.join(import.meta.dirname, 'plugins'),
45+
options: opts,
46+
});
47+
48+
fastify.register(glue, {
49+
// prefix: 'v1',
50+
serviceHandlers,
51+
specification: './tsp-output/@typespec/openapi3/openapi.v1.json',
52+
});
53+
54+
// This loads all plugins defined in routes
55+
// define your routes in one of these
56+
// fastify.register(AutoLoad, {
57+
// dir: path.join(import.meta.dirname, 'routes'),
58+
// options: opts,
59+
// });
60+
};
61+
62+
export default app;
63+
export { app, options };

biome.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3+
"vcs": {
4+
"enabled": true,
5+
"clientKind": "git",
6+
"useIgnoreFile": true
7+
},
8+
"files": {
9+
"ignoreUnknown": false,
10+
"includes": [
11+
"**",
12+
"!test/**",
13+
"!public/**",
14+
"!types/handlers/**"
15+
]
16+
},
17+
"formatter": {
18+
"enabled": true,
19+
"indentStyle": "space"
20+
},
21+
"linter": {
22+
"enabled": true,
23+
"rules": {
24+
"recommended": true,
25+
"complexity": {
26+
"noBannedTypes": "off"
27+
},
28+
"correctness": {
29+
"noUnusedImports": "off",
30+
"noUnusedFunctionParameters": "off",
31+
"noUnusedVariables": "off"
32+
},
33+
"style": {
34+
"noNonNullAssertion": "off"
35+
}
36+
}
37+
},
38+
"javascript": {
39+
"formatter": {
40+
"quoteStyle": "single"
41+
}
42+
},
43+
"assist": {
44+
"enabled": true,
45+
"actions": {
46+
"source": {
47+
"organizeImports": "on"
48+
}
49+
}
50+
}
51+
}
Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,30 @@
1-
import { sql } from 'drizzle-orm'
2-
import {
3-
text,
4-
integer,
5-
sqliteTable,
6-
} from 'drizzle-orm/sqlite-core'
1+
import { sql } from 'drizzle-orm';
2+
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
73

84
export const users = sqliteTable('users', {
95
id: integer('id').primaryKey(),
106
fullName: text('full_name'),
117
email: text('email').notNull().unique(),
128
updatedAt: text('updated_at'),
13-
createdAt: text('created_at')
14-
.notNull()
15-
.default(sql`(unixepoch())`),
16-
})
9+
createdAt: text('created_at').notNull().default(sql`(unixepoch())`),
10+
});
1711

1812
export const courses = sqliteTable('courses', {
1913
id: integer('id').primaryKey(),
2014
name: text('name').notNull(),
21-
creatorId: integer('creator_id').references(() => users.id).notNull(),
15+
creatorId: integer('creator_id')
16+
.references(() => users.id)
17+
.notNull(),
2218
description: text('description').notNull(),
23-
createdAt: text('created_at')
24-
.notNull()
25-
.default(sql`(unixepoch())`),
26-
})
19+
createdAt: text('created_at').notNull().default(sql`(unixepoch())`),
20+
});
2721

2822
export const courseLessons = sqliteTable('course_lessons', {
2923
id: integer('id').primaryKey(),
3024
name: text('name').notNull(),
31-
courseId: integer('courseId').references(() => courses.id).notNull(),
25+
courseId: integer('courseId')
26+
.references(() => courses.id)
27+
.notNull(),
3228
body: text('body').notNull(),
33-
createdAt: text('created_at')
34-
.notNull()
35-
.default(sql`(unixepoch())`),
36-
})
29+
createdAt: text('created_at').notNull().default(sql`(unixepoch())`),
30+
});

db/seeds.js renamed to db/seeds.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import * as schemas from './schema.js'
2-
import { buildCourse, buildCourseLesson, buildUser } from '../lib/data.js'
1+
import { buildCourse, buildCourseLesson, buildUser } from '../lib/data.ts'
2+
import type { DrizzleDB } from '../types/index.ts'
3+
import * as schemas from './schema.ts'
34
/**
45
* @param {import("drizzle-orm/better-sqlite3").BetterSQLite3Database<typeof schemas>} db
56
*/
6-
export default async (db) => {
7+
export default async (db: DrizzleDB) => {
78
const [_user1] = await db.insert(schemas.users).values(buildUser()).returning()
89
const [user2] = await db.insert(schemas.users).values(buildUser()).returning()
910
const [_user3] = await db.insert(schemas.users).values(buildUser({

drizzle.config.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

drizzle.config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineConfig } from "drizzle-kit";
2+
3+
export default defineConfig({
4+
dialect: "sqlite",
5+
schema: "./db/schema.ts",
6+
// out: "./drizzle",
7+
});

0 commit comments

Comments
 (0)