lynnix 0.0.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.
Files changed (70) hide show
  1. package/README.md +542 -0
  2. package/dist/index.d.ts +17 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +13 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/lynnix.d.ts +46 -0
  7. package/dist/lynnix.d.ts.map +1 -0
  8. package/dist/lynnix.js +199 -0
  9. package/dist/lynnix.js.map +1 -0
  10. package/dist/utils/augmentRequest.d.ts +12 -0
  11. package/dist/utils/augmentRequest.d.ts.map +1 -0
  12. package/dist/utils/augmentRequest.js +66 -0
  13. package/dist/utils/augmentRequest.js.map +1 -0
  14. package/dist/utils/augmentResponse.d.ts +17 -0
  15. package/dist/utils/augmentResponse.d.ts.map +1 -0
  16. package/dist/utils/augmentResponse.js +56 -0
  17. package/dist/utils/augmentResponse.js.map +1 -0
  18. package/dist/utils/buildRoutesMap.d.ts +19 -0
  19. package/dist/utils/buildRoutesMap.d.ts.map +1 -0
  20. package/dist/utils/buildRoutesMap.js +48 -0
  21. package/dist/utils/buildRoutesMap.js.map +1 -0
  22. package/dist/utils/error.d.ts +18 -0
  23. package/dist/utils/error.d.ts.map +1 -0
  24. package/dist/utils/error.js +20 -0
  25. package/dist/utils/error.js.map +1 -0
  26. package/dist/utils/findClosestBoundary.d.ts +14 -0
  27. package/dist/utils/findClosestBoundary.d.ts.map +1 -0
  28. package/dist/utils/findClosestBoundary.js +29 -0
  29. package/dist/utils/findClosestBoundary.js.map +1 -0
  30. package/dist/utils/getMiddlewareChain.d.ts +19 -0
  31. package/dist/utils/getMiddlewareChain.d.ts.map +1 -0
  32. package/dist/utils/getMiddlewareChain.js +31 -0
  33. package/dist/utils/getMiddlewareChain.js.map +1 -0
  34. package/dist/utils/handleHttpError.d.ts +24 -0
  35. package/dist/utils/handleHttpError.d.ts.map +1 -0
  36. package/dist/utils/handleHttpError.js +23 -0
  37. package/dist/utils/handleHttpError.js.map +1 -0
  38. package/dist/utils/handleNotFound.d.ts +33 -0
  39. package/dist/utils/handleNotFound.d.ts.map +1 -0
  40. package/dist/utils/handleNotFound.js +71 -0
  41. package/dist/utils/handleNotFound.js.map +1 -0
  42. package/dist/utils/lruCache.d.ts +17 -0
  43. package/dist/utils/lruCache.d.ts.map +1 -0
  44. package/dist/utils/lruCache.js +36 -0
  45. package/dist/utils/lruCache.js.map +1 -0
  46. package/dist/utils/lynnixRequest.d.ts +22 -0
  47. package/dist/utils/lynnixRequest.d.ts.map +1 -0
  48. package/dist/utils/lynnixRequest.js +21 -0
  49. package/dist/utils/lynnixRequest.js.map +1 -0
  50. package/dist/utils/lynnixResponse.d.ts +53 -0
  51. package/dist/utils/lynnixResponse.d.ts.map +1 -0
  52. package/dist/utils/lynnixResponse.js +142 -0
  53. package/dist/utils/lynnixResponse.js.map +1 -0
  54. package/dist/utils/matchRoute.d.ts +20 -0
  55. package/dist/utils/matchRoute.d.ts.map +1 -0
  56. package/dist/utils/matchRoute.js +55 -0
  57. package/dist/utils/matchRoute.js.map +1 -0
  58. package/dist/utils/parseReqBody.d.ts +28 -0
  59. package/dist/utils/parseReqBody.d.ts.map +1 -0
  60. package/dist/utils/parseReqBody.js +415 -0
  61. package/dist/utils/parseReqBody.js.map +1 -0
  62. package/dist/utils/runMiddlewares.d.ts +5 -0
  63. package/dist/utils/runMiddlewares.d.ts.map +1 -0
  64. package/dist/utils/runMiddlewares.js +18 -0
  65. package/dist/utils/runMiddlewares.js.map +1 -0
  66. package/dist/utils/sortRoutes.d.ts +16 -0
  67. package/dist/utils/sortRoutes.d.ts.map +1 -0
  68. package/dist/utils/sortRoutes.js +44 -0
  69. package/dist/utils/sortRoutes.js.map +1 -0
  70. package/package.json +68 -0
