@nilejs/nile 0.0.1 → 0.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 +367 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,3 +1,370 @@
|
|
|
1
1
|
# 🌊 Nile
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@nilejs/nile)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](./CONTRIBUTING.md)
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
3
12
|
TypeScript-first, service and actions oriented backend framework for building modern, fast, safe and AI-ready backends with simplest developer experience possible.
|
|
13
|
+
|
|
14
|
+
You define actions, group them into services, and get a predictable API with validation, error handling, and schema export, no route definitions, no controllers, no middleware chains and rest api conventions to care about, just your business logic. And it's all AI agent-ready out of the box, progressively discoverable and tool calling ready with validation.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
> Or View Full Docs -> [nile-js.github.io/nile](https://nile-js.github.io/nile)
|
|
19
|
+
|
|
20
|
+
### Scaffold a project (recommended)
|
|
21
|
+
|
|
22
|
+
The fastest way to start is with the CLI. It creates a working project with services, database, and dev tooling pre-configured:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @nilejs/cli new my-app
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd my-app && bun install && bun run dev
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The CLI also includes generators for adding services and actions to an existing project. See [`@nilejs/cli`](./cli/README.md) for details.
|
|
33
|
+
|
|
34
|
+
### Manual install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bun add @nilejs/nile zod slang-ts
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install @nilejs/nile zod slang-ts
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
If using the database layer (`createModel`, `getZodSchema`):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
bun add drizzle-orm drizzle-zod
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
### 1. Define an action
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// services/tasks/create.ts
|
|
56
|
+
import { Ok } from "slang-ts";
|
|
57
|
+
import z from "zod";
|
|
58
|
+
import { createAction, type Action } from "@nilejs/nile";
|
|
59
|
+
|
|
60
|
+
const createTaskSchema = z.object({
|
|
61
|
+
title: z.string().min(1, "Title is required"),
|
|
62
|
+
status: z.enum(["pending", "in-progress", "done"]).default("pending"),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const createTaskHandler = (data: Record<string, unknown>) => {
|
|
66
|
+
const task = {
|
|
67
|
+
id: crypto.randomUUID(),
|
|
68
|
+
title: data.title as string,
|
|
69
|
+
status: (data.status as string) ?? "pending",
|
|
70
|
+
};
|
|
71
|
+
return Ok({ task });
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const createTaskAction: Action = createAction({
|
|
75
|
+
name: "create",
|
|
76
|
+
description: "Create a new task",
|
|
77
|
+
validation: createTaskSchema,
|
|
78
|
+
handler: createTaskHandler,
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. Group actions into a service
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// services/config.ts
|
|
86
|
+
import { type Services } from "@nilejs/nile";
|
|
87
|
+
import { createTaskAction } from "./tasks/create";
|
|
88
|
+
import { listTaskAction } from "./tasks/list";
|
|
89
|
+
|
|
90
|
+
export const services: Services = [
|
|
91
|
+
{
|
|
92
|
+
name: "tasks",
|
|
93
|
+
description: "Task management",
|
|
94
|
+
actions: [createTaskAction, listTaskAction],
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 3. Start the server
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// server.ts
|
|
103
|
+
import { createNileServer } from "@nilejs/nile";
|
|
104
|
+
import { services } from "./services/config";
|
|
105
|
+
|
|
106
|
+
const server = createNileServer({
|
|
107
|
+
serverName: "my-app",
|
|
108
|
+
services,
|
|
109
|
+
rest: {
|
|
110
|
+
baseUrl: "/api",
|
|
111
|
+
port: 8000,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (server.rest) {
|
|
116
|
+
const { fetch } = server.rest.app;
|
|
117
|
+
Bun.serve({ fetch, port: 8000 });
|
|
118
|
+
console.log("Server running at http://localhost:8000");
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 4. Call it
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
curl -X POST http://localhost:8000/api/services \
|
|
126
|
+
-H "Content-Type: application/json" \
|
|
127
|
+
-d '{
|
|
128
|
+
"intent": "execute",
|
|
129
|
+
"service": "tasks",
|
|
130
|
+
"action": "create",
|
|
131
|
+
"payload": { "title": "Ship it", "status": "pending" }
|
|
132
|
+
}'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"status": true,
|
|
138
|
+
"message": "Action 'tasks.create' executed",
|
|
139
|
+
"data": {
|
|
140
|
+
"task": {
|
|
141
|
+
"id": "a1b2c3d4-...",
|
|
142
|
+
"title": "Ship it",
|
|
143
|
+
"status": "pending"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Why Nile
|
|
150
|
+
|
|
151
|
+
**You write business logic. Nile handles the rest.**
|
|
152
|
+
|
|
153
|
+
Most backend frameworks make you think about HTTP verbs, route trees, middleware ordering, and error serialization before you write a single line of domain logic. Nile removes that ceremony. You define actions, plain functions that take data and return results, and they become callable over a single POST endpoint or other protocols such as web sockets or rpc within your codebase.
|
|
154
|
+
|
|
155
|
+
**Nothing crashes silently.** Every action handler returns `Ok(data)` or `Err(message)` using the Result pattern from [Slang Ts](github.com/Hussseinkizz/slang) Functional programming utilities library. So no unhandled exceptions, no try-catch spaghetti, no mystery 500s. Your control flow is predictable by design and safe.
|
|
156
|
+
|
|
157
|
+
**AI agents can call your API without adapters.** Every action with a Zod validation schema automatically exports its parameters as JSON Schema. An LLM can discover your services, read the schemas, and make tool calls, no custom integration code required.
|
|
158
|
+
|
|
159
|
+
**Your database, your choice.** Nile doesn't own your data layer. When you want structured DB access, nile works with drizzle orm and any databases it supports, postgres, pglite or sqlite and more, but also provides utilities like `createModel` for simplifying type-safe CRUD operations for any Drizzle table with auto-validation, error handling, and pagination built in to reduce boilerplate. You can also use any other database library or raw queries in your action handlers, it's all up to you.
|
|
160
|
+
|
|
161
|
+
**There's more to Nile** than just the core server, you get service and action based architecture, powerful hook system, structured logging and enforced error handling, rate limiting, CORS control, uploads, single context for dependency injection or sharing, and more. And you don't need a Phd to understand how to use any of them.
|
|
162
|
+
|
|
163
|
+
## Core Concepts
|
|
164
|
+
|
|
165
|
+
Nile uses a single POST endpoint for everything. Instead of mapping HTTP verbs to routes, you send a JSON body with an **intent** that tells the server what you want to do.
|
|
166
|
+
|
|
167
|
+
Every request has the same shape:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
{
|
|
171
|
+
intent: "explore" | "execute" | "schema",
|
|
172
|
+
service: string, // service name, or "*" for all
|
|
173
|
+
action: string, // action name, or "*" for all
|
|
174
|
+
payload: object // data for the action (use {} when not needed)
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Every response has the same shape:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
{
|
|
182
|
+
status: boolean, // true = success, false = error
|
|
183
|
+
message: string, // human-readable description
|
|
184
|
+
data: object // result payload, or {} on error
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Explore, discover what's available
|
|
189
|
+
|
|
190
|
+
List all services:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
curl -X POST http://localhost:8000/api/services \
|
|
194
|
+
-H "Content-Type: application/json" \
|
|
195
|
+
-d '{ "intent": "explore", "service": "*", "action": "*", "payload": {} }'
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"status": true,
|
|
201
|
+
"message": "Available services",
|
|
202
|
+
"data": {
|
|
203
|
+
"result": [
|
|
204
|
+
{
|
|
205
|
+
"name": "tasks",
|
|
206
|
+
"description": "Task management",
|
|
207
|
+
"actions": ["create", "list"]
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Drill into a service to see its actions:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
curl -X POST http://localhost:8000/api/services \
|
|
218
|
+
-H "Content-Type: application/json" \
|
|
219
|
+
-d '{ "intent": "explore", "service": "tasks", "action": "*", "payload": {} }'
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"status": true,
|
|
225
|
+
"message": "Actions for 'tasks'",
|
|
226
|
+
"data": {
|
|
227
|
+
"result": [
|
|
228
|
+
{
|
|
229
|
+
"name": "create",
|
|
230
|
+
"description": "Create a new task",
|
|
231
|
+
"isProtected": false,
|
|
232
|
+
"validation": true
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Execute, call an action
|
|
240
|
+
|
|
241
|
+
This is the same call shown in Quick Start. Send `"intent": "execute"` with the service, action, and payload. The action's Zod schema validates the payload before the handler runs. If validation fails, you get a clear error:
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"status": false,
|
|
246
|
+
"message": "Validation failed: title - Required",
|
|
247
|
+
"data": {}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Schema, get JSON Schema for actions
|
|
252
|
+
|
|
253
|
+
Fetch the validation schema for any action as JSON Schema. This is what makes Nile AI-ready, an agent can read these schemas to know exactly what parameters an action accepts.
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
curl -X POST http://localhost:8000/api/services \
|
|
257
|
+
-H "Content-Type: application/json" \
|
|
258
|
+
-d '{ "intent": "schema", "service": "tasks", "action": "create", "payload": {} }'
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"status": true,
|
|
264
|
+
"message": "Schema for 'tasks.create'",
|
|
265
|
+
"data": {
|
|
266
|
+
"create": {
|
|
267
|
+
"type": "object",
|
|
268
|
+
"properties": {
|
|
269
|
+
"title": { "type": "string", "minLength": 1 },
|
|
270
|
+
"status": { "type": "string", "enum": ["pending", "in-progress", "done"], "default": "pending" }
|
|
271
|
+
},
|
|
272
|
+
"required": ["title"]
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Use `"service": "*", "action": "*"` to get schemas for every action across all services in one call.
|
|
279
|
+
|
|
280
|
+
### Hooks, intercept and transform
|
|
281
|
+
|
|
282
|
+
Hooks let you run logic before or after an action executes. They work at two levels:
|
|
283
|
+
|
|
284
|
+
**Per-action hooks** point to other registered actions. A hook definition is just a reference, `{ service, action, isCritical }`, so any action can serve as a hook for any other action.
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
export const createTaskAction: Action = createAction({
|
|
288
|
+
name: "create",
|
|
289
|
+
description: "Create a new task",
|
|
290
|
+
validation: createTaskSchema,
|
|
291
|
+
handler: createTaskHandler,
|
|
292
|
+
hooks: {
|
|
293
|
+
before: [
|
|
294
|
+
{ service: "audit", action: "logAccess", isCritical: false }
|
|
295
|
+
],
|
|
296
|
+
after: [
|
|
297
|
+
{ service: "notifications", action: "notify", isCritical: false }
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Before hooks run sequentially and chain, each hook's output becomes the next hook's input. After hooks work the same way, receiving the handler's result.
|
|
304
|
+
|
|
305
|
+
When `isCritical` is `true`, a hook failure stops the pipeline. When `false`, failures are logged and skipped.
|
|
306
|
+
|
|
307
|
+
**Global hooks** run on every action. Define them in your server config:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
const server = createNileServer({
|
|
311
|
+
serverName: "my-app",
|
|
312
|
+
services,
|
|
313
|
+
onBeforeActionHandler: ({ nileContext, action, payload }) => {
|
|
314
|
+
// runs before every action, auth checks, logging, etc.
|
|
315
|
+
return Ok(payload);
|
|
316
|
+
},
|
|
317
|
+
onAfterActionHandler: ({ nileContext, action, payload, result }) => {
|
|
318
|
+
// runs after every action, transforms, auditing, etc.
|
|
319
|
+
return result;
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The full execution pipeline runs in this order:
|
|
325
|
+
|
|
326
|
+
```txt
|
|
327
|
+
Global Before Hook
|
|
328
|
+
-> Per-Action Before Hooks (sequential)
|
|
329
|
+
-> Validation (Zod)
|
|
330
|
+
-> Handler
|
|
331
|
+
-> Per-Action After Hooks (sequential)
|
|
332
|
+
-> Global After Hook
|
|
333
|
+
-> Response
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Any step returning `Err` short-circuits the pipeline.
|
|
337
|
+
|
|
338
|
+
## Project Structure
|
|
339
|
+
|
|
340
|
+
```txt
|
|
341
|
+
my-api/
|
|
342
|
+
server.ts
|
|
343
|
+
services/
|
|
344
|
+
config.ts
|
|
345
|
+
tasks/
|
|
346
|
+
create.ts
|
|
347
|
+
list.ts
|
|
348
|
+
get.ts
|
|
349
|
+
db/
|
|
350
|
+
schema.ts
|
|
351
|
+
client.ts
|
|
352
|
+
models/
|
|
353
|
+
tasks.ts
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Contributing
|
|
357
|
+
|
|
358
|
+
> First developed by Hussein Kizz at [Nile Squad Labz](https://nilesquad.com) to power our own B2B saas products and services, and now open-sourced for the community. Over 1 year in the making, to now powering Agentic backends and open for community contributions.
|
|
359
|
+
|
|
360
|
+
Contributions are welcome.
|
|
361
|
+
|
|
362
|
+
1. Fork the repository
|
|
363
|
+
2. Create your feature branch (`git checkout -b feature/your-feature`)
|
|
364
|
+
3. Commit your changes (`git commit -m 'Add your feature'`)
|
|
365
|
+
4. Push to the branch (`git push origin feature/your-feature`)
|
|
366
|
+
5. Open a Pull Request
|
|
367
|
+
|
|
368
|
+
## License
|
|
369
|
+
|
|
370
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -1409,7 +1409,7 @@ function createNileServer(config) {
|
|
|
1409
1409
|
});
|
|
1410
1410
|
server.rest = { app, config: config.rest };
|
|
1411
1411
|
const host = config.rest.host ?? "localhost";
|
|
1412
|
-
const port = config.rest.port ??
|
|
1412
|
+
const port = config.rest.port ?? 8e3;
|
|
1413
1413
|
const base = `http://${host}:${port}`;
|
|
1414
1414
|
console.log(`
|
|
1415
1415
|
POST ${base}${config.rest.baseUrl}/services`);
|