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.
Files changed (213) hide show
  1. package/dist/auth/index.d.ts +3 -1
  2. package/dist/auth/index.d.ts.map +1 -1
  3. package/dist/auth/index.js +4 -1
  4. package/dist/auth/index.js.map +1 -1
  5. package/dist/auth/oauth/core.d.ts +96 -0
  6. package/dist/auth/oauth/core.d.ts.map +1 -0
  7. package/dist/auth/oauth/core.js +203 -0
  8. package/dist/auth/oauth/core.js.map +1 -0
  9. package/dist/auth/oauth/index.d.ts +45 -0
  10. package/dist/auth/oauth/index.d.ts.map +1 -0
  11. package/dist/auth/oauth/index.js +64 -0
  12. package/dist/auth/oauth/index.js.map +1 -0
  13. package/dist/auth/oauth/middleware.d.ts +63 -0
  14. package/dist/auth/oauth/middleware.d.ts.map +1 -0
  15. package/dist/auth/oauth/middleware.js +164 -0
  16. package/dist/auth/oauth/middleware.js.map +1 -0
  17. package/dist/auth/oauth/providers/custom.d.ts +41 -0
  18. package/dist/auth/oauth/providers/custom.d.ts.map +1 -0
  19. package/dist/auth/oauth/providers/custom.js +204 -0
  20. package/dist/auth/oauth/providers/custom.js.map +1 -0
  21. package/dist/auth/oauth/providers/discord.d.ts +23 -0
  22. package/dist/auth/oauth/providers/discord.d.ts.map +1 -0
  23. package/dist/auth/oauth/providers/discord.js +130 -0
  24. package/dist/auth/oauth/providers/discord.js.map +1 -0
  25. package/dist/auth/oauth/providers/facebook.d.ts +28 -0
  26. package/dist/auth/oauth/providers/facebook.d.ts.map +1 -0
  27. package/dist/auth/oauth/providers/facebook.js +124 -0
  28. package/dist/auth/oauth/providers/facebook.js.map +1 -0
  29. package/dist/auth/oauth/providers/github.d.ts +25 -0
  30. package/dist/auth/oauth/providers/github.d.ts.map +1 -0
  31. package/dist/auth/oauth/providers/github.js +132 -0
  32. package/dist/auth/oauth/providers/github.js.map +1 -0
  33. package/dist/auth/oauth/providers/google.d.ts +23 -0
  34. package/dist/auth/oauth/providers/google.d.ts.map +1 -0
  35. package/dist/auth/oauth/providers/google.js +134 -0
  36. package/dist/auth/oauth/providers/google.js.map +1 -0
  37. package/dist/auth/oauth/providers/microsoft.d.ts +28 -0
  38. package/dist/auth/oauth/providers/microsoft.d.ts.map +1 -0
  39. package/dist/auth/oauth/providers/microsoft.js +136 -0
  40. package/dist/auth/oauth/providers/microsoft.js.map +1 -0
  41. package/dist/auth/oauth/types.d.ts +158 -0
  42. package/dist/auth/oauth/types.d.ts.map +1 -0
  43. package/dist/auth/oauth/types.js +4 -0
  44. package/dist/auth/oauth/types.js.map +1 -0
  45. package/dist/auth/session.d.ts +4 -1
  46. package/dist/auth/session.d.ts.map +1 -1
  47. package/dist/auth/session.js +33 -5
  48. package/dist/auth/session.js.map +1 -1
  49. package/dist/cache/store.d.ts +11 -3
  50. package/dist/cache/store.d.ts.map +1 -1
  51. package/dist/cache/store.js +87 -19
  52. package/dist/cache/store.js.map +1 -1
  53. package/dist/cli/commands/dev.d.ts.map +1 -1
  54. package/dist/cli/commands/dev.js +4 -6
  55. package/dist/cli/commands/dev.js.map +1 -1
  56. package/dist/database/adapters/mongodb.d.ts +58 -0
  57. package/dist/database/adapters/mongodb.d.ts.map +1 -0
  58. package/dist/database/adapters/mongodb.js +264 -0
  59. package/dist/database/adapters/mongodb.js.map +1 -0
  60. package/dist/database/adapters/mysql.d.ts +27 -0
  61. package/dist/database/adapters/mysql.d.ts.map +1 -0
  62. package/dist/database/adapters/mysql.js +188 -0
  63. package/dist/database/adapters/mysql.js.map +1 -0
  64. package/dist/database/adapters/postgres.d.ts +28 -0
  65. package/dist/database/adapters/postgres.d.ts.map +1 -0
  66. package/dist/database/adapters/postgres.js +174 -0
  67. package/dist/database/adapters/postgres.js.map +1 -0
  68. package/dist/database/adapters/sqlite.d.ts +27 -0
  69. package/dist/database/adapters/sqlite.d.ts.map +1 -0
  70. package/dist/database/adapters/sqlite.js +149 -0
  71. package/dist/database/adapters/sqlite.js.map +1 -0
  72. package/dist/database/index.d.ts +42 -0
  73. package/dist/database/index.d.ts.map +1 -0
  74. package/dist/database/index.js +58 -0
  75. package/dist/database/index.js.map +1 -0
  76. package/dist/database/manager.d.ts +114 -0
  77. package/dist/database/manager.d.ts.map +1 -0
  78. package/dist/database/manager.js +269 -0
  79. package/dist/database/manager.js.map +1 -0
  80. package/dist/database/migration.d.ts +57 -0
  81. package/dist/database/migration.d.ts.map +1 -0
  82. package/dist/database/migration.js +329 -0
  83. package/dist/database/migration.js.map +1 -0
  84. package/dist/database/query-builder.d.ts +89 -0
  85. package/dist/database/query-builder.d.ts.map +1 -0
  86. package/dist/database/query-builder.js +353 -0
  87. package/dist/database/query-builder.js.map +1 -0
  88. package/dist/database/types.d.ts +184 -0
  89. package/dist/database/types.d.ts.map +1 -0
  90. package/dist/database/types.js +4 -0
  91. package/dist/database/types.js.map +1 -0
  92. package/dist/docs/ARCHITECTURE.md +349 -0
  93. package/dist/docs/BENCHMARKS.md +36 -0
  94. package/dist/docs/README.md +34 -0
  95. package/dist/docs/content/README.md +44 -0
  96. package/dist/docs/content/adapters.mdx +199 -0
  97. package/dist/docs/content/cache.mdx +239 -0
  98. package/dist/docs/content/circuit-breaker.mdx +130 -0
  99. package/dist/docs/content/cli/commands.mdx +214 -0
  100. package/dist/docs/content/cli/overview.mdx +92 -0
  101. package/dist/docs/content/cli/usage.mdx +61 -0
  102. package/dist/docs/content/config.mdx +214 -0
  103. package/dist/docs/content/controllers.mdx +241 -0
  104. package/dist/docs/content/cron.mdx +109 -0
  105. package/dist/docs/content/database.mdx +209 -0
  106. package/dist/docs/content/dataloader.mdx +170 -0
  107. package/dist/docs/content/decorators.mdx +204 -0
  108. package/dist/docs/content/errors.mdx +344 -0
  109. package/dist/docs/content/gateways.mdx +376 -0
  110. package/dist/docs/content/installation.mdx +210 -0
  111. package/dist/docs/content/introduction.mdx +96 -0
  112. package/dist/docs/content/middleware.mdx +441 -0
  113. package/dist/docs/content/modules.mdx +144 -0
  114. package/dist/docs/content/oauth.mdx +179 -0
  115. package/dist/docs/content/providers.mdx +147 -0
  116. package/dist/docs/content/queues.mdx +177 -0
  117. package/dist/docs/content/requests.mdx +293 -0
  118. package/dist/docs/content/responses.mdx +271 -0
  119. package/dist/docs/content/routing.mdx +541 -0
  120. package/dist/docs/content/rpc.mdx +146 -0
  121. package/dist/docs/content/security.mdx +294 -0
  122. package/dist/docs/content/sse.mdx +159 -0
  123. package/dist/docs/content/structure.mdx +116 -0
  124. package/dist/docs/content/testing.mdx +139 -0
  125. package/dist/docs/content/validation.mdx +249 -0
  126. package/dist/docs/content/websockets.mdx +289 -0
  127. package/dist/docs/integrations/README.md +23 -0
  128. package/dist/docs/integrations/jwt.mdx +151 -0
  129. package/dist/docs/integrations/mongo.mdx +183 -0
  130. package/dist/docs/integrations/observability.mdx +125 -0
  131. package/dist/docs/integrations/openai.mdx +108 -0
  132. package/dist/docs/integrations/postgres.mdx +190 -0
  133. package/dist/docs/integrations/posthog.mdx +122 -0
  134. package/dist/docs/integrations/redis.mdx +141 -0
  135. package/dist/docs/integrations/resend.mdx +117 -0
  136. package/dist/docs/integrations/s3.mdx +122 -0
  137. package/dist/docs/integrations/swagger.mdx +113 -0
  138. package/dist/docs/reference/README.md +3 -0
  139. package/dist/docs/reference/adapters.mdx +130 -0
  140. package/dist/docs/reference/auth.mdx +183 -0
  141. package/dist/docs/reference/cache.mdx +132 -0
  142. package/dist/docs/reference/circuit-breaker.mdx +71 -0
  143. package/dist/docs/reference/config.mdx +118 -0
  144. package/dist/docs/reference/context.mdx +168 -0
  145. package/dist/docs/reference/dataloader.mdx +91 -0
  146. package/dist/docs/reference/decorators.mdx +293 -0
  147. package/dist/docs/reference/di.mdx +123 -0
  148. package/dist/docs/reference/error.mdx +170 -0
  149. package/dist/docs/reference/exis.mdx +124 -0
  150. package/dist/docs/reference/middleware.mdx +304 -0
  151. package/dist/docs/reference/plugin.mdx +113 -0
  152. package/dist/docs/reference/queue.mdx +155 -0
  153. package/dist/docs/reference/router.mdx +248 -0
  154. package/dist/docs/reference/sanitize.mdx +130 -0
  155. package/dist/docs/reference/storage.mdx +116 -0
  156. package/dist/docs/reference/swagger.mdx +132 -0
  157. package/dist/docs/reference/testing.mdx +145 -0
  158. package/dist/docs/reference/validator.mdx +186 -0
  159. package/dist/index.d.ts +4 -0
  160. package/dist/index.d.ts.map +1 -1
  161. package/dist/index.js +10 -1
  162. package/dist/index.js.map +1 -1
  163. package/dist/middleware/backpressure.d.ts.map +1 -1
  164. package/dist/middleware/backpressure.js +21 -12
  165. package/dist/middleware/backpressure.js.map +1 -1
  166. package/dist/middleware/cache.d.ts +6 -0
  167. package/dist/middleware/cache.d.ts.map +1 -1
  168. package/dist/middleware/cache.js +84 -28
  169. package/dist/middleware/cache.js.map +1 -1
  170. package/dist/middleware/idempotency.d.ts +4 -1
  171. package/dist/middleware/idempotency.d.ts.map +1 -1
  172. package/dist/middleware/idempotency.js +29 -4
  173. package/dist/middleware/idempotency.js.map +1 -1
  174. package/dist/middleware/ip-filter.d.ts.map +1 -1
  175. package/dist/middleware/ip-filter.js +16 -0
  176. package/dist/middleware/ip-filter.js.map +1 -1
  177. package/dist/middleware/rate-limit.d.ts.map +1 -1
  178. package/dist/middleware/rate-limit.js +29 -12
  179. package/dist/middleware/rate-limit.js.map +1 -1
  180. package/dist/queue/drivers/MemoryDriver.d.ts +6 -3
  181. package/dist/queue/drivers/MemoryDriver.d.ts.map +1 -1
  182. package/dist/queue/drivers/MemoryDriver.js +76 -23
  183. package/dist/queue/drivers/MemoryDriver.js.map +1 -1
  184. package/dist/server/bootstrapper.d.ts +3 -0
  185. package/dist/server/bootstrapper.d.ts.map +1 -1
  186. package/dist/server/bootstrapper.js +56 -8
  187. package/dist/server/bootstrapper.js.map +1 -1
  188. package/dist/server/bun-adapter.d.ts +2 -0
  189. package/dist/server/bun-adapter.d.ts.map +1 -1
  190. package/dist/server/bun-adapter.js +23 -12
  191. package/dist/server/bun-adapter.js.map +1 -1
  192. package/dist/server/request-handler.d.ts +0 -2
  193. package/dist/server/request-handler.d.ts.map +1 -1
  194. package/dist/server/request-handler.js +2 -24
  195. package/dist/server/request-handler.js.map +1 -1
  196. package/dist/server/request.d.ts.map +1 -1
  197. package/dist/server/request.js +24 -11
  198. package/dist/server/request.js.map +1 -1
  199. package/dist/server/uws-adapter.d.ts +80 -0
  200. package/dist/server/uws-adapter.d.ts.map +1 -0
  201. package/dist/server/uws-adapter.js +357 -0
  202. package/dist/server/uws-adapter.js.map +1 -0
  203. package/dist/server/ws-orchestrator.d.ts +1 -0
  204. package/dist/server/ws-orchestrator.d.ts.map +1 -1
  205. package/dist/server/ws-orchestrator.js +83 -0
  206. package/dist/server/ws-orchestrator.js.map +1 -1
  207. package/dist/types.d.ts +1 -1
  208. package/dist/types.d.ts.map +1 -1
  209. package/dist/utils/config.js.map +1 -1
  210. package/dist/validator/tex.d.ts +21 -16
  211. package/dist/validator/tex.d.ts.map +1 -1
  212. package/dist/validator/tex.js.map +1 -1
  213. package/package.json +11 -3
