create-nifra 2.1.0 → 2.3.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 (160) hide show
  1. package/README.md +4 -4
  2. package/dist/agent-files.d.ts +14 -14
  3. package/dist/agent-files.js +29 -29
  4. package/dist/agents.d.ts +4 -4
  5. package/dist/agents.js +63 -63
  6. package/dist/auth.d.ts +2 -2
  7. package/dist/auth.js +8 -8
  8. package/dist/cli.d.ts +6 -6
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +56 -35
  11. package/dist/cli.js.map +1 -1
  12. package/dist/db.d.ts +6 -6
  13. package/dist/db.d.ts.map +1 -1
  14. package/dist/db.js +138 -41
  15. package/dist/db.js.map +1 -1
  16. package/dist/scaffold/features.d.ts +41 -0
  17. package/dist/scaffold/features.d.ts.map +1 -0
  18. package/dist/scaffold/features.js +48 -0
  19. package/dist/scaffold/features.js.map +1 -0
  20. package/dist/scaffold/frameworks.d.ts +73 -0
  21. package/dist/scaffold/frameworks.d.ts.map +1 -0
  22. package/dist/scaffold/frameworks.js +100 -0
  23. package/dist/scaffold/frameworks.js.map +1 -0
  24. package/dist/scaffold/site-build.d.ts +60 -0
  25. package/dist/scaffold/site-build.d.ts.map +1 -0
  26. package/dist/scaffold/site-build.js +198 -0
  27. package/dist/scaffold/site-build.js.map +1 -0
  28. package/dist/scaffold/site-files.d.ts +16 -0
  29. package/dist/scaffold/site-files.d.ts.map +1 -0
  30. package/dist/scaffold/site-files.js +112 -0
  31. package/dist/scaffold/site-files.js.map +1 -0
  32. package/dist/scaffold/site.d.ts +33 -0
  33. package/dist/scaffold/site.d.ts.map +1 -0
  34. package/dist/scaffold/site.js +99 -0
  35. package/dist/scaffold/site.js.map +1 -0
  36. package/package.json +3 -2
  37. package/src/agent-files.ts +149 -0
  38. package/template/README.md +3 -3
  39. package/template/nifra.assurance.ts +80 -0
  40. package/template/package.json +5 -3
  41. package/template/src/app.ts +10 -11
  42. package/template/src/routes.ts +13 -0
  43. package/template-isr/README.md +1 -1
  44. package/template-isr/backend.ts +11 -15
  45. package/template-isr/nifra.assurance.ts +81 -0
  46. package/template-isr/package.json +8 -7
  47. package/template-isr/page.ts +12 -0
  48. package/template-isr/routes/_404.tsx +1 -1
  49. package/template-isr/routes/_layout.tsx +1 -1
  50. package/template-isr/routes/index.tsx +2 -2
  51. package/template-isr/server.ts +1 -1
  52. package/template-isr/worker.ts +2 -2
  53. package/template-isr/wrangler.toml +2 -2
  54. package/template-site/Dockerfile +1 -1
  55. package/template-site/README.md +6 -6
  56. package/template-site/backend.ts +11 -13
  57. package/template-site/counter.ts +16 -0
  58. package/template-site/nifra.assurance.ts +83 -0
  59. package/template-site/nifra.config.ts +1 -1
  60. package/template-site/routes/index.tsx +1 -1
  61. package/template-site/server-node.ts +1 -1
  62. package/template-site/server-vercel.ts +1 -1
  63. package/template-site-preact/README.md +7 -7
  64. package/template-site-preact/nifra.config.ts +1 -1
  65. package/template-site-preact/routes/index.tsx +1 -1
  66. package/template-site-solid/README.md +7 -7
  67. package/template-site-solid/nifra.config.ts +2 -2
  68. package/template-site-solid/routes/index.tsx +1 -1
  69. package/template-site-svelte/README.md +7 -7
  70. package/template-site-svelte/nifra.config.ts +2 -2
  71. package/template-site-svelte/routes/_layout.svelte +1 -1
  72. package/template-site-svelte/routes/index.svelte +1 -1
  73. package/template-site-vue/README.md +7 -7
  74. package/template-site-vue/nifra.config.ts +2 -2
  75. package/template-site-vue/routes/_layout.vue +2 -2
  76. package/template-site-vue/routes/index.vue +3 -3
  77. package/template-site/build-bun.ts +0 -27
  78. package/template-site/build-deno.ts +0 -25
  79. package/template-site/build-node.ts +0 -26
  80. package/template-site/build-vercel.ts +0 -45
  81. package/template-site/build.ts +0 -29
  82. package/template-site/framework.ts +0 -6
  83. package/template-site/package.json +0 -38
  84. package/template-site/tsconfig.json +0 -37
  85. package/template-site-preact/.dockerignore +0 -14
  86. package/template-site-preact/Dockerfile +0 -20
  87. package/template-site-preact/_worker.ts +0 -17
  88. package/template-site-preact/backend.ts +0 -15
  89. package/template-site-preact/build-bun.ts +0 -27
  90. package/template-site-preact/build-deno.ts +0 -25
  91. package/template-site-preact/build-node.ts +0 -26
  92. package/template-site-preact/build-vercel.ts +0 -45
  93. package/template-site-preact/build.ts +0 -29
  94. package/template-site-preact/deno.json +0 -7
  95. package/template-site-preact/framework.ts +0 -6
  96. package/template-site-preact/gitignore +0 -16
  97. package/template-site-preact/package.json +0 -35
  98. package/template-site-preact/server-bun.ts +0 -35
  99. package/template-site-preact/server-deno.ts +0 -38
  100. package/template-site-preact/server-node.ts +0 -40
  101. package/template-site-preact/server-vercel.ts +0 -17
  102. package/template-site-preact/tsconfig.json +0 -38
  103. package/template-site-preact/wrangler.toml +0 -4
  104. package/template-site-solid/.dockerignore +0 -14
  105. package/template-site-solid/Dockerfile +0 -20
  106. package/template-site-solid/_worker.ts +0 -17
  107. package/template-site-solid/backend.ts +0 -15
  108. package/template-site-solid/build-bun.ts +0 -33
  109. package/template-site-solid/build-deno.ts +0 -32
  110. package/template-site-solid/build-node.ts +0 -33
  111. package/template-site-solid/build-vercel.ts +0 -47
  112. package/template-site-solid/build.ts +0 -36
  113. package/template-site-solid/deno.json +0 -7
  114. package/template-site-solid/framework.ts +0 -6
  115. package/template-site-solid/gitignore +0 -16
  116. package/template-site-solid/package.json +0 -35
  117. package/template-site-solid/server-bun.ts +0 -35
  118. package/template-site-solid/server-deno.ts +0 -38
  119. package/template-site-solid/server-node.ts +0 -40
  120. package/template-site-solid/server-vercel.ts +0 -17
  121. package/template-site-solid/tsconfig.json +0 -38
  122. package/template-site-solid/wrangler.toml +0 -4
  123. package/template-site-svelte/.dockerignore +0 -14
  124. package/template-site-svelte/Dockerfile +0 -20
  125. package/template-site-svelte/_worker.ts +0 -17
  126. package/template-site-svelte/backend.ts +0 -15
  127. package/template-site-svelte/build-bun.ts +0 -32
  128. package/template-site-svelte/build-deno.ts +0 -31
  129. package/template-site-svelte/build-node.ts +0 -32
  130. package/template-site-svelte/build-vercel.ts +0 -46
  131. package/template-site-svelte/build.ts +0 -35
  132. package/template-site-svelte/deno.json +0 -7
  133. package/template-site-svelte/framework.ts +0 -6
  134. package/template-site-svelte/gitignore +0 -16
  135. package/template-site-svelte/package.json +0 -35
  136. package/template-site-svelte/server-bun.ts +0 -35
  137. package/template-site-svelte/server-deno.ts +0 -38
  138. package/template-site-svelte/server-node.ts +0 -40
  139. package/template-site-svelte/server-vercel.ts +0 -17
  140. package/template-site-svelte/tsconfig.json +0 -37
  141. package/template-site-svelte/wrangler.toml +0 -4
  142. package/template-site-vue/.dockerignore +0 -14
  143. package/template-site-vue/Dockerfile +0 -20
  144. package/template-site-vue/_worker.ts +0 -17
  145. package/template-site-vue/backend.ts +0 -15
  146. package/template-site-vue/build-bun.ts +0 -37
  147. package/template-site-vue/build-deno.ts +0 -36
  148. package/template-site-vue/build-node.ts +0 -37
  149. package/template-site-vue/build-vercel.ts +0 -54
  150. package/template-site-vue/build.ts +0 -40
  151. package/template-site-vue/deno.json +0 -7
  152. package/template-site-vue/framework.ts +0 -6
  153. package/template-site-vue/gitignore +0 -16
  154. package/template-site-vue/package.json +0 -36
  155. package/template-site-vue/server-bun.ts +0 -35
  156. package/template-site-vue/server-deno.ts +0 -38
  157. package/template-site-vue/server-node.ts +0 -40
  158. package/template-site-vue/server-vercel.ts +0 -17
  159. package/template-site-vue/tsconfig.json +0 -37
  160. package/template-site-vue/wrangler.toml +0 -4
