create-nwire 0.11.1 → 0.12.1

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.js CHANGED
@@ -67,6 +67,9 @@ export function scaffold(opts) {
67
67
  const UNDERSCORE_TO_DOT = {
68
68
  _gitignore: ".gitignore",
69
69
  _npmrc: ".npmrc",
70
+ // Underscore-prefixed in the template so it doesn't make the nwire
71
+ // monorepo treat the template dir as a nested pnpm workspace.
72
+ "_pnpm-workspace.yaml": "pnpm-workspace.yaml",
70
73
  _eslintrc: ".eslintrc",
71
74
  _eslintignore: ".eslintignore",
72
75
  _prettierrc: ".prettierrc",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nwire",
3
- "version": "0.11.1",
3
+ "version": "0.12.1",
4
4
  "description": "Scaffolder for new Nwire projects. Run `pnpm create nwire <name>` or `npm create nwire <name>` to bootstrap.",
5
5
  "keywords": [
6
6
  "nwire",
@@ -0,0 +1,43 @@
1
+ # AGENTS.md — working in this nwire app
2
+
3
+ This is an **nwire** backend (enterprise tier: the full forge battery is
4
+ installed). The one idea: you write logic once as a **handler** (a plain
5
+ typed function) and wire it to a **transport** (HTTP route, queue, cron,
6
+ MCP tool). The handler never knows which transport called it. When logic
7
+ gets real, forge adds events, actors, projections, and workflows.
8
+
9
+ ## Import map — DO NOT GUESS THESE
10
+
11
+ The most common mistake is importing a primitive from the wrong package:
12
+
13
+ | Primitive | Package |
14
+ |---|---|
15
+ | `createApp`, `appCompose`, `definePlugin` | `@nwire/app` |
16
+ | `endpoint` | `@nwire/endpoint` |
17
+ | `get` `post` `put` `patch` `del` | `@nwire/wires/http` |
18
+ | `httpKoa` | `@nwire/koa` |
19
+ | `defineHandler`, `defineResource`, `defineError`, `Unauthorized`/`Forbidden`/`NotFound`/`Conflict`/`BadRequest` | `@nwire/handler` |
20
+ | `defineEvent` | `@nwire/messages` |
21
+ | `defineAction`, `defineActor`, `defineSchema`, `defineProjection`, `defineQuery`, `defineWorkflow`, `createForgePlugin` | `@nwire/forge` |
22
+
23
+ `defineResource` and `defineError` are in **`@nwire/handler`**, not
24
+ `@nwire/forge` — even though forge re-exports some of them, import from
25
+ `@nwire/handler` directly.
26
+
27
+ ## Concepts
28
+
29
+ - **Handler** — `(input, ctx) => result`; `ctx` carries `input`,
30
+ `resolve`, `execute`, `emit`, `envelope`, `logger`.
31
+ - **Event** — a past-tense fact (`defineEvent`). Emit with `ctx.emit`;
32
+ react to it with `when(Event, fn)` in a listener file (`on` is for
33
+ lifecycle hooks only).
34
+ - **Action** — a named command (`defineAction`) a handler fulfils and may
35
+ emit events from. Register via `createForgePlugin({ handlers: [act.handler] })`.
36
+ - **Actor** — a thing with identity that guards a rule. **Projection** — a
37
+ read model folded from events. **Workflow** — a process that reacts over
38
+ time.
39
+
40
+ ## Commands
41
+
42
+ - `pnpm dev` — run the app. `pnpm test` — tests. `pnpm doctor` — health
43
+ check. `pnpm studio` — live trace/inspect console.
@@ -0,0 +1,4 @@
1
+ # nwire dev runs via vite-node (esbuild). pnpm 11 defers esbuild's build
2
+ # script and then fails the pre-run deps check; skip that check so
3
+ # `pnpm dev` runs clean right after install.
4
+ verify-deps-before-run=false
@@ -0,0 +1,6 @@
1
+ # pnpm 11 reads build-allow + run settings here (not the package.json
2
+ # "pnpm" field). esbuild backs vite-node; allow its build, and skip the
3
+ # pre-run deps check so `pnpm dev` runs clean right after install.
4
+ onlyBuiltDependencies:
5
+ - esbuild
6
+ verifyDepsBeforeRun: false
@@ -52,6 +52,4 @@ export async function bootstrap(opts: number | BootstrapOptions = 3000) {
52
52
  return { app, running, koa };
53
53
  }
54
54
 
55
- if (import.meta.url === `file://${process.argv[1]}`) {
56
- await bootstrap();
57
- }
55
+ await bootstrap();
@@ -5,22 +5,26 @@
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite-node app/main.ts",
8
- "test": "vitest run"
8
+ "test": "vitest run",
9
+ "doctor": "nwire doctor",
10
+ "studio": "nwire studio",
11
+ "cache": "nwire cache"
9
12
  },
10
13
  "dependencies": {
11
- "@nwire/app": "^0.11.1",
12
- "@nwire/endpoint": "^0.11.1",
13
- "@nwire/forge": "^0.11.1",
14
- "@nwire/koa": "^0.11.1",
15
- "@nwire/messages": "^0.11.1",
16
- "@nwire/wires": "^0.11.1",
14
+ "@nwire/app": "^0.12.1",
15
+ "@nwire/endpoint": "^0.12.1",
16
+ "@nwire/forge": "^0.12.1",
17
+ "@nwire/koa": "^0.12.1",
18
+ "@nwire/messages": "^0.12.1",
19
+ "@nwire/wires": "^0.12.1",
17
20
  "zod": "^4.0.0"
18
21
  },
19
22
  "devDependencies": {
20
- "@nwire/test-kit": "^0.11.1",
23
+ "@nwire/test-kit": "^0.12.1",
21
24
  "@types/node": "^22.19.9",
22
25
  "typescript": "^5.9.0",
23
26
  "vite-node": "^3.2.4",
24
- "vitest": "^4.0.18"
27
+ "vitest": "^4.0.18",
28
+ "@nwire/cli": "^0.12.1"
25
29
  }
26
30
  }
@@ -9,7 +9,7 @@
9
9
  "skipLibCheck": true,
10
10
  "forceConsistentCasingInFileNames": true,
11
11
  "noEmit": true,
12
- "types": ["node", "vite/client"]
12
+ "types": ["node"]
13
13
  },
