exisjs 0.5.9 → 0.5.11
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/dist/auth/index.d.ts +3 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +4 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/oauth/core.d.ts +96 -0
- package/dist/auth/oauth/core.d.ts.map +1 -0
- package/dist/auth/oauth/core.js +203 -0
- package/dist/auth/oauth/core.js.map +1 -0
- package/dist/auth/oauth/index.d.ts +45 -0
- package/dist/auth/oauth/index.d.ts.map +1 -0
- package/dist/auth/oauth/index.js +64 -0
- package/dist/auth/oauth/index.js.map +1 -0
- package/dist/auth/oauth/middleware.d.ts +63 -0
- package/dist/auth/oauth/middleware.d.ts.map +1 -0
- package/dist/auth/oauth/middleware.js +164 -0
- package/dist/auth/oauth/middleware.js.map +1 -0
- package/dist/auth/oauth/providers/custom.d.ts +41 -0
- package/dist/auth/oauth/providers/custom.d.ts.map +1 -0
- package/dist/auth/oauth/providers/custom.js +204 -0
- package/dist/auth/oauth/providers/custom.js.map +1 -0
- package/dist/auth/oauth/providers/discord.d.ts +23 -0
- package/dist/auth/oauth/providers/discord.d.ts.map +1 -0
- package/dist/auth/oauth/providers/discord.js +130 -0
- package/dist/auth/oauth/providers/discord.js.map +1 -0
- package/dist/auth/oauth/providers/facebook.d.ts +28 -0
- package/dist/auth/oauth/providers/facebook.d.ts.map +1 -0
- package/dist/auth/oauth/providers/facebook.js +124 -0
- package/dist/auth/oauth/providers/facebook.js.map +1 -0
- package/dist/auth/oauth/providers/github.d.ts +25 -0
- package/dist/auth/oauth/providers/github.d.ts.map +1 -0
- package/dist/auth/oauth/providers/github.js +132 -0
- package/dist/auth/oauth/providers/github.js.map +1 -0
- package/dist/auth/oauth/providers/google.d.ts +23 -0
- package/dist/auth/oauth/providers/google.d.ts.map +1 -0
- package/dist/auth/oauth/providers/google.js +134 -0
- package/dist/auth/oauth/providers/google.js.map +1 -0
- package/dist/auth/oauth/providers/microsoft.d.ts +28 -0
- package/dist/auth/oauth/providers/microsoft.d.ts.map +1 -0
- package/dist/auth/oauth/providers/microsoft.js +136 -0
- package/dist/auth/oauth/providers/microsoft.js.map +1 -0
- package/dist/auth/oauth/types.d.ts +158 -0
- package/dist/auth/oauth/types.d.ts.map +1 -0
- package/dist/auth/oauth/types.js +4 -0
- package/dist/auth/oauth/types.js.map +1 -0
- package/dist/auth/session.d.ts +4 -1
- package/dist/auth/session.d.ts.map +1 -1
- package/dist/auth/session.js +33 -5
- package/dist/auth/session.js.map +1 -1
- package/dist/cache/store.d.ts +11 -3
- package/dist/cache/store.d.ts.map +1 -1
- package/dist/cache/store.js +87 -19
- package/dist/cache/store.js.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +4 -6
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/database/adapters/mongodb.d.ts +58 -0
- package/dist/database/adapters/mongodb.d.ts.map +1 -0
- package/dist/database/adapters/mongodb.js +264 -0
- package/dist/database/adapters/mongodb.js.map +1 -0
- package/dist/database/adapters/mysql.d.ts +27 -0
- package/dist/database/adapters/mysql.d.ts.map +1 -0
- package/dist/database/adapters/mysql.js +188 -0
- package/dist/database/adapters/mysql.js.map +1 -0
- package/dist/database/adapters/postgres.d.ts +28 -0
- package/dist/database/adapters/postgres.d.ts.map +1 -0
- package/dist/database/adapters/postgres.js +174 -0
- package/dist/database/adapters/postgres.js.map +1 -0
- package/dist/database/adapters/sqlite.d.ts +27 -0
- package/dist/database/adapters/sqlite.d.ts.map +1 -0
- package/dist/database/adapters/sqlite.js +149 -0
- package/dist/database/adapters/sqlite.js.map +1 -0
- package/dist/database/index.d.ts +42 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +58 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/manager.d.ts +114 -0
- package/dist/database/manager.d.ts.map +1 -0
- package/dist/database/manager.js +269 -0
- package/dist/database/manager.js.map +1 -0
- package/dist/database/migration.d.ts +57 -0
- package/dist/database/migration.d.ts.map +1 -0
- package/dist/database/migration.js +329 -0
- package/dist/database/migration.js.map +1 -0
- package/dist/database/query-builder.d.ts +89 -0
- package/dist/database/query-builder.d.ts.map +1 -0
- package/dist/database/query-builder.js +353 -0
- package/dist/database/query-builder.js.map +1 -0
- package/dist/database/types.d.ts +184 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +4 -0
- package/dist/database/types.js.map +1 -0
- package/dist/docs/ARCHITECTURE.md +349 -0
- package/dist/docs/BENCHMARKS.md +36 -0
- package/dist/docs/README.md +34 -0
- package/dist/docs/content/README.md +44 -0
- package/dist/docs/content/adapters.mdx +199 -0
- package/dist/docs/content/cache.mdx +239 -0
- package/dist/docs/content/circuit-breaker.mdx +130 -0
- package/dist/docs/content/cli/commands.mdx +214 -0
- package/dist/docs/content/cli/overview.mdx +92 -0
- package/dist/docs/content/cli/usage.mdx +61 -0
- package/dist/docs/content/config.mdx +214 -0
- package/dist/docs/content/controllers.mdx +241 -0
- package/dist/docs/content/cron.mdx +109 -0
- package/dist/docs/content/database.mdx +209 -0
- package/dist/docs/content/dataloader.mdx +170 -0
- package/dist/docs/content/decorators.mdx +204 -0
- package/dist/docs/content/errors.mdx +344 -0
- package/dist/docs/content/gateways.mdx +376 -0
- package/dist/docs/content/installation.mdx +210 -0
- package/dist/docs/content/introduction.mdx +96 -0
- package/dist/docs/content/middleware.mdx +441 -0
- package/dist/docs/content/modules.mdx +144 -0
- package/dist/docs/content/oauth.mdx +179 -0
- package/dist/docs/content/providers.mdx +147 -0
- package/dist/docs/content/queues.mdx +177 -0
- package/dist/docs/content/requests.mdx +293 -0
- package/dist/docs/content/responses.mdx +271 -0
- package/dist/docs/content/routing.mdx +541 -0
- package/dist/docs/content/rpc.mdx +146 -0
- package/dist/docs/content/security.mdx +294 -0
- package/dist/docs/content/sse.mdx +159 -0
- package/dist/docs/content/structure.mdx +116 -0
- package/dist/docs/content/testing.mdx +139 -0
- package/dist/docs/content/validation.mdx +249 -0
- package/dist/docs/content/websockets.mdx +289 -0
- package/dist/docs/integrations/README.md +23 -0
- package/dist/docs/integrations/jwt.mdx +151 -0
- package/dist/docs/integrations/mongo.mdx +183 -0
- package/dist/docs/integrations/observability.mdx +125 -0
- package/dist/docs/integrations/openai.mdx +108 -0
- package/dist/docs/integrations/postgres.mdx +190 -0
- package/dist/docs/integrations/posthog.mdx +122 -0
- package/dist/docs/integrations/redis.mdx +141 -0
- package/dist/docs/integrations/resend.mdx +117 -0
- package/dist/docs/integrations/s3.mdx +122 -0
- package/dist/docs/integrations/swagger.mdx +113 -0
- package/dist/docs/reference/README.md +3 -0
- package/dist/docs/reference/adapters.mdx +130 -0
- package/dist/docs/reference/auth.mdx +183 -0
- package/dist/docs/reference/cache.mdx +132 -0
- package/dist/docs/reference/circuit-breaker.mdx +71 -0
- package/dist/docs/reference/config.mdx +118 -0
- package/dist/docs/reference/context.mdx +168 -0
- package/dist/docs/reference/dataloader.mdx +91 -0
- package/dist/docs/reference/decorators.mdx +293 -0
- package/dist/docs/reference/di.mdx +123 -0
- package/dist/docs/reference/error.mdx +170 -0
- package/dist/docs/reference/exis.mdx +124 -0
- package/dist/docs/reference/middleware.mdx +304 -0
- package/dist/docs/reference/plugin.mdx +113 -0
- package/dist/docs/reference/queue.mdx +155 -0
- package/dist/docs/reference/router.mdx +248 -0
- package/dist/docs/reference/sanitize.mdx +130 -0
- package/dist/docs/reference/storage.mdx +116 -0
- package/dist/docs/reference/swagger.mdx +132 -0
- package/dist/docs/reference/testing.mdx +145 -0
- package/dist/docs/reference/validator.mdx +186 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/backpressure.d.ts.map +1 -1
- package/dist/middleware/backpressure.js +21 -12
- package/dist/middleware/backpressure.js.map +1 -1
- package/dist/middleware/cache.d.ts +6 -0
- package/dist/middleware/cache.d.ts.map +1 -1
- package/dist/middleware/cache.js +84 -28
- package/dist/middleware/cache.js.map +1 -1
- package/dist/middleware/idempotency.d.ts +4 -1
- package/dist/middleware/idempotency.d.ts.map +1 -1
- package/dist/middleware/idempotency.js +29 -4
- package/dist/middleware/idempotency.js.map +1 -1
- package/dist/middleware/ip-filter.d.ts.map +1 -1
- package/dist/middleware/ip-filter.js +16 -0
- package/dist/middleware/ip-filter.js.map +1 -1
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/middleware/rate-limit.js +29 -12
- package/dist/middleware/rate-limit.js.map +1 -1
- package/dist/queue/drivers/MemoryDriver.d.ts +6 -3
- package/dist/queue/drivers/MemoryDriver.d.ts.map +1 -1
- package/dist/queue/drivers/MemoryDriver.js +76 -23
- package/dist/queue/drivers/MemoryDriver.js.map +1 -1
- package/dist/server/bootstrapper.d.ts +3 -0
- package/dist/server/bootstrapper.d.ts.map +1 -1
- package/dist/server/bootstrapper.js +56 -8
- package/dist/server/bootstrapper.js.map +1 -1
- package/dist/server/bun-adapter.d.ts +2 -0
- package/dist/server/bun-adapter.d.ts.map +1 -1
- package/dist/server/bun-adapter.js +23 -12
- package/dist/server/bun-adapter.js.map +1 -1
- package/dist/server/request-handler.d.ts +0 -2
- package/dist/server/request-handler.d.ts.map +1 -1
- package/dist/server/request-handler.js +2 -24
- package/dist/server/request-handler.js.map +1 -1
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +24 -11
- package/dist/server/request.js.map +1 -1
- package/dist/server/uws-adapter.d.ts +80 -0
- package/dist/server/uws-adapter.d.ts.map +1 -0
- package/dist/server/uws-adapter.js +357 -0
- package/dist/server/uws-adapter.js.map +1 -0
- package/dist/server/ws-orchestrator.d.ts +1 -0
- package/dist/server/ws-orchestrator.d.ts.map +1 -1
- package/dist/server/ws-orchestrator.js +83 -0
- package/dist/server/ws-orchestrator.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/validator/tex.d.ts +21 -16
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js.map +1 -1
- package/package.json +11 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/database/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/database/types.ts"],"names":[],"mappings":";AAAA,kFAAkF"}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# ExisJS Architecture Reference
|
|
2
|
+
|
|
3
|
+
This document is the single source of truth for how ExisJS is built internally. Unlike marketing copy, everything here is written to match what the code actually does — verified against the source, not aspirational. Each subsystem is labeled with a **Status** tag; treat that label as load-bearing information, not decoration.
|
|
4
|
+
|
|
5
|
+
**Status legend:**
|
|
6
|
+
|
|
7
|
+
- 🟢 **Stable** — core path, exercised by real deployments and dense test coverage.
|
|
8
|
+
- 🟡 **Beta** — works as documented, but has known rough edges or thinner test coverage.
|
|
9
|
+
- 🟠 **Experimental** — functional but unproven under real production load; expect edge cases.
|
|
10
|
+
- 🔴 **Known Issue** — currently broken or non-functional; see linked bug reference.
|
|
11
|
+
- ⚪ **Scaffolded / Not Implemented** — API surface or docs exist, but the behavior described does not (yet) exist at runtime.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. File-System Routing 🟢
|
|
16
|
+
|
|
17
|
+
**What it is:** Folder-based routing mapping HTTP routes to the file system (`route.ts`, `gateway.ts`, `server.ts`, optionally `schema.ts` / `service.ts` / `controller.ts` as a convention, not an enforced structure).
|
|
18
|
+
|
|
19
|
+
**How it's implemented:** At boot, `RouteScanner` walks the configured `src/http` directory (or a custom `apiDir`) and registers discovered routes into the `Router`'s matching engine. Folder-naming conventions are translated during the scan:
|
|
20
|
+
|
|
21
|
+
- `[param]` → `:param` dynamic segment
|
|
22
|
+
- `[...param]` → `*param` catch-all
|
|
23
|
+
- `(group)` → ignored entirely in the resulting URL, but still traversed for nested routes
|
|
24
|
+
|
|
25
|
+
In development, routes are mounted lazily/dynamically and watched by `HotReloader` (see §12). In production, `exis build` can pre-generate a manifest (see §8) that lists every route file, though the request-time route matching itself is not manifest-driven — see §8 for the important distinction between "route discovery is fast" and "route execution uses a pre-compiled path," because only the former is currently true.
|
|
26
|
+
|
|
27
|
+
**Example:**
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// src/http/users/[id]/route.ts
|
|
31
|
+
import { controller, route } from 'exisjs/router'
|
|
32
|
+
export default controller({
|
|
33
|
+
getSingle: route.get('/', {
|
|
34
|
+
handle: ({ params }) => ({ id: params.id }),
|
|
35
|
+
}),
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 2. Dual Paradigm: Functional & Class-Based (OOP) 🟢
|
|
42
|
+
|
|
43
|
+
**What it is:** Two ways to define the same routing/controller concepts — `controller()`/`route.*()` factory functions, or `@Controller()`/`@Get()` class decorators — that compile down to the same internal `Route` representation and execute through the same pipeline.
|
|
44
|
+
|
|
45
|
+
**How it's implemented:** Class decorators (`decorators/index.ts`) attach metadata to the class prototype via `Symbol.for('exisjs:...')` keys (routes, middleware, param metadata, lifecycle metadata) rather than `reflect-metadata`, for O(1) lookup without a polyfill dependency. `ControllerRegistrar` reads this metadata at registration time and produces the exact same kind of route entries that the functional `controller()`/`route.*()` API produces directly. Both paradigms can be mixed file-by-file in the same application.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 3. The Context API & `AsyncLocalStorage` 🟢
|
|
50
|
+
|
|
51
|
+
**What it is:** Access to the current request's `req`/`res`/app-scoped state from anywhere in the call graph without prop-drilling, plus `after()` for deferred post-response work.
|
|
52
|
+
|
|
53
|
+
**How it's implemented:** `context.ts` wraps Node's native `AsyncLocalStorage` in an `InternalContext` store containing `{ state, afterCallbacks, req, res, app, diCache }`. `getContext()`, `setContext()`, `getRequest()`, `getResponse()`, `getApp()`, and `after()` all read from the active store, throwing a clear error if called outside an active request. `RequestHandler._executeWithContext` establishes the store per-request and flushes `afterCallbacks` on the `finish`/`close` events of the response, catching and logging (not throwing) errors in background `after()` work so they can't crash the request.
|
|
54
|
+
|
|
55
|
+
**Isolation:** each request gets its own `diCache` (a fresh `Map`) for request-scoped DI resolution, and context state does not leak across concurrent requests — this is tested and confirmed correct.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 4. Dependency Injection (`Container`) 🟢
|
|
60
|
+
|
|
61
|
+
**What it is:** A lightweight IoC container supporting value, factory, and class providers, with singleton and per-request scoping.
|
|
62
|
+
|
|
63
|
+
**How it's implemented:** `di/container.ts`'s `Container` class holds a `providers` map and a `singletonCache`. `resolve(token, requestCache?)` checks the singleton cache, then the passed-in per-request cache, then the registered provider definition, instantiating and caching as appropriate to the provider's declared `scope`. Unregistered class tokens are auto-instantiated on first resolve and cached as singletons by default.
|
|
64
|
+
|
|
65
|
+
**Two access patterns exist and are both supported:**
|
|
66
|
+
|
|
67
|
+
- `inject(token)` (`di/inject.ts`) — the recommended primitive, reads the active `AsyncLocalStorage` context and resolves against `app.resolve(token, store.diCache)`. Works inside any route handler or middleware.
|
|
68
|
+
- `app.provide(token, definition)` / `app.resolve(token)` — direct container access, primarily used at boot time (`server.ts`, module registration) rather than inside request handlers.
|
|
69
|
+
|
|
70
|
+
**Known limitation:** when auto-instantiation of an unregistered class throws (e.g., a constructor-time validation error), the container currently discards the original error and throws a generic "Cannot resolve provider" message. Treat the original error as recoverable debugging information that should not be lost — this is tracked as a fix, not a design decision.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 5. Modules & Plugins 🟢
|
|
75
|
+
|
|
76
|
+
**What it is:** Two related but distinct extension primitives.
|
|
77
|
+
|
|
78
|
+
- **`defineModule`** (`module/module.ts`) — groups domain-level providers, sub-module imports, and optional manual routes into a single registrable unit, with automatic deduplication of shared imports (`app.hasPlugin(name)` check before re-registering).
|
|
79
|
+
- **`definePlugin`** (`utils/plugin.ts`) — for distributable, configurable third-party packages. Returns a hybrid object: usable directly, or callable as a function to pass options (`MyPlugin({ apiKey })`).
|
|
80
|
+
|
|
81
|
+
**How registration/encapsulation works:** `PluginManager.register()` optionally wraps the target `App` in a `Proxy` (when `plugin.encapsulate !== false`) that intercepts routing methods and scopes them to a plugin-local `Router`, which is then mounted onto the main app under `/`. This prevents a plugin's internal route/middleware registration from silently leaking into the global app surface unless explicitly intended.
|
|
82
|
+
|
|
83
|
+
**Gateways as modules:** `defineGateway` (`router/gateway.ts`) can itself carry `imports`/`providers`, letting a folder-scoped `gateway.ts` file behave as a lightweight module that hydrates the DI container before any `route.ts` inside that folder executes.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 6. Cascading Gateways 🟢
|
|
88
|
+
|
|
89
|
+
**What it is:** Folder-level configuration (middleware, CORS, headers, guards, filters, interceptors, providers, plugins, metadata) that applies to a directory and all its subdirectories.
|
|
90
|
+
|
|
91
|
+
**How it's implemented:** During route mounting, the router maintains a stack of active gateway configs as it descends the directory tree, merging arrays (middleware, guards, filters, interceptors) from parent to child rather than overriding them. A gateway can return a response directly, which halts execution before the route handler is reached — the same mechanism regular middleware uses.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 7. Native Validation Engine (`v`) 🟢
|
|
96
|
+
|
|
97
|
+
**What it is:** A dependency-free, Zod-like schema validator with strict parsing and structured error aggregation.
|
|
98
|
+
|
|
99
|
+
**How it's implemented:** `utils/validator.ts` defines `ValidatorType` subclasses (`StringValidator`, `NumberValidator`, `ObjectValidator`, `ArrayValidator`, etc.) each implementing `.parse()`/`.validate()`. Validation failures collect into a `ValidatorError` carrying a structured `errors: { path, message }[]` array rather than throwing on the first failure. Supports `.optional()`, `.default()`, `.transform()`, `.refine()`, custom error messages, and `.sanitize()` — which composes with `utils/sanitize.ts`'s pure transformation functions (trim, escape, clamp, etc.) and runs _before_ validation rules are checked, so sanitization can bring otherwise-invalid input into a valid shape (e.g., trimming whitespace before a `.min(3)` check).
|
|
100
|
+
|
|
101
|
+
Test coverage here is dense and correctness-focused (string/number/boolean/object/array/enum/literal/union/date/record validators, nested objects, multi-error collection, async parsing) — this is one of the most solidly verified subsystems in the framework.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 8. Production Build Pipeline
|
|
106
|
+
|
|
107
|
+
### 8a. Compilation 🟢
|
|
108
|
+
|
|
109
|
+
`exis build` uses **esbuild** to compile TypeScript to JavaScript (`cli/commands/build.ts`), dropping the slow `tsc` dependency for the actual transpilation step, then rewrites path aliases (`@/*` → relative imports) via a native regex-based resolver (`resolve-aliases.ts`) so the output runs without needing a module-alias runtime.
|
|
110
|
+
|
|
111
|
+
### 8b. Route Manifest Generation 🟢
|
|
112
|
+
|
|
113
|
+
`cli/manifest.ts` scans `route.ts` files post-compile and writes `.exis/routes-manifest.js` (a flat list of `{ routePath, module, filePath, hash }`) plus a generated `.exis/types.d.ts` for the tRPC-style client (§17). This manifest is fully wired into the boot path: in production, `route-scanner.ts` detects `.exis/routes-manifest.js`, reads it, dynamically imports the required modules, and completely bypasses the recursive filesystem scan, delivering true O(1) boot performance regardless of route count.
|
|
114
|
+
|
|
115
|
+
### 8c. Production Optimizers (AOT Routing & JIT Serialization) 🟢
|
|
116
|
+
|
|
117
|
+
The framework achieves production optimizations directly within its core engine rather than relying on brittle post-processing build scripts:
|
|
118
|
+
|
|
119
|
+
- **AOT Routing (O(1) Boot Time)** — During `exis build`, `manifest.ts` generates a `.exis/routes-manifest.js` file. In production, `RouteScanner` detects this manifest and uses it to load routes instantly, skipping the expensive `fs.readdir` recursive scan entirely.
|
|
120
|
+
- **JIT Fast JSON Serialization** — During route registration at boot time, the `Router` checks if `fast-json-stringify` is installed. If a route has an ExisJS `v` response schema, the router automatically converts it to JSON Schema via `.toOpenApi()` and compiles an ultra-fast serializer Just-In-Time, attaching it to the route's response handler (`res._serializer`). This eliminates the need to precompile serializers to disk.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 9. Native Job Queues 🟢 (core queue & cron integration)
|
|
125
|
+
|
|
126
|
+
**What it is:** A background job queue with pluggable drivers, retry/backoff, visibility timeouts, and a worker-thread execution model.
|
|
127
|
+
|
|
128
|
+
**How enqueue/poll/ack/fail work (both drivers share this contract, `queue/types.ts` `QueueDriver`):**
|
|
129
|
+
|
|
130
|
+
- **Redis driver** (`queue/drivers/RedisDriver.ts`): pending jobs live in a Redis **sorted set** (`{prefix}:{name}:pending`, score = ready-at timestamp) with payloads stored separately in a **hash** (`{prefix}:{name}:payloads`). `enqueue()` uses a Lua script for atomic `ZADD` + `HSET` (with an optional max-queue-size check baked into the same script for backpressure). `poll()` atomically pops the earliest-ready job via `ZRANGEBYSCORE` + moves it to a `processing` zset with a visibility-timeout score, using another Lua script. `sweep()` periodically requeues anything left in `processing` past its visibility timeout (crash recovery for workers that died mid-job).
|
|
131
|
+
- **Memory driver** (`queue/drivers/MemoryDriver.ts`): the same conceptual model (pending list, processing list, sweep-based recovery) implemented with in-process arrays instead of Redis — correctly mirrors the Redis driver's semantics for single-process/dev use.
|
|
132
|
+
- **Job execution:** `ExisWorker` (`queue/worker.ts`) polls continuously (`setImmediate`-chained, with backoff on connection errors), and for jobs with a `filePath` (rather than an inline `handler`), dispatches execution to a `ThreadPool` (`threads/pool.ts`) — a zero-dependency `worker_threads` pool — so CPU-heavy job handlers don't block the main event loop.
|
|
133
|
+
|
|
134
|
+
**Retry behavior:** on failure, `attemptsMade` increments and the job is requeued (with optional exponential/fixed backoff) if under `maxAttempts`; once attempts are exhausted, the job is moved to a dead-letter queue (`{prefix}:{name}:dead` in Redis, or a `deadLetter` list in Memory) and an `onJobFailedPermanently` hook is fired, ensuring no data is lost for permanently failed jobs.
|
|
135
|
+
|
|
136
|
+
**Cron scheduling — 🟢 correctly integrated with the Redis driver:** `CronScheduler` (`cron/scheduler.ts`) uses its own native cron-expression parser (`cron/parser.ts`, supports lists/steps/ranges/wildcards) and ticks every 60 seconds, aligned to the minute boundary. To guarantee exactly-once execution across a multi-instance cluster, it acquires a `SETNX`-style Redis lock (`SET key val EX 55 NX`) per job per minute — this locking mechanism is correctly designed. Once the lock is acquired, the scheduler enqueues the triggered job payload via the exact same `ZADD` + `HSET` sequence the `RedisQueueDriver` expects, ensuring cron jobs are processed identically to manually enqueued jobs.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 10. WebSockets & Pub/Sub 🟢
|
|
141
|
+
|
|
142
|
+
**What it is:** Native WebSocket routes (`route.ws()` / `@Ws()`) with Socket.io-style room subscriptions, sitting behind the same middleware/auth pipeline as HTTP routes.
|
|
143
|
+
|
|
144
|
+
**How it's implemented:**
|
|
145
|
+
|
|
146
|
+
- `ExisWebSocketServer` (`websocket/server.ts`) tracks all active connections in a `Set` and room membership in a `Map<room, Set<socket>>`, with automatic room cleanup when a room empties and a 30-second `.unref()`'d ping/pong heartbeat that terminates dead connections.
|
|
147
|
+
- `ExisWebSocket` (`websocket/socket.ts`) wraps the raw socket with `.subscribe()`/`.publish()`/`.emit()`/`.to()`/`.broadcast`, JSON auto-serialization, and an `emitWithAck()` request/response pattern with timeout.
|
|
148
|
+
- **Upgrade handling on the Node backend** (`ws-orchestrator.ts`): builds a synthetic `ServerResponse` so the normal middleware pipeline (including auth) can run _before_ the actual protocol upgrade happens. If middleware sends a response (e.g., 401), the socket is destroyed instead of upgraded.
|
|
149
|
+
- **Upgrade handling on the uWebSockets.js backend** (`ws-orchestrator.ts`'s `handleUwsUpgrade` + `uws-adapter.ts`'s `open()` callback): functionally equivalent. The request shim correctly captures all synchronously-available request data (including `sec-websocket-*` headers), safely defers the `res.upgrade()` call until after asynchronous middlewares complete (using `onAborted` to catch dropped connections), and passes the required context into the `open()` callback seamlessly.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 11. Server-Sent Events (SSE) 🟢
|
|
154
|
+
|
|
155
|
+
**What it is:** One-way server-to-client streaming (`route.sse()` / `@Sse()`), useful for LLM token streaming and other push-style use cases.
|
|
156
|
+
|
|
157
|
+
**How it's implemented:** `ExisSSE` (`server/sse.ts`) sets the standard `text/event-stream` headers plus `X-Accel-Buffering: no` (to prevent reverse-proxy buffering from defeating streaming), tracks connection state via the response's `close` event, and exposes `.send(data, eventName?)` which formats either plain strings or JSON-serialized objects per the SSE wire format. Full middleware support is retained, so auth/rate-limiting apply the same as any other route.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 12. Intelligent Hot-Reloading 🟢
|
|
162
|
+
|
|
163
|
+
**What it is:** File-change-triggered reloading that patches only the affected routes rather than restarting the whole process, preserving WebSocket connections and in-memory state.
|
|
164
|
+
|
|
165
|
+
**How it's implemented:** `dep-graph.ts` builds a dependency graph by regex-scanning `import`/`export`/`require` statements in route files (resolving both relative imports and `@/` aliases) so that changing a `service.ts` file can be traced back to the `route.ts` files that depend on it. `hot-reload.ts`'s `HotReloader` watches via `chokidar`, and on a relevant change: removes the stale route(s) from the router, busts the CommonJS `require.cache` entry, re-imports the file, and re-mounts it — all without touching unrelated routes or dropping active connections. `dev-error-overlay.ts` provides a readable terminal error format (parsed stack trace + syntax-highlighted code frame) when a hot-reloaded file fails to import.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 13. uWebSockets.js Adapter 🟢
|
|
170
|
+
|
|
171
|
+
**What it is:** An optional high-throughput backend that swaps Node's native HTTP server for `uWebSockets.js`, auto-detected if installed (or forced via `server: 'uws'` config).
|
|
172
|
+
|
|
173
|
+
**How it's implemented:** `uws-adapter.ts` provides `UwsIncomingMessage`/`UwsServerResponse` shims that emulate the subset of Node's `IncomingMessage`/`ServerResponse` interface the rest of the framework depends on. Response writes use `res.cork()` to batch header/status/body into a single write. The shim correctly emulates asynchronous Node stream semantics (e.g., buffered `data`/`end` events delivered via microtasks for late listener registration), making it a robust and high-performance drop-in replacement.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 14. Request & Response Engine 🟢
|
|
178
|
+
|
|
179
|
+
**What it is:** `ExisRequest`/`ExisResponse` wrapper classes providing a fuller feature set than raw Node `IncomingMessage`/`ServerResponse`.
|
|
180
|
+
|
|
181
|
+
**Request (`server/request.ts`):**
|
|
182
|
+
|
|
183
|
+
- Multipart parsing via `busboy`, query parsing via `fast-querystring`.
|
|
184
|
+
- `trustProxy` logic (boolean, or numeric hop-count) correctly resolves `x-forwarded-for`/`x-forwarded-proto`/`x-forwarded-host` — this has explicit, well-covered test cases for the `false`/`true`/numeric-hop variants.
|
|
185
|
+
- Body-size limiting with a clear rejection error when exceeded.
|
|
186
|
+
- Per-request `Dataloader` instances (see §16) accessible via `req.dataloader(name)`.
|
|
187
|
+
|
|
188
|
+
**Response (`server/response.ts`):**
|
|
189
|
+
|
|
190
|
+
- Chainable `status()`/`set()`/`header()`/`cookie()` API.
|
|
191
|
+
- Automatic ETag generation (SHA1-based, weak ETags) plus `304 Not Modified` short-circuiting when the request is fresh.
|
|
192
|
+
- `.download()`, `.html()`, `.sendStream()`, `.redirect()` convenience methods.
|
|
193
|
+
- A subtlety worth knowing: **if you inject the raw response object to set custom headers but still return a value from the handler, ExisJS still auto-serializes the returned value to JSON** — unlike frameworks where injecting the raw response disables auto-serialization and silently hangs the request if you forget to call `.send()`.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 15. Global Error Handling 🟢
|
|
198
|
+
|
|
199
|
+
**What it is:** A structured `HttpError` hierarchy plus a global handler that formats operational errors consistently and masks internals in production.
|
|
200
|
+
|
|
201
|
+
**How it's implemented:** `HttpError` (`utils/errors.ts`) carries `statusCode`/`code`/`details`/`isOperational`, with static factories (`badRequest`, `unauthorized`, `notFound`, etc.) and matching subclasses/aliases for both functional and exception-style usage. `createErrorHandler(isDev)` recognizes `HttpError` instances, Zod-shaped and native `ValidatorError` shapes, and JSON `SyntaxError`s, formatting each appropriately; unrecognized errors are logged and returned as a generic message in production, or the full message + stack in development. A dev-mode HTML error overlay is available when the client accepts `text/html`.
|
|
202
|
+
|
|
203
|
+
**Exception filters:** `catchError(errorClass, handler)` (`middleware/exception-filter.ts`) provides a functional equivalent to `@Catch()` — a 4-argument middleware that only handles errors matching `instanceof errorClass`, falling through to the next handler otherwise. This composes cleanly with `HttpError`'s subclasses for granular per-error-type handling.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 16. Dataloaders 🟢
|
|
208
|
+
|
|
209
|
+
**What it is:** GraphQL-style per-request batching and caching to solve the N+1 query problem.
|
|
210
|
+
|
|
211
|
+
**How it's implemented:** `Dataloader` (`dataloader/dataloader.ts`) collects `.load(key)` calls into a queue and dispatches a single batched call to the developer's `batchFn` on `process.nextTick`, splitting into multiple batches if `maxBatchSize` is exceeded. Supports per-instance caching (default on, keyed by identity or a custom `cacheKeyFn` for object keys — objects are `JSON.stringify`'d by default to avoid reference-equality cache misses), `.prime()` for pre-filling the cache, and per-key error rejection (a batch function can return a mix of values and `Error` instances, with each key resolved/rejected independently). Loaders are correctly isolated per request via the DI/context system — verified by tests asserting two concurrent requests using the same loader name get independent batches and caches.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 17. tRPC-style Frontend Type Client 🟢
|
|
216
|
+
|
|
217
|
+
**What it is:** End-to-end type safety between the ExisJS backend and a frontend, without a separate tRPC dependency.
|
|
218
|
+
|
|
219
|
+
**How it's implemented:** The manifest generator (§8b) produces `.exis/types.d.ts` exporting an `AppRouter` type. The `exisjs/client` package's `createClient<AppRouter>()` uses a recursive `Proxy` to translate property access chains (`client.api.users.get(...)`) into the corresponding HTTP request, inferring the payload/response shape from the generated types. This is a type-level feature (compile-time safety) layered on a runtime `fetch` call. The framework CLI's file watcher (`exis dev`) explicitly listens to all file events (`add`, `unlink`, `change`) to instantly regenerate the types manifest, ensuring your frontend types never silently fall out of sync with your backend routes.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 18. Authentication Suite (`exisjs/auth`) 🟢
|
|
224
|
+
|
|
225
|
+
**What it is:** JWT, password hashing, RBAC, and session primitives built on Node's native `crypto`, with no third-party cryptography dependency.
|
|
226
|
+
|
|
227
|
+
- **JWT (`auth/jwt.ts`):** HS256 via `crypto.createHmac`. Verification checks buffer length before calling `timingSafeEqual` (avoiding the common length-mismatch-throws footgun), and distinguishes `TokenExpiredError` from a generic invalid-signature `HttpError` — useful for clients that want to auto-refresh on expiry specifically.
|
|
228
|
+
- **Passwords (`auth/password.ts`):** `crypto.scrypt`, promisified. Hashes are stored with their cost parameters embedded inline (`scrypt:N:r:p:keylen:salt:hash`), so cost factors can be increased later without invalidating existing hashes. Backwards-compatible with a legacy 2-part `salt:hash` format.
|
|
229
|
+
- **RBAC (`auth/rbac.ts`):** `requireRole(roles)` checks array intersection between `req.user.role` (string or array) and the allowed list, throwing `401` if unauthenticated or `403` if authenticated but unauthorized.
|
|
230
|
+
- **Sessions (`auth/session.ts`):** HMAC-signed, HttpOnly cookies (`timingSafeEqual`-verified) with a pluggable `SessionStore` (in-memory provided, Redis/DB left to the developer). Auto-saves session mutations back to the store by hooking `res.raw.end` and uses dirty-checking (`JSON.stringify(sessionData) !== initialState`) to ensure it only writes to the remote store when the session was actually mutated.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 19. Universal Edge & Serverless Adapters 🟢
|
|
235
|
+
|
|
236
|
+
**What it is:** A consistent `fetch`-based adapter pattern letting the same `App` run on Cloudflare Workers, Deno, Bun, Fastly Compute@Edge, Netlify Edge, Vercel, and AWS Lambda.
|
|
237
|
+
|
|
238
|
+
**How it's implemented:** All platform adapters (`adapters/*.ts`) follow the identical init-once-then-handle pattern: lazily call `app.create()`/`app.onStartHook()` on first invocation, then delegate to either `app.fetch(request, env, ctx)` (Web-standard platforms) or `app.handle(req, res)` (Node-stream platforms like Vercel). This consistency across seven adapters is a genuine strength — there's no divergence in behavior between them.
|
|
239
|
+
|
|
240
|
+
`adapters/fetch.ts`'s `FetchIncomingMessage`/`FetchServerResponse` polyfill is the shared foundation for all Web-standard-`Request`-based platforms: it emulates just enough of Node's stream/EventEmitter interface (including correctly flushing pre-buffered body bytes to late-registered `'data'`/`'end'` listeners, and correctly handling `.once()` since it delegates to the same `.on()` override) for the framework's existing middleware to run unmodified on Edge runtimes.
|
|
241
|
+
|
|
242
|
+
**AWS Lambda specifics (`adapters/aws-lambda.ts`):** automatically detects text-based payloads (JSON, HTML, plain text) and sends them as unencoded strings to save wire overhead, falling back to base64 encoding (`isBase64Encoded: true`) only for binary data (images, octet streams).
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 20. Security Middleware Suite
|
|
247
|
+
|
|
248
|
+
Split by actual strength, not lumped together as one "Advanced Security Suite":
|
|
249
|
+
|
|
250
|
+
### 20a. Solid, standard-pattern implementations 🟢
|
|
251
|
+
|
|
252
|
+
- **Helmet-equivalent headers** (`middleware/security.ts` `helmet()`) — standard static security headers (HSTS, X-Frame-Options, X-Content-Type-Options, etc.), configurable, well-tested.
|
|
253
|
+
- **CSRF** (`csrf({ secret })`) — Signed Double Submit Cookie pattern using HMAC SHA-256 to prevent Cookie Tossing attacks. Validated via header-vs-cookie match on state-changing methods.
|
|
254
|
+
- **HPP** (`hpp()`) — mitigates HTTP Parameter Pollution by forcing array-like query/body parameters down to their last element.
|
|
255
|
+
- **DB sanitize** (`dbSanitize()` / `mongoSanitize()`) — recursively strips keys starting with `$` or containing `.` from body/query/params to prevent NoSQL injection.
|
|
256
|
+
- **Timeout** (`timeout()`) — kills stalled requests and sends a 503 if response takes too long.
|
|
257
|
+
|
|
258
|
+
### 20b. Removed Features
|
|
259
|
+
|
|
260
|
+
- **`sqlSanitize()` / `dbSanitize({ sql: true })`** — Removed. This regex-based blocklist filtering was trivially bypassable and mangled legitimate content. Real protection comes from parameterized queries / ORM usage. See `REMOVED_FEATURES.md`.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 21. Observability
|
|
265
|
+
|
|
266
|
+
- **Health checks 🟢** (`observability/health.ts`) — runs an array of named async checks in parallel, each wrapped in a `Promise.race()` against a `.unref()`'d timeout so a hanging dependency check can't block the health endpoint itself; aggregates to `200 pass` / `503 fail`.
|
|
267
|
+
- **Metrics 🟢** (`observability/prometheus.ts`) — "bring your own metrics" adapter pattern; the middleware itself only measures duration and delegates recording to a developer-supplied adapter (prom-client, StatsD, custom), using `req.routePath` rather than the raw URL to avoid cardinality explosions on parameterized routes.
|
|
268
|
+
- **Tracing 🟢** (`observability/otel.ts`) — same "bring your own tracer" pattern for OpenTelemetry-shaped spans, correctly redacts sensitive headers (`authorization`, `cookie`, `x-api-key`, etc.) before they'd reach an external tracing backend, and maps 5xx responses to `SpanStatusCode.ERROR`.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 22. Zero-Config Third-Party Integrations 🟢
|
|
273
|
+
|
|
274
|
+
**What it is:** Lazy-loaded clients for 12 external services (Drizzle, JWT, MongoDB, Mongoose, OpenAI, PostgreSQL, PostHog, Prisma, Redis, Resend, S3, Supabase) that read connection config from conventional environment variables and dynamically `require()` the underlying SDK only when first accessed.
|
|
275
|
+
|
|
276
|
+
**How it's implemented:** Each integration (`integrations/*.ts`) exports a `Proxy`-wrapped singleton (e.g. `redis`, `s3`, `mongo`) whose `get` trap lazily constructs the real client on first property access, plus a `create*Client()` factory for explicit instantiation and a `configure*()` function to set options before first use. This means the framework's core bundle never imports these heavy SDKs directly, and connection setup is deferred until the exact moment it's needed — contributing to fast boot times. The test suite comprehensively validates both the missing-env-var failure modes and the successful client constructions (via mocked module injection) across the integrations.
|
|
277
|
+
|
|
278
|
+
**Important architectural caveat:** these are **process-wide module-level singletons**, not per-`App`-instance. Multiple `App` instances in one process (e.g. multi-tenant setups, or multiple test files each creating an app) will share the same underlying client unless the explicit `create*Client()` factory is used instead of the singleton proxy. Document this clearly for any usage beyond "one app, one process."
|
|
279
|
+
|
|
280
|
+
**Test coverage caveat:** the "throws when required env var is missing" path is tested for all 12 integrations; the "successfully constructs a client with valid config" path is currently only asserted for JWT — the equivalent Redis/S3 tests exist but are marked `.skip()`. Low-effort to complete since the mocking infrastructure is already built.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 23. CLI & Tooling 🟢
|
|
285
|
+
|
|
286
|
+
`exis dev` (esbuild/tsx-powered HMR via child process + chokidar watch, smart port-conflict messaging), `exis build` (§8), `exis start` (production boot), `exis routes` (colorized routing table), `exis test` (native `node:test` wrapper with a custom Jest-style reporter — see §24), `exis generate <type>` (scaffolding for routes/plugins/middleware/tests, functional or OOP), `exis init` / `create-exis` (interactive project scaffolding), `exis console`/`repl` (interactive REPL with auto-discovered model injection), `exis exports` (prints the subpath export map). This surface is broad but consistently implemented and reasonably well covered by both unit and e2e tests (`cli.test.ts` mocks `spawn`/`exit` for fast unit tests; `cli-e2e.test.ts` runs the compiled CLI as a real child process for a smaller set of true end-to-end checks).
|
|
287
|
+
|
|
288
|
+
**Cluster mode** (`server/cluster.ts`): `workers: 'safe' | 'max' | number` forks Node's native `cluster` module, with crash-loop detection (stops auto-respawning if more than `maxRespawns` crashes occur within `respawnWindow`) and graceful multi-worker shutdown on SIGINT/SIGTERM.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 24. Native Test Runner Integration 🟢
|
|
293
|
+
|
|
294
|
+
**What it is:** A zero-dependency test runner built on `node:test`, with a custom Jest/Vitest-style terminal reporter, a Jest-compatible `expect()` assertion library, and `createTestContext()` for booting a full `App` (DI container, database connections) inside tests without mocking.
|
|
295
|
+
|
|
296
|
+
**How it's implemented:** `testing/reporter.mts` consumes `node:test`'s async iterable of `TestEvent`s and renders grouped, colorized, file-scoped PASS/FAIL output with a Jest-style failure summary — genuinely nicer than raw TAP output. `testing/expect.ts` implements the common Jest matcher surface (`toBe`, `toEqual`, `toMatchObject`, `toHaveBeenCalledWith`, `resolves`/`rejects`, etc.) on top of `node:assert`. `createTestContext()` (`testing/index.ts`) wires `before`/`after` hooks that boot the app, and on teardown, correctly awaits and closes Mongoose, Redis, and Prisma connections if detected in use.
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
**Suite quality note:** the framework's own test suite (used to write this document) is dense and well-constructed across routing, request/response, DI, validator, dataloader, WebSockets (including negative-path auth tests), observability, and both queue drivers. Its main gaps are exactly the two areas this document flags as risk: no test crosses the cron↔queue integration boundary, and the dedupe/cache tests use a single logical client rather than simulating concurrent different users.
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## 25. Response Interceptors, Guards, and Pipes 🟢
|
|
304
|
+
|
|
305
|
+
- **`intercept(transform)`** (`middleware/interceptor.ts`) — monkey-patches `res.json` for the request duration to apply a transformation function to the outgoing payload, supporting both sync and async transforms. Deliberately does not intercept `res.send()`, since that's meant for raw string/buffer output.
|
|
306
|
+
- **`guard(canActivate, options?)`** (`middleware/guard.ts`) — evaluates a boolean (or async-boolean) condition and short-circuits with `403` if it returns false; the OOP equivalent (`@UseGuards`-style) resolves guard classes via the DI container if registered, or instantiates them directly otherwise.
|
|
307
|
+
- **`pipe(location, key, transformFn)`** (`middleware/pipe.ts`) — transforms a specific `body`/`query`/`params` key before the handler runs, catching thrown errors and responding `400` automatically.
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## 26. Caching, Deduplication, and Backpressure — read this section carefully
|
|
312
|
+
|
|
313
|
+
These three middlewares are implemented correctly for their _intended_ single-client use case, and are dangerous with default settings outside it. Do not present them as safe defaults without qualification.
|
|
314
|
+
|
|
315
|
+
### 26a. Tag-based Cache Stores 🟢
|
|
316
|
+
|
|
317
|
+
`FileSystemCacheStore`, `MemoryCacheStore`, `RedisCacheStore` (`cache/store.ts`) all implement the same `CacheStore` interface: entries carry an array of string `tags`, and a separate tag→`lastRevalidated` timestamp map is checked on every `get()` — if any of an entry's tags were revalidated after the entry was created, it's treated as a miss. `revalidateTag(tag)` instantly invalidates every entry carrying that tag, Next.js-style. **`MemoryCacheStore` automatically synchronizes invalidations across cluster workers** — when running in `workers > 1` mode, it uses IPC to broadcast tag invalidations to all sibling workers so the memory cache remains safely consistent across the cluster (without requiring Redis).
|
|
318
|
+
|
|
319
|
+
### 26b. `cacheMiddleware` 🟢
|
|
320
|
+
|
|
321
|
+
Requires a mandatory `keyGenerator` function (e.g., `(req) => req.user?.id + ':' + req.path`) to ensure cache segregation across users/sessions on authenticated routes. Attempting to use this middleware without providing a `keyGenerator` throws a startup error, preventing accidental cross-user data leaks.
|
|
322
|
+
|
|
323
|
+
### 26c. `dedupeMiddleware` 🟢
|
|
324
|
+
|
|
325
|
+
Also requires a mandatory `keyGenerator` to prevent cross-user broadcasting of in-flight requests. Two different authenticated users requesting the same path within the same in-flight window are correctly segregated if the key includes their identity.
|
|
326
|
+
|
|
327
|
+
### 26d. Backpressure Engine 🟢
|
|
328
|
+
|
|
329
|
+
`backpressureMiddleware` (`middleware/backpressure.ts`) tracks `activeCount` against `maxConcurrent`, queues overflow up to `maxQueue` with a timeout, and returns `503` once the queue itself is full — correctly implemented and tested, no identity-related caveats since it doesn't cache or share response data between requests.
|
|
330
|
+
|
|
331
|
+
### 26e. Circuit Breaker 🟢
|
|
332
|
+
|
|
333
|
+
`CircuitBreaker` (`utils/circuit-breaker.ts`) implements a standard `CLOSED`/`OPEN`/`HALF_OPEN` state machine with a single-probe half-open test — correctly implemented and tested, including the full open→half-open→closed recovery cycle.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 27. Standardized JSON Responses 🟢
|
|
338
|
+
|
|
339
|
+
`response/index.ts` exports `success(data, message?)` / `error(message, code?, details?)` helper functions producing a consistent `{ success: boolean, data?, error? }` envelope shape — a convention, not an enforced contract; handlers can return anything and it will still be auto-serialized, but using these helpers keeps API responses consistent across a codebase.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Summary: what to actually rely on today
|
|
344
|
+
|
|
345
|
+
| Category | Subsystems |
|
|
346
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
347
|
+
| **Rely on freely** | Routing, controllers/decorators, DI, context API, validation, request/response, error handling, dataloaders, gateways/modules/plugins, hot reload, CLI, WebSockets (Node backend), uWebSockets.js backend, SSE, auth primitives (JWT/password/RBAC/session), backpressure, circuit breaker, health/metrics/tracing, cacheMiddleware, dedupeMiddleware, cron, production optimizers (AOT/JIT), edge/serverless adapters, tRPC-style client |
|
|
348
|
+
| **Use, but read the caveat first** | Cache stores (mind multi-server horizontal scaling with `MemoryCacheStore`), zero-config integrations (mind singleton scope) |
|
|
349
|
+
This table should be kept current as fixes land — moving a row from the middle to the top category is exactly the kind of visible progress worth calling out in a changelog.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Benchmark Results
|
|
2
|
+
|
|
3
|
+
### System Information
|
|
4
|
+
- **OS:** win32 (x64)
|
|
5
|
+
- **CPU:** 11th Gen Intel(R) Core(TM) i5-11400H @ 2.70GHz (12 cores)
|
|
6
|
+
- **RAM:** 16 GB
|
|
7
|
+
- **Node:** v26.3.0
|
|
8
|
+
|
|
9
|
+
### Test Configuration
|
|
10
|
+
- **Tool:** autocannon unknown
|
|
11
|
+
- **Connections:** 100, **Duration:** 10s, **Pipelining:** 1 (no pipelining)
|
|
12
|
+
- **Trials:** 3 per framework, order randomized each trial, median reported
|
|
13
|
+
- **Warmup:** 2s untimed warmup run before each timed trial
|
|
14
|
+
- **Cores:** single-core, no clustering, for all four frameworks
|
|
15
|
+
- **Framework versions:** Express ^5.2.1, Fastify ^5.10.0, Hono ^4.12.32, ExisJS 0.5.0
|
|
16
|
+
|
|
17
|
+
### Performance Metrics
|
|
18
|
+
|
|
19
|
+
| Framework | Req/Sec (median) | Latency avg (ms) | Latency p50 (ms) | Latency p99 (ms) | Errors | Server backend |
|
|
20
|
+
|---|---|---|---|---|---|---|
|
|
21
|
+
| Express | 20036.80 | 4.43 | 4.00 | 7.00 | 0 | n/a |
|
|
22
|
+
| Fastify | 32799.28 | 2.53 | 2.00 | 4.00 | 0 | n/a |
|
|
23
|
+
| Hono | 34264.00 | 2.33 | 2.00 | 4.00 | 0 | n/a |
|
|
24
|
+
| Exis JS | 29128.00 | 3.04 | 3.00 | 5.00 | 0 | node |
|
|
25
|
+
|
|
26
|
+
### Handler Parity Note
|
|
27
|
+
|
|
28
|
+
ExisJS's handler in this benchmark declares a `response` JSON schema, which the framework uses to validate and serialize the response. Express, Fastify, and Hono's handlers here do not declare an equivalent schema. This makes ExisJS's numbers more notable if it still leads, since it's doing strictly more work per request — but it means this is not a pure routing-only comparison. A schema-stripped variant should be run separately if a fully symmetric comparison is needed.
|
|
29
|
+
|
|
30
|
+
### Understanding the Results
|
|
31
|
+
|
|
32
|
+
You may notice that in our microbenchmarks (routing only), ExisJS's Radix Tree performs route lookups in ~3ns compared to Fastify's ~130ns (a ~40x difference). However, in the full HTTP throughput benchmark above, the gap narrows substantially.
|
|
33
|
+
|
|
34
|
+
**Why?** Route lookup is only a microscopic fraction of a full HTTP request lifecycle. The vast majority of time is spent on Node.js I/O, socket management, header parsing, and JSON serialization. While our router is objectively faster in isolation, overall framework throughput is bounded by the same underlying Node.js stream primitives every framework here is built on.
|
|
35
|
+
|
|
36
|
+
> *Note: These benchmarks were run locally on Windows, single-core, no clustering. For production-representative numbers, they should be reproduced on isolated Linux server instances with clustering configuration matching your actual deployment.*
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/v25group/exisjs/main/.github/assets/exisjs.png" height="200" alt="Exis JS Logo" />
|
|
3
|
+
<h1 align="center">Exis JS Documentation</h1>
|
|
4
|
+
</p>
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
7
|
+
<b>Enterprise ambition with zero-config simplicity. The ultimate batteries-included Node.js backend.</b>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a aria-label="NPM version" href="https://www.npmjs.com/package/exisjs">
|
|
12
|
+
<img alt="" src="https://img.shields.io/npm/v/exisjs.svg?style=for-the-badge&labelColor=000000&color=000000">
|
|
13
|
+
</a>
|
|
14
|
+
<a aria-label="License" href="https://github.com/v25group/exisjs/blob/main/LICENSE">
|
|
15
|
+
<img alt="" src="https://img.shields.io/npm/l/exisjs.svg?style=for-the-badge&labelColor=000000&color=000000">
|
|
16
|
+
</a>
|
|
17
|
+
<a aria-label="Join the community on GitHub" href="https://github.com/v25group/exisjs/discussions">
|
|
18
|
+
<img alt="" src="https://img.shields.io/badge/Join%20the%20community-on%20GitHub-black.svg?style=for-the-badge&logo=github&labelColor=000000&color=000000&logoWidth=20">
|
|
19
|
+
</a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
Welcome to the Exis JS documentation! Exis JS is a powerful, folder-based routing framework designed for building scalable and type-safe backend APIs with ease. It features out-of-the-box RPC client generation, modular route organization, and intuitive middleware/gateway configurations.
|
|
23
|
+
|
|
24
|
+
## Documentation Structure
|
|
25
|
+
|
|
26
|
+
This documentation is split into the following main sections:
|
|
27
|
+
|
|
28
|
+
- **[Content](./content/)**: Core concepts, tutorials, and deep-dives into building applications with Exis JS. Learn about folder-based routing, setting up your `gateway.ts`, and leveraging the RPC client.
|
|
29
|
+
- **[Integrations](./integrations/)**: Guides on how to integrate Exis JS with external services and libraries.
|
|
30
|
+
- **[Architecture](./ARCHITECTURE.md)**: Catalog of features supported by Exis JS and their internal implementation details.
|
|
31
|
+
|
|
32
|
+
*(Note: Detailed API Reference will be added at a later time)*
|
|
33
|
+
|
|
34
|
+
Dive into the respective folders to get started!
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# ExisJS Documentation Blueprint
|
|
2
|
+
|
|
3
|
+
This directory contains the core documentation for ExisJS. This `README.md` acts as a **blueprint** and index to help you navigate the features and understand what each markdown file contains.
|
|
4
|
+
|
|
5
|
+
## 🚀 Getting Started
|
|
6
|
+
* **`introduction.mdx`**: High-level overview of ExisJS, its philosophy, and why it exists.
|
|
7
|
+
* **`installation.mdx`**: How to create a new project using `create-exis` and basic setup.
|
|
8
|
+
* **`structure.mdx`**: Explains the recommended folder structure (e.g., `src/http`, `src/jobs`, `src/services`).
|
|
9
|
+
* **`cli.mdx`**: Documentation for the Exis CLI (`exis dev`, `exis build`), including Esbuild integration and Smart Port Detection.
|
|
10
|
+
* **`config.mdx`**: How to configure the framework using `exis.config.ts`.
|
|
11
|
+
|
|
12
|
+
## 🧠 Core Routing & HTTP
|
|
13
|
+
* **`routing.mdx`**: Covers File-System Routing, exact matches, parameters, and wildcards using the Radix Tree.
|
|
14
|
+
* **`controllers.mdx`**: Using OOP class-based controllers (`@Controller`) and Functional controllers.
|
|
15
|
+
* **`gateways.mdx`**: Explains Topological folder-based middleware (e.g., `_gateway.ts`) that runs before routes in a directory.
|
|
16
|
+
* **`middleware.mdx`**: Global and route-level middleware, CORS, and `req.log` interception.
|
|
17
|
+
* **`requests.mdx`**: How to read query params, body data, headers, and use the global `req` object.
|
|
18
|
+
* **`responses.mdx`**: Sending JSON, streams, raw data, and manipulating HTTP status codes.
|
|
19
|
+
|
|
20
|
+
## 🏗️ Architecture & DI
|
|
21
|
+
* **`modules.mdx`**: Grouping controllers and providers into modular chunks.
|
|
22
|
+
* **`providers.mdx`**: Dependency Injection (DI) system, singletons, and injecting services.
|
|
23
|
+
* **`decorators.mdx`**: Overview of all decorators available for OOP paradigms (e.g., `@Get`, `@Inject`).
|
|
24
|
+
|
|
25
|
+
## 🛡️ Data & Validation
|
|
26
|
+
* **`validation.mdx`**: The Zod-like type-safe validation engine (`v`), strict parsing, and the standalone `sanitize` utilities.
|
|
27
|
+
* **`dataloader.mdx`**: Solving the N+1 database problem automatically using integrated data loaders.
|
|
28
|
+
* **`cache.mdx`**: Application-level caching, response caching, and invalidation strategies.
|
|
29
|
+
|
|
30
|
+
## ⚡ Advanced Capabilities
|
|
31
|
+
* **`websockets.mdx`**: Real-time bidirectional communication using the native uWebSockets integration.
|
|
32
|
+
* **`sse.mdx`**: Server-Sent Events for one-way streaming (useful for AI text generation).
|
|
33
|
+
* **`queues.mdx`**: Background job processing, workers, and Redis-backed queues.
|
|
34
|
+
* **`cron.mdx`**: Scheduling recurring tasks effortlessly using `cron` syntax.
|
|
35
|
+
* **`rpc.mdx`**: End-to-end type safety connecting the ExisJS backend with an `@exisjs/client` frontend.
|
|
36
|
+
|
|
37
|
+
## 🔒 Security & Reliability
|
|
38
|
+
* **`security.mdx`**: Helmet integration, Rate Limiting, CORS configuration, and best practices.
|
|
39
|
+
* **`errors.mdx`**: Global error handling, custom `HttpException` classes, and safe error masking in production.
|
|
40
|
+
* **`circuit-breaker.mdx`**: Preventing cascading failures when external APIs or databases go down.
|
|
41
|
+
* **`testing.mdx`**: Using the integrated, native `node:test` runner to test your endpoints without Jest.
|
|
42
|
+
|
|
43
|
+
## 🔌 Ecosystem
|
|
44
|
+
* **`adapters.mdx`**: How to run ExisJS inside serverless environments or mount it seamlessly alongside Next.js.
|