package/README.md CHANGED
@@ -11,11 +11,11 @@ bun create nifra my-app
11
11
 
12
12
  `--template <name>` (default `api`):
13
13
 
14
- - **`api`** — a typed nifra server (`src/app.ts` + `src/index.ts`) with an example test and
14
+ - **`api`** - a typed nifra server (`src/app.ts` + `src/index.ts`) with an example test and
15
15
  `dev`/`start`/`test`/`typecheck` scripts.
16
- - **`site`** — a nifra + React SSR site, one source deployable to Cloudflare Pages, Node, Deno, or
16
+ - **`site`** - a nifra + React SSR site, one source deployable to Cloudflare Pages, Node, Deno, or
17
17
  Vercel Edge.
18
- - **`isr`** — a nifra + React app with **Incremental Static Regeneration** on Cloudflare Workers + KV
18
+ - **`isr`** - a nifra + React app with **Incremental Static Regeneration** on Cloudflare Workers + KV
19
19
  (pages cached + served stale-while-revalidate; on-demand purge endpoint).
20
20
 
21
21
  ```sh
@@ -34,7 +34,7 @@ MIT.
34
34
 
35
35
  ## For AI agents
36
36
 
37
- Start with [`LLM.md`](./LLM.md) — this package's contract card (the exports you call + its footguns),
37
+ Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
38
38
  one cheap read instead of the whole corpus. For the wider framework: the repo's
39
39
  [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
40
40
  [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
@@ -2,33 +2,33 @@
2
2
  * The canonical generators for the agent-discovery files an app ships so a coding agent auto-discovers
3
3
  * the project's nifra MCP server and prefers it over writing nifra from memory:
4
4
  *
5
- * .mcp.json — Claude Code's project MCP registry ({ mcpServers: { nifra: { command, args } } })
6
- * .cursor/mcp.json — Cursor's MCP registry (same server config)
7
- * CLAUDE.md — Claude's preamble (use the MCP first; gate on `nifra check`) + `@AGENTS.md` import
8
- * AGENTS.md "MCP" — a section for non-Claude agents pointing at the same server
5
+ * .mcp.json - Claude Code's project MCP registry ({ mcpServers: { nifra: { command, args } } })
6
+ * .cursor/mcp.json - Cursor's MCP registry (same server config)
7
+ * CLAUDE.md - Claude's preamble (use the MCP first; gate on `nifra check`) + `@AGENTS.md` import
8
+ * AGENTS.md "MCP" - a section for non-Claude agents pointing at the same server
9
9
  *
10
10
  * Single source of truth: both `create-nifra` (scaffold time) and `@nifrajs/cli`'s `nifra init-agents`
11
11
  * (retrofit an existing app) import these, so the four files can never drift apart. This module is
12
- * dependency-free (pure string + object generation) on purpose — `create-nifra` ships with no runtime
12
+ * dependency-free (pure string + object generation) on purpose - `create-nifra` ships with no runtime
13
13
  * deps, and `@nifrajs/cli` imports it as a workspace dependency.
14
14
  *
15
15
  * Why `bunx @nifrajs/cli mcp` (not `bunx nifra mcp`): the `nifra` binary is provided by the
16
16
  * `@nifrajs/cli` package. A scaffolded `site` app carries `@nifrajs/cli` as a devDependency, so either
17
- * spelling would resolve `node_modules/.bin/nifra` locally — BUT the `api`/`isr` templates do NOT carry
17
+ * spelling would resolve `node_modules/.bin/nifra` locally - BUT the `api`/`isr` templates do NOT carry
18
18
  * `@nifrajs/cli`, and the bare npm package literally named `nifra` (this monorepo's `@nifrajs/web` shim)
19
19
  * exposes NO `nifra` bin. So `bunx nifra mcp` would fetch the wrong package and fail there. Naming the
20
20
  * bin-owning package, `bunx @nifrajs/cli mcp`, resolves the locally-installed bin when present and
21
- * otherwise fetches the package that actually provides the `nifra` command — robust across every
21
+ * otherwise fetches the package that actually provides the `nifra` command - robust across every
22
22
  * template and for any existing app.
23
23
  */
24
24
  /** The MCP launch command, shared by `.mcp.json` and `.cursor/mcp.json`. See the module header for why
25
25
  * the package is named explicitly rather than relying on the bare `nifra` bin. */
26
26
  export declare const MCP_SERVER_COMMAND: "bunx";
27
27
  /**
28
- * The `@nifrajs/cli` version the launch command pins to — DERIVED at load time from this package's own
28
+ * The `@nifrajs/cli` version the launch command pins to - DERIVED at load time from this package's own
29
29
  * `version`, never hardcoded. `fixed` changeset versioning ([["@nifrajs/*", "create-nifra", "nifra"]] in
30
30
  * `.changeset/config.json`) bumps `create-nifra` and `@nifrajs/cli` in lockstep, so `create-nifra@x`
31
- * always means `@nifrajs/cli@x`. Deriving it here means a release bump has nothing to forget to update —
31
+ * always means `@nifrajs/cli@x`. Deriving it here means a release bump has nothing to forget to update -
32
32
  * there is no stale literal to drift (the 1.0.0 cut shipped a beta pin exactly this way). `check:publish`
33
33
  * still asserts it equals `@nifrajs/cli`'s version as a belt-and-suspenders guard on the `fixed` link.
34
34
  *
@@ -39,11 +39,11 @@ export declare const MCP_SERVER_COMMAND: "bunx";
39
39
  *
40
40
  * Why pin at all instead of `bunx @nifrajs/cli mcp`: `bunx` keys its cache on the exact version spec.
41
41
  * An UNPINNED spec resolves to the `latest` tag once, then `bunx` reuses that cached copy on every
42
- * later spawn WITHOUT re-checking the registry — so an editor that once launched an older `@nifrajs/cli`
42
+ * later spawn WITHOUT re-checking the registry - so an editor that once launched an older `@nifrajs/cli`
43
43
  * keeps respawning the stale binary even after a newer one is published (the MCP server silently runs
44
44
  * old code). Pinning the exact version makes the version part of the cache key, so each release fetches
45
45
  * fresh and a stale cache can never shadow it. The cost: an already-scaffolded app's `.mcp.json` freezes
46
- * at its scaffold-time version until `nifra init-agents` is re-run — an acceptable, deterministic trade.
46
+ * at its scaffold-time version until `nifra init-agents` is re-run - an acceptable, deterministic trade.
47
47
  */
48
48
  export declare const MCP_CLI_VERSION: string;
49
49
  export declare const MCP_SERVER_ARGS: readonly [`@nifrajs/cli@${string}`, "mcp"];
@@ -56,16 +56,16 @@ export interface McpServerConfig {
56
56
  export interface McpConfig {
57
57
  readonly mcpServers: Readonly<Record<string, McpServerConfig>>;
58
58
  }
59
- /** The one canonical MCP config object both registries serialize — the anti-drift seam. */
59
+ /** The one canonical MCP config object both registries serialize - the anti-drift seam. */
60
60
  export declare const MCP_CONFIG: McpConfig;
61
61
  /** Serialize the canonical MCP config as the JSON written to `.mcp.json` and `.cursor/mcp.json`.
62
62
  * Trailing newline so the file is POSIX-clean and diffs don't flag a missing EOL. */
63
63
  export declare function mcpJson(): string;
64
64
  /**
65
- * `CLAUDE.md` — Claude Code reads this automatically. It is deliberately NOT a copy of `AGENTS.md`:
65
+ * `CLAUDE.md` - Claude Code reads this automatically. It is deliberately NOT a copy of `AGENTS.md`:
66
66
  * a short preamble that (1) tells Claude this project ships a nifra MCP, registered in `.mcp.json`, and
67
67
  * to PREFER it, and (2) pulls in the full cookbook with Claude Code's `@file` import directive on its
68
- * own line — `@AGENTS.md` — so the conventions live in exactly one place and can't drift between the
68
+ * own line - `@AGENTS.md` - so the conventions live in exactly one place and can't drift between the
69
69
  * two files. Keep this short; the depth is in `AGENTS.md`.
70
70
  */
71
71
  export declare function claudeMd(): string;
@@ -2,23 +2,23 @@
2
2
  * The canonical generators for the agent-discovery files an app ships so a coding agent auto-discovers
3
3
  * the project's nifra MCP server and prefers it over writing nifra from memory:
4
4
  *
5
- * .mcp.json — Claude Code's project MCP registry ({ mcpServers: { nifra: { command, args } } })
6
- * .cursor/mcp.json — Cursor's MCP registry (same server config)
7
- * CLAUDE.md — Claude's preamble (use the MCP first; gate on `nifra check`) + `@AGENTS.md` import
8
- * AGENTS.md "MCP" — a section for non-Claude agents pointing at the same server
5
+ * .mcp.json - Claude Code's project MCP registry ({ mcpServers: { nifra: { command, args } } })
6
+ * .cursor/mcp.json - Cursor's MCP registry (same server config)
7
+ * CLAUDE.md - Claude's preamble (use the MCP first; gate on `nifra check`) + `@AGENTS.md` import
8
+ * AGENTS.md "MCP" - a section for non-Claude agents pointing at the same server
9
9
  *
10
10
  * Single source of truth: both `create-nifra` (scaffold time) and `@nifrajs/cli`'s `nifra init-agents`
11
11
  * (retrofit an existing app) import these, so the four files can never drift apart. This module is
12
- * dependency-free (pure string + object generation) on purpose — `create-nifra` ships with no runtime
12
+ * dependency-free (pure string + object generation) on purpose - `create-nifra` ships with no runtime
13
13
  * deps, and `@nifrajs/cli` imports it as a workspace dependency.
14
14
  *
15
15
  * Why `bunx @nifrajs/cli mcp` (not `bunx nifra mcp`): the `nifra` binary is provided by the
16
16
  * `@nifrajs/cli` package. A scaffolded `site` app carries `@nifrajs/cli` as a devDependency, so either
17
- * spelling would resolve `node_modules/.bin/nifra` locally — BUT the `api`/`isr` templates do NOT carry
17
+ * spelling would resolve `node_modules/.bin/nifra` locally - BUT the `api`/`isr` templates do NOT carry
18
18
  * `@nifrajs/cli`, and the bare npm package literally named `nifra` (this monorepo's `@nifrajs/web` shim)
19
19
  * exposes NO `nifra` bin. So `bunx nifra mcp` would fetch the wrong package and fail there. Naming the
20
20
  * bin-owning package, `bunx @nifrajs/cli mcp`, resolves the locally-installed bin when present and
21
- * otherwise fetches the package that actually provides the `nifra` command — robust across every
21
+ * otherwise fetches the package that actually provides the `nifra` command - robust across every
22
22
  * template and for any existing app.
23
23
  */
24
24
  import { readFileSync } from "node:fs";
@@ -26,10 +26,10 @@ import { readFileSync } from "node:fs";
26
26
  * the package is named explicitly rather than relying on the bare `nifra` bin. */
27
27
  export const MCP_SERVER_COMMAND = "bunx";
28
28
  /**
29
- * The `@nifrajs/cli` version the launch command pins to — DERIVED at load time from this package's own
29
+ * The `@nifrajs/cli` version the launch command pins to - DERIVED at load time from this package's own
30
30
  * `version`, never hardcoded. `fixed` changeset versioning ([["@nifrajs/*", "create-nifra", "nifra"]] in
31
31
  * `.changeset/config.json`) bumps `create-nifra` and `@nifrajs/cli` in lockstep, so `create-nifra@x`
32
- * always means `@nifrajs/cli@x`. Deriving it here means a release bump has nothing to forget to update —
32
+ * always means `@nifrajs/cli@x`. Deriving it here means a release bump has nothing to forget to update -
33
33
  * there is no stale literal to drift (the 1.0.0 cut shipped a beta pin exactly this way). `check:publish`
34
34
  * still asserts it equals `@nifrajs/cli`'s version as a belt-and-suspenders guard on the `fixed` link.
35
35
  *
@@ -40,15 +40,15 @@ export const MCP_SERVER_COMMAND = "bunx";
40
40
  *
41
41
  * Why pin at all instead of `bunx @nifrajs/cli mcp`: `bunx` keys its cache on the exact version spec.
42
42
  * An UNPINNED spec resolves to the `latest` tag once, then `bunx` reuses that cached copy on every
43
- * later spawn WITHOUT re-checking the registry — so an editor that once launched an older `@nifrajs/cli`
43
+ * later spawn WITHOUT re-checking the registry - so an editor that once launched an older `@nifrajs/cli`
44
44
  * keeps respawning the stale binary even after a newer one is published (the MCP server silently runs
45
45
  * old code). Pinning the exact version makes the version part of the cache key, so each release fetches
46
46
  * fresh and a stale cache can never shadow it. The cost: an already-scaffolded app's `.mcp.json` freezes
47
- * at its scaffold-time version until `nifra init-agents` is re-run — an acceptable, deterministic trade.
47
+ * at its scaffold-time version until `nifra init-agents` is re-run - an acceptable, deterministic trade.
48
48
  */
49
49
  export const MCP_CLI_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
50
50
  export const MCP_SERVER_ARGS = [`@nifrajs/cli@${MCP_CLI_VERSION}`, "mcp"];
51
- /** The one canonical MCP config object both registries serialize — the anti-drift seam. */
51
+ /** The one canonical MCP config object both registries serialize - the anti-drift seam. */
52
52
  export const MCP_CONFIG = {
53
53
  mcpServers: {
54
54
  nifra: { command: MCP_SERVER_COMMAND, args: [...MCP_SERVER_ARGS] },
@@ -60,31 +60,31 @@ export function mcpJson() {
60
60
  return `${JSON.stringify(MCP_CONFIG, null, 2)}\n`;
61
61
  }
62
62
  /**
63
- * `CLAUDE.md` — Claude Code reads this automatically. It is deliberately NOT a copy of `AGENTS.md`:
63
+ * `CLAUDE.md` - Claude Code reads this automatically. It is deliberately NOT a copy of `AGENTS.md`:
64
64
  * a short preamble that (1) tells Claude this project ships a nifra MCP, registered in `.mcp.json`, and
65
65
  * to PREFER it, and (2) pulls in the full cookbook with Claude Code's `@file` import directive on its
66
- * own line — `@AGENTS.md` — so the conventions live in exactly one place and can't drift between the
66
+ * own line - `@AGENTS.md` - so the conventions live in exactly one place and can't drift between the
67
67
  * two files. Keep this short; the depth is in `AGENTS.md`.
68
68
  */
69
69
  export function claudeMd() {
70
70
  return `# CLAUDE.md
