create-nwire 0.12.1 → 0.13.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 (63) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +6 -2
  3. package/package.json +1 -1
  4. package/templates/enterprise/AGENTS.md +11 -11
  5. package/templates/enterprise/__tests__/auto-moderate.test.ts +22 -6
  6. package/templates/enterprise/__tests__/submit-flow.test.ts +22 -6
  7. package/templates/enterprise/app/api.ts +3 -1
  8. package/templates/enterprise/app/app.ts +23 -15
  9. package/templates/enterprise/app/main.ts +20 -45
  10. package/templates/enterprise/config/app.ts +18 -0
  11. package/templates/enterprise/config/env.ts +32 -0
  12. package/templates/enterprise/config/http.ts +15 -0
  13. package/templates/enterprise/modules/posts/actions/approve-post.ts +4 -3
  14. package/templates/enterprise/modules/posts/actions/reject-post.ts +3 -3
  15. package/templates/enterprise/modules/posts/actions/submit-post.ts +4 -3
  16. package/templates/enterprise/modules/posts/events/post-was-approved.ts +1 -1
  17. package/templates/enterprise/modules/posts/events/post-was-rejected.ts +2 -1
  18. package/templates/enterprise/modules/posts/events/post-was-submitted.ts +2 -1
  19. package/templates/enterprise/modules/posts/projections/queue-dashboard.ts +27 -25
  20. package/templates/enterprise/modules/posts/queries/posts-by-author.ts +7 -5
  21. package/templates/enterprise/modules/posts/routes/approve-post.ts +3 -3
  22. package/templates/enterprise/modules/posts/routes/get-post.ts +3 -3
  23. package/templates/enterprise/modules/posts/routes/list-queue.ts +4 -4
  24. package/templates/enterprise/modules/posts/routes/posts-by-author.ts +28 -0
  25. package/templates/enterprise/modules/posts/routes/reject-post.ts +3 -3
  26. package/templates/enterprise/modules/posts/routes/submit-post.ts +3 -3
  27. package/templates/enterprise/modules/posts/workflows/auto-moderate.ts +3 -3
  28. package/templates/enterprise/package.json +10 -11
  29. package/templates/enterprise/tsconfig.json +1 -1
  30. package/templates/mcp/AGENTS.md +73 -0
  31. package/templates/mcp/__tests__/mcp-server.test.ts +205 -0
  32. package/templates/mcp/_gitignore +5 -0
  33. package/templates/mcp/_npmrc +4 -0
  34. package/templates/mcp/_pnpm-workspace.yaml +6 -0
  35. package/templates/mcp/app/app.ts +24 -0
  36. package/templates/mcp/app/main.ts +197 -0
  37. package/templates/mcp/app/store/facts-store.ts +17 -0
  38. package/templates/mcp/app/tools/add-fact.ts +27 -0
  39. package/templates/mcp/app/tools/list-facts.ts +17 -0
  40. package/templates/mcp/app/tools/lookup-fact.ts +28 -0
  41. package/templates/mcp/app/tools.ts +19 -0
  42. package/templates/mcp/config/app.ts +16 -0
  43. package/templates/mcp/config/env.ts +27 -0
  44. package/templates/mcp/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  45. package/templates/mcp/package.json +25 -0
  46. package/templates/mcp/tsconfig.json +15 -0
  47. package/templates/mcp/vitest.config.ts +8 -0
  48. package/templates/minimal/AGENTS.md +5 -5
  49. package/templates/minimal/__tests__/hello.test.ts +3 -3
  50. package/templates/minimal/app/app.ts +21 -0
  51. package/templates/minimal/app/main.ts +11 -23
  52. package/templates/minimal/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  53. package/templates/minimal/package.json +7 -8
  54. package/templates/service/AGENTS.md +12 -10
  55. package/templates/service/__tests__/todo-api.test.ts +5 -5
  56. package/templates/service/app/app.ts +25 -0
  57. package/templates/service/app/main.ts +20 -30
  58. package/templates/service/app/store/todo-store.ts +6 -22
  59. package/templates/service/config/app.ts +18 -0
  60. package/templates/service/config/env.ts +32 -0
  61. package/templates/service/config/http.ts +15 -0
  62. package/templates/service/package.json +8 -9
  63. package/templates/service/tsconfig.json +1 -1
