@nilejs/cli 1.0.1 → 1.0.3
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/README.md +5 -5
- package/dist/index.js +14 -21
- package/package.json +5 -3
- package/template/README.md +1 -1
- package/template/SKILL.md +7 -7
- package/template/package.json +1 -1
- package/template/src/index.ts +2 -2
- package/template/src/services/tasks/actions/create.ts +7 -7
- package/template/src/services/tasks/actions/delete.ts +6 -6
- package/template/src/services/tasks/actions/get.ts +5 -5
- package/template/src/services/tasks/actions/list.ts +2 -2
- package/template/src/services/tasks/actions/update.ts +8 -8
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 (
|
|
75
|
+
const getUserHandler = async (data: GetUserInput) => {
|
|
76
76
|
// Implement users.get-user here
|
|
77
|
-
return Ok({ result:
|
|
77
|
+
return Ok({ result: data });
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
export const getUserAction = createAction
|
|
80
|
+
export const getUserAction = createAction({
|
|
81
81
|
name: "get-user",
|
|
82
82
|
description: "GetUser action for users",
|
|
83
83
|
validation: getUserSchema,
|
|
@@ -103,11 +103,11 @@ Reads every action's validation schema from `src/services/services.config.ts` an
|
|
|
103
103
|
| `-e, --entry <path>` | Path to the services config (detected by default) |
|
|
104
104
|
| `-o, --output <path>` | Output directory (default: `src/generated`) |
|
|
105
105
|
|
|
106
|
-
Actions without a validation schema
|
|
106
|
+
Every registered action is included in `ServicePayloads`. Actions without a validation schema accept an object payload (`Record<string, unknown>`), so actions such as `tasks.list` can be called with `{}`. This fallback does not add server-side validation. Schemas that cannot be converted are reported separately. Share the generated files with a frontend to validate forms with the same rules as the server.
|
|
107
107
|
|
|
108
108
|
## Current starter
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
The starter uses Nile `^1.0.5`, 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.
|
|
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 (
|
|
102
|
+
const ${camel}Handler = async (data: ${pascal}Input) => {
|
|
103
103
|
// Implement ${serviceName}.${actionName} here
|
|
104
|
-
return Ok({ result:
|
|
104
|
+
return Ok({ result: data });
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
-
export const ${camel}Action = createAction
|
|
107
|
+
export const ${camel}Action = createAction({
|
|
108
108
|
name: "${actionName}",
|
|
109
109
|
description: "${pascal} action for ${serviceName}",
|
|
110
110
|
validation: ${camel}Schema,
|
|
@@ -210,7 +210,7 @@ var buildSchemaEntries = (extraction) => {
|
|
|
210
210
|
const entries = [];
|
|
211
211
|
for (const [serviceName, actions] of Object.entries(extraction.schemas)) {
|
|
212
212
|
for (const [actionName, jsonSchema] of Object.entries(actions)) {
|
|
213
|
-
const zodCode = jsonSchemaToZod(jsonSchema, {
|
|
213
|
+
const zodCode = jsonSchema === null ? "z.record(z.string(), z.unknown())" : jsonSchemaToZod(jsonSchema, {
|
|
214
214
|
noImport: true
|
|
215
215
|
});
|
|
216
216
|
entries.push({
|
|
@@ -237,7 +237,7 @@ var generateSchemasFile = (extraction) => {
|
|
|
237
237
|
lines.push("");
|
|
238
238
|
}
|
|
239
239
|
if (extraction.skipped.length > 0) {
|
|
240
|
-
lines.push(`// Skipped (
|
|
240
|
+
lines.push(`// Skipped (schema conversion failed): ${extraction.skipped.join(", ")}`);
|
|
241
241
|
lines.push("");
|
|
242
242
|
}
|
|
243
243
|
return lines.join(`
|
|
@@ -247,7 +247,7 @@ var generateTypesFile = (extraction) => {
|
|
|
247
247
|
const entries = buildSchemaEntries(extraction);
|
|
248
248
|
if (entries.length === 0) {
|
|
249
249
|
return `${generateHeader()}
|
|
250
|
-
|
|
250
|
+
export type ServicePayloads = Record<never, never>;
|
|
251
251
|
`;
|
|
252
252
|
}
|
|
253
253
|
const schemaImports = entries.map((e) => e.exportName);
|
|
@@ -302,7 +302,7 @@ for (const service of services) {
|
|
|
302
302
|
|
|
303
303
|
for (const action of service.actions) {
|
|
304
304
|
if (!action.validation) {
|
|
305
|
-
|
|
305
|
+
serviceSchemas[action.name] = null;
|
|
306
306
|
continue;
|
|
307
307
|
}
|
|
308
308
|
|
|
@@ -419,15 +419,8 @@ var generateSchemaCommand = async (options) => {
|
|
|
419
419
|
error(err instanceof Error ? err.message : String(err));
|
|
420
420
|
process.exit(1);
|
|
421
421
|
}
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
spinner.stop("No action schemas found");
|
|
425
|
-
warn("None of the actions have validation schemas defined.");
|
|
426
|
-
hint("Add a Zod validation schema to your actions to generate types.");
|
|
427
|
-
outro();
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
spinner.stop(`Extracted ${schemaCount} action schema${schemaCount > 1 ? "s" : ""}`);
|
|
422
|
+
const actionCount = Object.values(extraction.schemas).reduce((sum, actions) => sum + Object.keys(actions).length, 0);
|
|
423
|
+
spinner.stop(`Read ${actionCount} action contract${actionCount === 1 ? "" : "s"}`);
|
|
431
424
|
const outputDir = resolve3(projectRoot, options.output ?? DEFAULT_OUTPUT_DIR);
|
|
432
425
|
await ensureDir(outputDir);
|
|
433
426
|
const schemasContent = generateSchemasFile(extraction);
|
|
@@ -440,7 +433,7 @@ var generateSchemaCommand = async (options) => {
|
|
|
440
433
|
success(`Generated ${typesPath.replace(projectRoot, "").replace(LEADING_SLASH2, "")}`);
|
|
441
434
|
if (extraction.skipped.length > 0) {
|
|
442
435
|
console.log("");
|
|
443
|
-
|
|
436
|
+
warn(`Could not convert validation schemas for ${extraction.skipped.length} action(s):`);
|
|
444
437
|
hint(extraction.skipped.join(", "));
|
|
445
438
|
}
|
|
446
439
|
outro();
|
|
@@ -466,11 +459,11 @@ const sample${pascal}Schema = z.object({
|
|
|
466
459
|
|
|
467
460
|
type Sample${pascal}Input = z.infer<typeof sample${pascal}Schema>;
|
|
468
461
|
|
|
469
|
-
const sample${pascal}Handler = async (
|
|
470
|
-
return Ok({ ${camel}: { name:
|
|
462
|
+
const sample${pascal}Handler = async (data: Sample${pascal}Input) => {
|
|
463
|
+
return Ok({ ${camel}: { name: data.name } });
|
|
471
464
|
};
|
|
472
465
|
|
|
473
|
-
export const sample${pascal}Action = createAction
|
|
466
|
+
export const sample${pascal}Action = createAction({
|
|
474
467
|
name: "sample",
|
|
475
468
|
description: "Sample ${serviceName} action",
|
|
476
469
|
validation: sample${pascal}Schema,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nilejs/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "CLI for scaffolding and generating Nile backend projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,12 +17,14 @@
|
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^22.8.1",
|
|
20
|
-
"typescript": "^5"
|
|
20
|
+
"typescript": "^5",
|
|
21
|
+
"zod": "4.6.7"
|
|
21
22
|
},
|
|
22
23
|
"scripts": {
|
|
23
24
|
"build": "bun run scripts/build.ts",
|
|
24
25
|
"dev": "bun run src/index.ts",
|
|
25
26
|
"typecheck": "tsc --noEmit",
|
|
26
|
-
"export": "bun run typecheck && bun run build && node dist/index.js --version"
|
|
27
|
+
"export": "bun run typecheck && bun run build && node dist/index.js --version",
|
|
28
|
+
"test": "bun test scripts/schema-contracts.spec.ts"
|
|
27
29
|
}
|
|
28
30
|
}
|
package/template/README.md
CHANGED
|
@@ -127,7 +127,7 @@ Options:
|
|
|
127
127
|
| `-e, --entry <path>` | Path to services config (auto-detected by default) |
|
|
128
128
|
| `-o, --output <path>` | Output directory (default: `src/generated`) |
|
|
129
129
|
|
|
130
|
-
Actions without
|
|
130
|
+
Every registered action is included in `ServicePayloads`. Actions without validation accept an object payload (`Record<string, unknown>`); call `tasks.list` with `{}`. This fallback does not add server-side validation. Schemas that cannot be converted are reported separately.
|
|
131
131
|
|
|
132
132
|
## Database
|
|
133
133
|
|
package/template/SKILL.md
CHANGED
|
@@ -49,7 +49,7 @@ Structure violations fail startup by default. An application needs `src/index.ts
|
|
|
49
49
|
|
|
50
50
|
## The action pattern
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
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
53
|
|
|
54
54
|
```ts
|
|
55
55
|
import { createAction } from "@nilejs/nile";
|
|
@@ -62,15 +62,15 @@ const createProjectSchema = z.object({
|
|
|
62
62
|
|
|
63
63
|
type CreateProjectInput = z.infer<typeof createProjectSchema>;
|
|
64
64
|
|
|
65
|
-
const createProjectHandler = (
|
|
66
|
-
if (
|
|
65
|
+
const createProjectHandler = (data: CreateProjectInput) => {
|
|
66
|
+
if (data.name === "system") {
|
|
67
67
|
return Err("Choose a different project name");
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
return Ok({ project: { name:
|
|
70
|
+
return Ok({ project: { name: data.name } });
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
export const createProjectAction = createAction
|
|
73
|
+
export const createProjectAction = createAction({
|
|
74
74
|
name: "create",
|
|
75
75
|
description: "Create a project",
|
|
76
76
|
validation: createProjectSchema,
|
|
@@ -104,7 +104,7 @@ Action names are unique within a service. Folder names do not create routes; onl
|
|
|
104
104
|
|
|
105
105
|
## Handlers take (data, context)
|
|
106
106
|
|
|
107
|
-
Nile Js calls every handler with two arguments: the validated input and the execution's `NileContext`.
|
|
107
|
+
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
108
|
|
|
109
109
|
- `context.getSession("rest")` returns the verified JWT identity (`userId`, `organizationId` and other claims) for a protected action.
|
|
110
110
|
- `context.resources` holds what `createNileServer` received (`logger`, `database`).
|
|
@@ -324,7 +324,7 @@ Advanced pages cover handler context, composed hooks, execution options and test
|
|
|
324
324
|
1. Read `services.config.ts`, the target service’s `index.ts` and its actions before you change anything.
|
|
325
325
|
2. Follow the existing pattern in that service. Do not add a second architecture.
|
|
326
326
|
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
|
|
327
|
+
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
328
|
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
329
|
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
330
|
7. Do not commit, publish or change secrets, `.env` values or deployment settings unless the user asks.
|
package/template/package.json
CHANGED
package/template/src/index.ts
CHANGED
|
@@ -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 (
|
|
57
|
+
fn: async (context) => {
|
|
58
58
|
const prepared = await pushSchema();
|
|
59
59
|
if (prepared.isErr) {
|
|
60
60
|
return prepared;
|
|
61
61
|
}
|
|
62
|
-
|
|
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 {
|
|
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 (
|
|
17
|
+
const createTaskHandler = async (data: CreateTaskInput) => {
|
|
18
18
|
const result = await taskModel.create({
|
|
19
19
|
data: {
|
|
20
|
-
title:
|
|
21
|
-
description:
|
|
22
|
-
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
|
|
27
|
+
return result;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
return Ok({ task: result.value });
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export const createTaskAction = createAction
|
|
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 {
|
|
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 (
|
|
13
|
-
const result = await taskModel.delete(
|
|
12
|
+
const deleteTaskHandler = async (data: DeleteTaskInput) => {
|
|
13
|
+
const result = await taskModel.delete(data.id);
|
|
14
14
|
|
|
15
15
|
if (result.isErr) {
|
|
16
|
-
return
|
|
16
|
+
return result;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
return Ok({ deleted: true, id:
|
|
19
|
+
return Ok({ deleted: true, id: data.id });
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
export const deleteTaskAction = createAction
|
|
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 {
|
|
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 (
|
|
13
|
-
const result = await taskModel.findById(
|
|
12
|
+
const getTaskHandler = async (data: GetTaskInput) => {
|
|
13
|
+
const result = await taskModel.findById(data.id);
|
|
14
14
|
|
|
15
15
|
if (result.isErr) {
|
|
16
|
-
return
|
|
16
|
+
return result;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
return Ok({ task: result.value });
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
export const getTaskAction = createAction
|
|
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 {
|
|
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
|
|
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 {
|
|
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 (
|
|
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:
|
|
18
|
+
id: data.id,
|
|
19
19
|
data: {
|
|
20
|
-
title:
|
|
21
|
-
description:
|
|
22
|
-
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
|
|
27
|
+
return result;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
return Ok({ task: result.value });
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export const updateTaskAction = createAction
|
|
33
|
+
export const updateTaskAction = createAction({
|
|
34
34
|
name: "update",
|
|
35
35
|
description: "Update an existing task",
|
|
36
36
|
validation: updateTaskSchema,
|