71
71
 
72
72
  This project ships a **nifra MCP server**, registered for Claude Code in \`.mcp.json\` (it launches with
73
- \`${MCP_SERVER_COMMAND} ${MCP_SERVER_ARGS.join(" ")}\`). **Prefer the MCP tools** over writing nifra from memory — they are
73
+ \`${MCP_SERVER_COMMAND} ${MCP_SERVER_ARGS.join(" ")}\`). **Prefer the MCP tools** over writing nifra from memory - they are
74
74
  typechecked against *this* project and this installed version, so they beat training-data recall:
75
75
 
76
- - \`nifra_docs\` / \`nifra_example\` — exact signatures + verified, compiling snippets. Reach for these
76
+ - \`nifra_docs\` / \`nifra_example\` - exact signatures + verified, compiling snippets. Reach for these
77
77
  before hand-writing a route, a loader, or a client call; a remembered API is often stale or wrong.
78
- - \`nifra_context\` — this project's route index + conventions, so changes fit the existing surface.
79
- - \`nifra_types\` — the EXACT TypeScript of any \`@nifrajs/*\` symbol (e.g. \`nifra_types({ name: "RateLimitStore" })\`).
80
- Call it for a precise type; **never read \`node_modules/@nifrajs/**/*.d.ts\`** — the tool is the authoritative, complete source.
78
+ - \`nifra_context\` - this project's route index + conventions, so changes fit the existing surface.
79
+ - \`nifra_types\` - the EXACT TypeScript of any \`@nifrajs/*\` symbol (e.g. \`nifra_types({ name: "RateLimitStore" })\`).
80
+ Call it for a precise type; **never read \`node_modules/@nifrajs/**/*.d.ts\`** - the tool is the authoritative, complete source.
81
81
  - **\`nifra_check\` is the done-gate.** Before declaring any change complete, run \`nifra_check\` (or
82
82
  \`nifra check --json\` in a terminal). It typechecks the frontend↔backend contract and flags drift
83
83
  (hand-rolled \`fetch()\` to this app's own API, server-only imports in \`routes/\`). A failing check
84
- means the work isn't done — fix it, don't ship around it.
84
+ means the work isn't done - fix it, don't ship around it.
85
85
 
86
- The full nifra cookbook for this app — backend rules, the typed never-throwing client, file routing, and
87
- the gotchas — lives in \`AGENTS.md\`, imported here so it stays the single source of truth:
86
+ The full nifra cookbook for this app - backend rules, the typed never-throwing client, file routing, and
87
+ the gotchas - lives in \`AGENTS.md\`, imported here so it stays the single source of truth:
88
88
 
89
89
  @AGENTS.md
90
90
  `;
