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.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -2
- package/package.json +1 -1
- package/templates/enterprise/AGENTS.md +11 -11
- package/templates/enterprise/__tests__/auto-moderate.test.ts +22 -6
- package/templates/enterprise/__tests__/submit-flow.test.ts +22 -6
- package/templates/enterprise/app/api.ts +3 -1
- package/templates/enterprise/app/app.ts +23 -15
- package/templates/enterprise/app/main.ts +20 -45
- package/templates/enterprise/config/app.ts +18 -0
- package/templates/enterprise/config/env.ts +32 -0
- package/templates/enterprise/config/http.ts +15 -0
- package/templates/enterprise/modules/posts/actions/approve-post.ts +4 -3
- package/templates/enterprise/modules/posts/actions/reject-post.ts +3 -3
- package/templates/enterprise/modules/posts/actions/submit-post.ts +4 -3
- package/templates/enterprise/modules/posts/events/post-was-approved.ts +1 -1
- package/templates/enterprise/modules/posts/events/post-was-rejected.ts +2 -1
- package/templates/enterprise/modules/posts/events/post-was-submitted.ts +2 -1
- package/templates/enterprise/modules/posts/projections/queue-dashboard.ts +27 -25
- package/templates/enterprise/modules/posts/queries/posts-by-author.ts +7 -5
- package/templates/enterprise/modules/posts/routes/approve-post.ts +3 -3
- package/templates/enterprise/modules/posts/routes/get-post.ts +3 -3
- package/templates/enterprise/modules/posts/routes/list-queue.ts +4 -4
- package/templates/enterprise/modules/posts/routes/posts-by-author.ts +28 -0
- package/templates/enterprise/modules/posts/routes/reject-post.ts +3 -3
- package/templates/enterprise/modules/posts/routes/submit-post.ts +3 -3
- package/templates/enterprise/modules/posts/workflows/auto-moderate.ts +3 -3
- package/templates/enterprise/package.json +10 -11
- package/templates/enterprise/tsconfig.json +1 -1
- package/templates/mcp/AGENTS.md +73 -0
- package/templates/mcp/__tests__/mcp-server.test.ts +205 -0
- package/templates/mcp/_gitignore +5 -0
- package/templates/mcp/_npmrc +4 -0
- package/templates/mcp/_pnpm-workspace.yaml +6 -0
- package/templates/mcp/app/app.ts +24 -0
- package/templates/mcp/app/main.ts +197 -0
- package/templates/mcp/app/store/facts-store.ts +17 -0
- package/templates/mcp/app/tools/add-fact.ts +27 -0
- package/templates/mcp/app/tools/list-facts.ts +17 -0
- package/templates/mcp/app/tools/lookup-fact.ts +28 -0
- package/templates/mcp/app/tools.ts +19 -0
- package/templates/mcp/config/app.ts +16 -0
- package/templates/mcp/config/env.ts +27 -0
- package/templates/mcp/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/templates/mcp/package.json +25 -0
- package/templates/mcp/tsconfig.json +15 -0
- package/templates/mcp/vitest.config.ts +8 -0
- package/templates/minimal/AGENTS.md +5 -5
- package/templates/minimal/__tests__/hello.test.ts +3 -3
- package/templates/minimal/app/app.ts +21 -0
- package/templates/minimal/app/main.ts +11 -23
- package/templates/minimal/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/templates/minimal/package.json +7 -8
- package/templates/service/AGENTS.md +12 -10
- package/templates/service/__tests__/todo-api.test.ts +5 -5
- package/templates/service/app/app.ts +25 -0
- package/templates/service/app/main.ts +20 -30
- package/templates/service/app/store/todo-store.ts +6 -22
- package/templates/service/config/app.ts +18 -0
- package/templates/service/config/env.ts +32 -0
- package/templates/service/config/http.ts +15 -0
- package/templates/service/package.json +8 -9
- package/templates/service/tsconfig.json +1 -1
|
@@ -1,42 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Entry —
|
|
2
|
+
* Entry — the only file that boots a real HTTP server.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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 {
|
|
21
|
+
import { appConfig } from "../config/app";
|
|
22
|
+
import { httpConfig } from "../config/http";
|
|
23
23
|
import { requireUser } from "./middleware/require-user";
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
const todoStorePlugin = definePlugin("todo-store", ({ bind }) => {
|
|
27
|
-
bind("todos", new TodoStore());
|
|
28
|
-
});
|
|
24
|
+
import { app } from "./app";
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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: `
|
|
102
|
+
* Plugin shape: `bind("todos", store)`.
|
|
103
103
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
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", ({
|
|
117
|
-
|
|
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": "
|
|
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.
|
|
15
|
-
"@nwire/endpoint": "^0.
|
|
16
|
-
"@nwire/handler": "^0.
|
|
17
|
-
"@nwire/koa": "^0.
|
|
18
|
-
"@nwire/wires": "^0.
|
|
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
|
-
"
|
|
27
|
-
"vitest": "^4.0.18",
|
|
28
|
-
"@nwire/cli": "^0.12.1"
|
|
27
|
+
"vitest": "^4.0.18"
|
|
29
28
|
}
|
|
30
29
|
}
|