package/README.md ADDED
@@ -0,0 +1,542 @@
1
+ # Lynnix
2
+
3
+ **File-based routing for htmx, powered by Mutor.js.**
4
+
5
+ Lynnix is a lightweight, framework-agnostic routing and SSR middleware for Node.js that makes building htmx applications feel natural. Drop your files in the right place, export a function, and Lynnix handles the rest — routing, rendering, middleware chains, htmx-aware responses, and error boundaries, all wired together automatically.
6
+
7
+ No magic config files. No build step. Just a filesystem that speaks HTTP.
8
+
9
+ ```js
10
+ import { createLynnixApp } from "lynnix";
11
+ import express from "express";
12
+
13
+ const app = express();
14
+ const handler = await createLynnixApp("app");
15
+
16
+ app.use(express.static("public"));
17
+ app.use(handler);
18
+ app.listen(3000);
19
+ ```
20
+
21
+ That's it. Everything else comes from your files.
22
+
23
+ ---
24
+
25
+ ## Why Lynnix?
26
+
27
+ htmx is a breath of fresh air — it brings back the simplicity of server-rendered HTML without sacrificing interactivity. But as your application grows, wiring up routes, rendering templates, and managing partial responses by hand gets tedious fast.
28
+
29
+ Lynnix gives htmx applications the structure they deserve. It handles the routing and rendering layer so you can focus on what actually matters: building your product.
30
+
31
+ It's built on [Mutor.js](https://github.com/allAboutJS/Mutor.js) — a fast, TypeScript-native, zero-dependency template engine — so your templates are expressive, secure, and compiled for performance.
32
+
33
+ ---
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ npm install lynnix
39
+ ```
40
+
41
+ Lynnix has a lean set of optional peer dependencies that unlock additional features:
42
+
43
+ | Package | What it unlocks |
44
+ |---|---|
45
+ | `cookie` | Cookie parsing and setting |
46
+ | `@fastify/busboy` | `multipart/form-data` and `application/x-www-form-urlencoded` body parsing |
47
+ | `body-parser` | `application/json` body parsing |
48
+ | `qs` | Advanced query string and URL-encoded body parsing |
49
+
50
+ Install only what you need. Lynnix will work without any of them and warn you in the console if a feature requires one that isn't installed.
51
+
52
+ ---
53
+
54
+ ## Getting Started
55
+
56
+ ### With bare `node:http`
57
+
58
+ Lynnix works with Node's built-in HTTP server out of the box. For static files, pair it with [`send-static`](https://www.npmjs.com/package/send-static):
59
+
60
+ ```js
61
+ import { createLynnixApp } from "lynnix";
62
+ import sendStatic from "send-static";
63
+ import * as http from "node:http";
64
+
65
+ async function main() {
66
+ const serve = sendStatic("public", { index: false });
67
+ const handle = await createLynnixApp("app");
68
+
69
+ const server = http.createServer((req, res) => {
70
+ serve(req, res, () => handle(req, res));
71
+ });
72
+
73
+ server.listen(3000, () => {
74
+ console.log("Server running on http://localhost:3000");
75
+ });
76
+ }
77
+
78
+ main();
79
+ ```
80
+
81
+ ### With Express
82
+
83
+ ```js
84
+ import { createLynnixApp } from "lynnix";
85
+ import express from "express";
86
+
87
+ async function main() {
88
+ const app = express();
89
+ const handler = await createLynnixApp("app");
90
+
91
+ app.use(express.static("public"));
92
+ app.use(handler);
93
+
94
+ app.listen(3000, () => {
95
+ console.log("Server running on http://localhost:3000");
96
+ });
97
+ }
98
+
99
+ main();
100
+ ```
101
+
102
+ ### `createLynnixApp(path, mutorConfig?, bodyParserOptions?)`
103
+
104
+ | Parameter | Type | Description |
105
+ |---|---|---|
106
+ | `path` | `string` | The root directory of your application (e.g. `"app"`) |
107
+ | `mutorConfig` | `PartialMutorConfig` | Optional Mutor.js configuration (excluding `rootDir`) |
108
+ | `bodyParserOptions` | `ParseReqBodyOptions` | Optional body parser limits and settings |
109
+
110
+ Returns a standard `(req, res) => void` request handler you can mount anywhere.
111
+
112
+ ---
113
+
114
+ ## Project Structure
115
+
116
+ A Lynnix application lives inside a single directory (conventionally `app/`). The filesystem is your router.
117
+
118
+ ```
119
+ app/
120
+ ├── components/
121
+ │ └── header.html
122
+ ├── dashboard/
123
+ │ ├── posts/
124
+ │ │ ├── [slug]/
125
+ │ │ │ └── loader.js
126
+ │ │ ├── loader.js
127
+ │ │ └── page.html
128
+ │ ├── layout.html
129
+ │ ├── loader.js
130
+ │ ├── middleware.js
131
+ │ ├── not-found.html
132
+ │ └── page.html
133
+ ├── loader.js
134
+ ├── not-found.html
135
+ └── page.html
136
+ ```
137
+
138
+ Each directory maps to a route. The files inside determine how that route behaves.
139
+
140
+ ---
141
+
142
+ ## File Conventions
143
+
144
+ These are the reserved filenames Lynnix recognises in any route directory:
145
+
146
+ | File | Purpose |
147
+ |---|---|
148
+ | `page.html` | Full-page HTML response for regular requests |
149
+ | `fragment.html` | Partial HTML response for htmx requests |
150
+ | `loader.js` / `loader.ts` | HTTP method handlers and data loading |
151
+ | `middleware.js` / `middleware.ts` | Route-level middleware |
152
+ | `not-found.html` | 404 page for regular requests |
153
+ | `fragment.not-found.html` | 404 fragment for htmx requests |
154
+ | `error.html` | Error page for regular requests |
155
+ | `fragment.error.html` | Error fragment for htmx requests |
156
+
157
+ Any other file (components, utilities, layouts) is invisible to the router and can be named freely.
158
+
159
+ ---
160
+
161
+ ## Routing
162
+
163
+ ### Static Routes
164
+
165
+ A directory named `about` maps to `/about`. Nest them as deep as you like.
166
+
167
+ ```
168
+ app/
169
+ ├── about/
170
+ │ └── page.html → /about
171
+ ├── blog/
172
+ │ └── page.html → /blog
173
+ └── page.html → /
174
+ ```
175
+
176
+ ### Dynamic Routes
177
+
178
+ Wrap a directory name in square brackets to create a dynamic segment. The captured value is available in your loader as `req.params`.
179
+
180
+ ```
181
+ app/
182
+ └── posts/
183
+ ├── [slug]/
184
+ │ ├── loader.js
185
+ │ └── page.html → /posts/:slug
186
+ └── page.html → /posts
187
+ ```
188
+
189
+ ```js
190
+ // app/posts/[slug]/loader.js
191
+ export function GET(req) {
192
+ const { slug } = req.params;
193
+ const post = db.posts.find(slug);
194
+ return { post };
195
+ }
196
+ ```
197
+
198
+ ### Catch-All Routes
199
+
200
+ Double brackets capture every path segment from that point onward. Use this for wildcard pages, CMS-driven routes, or custom 404 experiences.
201
+
202
+ ```
203
+ app/
204
+ └── [[slug]]/
205
+ ├── loader.js
206
+ └── page.html → matches /anything, /a/b/c, /a/b/c/d ...
207
+ ```
208
+
209
+ The entire remaining path is available as a string in `req.params`:
210
+
211
+ ```js
212
+ // app/[[slug]]/loader.js
213
+ export function GET(req) {
214
+ const { slug } = req.params; // e.g. "docs/getting-started/installation"
215
+ return { slug };
216
+ }
217
+ ```
218
+
219
+ ### Route Priority
220
+
221
+ When multiple routes could match the same path, Lynnix resolves the conflict by **specificity** — the more concrete a route is, the higher its priority. Specificity is determined by three factors in order:
222
+
223
+ **1. Tier** — Static routes always beat dynamic routes, which always beat catch-all routes.
224
+
225
+ **2. Static segment count** — Within the same tier, routes with more concrete (non-dynamic) segments win. `/posts/featured` has two static segments and beats `/posts/[slug]` which has one. `/[category]/featured` has one static segment and beats `/[category]/[slug]` which has none.
226
+
227
+ **3. Depth** — When two routes in the same tier have the same number of static segments, shallower routes win for static and dynamic routes (less ambiguous), while deeper routes win for catch-all routes (a more constrained prefix is more specific).
228
+
229
+ A few examples to make it concrete:
230
+
231
+ | Path | Matches |
232
+ |---|---|
233
+ | `/posts/featured` | `/posts/featured` — static wins |
234
+ | `/posts/hello-world` | `/posts/[slug]` — dynamic picks it up |
235
+ | `/electronics/featured` | `/[category]/featured` — more static segments wins |
236
+ | `/electronics/some-product` | `/[category]/[slug]` — falls through to two-dynamic route |
237
+ | `/posts/a/b/c` | `/posts/[[slug]]` — deeper catch-all prefix beats shallower |
238
+ | `/anything/goes/here` | `/[[slug]]` — root catch-all is the last resort |
239
+
240
+ You never have to think about this ordering explicitly — Lynnix sorts your routes at startup so the right one always wins.
241
+
242
+ ---
243
+
244
+ ## Loaders
245
+
246
+ A `loader.js` file exports named functions matching the HTTP methods they handle. Method names are uppercased.
247
+
248
+ ```js
249
+ // app/posts/loader.js
250
+ export function GET(req, res) {
251
+ return { posts: db.posts.all() };
252
+ }
253
+
254
+ export function POST(req, res) {
255
+ const { title, content } = req.body;
256
+ db.posts.create({ title, content });
257
+ res.redirect("/posts");
258
+ }
259
+
260
+ export function DELETE(req, res) {
261
+ db.posts.delete(req.params.slug);
262
+ res.status(200).end();
263
+ }
264
+ ```
265
+
266
+ Whatever you return from a loader becomes `data` in your template:
267
+
268
+ ```html
269
+ <!-- app/posts/page.html -->
270
+ {{ for post of data.posts }}
271
+ <article>
272
+ <h2>{{ post.title }}</h2>
273
+ </article>
274
+ {{ endfor }}
275
+ ```
276
+
277
+ If the response has already been ended inside the loader (via `res.redirect()`, `res.end()`, etc.), Lynnix skips rendering entirely. If a route has no loader, non-GET requests return `405 Method Not Allowed` automatically.
278
+
279
+ ---
280
+
281
+ ## Middleware
282
+
283
+ A `middleware.js` file exports a single default function. It runs before the loader on every request to that route and all routes nested beneath it.
284
+
285
+ ```js
286
+ // app/dashboard/middleware.js
287
+ import { users } from "../lib/users.js";
288
+
289
+ export default function dashboardMiddleware(req, res) {
290
+ const userId = req.cookies.auth;
291
+
292
+ if (!userId) {
293
+ res.redirect("/sign-in");
294
+ return;
295
+ }
296
+
297
+ const user = users.find((u) => u.id === userId);
298
+
299
+ if (!user) {
300
+ res.redirect("/sign-in");
301
+ return;
302
+ }
303
+
304
+ req.user = user;
305
+ }
306
+ ```
307
+
308
+ **Middleware chains run top-down** — from the root of your app to the matched route. A middleware at `app/middleware.js` runs on every request. A middleware at `app/dashboard/middleware.js` runs on every request under `/dashboard`. If any middleware ends the response, the chain stops and the loader never runs.
309
+
310
+ There is no `next` function. Returning from the middleware function is enough to continue.
311
+
312
+ ---
313
+
314
+ ## Layouts
315
+
316
+ Layouts let you define a reusable HTML shell and inject page content into it. They're a Mutor.js feature that Lynnix makes available across your entire application.
317
+
318
+ ### Declaring a Layout
319
+
320
+ Any template that starts with `{{# layout "name" }}` is registered as a named layout at startup. The `{{ ::slot }}` tag marks where page content gets injected.
321
+
322
+ ```html
323
+ <!-- app/dashboard/layout.html -->
324
+ {{# layout "dashboard_layout" }}
325
+
326
+ <!doctype html>
327
+ <html lang="en">
328
+ <head>
329
+ <title>{{ data.title }}</title>
330
+ </head>
331
+ <body>
332
+ <aside><!-- sidebar --></aside>
333
+ <main>{{ ::slot }}</main>
334
+ </body>
335
+ </html>
336
+ ```
337
+
338
+ ### Using a Layout
339
+
340
+ Any page or fragment that starts with `{{# use "name" }}` is rendered inside that layout. No boilerplate, no repeated markup.
341
+
342
+ ```html
343
+ <!-- app/dashboard/page.html -->
344
+ {{# use "dashboard_layout" }}
345
+
346
+ <h1>Welcome, {{ data.user.name }}</h1>
347
+ ```
348
+
349
+ The filename doesn't matter to Lynnix — `layout.html` is just a convention. What matters is the `{{# layout }}` declaration inside the file.
350
+
351
+ ---
352
+
353
+ ## Fragments (htmx Partial Rendering)
354
+
355
+ When htmx makes a request, it sends an `HX-Request: true` header. Lynnix detects this automatically and renders `fragment.html` instead of `page.html`, giving you clean partial responses without any conditional logic in your loader.
356
+
357
+ ```html
358
+ <!-- app/posts/fragment.html -->
359
+ <div id="posts-list">
360
+ {{ for post of data.posts }}
361
+ <article>{{ post.title }}</article>
362
+ {{ endfor }}
363
+ </div>
364
+ ```
365
+
366
+ Your loader doesn't need to change — the same return value feeds both `page.html` and `fragment.html`. If a route has no `fragment.html`, Lynnix returns an empty `200` for htmx requests.
367
+
368
+ You can also check `req.isHtmx` in your loader if you need to branch on request type:
369
+
370
+ ```js
371
+ export function GET(req) {
372
+ if (!req.isHtmx) {
373
+ return { title: "Posts", posts: db.posts.all() };
374
+ }
375
+
376
+ return { posts: db.posts.all() };
377
+ }
378
+ ```
379
+
380
+ ---
381
+
382
+ ## Error Handling
383
+
384
+ ### Not Found
385
+
386
+ Throw a `NotFoundError` from any loader or middleware to render the nearest `not-found.html` boundary up the directory tree.
387
+
388
+ ```js
389
+ import { NotFoundError } from "lynnix";
390
+
391
+ export function GET(req) {
392
+ const post = db.posts.find(req.params.slug);
393
+
394
+ if (!post) {
395
+ throw new NotFoundError();
396
+ }
397
+
398
+ return { post };
399
+ }
400
+ ```
401
+
402
+ For htmx requests, Lynnix serves `fragment.not-found.html` instead. If no boundary is found, Lynnix returns a plain `404`.
403
+
404
+ ### HTTP Errors
405
+
406
+ Throw an `HttpError` with a status code and optional metadata for any other error scenario.
407
+
408
+ ```js
409
+ import { HttpError } from "lynnix";
410
+
411
+ export function GET(req) {
412
+ if (!req.user.isAdmin) {
413
+ throw new HttpError(403, { message: "Admins only" });
414
+ }
415
+ }
416
+ ```
417
+
418
+ In your error template, you have access to `{{ error }}`, `{{ pathname }}`, and `{{ data }}` (the metadata you passed in).
419
+
420
+ ```html
421
+ <!-- app/error.html -->
422
+ <h1>{{ error.code }}</h1>
423
+ <p>{{ data.message }}</p>
424
+ ```
425
+
426
+ ### Boundary Resolution
427
+
428
+ Lynnix walks up the directory tree from the current route to find the nearest error or not-found boundary. This means a `not-found.html` at `app/dashboard/not-found.html` catches 404s for any unmatched route under `/dashboard`, while `app/not-found.html` serves as the global fallback.
429
+
430
+ ---
431
+
432
+ ## Request API
433
+
434
+ The `req` object passed to loaders and middleware implements `LynnixServerRequest`:
435
+
436
+ | Property | Type | Description |
437
+ |---|---|---|
438
+ | `req.raw` | `http.IncomingMessage` | The underlying Node.js request object |
439
+ | `req.body` | `Record<string, unknown>` | Parsed request body |
440
+ | `req.files` | `LynnixUploadedFiles` | Uploaded files (multipart only) |
441
+ | `req.cookies` | `Record<string, string>` | Parsed request cookies (requires `cookie`) |
442
+ | `req.params` | `Record<string, string>` | Dynamic and catch-all route parameters |
443
+ | `req.query` | `Record<string, unknown>` | Parsed query string |
444
+ | `req.htmx` | `Record<string, string>` | All `hx-*` request headers |
445
+ | `req.isHtmx` | `boolean` | `true` if the request was made by htmx |
446
+
447
+ ---
448
+
449
+ ## Response API
450
+
451
+ The `res` object passed to loaders and middleware implements `LynnixServerResponse`:
452
+
453
+ ### Core
454
+
455
+ | Method | Description |
456
+ |---|---|
457
+ | `res.status(code)` | Set the HTTP status code. Returns `this` for chaining. |
458
+ | `res.end(value?)` | End the response, optionally with a body. |
459
+ | `res.html(content)` | Send an HTML response with the correct `Content-Type`. |
460
+ | `res.json(content)` | Send a JSON response with the correct `Content-Type`. |
461
+ | `res.redirect(url, permanent?)` | Redirect the client. htmx-aware — sets `HX-Redirect` for htmx requests. Pass `true` for a `301` permanent redirect. |
462
+
463
+ ### Cookies
464
+
465
+ | Method | Description |
466
+ |---|---|
467
+ | `res.setCookie(name, value, options)` | Set a cookie. Requires the `cookie` peer dependency. |
468
+ | `res.deleteCookie(name)` | Delete a cookie by setting it as expired. Requires the `cookie` peer dependency. |
469
+ | `res.cookies` | The current response cookies as a plain object. |
470
+
471
+ ### htmx Response Headers
472
+
473
+ These methods are no-ops for non-htmx requests, so you can call them freely without checking `req.isHtmx`.
474
+
475
+ | Method | Description |
476
+ |---|---|
477
+ | `res.htmxTrigger(event)` | Trigger a client-side event via `HX-Trigger`. |
478
+ | `res.htmxTriggerAfterSwap(event)` | Trigger an event after the swap via `HX-Trigger-After-Swap`. |
479
+ | `res.htmxTriggerAfterSettle(event)` | Trigger an event after settle via `HX-Trigger-After-Settle`. |
480
+ | `res.htmxPush(url)` | Push a URL to the browser history via `HX-Push-Url`. Pass `false` to prevent pushing. |
481
+ | `res.htmxReplaceUrl(url)` | Replace the current URL via `HX-Replace-Url`. |
482
+ | `res.htmxRedirect(url)` | Client-side redirect via `HX-Redirect`. |
483
+ | `res.htmxLocation(location)` | Navigate without a full page reload via `HX-Location`. |
484
+ | `res.htmxReswap(strategy)` | Override the swap strategy via `HX-Reswap`. |
485
+ | `res.htmxRetarget(selector)` | Override the target element via `HX-Retarget`. |
486
+ | `res.htmxReselect(selector)` | Override the select expression via `HX-Reselect`. |
487
+ | `res.htmxRefresh()` | Trigger a full page refresh via `HX-Refresh`. |
488
+
489
+ ### Raw Access
490
+
491
+ `res.raw` gives you direct access to the underlying `http.ServerResponse` for anything Lynnix doesn't cover.
492
+
493
+ ---
494
+
495
+ ## Framework Integration
496
+
497
+ Lynnix needs two things from the framework you're using: a request object with `headers` and a response object with `end` and `setHeader`. Anything that provides those works.
498
+
499
+ The `req.raw` and `res.raw` escape hatches expose the underlying objects directly. Any code that touches `.raw` is framework-specific — keep that in mind when writing loaders you want to stay portable.
500
+
501
+ ### Express
502
+
503
+ Express works out of the box. Mount Lynnix as middleware after your static file and body parser middleware.
504
+
505
+ ```js
506
+ app.use(express.static("public"));
507
+ app.use(express.urlencoded({ extended: true }));
508
+ app.use(express.json());
509
+ app.use(cookieParser());
510
+ app.use(await createLynnixApp("app"));
511
+ ```
512
+
513
+ > **Note:** If you're using Express's body parsing middleware, Lynnix's built-in body parser will defer to it automatically. You don't need both.
514
+
515
+ ### Bare `node:http`
516
+
517
+ Use `send-static` for static files and let Lynnix handle everything else. See [Getting Started](#getting-started).
518
+
519
+ ### Fastify
520
+
521
+ Fastify's `reply` object exposes `reply.raw` for the underlying `ServerResponse`. Pass `req.raw` and `reply.raw` to the Lynnix handler:
522
+
523
+ ```js
524
+ fastify.all("/*", (req, reply) => {
525
+ handler(req.raw, reply.raw);
526
+ });
527
+ ```
528
+
529
+ Cookie handling differs between frameworks — if you're using Fastify, install `@fastify/cookie` and set cookies via `res.raw` directly, or use Lynnix's built-in `res.setCookie` with the `cookie` peer dependency.
530
+
531
+ ---
532
+
533
+ ## Built With
534
+
535
+ - [Mutor.js](https://github.com/allAboutJS/Mutor) — the template engine powering Lynnix's rendering layer
536
+ - [htmx](https://htmx.org) — the hypermedia library Lynnix is designed around
537
+
538
+ ---
539
+
540
+ ## License
541
+
542
+ MIT © [Onah Victor](https://github.com/allAboutJS)
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Lynnix
3
+ * File-based hypermedia routing middleware for Node.js, Mutor.js, and HTMX.
4
+ *
5
+ * @author Onah Victor <victoronah.dev@gmail.com>
6
+ * @repository https://github.com/allAboutJS/Lynnix
7
+ * @license MIT
8
+ */
9
+ import createLynnixApp from "./lynnix.js";
10
+ import type LynnixRequest from "./utils/lynnixRequest.js";
11
+ import type LynnixResponse from "./utils/lynnixResponse.js";
12
+ export type { MutorConfig, PartialMutorConfig } from "mutorjs/server";
13
+ export type { CookieOptions, HtmxHeaders, LynnixServerRequest, LynnixServerResponse, LynnixUploadedFiles, ParsedRequestBody, QsModule, } from "./types.js";
14
+ export { HttpError, NotFoundError } from "./utils/error.js";
15
+ export { createLynnixApp, type LynnixRequest, type LynnixResponse };
16
+ export default createLynnixApp;
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,eAAe,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,aAAa,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,cAAc,MAAM,2BAA2B,CAAC;AAE5D,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACtE,YAAY,EACX,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,QAAQ,GACR,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,CAAC;AAEpE,eAAe,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Lynnix
3
+ * File-based hypermedia routing middleware for Node.js, Mutor.js, and HTMX.
4
+ *
5
+ * @author Onah Victor <victoronah.dev@gmail.com>
6
+ * @repository https://github.com/allAboutJS/Lynnix
7
+ * @license MIT
8
+ */
9
+ import createLynnixApp from "./lynnix.js";
10
+ export { HttpError, NotFoundError } from "./utils/error.js";
11
+ export { createLynnixApp };
12
+ export default createLynnixApp;
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,eAAe,MAAM,aAAa,CAAC;AAe1C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAA2C,CAAC;AAEpE,eAAe,eAAe,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Lynnix
3
+ * File-based hypermedia routing middleware for Node.js, Mutor.js, and HTMX.
4
+ *
5
+ * @author Onah Victor <victoronah.dev@gmail.com>
6
+ * @repository https://github.com/allAboutJS/Lynnix
7
+ * @license MIT
8
+ */
9
+ import type * as http from "node:http";
10
+ import { type PartialMutorConfig } from "mutorjs/server";
11
+ import type { ParseReqBodyOptions } from "./types.js";
12
+ /**
13
+ * @param path The root directory of the mutor instance
14
+ * @returns A request handler function that serves the mutor instance
15
+ * @example
16
+ *
17
+ * import createLynnixApp from "lynnix";
18
+ * import * as http from "node:http";
19
+ * import sendStatic from "serve-static";
20
+ *
21
+ * const handler = createLynnixApp("app");
22
+ * const serve = serveStatic("./public", { index: false });
23
+ *
24
+ * const server = http.createServer((req, res) => {
25
+ * serve(req, res, () => handler(req, res));
26
+ * });
27
+ *
28
+ * // OR
29
+ *
30
+ * import express from "express";
31
+ *
32
+ * const server = express();
33
+ * const handler = createLynnixApp("app");
34
+ *
35
+ * server.use(express.urlencoded({ extended: true }));
36
+ * server.use(express.json());
37
+ * server.static("./public");
38
+ *
39
+ * server.use(handler);
40
+ *
41
+ * // FINALLY
42
+ *
43
+ * server.listen(3000);
44
+ */
45
+ export default function createLynnixApp(path: string, mutorConfig?: Omit<PartialMutorConfig, "rootDir">, bodyParserOptions?: ParseReqBodyOptions): Promise<(_req: http.IncomingMessage, _res: http.ServerResponse<http.IncomingMessage>) => Promise<http.ServerResponse<http.IncomingMessage>>>;
46
+ //# sourceMappingURL=lynnix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lynnix.d.ts","sourceRoot":"","sources":["../src/lynnix.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC;AAEvC,OAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AActD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAA8B,eAAe,CAC5C,IAAI,EAAE,MAAM,EACZ,WAAW,GAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,CAAM,EACrD,iBAAiB,GAAE,mBAAwB,kBAmBpC,IAAI,CAAC,eAAe,QACpB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,eAAe,CAAC,yDA4KhD"}