@@ -97,16 +97,16 @@ the gotchas — lives in \`AGENTS.md\`, imported here so it stays the single sou
97
97
  export function agentsMcpSection() {
98
98
  return `## MCP server
99
99
 
100
- This project ships a **nifra MCP server** — launch it with \`${MCP_SERVER_COMMAND} ${MCP_SERVER_ARGS.join(" ")}\`. It's registered for
100
+ This project ships a **nifra MCP server** - launch it with \`${MCP_SERVER_COMMAND} ${MCP_SERVER_ARGS.join(" ")}\`. It's registered for
101
101
  Claude Code in \`.mcp.json\` and for Cursor in \`.cursor/mcp.json\`; other agents can point their MCP client
102
- at that command. **Prefer its tools** over writing nifra from memory — they're typechecked against this
102
+ at that command. **Prefer its tools** over writing nifra from memory - they're typechecked against this
103
103
  project + the installed version, so they beat training-data recall:
104
104
 
105
- - \`nifra_docs\` / \`nifra_example\` — exact signatures + verified, compiling snippets.
106
- - \`nifra_context\` / \`nifra_routes\` — this project's route index + per-route schemas.
107
- - \`nifra_types\` — the EXACT TypeScript of any \`@nifrajs/*\` symbol (interface/type/function). Call it for a
108
- precise type; **never read \`@nifrajs\` \`.d.ts\` files** — this is the authoritative, complete source.
109
- - \`nifra_check\` — the done-gate (typecheck + drift lints). Run it before calling work complete; a
105
+ - \`nifra_docs\` / \`nifra_example\` - exact signatures + verified, compiling snippets.
106
+ - \`nifra_context\` / \`nifra_routes\` - this project's route index + per-route schemas.
107
+ - \`nifra_types\` - the EXACT TypeScript of any \`@nifrajs/*\` symbol (interface/type/function). Call it for a
108
+ precise type; **never read \`@nifrajs\` \`.d.ts\` files** - this is the authoritative, complete source.
109
+ - \`nifra_check\` - the done-gate (typecheck + drift lints). Run it before calling work complete; a
110
110
  failing check means it isn't done. (\`nifra check --json\` in a terminal does the same.)`;
111
111
  }
112
112
  /** The standalone files this module fully owns (whole-file generators). AGENTS.md is handled separately
package/dist/agents.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Generate the `AGENTS.md` a scaffolded app ships with — the conventions file read by coding agents
2
+ * Generate the `AGENTS.md` a scaffolded app ships with - the conventions file read by coding agents
3
3
  * (Claude Code, Cursor, …). It encodes nifra's non-obvious rules so an agent writes correct code from
4
4
  * the first prompt: validate at the boundary, the never-throwing client, `app.fetch` as the universal
5
- * entry, and (for full-stack apps) the one gotcha that bites everyone — never top-level-import
5
+ * entry, and (for full-stack apps) the one gotcha that bites everyone - never top-level-import
6
6
  * server-only code into a route module. Tailored per template so the paths + commands are real.
7
7
  */
8
8
  import type { AuthChoice } from "./auth.js";