14
14
  "include": ["app/**/*", "modules/**/*", "__tests__/**/*"]
15
15
  }
@@ -0,0 +1,37 @@
1
+ # AGENTS.md — working in this nwire app
2
+
3
+ This is a **minimal nwire** backend. You write logic once as a **handler**
4
+ (a plain typed function) and wire it to a **transport** (here, HTTP). The
5
+ handler never knows which transport called it.
6
+
7
+ ## The shape (`app/main.ts`)
8
+
9
+ ```ts
10
+ import { createApp } from "@nwire/app";
11
+ import { endpoint } from "@nwire/endpoint";
12
+ import { get } from "@nwire/wires/http";
13
+ import { httpKoa } from "@nwire/koa";
14
+
15
+ const app = createApp({ appName: "app" });
16
+ app.wire(get("/hello"), async () => ({ message: "hello" }));
17
+ await endpoint("app", { port: 3000 }).use(httpKoa()).mount(app).run();
18
+ ```
19
+
20
+ ## What's installed (don't import beyond these)
21
+
22
+ | Primitive | Package |
23
+ |---|---|
24
+ | `createApp`, `definePlugin` | `@nwire/app` |
25
+ | `endpoint` | `@nwire/endpoint` |
26
+ | `get` `post` `put` `patch` `del` | `@nwire/wires/http` |
27
+ | `httpKoa` | `@nwire/koa` |
28
+
29
+ This tier is HTTP-only. Add capabilities by installing the package first:
30
+ `@nwire/handler` for `defineResource`/`defineError`, `@nwire/messages` for
31
+ `defineEvent`, `@nwire/forge` for actions/actors/projections/workflows.
32
+ **Don't import a primitive from a package that isn't in `package.json`.**
33
+
34
+ ## Commands
35
+
36
+ - `pnpm dev` — run the app (HTTP on :3000). `pnpm test` — tests.
37
+ - `pnpm doctor` — health-check the setup. `pnpm studio` — trace console.
@@ -0,0 +1,4 @@
1
+ # nwire dev runs via vite-node (esbuild). pnpm 11 defers esbuild's build
2
+ # script and then fails the pre-run deps check; skip that check so
3
+ # `pnpm dev` runs clean right after install.
4
+ verify-deps-before-run=false
@@ -0,0 +1,6 @@
1
+ # pnpm 11 reads build-allow + run settings here (not the package.json
2
+ # "pnpm" field). esbuild backs vite-node; allow its build, and skip the
3
+ # pre-run deps check so `pnpm dev` runs clean right after install.
4
+ onlyBuiltDependencies:
5
+ - esbuild
6
+ verifyDepsBeforeRun: false
@@ -29,7 +29,5 @@ export function buildApp() {
29
29
  return app;
30
30
  }