@@ -0,0 +1,145 @@
1
+ ---
2
+ title: "Testing"
3
+ description: "Strict API Reference for all Testing utilities and mocking objects."
4
+ ---
5
+
6
+ # Testing (`exisjs/testing`)
7
+
8
+ This document provides the strict API signatures and TypeScript interfaces exported by the `exisjs/testing` module. For high-level testing guides, see the [Testing Guide](/docs/testing).
9
+
10
+ ---
11
+
12
+ ## Native Node Runner Exports
13
+
14
+ ExisJS fully re-exports the native `node:test` runner methods, ensuring maximum IDE compatibility while providing powerful internal enhancements (like `.each()`).
15
+
16
+ ```typescript
17
+ export function test(name: string, fn?: TestFunction): void
18
+ export function test(name: string, options: TestOptions, fn?: TestFunction): void
19
+
20
+ export function describe(name: string, fn?: TestFunction): void
21
+ export function it(name: string, fn?: TestFunction): void
22
+
23
+ export function before(fn: HookFunction): void
24
+ export function after(fn: HookFunction): void
25
+ export function beforeEach(fn: HookFunction): void
26
+ export function afterEach(fn: HookFunction): void
27
+ ```
28
+
29
+ ### Parametrized Tests (`.each`)
30
+ All test blocks (`test`, `describe`, `it`) support a Jest-like `.each()` signature.
31
+
32
+ ```typescript
33
+ test.each([
34
+ [1, 1, 2],
35
+ [2, 3, 5]
36
+ ])('adds %d and %d to equal %d', (a, b, expected) => {
37
+ assert.equal(a + b, expected)
38
+ })
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Context Management
44
+
45
+ ### `createTestContext`
46
+ Automatically handles booting an ExisJS `App`, processing its startup hooks, and safely closing all database and worker connections. Returns a `TestApp` client for performing HTTP requests.
47
+
48
+ ```typescript
49
+ function createTestContext(app: App | typeof Class): TestApp
50
+ ```
51
+
52
+ ### `registerCleanup`
53
+ Allows third-party plugins or custom test setups to register a global cleanup function that will be executed when the test runner terminates.
54
+
55
+ ```typescript
56
+ function registerCleanup(fn: () => Promise<void> | void): void
57
+ ```
58
+
59
+ ---
60
+
61
+ ## HTTP Client (TestApp & TestRequest)
62
+
63
+ The `TestApp` interface provides a fluent API for making HTTP requests directly against your application without binding to a network port.
64
+
65
+ ### `TestApp`
66
+ ```typescript
67
+ interface TestApp {
68
+ get(path: string): TestRequest
69
+ post(path: string): TestRequest
70
+ put(path: string): TestRequest
71
+ patch(path: string): TestRequest
72
+ delete(path: string): TestRequest
73
+ options(path: string): TestRequest
74
+ head(path: string): TestRequest
75
+ request(method: string, path: string): TestRequest
76
+ }
77
+ ```
78
+
79
+ ### `TestRequest`
80
+ Supports chaining headers, bodies, and assertions. Once `.execute()` is called (or awaited), it returns a `TestResponse`.
81
+
82
+ ```typescript
83
+ class TestRequest {
84
+ send(body: unknown): this
85
+ set(header: string, value: string): this
86
+
87
+ // Assertions
88
+ expect(status: number): this
89
+ expect(header: string, value: string | RegExp): this
90
+ expect(body: unknown): this
91
+
92
+ // Execution
93
+ execute(): Promise<TestResponse>
94
+ then<T>(onfulfilled: (res: TestResponse) => T): Promise<T>
95
+ }
96
+ ```
97
+
98
+ ### `TestResponse`
99
+ ```typescript
100
+ interface TestResponse {
101
+ status: number
102
+ headers: Record<string, string | string[] | undefined>
103
+ body: any // The parsed JSON payload (if applicable)
104
+ text: string // The raw string response
105
+ }
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Mocks & Spies (`ex`)
111
+
112
+ The `ex` object provides a Jest-compatible API wrapper over the native `node:test` mocking utilities.
113
+
114
+ ```typescript
115
+ const ex = {
116
+ // Spies
117
+ fn: (impl?: Function) => MockFunction,
118
+ spyOn: (obj: any, methodName: string) => MockFunction,
119
+
120
+ // Modules
121
+ mock: (specifier: string, factory?: () => any) => void,
122
+
123
+ // Timers
124
+ setSystemTime: (time: number | Date) => void,
125
+ advanceTimersByTime: (ms: number) => void,
126
+ useFakeTimers: () => void,
127
+ useRealTimers: () => void,
128
+
129
+ clearAllMocks: () => void
130
+ }
131
+ ```
132
+
133
+ ### `MockFunction` Extensions
134
+ ExisJS extends the native `MockFunction` returned by `ex.fn()` and `ex.spyOn()` with Jest-like helpers:
135
+
136
+ ```typescript
137
+ interface MockFunction {
138
+ mockRestore(): void
139
+ mockImplementation(impl: Function): this
140
+ mockResolvedValue(val: any): this
141
+ mockReturnValue(val: any): this
142
+ mockRejectedValue(err: any): this
143
+ mockClear(): void
144
+ }
145
+ ```
@@ -0,0 +1,186 @@
1
+ ---
2
+ title: "Validation"
3
+ description: "API Reference for ExisJS built-in Schema Validator (tex)."
4
+ ---
5
+
6
+ # Validation (`tex`)
7
+
8
+ ExisJS ships with a blazingly fast, zero-dependency validation library out of the box, exposed as `tex` from `exisjs/validator`. It is powered by a high-performance Rust engine, fully isomorphic, and deeply integrated with TypeScript and OpenAPI (Swagger).
9
+
10
+ ---
11
+
12
+ ## Core Types
13
+
14
+ The `tex` object provides factory functions for every common primitive and data structure.
15
+
16
+ - `tex.string()`: Validates a string.
17
+ - `tex.number()`: Validates a number.
18
+ - `tex.boolean()`: Validates a boolean.
19
+ - `tex.email()`: Validates standard email formatting.
20
+ - `tex.enum(['A', 'B'])`: Validates against a specific set of allowed strings.
21
+
22
+ ### Objects & Arrays
23
+
24
+ - `tex.object({ ... })`: Validates an object against a strict shape.
25
+ - `tex.array(schema)`: Validates an array of items matching the given schema.
26
+
27
+ ```typescript
28
+ const UserProfile = tex.object({
29
+ id: tex.string(),
30
+ tags: tex.array(tex.string()),
31
+ role: tex.enum(['admin', 'user'])
32
+ })
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Usage in Routes
38
+
39
+ ExisJS tightly integrates the `tex` validator directly into its routing layer, providing immediate TypeScript inference for `ctx.body`, `@Body()`, and other parameters.
40
+
41
+ **Class-Based (OOP)**
42
+ ```typescript filename="src/http/users/route.ts"
43
+ import { Controller, Post, Body } from 'exisjs/decorators'
44
+ import { tex } from 'exisjs/validator'
45
+
46
+ const CreateUserSchema = tex.object({ email: tex.email() })
47
+
48
+ @Controller('/users')
49
+ export default class UserController {
50
+ @Post('/', { body: CreateUserSchema })
51
+ createUser(@Body() body: any) {
52
+ return { success: true, email: body.email }
53
+ }
54
+ }
55
+ ```
56
+
57
+ **Functional**
58
+ ```typescript filename="src/http/users/route.ts"
59
+ import { controller, route } from 'exisjs/router'
60
+ import { tex } from 'exisjs/validator'
61
+
62
+ const CreateUserSchema = tex.object({ email: tex.email() })
63
+
64
+ export default controller({
65
+ createUser: route.post('/users', {
66
+ body: CreateUserSchema,
67
+ async handle(ctx) {
68
+ // ctx.body is strictly inferred as { email: string }
69
+ return { success: true, email: ctx.body.email }
70
+ }
71
+ })
72
+ })
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Type Options
78
+
79
+ All validators accept an options object that allows you to configure validation constraints and behaviors.
80
+
81
+ - `optional`: Marks the field as optional (allows `undefined`).
82
+ - `coerce`: Instructs the ultra-fast Rust engine to automatically convert types when possible before validating. For example, converting `"123"` into a number, or `3000` into a string.
83
+
84
+ ```typescript
85
+ // Type Conversion Examples using { coerce: true }
86
+ const EnvSchema = tex.object({
87
+ // Converts string "3000" from process.env to number 3000
88
+ PORT: tex.number({ coerce: true }),
89
+
90
+ // Converts boolean 'false' or number '0' to string "false" / "0"
91
+ ID_AS_STRING: tex.string({ coerce: true }),
92
+
93
+ // Converts string "true" or number '1' to boolean true
94
+ ENABLE_FEATURE: tex.boolean({ coerce: true })
95
+ })
96
+ ```
97
+
98
+ ### String Options
99
+ - `min`: Enforces a minimum string length.
100
+ - `max`: Enforces a maximum string length.
101
+
102
+ ### Number Options
103
+ - `min`: Enforces a minimum numeric value.
104
+ - `max`: Enforces a maximum numeric value.
105
+
106
+ ```typescript
107
+ const User = tex.object({
108
+ id: tex.string(),
109
+ email: tex.email(),
110
+ password: tex.string({ min: 8, max: 100 }),
111
+ age: tex.number({ min: 13, optional: true })
112
+ })
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Custom Data Manipulation
118
+
119
+ ### `.sanitize(...fns)`
120
+ Runs one or more synchronous sanitization functions over the data **before** the Rust validation executes. Use this for complex object transformations or string formatting. ExisJS provides a rich suite of built-in sanitizers through the `exisjs/sanitize` module.
121
+
122
+ ```typescript
123
+ import { sanitize } from 'exisjs/sanitize'
124
+
125
+ // Built-in sanitization
126
+ const CleanString = tex.string().sanitize(sanitize.trim, sanitize.toLowerCase)
127
+
128
+ // Custom data transformation
129
+ const WhiteListIPs = tex.array(tex.string())
130
+ .sanitize((val) => typeof val === 'string' ? val.split(',') : val)
131
+ ```
132
+
133
+ ### `.refine(fn, message)`
134
+ Runs custom validation logic **after** the Rust validation executes. Useful for complex business logic that cannot be expressed purely with basic schema types.
135
+
136
+ ```typescript
137
+ const PasswordConfirm = tex.object({
138
+ password: tex.string(),
139
+ confirm: tex.string()
140
+ }).refine((data) => data.password === data.confirm, "Passwords must match")
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Type Inference
146
+
147
+ You can extract the raw TypeScript interface directly from any schema using the `ResolveSchema` utility.
148
+
149
+ ```typescript
150
+ import { tex, type ResolveSchema } from 'exisjs/validator'
151
+
152
+ const ProfileSchema = tex.object({
153
+ name: tex.string(),
154
+ age: tex.number({ optional: true })
155
+ })
156
+
157
+ // Resolves to: { name: string; age?: number | undefined }
158
+ type Profile = ResolveSchema<typeof ProfileSchema>
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Integrations
164
+
165
+ Because the ExisJS Validator is native to the framework, it is tightly coupled with our database and documentation adapters.
166
+
167
+ ### OpenAPI Generation
168
+ The `TexEngine` automatically converts validation schemas into strictly compliant OpenAPI/Swagger JSON schemas when attached to routes. This powers the zero-config ExisJS Swagger integration without any manual YAML authoring.
169
+
170
+ ---
171
+
172
+ ## Environment Validation
173
+
174
+ You can safely parse and validate your environment variables at startup using the standard `.parse()` method.
175
+
176
+ ```typescript filename="env.ts"
177
+ import { tex } from 'exisjs/validator'
178
+
179
+ // Validates process.env synchronously. Crashes the app on boot with a
180
+ // beautiful error message if validation fails.
181
+ export const env = tex.object({
182
+ PORT: tex.number({ coerce: true, optional: true }),
183
+ DATABASE_URL: tex.string(),
184
+ JWT_SECRET: tex.string({ min: 32 })
185
+ }).parse(process.env)
186
+ ```
package/dist/index.d.ts CHANGED
@@ -5,6 +5,10 @@ export { defineConfig } from './utils/config';
5
5
  export { createLogger } from './utils/logger';
6
6
  export { parseEnv, loadEnv } from './utils/env';
7
7
  export { CircuitBreaker, CircuitBreakerError, CircuitState, } from './utils/circuit-breaker';
8
+ export { db, createDatabase, DatabaseManager } from './database';
9
+ export { Migrator } from './database/migration';
10
+ export { QueryBuilder } from './database/query-builder';
11
+ export type { DatabaseConfig, DatabaseDialect, DatabaseAdapter, QueryResult, ExecuteResult, Transaction, } from './database/types';
8
12
  export type { ExisConfig, ExisPlugin, ExisPluginInstance, Logger, CorsConfig, LoggerConfig, HookRequest, } from './types';
9
13
  export interface ExisUser {
10
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAGpB,OAAO,EAAE,SAAS,IAAI,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,KAAK,GAAG,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAG7C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE7C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAI/C,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,YAAY,GACb,MAAM,yBAAyB,CAAA;AAGhC,YAAY,EACV,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,MAAM,EACN,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,MAAM,SAAS,CAAA;AAGhB,MAAM,WAAW,QAAQ;CAAG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAGpB,OAAO,EAAE,SAAS,IAAI,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACnD,OAAO,EAAE,KAAK,GAAG,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAG7C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE7C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAI/C,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,YAAY,GACb,MAAM,yBAAyB,CAAA;AAGhC,OAAO,EAAE,EAAE,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,YAAY,EACV,cAAc,EACd,eAAe,EACf,eAAe,EACf,WAAW,EACX,aAAa,EACb,WAAW,GACZ,MAAM,kBAAkB,CAAA;AAGzB,YAAY,EACV,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,MAAM,EACN,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,MAAM,SAAS,CAAA;AAGhB,MAAM,WAAW,QAAQ;CAAG"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CircuitState = exports.CircuitBreakerError = exports.CircuitBreaker = exports.loadEnv = exports.parseEnv = exports.createLogger = exports.defineConfig = exports.getActiveApp = exports.exis = void 0;
3
+ exports.QueryBuilder = exports.Migrator = exports.DatabaseManager = exports.createDatabase = exports.db = exports.CircuitState = exports.CircuitBreakerError = exports.CircuitBreaker = exports.loadEnv = exports.parseEnv = exports.createLogger = exports.defineConfig = exports.getActiveApp = exports.exis = void 0;
4
4
  require("./utils/env");
5
5
  // ─── Core ─────────────────────────────────────────────────────────────────────
6
6
  var define_1 = require("./server/define");
@@ -20,4 +20,13 @@ var circuit_breaker_1 = require("./utils/circuit-breaker");
20
20
  Object.defineProperty(exports, "CircuitBreaker", { enumerable: true, get: function () { return circuit_breaker_1.CircuitBreaker; } });
21
21
  Object.defineProperty(exports, "CircuitBreakerError", { enumerable: true, get: function () { return circuit_breaker_1.CircuitBreakerError; } });
22
22
  Object.defineProperty(exports, "CircuitState", { enumerable: true, get: function () { return circuit_breaker_1.CircuitState; } });
23
+ // ─── Database ─────────────────────────────────────────────────────────────────
24
+ var database_1 = require("./database");
25
+ Object.defineProperty(exports, "db", { enumerable: true, get: function () { return database_1.db; } });
26
+ Object.defineProperty(exports, "createDatabase", { enumerable: true, get: function () { return database_1.createDatabase; } });
27
+ Object.defineProperty(exports, "DatabaseManager", { enumerable: true, get: function () { return database_1.DatabaseManager; } });
28
+ var migration_1 = require("./database/migration");
29
+ Object.defineProperty(exports, "Migrator", { enumerable: true, get: function () { return migration_1.Migrator; } });
30
+ var query_builder_1 = require("./database/query-builder");
31
+ Object.defineProperty(exports, "QueryBuilder", { enumerable: true, get: function () { return query_builder_1.QueryBuilder; } });
23
32
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uBAAoB;AAEpB,iFAAiF;AACjF,0CAAmD;AAA1C,8FAAA,SAAS,OAAQ;AAC1B,oCAAqD;AAAlC,mGAAA,YAAY,OAAA;AAC/B,yCAA6C;AAApC,sGAAA,YAAY,OAAA;AAErB,iFAAiF;AACjF,yCAA6C;AAApC,sGAAA,YAAY,OAAA;AAErB,mCAA+C;AAAtC,+FAAA,QAAQ,OAAA;AAAE,8FAAA,OAAO,OAAA;AAE1B,8EAA8E;AAE9E,2DAIgC;AAH9B,iHAAA,cAAc,OAAA;AACd,sHAAA,mBAAmB,OAAA;AACnB,+GAAA,YAAY,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uBAAoB;AAEpB,iFAAiF;AACjF,0CAAmD;AAA1C,8FAAA,SAAS,OAAQ;AAC1B,oCAAqD;AAAlC,mGAAA,YAAY,OAAA;AAC/B,yCAA6C;AAApC,sGAAA,YAAY,OAAA;AAErB,iFAAiF;AACjF,yCAA6C;AAApC,sGAAA,YAAY,OAAA;AAErB,mCAA+C;AAAtC,+FAAA,QAAQ,OAAA;AAAE,8FAAA,OAAO,OAAA;AAE1B,8EAA8E;AAE9E,2DAIgC;AAH9B,iHAAA,cAAc,OAAA;AACd,sHAAA,mBAAmB,OAAA;AACnB,+GAAA,YAAY,OAAA;AAGd,iFAAiF;AACjF,uCAAgE;AAAvD,8FAAA,EAAE,OAAA;AAAE,0GAAA,cAAc,OAAA;AAAE,2GAAA,eAAe,OAAA;AAC5C,kDAA+C;AAAtC,qGAAA,QAAQ,OAAA;AACjB,0DAAuD;AAA9C,6GAAA,YAAY,OAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"backpressure.d.ts","sourceRoot":"","sources":["../../src/middleware/backpressure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAmC,MAAM,UAAU,CAAA;AAGxE,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAwET"}
1
+ {"version":3,"file":"backpressure.d.ts","sourceRoot":"","sources":["../../src/middleware/backpressure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAmC,MAAM,UAAU,CAAA;AAGxE,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAqFT"}
@@ -13,6 +13,11 @@ function backpressureMiddleware(options = {}) {
13
13
  const queued = queue.shift();
14
14
  if (queued) {
15
15
  clearTimeout(queued.timer);
16
+ // O(1) skip if the request timed out
17
+ if (queued.isCancelled) {
18
+ process.nextTick(processNext);
19
+ return;
20
+ }
16
21
  activeCount++;
17
22
  // Use _onFinish to decrement activeCount when done
18
23
  queued.res._onFinish.push(() => {
@@ -37,19 +42,23 @@ function backpressureMiddleware(options = {}) {
37
42
  // Return 503 Service Unavailable immediately
38
43
  return next(errors_1.HttpError.serviceUnavailable('Server is at capacity. Please try again later.'));
39
44
  }
40
- const timer = setTimeout(() => {
41
- // Remove from queue
42
- const index = queue.findIndex((q) => q.req === req);
43
- if (index !== -1) {
44
- queue.splice(index, 1);
45
- next(errors_1.HttpError.serviceUnavailable('Request timed out in queue.'));
46
- }
47
- }, timeoutMs);
48
- // Allow process to exit if only this timer remains
49
- if (timer.unref) {
50
- timer.unref();
45
+ const queuedReq = {
46
+ req,
47
+ res,
48
+ next,
49
+ timer: null,
50
+ isCancelled: false,
51
+ };
52
+ const timeoutCallback = () => {
53
+ queuedReq.isCancelled = true;
54
+ next(errors_1.HttpError.serviceUnavailable('Request timed out in queue.'));
55
+ };
56
+ // Refresh timer logic
57
+ queuedReq.timer = setTimeout(timeoutCallback, timeoutMs);
58
+ if (queuedReq.timer.unref) {
59
+ queuedReq.timer.unref();
51
60
  }
52
- queue.push({ req, res, next, timer });
61
+ queue.push(queuedReq);
53
62
  };
54
63
  }
55
64
  //# sourceMappingURL=backpressure.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"backpressure.js","sourceRoot":"","sources":["../../src/middleware/backpressure.ts"],"names":[],"mappings":";;AAYA,wDA0EC;AArFD,4CAA2C;AAW3C,SAAgB,sBAAsB,CACpC,UAA+B,EAAE;IAEjC,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,IAAI,CAAA;IACnD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,GAAG,CAAA;IACxC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAA;IAE5C,IAAI,WAAW,GAAG,CAAC,CAAA;IASnB,MAAM,KAAK,GAAoB,EAAE,CAAA;IAEjC,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,WAAW,GAAG,aAAa,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,EAAE,CAAA;YAC5B,IAAI,MAAM,EAAE,CAAC;gBACX,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;gBAC1B,WAAW,EAAE,CAAA;gBAEb,mDAAmD;gBACnD,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE;oBAC7B,WAAW,EAAE,CAAA;oBACb,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;gBAC/B,CAAC,CAAC,CAAA;gBAEF,MAAM,CAAC,IAAI,EAAE,CAAA;YACf,CAAC;QACH,CAAC;IACH,CAAC,CAAA;IAED,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACzD,IAAI,WAAW,GAAG,aAAa,EAAE,CAAC;YAChC,WAAW,EAAE,CAAA;YAEb,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE;gBACtB,WAAW,EAAE,CAAA;gBACb,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;YAC/B,CAAC,CAAC,CAAA;YAEF,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;QAED,sCAAsC;QACtC,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;YAC7B,6CAA6C;YAC7C,OAAO,IAAI,CACT,kBAAS,CAAC,kBAAkB,CAC1B,gDAAgD,CACjD,CACF,CAAA;QACH,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,oBAAoB;YACpB,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAA;YACnD,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;gBACjB,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;gBACtB,IAAI,CAAC,kBAAS,CAAC,kBAAkB,CAAC,6BAA6B,CAAC,CAAC,CAAA;YACnE,CAAC;QACH,CAAC,EAAE,SAAS,CAAC,CAAA;QAEb,mDAAmD;QACnD,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAChB,KAAK,CAAC,KAAK,EAAE,CAAA;QACf,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IACvC,CAAC,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"backpressure.js","sourceRoot":"","sources":["../../src/middleware/backpressure.ts"],"names":[],"mappings":";;AAYA,wDAuFC;AAlGD,4CAA2C;AAW3C,SAAgB,sBAAsB,CACpC,UAA+B,EAAE;IAEjC,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,IAAI,CAAA;IACnD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,GAAG,CAAA;IACxC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAA;IAE5C,IAAI,WAAW,GAAG,CAAC,CAAA;IAUnB,MAAM,KAAK,GAAoB,EAAE,CAAA;IAEjC,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,WAAW,GAAG,aAAa,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,EAAE,CAAA;YAC5B,IAAI,MAAM,EAAE,CAAC;gBACX,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;gBAE1B,qCAAqC;gBACrC,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;oBACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;oBAC7B,OAAM;gBACR,CAAC;gBAED,WAAW,EAAE,CAAA;gBAEb,mDAAmD;gBACnD,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE;oBAC7B,WAAW,EAAE,CAAA;oBACb,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;gBAC/B,CAAC,CAAC,CAAA;gBAEF,MAAM,CAAC,IAAI,EAAE,CAAA;YACf,CAAC;QACH,CAAC;IACH,CAAC,CAAA;IAED,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACzD,IAAI,WAAW,GAAG,aAAa,EAAE,CAAC;YAChC,WAAW,EAAE,CAAA;YAEb,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE;gBACtB,WAAW,EAAE,CAAA;gBACb,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;YAC/B,CAAC,CAAC,CAAA;YAEF,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;QAED,sCAAsC;QACtC,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;YAC7B,6CAA6C;YAC7C,OAAO,IAAI,CACT,kBAAS,CAAC,kBAAkB,CAC1B,gDAAgD,CACjD,CACF,CAAA;QACH,CAAC;QAED,MAAM,SAAS,GAAkB;YAC/B,GAAG;YACH,GAAG;YACH,IAAI;YACJ,KAAK,EAAE,IAAW;YAClB,WAAW,EAAE,KAAK;SACnB,CAAA;QAED,MAAM,eAAe,GAAG,GAAG,EAAE;YAC3B,SAAS,CAAC,WAAW,GAAG,IAAI,CAAA;YAC5B,IAAI,CAAC,kBAAS,CAAC,kBAAkB,CAAC,6BAA6B,CAAC,CAAC,CAAA;QACnE,CAAC,CAAA;QAED,sBAAsB;QACtB,SAAS,CAAC,KAAK,GAAG,UAAU,CAAC,eAAe,EAAE,SAAS,CAAC,CAAA;QACxD,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YAC1B,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;QACzB,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACvB,CAAC,CAAA;AACH,CAAC"}
@@ -10,6 +10,12 @@ export interface CacheOptions {
10
10
  * Override this for user-scoped caching (e.g. `(req) => req.user.id + req.path`).
11
11
  */
12
12
  keyGenerator?: (req: Request) => string;
13
+ /**
14
+ * Stale-While-Revalidate duration in milliseconds.
15
+ * If the cache is older than ttlMs but younger than ttlMs + swrMs,
16
+ * stale data is served instantly while it revalidates in the background.
17
+ */
18
+ swrMs?: number;
13
19
  }
14
20
  export declare function cacheMiddleware(options: CacheOptions): Handler;
15
21
  //# sourceMappingURL=cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/middleware/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAA0B,MAAM,UAAU,CAAA;AACxE,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE/D,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,EAAE,CAAC,CAAA;IAC9C;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAA;CACxC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAsH9D"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/middleware/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAA0B,MAAM,UAAU,CAAA;AACxE,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE/D,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,EAAE,CAAC,CAAA;IAC9C;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAA;IACvC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAKD,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CA4K9D"}
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.cacheMiddleware = cacheMiddleware;
4
4
  const store_1 = require("../cache/store");
5
+ // Global in-flight requests map to prevent Cache Stampedes (Dogpile effect)
6
+ const inFlightRequests = new Map();
5
7
  function cacheMiddleware(options) {
6
8
  const ttlMs = options.ttlMs;
7
9
  if (!options || typeof options.keyGenerator !== 'function') {
@@ -17,37 +19,74 @@ function cacheMiddleware(options) {
17
19
  try {
18
20
  const cached = await store.get(key);
19
21
  if (cached) {
20
- if (cached.data.contentType) {
21
- res.set('Content-Type', cached.data.contentType);
22
+ const isStale = ttlMs && Date.now() - cached.createdAt > ttlMs;
23
+ // If it's stale and we have no SWR allowance, we act as a miss
24
+ if (isStale &&
25
+ (!options.swrMs ||
26
+ Date.now() - cached.createdAt > ttlMs + options.swrMs)) {
27
+ // Treated as miss
22
28
  }
23
- res.set('X-Exis-Cache', 'HIT');
24
- let bodyToSend = cached.data.body;
25
- // Handle Buffer reconstruction for Redis
26
- if (typeof cached.data.body === 'string' && cached.data.isBuffer) {
27
- bodyToSend = Buffer.from(cached.data.body, 'base64');
28
- }
29
- // For JSON responses, replay via res.json() so middleware chains
30
- // (interceptor, dedupe) receive the correct type instead of raw strings
31
- const ct = cached.data.contentType || '';
32
- if (ct.includes('application/json') && typeof bodyToSend === 'string') {
33
- try {
34
- res.status(cached.data.statusCode).json(JSON.parse(bodyToSend));
29
+ else {
30
+ // Handle Buffer reconstruction for Redis
31
+ if (cached.data.contentType) {
32
+ res.set('Content-Type', cached.data.contentType);
33
+ }
34
+ res.set('X-Exis-Cache', isStale ? 'STALE' : 'HIT');
35
+ let bodyToSend = cached.data.body;
36
+ if (typeof cached.data.body === 'string' && cached.data.isBuffer) {
37
+ bodyToSend = Buffer.from(cached.data.body, 'base64');
38
+ }
39
+ if (cached.data.contentType?.includes('application/json') &&
40
+ typeof bodyToSend === 'string') {
41
+ try {
42
+ res.status(cached.data.statusCode).json(JSON.parse(bodyToSend));
43
+ }
44
+ catch {
45
+ res.status(cached.data.statusCode).send(bodyToSend);
46
+ }
35
47
  }
36
- catch {
37
- // If parsing fails, fall back to send
48
+ else {
38
49
  res.status(cached.data.statusCode).send(bodyToSend);
39
50
  }
51
+ if (!isStale) {
52
+ return;
53
+ }
54
+ // If it was stale, we returned the response already. Now we fall through
55
+ // to trigger a background revalidation! We MUST NOT return here if stale.
40
56
  }
41
- else {
42
- res.status(cached.data.statusCode).send(bodyToSend);
43
- }
44
- return;
45
57
  }
46
58
  }
47
59
  catch {
48
60
  // Fallback to normal execution if cache read fails
49
61
  }
50
- res.set('X-Exis-Cache', 'MISS');
62
+ // --- Cache Stampede Prevention ---
63
+ // If another request is currently resolving this exact same cache key, we just wait for it.
64
+ if (!res.headersSent) {
65
+ if (inFlightRequests.has(key)) {
66
+ res.set('X-Exis-Cache', 'DEDUPED');
67
+ const dedupeResponse = await inFlightRequests.get(key);
68
+ if (dedupeResponse.contentType) {
69
+ res.set('Content-Type', dedupeResponse.contentType);
70
+ }
71
+ if (dedupeResponse.contentType?.includes('application/json') &&
72
+ typeof dedupeResponse.body === 'string') {
73
+ return res
74
+ .status(dedupeResponse.statusCode)
75
+ .json(JSON.parse(dedupeResponse.body));
76
+ }
77
+ return res.status(dedupeResponse.statusCode).send(dedupeResponse.body);
78
+ }
79
+ }
80
+ // Set headers if not already sent (by SWR)
81
+ if (!res.headersSent) {
82
+ res.set('X-Exis-Cache', 'MISS');
83
+ }
84
+ let resolveInFlight;
85
+ const inFlightPromise = new Promise((resolve) => {
86
+ resolveInFlight = resolve;
87
+ });
88
+ inFlightRequests.set(key, inFlightPromise);
89
+ // --- End Stampede Prevention ---
51
90
  // Intercept send/json
52
91
  const originalSend = res.send.bind(res);
53
92
  const originalJson = res.json.bind(res);
@@ -60,19 +99,27 @@ function cacheMiddleware(options) {
60
99
  else if (options.tags) {
61
100
  tags = options.tags;
62
101
  }
63
- // Fire and forget caching
64
- Promise.resolve(store.set(key, {
102
+ const payload = {
65
103
  body,
66
104
  contentType: typeof res.getHeader === 'function'
67
105
  ? res.getHeader('content-type')
68
106
  : undefined,
69
107
  statusCode: res.statusCode,
70
108
  isBuffer: Buffer.isBuffer(body),
71
- }, tags, ttlMs)).catch(() => {
109
+ };
110
+ Promise.resolve(store.set(key, payload, tags, ttlMs)).catch(() => {
72
111
  /* noop */
73
112
  });
113
+ resolveInFlight(payload);
114
+ inFlightRequests.delete(key);
115
+ }
116
+ else {
117
+ resolveInFlight({});
118
+ inFlightRequests.delete(key);
119
+ }
120
+ if (!res.headersSent) {
121
+ return originalSend(body);
74
122
  }
75
- return originalSend(body);
76
123
  };
77
124
  res.json = function (data) {
78
125
  if (res.statusCode >= 200 && res.statusCode < 300) {
@@ -83,16 +130,25 @@ function cacheMiddleware(options) {
83
130
  else if (options.tags) {
84
131
  tags = options.tags;
85
132
  }
86
- Promise.resolve(store.set(key, {
133
+ const payload = {
87
134
  body: JSON.stringify(data),
88
135
  contentType: 'application/json; charset=utf-8',
89
136
  statusCode: res.statusCode,
90
137
  isBuffer: false,
91
- }, tags, ttlMs)).catch(() => {
138
+ };
139
+ Promise.resolve(store.set(key, payload, tags, ttlMs)).catch(() => {
92
140
  /* noop */
93
141
  });
142
+ resolveInFlight(payload);
143
+ inFlightRequests.delete(key);
144
+ }
145
+ else {
146
+ resolveInFlight({});
147
+ inFlightRequests.delete(key);
148
+ }
149
+ if (!res.headersSent) {
150
+ return originalJson(data);
94
151
  }
95
- return originalJson(data);
96
152
  };
97
153
  next();
98
154
  };
@@ -1 +1 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/middleware/cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/middleware/cache.ts"],"names":[],"mappings":"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"}