@@ -12,9 +12,9 @@ export interface AgentsMdOptions {
12
12
  readonly template: TemplateName;
13
13
  readonly framework: Framework;
14
14
  readonly name: string;
15
- /** Drizzle preset, when scaffolded with `--db` — adds a Database section. */
15
+ /** Drizzle preset, when scaffolded with `--db` - adds a Database section. */
16
16
  readonly db?: DbChoice;
17
- /** Auth preset, when scaffolded with `--auth` — adds an Authentication section. */
17
+ /** Auth preset, when scaffolded with `--auth` - adds an Authentication section. */
18
18
  readonly auth?: AuthChoice;
19
19
  }
20
20
  /** Build the full `AGENTS.md` for a freshly scaffolded app. */
package/dist/agents.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Generate the `AGENTS.md` a scaffolded app ships with — the conventions file read by coding agents
2
+ * Generate the `AGENTS.md` a scaffolded app ships with - the conventions file read by coding agents
3
3
  * (Claude Code, Cursor, …). It encodes nifra's non-obvious rules so an agent writes correct code from
4
4
  * the first prompt: validate at the boundary, the never-throwing client, `app.fetch` as the universal
5
- * entry, and (for full-stack apps) the one gotcha that bites everyone — never top-level-import
5
+ * entry, and (for full-stack apps) the one gotcha that bites everyone - never top-level-import
6
6
  * server-only code into a route module. Tailored per template so the paths + commands are real.
7
7
  */
8
8
  import { agentsMcpSection } from "./agent-files.js";
@@ -14,7 +14,7 @@ const FRAMEWORK_LABEL = {
14
14
  preact: "Preact",
15
15
  svelte: "Svelte",
16
16
  };
17
- /** Shared backend rules — every template is, at its core, a nifra `server()`. */
17
+ /** Shared backend rules - every template is, at its core, a nifra `server()`. */
18
18
  const API_RULES = `## The backend: \`server()\`
19
19
 
20
20
  \`\`\`ts
@@ -39,54 +39,54 @@ export type App = typeof app
39
39
  Rules an agent must follow:
40
40
 
41
41
  - **Validate every input at the boundary.** The route schema slots are \`{ body, query }\` (plus
42
- \`response\`, below) — use \`t\` from \`@nifrajs/schema\` (installed) or any Standard Schema (zod, valibot).
42
+ \`response\`, below) - use \`t\` from \`@nifrajs/schema\` (installed) or any Standard Schema (zod, valibot).
43
43
  Read the typed, already-validated \`c.body\` / \`c.query\` (invalid input was rejected with a 422 before the
44
- handler ran). **Never** hand-parse \`await c.req.json()\` and poke at properties — that's the bug class the
44
+ handler ran). **Never** hand-parse \`await c.req.json()\` and poke at properties - that's the bug class the
45
45
  schema exists to remove.
46
46
  - **Path params are NOT a schema slot.** \`:id\` etc. are inferred from the path literal as \`string\` and
47
47
  read via \`c.params.id\`; there is no \`params\` (or \`headers\`) key in the route schema. Validate a param's
48
- shape inside the handler (a length/format check), not via \`{ params: ... }\` — that's a type error.
48
+ shape inside the handler (a length/format check), not via \`{ params: ... }\` - that's a type error.
49
49
  - **Lock the output shape with \`response\` (no drift).** Add \`{ response: t.object({...}) }\` to a route:
50
50
  the handler's return is type-checked against it, and the typed client sees exactly that shape. One
51
- contract, both sides — the frontend physically can't drift from the backend's output.
51
+ contract, both sides - the frontend physically can't drift from the backend's output.
52
52
  - **Data-or-error routes: set the status with \`c.set.status\`, then read the error off \`res.error\` (NOT \`res.data\`).**
53
53
  The typed client maps \`res.ok\` to the HTTP status. A route that does \`c.set.status = 404; return { ok: false,
54
54
  error: "not_found" }\` arrives at the client as \`res.ok === false\`, \`res.data === null\`, and the body on
55
55
  \`res.error\` (\`res.error.error === "not_found"\`). So branch on \`res.ok\` FIRST, then read \`res.data\` on success
56
- or \`res.error\` on failure — never assert a 4xx onto \`res.data\`. Declaring the error arm in \`response\`
56
+ or \`res.error\` on failure - never assert a 4xx onto \`res.data\`. Declaring the error arm in \`response\`
57
57
  (\`t.union([Item, t.object({ ok: t.literal(false), error: t.string() })])\`) documents the shape and keeps the
58
58
  success \`res.data\` precise, but a 4xx still surfaces via \`res.error\`. Do NOT return a raw \`Response\` just to
59
59
  set an error status.
60
60
  - **Raw \`Response\` is for redirects / streams / files** (\`Response.redirect(url, 302)\`, a file body, an
61
61
  SSE stream). The typed client infers \`res.data: never\` on such a route and \`nifra check\` emits a
62
- non-blocking \`response-route\` warning — both are EXPECTED there, not defects.
63
- - **\`app.fetch(request)\` is the whole app** — \`(Request) => Response | Promise<Response>\`. Tests drive it
62
+ non-blocking \`response-route\` warning - both are EXPECTED there, not defects.
63
+ - **\`app.fetch(request)\` is the whole app** - \`(Request) => Response | Promise<Response>\`. Tests drive it
64
64
  directly (\`await app.fetch(new Request("http://x/users/1"))\`); no server needs to be running.
65
65
  - **Don't reach for a heavy ORM/HTTP layer.** Routing, validation, cookies, and the typed client are
66
66
  built in. Parameterize DB queries; use \`timingSafeEqual\` (or WebCrypto) for secret comparison.
67
- - **Cross-cutting concerns — use \`@nifrajs/middleware\`, don't hand-roll.** Rate limiting (\`429\` +
67
+ - **Cross-cutting concerns - use \`@nifrajs/middleware\`, don't hand-roll.** Rate limiting (\`429\` +
68
68
  \`Retry-After\`), CORS, security headers, body limits, auth (\`bearer\`/\`apiKey\`/\`basicAuth\`/\`jwt\`), CSRF,
69
- IP restriction, response caching, compression — all \`app.use(...)\` plugins in \`@nifrajs/middleware\`.
69
+ IP restriction, response caching, compression - all \`app.use(...)\` plugins in \`@nifrajs/middleware\`.
70
70
  Call \`nifra_docs("middleware")\` for the full list + usage before building one of these by hand.
71
71
 
72
- ## The client is typed + never throws — ALWAYS use it for internal API calls
72
+ ## The client is typed + never throws - ALWAYS use it for internal API calls
73
73
 
74
74
  \`\`\`ts
75
75
  import { client } from "@nifrajs/client"
76
- import type { app } from "./app" // type-only import — server code never ships to the client
76
+ import type { app } from "./app" // type-only import - server code never ships to the client
77
77
 
78
78
  const api = client<typeof app>("https://api.example.com")
79
79
  const res = await api.users({ id: "42" }).get()
80
- if (res.ok) res.data // typed from the route's return type — zero codegen
80
+ if (res.ok) res.data // typed from the route's return type - zero codegen
81
81
  else res.error // errors are returned, never thrown
82
82
  \`\`\`
83
83
 
84
- **Never hand-roll \`fetch\` + ad-hoc response types for this app's own API** — that's exactly how a screen
84
+ **Never hand-roll \`fetch\` + ad-hoc response types for this app's own API** - that's exactly how a screen
85
85
  drifts from the backend. \`client<typeof app>\` derives both the request inputs and \`res.data\` from the
86
86
  route types, so \`tsc\` (and \`nifra build\`) catch any mismatch the moment a route changes. When building a
87
87
  page that calls the API, reach for the client first.
88
88
  `;
