create-nwire 0.13.0 → 0.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nwire",
3
- "version": "0.13.0",
3
+ "version": "0.13.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",
@@ -16,19 +16,24 @@ pnpm dev
16
16
 
17
17
  ## Try
18
18
 
19
+ `pnpm dev` serves the wire + Studio on http://localhost:4000.
20
+
19
21
  ```bash
20
22
  # submit a post (becomes pending, auto-moderation triggers)
21
- curl -X POST http://localhost:3000/api/posts \
23
+ curl -X POST http://localhost:4000/api/posts \
22
24
  -H "content-type: application/json" \
23
25
  -d '{"authorId":"alice","body":"Hello, world!"}'
24
26
 
25
- # read the dashboard projection
26
- curl http://localhost:3000/api/queue
27
+ # read the dashboard projection — copy a pending postId from here
28
+ curl http://localhost:4000/api/queue
27
29
 
28
- # decide a borderline one manually
29
- curl -X POST http://localhost:3000/api/posts/<id>/approve \
30
+ # decide one manually (paste the postId from /api/queue above)
31
+ curl -X POST http://localhost:4000/api/posts/<postId>/approve \
30
32
  -H "content-type: application/json" \
31
33
  -d '{"moderatorId":"miri"}'
34
+
35
+ # all posts by an author, across statuses
36
+ curl "http://localhost:4000/api/posts/by-author?authorId=alice"
32
37
  ```
33
38
 
34
39
  ## Test
