@nilejs/cli 1.0.2 → 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nile-js
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -72,12 +72,12 @@ const getUserSchema = z.object({
72
72
 
73
73
  type GetUserInput = z.infer<typeof getUserSchema>;
74
74
 
75
- const getUserHandler = async (input: GetUserInput) => {
75
+ const getUserHandler = async (data: GetUserInput) => {
76
76
  // Implement users.get-user here
77
- return Ok({ result: input });
77
+ return Ok({ result: data });
78
78
  };
79
79
 
80
- export const getUserAction = createAction<GetUserInput>({
80
+ export const getUserAction = createAction({
81
81
  name: "get-user",
82
82
  description: "GetUser action for users",
83
83
  validation: getUserSchema,
@@ -107,7 +107,7 @@ Every registered action is included in `ServicePayloads`. Actions without a vali
107
107
 
108
108
  ## Current starter
109
109
 
110
- CLI 1.0.2 targets Nile `^1.0.4`, Zod 4, TypeScript 7 and the Drizzle model helpers. The generated layout passes Nile’s strict boot checks. Database setup returns a Result and prevents startup on failure; shutdown drains Nile’s work before closing PGlite. Helpers are available as `utils.createModel`, and Result helpers come from `@nilejs/nile/fp`.
110
+ The starter uses Nile `^1.1.0`, Zod 4, TypeScript 7 and the Drizzle model helpers. The generated layout passes Nile’s strict boot checks. Database setup returns a Result and prevents startup on failure; shutdown drains Nile’s work before closing PGlite. Helpers are available as `utils.createModel`, and Result helpers come from `@nilejs/nile/fp`.
111
111
 
112
112
  Existing apps are not rewritten when the CLI updates. For an older starter, add a service entry point at `src/services/<domain>/index.ts`, register its actions there, and import that service from `services.config.ts`. [Nile’s migration notes](https://github.com/nile-js/nile/blob/main/packages/nile/docs/release-migration.md) describe the current error and startup behavior.
113
113
 
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { resolve as resolve6 } from "node:path";
5
5
  import { Command } from "commander";
6
6
  // package.json
7
- var version = "1.0.2";
7
+ var version = "1.0.3";
8
8
 
9
9
  // src/commands/generate-action.ts
10
10
  import { resolve } from "node:path";
@@ -99,12 +99,12 @@ const ${camel}Schema = z.object({
99
99
 
100
100
  type ${pascal}Input = z.infer<typeof ${camel}Schema>;
101
101
 
102
- const ${camel}Handler = async (input: ${pascal}Input) => {
102
+ const ${camel}Handler = async (data: ${pascal}Input) => {
103
103
  // Implement ${serviceName}.${actionName} here
104
- return Ok({ result: input });
104
+ return Ok({ result: data });
105
105
  };
106
106
 
107
- export const ${camel}Action = createAction<${pascal}Input>({
107
+ export const ${camel}Action = createAction({
108
108
  name: "${actionName}",
109
109
  description: "${pascal} action for ${serviceName}",
110
110
  validation: ${camel}Schema,
@@ -459,11 +459,11 @@ const sample${pascal}Schema = z.object({
459
459
 
460
460
  type Sample${pascal}Input = z.infer<typeof sample${pascal}Schema>;
461
461
 
462
- const sample${pascal}Handler = async (input: Sample${pascal}Input) => {
463
- return Ok({ ${camel}: { name: input.name } });
462
+ const sample${pascal}Handler = async (data: Sample${pascal}Input) => {
463
+ return Ok({ ${camel}: { name: data.name } });
464
464
  };
465
465
 
466
- export const sample${pascal}Action = createAction<Sample${pascal}Input>({
466
+ export const sample${pascal}Action = createAction({
467
467
  name: "sample",
468
468
  description: "Sample ${serviceName} action",
469
469
  validation: sample${pascal}Schema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nilejs/cli",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "CLI for scaffolding and generating Nile backend projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,6 +10,31 @@
10
10
  "dist",
11
11
  "template"
12
12
  ],
13
+ "homepage": "https://nile-js.github.io/nile/",
14
+ "bugs": {
15
+ "url": "https://github.com/nile-js/nile/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/nile-js/nile.git",
20
+ "directory": "packages/cli"
21
+ },
22
+ "author": "Hussein Kizz",
23
+ "license": "MIT",
24
+ "keywords": [
25
+ "nile",
26
+ "cli",
27
+ "scaffolding",
28
+ "generator",
29
+ "codegen",
30
+ "backend",
31
+ "typescript",
32
+ "bun",
33
+ "zod"
34
+ ],
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
13
38
  "dependencies": {
14
39
  "commander": "^13.1.0",
15
40
  "json-schema-to-zod": "^2.7.0",
@@ -85,7 +85,7 @@ Responses have exactly `status`, `message` and `data`. The default success messa
85
85
 
86
86
  ## Optional features
87
87
 
88
- Keep business logic in actions and ordinary functions. Configure other features when the product needs them: [typed Socket.IO topics](https://nile-js.github.io/nile/guide/features/websockets/), [cron jobs](https://nile-js.github.io/nile/guide/features/cron-jobs/), [rate limits](https://nile-js.github.io/nile/guide/features/rate-limiting/), [queues](https://nile-js.github.io/nile/guide/features/queues-debounce-throttle/) and [telemetry](https://nile-js.github.io/nile/guide/features/telemetry/). The frontend package provides both `createNileClient` and a separate `createNileSocketClient`.
88
+ Keep business logic in actions and ordinary functions. Configure other features when the product needs them: [typed Socket.IO topics](https://nile-js.github.io/nile/guide/features/websockets/), [cron jobs](https://nile-js.github.io/nile/guide/features/cron-jobs/), [rate limits](https://nile-js.github.io/nile/guide/features/rate-limiting/), [queues](https://nile-js.github.io/nile/guide/features/queues-debounce-throttle/) and [telemetry](https://nile-js.github.io/nile/guide/features/telemetry/). On the frontend, `createNileClient` calls actions and sends and receives topics through `nile.socket`.
89
89
 
90
90
  ## Adding services
91
91
 
package/template/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: nile
3
- description: Work in a Nile Js backend (@nilejs/nile 1.x). Organize business operations as services and actions with Zod validation, Result returns, hooks, JWT protection and typed clients. Use when adding or changing actions, services, hooks, uploads, topics, schedules or the @nilejs/client calls in a Nile Js project.
3
+ description: Work in a Nile Js backend (@nilejs/nile 2.x). Organize business operations as services and actions with Zod validation, Result returns, hooks, JWT protection and typed clients. Use when adding or changing actions, services, hooks, uploads, topics, schedules or the @nilejs/client calls in a Nile Js project.
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -39,7 +39,6 @@ src/
39
39
  client.ts connection and driver
40
40
  models/ persistence operations
41
41
  hooks/ shared action policies
42
- business/ optional reusable domain functions
43
42
  generated/ client payload/schema contracts
44
43
  ```
45
44
 
@@ -49,7 +48,7 @@ Structure violations fail startup by default. An application needs `src/index.ts
49
48
 
50
49
  ## The action pattern
51
50
 
52
- Every action uses a named schema, an inferred input type, a separately written named handler and `createAction<T>`:
51
+ Define a named schema, derive the handler’s data type and pass the named handler to `createAction`. The action type and handler return type are inferred. Use `data` and `context` for the parameters you need; omit unused parameters:
53
52
 
54
53
  ```ts
55
54
  import { createAction } from "@nilejs/nile";
@@ -62,15 +61,15 @@ const createProjectSchema = z.object({
62
61
 
63
62
  type CreateProjectInput = z.infer<typeof createProjectSchema>;
64
63
 
65
- const createProjectHandler = (input: CreateProjectInput) => {
66
- if (input.name === "system") {
64
+ const createProjectHandler = (data: CreateProjectInput) => {
65
+ if (data.name === "system") {
67
66
  return Err("Choose a different project name");
68
67
  }
69
68
 
70
- return Ok({ project: { name: input.name } });
69
+ return Ok({ project: { name: data.name } });
71
70
  };
72
71
 
73
- export const createProjectAction = createAction<CreateProjectInput>({
72
+ export const createProjectAction = createAction({
74
73
  name: "create",
75
74
  description: "Create a project",
76
75
  validation: createProjectSchema,
@@ -104,7 +103,7 @@ Action names are unique within a service. Folder names do not create routes; onl
104
103
 
105
104
  ## Handlers take (data, context)
106
105
 
107
- Nile Js calls every handler with two arguments: the validated input and the execution's `NileContext`. The type marks the context optional. Use `createAction<Input>` for the schema’s parsed output type, and import `NileContext` when annotating a named handler’s second argument. Read an optional context with `?.` or explicitly reject its absence before using it.
106
+ Nile Js calls every handler with two arguments: the validated input and the execution's `NileContext`. For a named handler, annotate `data` with the schema’s parsed output type and use `context` for its second parameter. Import `NileContext` when you need to annotate it. `createAction` infers its type from the handler; leave off the repeated generic argument and inferred return annotation. Read an optional context with `?.` or explicitly reject its absence before using it.
108
107
 
109
108
  - `context.getSession("rest")` returns the verified JWT identity (`userId`, `organizationId` and other claims) for a protected action.
110
109
  - `context.resources` holds what `createNileServer` received (`logger`, `database`).
@@ -258,7 +257,7 @@ This helper is internal: its failure still needs translation before being return
258
257
 
259
258
  - HTTP: `POST /api/services` with `{ "intent": "execute", "service": "projects", "action": "create", "payload": { ... } }`.
260
259
  - Client (`@nilejs/client`): `createNileClient<Payloads>({ baseUrl })`, then `await nile.invoke({ service, action, payload })`. Every method returns a Result: `if (result.isErr) { ... return; }`, then read `result.value`. On failure `result.error` is the message and `result.details` holds the server's `data`. `data` is a deprecated alias; do not use it.
261
- - Sockets: `createNileSocketClient<Actions, Topics>` owns its connection. Use `socket.invoke`, `socket.send` and `socket.receive`; check each Result and unsubscribe/close when finished. Declare backend topics and validate their data and scope; frontend types come from shared or generated definitions. Authorization is still a backend policy.
260
+ - Topics: define each with `createTopic({ name, data, scope?, canReceive?, canSend? })` and pass `createTopics([...])` to the server `socket` config. A missing `canReceive` or `canSend` denies clients. Connections verify the server `auth` JWT; `user` in the checks is `{ userId, organizationId, claims }` or `undefined`. In actions, `const { socket } = getContext({ topics })`, then `socket.send({ topic, scope?, to?: { userId } | { connectionId }, data })` after the write commits. On the frontend, `nile.socket.receive({ topic, scope, onMessage })` subscribes and returns a Result holding `unsubscribe()`; `nile.socket.send` sends. Sockets do not call actions; use `nile.invoke`.
262
261
  - Server code: `await server.invoke({ service, action, payload, credentials? })`. Protected actions need `credentials` with headers.
263
262
  - Uploads: `nile.upload({ service, action, files, fields })` for `isSpecial: { contentType: "multipart/form-data" }` actions. A JSON request to such an action returns HTTP 400.
264
263
 
@@ -267,7 +266,7 @@ This helper is internal: its failure still needs translation before being return
267
266
  1. Find the domain in `services.config.ts` and read its `index.ts`. Name the action for the business operation, then define its input schema and named handler.
268
267
  2. Keep a short, single-use rule in that handler. Extract ordinary domain functions when a rule is reused, complicated enough to test independently, or coordinates a transaction. Do not create a repository, class or service layer solely because a folder exists.
269
268
  3. Put data access in the project’s model convention. `utils.createModel` and `utils.getZodSchema` are optional Drizzle helpers; a handwritten model is also valid. Pass the database or transaction explicitly where transaction ownership matters.
270
- 4. For a growing action, move its private helpers under its action folder’s `modules/`. Put helpers shared by the domain in the service’s `modules/`. Cross-domain rules can use `business/` when useful. Do not expose a private helper as an action just to call it.
269
+ 4. For a growing action, move its private helpers under its action folder’s `modules/`. Put helpers shared by the domain in the service’s `modules/`. A rule another domain also needs stays in the domain that owns it; the other service imports it from there. Do not expose a private helper as an action just to call it.
271
270
  5. Register the action in the domain’s `actions` list. An export alone does not make it reachable. Register a new service in the root registry.
272
271
  6. Carry validated input into business functions and trusted identity into policy checks. Keep request identity in scoped sessions and execution state, not process-wide context values.
273
272
  7. Verify the registered operation through `server.invoke` or a transport when changing validation, permissions or registration. Test a transaction’s rollback and idempotency when those are part of the business guarantee.
@@ -311,7 +310,7 @@ Read the page for the feature you change before you write code:
311
310
  - [Cron jobs](https://nile-js.github.io/nile/guide/features/cron-jobs)
312
311
  - [Database models](https://nile-js.github.io/nile/guide/features/database-models)
313
312
  - [API discovery and schemas](https://nile-js.github.io/nile/guide/features/api-discovery)
314
- - [HTTP, WebSocket and direct calls](https://nile-js.github.io/nile/guide/features/calling-actions)
313
+ - [HTTP and direct calls](https://nile-js.github.io/nile/guide/features/calling-actions)
315
314
  - [Retries and utilities](https://nile-js.github.io/nile/guide/features/retries-and-utilities)
316
315
  - [Rate limiting](https://nile-js.github.io/nile/guide/features/rate-limiting)
317
316
  - [File uploads](https://nile-js.github.io/nile/guide/features/file-uploads)
@@ -324,7 +323,7 @@ Advanced pages cover handler context, composed hooks, execution options and test
324
323
  1. Read `services.config.ts`, the target service’s `index.ts` and its actions before you change anything.
325
324
  2. Follow the existing pattern in that service. Do not add a second architecture.
326
325
  3. Never invent an API. Check the guide, the installed `@nilejs/nile` types in `node_modules`, or the existing code. If it is not there, say so.
327
- 4. Use named schemas, inferred input types, named handlers, `createAction<T>` and braces on every `if`.
326
+ 4. Use named schemas and handlers. Name action parameters `data` and `context`, and hook parameters `hookContext`. Annotate a named handler’s data from the schema; let `createAction` and return types infer the rest. Use braces on every `if`.
328
327
  5. Start with business rules in the action’s named handler. Move reusable rules or transaction coordination into ordinary domain functions when that makes them easier to share or test. Keep persistence in models or repositories; keep transaction boundaries explicit.
329
328
  6. Typecheck and run the relevant tests after a coherent change. Exercise the registered action through `server.invoke` or a transport when registration, policy or validation changes.
330
329
  7. Do not commit, publish or change secrets, `.env` values or deployment settings unless the user asks.
@@ -337,7 +336,7 @@ Advanced pages cover handler context, composed hooks, execution options and test
337
336
  - A global before hook's returned value does not replace the payload. Use a local hook to transform input.
338
337
  - A local hook does not protect the parent action.
339
338
  - `context.set` values are shared by all requests. Never store the current user there.
340
- - A rate limit or topic policy that is missing denies or does not apply. Read the feature page for the default.
339
+ - A rate limit or topic permission (`canReceive`, `canSend`) that is missing denies or does not apply. Read the feature page for the default.
341
340
  - Multiple replicas each keep their own queues, schedules, rate-limit counters and WebSocket subscriptions.
342
341
  - JWT failures return fixed messages such as `JWT authentication failed: token expired` or `JWT authentication failed: invalid token`. They never include the token or its claims, so do not add them back in your own logs.
343
342
  - Import Results from `@nilejs/nile/fp`, not a separately installed `slang-ts`. Two copies of `slang-ts` produce type errors.
@@ -12,7 +12,7 @@
12
12
  "typecheck": "tsc --noEmit"
13
13
  },
14
14
  "dependencies": {
15
- "@nilejs/nile": "^1.0.4",
15
+ "@nilejs/nile": "^1.1.0",
16
16
  "@electric-sql/pglite": "^0.2.5",
17
17
  "drizzle-orm": "^0.45.4",
18
18
  "pino": "^10.4.0",
@@ -54,12 +54,12 @@ const server = await createNileServer({
54
54
  onBoot: {
55
55
  // The first PGlite start on a slow machine can take longer than the 10 second default.
56
56
  maxWaitTime: 30_000,
57
- fn: async (ctx) => {
57
+ fn: async (context) => {
58
58
  const prepared = await pushSchema();
59
59
  if (prepared.isErr) {
60
60
  return prepared;
61
61
  }
62
- ctx.resources?.logger?.info({
62
+ context.resources?.logger?.info({
63
63
  atFunction: "onBoot",
64
64
  message: "{{projectName}} booted - PGLite schema ready",
65
65
  });
@@ -1,5 +1,5 @@
1
1
  import { createAction } from "@nilejs/nile";
2
- import { Err, Ok } from "@nilejs/nile/fp";
2
+ import { Ok } from "@nilejs/nile/fp";
3
3
  import z from "zod";
4
4
  import { taskModel } from "@/db/models";
5
5
 
@@ -14,23 +14,23 @@ const createTaskSchema = z.object({
14
14
 
15
15
  type CreateTaskInput = z.infer<typeof createTaskSchema>;
16
16
 
17
- const createTaskHandler = async (input: CreateTaskInput) => {
17
+ const createTaskHandler = async (data: CreateTaskInput) => {
18
18
  const result = await taskModel.create({
19
19
  data: {
20
- title: input.title,
21
- description: input.description,
22
- status: input.status,
20
+ title: data.title,
21
+ description: data.description,
22
+ status: data.status,
23
23
  },
24
24
  });
25
25
 
26
26
  if (result.isErr) {
27
- return Err(result.error);
27
+ return result;
28
28
  }
29
29
 
30
30
  return Ok({ task: result.value });
31
31
  };
32
32
 
33
- export const createTaskAction = createAction<CreateTaskInput>({
33
+ export const createTaskAction = createAction({
34
34
  name: "create",
35
35
  description: "Create a new task",
36
36
  validation: createTaskSchema,
@@ -1,5 +1,5 @@
1
1
  import { createAction } from "@nilejs/nile";
2
- import { Err, Ok } from "@nilejs/nile/fp";
2
+ import { Ok } from "@nilejs/nile/fp";
3
3
  import z from "zod";
4
4
  import { taskModel } from "@/db/models";
5
5
 
@@ -9,17 +9,17 @@ const deleteTaskSchema = z.object({
9
9
 
10
10
  type DeleteTaskInput = z.infer<typeof deleteTaskSchema>;
11
11
 
12
- const deleteTaskHandler = async (input: DeleteTaskInput) => {
13
- const result = await taskModel.delete(input.id);
12
+ const deleteTaskHandler = async (data: DeleteTaskInput) => {
13
+ const result = await taskModel.delete(data.id);
14
14
 
15
15
  if (result.isErr) {
16
- return Err(result.error);
16
+ return result;
17
17
  }
18
18
 
19
- return Ok({ deleted: true, id: input.id });
19
+ return Ok({ deleted: true, id: data.id });
20
20
  };
21
21
 
22
- export const deleteTaskAction = createAction<DeleteTaskInput>({
22
+ export const deleteTaskAction = createAction({
23
23
  name: "delete",
24
24
  description: "Delete a task by ID",
25
25
  validation: deleteTaskSchema,
@@ -1,5 +1,5 @@
1
1
  import { createAction } from "@nilejs/nile";
2
- import { Err, Ok } from "@nilejs/nile/fp";
2
+ import { Ok } from "@nilejs/nile/fp";
3
3
  import z from "zod";
4
4
  import { taskModel } from "@/db/models";
5
5
 
@@ -9,17 +9,17 @@ const getTaskSchema = z.object({
9
9
 
10
10
  type GetTaskInput = z.infer<typeof getTaskSchema>;
11
11
 
12
- const getTaskHandler = async (input: GetTaskInput) => {
13
- const result = await taskModel.findById(input.id);
12
+ const getTaskHandler = async (data: GetTaskInput) => {
13
+ const result = await taskModel.findById(data.id);
14
14
 
15
15
  if (result.isErr) {
16
- return Err(result.error);
16
+ return result;
17
17
  }
18
18
 
19
19
  return Ok({ task: result.value });
20
20
  };
21
21
 
22
- export const getTaskAction = createAction<GetTaskInput>({
22
+ export const getTaskAction = createAction({
23
23
  name: "get",
24
24
  description: "Get a task by ID",
25
25
  validation: getTaskSchema,
@@ -1,12 +1,12 @@
1
1
  import { createAction } from "@nilejs/nile";
2
- import { Err, Ok } from "@nilejs/nile/fp";
2
+ import { Ok } from "@nilejs/nile/fp";
3
3
  import { taskModel } from "@/db/models";
4
4
 
5
5
  const listTasksHandler = async () => {
6
6
  const result = await taskModel.findAll();
7
7
 
8
8
  if (result.isErr) {
9
- return Err(result.error);
9
+ return result;
10
10
  }
11
11
 
12
12
  return Ok({ tasks: result.value });
@@ -1,5 +1,5 @@
1
1
  import { createAction } from "@nilejs/nile";
2
- import { Err, Ok } from "@nilejs/nile/fp";
2
+ import { Ok } from "@nilejs/nile/fp";
3
3
  import z from "zod";
4
4
  import { taskModel } from "@/db/models";
5
5
 
@@ -12,25 +12,25 @@ const updateTaskSchema = z.object({
12
12
 
13
13
  type UpdateTaskInput = z.infer<typeof updateTaskSchema>;
14
14
 
15
- const updateTaskHandler = async (input: UpdateTaskInput) => {
15
+ const updateTaskHandler = async (data: UpdateTaskInput) => {
16
16
  // Fields left out of the request stay undefined and are not changed.
17
17
  const result = await taskModel.update({
18
- id: input.id,
18
+ id: data.id,
19
19
  data: {
20
- title: input.title,
21
- description: input.description,
22
- status: input.status,
20
+ title: data.title,
21
+ description: data.description,
22
+ status: data.status,
23
23
  },
24
24
  });
25
25
 
26
26
  if (result.isErr) {
27
- return Err(result.error);
27
+ return result;
28
28
  }
29
29
 
30
30
  return Ok({ task: result.value });
31
31
  };
32
32
 
33
- export const updateTaskAction = createAction<UpdateTaskInput>({
33
+ export const updateTaskAction = createAction({
34
34
  name: "update",
35
35
  description: "Update an existing task",
36
36
  validation: updateTaskSchema,