89
- /** File-routing + the server-only gotcha — only for full-stack (site/isr) templates. */
89
+ /** File-routing + the server-only gotcha - only for full-stack (site/isr) templates. */
90
90
  function webRules(framework) {
91
91
  const label = FRAMEWORK_LABEL[framework];
92
92
  return `## The frontend: file routing + loaders (${label})
@@ -96,7 +96,7 @@ function webRules(framework) {
96
96
  - A route file may export: \`default\` (the page component), \`loader\` (server-only data), \`action\`
97
97
  (server-only mutation), and \`meta\` (head tags). The \`loader\` returns data typed straight into the page.
98
98
  - **The \`server()\` backend is IN-PROCESS, not a public HTTP surface.** Loaders/actions call it through
99
- \`ctx.api\` (the typed client — no network) during SSR; there is no \`GET /your-route\` endpoint on the page
99
+ \`ctx.api\` (the typed client - no network) during SSR; there is no \`GET /your-route\` endpoint on the page
100
100
  server to curl. Build features through loaders/actions + the typed client, not direct HTTP calls.
101
101
  - Swap UI frameworks by changing one import (\`@nifrajs/web-${framework}\` → another adapter); your route,
102
102
  loader, and action code do not change.
@@ -108,10 +108,10 @@ function webRules(framework) {
108
108
  ## ⚠️ The one rule that bites everyone: never import server-only code at a route's top level
109
109
 
110
110
  A route module's \`loader\`/\`action\` run **only on the server**, but the module is **also bundled for the
111
- browser** (for the component) — and the loader is *not* stripped. So a top-level
111
+ browser** (for the component) - and the loader is *not* stripped. So a top-level
112
112
  \`import db from "./db"\` (or anything touching \`process.env\`, secrets, \`node:\` APIs) ships server code to
113
113
  the client and crashes hydration. Reach server resources through \`ctx.api\` / \`ctx.env\` (injected via
114
- \`createWebApp\`) inside the loader instead — never as a top-level import in a \`routes/\` file.
114
+ \`createWebApp\`) inside the loader instead - never as a top-level import in a \`routes/\` file.
115
115
  `;
116
116
  }
117
117
  /** Per-ORM body of the DB rules: the query idiom, schema location, and migrate story all differ. */
@@ -133,11 +133,11 @@ singleton \`PrismaClient\`).
133
133
  c.db.note.create({ data: { title: c.body.title } }),
134
134
  )
135
135
  \`\`\`
136
- - **Migrations:** edit \`prisma/schema.prisma\`, then \`bun run db:migrate\` (\`prisma migrate dev\` — creates +
136
+ - **Migrations:** edit \`prisma/schema.prisma\`, then \`bun run db:migrate\` (\`prisma migrate dev\` - creates +
137
137
  applies the migration and regenerates the client). \`bun run db:studio\` opens a DB browser.`;
138
138
  }
139
139
  if (orm === "kysely") {
140
- return `A Kysely typed query builder is wired in \`db/\`: \`db/schema.ts\` (the DB-shape interface — keep it in
140
+ return `A Kysely typed query builder is wired in \`db/\`: \`db/schema.ts\` (the DB-shape interface - keep it in
141
141
  sync with your migrations) + \`db/index.ts\` (the typed client). You own the migrations (\`db/migrations/\`,
142
142
  run by \`db/migrate.ts\`).
143
143
 
@@ -176,25 +176,25 @@ typed client).
176
176
  - **Migrations:** edit \`db/schema.ts\`, then \`bun run db:generate\` (writes SQL to \`db/migrations\`) +
177
177
  \`bun run db:migrate\` (applies it). \`bun run db:studio\` opens a DB browser.`;
178
178
  }
179
- /** DB rules — only when scaffolded with `--db`. Teaches the wired data layer + the `c.db` seam. */
179
+ /** DB rules - only when scaffolded with `--db`. Teaches the wired data layer + the `c.db` seam. */
180
180
  function dbRules(db) {
181
181
  const p = DB_PRESETS[db];
182
182
  return `## Database (${p.label})
183
183
 
184
184
  ${dbRulesBody(p.orm)} ${p.note}
185
185
 
186
- - **Query is fully typed** — types flow from the schema/model; \`c.db\` is typed end to end.
187
- - **Never top-level-import \`db\` into a \`routes/\` page file** — it's server-only; reach it via \`c.db\` on
186
+ - **Query is fully typed** - types flow from the schema/model; \`c.db\` is typed end to end.
187
+ - **Never top-level-import \`db\` into a \`routes/\` page file** - it's server-only; reach it via \`c.db\` on
188
188
  the backend (or \`ctx.api\` from a loader). A top-level import ships it to the browser and breaks the build.`;
189
189
  }
190
- /** Auth rules — only when scaffolded with `--auth`. Teaches the better-auth mount + the typed guards. */
190
+ /** Auth rules - only when scaffolded with `--auth`. Teaches the better-auth mount + the typed guards. */
191
191
  function authRules(_auth) {
192
192
  return `## Authentication (better-auth)
193
193
 
194
194
  better-auth is configured in \`auth.ts\` (email/password + sessions, backed by your scaffolded database via
195
195
  its ORM adapter). nifra mounts it and gives you typed session guards via \`@nifrajs/better-auth\`.
196
196
 
197
- - **Mount it once** on the backend — it serves every auth endpoint under \`/api/auth/*\`:
197
+ - **Mount it once** on the backend - it serves every auth endpoint under \`/api/auth/*\`:
198
198
  \`\`\`ts
199
199
  import { server } from "@nifrajs/core/server"
200
200
  import { betterAuth } from "@nifrajs/better-auth"
@@ -204,7 +204,7 @@ its ORM adapter). nifra mounts it and gives you typed session guards via \`@nifr
204
204
  - **Generate the auth tables** once, and after changing the config: \`bunx @better-auth/cli@latest generate\`
205
205
  writes them into your schema (\`db/schema.ts\` for Drizzle, \`prisma/schema.prisma\` for Prisma); then
206
206
  \`bun run db:migrate\`.
207
- - **Read / require a session** with the typed guards (pass the raw \`Request\` — \`c.req\` in a handler, or a
207
+ - **Read / require a session** with the typed guards (pass the raw \`Request\` - \`c.req\` in a handler, or a
208
208
  loader's \`request\`):
209
209
  \`\`\`ts
210
210
  import { getSession, requireSession } from "@nifrajs/better-auth"
@@ -214,74 +214,74 @@ its ORM adapter). nifra mounts it and gives you typed session guards via \`@nifr
214
214
  return { id: user.id }
215
215
  })
216
216
  \`\`\`
217
- - \`auth\` is **server-only** — never import it into a \`routes/\` page, and keep \`BETTER_AUTH_SECRET\` in \`.env\`.`;
217
+ - \`auth\` is **server-only** - never import it into a \`routes/\` page, and keep \`BETTER_AUTH_SECRET\` in \`.env\`.`;
218
218
  }
