@nilejs/nile 0.0.4 → 0.0.5

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 CHANGED
@@ -1,369 +1,92 @@
1
- # 🌊 Nile
1
+ # @nilejs/nile
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/@nilejs/nile.svg)](https://www.npmjs.com/package/@nilejs/nile)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md)
6
5
 
7
- ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white)
8
- ![Hono](https://img.shields.io/badge/Hono-E36002?logo=hono&logoColor=white)
9
- ![Zod](https://img.shields.io/badge/Zod-3E67B1?logo=zod&logoColor=white)
10
- ![Drizzle](https://img.shields.io/badge/Drizzle-C5F74F?logo=drizzle&logoColor=black)
11
-
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.
6
+ The core framework package for Nile, a TypeScript-first, service and actions oriented backend framework.
15
7
 
16
8
  ## Install
17
9
 
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, actions, and extracting Zod schemas with TypeScript types. See [`@nilejs/cli`](./cli/README.md) for details.
33
-
34
- ### Manual install
35
-
36
10
  ```bash
37
11
  bun add @nilejs/nile zod slang-ts
38
12
  ```
39
13
 
40
- ```bash
41
- npm install @nilejs/nile zod slang-ts
42
- ```
43
-
44
14
  If using the database layer (`createModel`, `getZodSchema`):
45
15
 
46
16
  ```bash
47
17
  bun add drizzle-orm drizzle-zod
48
18
  ```
49
19
 
50
- ## Quick Start
20
+ ## What's in this package
51
21
 
52
- ### 1. Define an action
22
+ This package provides the server runtime, engine, and all core utilities:
23
+
24
+ - **`createNileServer`** - Server factory that wires up services, hooks, and REST transport
25
+ - **`createService` / `createServices`** - Service definition factories
26
+ - **`createAction` / `createActions`** - Action definition factories with Zod validation
27
+ - **`createModel`** - Type-safe CRUD model factory for Drizzle tables
28
+ - **`getContext`** - Access the shared NileContext (dependency injection, resources, sessions)
29
+ - **Engine** - Pipeline execution with before/after hooks, validation, and Result-based flow
30
+ - **REST layer** - Single-endpoint `POST /services` transport built on Hono
31
+ - **CORS** - Configurable origin control with per-route rules
32
+ - **Logging** - Structured log persistence with chunking support
33
+ - **Error handling** - `handleError` utility with Result pattern enforcement
34
+
35
+ ## Quick example
53
36
 
54
37
  ```typescript
55
- // services/tasks/create.ts
38
+ import { createNileServer, createAction } from "@nilejs/nile";
56
39
  import { Ok } from "slang-ts";
57
40
  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
41
 
74
- export const createTaskAction: Action = createAction({
75
- name: "create",
76
- description: "Create a new task",
77
- validation: createTaskSchema,
78
- handler: createTaskHandler,
42
+ const greet = createAction({
43
+ name: "greet",
44
+ description: "Say hello",
45
+ validation: z.object({ name: z.string() }),
46
+ handler: (data) => Ok({ message: `Hello, ${data.name}!` }),
79
47
  });
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
48
 
106
49
  const server = createNileServer({
107
50
  serverName: "my-app",
108
- services,
109
- rest: {
110
- baseUrl: "/api",
111
- port: 8000,
112
- },
51
+ services: [{ name: "hello", description: "Greeting service", actions: [greet] }],
52
+ rest: { baseUrl: "/api", port: 8000 },
113
53
  });
114
54
 
115
55
  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)
56
+ Bun.serve({ fetch: server.rest.app.fetch, port: 8000 });
175
57
  }
176
58
  ```
177
59
 
178
- Every response has the same shape:
60
+ ## Project structure
179
61
 
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
62
  ```
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": {} }'
63
+ packages/nile/
64
+ index.ts # Public API exports
65
+ engine/ # Service registry, action pipeline, hook execution
66
+ nile/ # Server factory, context management
67
+ rest/ # Hono-based REST transport, intent handlers, middleware
68
+ cors/ # CORS configuration and resolution
69
+ logging/ # Structured log creation and retrieval
70
+ utils/ # Error handling, diagnostics, DB model utilities
196
71
  ```
197
72
 
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:
73
+ ## Development
215
74
 
216
75
  ```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
76
+ # Run tests
77
+ bun run test:run
281
78
 
282
- Hooks let you run logic before or after an action executes. They work at two levels:
79
+ # Build
80
+ bun run build
283
81
 
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
- });
82
+ # Lint and format
83
+ bun run check && bun run fix
301
84
  ```
302
85
 
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.
86
+ ## Related packages
361
87
 
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
88
+ - [`@nilejs/cli`](https://github.com/nile-js/nile/tree/main/packages/cli) - Project scaffolding and code generation
89
+ - [`@nilejs/client`](https://github.com/nile-js/nile/tree/main/packages/client) - Type-safe frontend client
367
90
 
368
91
  ## License
369
92
 
package/dist/index.cjs CHANGED
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
- // src/index.ts
30
+ // index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  createAction: () => createAction,
@@ -46,7 +46,7 @@ __export(index_exports, {
46
46
  });
47
47
  module.exports = __toCommonJS(index_exports);
48
48
 
49
- // src/engine/create-action.ts
49
+ // engine/create-action.ts
50
50
  function createAction(config) {
51
51
  return config;
52
52
  }
@@ -54,7 +54,7 @@ function createActions(configs) {
54
54
  return configs;
55
55
  }
56
56
 
57
- // src/engine/create-service.ts
57
+ // engine/create-service.ts
58
58
  function createService(config) {
59
59
  return config;
60
60
  }
@@ -62,7 +62,7 @@ function createServices(configs) {
62
62
  return configs;
63
63
  }
64
64
 
65
- // src/logging/logger.ts
65
+ // logging/logger.ts
66
66
  var import_node_fs = require("fs");
67
67
  var import_node_path = require("path");
68
68
  var import_nanoid = require("nanoid");
@@ -305,7 +305,7 @@ function applyLogFilters(logs, filters) {
305
305
  });
306
306
  }
307
307
 
308
- // src/logging/create-log.ts
308
+ // logging/create-log.ts
309
309
  var createLogger = (appName, config) => {
310
310
  return {
311
311
  info: (input) => createLog({ ...input, appName, level: "info" }, config),
@@ -314,17 +314,17 @@ var createLogger = (appName, config) => {
314
314
  };
315
315
  };
316
316
 
317
- // src/nile/server.ts
317
+ // nile/server.ts
318
318
  var import_slang_ts7 = require("slang-ts");
319
319
 
320
- // src/engine/engine.ts
320
+ // engine/engine.ts
321
321
  var import_slang_ts4 = require("slang-ts");
322
322
 
323
- // src/utils/db/create-model.ts
323
+ // utils/db/create-model.ts
324
324
  var import_drizzle_orm = require("drizzle-orm");
325
325
  var import_slang_ts2 = require("slang-ts");
326
326
 
327
- // src/utils/handle-error.ts
327
+ // utils/handle-error.ts
328
328
  var import_slang_ts = require("slang-ts");
329
329
  var CALLER_LINE_REGEX = /at\s+(\S+)\s+/;
330
330
  function inferCallerName() {
@@ -360,7 +360,7 @@ function handleError(params) {
360
360
  return (0, import_slang_ts.Err)(`[${logId}] ${params.message}`);
361
361
  }
362
362
 
363
- // src/utils/db/create-transaction-variant.ts
363
+ // utils/db/create-transaction-variant.ts
364
364
  function createTransactionVariant(fn) {
365
365
  return async (params) => {
366
366
  const { dbx, ...rest } = params;
@@ -389,7 +389,7 @@ function createTransactionVariant(fn) {
389
389
  };
390
390
  }
391
391
 
392
- // src/utils/db/get-zod-schema.ts
392
+ // utils/db/get-zod-schema.ts
393
393
  var import_drizzle_zod = require("drizzle-zod");
394
394
  function getZodSchema(table) {
395
395
  const isRelation = Object.hasOwn(table, "config") && Object.hasOwn(table, "table");
@@ -414,7 +414,7 @@ function getZodSchema(table) {
414
414
  };
415
415
  }
416
416
 
417
- // src/utils/db/create-model.ts
417
+ // utils/db/create-model.ts
418
418
  function asDb(db) {
419
419
  return db;
420
420
  }
@@ -666,7 +666,7 @@ function createModel(table, options) {
666
666
  };
667
667
  }
668
668
 
669
- // src/utils/diagnostics-log.ts
669
+ // utils/diagnostics-log.ts
670
670
  function isNileLogger(logger) {
671
671
  return "warn" in logger && "error" in logger;
672
672
  }
@@ -693,7 +693,7 @@ function createDiagnosticsLog(prefix, params) {
693
693
  };
694
694
  }
695
695
 
696
- // src/engine/pipeline.ts
696
+ // engine/pipeline.ts
697
697
  var import_slang_ts3 = require("slang-ts");
698
698
  var import_zod = require("zod");
699
699
  async function runHook(hookDef, hookAction, input, nileContext) {
@@ -803,7 +803,7 @@ async function runHandler(action, payload, nileContext, log) {
803
803
  return (0, import_slang_ts3.Ok)(result.value);
804
804
  }
805
805
 
806
- // src/engine/engine.ts
806
+ // engine/engine.ts
807
807
  function createEngine(options) {
808
808
  const { diagnostics, services, logger } = options;
809
809
  const log = createDiagnosticsLog("Engine", {
@@ -937,11 +937,11 @@ function createEngine(options) {
937
937
  };
938
938
  }
939
939
 
940
- // src/rest/rest.ts
940
+ // rest/rest.ts
941
941
  var import_hono = require("hono");
942
942
  var import_zod3 = __toESM(require("zod"), 1);
943
943
 
944
- // src/cors/cors.ts
944
+ // cors/cors.ts
945
945
  var import_cors = require("hono/cors");
946
946
  var buildDefaultCorsOptions = (config) => {
947
947
  const getDefaultOrigin = (reqOrigin) => {
@@ -1023,7 +1023,7 @@ var evaluateResolver = (resolver, origin, c, defaultOpts) => {
1023
1023
  }
1024
1024
  };
1025
1025
 
1026
- // src/rest/intent-handlers.ts
1026
+ // rest/intent-handlers.ts
1027
1027
  var import_slang_ts5 = require("slang-ts");
1028
1028
  var import_zod2 = __toESM(require("zod"), 1);
1029
1029
  function toExternalResponse(result, successMessage) {
@@ -1156,7 +1156,7 @@ var intentHandlers = {
1156
1156
  schema: (engine, request) => handleSchema(engine, request)
1157
1157
  };
1158
1158
 
1159
- // src/rest/middleware.ts
1159
+ // rest/middleware.ts
1160
1160
  var import_hono_rate_limiter = require("hono-rate-limiter");
1161
1161
  var import_slang_ts6 = require("slang-ts");
1162
1162
  var ASSETS_REGEX = /^\/assets\//;
@@ -1226,7 +1226,7 @@ function applyStaticServing(app, config, runtime, log) {
1226
1226
  log("Static file serving enabled at /assets/*");
1227
1227
  }
1228
1228
 
1229
- // src/rest/rest.ts
1229
+ // rest/rest.ts
1230
1230
  var externalRequestSchema = import_zod3.default.object({
1231
1231
  intent: import_zod3.default.enum(["explore", "execute", "schema"]),
1232
1232
  service: import_zod3.default.string().min(1),
@@ -1297,7 +1297,7 @@ function createRestApp(params) {
1297
1297
  return app;
1298
1298
  }
1299
1299
 
1300
- // src/nile/nile.ts
1300
+ // nile/nile.ts
1301
1301
  function createNileContext(params) {
1302
1302
  const store = /* @__PURE__ */ new Map();
1303
1303
  const interfaceContext = params?.interfaceContext;
@@ -1351,7 +1351,7 @@ function createNileContext(params) {
1351
1351
  return context;
1352
1352
  }
1353
1353
 
1354
- // src/nile/server.ts
1354
+ // nile/server.ts
1355
1355
  var _nileContext = null;
1356
1356
  function getContext() {
1357
1357
  if (!_nileContext) {