31
31
 
32
- if (import.meta.url === `file://${process.argv[1]}`) {
33
- const app = buildApp();
34
- await endpoint("{{PROJECT_NAME}}", { port: 3000 }).use(httpKoa()).mount(app).run();
35
- }
32
+ const app = buildApp();
33
+ await endpoint("{{PROJECT_NAME}}", { port: 3000 }).use(httpKoa()).mount(app).run();
@@ -5,19 +5,23 @@
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite-node app/main.ts",
8
- "test": "vitest run"
8
+ "test": "vitest run",
9
+ "doctor": "nwire doctor",
10
+ "studio": "nwire studio",
11
+ "cache": "nwire cache"
9
12
  },
10
13
  "dependencies": {
11
- "@nwire/app": "^0.11.1",
12
- "@nwire/endpoint": "^0.11.1",
13
- "@nwire/koa": "^0.11.1",
14
- "@nwire/wires": "^0.11.1",
14
+ "@nwire/app": "^0.12.1",
15
+ "@nwire/endpoint": "^0.12.1",
16
+ "@nwire/koa": "^0.12.1",
17
+ "@nwire/wires": "^0.12.1",
15
18
  "zod": "^4.0.0"
16
19
  },
17
20
  "devDependencies": {
18
21
  "@types/node": "^22.19.9",
19
22
  "typescript": "^5.9.0",
20
23
  "vite-node": "^3.2.4",
21
- "vitest": "^4.0.18"
24
+ "vitest": "^4.0.18",
25
+ "@nwire/cli": "^0.12.1"
22
26
  }
23
27
  }
@@ -9,7 +9,7 @@
9
9
  "skipLibCheck": true,
10
10
  "forceConsistentCasingInFileNames": true,
11
11
  "noEmit": true,
12
- "types": ["node", "vite/client"]
12
+ "types": ["node"]
13
13
  },
14
14
  "include": ["app/**/*", "__tests__/**/*"]
15
15
  }
