@raiyyan/brisk 0.1.0 → 0.2.0
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/AGENTS.md +135 -67
- package/README.md +467 -139
- package/dist/src/cli/agents.js +5 -5
- package/dist/src/cli/agents.js.map +1 -1
- package/dist/src/cli/cli.d.ts +1 -1
- package/dist/src/cli/cli.d.ts.map +1 -1
- package/dist/src/cli/cli.js +22 -36
- package/dist/src/cli/cli.js.map +1 -1
- package/dist/src/cli/init.d.ts +8 -11
- package/dist/src/cli/init.d.ts.map +1 -1
- package/dist/src/cli/init.js +120 -96
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/render.d.ts +30 -0
- package/dist/src/cli/render.d.ts.map +1 -0
- package/dist/src/cli/render.js +101 -0
- package/dist/src/cli/render.js.map +1 -0
- package/dist/src/cli/system.d.ts +2 -2
- package/dist/src/cli/system.d.ts.map +1 -1
- package/dist/src/cli/system.js +3 -5
- package/dist/src/cli/system.js.map +1 -1
- package/dist/src/client/client.d.ts.map +1 -1
- package/dist/src/client/client.js +32 -5
- package/dist/src/client/client.js.map +1 -1
- package/dist/src/client/index.d.ts +1 -1
- package/dist/src/client/index.d.ts.map +1 -1
- package/dist/src/di/container.d.ts +10 -2
- package/dist/src/di/container.d.ts.map +1 -1
- package/dist/src/di/container.js +20 -3
- package/dist/src/di/container.js.map +1 -1
- package/dist/src/http/app.d.ts +41 -12
- package/dist/src/http/app.d.ts.map +1 -1
- package/dist/src/http/app.js.map +1 -1
- package/dist/src/http/context.d.ts +5 -5
- package/dist/src/http/context.js +5 -5
- package/dist/src/http/controller.d.ts +26 -9
- package/dist/src/http/controller.d.ts.map +1 -1
- package/dist/src/http/controller.js +25 -52
- package/dist/src/http/controller.js.map +1 -1
- package/dist/src/http/error-handler.d.ts +27 -0
- package/dist/src/http/error-handler.d.ts.map +1 -1
- package/dist/src/http/error-handler.js +26 -3
- package/dist/src/http/error-handler.js.map +1 -1
- package/dist/src/http/instance.d.ts +2 -2
- package/dist/src/http/instance.d.ts.map +1 -1
- package/dist/src/http/instance.js +107 -39
- package/dist/src/http/instance.js.map +1 -1
- package/dist/src/http/middleware.d.ts +104 -17
- package/dist/src/http/middleware.d.ts.map +1 -1
- package/dist/src/http/middleware.js +85 -26
- package/dist/src/http/middleware.js.map +1 -1
- package/dist/src/http/openapi.d.ts.map +1 -1
- package/dist/src/http/openapi.js +30 -2
- package/dist/src/http/openapi.js.map +1 -1
- package/dist/src/http/routes.d.ts +29 -7
- package/dist/src/http/routes.d.ts.map +1 -1
- package/dist/src/http/validation.d.ts +16 -0
- package/dist/src/http/validation.d.ts.map +1 -0
- package/dist/src/http/validation.js +51 -0
- package/dist/src/http/validation.js.map +1 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/testing/test-client.d.ts +8 -6
- package/dist/src/testing/test-client.d.ts.map +1 -1
- package/dist/src/testing/test-client.js +3 -2
- package/dist/src/testing/test-client.js.map +1 -1
- package/package.json +3 -2
- package/starters/minimal/.env.example +3 -0
- package/starters/minimal/.oxfmtrc.json +5 -0
- package/starters/minimal/.oxlintrc.json +14 -0
- package/starters/minimal/AGENTS.md +23 -0
- package/starters/minimal/CLAUDE.md +2 -0
- package/starters/minimal/README.md +53 -0
- package/starters/minimal/_gitignore +3 -0
- package/starters/minimal/package.json +29 -0
- package/starters/minimal/src/app.ts +14 -0
- package/starters/minimal/src/env.ts +7 -0
- package/starters/minimal/src/features/index.ts +8 -0
- package/starters/minimal/src/features/notes/notes.controller.ts +38 -0
- package/starters/minimal/src/features/notes/notes.repository.ts +36 -0
- package/starters/minimal/src/features/notes/notes.schema.ts +24 -0
- package/starters/minimal/src/features/notes/notes.service.ts +33 -0
- package/starters/minimal/src/main.ts +4 -0
- package/starters/minimal/src/schemas.ts +26 -0
- package/starters/minimal/templates/feature/src/features/index.ts.controllers.insert +1 -0
- package/starters/minimal/templates/feature/src/features/index.ts.imports.insert +1 -0
- package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.controller.ts +38 -0
- package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.repository.ts +36 -0
- package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.schema.ts +24 -0
- package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.service.ts +33 -0
- package/starters/minimal/templates/feature/test/{{kebab}}.service.test.ts +20 -0
- package/starters/minimal/templates/feature/test/{{kebab}}.test.ts +35 -0
- package/starters/minimal/templates/middleware/src/middlewares/{{kebab}}.ts +12 -0
- package/starters/minimal/test/notes.service.test.ts +20 -0
- package/starters/minimal/test/notes.test.ts +35 -0
- package/starters/minimal/tsconfig.json +17 -0
- package/starters/minimal/vitest.config.ts +9 -0
- package/starters/sqlite-auth/.dockerignore +8 -0
- package/starters/sqlite-auth/.env.example +9 -0
- package/starters/sqlite-auth/.oxfmtrc.json +5 -0
- package/starters/sqlite-auth/.oxlintrc.json +14 -0
- package/starters/sqlite-auth/AGENTS.md +23 -0
- package/starters/sqlite-auth/CLAUDE.md +2 -0
- package/starters/sqlite-auth/Dockerfile +23 -0
- package/starters/sqlite-auth/README.md +93 -0
- package/starters/sqlite-auth/_gitignore +5 -0
- package/starters/sqlite-auth/compose.yaml +14 -0
- package/starters/sqlite-auth/package.json +35 -0
- package/starters/sqlite-auth/public/.gitkeep +0 -0
- package/starters/sqlite-auth/src/app.ts +39 -0
- package/starters/sqlite-auth/src/auth/tokens.ts +41 -0
- package/starters/sqlite-auth/src/database/clock.ts +4 -0
- package/starters/sqlite-auth/src/database/database.ts +29 -0
- package/starters/sqlite-auth/src/database/errors.ts +11 -0
- package/starters/sqlite-auth/src/database/migrations.ts +29 -0
- package/starters/sqlite-auth/src/env.ts +17 -0
- package/starters/sqlite-auth/src/features/auth/auth.controller.ts +28 -0
- package/starters/sqlite-auth/src/features/index.ts +12 -0
- package/starters/sqlite-auth/src/features/notes/notes.controller.ts +39 -0
- package/starters/sqlite-auth/src/features/notes/notes.repository.ts +47 -0
- package/starters/sqlite-auth/src/features/notes/notes.schema.ts +24 -0
- package/starters/sqlite-auth/src/features/notes/notes.service.ts +33 -0
- package/starters/sqlite-auth/src/features/users/admin-seed.ts +21 -0
- package/starters/sqlite-auth/src/features/users/users.controller.ts +42 -0
- package/starters/sqlite-auth/src/features/users/users.errors.ts +13 -0
- package/starters/sqlite-auth/src/features/users/users.repository.ts +77 -0
- package/starters/sqlite-auth/src/features/users/users.schema.ts +35 -0
- package/starters/sqlite-auth/src/features/users/users.service.ts +80 -0
- package/starters/sqlite-auth/src/main.ts +4 -0
- package/starters/sqlite-auth/src/middlewares/authenticate.ts +29 -0
- package/starters/sqlite-auth/src/schemas.ts +26 -0
- package/starters/sqlite-auth/templates/feature/src/database/migrations.ts.migrations.insert +5 -0
- package/starters/sqlite-auth/templates/feature/src/features/index.ts.controllers.insert +1 -0
- package/starters/sqlite-auth/templates/feature/src/features/index.ts.imports.insert +1 -0
- package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.controller.ts +39 -0
- package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.repository.ts +47 -0
- package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.schema.ts +24 -0
- package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.service.ts +33 -0
- package/starters/sqlite-auth/templates/feature/test/{{kebab}}.service.test.ts +20 -0
- package/starters/sqlite-auth/templates/feature/test/{{kebab}}.test.ts +41 -0
- package/starters/sqlite-auth/templates/middleware/src/middlewares/{{kebab}}.ts +12 -0
- package/starters/sqlite-auth/test/auth.test.ts +18 -0
- package/starters/sqlite-auth/test/helpers.ts +13 -0
- package/starters/sqlite-auth/test/notes.service.test.ts +20 -0
- package/starters/sqlite-auth/test/notes.test.ts +41 -0
- package/starters/sqlite-auth/test/users.service.test.ts +25 -0
- package/starters/sqlite-auth/test/users.test.ts +164 -0
- package/starters/sqlite-auth/tsconfig.json +17 -0
- package/starters/sqlite-auth/vitest.config.ts +15 -0
- package/dist/src/cli/generate.d.ts +0 -9
- package/dist/src/cli/generate.d.ts.map +0 -1
- package/dist/src/cli/generate.js +0 -39
- package/dist/src/cli/generate.js.map +0 -1
- package/dist/src/cli/templates/auth.d.ts +0 -4
- package/dist/src/cli/templates/auth.d.ts.map +0 -1
- package/dist/src/cli/templates/auth.js +0 -90
- package/dist/src/cli/templates/auth.js.map +0 -1
- package/dist/src/cli/templates/database.d.ts +0 -4
- package/dist/src/cli/templates/database.d.ts.map +0 -1
- package/dist/src/cli/templates/database.js +0 -107
- package/dist/src/cli/templates/database.js.map +0 -1
- package/dist/src/cli/templates/docker.d.ts +0 -4
- package/dist/src/cli/templates/docker.d.ts.map +0 -1
- package/dist/src/cli/templates/docker.js +0 -102
- package/dist/src/cli/templates/docker.js.map +0 -1
- package/dist/src/cli/templates/feature.d.ts +0 -8
- package/dist/src/cli/templates/feature.d.ts.map +0 -1
- package/dist/src/cli/templates/feature.js +0 -145
- package/dist/src/cli/templates/feature.js.map +0 -1
- package/dist/src/cli/templates/project.d.ts +0 -24
- package/dist/src/cli/templates/project.d.ts.map +0 -1
- package/dist/src/cli/templates/project.js +0 -166
- package/dist/src/cli/templates/project.js.map +0 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2024",
|
|
4
|
+
"module": "nodenext",
|
|
5
|
+
"moduleResolution": "nodenext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"exactOptionalPropertyTypes": true,
|
|
8
|
+
"noUncheckedIndexedAccess": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"erasableSyntaxOnly": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"types": ["node"]
|
|
15
|
+
},
|
|
16
|
+
"include": ["src", "test", "vitest.config.ts"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
// templates/ holds the files `brisk g` renders; they are not tests until rendered.
|
|
6
|
+
include: ["test/**/*.test.ts"],
|
|
7
|
+
env: {
|
|
8
|
+
NODE_ENV: "test",
|
|
9
|
+
DATABASE_PATH: ":memory:",
|
|
10
|
+
AUTH_SECRET: "test-secret-that-is-at-least-32-characters-long",
|
|
11
|
+
ADMIN_EMAIL: "admin@example.com",
|
|
12
|
+
ADMIN_PASSWORD: "admin-password-for-tests",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { Names } from "./names.ts";
|
|
2
|
-
/**
|
|
3
|
-
* Writes a feature folder (schema, repository, service, controller) and its test, and registers the controller
|
|
4
|
-
* in `src/features/index.ts` under its camel-case name, which becomes its key in the typed client.
|
|
5
|
-
*/
|
|
6
|
-
export declare function generateFeature(root: string, names: Names): string[];
|
|
7
|
-
/** Writes a typed middleware to `src/middlewares/<name>.ts`. */
|
|
8
|
-
export declare function generateMiddleware(root: string, names: Names): string[];
|
|
9
|
-
//# sourceMappingURL=generate.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/cli/generate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAYxC;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,CA2BpE;AAED,gEAAgE;AAChE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,CAIvE"}
|
package/dist/src/cli/generate.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { CliError, write } from "./system.js";
|
|
4
|
-
import { controllerTemplate, middlewareTemplate, repositoryTemplate, schemaTemplate, serviceTemplate, testTemplate, } from "./templates/feature.js";
|
|
5
|
-
import { CONTROLLERS_MARKER, IMPORTS_MARKER, REGISTRY } from "./templates/project.js";
|
|
6
|
-
/**
|
|
7
|
-
* Writes a feature folder (schema, repository, service, controller) and its test, and registers the controller
|
|
8
|
-
* in `src/features/index.ts` under its camel-case name, which becomes its key in the typed client.
|
|
9
|
-
*/
|
|
10
|
-
export function generateFeature(root, names) {
|
|
11
|
-
const { kebab, camel } = names;
|
|
12
|
-
const dir = join(root, "src/features", kebab);
|
|
13
|
-
if (existsSync(dir))
|
|
14
|
-
throw new CliError(`src/features/${kebab} already exists`);
|
|
15
|
-
const registry = join(root, REGISTRY);
|
|
16
|
-
const source = existsSync(registry) ? readFileSync(registry, "utf8") : "";
|
|
17
|
-
if (!source.includes(IMPORTS_MARKER) || !source.includes(CONTROLLERS_MARKER)) {
|
|
18
|
-
throw new CliError(`${REGISTRY} must contain "${IMPORTS_MARKER}" and "${CONTROLLERS_MARKER}"`);
|
|
19
|
-
}
|
|
20
|
-
const created = [
|
|
21
|
-
write(join(dir, `${kebab}.schema.ts`), schemaTemplate(names)),
|
|
22
|
-
write(join(dir, `${kebab}.repository.ts`), repositoryTemplate(names)),
|
|
23
|
-
write(join(dir, `${kebab}.service.ts`), serviceTemplate(names)),
|
|
24
|
-
write(join(dir, `${kebab}.controller.ts`), controllerTemplate(names)),
|
|
25
|
-
write(join(root, "test", `${kebab}.test.ts`), testTemplate(names)),
|
|
26
|
-
];
|
|
27
|
-
writeFileSync(registry, source
|
|
28
|
-
.replace(IMPORTS_MARKER, `import { ${camel}Controller } from "./${kebab}/${kebab}.controller.ts";\n${IMPORTS_MARKER}`)
|
|
29
|
-
.replace(/^(\s*)\/\/ @brisk:controllers/m, `$1${camel}: ${camel}Controller,\n$1${CONTROLLERS_MARKER}`));
|
|
30
|
-
return created;
|
|
31
|
-
}
|
|
32
|
-
/** Writes a typed middleware to `src/middlewares/<name>.ts`. */
|
|
33
|
-
export function generateMiddleware(root, names) {
|
|
34
|
-
const path = join(root, "src/middlewares", `${names.kebab}.ts`);
|
|
35
|
-
if (existsSync(path))
|
|
36
|
-
throw new CliError(`src/middlewares/${names.kebab}.ts already exists`);
|
|
37
|
-
return [write(path, middlewareTemplate(names))];
|
|
38
|
-
}
|
|
39
|
-
//# sourceMappingURL=generate.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../src/cli/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,YAAY,GACb,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEtF;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,KAAY;IACxD,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,CAAC,CAAC;IAC9C,IAAI,UAAU,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,QAAQ,CAAC,gBAAgB,KAAK,iBAAiB,CAAC,CAAC;IAChF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC7E,MAAM,IAAI,QAAQ,CAAC,GAAG,QAAQ,kBAAkB,cAAc,UAAU,kBAAkB,GAAG,CAAC,CAAC;IACjG,CAAC;IAED,MAAM,OAAO,GAAG;QACd,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,YAAY,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;QAC7D,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACrE,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,aAAa,CAAC,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;QAC/D,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACrE,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,UAAU,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;KACnE,CAAC;IACF,aAAa,CACX,QAAQ,EACR,MAAM;SACH,OAAO,CACN,cAAc,EACd,YAAY,KAAK,wBAAwB,KAAK,IAAI,KAAK,qBAAqB,cAAc,EAAE,CAC7F;SACA,OAAO,CAAC,gCAAgC,EAAE,KAAK,KAAK,KAAK,KAAK,kBAAkB,kBAAkB,EAAE,CAAC,CACzG,CAAC;IACF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,KAAY;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,KAAK,CAAC,KAAK,KAAK,CAAC,CAAC;IAChE,IAAI,UAAU,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,QAAQ,CAAC,mBAAmB,KAAK,CAAC,KAAK,oBAAoB,CAAC,CAAC;IAC7F,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../../src/cli/templates/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,2GAA2G;AAC3G,wBAAgB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAuFzC"}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/** Bearer-token auth with HS256 tokens from `node:crypto`: no dependency, constant-time checks, expiry. */
|
|
2
|
-
export function authFiles() {
|
|
3
|
-
return {
|
|
4
|
-
files: {
|
|
5
|
-
"src/auth/tokens.ts": `import { createHmac, timingSafeEqual } from "node:crypto";
|
|
6
|
-
import { z } from "@raiyyan/brisk";
|
|
7
|
-
import { env } from "../env.ts";
|
|
8
|
-
|
|
9
|
-
export const Role = z.enum(["user", "admin"]);
|
|
10
|
-
export type Role = z.infer<typeof Role>;
|
|
11
|
-
|
|
12
|
-
const Claims = z.object({ sub: z.string(), role: Role, exp: z.number() });
|
|
13
|
-
export type Claims = z.infer<typeof Claims>;
|
|
14
|
-
|
|
15
|
-
const HEADER = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
|
|
16
|
-
|
|
17
|
-
/** Signs and verifies HS256 JSON Web Tokens with \`AUTH_SECRET\`. */
|
|
18
|
-
export class Tokens {
|
|
19
|
-
readonly #key = Buffer.from(env.AUTH_SECRET);
|
|
20
|
-
|
|
21
|
-
sign(subject: string, role: Role, ttlSeconds = env.AUTH_TOKEN_TTL_SECONDS): string {
|
|
22
|
-
const claims: Claims = { sub: subject, role, exp: Math.floor(Date.now() / 1000) + ttlSeconds };
|
|
23
|
-
const body = \`\${HEADER}.\${Buffer.from(JSON.stringify(claims)).toString("base64url")}\`;
|
|
24
|
-
return \`\${body}.\${this.#signature(body)}\`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** The token's claims, or \`undefined\` when it is malformed, forged or expired. */
|
|
28
|
-
verify(token: string): Claims | undefined {
|
|
29
|
-
const [header, payload, signature, ...rest] = token.split(".");
|
|
30
|
-
if (!header || !payload || !signature || rest.length > 0 || header !== HEADER) return undefined;
|
|
31
|
-
const expected = Buffer.from(this.#signature(\`\${header}.\${payload}\`));
|
|
32
|
-
const actual = Buffer.from(signature);
|
|
33
|
-
if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) return undefined;
|
|
34
|
-
try {
|
|
35
|
-
const claims = Claims.parse(JSON.parse(Buffer.from(payload, "base64url").toString("utf8")));
|
|
36
|
-
return claims.exp > Date.now() / 1000 ? claims : undefined;
|
|
37
|
-
} catch {
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
#signature(body: string): string {
|
|
43
|
-
return createHmac("sha256", this.#key).update(body).digest("base64url");
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
`,
|
|
47
|
-
"src/middlewares/authenticate.ts": `import { defineMiddleware, ForbiddenError, inject, UnauthorizedError } from "@raiyyan/brisk";
|
|
48
|
-
import { Tokens, type Role } from "../auth/tokens.ts";
|
|
49
|
-
|
|
50
|
-
/** Requires \`Authorization: Bearer <token>\` and adds \`user\` to ctx; request logs gain the user id. */
|
|
51
|
-
export const authenticate = defineMiddleware(({ req, log }) => {
|
|
52
|
-
const header = req.get("authorization");
|
|
53
|
-
const token = header?.startsWith("Bearer ") ? header.slice(7) : undefined;
|
|
54
|
-
const claims = token ? inject(Tokens).verify(token) : undefined;
|
|
55
|
-
if (!claims) throw new UnauthorizedError("Valid bearer token required", { challenge: "Bearer" });
|
|
56
|
-
const user = { id: claims.sub, role: claims.role };
|
|
57
|
-
return { user, log: log.child({ userId: user.id }) };
|
|
58
|
-
}, "authenticate");
|
|
59
|
-
|
|
60
|
-
/** Requires an authenticated user with \`role\`; use it after or instead of \`authenticate\`. */
|
|
61
|
-
export const requireRole = (role: Role) =>
|
|
62
|
-
defineMiddleware(async (ctx) => {
|
|
63
|
-
const { user } = await authenticate.run(ctx);
|
|
64
|
-
if (user.role !== role) throw new ForbiddenError(\`Requires the \${role} role\`);
|
|
65
|
-
return { user };
|
|
66
|
-
}, \`requireRole(\${role})\`);
|
|
67
|
-
`,
|
|
68
|
-
"test/auth.test.ts": `import { describe, expect, it } from "vitest";
|
|
69
|
-
import { Container } from "@raiyyan/brisk";
|
|
70
|
-
import { Tokens } from "../src/auth/tokens.ts";
|
|
71
|
-
|
|
72
|
-
const tokens = new Container().get(Tokens);
|
|
73
|
-
|
|
74
|
-
describe("tokens", () => {
|
|
75
|
-
it("round-trips claims", () => {
|
|
76
|
-
expect(tokens.verify(tokens.sign("u1", "admin"))).toMatchObject({ sub: "u1", role: "admin" });
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("rejects forged, malformed and expired tokens", () => {
|
|
80
|
-
const [header, payload] = tokens.sign("u1", "user").split(".");
|
|
81
|
-
expect(tokens.verify(\`\${header}.\${payload}.forged\`)).toBeUndefined();
|
|
82
|
-
expect(tokens.verify("not-a-token")).toBeUndefined();
|
|
83
|
-
expect(tokens.verify(tokens.sign("u1", "user", -1))).toBeUndefined();
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
`,
|
|
87
|
-
},
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../../src/cli/templates/auth.ts"],"names":[],"mappings":"AAEA,2GAA2G;AAC3G,MAAM,UAAU,SAAS;IACvB,OAAO;QACL,KAAK,EAAE;YACL,oBAAoB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyC3B;YACK,iCAAiC,EAAE;;;;;;;;;;;;;;;;;;;;CAoBxC;YACK,mBAAmB,EAAE;;;;;;;;;;;;;;;;;;CAkB1B;SACI;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../../src/cli/templates/database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,oGAAoG;AACpG,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE"}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/** A database provider with its lifecycle, a unique-violation mapper, and what the engine needs. */
|
|
2
|
-
export function databaseFiles(db) {
|
|
3
|
-
return db === "sqlite" ? sqlite() : postgres();
|
|
4
|
-
}
|
|
5
|
-
const errorsTemplate = (check) => `import { ConflictError, type ErrorMapper } from "@raiyyan/brisk";
|
|
6
|
-
|
|
7
|
-
/** A unique constraint failed, here or in an error the driver wrapped: answer 409 instead of 500. */
|
|
8
|
-
export const uniqueViolation: ErrorMapper = (error) => {
|
|
9
|
-
for (let current: unknown = error; current instanceof Error; current = current.cause) {
|
|
10
|
-
if (${check}) {
|
|
11
|
-
return new ConflictError("Already exists", { code: "ALREADY_EXISTS", cause: error });
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return undefined;
|
|
15
|
-
};
|
|
16
|
-
`;
|
|
17
|
-
function sqlite() {
|
|
18
|
-
return {
|
|
19
|
-
files: {
|
|
20
|
-
"src/database/database.ts": `import { DatabaseSync } from "node:sqlite";
|
|
21
|
-
import { inject, Logger, type OnInit, type OnShutdown } from "@raiyyan/brisk";
|
|
22
|
-
import { env } from "../env.ts";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The app's SQLite connection (Node's built-in \`node:sqlite\`), opened at boot and closed on shutdown.
|
|
26
|
-
* Repositories inject it: \`readonly #db = inject(Database)\`, then \`this.#db.sql.prepare(...)\`.
|
|
27
|
-
*/
|
|
28
|
-
export class Database implements OnInit, OnShutdown {
|
|
29
|
-
readonly #log = inject(Logger);
|
|
30
|
-
#connection: DatabaseSync | undefined;
|
|
31
|
-
|
|
32
|
-
get sql(): DatabaseSync {
|
|
33
|
-
if (!this.#connection) throw new Error("Database used before the app booted");
|
|
34
|
-
return this.#connection;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
onInit(): void {
|
|
38
|
-
this.#connection = new DatabaseSync(env.DATABASE_PATH);
|
|
39
|
-
this.#connection.exec("PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000;");
|
|
40
|
-
this.#log.info({ path: env.DATABASE_PATH }, "database opened");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
onShutdown(): void {
|
|
44
|
-
this.#connection?.close();
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
`,
|
|
48
|
-
"src/database/errors.ts": errorsTemplate(`"errcode" in current && (current.errcode === 2067 || current.errcode === 1555)`),
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
function postgres() {
|
|
53
|
-
return {
|
|
54
|
-
files: {
|
|
55
|
-
"src/database/database.ts": `import { inject, Logger, type OnInit, type OnShutdown } from "@raiyyan/brisk";
|
|
56
|
-
import { drizzle, type NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
57
|
-
import { Pool } from "pg";
|
|
58
|
-
import { env } from "../env.ts";
|
|
59
|
-
import * as schema from "./schema.ts";
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* The app's PostgreSQL pool with Drizzle on top. Connections open lazily; \`onInit\` checks the database is
|
|
63
|
-
* reachable before the server listens, and \`onShutdown\` closes the pool after the last request.
|
|
64
|
-
* Repositories inject it: \`readonly #db = inject(Database).db\`.
|
|
65
|
-
*/
|
|
66
|
-
export class Database implements OnInit, OnShutdown {
|
|
67
|
-
readonly #log = inject(Logger);
|
|
68
|
-
readonly pool = new Pool({ connectionString: env.DATABASE_URL, max: env.DATABASE_POOL_SIZE });
|
|
69
|
-
readonly db: NodePgDatabase<typeof schema> = drizzle(this.pool, { schema });
|
|
70
|
-
|
|
71
|
-
async onInit(): Promise<void> {
|
|
72
|
-
await this.pool.query("select 1");
|
|
73
|
-
this.#log.info("database connected");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async onShutdown(): Promise<void> {
|
|
77
|
-
await this.pool.end();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
`,
|
|
81
|
-
"src/database/schema.ts": `// Drizzle tables. Define them here, then \`npm run db:generate\` and \`npm run db:migrate\`.
|
|
82
|
-
// import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
|
83
|
-
//
|
|
84
|
-
// export const users = pgTable("users", {
|
|
85
|
-
// id: uuid().primaryKey().defaultRandom(),
|
|
86
|
-
// email: text().notNull().unique(),
|
|
87
|
-
// createdAt: timestamp().notNull().defaultNow(),
|
|
88
|
-
// });
|
|
89
|
-
export {};
|
|
90
|
-
`,
|
|
91
|
-
"src/database/errors.ts": errorsTemplate(`"code" in current && current.code === "23505"`),
|
|
92
|
-
"drizzle.config.ts": `import { defineConfig } from "drizzle-kit";
|
|
93
|
-
|
|
94
|
-
export default defineConfig({
|
|
95
|
-
dialect: "postgresql",
|
|
96
|
-
schema: "./src/database/schema.ts",
|
|
97
|
-
out: "./drizzle",
|
|
98
|
-
dbCredentials: { url: process.env["DATABASE_URL"] ?? "" },
|
|
99
|
-
});
|
|
100
|
-
`,
|
|
101
|
-
},
|
|
102
|
-
dependencies: ["pg", "drizzle-orm"],
|
|
103
|
-
devDependencies: ["drizzle-kit", "@types/pg"],
|
|
104
|
-
scripts: { "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate" },
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
//# sourceMappingURL=database.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../../../src/cli/templates/database.ts"],"names":[],"mappings":"AAEA,oGAAoG;AACpG,MAAM,UAAU,aAAa,CAAC,EAAyB;IACrD,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,cAAc,GAAG,CACrB,KAAa,EACL,EAAE,CAAC;;;;;UAKH,KAAK;;;;;;CAMd,CAAC;AAEF,SAAS,MAAM;IACb,OAAO;QACL,KAAK,EAAE;YACL,0BAA0B,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BjC;YACK,wBAAwB,EAAE,cAAc,CACtC,gFAAgF,CACjF;SACF;KACF,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ;IACf,OAAO;QACL,KAAK,EAAE;YACL,0BAA0B,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;CAyBjC;YACK,wBAAwB,EAAE;;;;;;;;;CAS/B;YACK,wBAAwB,EAAE,cAAc,CAAC,+CAA+C,CAAC;YACzF,mBAAmB,EAAE;;;;;;;;CAQ1B;SACI;QACD,YAAY,EAAE,CAAC,IAAI,EAAE,aAAa,CAAC;QACnC,eAAe,EAAE,CAAC,aAAa,EAAE,WAAW,CAAC;QAC7C,OAAO,EAAE,EAAE,aAAa,EAAE,sBAAsB,EAAE,YAAY,EAAE,qBAAqB,EAAE;KACxF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../../../src/cli/templates/docker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAgBtD,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB9E"}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
const INSTALL = {
|
|
2
|
-
pnpm: {
|
|
3
|
-
lock: "pnpm-lock.yaml",
|
|
4
|
-
prod: "pnpm install --prod --frozen-lockfile",
|
|
5
|
-
ci: "pnpm install --frozen-lockfile",
|
|
6
|
-
},
|
|
7
|
-
npm: { lock: "package-lock.json", prod: "npm ci --omit=dev", ci: "npm ci" },
|
|
8
|
-
yarn: {
|
|
9
|
-
lock: "yarn.lock",
|
|
10
|
-
prod: "yarn install --production --frozen-lockfile",
|
|
11
|
-
ci: "yarn install --frozen-lockfile",
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
|
-
/** Dockerfile and compose for `docker`, a GitHub Actions workflow for `ci`. */
|
|
15
|
-
export function dockerFiles({ pm, db, docker, ci }) {
|
|
16
|
-
const install = INSTALL[pm];
|
|
17
|
-
const files = {};
|
|
18
|
-
if (docker) {
|
|
19
|
-
files["Dockerfile"] = `# Node runs the TypeScript sources directly: no build step.
|
|
20
|
-
FROM node:26-slim
|
|
21
|
-
WORKDIR /app
|
|
22
|
-
ENV NODE_ENV=production
|
|
23
|
-
${pm === "npm" ? "" : "RUN corepack enable\n"}COPY package.json ${install.lock} ./
|
|
24
|
-
RUN ${install.prod}
|
|
25
|
-
COPY tsconfig.json ./
|
|
26
|
-
COPY src ./src
|
|
27
|
-
${db === "sqlite" ? "RUN mkdir -p /app/data && chown node:node /app/data\n" : ""}USER node
|
|
28
|
-
EXPOSE 3000
|
|
29
|
-
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \\
|
|
30
|
-
CMD node -e "fetch('http://127.0.0.1:' + (process.env.PORT ?? 3000) + '/health').then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))"
|
|
31
|
-
# SIGTERM from \`docker stop\` starts brisk's graceful shutdown.
|
|
32
|
-
CMD ["node", "src/main.ts"]
|
|
33
|
-
`;
|
|
34
|
-
files[".dockerignore"] = "node_modules\n.env\ncoverage\n.git\n*.db\n*.db-*\n";
|
|
35
|
-
files["compose.yaml"] = composeTemplate(db);
|
|
36
|
-
}
|
|
37
|
-
if (ci)
|
|
38
|
-
files[".github/workflows/ci.yml"] = ciTemplate(pm, install.ci);
|
|
39
|
-
return { files };
|
|
40
|
-
}
|
|
41
|
-
function composeTemplate(db) {
|
|
42
|
-
const app = [
|
|
43
|
-
" app:",
|
|
44
|
-
" build: .",
|
|
45
|
-
" env_file: .env",
|
|
46
|
-
" ports:",
|
|
47
|
-
' - "3000:3000"',
|
|
48
|
-
" stop_grace_period: 30s",
|
|
49
|
-
];
|
|
50
|
-
if (db === "postgres") {
|
|
51
|
-
app.push(" environment:", " DATABASE_URL: postgres://app:app@db:5432/app", " depends_on:", " db:", " condition: service_healthy");
|
|
52
|
-
return `services:
|
|
53
|
-
${app.join("\n")}
|
|
54
|
-
db:
|
|
55
|
-
image: postgres:17-alpine
|
|
56
|
-
environment:
|
|
57
|
-
POSTGRES_USER: app
|
|
58
|
-
POSTGRES_PASSWORD: app
|
|
59
|
-
POSTGRES_DB: app
|
|
60
|
-
ports:
|
|
61
|
-
- "5432:5432"
|
|
62
|
-
volumes:
|
|
63
|
-
- db:/var/lib/postgresql/data
|
|
64
|
-
healthcheck:
|
|
65
|
-
test: ["CMD-SHELL", "pg_isready -U app"]
|
|
66
|
-
interval: 5s
|
|
67
|
-
retries: 10
|
|
68
|
-
|
|
69
|
-
volumes:
|
|
70
|
-
db:
|
|
71
|
-
`;
|
|
72
|
-
}
|
|
73
|
-
if (db === "sqlite") {
|
|
74
|
-
app.push(" environment:", " DATABASE_PATH: /app/data/app.db", " volumes:", " - data:/app/data");
|
|
75
|
-
return `services:\n${app.join("\n")}\n\nvolumes:\n data:\n`;
|
|
76
|
-
}
|
|
77
|
-
return `services:\n${app.join("\n")}\n`;
|
|
78
|
-
}
|
|
79
|
-
function ciTemplate(pm, install) {
|
|
80
|
-
const setup = pm === "pnpm" ? " - uses: pnpm/action-setup@v4\n" : "";
|
|
81
|
-
return `name: CI
|
|
82
|
-
|
|
83
|
-
on:
|
|
84
|
-
push:
|
|
85
|
-
branches: [main]
|
|
86
|
-
pull_request:
|
|
87
|
-
|
|
88
|
-
jobs:
|
|
89
|
-
check:
|
|
90
|
-
runs-on: ubuntu-latest
|
|
91
|
-
steps:
|
|
92
|
-
- uses: actions/checkout@v4
|
|
93
|
-
${setup} - uses: actions/setup-node@v4
|
|
94
|
-
with:
|
|
95
|
-
node-version: 26
|
|
96
|
-
cache: ${pm}
|
|
97
|
-
- run: ${install}
|
|
98
|
-
- run: ${pm} run typecheck
|
|
99
|
-
- run: ${pm} test
|
|
100
|
-
`;
|
|
101
|
-
}
|
|
102
|
-
//# sourceMappingURL=docker.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"docker.js","sourceRoot":"","sources":["../../../../src/cli/templates/docker.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,GAAG;IACd,IAAI,EAAE;QACJ,IAAI,EAAE,gBAAgB;QACtB,IAAI,EAAE,uCAAuC;QAC7C,EAAE,EAAE,gCAAgC;KACrC;IACD,GAAG,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,mBAAmB,EAAE,EAAE,EAAE,QAAQ,EAAE;IAC3E,IAAI,EAAE;QACJ,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,6CAA6C;QACnD,EAAE,EAAE,gCAAgC;KACrC;CACO,CAAC;AAEX,+EAA+E;AAC/E,MAAM,UAAU,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAe;IAC7D,MAAM,OAAO,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;IAC5B,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,IAAI,MAAM,EAAE,CAAC;QACX,KAAK,CAAC,YAAY,CAAC,GAAG;;;;EAIxB,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,uBAAuB,qBAAqB,OAAO,CAAC,IAAI;MACxE,OAAO,CAAC,IAAI;;;EAGhB,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,uDAAuD,CAAC,CAAC,CAAC,EAAE;;;;;;CAM/E,CAAC;QACE,KAAK,CAAC,eAAe,CAAC,GAAG,oDAAoD,CAAC;QAC9E,KAAK,CAAC,cAAc,CAAC,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,EAAE;QAAE,KAAK,CAAC,0BAA0B,CAAC,GAAG,UAAU,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACvE,OAAO,EAAE,KAAK,EAAE,CAAC;AACnB,CAAC;AAED,SAAS,eAAe,CAAC,EAAqB;IAC5C,MAAM,GAAG,GAAG;QACV,QAAQ;QACR,cAAc;QACd,oBAAoB;QACpB,YAAY;QACZ,qBAAqB;QACrB,4BAA4B;KAC7B,CAAC;IACF,IAAI,EAAE,KAAK,UAAU,EAAE,CAAC;QACtB,GAAG,CAAC,IAAI,CACN,kBAAkB,EAClB,oDAAoD,EACpD,iBAAiB,EACjB,WAAW,EACX,oCAAoC,CACrC,CAAC;QACF,OAAO;EACT,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;CAkBf,CAAC;IACA,CAAC;IACD,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;QACpB,GAAG,CAAC,IAAI,CACN,kBAAkB,EAClB,uCAAuC,EACvC,cAAc,EACd,wBAAwB,CACzB,CAAC;QACF,OAAO,cAAc,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,CAAC;IAC/D,CAAC;IACD,OAAO,cAAc,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AAC1C,CAAC;AAED,SAAS,UAAU,CAAC,EAAqB,EAAE,OAAe;IACxD,MAAM,KAAK,GAAG,EAAE,KAAK,MAAM,CAAC,CAAC,CAAC,sCAAsC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,OAAO;;;;;;;;;;;;EAYP,KAAK;;;mBAGY,EAAE;eACN,OAAO;eACP,EAAE;eACF,EAAE;CAChB,CAAC;AACF,CAAC"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { Names } from "../names.ts";
|
|
2
|
-
export declare const schemaTemplate: ({ entity }: Names) => string;
|
|
3
|
-
export declare const repositoryTemplate: ({ pascal, entity }: Names) => string;
|
|
4
|
-
export declare const serviceTemplate: ({ kebab, pascal, entity, }: Names) => string;
|
|
5
|
-
export declare const controllerTemplate: ({ kebab, camel, pascal, entity, }: Names) => string;
|
|
6
|
-
export declare const testTemplate: ({ kebab, camel, }: Names) => string;
|
|
7
|
-
export declare const middlewareTemplate: ({ kebab, camel, }: Names) => string;
|
|
8
|
-
//# sourceMappingURL=feature.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../../../../src/cli/templates/feature.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,eAAO,MAAM,cAAc,eAAgB,KAAK,KAAG,MAkBlD,CAAC;AAEF,eAAO,MAAM,kBAAkB,uBAAwB,KAAK,KAAG,MA0B9D,CAAC;AAEF,eAAO,MAAM,eAAe,+BAIzB,KAAK,KAAG,MA+BV,CAAC;AAEF,eAAO,MAAM,kBAAkB,sCAK5B,KAAK,KAAG,MA6BV,CAAC;AAEF,eAAO,MAAM,YAAY,sBAGtB,KAAK,KAAG,MAuBV,CAAC;AAEF,eAAO,MAAM,kBAAkB,sBAG5B,KAAK,KAAG,MAWV,CAAC"}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
export const schemaTemplate = ({ entity }) => `import { z } from "@raiyyan/brisk";
|
|
2
|
-
|
|
3
|
-
export const Create${entity}Body = z.object({
|
|
4
|
-
name: z.string().trim().min(1),
|
|
5
|
-
});
|
|
6
|
-
|
|
7
|
-
// Keep .default() out of the create schema: .partial() keeps defaults, and a PATCH would reset omitted fields.
|
|
8
|
-
export const Update${entity}Body = Create${entity}Body.partial();
|
|
9
|
-
|
|
10
|
-
export const ${entity}IdParams = z.object({ id: z.uuid() });
|
|
11
|
-
|
|
12
|
-
export const ${entity}Response = z.object({
|
|
13
|
-
id: z.string(),
|
|
14
|
-
name: z.string(),
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
export type Create${entity}Body = z.infer<typeof Create${entity}Body>;
|
|
18
|
-
export type Update${entity}Body = z.infer<typeof Update${entity}Body>;
|
|
19
|
-
`;
|
|
20
|
-
export const repositoryTemplate = ({ pascal, entity }) => `export interface ${entity} {
|
|
21
|
-
id: string;
|
|
22
|
-
name: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** In-memory storage. Swap the map for your database; the service does not change. */
|
|
26
|
-
export class ${pascal}Repository {
|
|
27
|
-
readonly #rows = new Map<string, ${entity}>();
|
|
28
|
-
|
|
29
|
-
findAll(): ${entity}[] {
|
|
30
|
-
return [...this.#rows.values()];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
findById(id: string): ${entity} | undefined {
|
|
34
|
-
return this.#rows.get(id);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
save(row: ${entity}): ${entity} {
|
|
38
|
-
this.#rows.set(row.id, row);
|
|
39
|
-
return row;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
delete(id: string): boolean {
|
|
43
|
-
return this.#rows.delete(id);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
`;
|
|
47
|
-
export const serviceTemplate = ({ kebab, pascal, entity, }) => `import { randomUUID } from "node:crypto";
|
|
48
|
-
import { inject, NotFoundError } from "@raiyyan/brisk";
|
|
49
|
-
import { ${pascal}Repository, type ${entity} } from "./${kebab}.repository.ts";
|
|
50
|
-
import type { Create${entity}Body, Update${entity}Body } from "./${kebab}.schema.ts";
|
|
51
|
-
|
|
52
|
-
export class ${pascal}Service {
|
|
53
|
-
readonly #repository = inject(${pascal}Repository);
|
|
54
|
-
|
|
55
|
-
list(): ${entity}[] {
|
|
56
|
-
return this.#repository.findAll();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
get(id: string): ${entity} {
|
|
60
|
-
const row = this.#repository.findById(id);
|
|
61
|
-
if (!row) throw new NotFoundError("${entity} not found", { details: { id } });
|
|
62
|
-
return row;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
create(input: Create${entity}Body): ${entity} {
|
|
66
|
-
return this.#repository.save({ id: randomUUID(), ...input });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
update(id: string, { name }: Update${entity}Body): ${entity} {
|
|
70
|
-
const row = this.get(id);
|
|
71
|
-
return this.#repository.save({ ...row, name: name ?? row.name });
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
remove(id: string): void {
|
|
75
|
-
if (!this.#repository.delete(id)) throw new NotFoundError("${entity} not found", { details: { id } });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
`;
|
|
79
|
-
export const controllerTemplate = ({ kebab, camel, pascal, entity, }) => `import { defineController, inject } from "@raiyyan/brisk";
|
|
80
|
-
import { Create${entity}Body, ${entity}IdParams, ${entity}Response, Update${entity}Body } from "./${kebab}.schema.ts";
|
|
81
|
-
import { ${pascal}Service } from "./${kebab}.service.ts";
|
|
82
|
-
|
|
83
|
-
export const ${camel}Controller = defineController({ prefix: "/${kebab}" }, (route) => {
|
|
84
|
-
const ${camel} = inject(${pascal}Service);
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
list: route.get("/", { response: ${entity}Response.array() }, () => ${camel}.list()),
|
|
88
|
-
|
|
89
|
-
get: route.get("/:id", { params: ${entity}IdParams, response: ${entity}Response }, ({ params }) =>
|
|
90
|
-
${camel}.get(params.id),
|
|
91
|
-
),
|
|
92
|
-
|
|
93
|
-
create: route.post("/", { body: Create${entity}Body, response: ${entity}Response }, ({ body }) =>
|
|
94
|
-
${camel}.create(body),
|
|
95
|
-
),
|
|
96
|
-
|
|
97
|
-
update: route.patch(
|
|
98
|
-
"/:id",
|
|
99
|
-
{ params: ${entity}IdParams, body: Update${entity}Body, response: ${entity}Response },
|
|
100
|
-
({ params, body }) => ${camel}.update(params.id, body),
|
|
101
|
-
),
|
|
102
|
-
|
|
103
|
-
remove: route.delete("/:id", { params: ${entity}IdParams }, ({ params }) => {
|
|
104
|
-
${camel}.remove(params.id);
|
|
105
|
-
}),
|
|
106
|
-
};
|
|
107
|
-
});
|
|
108
|
-
`;
|
|
109
|
-
export const testTemplate = ({ kebab, camel, }) => `import { afterAll, describe, expect, it } from "vitest";
|
|
110
|
-
import { createTestClient } from "@raiyyan/brisk/testing";
|
|
111
|
-
import { app } from "../src/app.ts";
|
|
112
|
-
|
|
113
|
-
const api = await createTestClient(app);
|
|
114
|
-
afterAll(() => api.close());
|
|
115
|
-
|
|
116
|
-
describe("${kebab}", () => {
|
|
117
|
-
it("creates, reads, updates and deletes", async () => {
|
|
118
|
-
const created = await api.call.${camel}.create({ body: { name: "first" } });
|
|
119
|
-
const { id } = created;
|
|
120
|
-
|
|
121
|
-
expect(await api.call.${camel}.get({ params: { id } })).toEqual(created);
|
|
122
|
-
expect(await api.call.${camel}.update({ params: { id }, body: { name: "second" } })).toEqual({ id, name: "second" });
|
|
123
|
-
await api.call.${camel}.remove({ params: { id } });
|
|
124
|
-
await expect(api.call.${camel}.get({ params: { id } })).rejects.toMatchObject({ status: 404 });
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("rejects invalid input with field issues", async () => {
|
|
128
|
-
const res = await api.post("/api/${kebab}", { body: { name: "" } });
|
|
129
|
-
expect(res.status).toBe(400);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
`;
|
|
133
|
-
export const middlewareTemplate = ({ kebab, camel, }) => `import { BadRequestError, defineMiddleware } from "@raiyyan/brisk";
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Reads the \`x-${kebab}\` header and adds \`${camel}\` to the ctx of every route that uses it.
|
|
137
|
-
* Replace the check with your own logic; return nothing for a check-only middleware.
|
|
138
|
-
*/
|
|
139
|
-
export const ${camel} = defineMiddleware(({ req }) => {
|
|
140
|
-
const value = req.get("x-${kebab}");
|
|
141
|
-
if (!value) throw new BadRequestError("Missing x-${kebab} header", { code: "MISSING_${kebab.toUpperCase().replaceAll("-", "_")}" });
|
|
142
|
-
return { ${camel}: value };
|
|
143
|
-
}, "${camel}");
|
|
144
|
-
`;
|
|
145
|
-
//# sourceMappingURL=feature.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"feature.js","sourceRoot":"","sources":["../../../../src/cli/templates/feature.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,EAAE,MAAM,EAAS,EAAU,EAAE,CAAC;;qBAExC,MAAM;;;;;qBAKN,MAAM,gBAAgB,MAAM;;eAElC,MAAM;;eAEN,MAAM;;;;;oBAKD,MAAM,+BAA+B,MAAM;oBAC3C,MAAM,+BAA+B,MAAM;CAC9D,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,EAAS,EAAU,EAAE,CAAC,oBAAoB,MAAM;;;;;;eAMpF,MAAM;qCACgB,MAAM;;eAE5B,MAAM;;;;0BAIK,MAAM;;;;cAIlB,MAAM,MAAM,MAAM;;;;;;;;;CAS/B,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAC9B,KAAK,EACL,MAAM,EACN,MAAM,GACA,EAAU,EAAE,CAAC;;WAEV,MAAM,oBAAoB,MAAM,cAAc,KAAK;sBACxC,MAAM,eAAe,MAAM,kBAAkB,KAAK;;eAEzD,MAAM;kCACa,MAAM;;YAE5B,MAAM;;;;qBAIG,MAAM;;yCAEc,MAAM;;;;wBAIvB,MAAM,UAAU,MAAM;;;;uCAIP,MAAM,UAAU,MAAM;;;;;;iEAMI,MAAM;;;CAGtE,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,EACjC,KAAK,EACL,KAAK,EACL,MAAM,EACN,MAAM,GACA,EAAU,EAAE,CAAC;iBACJ,MAAM,SAAS,MAAM,aAAa,MAAM,mBAAmB,MAAM,kBAAkB,KAAK;WAC9F,MAAM,qBAAqB,KAAK;;eAE5B,KAAK,6CAA6C,KAAK;UAC5D,KAAK,aAAa,MAAM;;;uCAGK,MAAM,6BAA6B,KAAK;;uCAExC,MAAM,uBAAuB,MAAM;QAClE,KAAK;;;4CAG+B,MAAM,mBAAmB,MAAM;QACnE,KAAK;;;;;kBAKK,MAAM,yBAAyB,MAAM,mBAAmB,MAAM;8BAClD,KAAK;;;6CAGU,MAAM;QAC3C,KAAK;;;;CAIZ,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,EAC3B,KAAK,EACL,KAAK,GACC,EAAU,EAAE,CAAC;;;;;;;YAOT,KAAK;;qCAEoB,KAAK;;;4BAGd,KAAK;4BACL,KAAK;qBACZ,KAAK;4BACE,KAAK;;;;uCAIM,KAAK;;;;CAI3C,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,EACjC,KAAK,EACL,KAAK,GACC,EAAU,EAAE,CAAC;;;mBAGF,KAAK,wBAAwB,KAAK;;;eAGtC,KAAK;6BACS,KAAK;qDACmB,KAAK,8BAA8B,KAAK,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC;aACnH,KAAK;MACZ,KAAK;CACV,CAAC"}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { PackageManager } from "../system.ts";
|
|
2
|
-
export declare const PACKAGE = "@raiyyan/brisk";
|
|
3
|
-
export declare const IMPORTS_MARKER = "// @brisk:imports";
|
|
4
|
-
export declare const CONTROLLERS_MARKER = "// @brisk:controllers";
|
|
5
|
-
export declare const REGISTRY = "src/features/index.ts";
|
|
6
|
-
/** What `brisk init` was asked to set up. */
|
|
7
|
-
export interface InitOptions {
|
|
8
|
-
db: "none" | "sqlite" | "postgres";
|
|
9
|
-
auth: boolean;
|
|
10
|
-
docker: boolean;
|
|
11
|
-
ci: boolean;
|
|
12
|
-
web: boolean;
|
|
13
|
-
pm: PackageManager;
|
|
14
|
-
}
|
|
15
|
-
/** Everything a set of options adds to a project. */
|
|
16
|
-
export interface Plan {
|
|
17
|
-
files: Record<string, string>;
|
|
18
|
-
dependencies: string[];
|
|
19
|
-
devDependencies: string[];
|
|
20
|
-
scripts: Record<string, string>;
|
|
21
|
-
}
|
|
22
|
-
/** Composes the base project with every chosen option. */
|
|
23
|
-
export declare function projectPlan(options: InitOptions): Plan;
|
|
24
|
-
//# sourceMappingURL=project.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../../../src/cli/templates/project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAKnD,eAAO,MAAM,OAAO,mBAAmB,CAAC;AACxC,eAAO,MAAM,cAAc,sBAAsB,CAAC;AAClD,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAC1D,eAAO,MAAM,QAAQ,0BAA0B,CAAC;AAEhD,6CAA6C;AAC7C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;IACZ,GAAG,EAAE,OAAO,CAAC;IACb,EAAE,EAAE,cAAc,CAAC;CACpB;AAED,qDAAqD;AACrD,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,0DAA0D;AAC1D,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CA+BtD"}
|