219
219
  const SECURITY_RULES = `## Production defaults (don't ship the happy path only)
220
220
 
221
- - **Dependencies: install current, never pin from memory.** Your training data lags npm by months — do
221
+ - **Dependencies: install current, never pin from memory.** Your training data lags npm by months - do
222
222
  not hand-write a version range or reach for a remembered (often deprecated or vulnerable) API. Add a
223
- dependency with \`bun add <pkg>\` so it resolves the **latest stable**, then use *that* version's API —
223
+ dependency with \`bun add <pkg>\` so it resolves the **latest stable**, then use *that* version's API -
224
224
  if unsure of a signature, read \`node_modules/<pkg>\`'s types or run \`bun pm view <pkg> version\`, don't
225
225
  guess. Never **downgrade** versions already in \`package.json\` (especially the pinned \`@nifrajs/*\` ones).
226
226
  - Validate at every trust boundary (above). Reject unknown fields and out-of-range values.
227
227
  - Constant-time comparison for secrets/tokens/HMACs (\`timingSafeEqual\` or WebCrypto), never \`===\`.
228
- - Verify ownership/permission on any resource a handler reads or mutates — auth'd ≠ authorized.
228
+ - Verify ownership/permission on any resource a handler reads or mutates - auth'd ≠ authorized.
229
229
  - Idempotency on retryable side effects (webhooks, payments): short-circuit on a repeated key.
230
230
  - Money in integer minor units; timestamps as UTC; parse client dates at the boundary.
231
231
  - No secrets/PII in logs or error responses.
232
232
  `;
233
233
  const COMMANDS = {
234
- api: `- \`bun install\` — install dependencies
235
- - \`bun run dev\` — run the API (watch mode)
236
- - \`bun test\` — drive \`app.fetch\` directly; no server required
237
- - \`nifra check\` — typecheck + typed-client lint (run before you call work done)`,
238
- site: `- \`bun install\` — install dependencies
239
- - \`nifra dev\` — true-HMR dev server
240
- - \`nifra build\` — complete Bun deploy (server + content-hashed client assets)
241
- - \`nifra start\` — run the generated Bun server — pairs with \`nifra build\`
242
- - \`nifra check\` — typecheck + typed-client lint (run before you call work done)
234
+ api: `- \`bun install\` - install dependencies
235
+ - \`bun run dev\` - run the API (watch mode)
236
+ - \`bun test\` - drive \`app.fetch\` directly; no server required
237
+ - \`nifra check\` - typecheck + typed-client lint (run before you call work done)`,
238
+ site: `- \`bun install\` - install dependencies
239
+ - \`nifra dev\` - true-HMR dev server
240
+ - \`nifra build\` - complete Bun deploy (server + content-hashed client assets)
241
+ - \`nifra start\` - run the generated Bun server - pairs with \`nifra build\`
242
+ - \`nifra check\` - typecheck + typed-client lint (run before you call work done)
243
243
 
244
244
  For a local production run use \`nifra build && nifra start\` (or \`bun run build:bun && bun run start\`). NOTE:
245
245
  the bare \`bun run build\` script targets **Cloudflare Pages for DEPLOY** (emits \`dist/\`, a different layout)
246
- — it does NOT pair with \`nifra start\`/\`bun run start\` (which serve \`dist-bun/\`). Don't mix the two.`,
247
- isr: `- \`bun install\` — install dependencies
248
- - \`nifra dev\` — true-HMR dev server
249
- - \`nifra build\` — complete Bun deploy (server + content-hashed client assets)
250
- - \`nifra start\` — run the generated Bun server (SSR + ISR cache) — pairs with \`nifra build\`
251
- - \`nifra check\` — typecheck + typed-client lint (run before you call work done)
246
+ - it does NOT pair with \`nifra start\`/\`bun run start\` (which serve \`dist-bun/\`). Don't mix the two.`,
247
+ isr: `- \`bun install\` - install dependencies
248
+ - \`nifra dev\` - true-HMR dev server
249
+ - \`nifra build\` - complete Bun deploy (server + content-hashed client assets)
250
+ - \`nifra start\` - run the generated Bun server (SSR + ISR cache) - pairs with \`nifra build\`
251
+ - \`nifra check\` - typecheck + typed-client lint (run before you call work done)
252
252
 
253
253
  For a local production run use \`nifra build && nifra start\`. NOTE: the bare \`bun run build\` script targets
254
- **Cloudflare Pages for DEPLOY** (\`dist/\`) and does NOT pair with \`nifra start\` — don't mix the two.`,
255
- fullstack: `- \`bun install\` — install dependencies
256
- - \`bun run dev\` — run the API (watch mode); \`src/index.ts\` also starts the job worker (\`queue.start()\`)
257
- - \`bun test\` — drives \`app.fetch\` directly (pagination, jobs via \`queue.drain()\`, cache, storage); no server required
258
- - \`bun run typecheck\` — run before you call work done
254
+ **Cloudflare Pages for DEPLOY** (\`dist/\`) and does NOT pair with \`nifra start\` - don't mix the two.`,
255
+ fullstack: `- \`bun install\` - install dependencies
256
+ - \`bun run dev\` - run the API (watch mode); \`src/index.ts\` also starts the job worker (\`queue.start()\`)
257
+ - \`bun test\` - drives \`app.fetch\` directly (pagination, jobs via \`queue.drain()\`, cache, storage); no server required
258
+ - \`bun run typecheck\` - run before you call work done
259
259
 
260
260
  Wired: cursor pagination (\`t.pageQuery\`/\`t.paginated\`/\`paginate\`), background jobs (\`@nifrajs/jobs\`),
261
261
  a TTL cache (\`@nifrajs/cache\`), blob storage (\`@nifrajs/storage\`). Swap the in-memory \`notes\` store for
262
262
  your DB, and the memory cache/job/storage adapters for shared ones (Redis / CF KV / R2) when multi-process.`,
263
263
  };
264
- /** The agent's stop condition — what makes the anti-drift guarantees actually fire. */
265
- const DONE = `## Definition of done — run \`nifra check\`
264
+ /** The agent's stop condition - what makes the anti-drift guarantees actually fire. */
265
+ const DONE = `## Definition of done - run \`nifra check\`
266
266
 
267
- Before considering any change complete, run **\`nifra check\`**. It (1) typechecks — the frontend↔backend
267
+ Before considering any change complete, run **\`nifra check\`**. It (1) typechecks - the frontend↔backend
268
268
  contract is compiler-enforced, so the typed client deriving \`res.data\` from your routes turns any
269
269
  mismatch into an error; (2) flags any hand-rolled \`fetch()\` to this app's own API (which bypasses that
270
270
  check); and (3) flags a server-only import (a DB driver, \`node:\`/\`bun:\`, \`./db\`) at the top level of a
271
- \`routes/\` page — that ships server code to the browser. Use \`nifra check --json\` for machine-readable
272
- diagnostics. A failing \`nifra check\` means the work isn't done — fix it, don't ship around it.`;
271
+ \`routes/\` page - that ships server code to the browser. Use \`nifra check --json\` for machine-readable
272
+ diagnostics. A failing \`nifra check\` means the work isn't done - fix it, don't ship around it.`;
273
273
  /** Build the full `AGENTS.md` for a freshly scaffolded app. */
