expediate 1.0.5 → 1.0.6

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 (73) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/CONTRIBUTING.md +150 -0
  3. package/README.md +278 -779
  4. package/dist/apis.d.ts +372 -12
  5. package/dist/apis.d.ts.map +1 -1
  6. package/dist/apis.js +483 -65
  7. package/dist/apis.js.map +1 -1
  8. package/dist/cjs/index.js +2290 -807
  9. package/dist/git.d.ts +1 -1
  10. package/dist/git.d.ts.map +1 -1
  11. package/dist/git.js +5 -5
  12. package/dist/git.js.map +1 -1
  13. package/dist/http-objects.d.ts +26 -0
  14. package/dist/http-objects.d.ts.map +1 -0
  15. package/dist/http-objects.js +588 -0
  16. package/dist/http-objects.js.map +1 -0
  17. package/dist/index.d.ts +6 -5
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/jwt-auth.d.ts +11 -0
  22. package/dist/jwt-auth.d.ts.map +1 -1
  23. package/dist/jwt-auth.js +9 -9
  24. package/dist/jwt-auth.js.map +1 -1
  25. package/dist/middleware.js +2 -2
  26. package/dist/middleware.js.map +1 -1
  27. package/dist/mimetypes.json +882 -1
  28. package/dist/misc.d.ts +161 -25
  29. package/dist/misc.d.ts.map +1 -1
  30. package/dist/misc.js +228 -80
  31. package/dist/misc.js.map +1 -1
  32. package/dist/openapi.d.ts +156 -13
  33. package/dist/openapi.d.ts.map +1 -1
  34. package/dist/openapi.js +214 -71
  35. package/dist/openapi.js.map +1 -1
  36. package/dist/router-types.d.ts +760 -0
  37. package/dist/router-types.d.ts.map +1 -0
  38. package/dist/router-types.js +23 -0
  39. package/dist/router-types.js.map +1 -0
  40. package/dist/router.d.ts +7 -530
  41. package/dist/router.d.ts.map +1 -1
  42. package/dist/router.js +128 -375
  43. package/dist/router.js.map +1 -1
  44. package/dist/static.d.ts +2 -2
  45. package/dist/static.d.ts.map +1 -1
  46. package/dist/static.js +77 -22
  47. package/dist/static.js.map +1 -1
  48. package/docs/THREAT_MODEL.md +52 -0
  49. package/docs/api-builder-v2-design.md +644 -0
  50. package/docs/api-builder-v3-design.md +397 -0
  51. package/docs/api-builder.md +454 -0
  52. package/docs/benchmark.md +27 -0
  53. package/docs/body-parsing.md +223 -0
  54. package/docs/errors.md +359 -0
  55. package/docs/expediate.png +0 -0
  56. package/docs/git.md +139 -0
  57. package/docs/jwt-auth.md +251 -0
  58. package/docs/logo.svg +12 -0
  59. package/docs/middleware.md +264 -0
  60. package/docs/openapi.md +180 -0
  61. package/docs/router.md +356 -0
  62. package/docs/static.md +128 -0
  63. package/docs/wiki.json +123 -0
  64. package/package.json +30 -8
  65. package/dist/cjs/apis.js +0 -327
  66. package/dist/cjs/git.js +0 -293
  67. package/dist/cjs/jwt-auth.js +0 -532
  68. package/dist/cjs/middleware.js +0 -511
  69. package/dist/cjs/mimetypes.json +0 -1
  70. package/dist/cjs/misc.js +0 -787
  71. package/dist/cjs/openapi.js +0 -485
  72. package/dist/cjs/router.js +0 -898
  73. package/dist/cjs/static.js +0 -669