@@ -1,42 +1,32 @@
1
1
  /**
2
- * Entry — boot the app under HTTP.
2
+ * Entry — the only file that boots a real HTTP server.
3
3
  *
4
- * 1. createApp constructs the bounded context; `.with(plugin)` installs
5
- * the TodoStore on the container.
6
- * 2. app.wire(...) pairs every route binding with its handler.
7
- * 3. endpoint().use(httpKoa({ middleware: [requireUser] })).mount(app).run()
8
- * runs the HTTP adapter under graceful drain + K8s probes.
4
+ * pnpm dev
9
5
  *
10
- * Run: pnpm dev
11
- * Try: curl -X POST http://localhost:3000/api/todos \
12
- * -H "content-type: application/json" \
13
- * -H "x-user-id: alice" \
14
- * -d '{"text":"buy milk"}'
15
- * curl -H "x-user-id: alice" http://localhost:3000/api/todos
6
+ * curl -X POST http://localhost:3000/api/todos \
7
+ * -H "content-type: application/json" \
8
+ * -H "x-user-id: alice" \
9
+ * -d '{"text":"buy milk"}'
10
+ * curl -H "x-user-id: alice" http://localhost:3000/api/todos
11
+ *
12
+ * `app` is a pure value — routes are wired in `./app` on import.
13
+ * This file adds the one side effect: binding a port and serving traffic.
14
+ * Tests import `{ app }` from `./app` and build their own ephemeral
15
+ * endpoint — never import this file from tests or it boots a server and
16
+ * hangs the runner.
16
17
  */
17
18
 
18
- import { createApp, definePlugin } from "@nwire/app";
19
19
  import { endpoint } from "@nwire/endpoint";
20
20
  import { httpKoa } from "@nwire/koa";
21
-
22
- import { wires } from "./api";
21
+ import { appConfig } from "../config/app";
22
+ import { httpConfig } from "../config/http";
23
23
  import { requireUser } from "./middleware/require-user";
24
- import { TodoStore } from "./store/todo-store";
25
-
26
- const todoStorePlugin = definePlugin("todo-store", ({ bind }) => {
27
- bind("todos", new TodoStore());
28
- });
24
+ import { app } from "./app";
29
25
 
30
- export function buildApp() {
31
- const app = createApp({ appName: "{{PROJECT_NAME}}" }).with(todoStorePlugin);
32
- for (const { binding, handler } of wires) {
33
- app.wire(binding, handler);
34
- }
35
- return app;
36
- }
26
+ const cfg = appConfig();
27
+ const http = httpConfig();
37
28
 
38
- const app = buildApp();
39
- await endpoint("{{PROJECT_NAME}}", { port: 3000 })
40
- .use(httpKoa({ prefix: "/api", middleware: [requireUser] }))
29
+ await endpoint(cfg.name, { port: cfg.port, banner: cfg.banner })
30
+ .use(httpKoa({ prefix: http.prefix, middleware: [requireUser] }))
41
31
  .mount(app)
42
32
  .run();