@@ -0,0 +1,62 @@
1
+ # AGENTS.md — working in this nwire app
2
+
3
+ This is an **nwire** backend. The one idea: you write logic once as a
4
+ **handler** (a plain typed function) and wire it to a **transport** (an
5
+ HTTP route, a queue, cron, an MCP tool). The handler never knows which
6
+ transport called it.
7
+
8
+ ## The shape
9
+
10
+ ```ts
11
+ import { createApp } from "@nwire/app";
12
+ import { endpoint } from "@nwire/endpoint";
13
+ import { post } from "@nwire/wires/http";
14
+ import { httpKoa } from "@nwire/koa";
15
+
16
+ const app = createApp({ appName: "svc" });
17
+ app.wire(post("/things", { body: z.object({ name: z.string() }) }), async (input) => ({ ok: input.name }));
18
+ await endpoint("svc", { port: 3000 }).use(httpKoa()).mount(app).run();
19
+ ```
20
+
21
+ `app/main.ts` builds and runs the app. Routes live in `app/routes/`,
22
+ their request shapes in `app/resources/`, errors in `app/errors/`.
23
+
24
+ ## Import map — DO NOT GUESS THESE
25
+
26
+ The single most common mistake is importing a primitive from the wrong
27
+ package. Use exactly:
28
+
29
+ | Primitive | Package |
30
+ |---|---|
31
+ | `createApp`, `appCompose`, `definePlugin` | `@nwire/app` |
32
+ | `endpoint` | `@nwire/endpoint` |
33
+ | `get` `post` `put` `patch` `del` | `@nwire/wires/http` |
34
+ | `httpKoa` | `@nwire/koa` |
35
+ | `defineHandler`, `defineResource`, `defineError`, `Unauthorized`/`Forbidden`/`NotFound`/`Conflict`/`BadRequest` | `@nwire/handler` |
36
+ | `defineEvent` | `@nwire/messages` |
37
+ | `defineAction`, `defineActor`, `defineProjection`, `defineQuery`, `defineWorkflow`, `createForgePlugin` | `@nwire/forge` |
38
+
39
+ `defineResource` and `defineError` are in **`@nwire/handler`**, not
40
+ `@nwire/forge`. This service template does not depend on `@nwire/forge` —
41
+ don't import from it unless you add it to `package.json` first.
42
+
43
+ ## Adding a route
44
+
45
+ 1. A binding + handler: `app/routes/<verb>-<noun>.ts` exports a route
46
+ (`post("/path", { body })`) and a handler `(input, ctx) => result`.
47
+ 2. Register it in `app/api.ts`'s wire list (or `app.wire(route, handler)`).
48
+ 3. Throw a typed `defineError` for failures; return a plain object or a
49
+ `defineResource` projection for success.
50
+
51
+ ## Verbs
52
+
53
+ `.wire(binding, handler)` binds a transport. `.use(adapter)` mounts a
54
+ transport on the endpoint. To react to events, use `when(Event, fn)` in a
55
+ listener file (not `on` — `on` is for lifecycle hooks).
56
+
57
+ ## Commands
58
+
59
+ - `pnpm dev` — run the app (HTTP on :3000).
60
+ - `pnpm test` — run the test suite.
61
+ - `pnpm doctor` — health-check the project setup.
62
+ - `pnpm studio` — open the live trace/inspect console.
@@ -0,0 +1,4 @@
1
+ # nwire dev runs via vite-node (esbuild). pnpm 11 defers esbuild's build
2
+ # script and then fails the pre-run deps check; skip that check so
3
+ # `pnpm dev` runs clean right after install.
4
+ verify-deps-before-run=false
@@ -0,0 +1,6 @@
1
+ # pnpm 11 reads build-allow + run settings here (not the package.json
2
+ # "pnpm" field). esbuild backs vite-node; allow its build, and skip the
3
+ # pre-run deps check so `pnpm dev` runs clean right after install.
4
+ onlyBuiltDependencies:
5
+ - esbuild
6
+ verifyDepsBeforeRun: false
@@ -13,7 +13,7 @@
13
13
  * contract, not invented per call site.
14
14
  */
15
15
 
16
- import { defineError } from "@nwire/forge";
16
+ import { defineError } from "@nwire/handler";
17
17
 
18
18
  /** A user looked up a todo by id and we don't have it. */