@@ -42,8 +47,8 @@ pnpm test
42
47
  ```
43
48
  {{PROJECT_NAME}}/
44
49
  ├── app/
45
- │ ├── main.ts ← endpoint + app.start + run
46
- │ ├── api.ts ← httpInterfacewires every route
50
+ │ ├── main.ts ← endpoint().use(httpKoa()).mount(app).run()
51
+ │ ├── api.ts ← wires[] — every route + handler pair
47
52
  │ └── app.ts ← createApp({ modules: [...] })
48
53
  ├── modules/
49
54
  │ └── posts/ ← bounded context: posts moderation
@@ -90,11 +95,11 @@ actor (`defineActor`) versus a direct DB write inside a handler.
90
95
 
91
96
  ## Studio
92
97
 
93
- `api.inspect(app)` mounts the `/_nwire/*` introspection surface. Point
94
- Studio at this app:
98
+ `pnpm dev` already serves Studio alongside the wire on http://localhost:4000.
99
+ For the standalone Studio process (multi-project shell, process manager):
95
100
 
96
101
  ```bash
97
- npx -p @nwire/cli nwire studio
102
+ pnpm studio
98
103
  ```
99
104
 
100
105
  You'll get live traces, action / event / workflow browsers, projection
@@ -18,6 +18,8 @@ export const wires = [
18
18
  { binding: approvePostRoute, handler: approvePostHandler },
19
19
  { binding: rejectPostRoute, handler: rejectPostHandler },
20
20
  { binding: listQueueRoute, handler: listQueueHandler },
21
- { binding: getPostRoute, handler: getPostHandler },
21
+ // Literal `/posts/by-author` must be wired before the `/posts/:postId`
22
+ // param route — otherwise `:postId` captures "by-author" and shadows it.
22
23
  { binding: postsByAuthorRoute, handler: postsByAuthorHandler },
24
+ { binding: getPostRoute, handler: getPostHandler },
23
25
  ] as const;
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Entry — the only file that boots a real HTTP server.
3
3
  *
4
- * pnpm dev
4
+ * pnpm dev # wire + Studio on http://localhost:4000
5
5
  *
6
- * curl -X POST http://localhost:3000/api/posts \
6
+ * curl -X POST http://localhost:4000/api/posts \
7
7
  * -H "content-type: application/json" \
8
8
  * -d '{"authorId":"alice","body":"Hello, world!"}'
9
- * curl http://localhost:3000/api/queue
9
+ * curl http://localhost:4000/api/queue
10
10
  *
11
11
  * `app` is a pure value — routes are wired in `./app` on import.
12
12
  * This file adds the one side effect: binding a port and serving traffic.
@@ -74,7 +74,10 @@ export const queueDashboard = defineProjection<QueueState>(
74
74
 
75
75
  when(PostWasApproved, (state, event) => {
76
76
  const item = state.byId[event.postId];
77
- if (!item) return state;
77
+ // Only a pending post transitions. Re-applying a decision (a duplicate
78
+ // event, or an approve after a reject) must be a no-op — otherwise the
79
+ // pending counter is decremented twice and the totals drift negative.
80
+ if (!item || item.status !== "pending") return state;
78
81
  return {
79
82
  byId: {
80
83
  ...state.byId,
@@ -96,7 +99,9 @@ export const queueDashboard = defineProjection<QueueState>(
96
99
 
97
100
  when(PostWasRejected, (state, event) => {
98
101
  const item = state.byId[event.postId];
99
- if (!item) return state;
102
+ // Same idempotency guard as approve: a post that already left the queue
103
+ // must not decrement `pending` a second time.
104
+ if (!item || item.status !== "pending") return state;
100
105
  return {
101
106
  byId: {
102
107
  ...state.byId,
@@ -11,17 +11,17 @@
11
11
  "cache": "nwire cache"
12
12
  },
13
13
  "dependencies": {
14
- "@nwire/app": "^0.13.0",
15
- "@nwire/endpoint": "^0.13.0",
16
- "@nwire/forge": "^0.13.0",
17
- "@nwire/koa": "^0.13.0",
18
- "@nwire/messages": "^0.13.0",
19
- "@nwire/wires": "^0.13.0",
14
+ "@nwire/app": "^0.13.1",
15
+ "@nwire/endpoint": "^0.13.1",
16
+ "@nwire/forge": "^0.13.1",
17
+ "@nwire/koa": "^0.13.1",
18
+ "@nwire/messages": "^0.13.1",
19
+ "@nwire/wires": "^0.13.1",
20
20
  "zod": "^4.0.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@nwire/cli": "^0.13.0",
24
- "@nwire/test-kit": "^0.13.0",
23
+ "@nwire/cli": "^0.13.1",
24
+ "@nwire/test-kit": "^0.13.1",
25
25
  "@types/node": "^22.19.9",
26
26
  "typescript": "^5.9.0",
27
27
  "vitest": "^4.0.18"
@@ -1,6 +1,12 @@
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
1
+ # pnpm reads build-allow + run settings here (not the package.json "pnpm"
2
+ # field). This server runs TypeScript via tsx, which pulls esbuild — a
3
+ # dependency with a build script. pnpm gates build scripts: without an
4
+ # explicit decision it writes an `allowBuilds` stub and exits non-zero on
5
+ # the very first `pnpm install`. Pre-approve esbuild here so install is
6
+ # clean out of the box. `onlyBuiltDependencies` covers older pnpm; skip the
3
7
  # pre-run deps check so `pnpm dev` runs clean right after install.
8
+ allowBuilds:
9
+ esbuild: true
4
10
  onlyBuiltDependencies:
5
11
  - esbuild
6
12
  verifyDepsBeforeRun: false
@@ -10,10 +10,10 @@
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
- "@nwire/app": "^0.13.0",
14
- "@nwire/endpoint": "^0.13.0",
15
- "@nwire/mcp": "^0.13.0",
16
- "@nwire/wires": "^0.13.0",
13
+ "@nwire/app": "^0.13.1",
14
+ "@nwire/endpoint": "^0.13.1",
15
+ "@nwire/mcp": "^0.13.1",
16
+ "@nwire/wires": "^0.13.1",
17
17
  "zod": "^4.0.0"
18
18
  },
19
19
  "devDependencies": {
@@ -9,12 +9,19 @@ handler never knows which transport called it.
9
9
  ```ts
10
10
  import { createApp } from "@nwire/app";
11
11
  import { endpoint } from "@nwire/endpoint";
12
- import { get } from "@nwire/wires/http";
12
+ import { post } from "@nwire/wires/http";
13
13
  import { httpKoa } from "@nwire/koa";
14
+ import { z } from "zod";
14
15
 
15
16
  const app = createApp({ appName: "app" });
16
- app.wire(get("/hello"), async () => ({ message: "hello" }));
17
- await endpoint("app", { port: 3000 }).use(httpKoa()).mount(app).run();
17
+ // The route declares its schema; the handler receives the validated input.
18
+ app.wire(post("/hello", { body: z.object({ name: z.string().min(1) }) }), async (input) => ({
19
+ message: `Hello, ${input.name}!`,
20
+ }));
21
+ await endpoint("app", { port: Number(process.env.PORT) || 3000 })
22
+ .use(httpKoa())
23
+ .mount(app)
24
+ .run();
18
25
  ```
19
26
 
20
27
  ## What's installed (don't import beyond these)
@@ -33,5 +40,5 @@ This tier is HTTP-only. Add capabilities by installing the package first:
33
40
 
34
41
  ## Commands
35
42
 
36
- - `pnpm dev` — run the app (HTTP on :3000). `pnpm test` — tests.
43
+ - `pnpm dev` — wire + Studio on http://localhost:4000. `pnpm test` — tests.
37
44
  - `pnpm doctor` — health-check the setup. `pnpm studio` — trace console.
@@ -16,8 +16,10 @@ pnpm dev
16
16
 
17
17
  ## Try
18
18
 
19
+ `pnpm dev` serves the wire + Studio on http://localhost:4000.
20
+
19
21
  ```bash
20
- curl -X POST http://localhost:3000/hello \
22
+ curl -X POST http://localhost:4000/hello \
21
23
  -H "content-type: application/json" \
22
24
  -d '{"name":"Alice"}'
23
25
  ```
@@ -33,8 +35,9 @@ pnpm test
33
35
  ```
34
36
  {{PROJECT_NAME}}/
35
37
  ├── app/
36
- │ ├── main.ts ← endpoint().serve(api).run()
37
- │ ├── api.ts ← httpInterface().wire(route, handler)
38
+ │ ├── main.ts ← endpoint().use(httpKoa()).mount(app).run()
39
+ │ ├── app.ts ← createApp().wire(...) — the app value
40
+ │ ├── api.ts ← wires[] (route + handler pairs)
38
41
  │ └── routes/
39
42
  │ └── hello.ts ← POST /hello — route + handler pair
40
43
  └── __tests__/
@@ -51,13 +54,20 @@ pnpm test
51
54
 
52
55
  ## What you get free
53
56
 
54
- | Feature | Comes from |
55
- | -------------------------------------- | ----------------- |
56
- | Zod request validation | `@nwire/http` |
57
- | OpenAPI schema (visit `/openapi.json`) | `@nwire/http` |
58
- | Scalar UI docs (visit `/docs`) | `@nwire/http` |
59
- | Graceful SIGTERM drain | `@nwire/endpoint` |
60
- | K8s probes on port 9400 | `@nwire/endpoint` |
57
+ | Feature | Comes from |
58
+ | ------------------------------------ | ----------------- |
59
+ | Zod request validation | `@nwire/koa` |
60
+ | Graceful SIGTERM drain | `@nwire/endpoint` |
61
+ | K8s probes (port 9400/`$PROBE_PORT`) | `@nwire/endpoint` |
62
+
63
+ OpenAPI + Scalar docs are one opt-in away — pass them to the transport:
64
+
65
+ ```ts
66
+ endpoint("{{PROJECT_NAME}}")
67
+ .use(httpKoa({ openapi: { auto: true }, docs: true })) // /openapi.json + /docs
68
+ .mount(app)
69
+ .run();
70
+ ```
61
71
 
62
72
  ## Grow up
63
73
 
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Entry — the only file that boots a real HTTP server.
3
3
  *
4
- * pnpm dev
4
+ * pnpm dev # wire + Studio on http://localhost:4000
5
5
  *
6
- * curl -X POST http://localhost:3000/hello \
6
+ * curl -X POST http://localhost:4000/hello \
7
7
  * -H "content-type: application/json" \
8
8
  * -d '{"name":"Alice"}'
9
9
  *
@@ -18,4 +18,7 @@ import { endpoint } from "@nwire/endpoint";
18
18
  import { httpKoa } from "@nwire/koa";
19
19
  import { app } from "./app";
20
20
 
21
- await endpoint("{{PROJECT_NAME}}", { port: 3000 }).use(httpKoa()).mount(app).run();
21
+ await endpoint("{{PROJECT_NAME}}", { port: Number(process.env.PORT) || 3000 })
22
+ .use(httpKoa())
23
+ .mount(app)
24
+ .run();
@@ -11,14 +11,14 @@
11
11
  "cache": "nwire cache"
12
12
  },
13
13
  "dependencies": {
14
- "@nwire/app": "^0.13.0",
15
- "@nwire/endpoint": "^0.13.0",
16
- "@nwire/koa": "^0.13.0",
17
- "@nwire/wires": "^0.13.0",
14
+ "@nwire/app": "^0.13.1",
15
+ "@nwire/endpoint": "^0.13.1",
16
+ "@nwire/koa": "^0.13.1",
17
+ "@nwire/wires": "^0.13.1",
18
18
  "zod": "^4.0.0"
19
19
  },
20
20
  "devDependencies": {
21
- "@nwire/cli": "^0.13.0",
21
+ "@nwire/cli": "^0.13.1",
22
22
  "@types/node": "^22.19.9",
23
23
  "typescript": "^5.9.0",
24
24
  "vitest": "^4.0.18"
@@ -58,7 +58,7 @@ listener file (not `on` — `on` is for lifecycle hooks).
58
58
 
59
59
  ## Commands
60
60
 
61
- - `pnpm dev` — run the app (HTTP on :3000).
61
+ - `pnpm dev` — wire + Studio on http://localhost:4000.
62
62
  - `pnpm test` — run the test suite.
63
63
  - `pnpm doctor` — health-check the project setup.
64
64
  - `pnpm studio` — open the live trace/inspect console.
@@ -16,18 +16,20 @@ pnpm dev
16
16
 
17
17
  ## Try
18
18
 
19
+ `pnpm dev` serves the wire + Studio on http://localhost:4000.
20
+
19
21
  ```bash
20
22
  # create a todo
21
- curl -X POST http://localhost:3000/api/todos \
23
+ curl -X POST http://localhost:4000/api/todos \
22
24
  -H "content-type: application/json" \
23
25
  -H "x-user-id: alice" \
24
26
  -d '{"text":"buy milk"}'
25
27
 
26
28
  # list this user's todos
27
- curl -H "x-user-id: alice" http://localhost:3000/api/todos
29
+ curl -H "x-user-id: alice" http://localhost:4000/api/todos
28
30
 
29
31
  # complete one
30
- curl -X POST http://localhost:3000/api/todos/<id>/complete \
32
+ curl -X POST http://localhost:4000/api/todos/<id>/complete \
31
33
  -H "x-user-id: alice"
32
34
  ```
33
35
 
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Entry — the only file that boots a real HTTP server.
3
3
  *
4
- * pnpm dev
4
+ * pnpm dev # wire + Studio on http://localhost:4000
5
5
  *
6
- * curl -X POST http://localhost:3000/api/todos \
6
+ * curl -X POST http://localhost:4000/api/todos \
7
7
  * -H "content-type: application/json" \
8
8
  * -H "x-user-id: alice" \
9
9
  * -d '{"text":"buy milk"}'
10
- * curl -H "x-user-id: alice" http://localhost:3000/api/todos
10
+ * curl -H "x-user-id: alice" http://localhost:4000/api/todos
11
11
  *
12
12
  * `app` is a pure value — routes are wired in `./app` on import.
13
13
  * This file adds the one side effect: binding a port and serving traffic.
@@ -11,16 +11,16 @@
11
11
  "cache": "nwire cache"
12
12
  },
13
13
  "dependencies": {
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",
14
+ "@nwire/app": "^0.13.1",
15
+ "@nwire/endpoint": "^0.13.1",
16
+ "@nwire/handler": "^0.13.1",
17
+ "@nwire/koa": "^0.13.1",
18
+ "@nwire/wires": "^0.13.1",
19
19
  "koa": "^2.16.1",
20
20
  "zod": "^4.0.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@nwire/cli": "^0.13.0",
23
+ "@nwire/cli": "^0.13.1",
24
24
  "@types/koa": "^2.15.0",
25
25
  "@types/node": "^22.19.9",
26
26
  "typescript": "^5.9.0",