@@ -99,32 +99,16 @@ export class TodoStore {
99
99
  // ─── The plugin (optional — used when you add the forge battery) ────────
100
100
 
101
101
  /**
102
- * Plugin shape: `provide("todos", { boot, shutdown })`.
102
+ * Plugin shape: `bind("todos", store)`.
103
103
  *
104
- * The framework calls `boot()` during app start, registers the result
105
- * on the container under the name we chose, and calls `shutdown(store)`
106
- * during graceful drain. From any handler: `resolve("todos")`.
104
+ * `bind` registers the store on the container under the name we chose.
105
+ * From any handler: `resolve("todos")`. For lifecycle teardown, use
106
+ * `dispose(() => ...)` called on graceful shutdown.
107
107
  *
108
108
  * This same shape works whether the store is in-memory (here), a Pool
109
109
  * (Postgres via Drizzle), or a MongoClient. The plugin wrapper handles
110
110
  * the lifecycle; the store implementation handles its own concerns.
111
- *
112
- * this template's main.ts uses the simpler `container.register(...)` path because we
113
- * don't have a forge app yet. The plugin is exported so forge graduation
114
- * is `import { todoStorePlugin } from "./store/todo-store"`.
115
111
  */
116
- export const todoStorePlugin = definePlugin("todos", ({ provide }) => {
117
- provide("todos", {
118
- boot: () => new TodoStore(),
119
- shutdown: (store) => {
120
- // For an in-memory store there's nothing to close. A real adapter
121
- // would call `pool.end()` / `client.close()` here.
122
- void store;
123
- },
124
- healthCheck: (store) => {
125
- // Trivial — proves the store is alive. A real adapter would
126
- // SELECT 1 against the DB. Lightship calls this on every /ready.
127
- if (store.size() < 0) throw new Error("store corrupt");
128
- },
129
- });
112
+ export const todoStorePlugin = definePlugin("todos", ({ bind }) => {
113
+ bind("todos", new TodoStore());
130
114
  });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * App-level settings — the process identity and lifecycle budgets the
3
+ * endpoint owns. A function of the typed `env`, so the same config file
4
+ * reads differently per environment without an `if` in sight.
5
+ */
6
+
7
+ import { env, type Env } from "./env";
8
+
9
+ export const appConfig = (e: Env = env) => ({
10
+ /** Endpoint + app name — shows in the banner, logs, and telemetry. */
11
+ name: "{{PROJECT_NAME}}",
12
+ /** Port the HTTP transport binds. */
13
+ port: e.PORT,
14
+ /** Print the boot banner outside of tests. */
15
+ banner: e.NODE_ENV !== "test",
16
+ });
17
+
18
+ export type AppConfig = ReturnType<typeof appConfig>;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Typed environment — read `process.env` once, through a schema, and never
3
+ * touch it again from app code. Missing or malformed values fail loudly at
4
+ * boot with a readable message, not three layers deep at first request.
5
+ *
6
+ * `config/*.ts` files take this typed `env` and shape it into the settings
7
+ * each concern needs.
8
+ */
9
+
10
+ import { z } from "zod";
11
+
12
+ const EnvSchema = z.object({
13
+ NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
14
+ PORT: z.coerce.number().int().min(0).max(65_535).default(3000),
15
+ HTTP_PREFIX: z.string().default("/api"),
16
+ });
17
+
18
+ export type Env = z.output<typeof EnvSchema>;
19
+
20
+ export function loadEnv(source: NodeJS.ProcessEnv = process.env): Env {
21
+ const parsed = EnvSchema.safeParse(source);
22
+ if (!parsed.success) {
23
+ const summary = parsed.error.issues
24
+ .map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`)
25
+ .join("\n");
26
+ throw new Error(`Invalid environment:\n${summary}`);
27
+ }
28
+ return parsed.data;
29
+ }
30
+
31
+ /** The validated environment, loaded once. */
32
+ export const env: Env = loadEnv();
@@ -0,0 +1,15 @@
1
+ /**
2
+ * HTTP transport settings — prefix, OpenAPI, and the dev inspect surface
3
+ * Studio reads. A function of the typed `env`, like every config file here.
4
+ */
5
+
6
+ import { env, type Env } from "./env";
7
+
8
+ export const httpConfig = (e: Env = env) => ({
9
+ /** Mounted under this prefix — e.g. `POST /api/todos`. */
10
+ prefix: e.HTTP_PREFIX,
11
+ /** Expose detailed errors in dev; redact in production. */
12
+ exposeErrors: e.NODE_ENV !== "production",
13
+ });
14
+
15
+ export type HttpConfig = ReturnType<typeof httpConfig>;
@@ -4,27 +4,26 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "vite-node app/main.ts",
7
+ "dev": "nwire dev",
8
8
  "test": "vitest run",
9
9
  "doctor": "nwire doctor",
10
10
  "studio": "nwire studio",
11
11
  "cache": "nwire cache"
12
12
  },
13
13
  "dependencies": {
14
- "@nwire/app": "^0.12.1",
15
- "@nwire/endpoint": "^0.12.1",
16
- "@nwire/handler": "^0.12.1",
17
- "@nwire/koa": "^0.12.1",
18
- "@nwire/wires": "^0.12.1",
14
+ "@nwire/app": "^0.13.0",
15
+ "@nwire/endpoint": "^0.13.0",
16
+ "@nwire/handler": "^0.13.0",
17
+ "@nwire/koa": "^0.13.0",
18
+ "@nwire/wires": "^0.13.0",
19
19
  "koa": "^2.16.1",
20
20
  "zod": "^4.0.0"
21
21
  },
22
22
  "devDependencies": {
23
+ "@nwire/cli": "^0.13.0",
23
24
  "@types/koa": "^2.15.0",
24
25
  "@types/node": "^22.19.9",
25
26
  "typescript": "^5.9.0",
26
- "vite-node": "^3.2.4",
27
- "vitest": "^4.0.18",
28
- "@nwire/cli": "^0.12.1"
27
+ "vitest": "^4.0.18"
29
28
  }
30
29
  }
@@ -11,5 +11,5 @@
11
11
  "noEmit": true,
12
12
  "types": ["node"]
13
13
  },
14
- "include": ["app/**/*", "__tests__/**/*"]
14
+ "include": ["app/**/*", "config/**/*", "__tests__/**/*"]
15
15
  }