create-nwire 0.12.0 → 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 +3 -0
- package/package.json +1 -1
- package/templates/enterprise/AGENTS.md +43 -0
- package/templates/enterprise/_npmrc +4 -0
- package/templates/enterprise/_pnpm-workspace.yaml +6 -0
- package/templates/enterprise/app/main.ts +1 -3
- package/templates/enterprise/package.json +13 -9
- package/templates/minimal/AGENTS.md +37 -0
- package/templates/minimal/_npmrc +4 -0
- package/templates/minimal/_pnpm-workspace.yaml +6 -0
- package/templates/minimal/app/main.ts +2 -4
- package/templates/minimal/package.json +10 -6
- package/templates/service/AGENTS.md +62 -0
- package/templates/service/_npmrc +4 -0
- package/templates/service/_pnpm-workspace.yaml +6 -0
- package/templates/service/app/errors/todo-errors.ts +2 -2
- package/templates/service/app/main.ts +5 -7
- package/templates/service/app/resources/todo.ts +1 -1
- package/templates/service/app/store/todo-store.ts +5 -5
- package/templates/service/package.json +11 -7
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
|
@@ -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,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
|
|
@@ -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.12.
|
|
12
|
-
"@nwire/endpoint": "^0.12.
|
|
13
|
-
"@nwire/forge": "^0.12.
|
|
14
|
-
"@nwire/koa": "^0.12.
|
|
15
|
-
"@nwire/messages": "^0.12.
|
|
16
|
-
"@nwire/wires": "^0.12.
|
|
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.12.
|
|
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
|
}
|
|
@@ -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,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
|
-
|
|
33
|
-
|
|
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.12.
|
|
12
|
-
"@nwire/endpoint": "^0.12.
|
|
13
|
-
"@nwire/koa": "^0.12.
|
|
14
|
-
"@nwire/wires": "^0.12.
|
|
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
|
}
|
|
@@ -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,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/
|
|
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/
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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();
|
|
@@ -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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
113
|
-
* don't have a forge app yet. The plugin is exported so
|
|
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.12.
|
|
12
|
-
"@nwire/endpoint": "^0.12.
|
|
13
|
-
"@nwire/handler": "^0.12.
|
|
14
|
-
"@nwire/koa": "^0.12.
|
|
15
|
-
"@nwire/wires": "^0.12.
|
|
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
|
}
|