@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.
Files changed (174) hide show
  1. package/AGENTS.md +135 -67
  2. package/README.md +467 -139
  3. package/dist/src/cli/agents.js +5 -5
  4. package/dist/src/cli/agents.js.map +1 -1
  5. package/dist/src/cli/cli.d.ts +1 -1
  6. package/dist/src/cli/cli.d.ts.map +1 -1
  7. package/dist/src/cli/cli.js +22 -36
  8. package/dist/src/cli/cli.js.map +1 -1
  9. package/dist/src/cli/init.d.ts +8 -11
  10. package/dist/src/cli/init.d.ts.map +1 -1
  11. package/dist/src/cli/init.js +120 -96
  12. package/dist/src/cli/init.js.map +1 -1
  13. package/dist/src/cli/render.d.ts +30 -0
  14. package/dist/src/cli/render.d.ts.map +1 -0
  15. package/dist/src/cli/render.js +101 -0
  16. package/dist/src/cli/render.js.map +1 -0
  17. package/dist/src/cli/system.d.ts +2 -2
  18. package/dist/src/cli/system.d.ts.map +1 -1
  19. package/dist/src/cli/system.js +3 -5
  20. package/dist/src/cli/system.js.map +1 -1
  21. package/dist/src/client/client.d.ts.map +1 -1
  22. package/dist/src/client/client.js +32 -5
  23. package/dist/src/client/client.js.map +1 -1
  24. package/dist/src/client/index.d.ts +1 -1
  25. package/dist/src/client/index.d.ts.map +1 -1
  26. package/dist/src/di/container.d.ts +10 -2
  27. package/dist/src/di/container.d.ts.map +1 -1
  28. package/dist/src/di/container.js +20 -3
  29. package/dist/src/di/container.js.map +1 -1
  30. package/dist/src/http/app.d.ts +41 -12
  31. package/dist/src/http/app.d.ts.map +1 -1
  32. package/dist/src/http/app.js.map +1 -1
  33. package/dist/src/http/context.d.ts +5 -5
  34. package/dist/src/http/context.js +5 -5
  35. package/dist/src/http/controller.d.ts +26 -9
  36. package/dist/src/http/controller.d.ts.map +1 -1
  37. package/dist/src/http/controller.js +25 -52
  38. package/dist/src/http/controller.js.map +1 -1
  39. package/dist/src/http/error-handler.d.ts +27 -0
  40. package/dist/src/http/error-handler.d.ts.map +1 -1
  41. package/dist/src/http/error-handler.js +26 -3
  42. package/dist/src/http/error-handler.js.map +1 -1
  43. package/dist/src/http/instance.d.ts +2 -2
  44. package/dist/src/http/instance.d.ts.map +1 -1
  45. package/dist/src/http/instance.js +107 -39
  46. package/dist/src/http/instance.js.map +1 -1
  47. package/dist/src/http/middleware.d.ts +104 -17
  48. package/dist/src/http/middleware.d.ts.map +1 -1
  49. package/dist/src/http/middleware.js +85 -26
  50. package/dist/src/http/middleware.js.map +1 -1
  51. package/dist/src/http/openapi.d.ts.map +1 -1
  52. package/dist/src/http/openapi.js +30 -2
  53. package/dist/src/http/openapi.js.map +1 -1
  54. package/dist/src/http/routes.d.ts +29 -7
  55. package/dist/src/http/routes.d.ts.map +1 -1
  56. package/dist/src/http/validation.d.ts +16 -0
  57. package/dist/src/http/validation.d.ts.map +1 -0
  58. package/dist/src/http/validation.js +51 -0
  59. package/dist/src/http/validation.js.map +1 -0
  60. package/dist/src/index.d.ts +2 -2
  61. package/dist/src/index.d.ts.map +1 -1
  62. package/dist/src/index.js +2 -2
  63. package/dist/src/index.js.map +1 -1
  64. package/dist/src/testing/test-client.d.ts +8 -6
  65. package/dist/src/testing/test-client.d.ts.map +1 -1
  66. package/dist/src/testing/test-client.js +3 -2
  67. package/dist/src/testing/test-client.js.map +1 -1
  68. package/package.json +3 -2
  69. package/starters/minimal/.env.example +3 -0
  70. package/starters/minimal/.oxfmtrc.json +5 -0
  71. package/starters/minimal/.oxlintrc.json +14 -0
  72. package/starters/minimal/AGENTS.md +23 -0
  73. package/starters/minimal/CLAUDE.md +2 -0
  74. package/starters/minimal/README.md +53 -0
  75. package/starters/minimal/_gitignore +3 -0
  76. package/starters/minimal/package.json +29 -0
  77. package/starters/minimal/src/app.ts +14 -0
  78. package/starters/minimal/src/env.ts +7 -0
  79. package/starters/minimal/src/features/index.ts +8 -0
  80. package/starters/minimal/src/features/notes/notes.controller.ts +38 -0
  81. package/starters/minimal/src/features/notes/notes.repository.ts +36 -0
  82. package/starters/minimal/src/features/notes/notes.schema.ts +24 -0
  83. package/starters/minimal/src/features/notes/notes.service.ts +33 -0
  84. package/starters/minimal/src/main.ts +4 -0
  85. package/starters/minimal/src/schemas.ts +26 -0
  86. package/starters/minimal/templates/feature/src/features/index.ts.controllers.insert +1 -0
  87. package/starters/minimal/templates/feature/src/features/index.ts.imports.insert +1 -0
  88. package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.controller.ts +38 -0
  89. package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.repository.ts +36 -0
  90. package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.schema.ts +24 -0
  91. package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.service.ts +33 -0
  92. package/starters/minimal/templates/feature/test/{{kebab}}.service.test.ts +20 -0
  93. package/starters/minimal/templates/feature/test/{{kebab}}.test.ts +35 -0
  94. package/starters/minimal/templates/middleware/src/middlewares/{{kebab}}.ts +12 -0
  95. package/starters/minimal/test/notes.service.test.ts +20 -0
  96. package/starters/minimal/test/notes.test.ts +35 -0
  97. package/starters/minimal/tsconfig.json +17 -0
  98. package/starters/minimal/vitest.config.ts +9 -0
  99. package/starters/sqlite-auth/.dockerignore +8 -0
  100. package/starters/sqlite-auth/.env.example +9 -0
  101. package/starters/sqlite-auth/.oxfmtrc.json +5 -0
  102. package/starters/sqlite-auth/.oxlintrc.json +14 -0
  103. package/starters/sqlite-auth/AGENTS.md +23 -0
  104. package/starters/sqlite-auth/CLAUDE.md +2 -0
  105. package/starters/sqlite-auth/Dockerfile +23 -0
  106. package/starters/sqlite-auth/README.md +93 -0
  107. package/starters/sqlite-auth/_gitignore +5 -0
  108. package/starters/sqlite-auth/compose.yaml +14 -0
  109. package/starters/sqlite-auth/package.json +35 -0
  110. package/starters/sqlite-auth/public/.gitkeep +0 -0
  111. package/starters/sqlite-auth/src/app.ts +39 -0
  112. package/starters/sqlite-auth/src/auth/tokens.ts +41 -0
  113. package/starters/sqlite-auth/src/database/clock.ts +4 -0
  114. package/starters/sqlite-auth/src/database/database.ts +29 -0
  115. package/starters/sqlite-auth/src/database/errors.ts +11 -0
  116. package/starters/sqlite-auth/src/database/migrations.ts +29 -0
  117. package/starters/sqlite-auth/src/env.ts +17 -0
  118. package/starters/sqlite-auth/src/features/auth/auth.controller.ts +28 -0
  119. package/starters/sqlite-auth/src/features/index.ts +12 -0
  120. package/starters/sqlite-auth/src/features/notes/notes.controller.ts +39 -0
  121. package/starters/sqlite-auth/src/features/notes/notes.repository.ts +47 -0
  122. package/starters/sqlite-auth/src/features/notes/notes.schema.ts +24 -0
  123. package/starters/sqlite-auth/src/features/notes/notes.service.ts +33 -0
  124. package/starters/sqlite-auth/src/features/users/admin-seed.ts +21 -0
  125. package/starters/sqlite-auth/src/features/users/users.controller.ts +42 -0
  126. package/starters/sqlite-auth/src/features/users/users.errors.ts +13 -0
  127. package/starters/sqlite-auth/src/features/users/users.repository.ts +77 -0
  128. package/starters/sqlite-auth/src/features/users/users.schema.ts +35 -0
  129. package/starters/sqlite-auth/src/features/users/users.service.ts +80 -0
  130. package/starters/sqlite-auth/src/main.ts +4 -0
  131. package/starters/sqlite-auth/src/middlewares/authenticate.ts +29 -0
  132. package/starters/sqlite-auth/src/schemas.ts +26 -0
  133. package/starters/sqlite-auth/templates/feature/src/database/migrations.ts.migrations.insert +5 -0
  134. package/starters/sqlite-auth/templates/feature/src/features/index.ts.controllers.insert +1 -0
  135. package/starters/sqlite-auth/templates/feature/src/features/index.ts.imports.insert +1 -0
  136. package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.controller.ts +39 -0
  137. package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.repository.ts +47 -0
  138. package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.schema.ts +24 -0
  139. package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.service.ts +33 -0
  140. package/starters/sqlite-auth/templates/feature/test/{{kebab}}.service.test.ts +20 -0
  141. package/starters/sqlite-auth/templates/feature/test/{{kebab}}.test.ts +41 -0
  142. package/starters/sqlite-auth/templates/middleware/src/middlewares/{{kebab}}.ts +12 -0
  143. package/starters/sqlite-auth/test/auth.test.ts +18 -0
  144. package/starters/sqlite-auth/test/helpers.ts +13 -0
  145. package/starters/sqlite-auth/test/notes.service.test.ts +20 -0
  146. package/starters/sqlite-auth/test/notes.test.ts +41 -0
  147. package/starters/sqlite-auth/test/users.service.test.ts +25 -0
  148. package/starters/sqlite-auth/test/users.test.ts +164 -0
  149. package/starters/sqlite-auth/tsconfig.json +17 -0
  150. package/starters/sqlite-auth/vitest.config.ts +15 -0
  151. package/dist/src/cli/generate.d.ts +0 -9
  152. package/dist/src/cli/generate.d.ts.map +0 -1
  153. package/dist/src/cli/generate.js +0 -39
  154. package/dist/src/cli/generate.js.map +0 -1
  155. package/dist/src/cli/templates/auth.d.ts +0 -4
  156. package/dist/src/cli/templates/auth.d.ts.map +0 -1
  157. package/dist/src/cli/templates/auth.js +0 -90
  158. package/dist/src/cli/templates/auth.js.map +0 -1
  159. package/dist/src/cli/templates/database.d.ts +0 -4
  160. package/dist/src/cli/templates/database.d.ts.map +0 -1
  161. package/dist/src/cli/templates/database.js +0 -107
  162. package/dist/src/cli/templates/database.js.map +0 -1
  163. package/dist/src/cli/templates/docker.d.ts +0 -4
  164. package/dist/src/cli/templates/docker.d.ts.map +0 -1
  165. package/dist/src/cli/templates/docker.js +0 -102
  166. package/dist/src/cli/templates/docker.js.map +0 -1
  167. package/dist/src/cli/templates/feature.d.ts +0 -8
  168. package/dist/src/cli/templates/feature.d.ts.map +0 -1
  169. package/dist/src/cli/templates/feature.js +0 -145
  170. package/dist/src/cli/templates/feature.js.map +0 -1
  171. package/dist/src/cli/templates/project.d.ts +0 -24
  172. package/dist/src/cli/templates/project.d.ts.map +0 -1
  173. package/dist/src/cli/templates/project.js +0 -166
  174. 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"}
@@ -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,4 +0,0 @@
1
- import type { Plan } from "./project.ts";
2
- /** Bearer-token auth with HS256 tokens from `node:crypto`: no dependency, constant-time checks, expiry. */
3
- export declare function authFiles(): Partial<Plan>;
4
- //# sourceMappingURL=auth.d.ts.map
@@ -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,4 +0,0 @@
1
- import type { Plan } from "./project.ts";
2
- /** A database provider with its lifecycle, a unique-violation mapper, and what the engine needs. */
3
- export declare function databaseFiles(db: "sqlite" | "postgres"): Partial<Plan>;
4
- //# sourceMappingURL=database.d.ts.map
@@ -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,4 +0,0 @@
1
- import type { InitOptions, Plan } from "./project.ts";
2
- /** Dockerfile and compose for `docker`, a GitHub Actions workflow for `ci`. */
3
- export declare function dockerFiles({ pm, db, docker, ci }: InitOptions): Partial<Plan>;
4
- //# sourceMappingURL=docker.d.ts.map
@@ -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"}