274
274
  export function agentsMd(opts) {
275
275
  const isWeb = opts.template === "site" || opts.template === "isr";
276
276
  const what = isWeb
277
- ? `a full-stack nifra app — a typed \`server()\` backend plus file-routed, server-rendered ${FRAMEWORK_LABEL[opts.framework]} pages`
278
- : `a nifra API — a typed, contract-first \`server()\` backend`;
277
+ ? `a full-stack nifra app - a typed \`server()\` backend plus file-routed, server-rendered ${FRAMEWORK_LABEL[opts.framework]} pages`
278
+ : `a nifra API - a typed, contract-first \`server()\` backend`;
279
279
  // Each entry is a self-contained markdown block; join with a blank line between blocks. The web-rules
280
- // block is empty for API templates — dropped before joining so there's no doubled blank line.
280
+ // block is empty for API templates - dropped before joining so there's no doubled blank line.
281
281
  const blocks = [
282
- `# AGENTS.md — ${opts.name}\n\nGuidance for AI coding agents working in this repo. This is ${what}. nifra is Bun-native, Web-standard, and framework-agnostic; the same app runs on Bun, Node, Deno, and the edge.`,
282
+ `# AGENTS.md - ${opts.name}\n\nGuidance for AI coding agents working in this repo. This is ${what}. nifra is Bun-native, Web-standard, and framework-agnostic; the same app runs on Bun, Node, Deno, and the edge.`,
283
283
  `## Commands\n\n${COMMANDS[opts.template]}`,
284
- // Surface the MCP early — an agent that learns the typechecked tools exist (and that `nifra check`
284
+ // Surface the MCP early - an agent that learns the typechecked tools exist (and that `nifra check`
285
285
  // is the done-gate) up front will reach for them instead of writing nifra from stale memory.
286
286
  agentsMcpSection(),
287
287
  API_RULES.trim(),
@@ -290,7 +290,7 @@ export function agentsMd(opts) {
290
290
  isWeb ? webRules(opts.framework).trim() : "",
291
291
  SECURITY_RULES.trim(),
292
292
  DONE,
293
- "## Full reference\n\nThe complete, machine-readable API + docs live in **`llms-full.txt`** (served at the nifra site root). When you need an exact signature or a feature you haven't used, read that rather than guessing — it inlines every doc page and the export index.",
293
+ "## Full reference\n\nThe complete, machine-readable API + docs live in **`llms-full.txt`** (served at the nifra site root). When you need an exact signature or a feature you haven't used, read that rather than guessing - it inlines every doc page and the export index.",
294
294
  ];
295
295
  return `${blocks.filter(Boolean).join("\n\n")}\n`;
296
296
  }
package/dist/auth.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `--auth better-auth` — wire authentication into a scaffolded app, the same "bundle the right tool,
2
+ * `--auth better-auth` - wire authentication into a scaffolded app, the same "bundle the right tool,
3
3
  * correctly" move as `--db`. nifra owns the *mount* (`@nifrajs/better-auth` plugs better-auth's handler
4
4
  * into a `server()` and gives typed `getSession`/`requireSession` guards); better-auth owns the auth
5
5
  * logic. Auth needs a database, so this composes with `--db`: the generated `auth.ts` reuses the client
@@ -7,7 +7,7 @@
7
7
  * agent then runs `bunx @better-auth/cli generate` to add the auth tables and migrates.
8
8
  *
9
9
  * `--auth` therefore requires `--db` (the CLI enforces it). better-auth's clean adapters cover Drizzle
10
- * and Prisma; Kysely is rejected (it has no drop-in adapter — wire better-auth's own dialect manually).
10
+ * and Prisma; Kysely is rejected (it has no drop-in adapter - wire better-auth's own dialect manually).
11
11
  * One preset today: `better-auth`.
12
12
  */
13
13
  import { type DbChoice } from "./db.js";
package/dist/auth.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `--auth better-auth` — wire authentication into a scaffolded app, the same "bundle the right tool,
2
+ * `--auth better-auth` - wire authentication into a scaffolded app, the same "bundle the right tool,
3
3
  * correctly" move as `--db`. nifra owns the *mount* (`@nifrajs/better-auth` plugs better-auth's handler
4
4
  * into a `server()` and gives typed `getSession`/`requireSession` guards); better-auth owns the auth
5
5
  * logic. Auth needs a database, so this composes with `--db`: the generated `auth.ts` reuses the client
@@ -7,14 +7,14 @@
7
7
  * agent then runs `bunx @better-auth/cli generate` to add the auth tables and migrates.
8
8
  *
9
9
  * `--auth` therefore requires `--db` (the CLI enforces it). better-auth's clean adapters cover Drizzle
10
- * and Prisma; Kysely is rejected (it has no drop-in adapter — wire better-auth's own dialect manually).
10
+ * and Prisma; Kysely is rejected (it has no drop-in adapter - wire better-auth's own dialect manually).
11
11
  * One preset today: `better-auth`.
12
12
  */
13
13
  import { appendFile, writeFile } from "node:fs/promises";
14
14
  import { join } from "node:path";
15
15
  import { DB_PRESETS } from "./db.js";
16
16
  export const AUTH_CHOICES = ["better-auth"];
17
- /** ORMs better-auth wires via a drop-in adapter. Kysely is excluded — see {@link assertAuthableDb}. */
17
+ /** ORMs better-auth wires via a drop-in adapter. Kysely is excluded - see {@link assertAuthableDb}. */
18
18
  const AUTHABLE_ORMS = new Set(["drizzle", "prisma"]);
19
19
  /**
20
20
  * Guard `--auth` against a DB whose ORM has no clean better-auth adapter. Throws an actionable error
@@ -31,9 +31,9 @@ export function assertAuthableDb(db) {
31
31
  export const AUTH_PRESETS = {
32
32
  "better-auth": {
33
33
  label: "better-auth",
34
- note: "email/password + sessions, mounted at /api/auth/* — add OAuth providers in auth.ts.",
34
+ note: "email/password + sessions, mounted at /api/auth/* - add OAuth providers in auth.ts.",
35
35
  // @nifrajs/better-auth mounts it; better-auth is the implementation (peer-installed alongside).
36
- deps: { "@nifrajs/better-auth": "^2.1.0", "better-auth": "^1.2.0" },
36
+ deps: { "@nifrajs/better-auth": "^2.3.0", "better-auth": "^1.2.0" },
37
37
  },
38
38
  };
39
39
  /** The better-auth `database` adapter expression + its import line, for the scaffolded DB's ORM. */
@@ -54,7 +54,7 @@ function adapterFor(db) {
54
54
  database: `drizzleAdapter(db, { provider: "${provider}" })`,
55
55
  };
56
56
  }
57
- /** The generated `auth.ts` — a better-auth instance backed by the scaffolded DB client. */
57
+ /** The generated `auth.ts` - a better-auth instance backed by the scaffolded DB client. */
58
58
  function authModule(db) {
59
59
  const { importLine, database } = adapterFor(db);
60
60
  return `import { betterAuth as createBetterAuth } from "better-auth"
@@ -67,7 +67,7 @@ import { db } from "./db"
67
67
  export const auth = createBetterAuth({
68
68
  database: ${database},
69
69
  emailAndPassword: { enabled: true },
70
- // Set in .env — generate a strong value: openssl rand -base64 32
70
+ // Set in .env - generate a strong value: openssl rand -base64 32
71
71
  secret: process.env.BETTER_AUTH_SECRET,
72
72
  baseURL: process.env.BETTER_AUTH_URL,
73
73
  })
@@ -76,7 +76,7 @@ export type Auth = typeof auth
76
76
  `;
77
77
  }
78
78
  const ENV = `
79
- # better-auth — REQUIRED. Generate a 32+ char secret: openssl rand -base64 32
79
+ # better-auth - REQUIRED. Generate a 32+ char secret: openssl rand -base64 32
80
80
  BETTER_AUTH_SECRET="change-me-before-production"
81
81
  BETTER_AUTH_URL="http://localhost:3000"
82
82
  `;