package/CHANGELOG.md ADDED
@@ -0,0 +1,138 @@
1
+ # Changelog
2
+
3
+ All notable changes to **expediate** are documented here.
4
+
5
+ ---
6
+
7
+ ## [1.0.6] — 25 June 2026
8
+
9
+ ### Added
10
+ - **API Builder v2** (see `docs/api-builder-v2-design.md`):
11
+ - **Controllers** — `defineController()` / `ServiceDefinition.controllers` split an API into per-domain files merged into one router and one OpenAPI document, with prefix joining, global specificity sorting, and build-time duplicate-route detection
12
+ - **Guards** — pre-handler hooks in the `ctx` world at API, controller, and route level (`OperationMeta.guards`); returned objects accumulate into the new `ctx.state` bag
13
+ - **Auth binding** — `ServiceDefinition.auth` auto-registers an `authenticate` middleware and enforces declarative `permission` requirements (`OperationMeta.permission` / controller-level) via a default check matching `jwtPlugin.requirePermission` semantics, overridable for resource-scoped models
14
+ - **Request validation** — `ServiceDefinition.validate` executes the JSON Schemas declared in `requestBody` metadata (subset validator, `$ref` resolved against the new `ServiceDefinition.schemas`); failures return `400` with `{ message, fieldErrors }`
15
+ - **Context ergonomics** — `ctx.params` (alias of `ctx.query.route`) and `ApiContext<TUser, TState>` generics
16
+ - **OpenAPI security output** — secured operations emit `security: [{ bearerAuth: [] }]`, an `x-required-permissions` vendor extension, and `components.securitySchemes`
17
+ - **Body parsing** — `raw()` (body as `Buffer`) and `text()` (body as `string`) middleware; Brotli (`br`) request decompression alongside gzip/deflate; new `type` option (string / `string[]` / predicate, with `*` wildcards) to control which requests a parser handles, and a `verify(req, res, buf, encoding)` hook run on the raw body before parsing (throw to reject)
18
+ - **Routing** — `router.route(path)` fluent builder for registering several HTTP methods against one path
19
+ - **Method handling** — `HEAD` requests are now served by the matching `GET` handler (body suppressed); unhandled `OPTIONS` on a registered path replies `204` with an `Allow` header; the `405`/OPTIONS `Allow` headers advertise `HEAD` (when a `GET` exists) and `OPTIONS`. Explicit `use()`/`all()` handlers and `cors()` still take precedence
20
+ - **Routing** — `head()` and `options()` registration helpers (on the router and the `route()` builder) for method-specific `HEAD`/`OPTIONS` handlers
21
+ - **Error handling** — `router.error()` registers an ordered, forwardable error-middleware chain (`(err, req, res, next)`); when exhausted it falls back to the single `onError()` handler and then **bubbles to the parent router**, so a root handler can catch failures from deeply nested sub-routers. `apiBuilder` adds a `ServiceDefinition.onError(err, ctx, req)` hook. `ErrorMiddleware` type exported
22
+ - **Response helpers** — `res.append()`, `res.vary()`, `res.location()`, `res.clearCookie()`, `res.sendStatus()`, `res.attachment()`, and a per-response `res.locals` object
23
+ - **Headers** — `req.header(name)` (case-insensitive request-header lookup, `referer`/`referrer` equivalent) and chainable `res.header(field, value)`
24
+ - **Route safety** — `RegExp` routes that use the `g` (global) or `y` (sticky) flag are now rejected at registration; those flags make `exec()` stateful and cause intermittent, hard-to-debug `404`s. Inline `:name(constraint)` patterns are scanned so regex metacharacters inside them are no longer mistaken for glob wildcards
25
+ - **Cookies** — `res.cookie()` now percent-encodes values (after any `j:`/`s:` wrapping) so semicolons, commas, spaces, quotes, and backslashes transmit safely; cookie parsing de-quotes (RFC 6265 quoted-string) and percent-decodes values before interpreting `j:`/`s:` prefixes. Signed-cookie HMAC remains computed over the unencoded value
26
+ - **Response validation** — `apiBuilder` accepts an optional second argument, `ApiBuilderOptions`, controlling validation. `validateResponses` checks each handler's return against its declared `responses['200']` schema: `true` returns `500 { message, fieldErrors }` when the server would emit an off-spec body, `'warn'` logs the mismatch via `console.warn` and sends the response unchanged. `validateRequests` (default on) toggles the existing request check. `ServiceDefinition.validate` accepts the same `boolean | ApiBuilderOptions` shape
27
+ - **MIME types** — bundled `src/mimetypes.json` table expanded with hundreds of additional extensions
28
+ - `ErrorHandler` and `RouteInfo` types re-exported from the public API
29
+ - `TokenPayload` and `UserRecord` types re-exported from the public API
30
+ - `CorsOptions` type re-exported from the public API
31
+ - `cors()` middleware documented in README
32
+
33
+ ### Changed
34
+ - **Breaking:** a duplicate `(verb, path)` route pair now **throws at build time** in `apiBuilder` / `openApiSpec` (was silent shadowing)
35
+ - **Breaking (compile-time):** `ApiContext.user` defaults to `unknown` instead of `any`
36
+ - **Breaking (compile-time):** `RouterRequest.body` is now typed `unknown` instead of `any` — narrow it before use (e.g. `req.body as { id: string }`)
37
+ - `req.user` is now typed (`TokenPayload`) on `RouterRequest` when the JWT plugin is loaded, instead of requiring an `as any` cast
38
+ - `ServiceDefinition.schemas` supersedes `SpecOptions.schemas` on name conflicts (the spec-options form is kept as a fallback)
39
+
40
+ ### Removed
41
+ - **Breaking:** `router.setNotFound()` and the not-found handler hook. Register a catch-all layer **last** instead — `app.all('/**', (req, res) => …)` — which matches in registration order and, unlike `setNotFound()`, also fires correctly inside mounted sub-routers. The built-in `Cannot METHOD /path` 404 remains the default fallback
42
+
43
+ ### Performance
44
+ - Per-request `req`/`res` helpers are now defined once on shared prototypes and attached via `Object.setPrototypeOf` instead of allocating ~20 closures on every request; the `status()` range check was folded into the single prototype method (removing a second per-request allocation)
45
+
46
+ ### Fixed
47
+ - `cors()` with an array `origin` now matches the request's `Origin` header against the allow-list and echoes back only the matching entry (previously it only ever matched a single string origin, so an array never granted access)
48
+ - Body parsing no longer returns `415` when a parser's content type doesn't match — it passes through to the next middleware, so parser stacks (`json()` + `formEncoded()` + …) compose correctly
49
+ - Static file serving hardened against control characters in the path and corrected path-handling edge cases
50
+ - Consistent method-routing behaviour (`HEAD`/`OPTIONS`/`405`) across registered and unregistered paths
51
+ - Misc. test-reliability improvements
52
+
53
+ ### Internal / Tooling
54
+ - Split the ~2,200-line `src/router.ts` into `router.ts` + `router-types.ts` (type declarations) + `http-objects.ts` (req/res augmentation and cookie helpers); the public API and exports are unchanged
55
+ - Added a type-aware ESLint flat config (`eslint.config.js`) and `tsconfig.eslint.json`; `npm run lint` now passes. Reduced `any` usage across `src/`
56
+ - Bumped `esbuild` (build-time dev dependency) to resolve security advisories; pruned stale repo files (`AUDIT.md`, duplicate `docs/middlewares.md`)
57
+ - Documentation overhaul: corrected the JWT refresh-token description (signed JWT keyed by `jti`, opt-in via `refreshTokenStore`, `501` when absent — was documented as an always-on opaque hex token), the Brotli request-decompression support (was documented as unsupported), `static.md`'s conditional-request coverage (`If-Match`/`If-Unmodified-Since` → `412`), and `git.md`'s `gitCreate` `bare` option; documented `router.route()`, `raw()`, and `text()`. Removed `ROADMAP.md`, `AUDIT.md` (disposable, point-in-time planning/audit artifacts), and the `docs/mdlw/` directory (20 one-function-per-file pages that duplicated the topic docs in `docs/`) — `docs/wiki.json` updated to drop the now-removed entries
58
+
59
+ ---
60
+
61
+ ## [1.0.5] — 8 June 2026
62
+
63
+ ### Added
64
+ - **ETag / conditional GET** — `res.etag()` response helper and `conditionalGet()` middleware implement RFC 7232 (`If-None-Match`, `If-Modified-Since`) so clients receive 304 Not Modified when their cache is fresh
65
+ - **Path parameter type constraints** — inline regex in route patterns (`:id(\d+)`, `:slug([a-z-]+)`) so parameters are validated at the routing level without extra middleware
66
+ - **Asymmetric JWT keys** — `createJwtPlugin` now supports RS256/384/512 and ES256/384/512 algorithms via `accessTokenPrivateKey` / `accessTokenPublicKey` PEM options alongside the existing HMAC `accessTokenSecret`
67
+ - **Directory listing sort** — `serveStatic` directory index now supports column sort via `?C=N|M|S;O=A|D` query params (directories always listed first)
68
+ - **ESM-first with CJS compatibility** — package is now `"type": "module"` with a bundled CJS shim at `dist/cjs/index.js`; dual `exports` field in `package.json` routes consumers to the right build automatically
69
+
70
+ ### Fixed
71
+ - Cookie reading and writing: signed cookies (HMAC-SHA256 via `createRouter({ secret })`) and JSON-prefixed cookies (`j:`) now work correctly on both read and write paths
72
+ - Async error handling: async middleware rejections are now caught and routed to the error handler (fixes unhandled promise rejections)
73
+ - `listen()` now returns the underlying `http.Server` / `https.Server` instance for graceful shutdown and ephemeral port discovery
74
+ - Git handler options (`strict`, `timeout`, `gitPath`) documented and applied correctly
75
+
76
+ ---
77
+
78
+ ## [1.0.4] — May 2026
79
+
80
+ ### Added
81
+ - **OpenAPI spec generation** — `describe()`, `openApiSpec()`, and `serializeSpec()` (JSON and YAML) for annotating API service routes with schema metadata
82
+ - **API service builder** — `apiBuilder()` for defining scoped (singleton / keyed / ephemeral) HTTP services with automatic JSON error handling and route priority sorting
83
+ - **Middleware suite** — `compress()` (Brotli/gzip/deflate), `requestId()`, `rateLimit()`, `cacheControl()`, `csrf()` (double-submit cookie), `securityHeaders()`, `conditionalGet()`
84
+ - **CORS support** — `cors()` middleware with origin, credentials, preflight guard, and `Vary` header support
85
+
86
+ ### Fixed
87
+ - Common error schema standardised to `{ status, message }` across all built-in error responses
88
+ - Git Smart HTTP handler corrected and end-to-end tested
89
+
90
+ ---
91
+
92
+ ## [1.0.3] — Mars 2026
93
+
94
+ ### Added
95
+ - **JWT authentication plugin** — `createJwtPlugin()` with login, refresh (token rotation), logout, `authenticate`, `authorize`, `requireRole`, and `requirePermission` middleware; HMAC HS256/384/512 support
96
+ - **TypeScript rewrite** — entire codebase converted to strict TypeScript (ESNext target, `isolatedModules`)
97
+ - **Router interface improvements** — `router.routes()`, `router.onError()`, sub-router path stripping, and `req`/`res` helper augmentation (`req.json()`, `req.text()`, `res.status()`, `res.cookie()`, `res.redirect()`, `res.download()`, `res.type()`)
98
+
99
+ ---
100
+
101
+ ## [1.0.2] — Mars 2026
102
+
103
+ ### Added
104
+ - **Git Smart HTTP gateway** — `gitHandler()` serves `git clone`, `git fetch`, and `git push` over HTTP; `gitCreate()` initialises bare repositories programmatically
105
+ - **Body parsing** — `json()`, `formData()`, `formEncoded()`, `parseBody()`, `streamFormData()` with size limits, gzip/deflate decompression, and multipart boundary parsing
106
+ - **Request logger** — `logger()` middleware with ANSI-coloured output, structured JSON mode, and lost-request tracking
107
+ - **RegExp routes** — route paths may be `RegExp` instances; named capture groups populate `req.params`
108
+ - **Glob patterns** — `*`, `**`, `?` wildcard support in route paths
109
+
110
+ ---
111
+
112
+ ## [1.0.1] — Mars 2026
113
+
114
+ ### Added
115
+ - Initial release
116
+ - `createRouter()` — zero-dependency HTTP/HTTPS/HTTP2 server with Express-compatible `use()`, `get()`, `post()`, `put()`, `delete()`, `patch()`, `all()` route registration
117
+ - Named route parameters (`:name`, `:id(\d+)`)
118
+ - `serveStatic()`, `serveFile()`, `sendFile()` for static file serving with ETag caching, dotfile protection, and path traversal guards
119
+ - MIME type detection via `src/mimetypes.json`
120
+ - Dual CJS + ESM output via `tsc` + `esbuild`
121
+
122
+ ---
123
+
124
+ ## [1.0.0] — Mars 2026
125
+
126
+ _Not documented_
127
+
128
+ ## [0.0.3] — 2022
129
+
130
+ _Not documented_
131
+
132
+ ## [0.0.2] — 2021
133
+
134
+ _Not documented_
135
+
136
+ ## [0.0.1] — 2021
137
+
138
+ _Not documented_
@@ -0,0 +1,150 @@
1
+ # Contributing
2
+
3
+ First of all, thank you for taking the time to read this. It means you're considering contributing to one of my projects — and that genuinely matters to me.
4
+
5
+ ## Who I am and why this exists
6
+
7
+ I'm a backend and systems developer. I build tools — mostly because I need them, and sometimes because I get frustrated that nothing out there does exactly what I want without dragging along a mountain of complexity I didn't ask for.
8
+
9
+ My projects exist because they solved a real problem for me. They're designed to be **lightweight, easy to integrate, and honest about what they do**. I care about optimization, but never at the cost of core functionality — that's exactly the kind of tradeoff that pushes me away from many professional solutions. If a feature makes the tool harder to understand or harder to use, it's probably not worth it.
10
+
11
+ I like to understand the tools I work with, deeply. I expect the same curiosity from people who contribute here. You don't have to know everything — but you should want to.
12
+
13
+ ---
14
+
15
+ ## Code of conduct
16
+
17
+ This community runs on a simple principle: **be decent to each other**.
18
+
19
+ I don't expect everyone to agree. In fact, I expect the opposite — technically skilled people tend to have strong opinions and can be stubborn (myself included). That's fine. Disagreement is productive. But it requires a constant effort of **comprehension, tolerance, and ego management**.
20
+
21
+ What I won't tolerate:
22
+ - Hostility, condescension, or dismissiveness toward other contributors
23
+ - Behavior that makes this space uncomfortable for others or for me
24
+
25
+ I reserve the right to remove any member whose behavior is inappropriate or disruptive — to the community or to me personally. There's no appeals process for that.
26
+
27
+ ---
28
+
29
+ ## What you can contribute
30
+
31
+ All forms of contribution are welcome:
32
+
33
+ - **Bug reports** — Something's broken? Tell me.
34
+ - **Feature requests** — Have an idea? Let's talk about it.
35
+ - **Documentation** — Often the most underrated contribution.
36
+ - **Tests & benchmarks** — Always appreciated, especially if they reveal something.
37
+ - **Translations** — If you want to make the project accessible to more people.
38
+ - **Code** — Of course.
39
+
40
+ A note on **reviews and corrections**: I prefer that feedback on PRs comes from people who have already contributed actively to the project. I want to avoid people who show up only to critique, or who try to take ownership of someone else's effort. If you're new here, start by contributing before reviewing.
41
+
42
+ ---
43
+
44
+ ## Git workflow
45
+
46
+ I care about history. Real history — not a rewritten, sanitized version of it.
47
+
48
+ ### Merge strategy
49
+
50
+ - **Merges over rebases.** I want to see what actually happened, not what someone wished had happened.
51
+ - **Non-fast-forward merges** are preferred. They preserve the branch structure and make it clear on which version a ticket was resolved.
52
+ - **Squash with caution.** It can be useful to clean up meaningless save-state commits with no real progress, but it should never be applied to a significant branch. Squashing a week of work into one commit destroys context.
53
+
54
+ ### Branch naming
55
+
56
+ Use the following format:
57
+
58
+ ```
59
+ {group}/{alias}/{topic}
60
+ ```
61
+
62
+ - **group** — the type of work: `feat`, `fix`, `chore`, `release`, `docs`, `test`, etc.
63
+ - **alias** — something that identifies you as the author (your GitHub handle, initials, whatever you use consistently)
64
+ - **topic** — a short, free-form description of the work
65
+
66
+ Examples:
67
+ ```
68
+ feat/alex/user-auth
69
+ fix/jdoe/connection-pool-leak
70
+ chore/sam/update-ci-deps
71
+ ```
72
+
73
+ ### Publish early
74
+
75
+ **Favor small, focused branches.** Don't disappear for three weeks and resurface with a 2,000-line PR.
76
+
77
+ Open a **draft PR as early as possible**, even if the work isn't done. It signals what you're working on, invites early feedback, and dramatically reduces the chance of a rejection. A PR that surprises everyone is a PR at risk.
78
+
79
+ ---
80
+
81
+ ## Opening an issue
82
+
83
+ I don't have formal templates. I don't want the friction of a strict template to discourage someone from reporting a bug.
84
+
85
+ That said, a good issue saves everyone time. When in doubt, include:
86
+
87
+ - What you expected to happen
88
+ - What actually happened
89
+ - How to reproduce it (minimal example if possible)
90
+ - Your environment (OS, version, runtime)
91
+
92
+ For feature requests: explain the problem you're trying to solve, not just the solution you have in mind. Context helps more than a spec.
93
+
94
+ If your issue is vague, I'll ask for more detail. That's not a rejection — it's a conversation.
95
+
96
+ ---
97
+
98
+ ## Submitting a pull request
99
+
100
+ Before submitting, ask yourself:
101
+
102
+ - [ ] Is there an open, accepted issue for this? If not — did you discuss it first?
103
+ - [ ] Do the existing tests still pass?
104
+ - [ ] Did you add tests for the new behavior? (See below.)
105
+ - [ ] Is documentation up to date?
106
+ - [ ] If performance-relevant, did you run benchmarks?
107
+
108
+ **If you skipped any of these**, that's okay — but explain why in the PR description. "I didn't add tests because this only affects the build pipeline" is fine. Silence is not.
109
+
110
+
111
+ ---
112
+
113
+ ## Code style
114
+
115
+ In case no linter are configured, the rule is simple: **match what's already there**.
116
+
117
+ Read the existing code. Understand its style. Integrate into it — don't reformat it to match your preferences.
118
+
119
+ Beyond formatting, I pay close attention to:
120
+
121
+ - **Readability** — Can someone who didn't write this understand it quickly?
122
+ - **Function length and complexity** — If a function is doing too much, it probably needs to be split.
123
+ - **Naming** — Names should reflect intent, not implementation.
124
+ - **Unnecessary complexity** — If there's a simpler way, use it.
125
+
126
+ ---
127
+
128
+ ## Development environment
129
+
130
+ I change environments often, so I've made a point of keeping setups lightweight and self-contained. Everything you need to get started is in the `README`.
131
+
132
+ If you run into something that isn't documented and blocks you — open an issue or reach out to an active contributor. If the setup is broken or confusing in a way that isn't documented, that's a real bug and worth filing.
133
+
134
+ ---
135
+
136
+ ## Recognition
137
+
138
+ Every contributor is credited in the `CHANGELOG` at release time.
139
+
140
+ Recurring contributors might also appear in the `README` as well.
141
+
142
+ For contributors, I'll ask for:
143
+ - A **real first name** (minimum) — aliases are fine for commits, but I want to know who's in this community
144
+ - A **real email address** (personal or professional) — this is how I'll reach you if needed and how attribution is tracked
145
+
146
+ You can use an alias in your git commits. But when it comes to the community and the changelog, I want real people behind the names.
147
+
148
+ ---
149
+
150
+ *This document reflects how I like to work. It will evolve. If something here is unclear or feels off, open an issue — that's a valid contribution too.*