19
19
  export const TodoNotFound = defineError({
@@ -45,4 +45,4 @@ export const NoUserId = defineError({
45
45
  });
46
46
 
47
47
  // Re-export common framework errors so handlers import from one place.
48
- export { Unauthorized, Forbidden, NotFound, Gone } from "@nwire/forge";
48
+ export { Unauthorized, Forbidden, NotFound, Gone } from "@nwire/handler";
@@ -35,10 +35,8 @@ export function buildApp() {
35
35
  return app;
36
36
  }
37
37
 
38
- if (import.meta.url === `file://${process.argv[1]}`) {
39
- const app = buildApp();
40
- await endpoint("{{PROJECT_NAME}}", { port: 3000 })
41
- .use(httpKoa({ prefix: "/api", middleware: [requireUser] }))
42
- .mount(app)
43
- .run();
44
- }
38
+ const app = buildApp();
39
+ await endpoint("{{PROJECT_NAME}}", { port: 3000 })
40
+ .use(httpKoa({ prefix: "/api", middleware: [requireUser] }))
41
+ .mount(app)
42
+ .run();
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  import { z } from "zod";
19
- import { defineResource } from "@nwire/forge";
19
+ import { defineResource } from "@nwire/handler";
20
20
 
21
21
  export const Todo = defineResource("Todo", {
22
22
  summary: "A single todo item in a user's list",
@@ -14,9 +14,9 @@
14
14
  * the same — `provide("todos", { boot, shutdown })`. Handlers always do
15
15
  * `resolve("todos")` and get whatever the plugin booted.
16
16
  *
17
- * The L2 template uses the simpler container-register-then-provide path
17
+ * This template uses the simpler container-register-then-provide path
18
18
  * from `main.ts` (no createApp). The plugin form is exported so it's
19
- * one rename away once you graduate to L4.
19
+ * one rename away once you add the forge battery.
20
20
  */
21
21
 
22
22
  import { randomUUID } from "node:crypto";
@@ -96,7 +96,7 @@ export class TodoStore {
96
96
  }
97
97
  }
98
98
 
99
- // ─── The plugin (optional — used when you graduate to L4) ────────
99
+ // ─── The plugin (optional — used when you add the forge battery) ────────
100
100
 
101
101
  /**
102
102
  * Plugin shape: `provide("todos", { boot, shutdown })`.
@@ -109,8 +109,8 @@ export class TodoStore {
109
109
  * (Postgres via Drizzle), or a MongoClient. The plugin wrapper handles
110
110
  * the lifecycle; the store implementation handles its own concerns.
111
111
  *
112
- * L2 main.ts uses the simpler `container.register(...)` path because we
113
- * don't have a forge app yet. The plugin is exported so L4 graduation
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
114
  * is `import { todoStorePlugin } from "./store/todo-store"`.
115
115
  */
116
116
  export const todoStorePlugin = definePlugin("todos", ({ provide }) => {
@@ -5,14 +5,17 @@
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite-node app/main.ts",
8
- "test": "vitest run"
8
+ "test": "vitest run",
9
+ "doctor": "nwire doctor",
10
+ "studio": "nwire studio",
11
+ "cache": "nwire cache"
9
12
  },
10
13
  "dependencies": {
11
- "@nwire/app": "^0.11.1",
12
- "@nwire/endpoint": "^0.11.1",
13
- "@nwire/handler": "^0.11.1",
14
- "@nwire/koa": "^0.11.1",
15
- "@nwire/wires": "^0.11.1",
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",
16
19
  "koa": "^2.16.1",
17
20
  "zod": "^4.0.0"
18
21
  },
@@ -21,6 +24,7 @@
21
24
  "@types/node": "^22.19.9",
22
25
  "typescript": "^5.9.0",
23
26
  "vite-node": "^3.2.4",
24
- "vitest": "^4.0.18"
27
+ "vitest": "^4.0.18",
28
+ "@nwire/cli": "^0.12.1"
25
29
  }
26
30
  }
@@ -9,7 +9,7 @@
9
9
  "skipLibCheck": true,
10
10
  "forceConsistentCasingInFileNames": true,
11
11
  "noEmit": true,
12
- "types": ["node", "vite/client"]
12
+ "types": ["node"]
13
13
  },
14
14
  "include": ["app/**/*", "__tests__/**/*"]
15
15
  }