ingenium 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +943 -0
  3. package/dist/index.cjs +7078 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +4262 -0
  6. package/dist/index.d.ts +4262 -0
  7. package/dist/index.js +6963 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +47 -0
  10. package/src/api-key/middleware.ts +157 -0
  11. package/src/api-key/types.ts +37 -0
  12. package/src/app/scope.ts +392 -0
  13. package/src/app.ts +1752 -0
  14. package/src/body/limit.ts +21 -0
  15. package/src/body/middleware.ts +30 -0
  16. package/src/body/multipart-types.ts +40 -0
  17. package/src/body/multipart.ts +254 -0
  18. package/src/context/body.ts +324 -0
  19. package/src/context/context.ts +650 -0
  20. package/src/context/cookies.ts +282 -0
  21. package/src/context/pool.ts +32 -0
  22. package/src/cors/middleware.ts +182 -0
  23. package/src/cors/types.ts +79 -0
  24. package/src/cron/parser.ts +311 -0
  25. package/src/cron/registry.ts +49 -0
  26. package/src/cron/scheduler.ts +153 -0
  27. package/src/csrf/middleware.ts +224 -0
  28. package/src/csrf/types.ts +65 -0
  29. package/src/errors.ts +148 -0
  30. package/src/idempotency/middleware.ts +197 -0
  31. package/src/idempotency/store.ts +70 -0
  32. package/src/idempotency/types.ts +87 -0
  33. package/src/index.ts +328 -0
  34. package/src/jobs/queue.ts +306 -0
  35. package/src/jobs/registry.ts +82 -0
  36. package/src/jobs/store-memory.ts +113 -0
  37. package/src/jobs/types.ts +135 -0
  38. package/src/jwt/jwks.ts +143 -0
  39. package/src/jwt/middleware.ts +313 -0
  40. package/src/jwt/types.ts +137 -0
  41. package/src/jwt/verify.ts +370 -0
  42. package/src/middleware/compose.ts +94 -0
  43. package/src/middleware/types.ts +37 -0
  44. package/src/negotiation/accept.ts +159 -0
  45. package/src/negotiation/etag.ts +30 -0
  46. package/src/negotiation/format.ts +88 -0
  47. package/src/negotiation/fresh.ts +89 -0
  48. package/src/negotiation/json-etag.ts +122 -0
  49. package/src/negotiation/negotiate.ts +97 -0
  50. package/src/openapi/describe.ts +79 -0
  51. package/src/openapi/extract-params.ts +62 -0
  52. package/src/openapi/generate.ts +251 -0
  53. package/src/openapi/handler.ts +73 -0
  54. package/src/openapi/types.ts +145 -0
  55. package/src/plugin/decorators.ts +100 -0
  56. package/src/plugin/hooks.ts +114 -0
  57. package/src/plugin/types.ts +189 -0
  58. package/src/problem/middleware.ts +55 -0
  59. package/src/problem/serialize.ts +121 -0
  60. package/src/problem/types.ts +68 -0
  61. package/src/proxy/trust.ts +247 -0
  62. package/src/rate-limit/middleware.ts +72 -0
  63. package/src/rate-limit/store.ts +129 -0
  64. package/src/rate-limit/types.ts +60 -0
  65. package/src/response/reflect.ts +93 -0
  66. package/src/router/router.ts +284 -0
  67. package/src/router/trie.ts +309 -0
  68. package/src/router/types.ts +54 -0
  69. package/src/schema/standard.ts +67 -0
  70. package/src/session/middleware.ts +379 -0
  71. package/src/session/store-memory.ts +79 -0
  72. package/src/session/types.ts +95 -0
  73. package/src/sinatra/filters.ts +129 -0
  74. package/src/sinatra/top-level.ts +151 -0
  75. package/src/sse/keep-alive.ts +52 -0
  76. package/src/sse/sse.ts +115 -0
  77. package/src/static/middleware.ts +254 -0
  78. package/src/static/types.ts +31 -0
  79. package/src/transport/http2-helpers.ts +242 -0
  80. package/src/transport/http2.ts +316 -0
  81. package/src/transport/node.ts +261 -0
  82. package/src/transport/shutdown.ts +86 -0
  83. package/src/transport/types.ts +72 -0
  84. package/src/util/safe-json.ts +66 -0
  85. package/src/ws/index.ts +164 -0
  86. package/src/ws/middleware.ts +178 -0
  87. package/src/ws/types.ts +52 -0
  88. package/src/ws/ws-node-adapter.ts +162 -0
@@ -0,0 +1,70 @@
1
+ import type { CachedResponse, IdempotencyStore } from './types.ts'
2
+
3
+ interface Entry {
4
+ value: CachedResponse
5
+ expiresAt: number
6
+ }
7
+
8
+ /**
9
+ * In-process idempotency cache. Suitable for single-replica deployments and
10
+ * tests; back with Redis when running multiple replicas behind a load
11
+ * balancer (responses cached on one replica won't replay on another).
12
+ *
13
+ * A periodic sweep removes expired entries so long-lived processes don't
14
+ * leak memory across forgotten keys. The sweep timer is `.unref()`'d, so
15
+ * it never keeps the Node event loop alive.
16
+ */
17
+ export class IdempotencyMemoryStore implements IdempotencyStore {
18
+ private readonly map: Map<string, Entry> = new Map()
19
+ private sweeper: NodeJS.Timeout | null = null
20
+ private sweepIntervalMs = 0
21
+
22
+ get(key: string): Promise<CachedResponse | null> {
23
+ const entry = this.map.get(key)
24
+ if (!entry) return Promise.resolve(null)
25
+ if (Date.now() >= entry.expiresAt) {
26
+ this.map.delete(key)
27
+ return Promise.resolve(null)
28
+ }
29
+ return Promise.resolve(entry.value)
30
+ }
31
+
32
+ set(key: string, value: CachedResponse, ttlMs: number): Promise<void> {
33
+ this.map.set(key, { value, expiresAt: Date.now() + ttlMs })
34
+ this.ensureSweeper(ttlMs)
35
+ return Promise.resolve()
36
+ }
37
+
38
+ delete(key: string): Promise<void> {
39
+ this.map.delete(key)
40
+ return Promise.resolve()
41
+ }
42
+
43
+ /**
44
+ * Stop the cleanup interval. Safe to call multiple times. Mostly useful
45
+ * in tests; production usage doesn't need this because the timer is
46
+ * already unref'd.
47
+ */
48
+ destroy(): void {
49
+ if (this.sweeper) {
50
+ clearInterval(this.sweeper)
51
+ this.sweeper = null
52
+ }
53
+ this.map.clear()
54
+ }
55
+
56
+ private ensureSweeper(ttlMs: number): void {
57
+ if (this.sweeper && this.sweepIntervalMs === ttlMs) return
58
+ if (this.sweeper) clearInterval(this.sweeper)
59
+ this.sweepIntervalMs = ttlMs
60
+ this.sweeper = setInterval(() => this.sweep(), ttlMs)
61
+ if (typeof this.sweeper.unref === 'function') this.sweeper.unref()
62
+ }
63
+
64
+ private sweep(): void {
65
+ const now = Date.now()
66
+ for (const [key, entry] of this.map) {
67
+ if (now >= entry.expiresAt) this.map.delete(key)
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,87 @@
1
+ import type { Buffer } from 'node:buffer'
2
+ import type { IngeniumContext } from '../context/context.ts'
3
+ import type { HttpMethod } from '../router/types.ts'
4
+
5
+ /**
6
+ * A frozen snapshot of an outgoing response. Captured AFTER the handler
7
+ * runs and stored in the idempotency cache for replay on retry.
8
+ *
9
+ * `body` is `null` only for empty responses (e.g. 204).
10
+ */
11
+ export interface CachedResponse {
12
+ /** HTTP status code from the original response. */
13
+ statusCode: number
14
+ /** Plain header bag (lowercased keys), copied from `ctx._headers`. */
15
+ headers: Record<string, string | string[]>
16
+ /** Serialized body. `null` when the original response had no body. */
17
+ body: string | Buffer | null
18
+ }
19
+
20
+ /**
21
+ * Pluggable storage for the idempotency cache. Default impl is in-memory;
22
+ * swap for Redis/etc. when running multiple replicas.
23
+ */
24
+ export interface IdempotencyStore {
25
+ /** Returns the cached response for `key` or `null` if missing/expired. */
26
+ get(key: string): Promise<CachedResponse | null>
27
+ /** Persist `value` under `key` for `ttlMs` milliseconds. */
28
+ set(key: string, value: CachedResponse, ttlMs: number): Promise<void>
29
+ /** Remove `key`. Idempotent — does nothing if absent. */
30
+ delete(key: string): Promise<void>
31
+ }
32
+
33
+ /** Options accepted by `ingenium.idempotency(...)`. */
34
+ export interface IdempotencyOptions {
35
+ /**
36
+ * Header name carrying the idempotency key. Comparison is
37
+ * case-insensitive (Node lowercases header names automatically).
38
+ * Default `'Idempotency-Key'`.
39
+ */
40
+ header?: string
41
+
42
+ /** Backing cache. Default: an in-process `IdempotencyMemoryStore`. */
43
+ store?: IdempotencyStore
44
+
45
+ /**
46
+ * Time-to-live for cached responses, in seconds. After this elapses, the
47
+ * same key replays nothing and the handler runs again. Default `86400`
48
+ * (24h) — matches Stripe's documented behavior.
49
+ */
50
+ ttlSeconds?: number
51
+
52
+ /**
53
+ * Namespace function — distinguishes keys belonging to different callers
54
+ * so two clients can independently use the same idempotency-key string.
55
+ * Default uses the `Authorization` header (or `'anon'` when absent).
56
+ */
57
+ scope?: (ctx: IngeniumContext) => string
58
+
59
+ /**
60
+ * HTTP methods eligible for idempotency caching. Default: `['POST',
61
+ * 'PATCH', 'DELETE']` — only mutating methods. Safe methods (GET/HEAD/
62
+ * OPTIONS) and idempotent-by-spec PUT are skipped by default; opt in by
63
+ * extending this list if you need PUT semantics cached too.
64
+ */
65
+ methods?: readonly HttpMethod[]
66
+
67
+ /**
68
+ * Predicate deciding which response status codes are cacheable. Default:
69
+ * `(s) => s >= 200 && s < 500` — caches 2xx/3xx/4xx but NOT 5xx, matching
70
+ * Stripe's documented behavior. The intent: a transient 500 (DB blip,
71
+ * deploy race) must NOT be cached for the entire TTL — every retry would
72
+ * replay the same failure and the user would be permanently broken until
73
+ * the cache expires. Validation errors (4xx) are deterministic and worth
74
+ * caching. Override to opt in to 5xx caching or tighten further.
75
+ */
76
+ cacheable?: (statusCode: number) => boolean
77
+ }
78
+
79
+ /** Resolved options after defaults have been applied. Internal. */
80
+ export interface ResolvedIdempotencyOptions {
81
+ header: string
82
+ store: IdempotencyStore
83
+ ttlMs: number
84
+ scope: (ctx: IngeniumContext) => string
85
+ methodSet: ReadonlySet<HttpMethod>
86
+ cacheable: (statusCode: number) => boolean
87
+ }
package/src/index.ts ADDED
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Ingenium — Express DX, Hono/Fastify throughput.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ import { makeIngeniumFactory, type IngeniumFactory } from './app.ts'
8
+ import { jsonMiddleware, urlencodedMiddleware } from './body/middleware.ts'
9
+ import { staticMiddleware } from './static/middleware.ts'
10
+ import { corsMiddleware } from './cors/middleware.ts'
11
+ import { sse } from './sse/sse.ts'
12
+ import { rateLimit } from './rate-limit/middleware.ts'
13
+ import { csrfMiddleware } from './csrf/middleware.ts'
14
+ import { problemDetailsMiddleware } from './problem/middleware.ts'
15
+ import { idempotencyMiddleware } from './idempotency/middleware.ts'
16
+ import { jwtMiddleware } from './jwt/middleware.ts'
17
+ import { apiKeyMiddleware } from './api-key/middleware.ts'
18
+ import { openapiHandler } from './openapi/handler.ts'
19
+
20
+ // ───── App + Router ────────────────────────────────────────────────────────
21
+ export {
22
+ IngeniumApp,
23
+ type IngeniumAppOptions,
24
+ type IngeniumErrorHandler,
25
+ type RouteOptions,
26
+ type InjectRequest,
27
+ type InjectResponse,
28
+ } from './app.ts'
29
+ export { Router, RouteBuilder } from './router/router.ts'
30
+
31
+ // ───── Context + Body ──────────────────────────────────────────────────────
32
+ export { IngeniumContext, type ResponseBody, type IngeniumQuery } from './context/context.ts'
33
+ export type {
34
+ IngeniumCookies,
35
+ CookieSetOptions,
36
+ CookieGetOptions,
37
+ } from './context/cookies.ts'
38
+ export { IngeniumBody, type ParseSchema, type SafeParseSchema } from './context/body.ts'
39
+ export { IngeniumContextPool } from './context/pool.ts'
40
+ export type {
41
+ MultipartFile,
42
+ MultipartOptions,
43
+ MultipartResult,
44
+ } from './body/multipart-types.ts'
45
+
46
+ // ───── Standard Schema (https://standardschema.dev) ────────────────────────
47
+ export {
48
+ isStandardSchema,
49
+ type StandardSchemaV1,
50
+ type StandardResult,
51
+ type StandardIssue,
52
+ type StandardPathSegment,
53
+ type StandardSuccessResult,
54
+ type StandardFailureResult,
55
+ type StandardSchemaV1Props,
56
+ } from './schema/standard.ts'
57
+
58
+ // ───── Middleware + Handler types ──────────────────────────────────────────
59
+ export type { IngeniumMiddleware, IngeniumHandler, ComposedHandler } from './middleware/types.ts'
60
+ export { compose, composeWithHandler } from './middleware/compose.ts'
61
+
62
+ // ───── Router types ────────────────────────────────────────────────────────
63
+ export type { HttpMethod, ExtractParams } from './router/types.ts'
64
+ export { HTTP_METHODS } from './router/types.ts'
65
+ export { RouterTrie, TrieNode, type MatchResult, type MatchMiss } from './router/trie.ts'
66
+
67
+ // ───── Static-file middleware ──────────────────────────────────────────────
68
+ export { staticMiddleware as static_ } from './static/middleware.ts'
69
+ export type { StaticOptions } from './static/types.ts'
70
+
71
+ // ───── CORS middleware ─────────────────────────────────────────────────────
72
+ export { corsMiddleware as cors_ } from './cors/middleware.ts'
73
+ export type { CorsOptions, CorsOrigin, CorsOriginFn } from './cors/types.ts'
74
+
75
+ // ───── Errors ──────────────────────────────────────────────────────────────
76
+ export {
77
+ IngeniumError,
78
+ IngeniumNotFoundError,
79
+ IngeniumUnauthorizedError,
80
+ IngeniumMethodNotAllowedError,
81
+ IngeniumPayloadTooLargeError,
82
+ IngeniumValidationError,
83
+ IngeniumBadRequestError,
84
+ IngeniumHeaderInjectionError,
85
+ IngeniumUnserializableError,
86
+ IngeniumTimeoutError,
87
+ IngeniumHaltError,
88
+ } from './errors.ts'
89
+
90
+ // ───── JSON serialization helpers ──────────────────────────────────────────
91
+ export { safeJsonStringify, type SafeJsonStringifyOptions } from './util/safe-json.ts'
92
+
93
+ // ───── Transport (mainly for advanced users / tests) ───────────────────────
94
+ export type { Transport, TransportHooks, ListeningServer, CloseOptions } from './transport/types.ts'
95
+ export { NodeAdapter } from './transport/node.ts'
96
+ export { Http2Adapter, Http2cAdapter, type Http2AdapterOptions } from './transport/http2.ts'
97
+ export { gracefulShutdown, type ShutdownOptions } from './transport/shutdown.ts'
98
+
99
+ // ───── Trust-proxy ─────────────────────────────────────────────────────────
100
+ export { resolveForwarded, type TrustProxy, type ForwardedInfo } from './proxy/trust.ts'
101
+
102
+ // ───── Server-Sent Events helper ───────────────────────────────────────────
103
+ export { sse, type SseStream, type SseEvent } from './sse/sse.ts'
104
+ export { startKeepAlive } from './sse/keep-alive.ts'
105
+
106
+ // ───── Rate-limit middleware ───────────────────────────────────────────────
107
+ export { rateLimit } from './rate-limit/middleware.ts'
108
+ export { MemoryStore as RateLimitMemoryStore } from './rate-limit/store.ts'
109
+ export type { RateLimitOptions, RateLimitStore } from './rate-limit/types.ts'
110
+
111
+ // ───── CSRF middleware ─────────────────────────────────────────────────────
112
+ export { csrfMiddleware, IngeniumCsrfError } from './csrf/middleware.ts'
113
+ export type { CsrfOptions, CsrfStorage, CsrfCookieOptions, CsrfValueReader } from './csrf/types.ts'
114
+
115
+ // ───── Content negotiation ─────────────────────────────────────────────────
116
+ export {
117
+ parseAcceptHeader,
118
+ selectBest,
119
+ expandShorthand,
120
+ sortByPreference,
121
+ type ParsedAccept,
122
+ } from './negotiation/accept.ts'
123
+ export {
124
+ accepts,
125
+ acceptsCharsets,
126
+ acceptsLanguages,
127
+ acceptsEncodings,
128
+ type NegotiableCtx,
129
+ } from './negotiation/negotiate.ts'
130
+ export {
131
+ formatResponse,
132
+ type FormatHandlers,
133
+ type FormattableCtx,
134
+ } from './negotiation/format.ts'
135
+ export { isFresh, type HeaderBag } from './negotiation/fresh.ts'
136
+ export { computeEtag } from './negotiation/etag.ts'
137
+ export {
138
+ respondJsonWithEtag,
139
+ type JsonEtagOptions,
140
+ type JsonEtagCtx,
141
+ } from './negotiation/json-etag.ts'
142
+
143
+ // ───── RFC 7807 Problem Details middleware ─────────────────────────────────
144
+ export { problemDetailsMiddleware } from './problem/middleware.ts'
145
+ export { toProblemDetails } from './problem/serialize.ts'
146
+ export type { ProblemDetails, ProblemDetailsOptions } from './problem/types.ts'
147
+
148
+ // ───── Idempotency-Key middleware ──────────────────────────────────────────
149
+ export { idempotencyMiddleware } from './idempotency/middleware.ts'
150
+ export { IdempotencyMemoryStore } from './idempotency/store.ts'
151
+ export type {
152
+ CachedResponse,
153
+ IdempotencyOptions,
154
+ IdempotencyStore,
155
+ } from './idempotency/types.ts'
156
+
157
+ // ───── JWT middleware ──────────────────────────────────────────────────────
158
+ export { jwtMiddleware } from './jwt/middleware.ts'
159
+ export { verifyJwt } from './jwt/verify.ts'
160
+ export { fetchJwks, clearJwksCache } from './jwt/jwks.ts'
161
+ export type {
162
+ JwtAlgorithm,
163
+ JwtHeader,
164
+ JwtKey,
165
+ JwtOptions,
166
+ JwtSecret,
167
+ JwtSecretResolver,
168
+ JwtTokenReader,
169
+ JwtVerified,
170
+ JwtLogger,
171
+ } from './jwt/types.ts'
172
+
173
+ // ───── API-key middleware ──────────────────────────────────────────────────
174
+ export { apiKeyMiddleware } from './api-key/middleware.ts'
175
+ export type { ApiKeyOptions, ApiKeyValidator, ApiKeyLogger } from './api-key/types.ts'
176
+
177
+ // ───── OpenAPI 3.1 spec generation ─────────────────────────────────────────
178
+ export { generateOpenApi } from './openapi/generate.ts'
179
+ export type { GenerateOpenApiOptions } from './openapi/generate.ts'
180
+ export { openapiHandler } from './openapi/handler.ts'
181
+ export type { RouteDescriptor } from './openapi/describe.ts'
182
+ export type {
183
+ OpenApiSpec,
184
+ PathItem,
185
+ Operation,
186
+ Parameter,
187
+ RequestBody,
188
+ Response as OpenApiResponse,
189
+ Schema as OpenApiSchema,
190
+ Info as OpenApiInfo,
191
+ Server as OpenApiServer,
192
+ Tag as OpenApiTag,
193
+ Components as OpenApiComponents,
194
+ SecurityScheme as OpenApiSecurityScheme,
195
+ SecurityRequirement as OpenApiSecurityRequirement,
196
+ } from './openapi/types.ts'
197
+
198
+ // ───── Background jobs ─────────────────────────────────────────────────────
199
+ export { IngeniumQueue } from './jobs/queue.ts'
200
+ export { QueueRegistry } from './jobs/registry.ts'
201
+ export { MemoryQueueStore } from './jobs/store-memory.ts'
202
+ export type {
203
+ QueueOptions,
204
+ QueueWorker,
205
+ QueueStore,
206
+ RetryPolicy,
207
+ FailedJob,
208
+ JobHandle,
209
+ RegisteredQueue,
210
+ } from './jobs/types.ts'
211
+
212
+ // ───── Cron scheduling ─────────────────────────────────────────────────────
213
+ export { IngeniumCronJob } from './cron/scheduler.ts'
214
+ export type { CronHandler, CronOptions } from './cron/scheduler.ts'
215
+ export { CronRegistry } from './cron/registry.ts'
216
+ export { parseCronSpec, nextFireFrom } from './cron/parser.ts'
217
+ export type { CronMatch } from './cron/parser.ts'
218
+
219
+ // ───── Session middleware ──────────────────────────────────────────────────
220
+ export { sessionMiddleware } from './session/middleware.ts'
221
+ export { MemoryStore as SessionMemoryStore } from './session/store-memory.ts'
222
+ export type {
223
+ Session,
224
+ SessionOptions,
225
+ SessionStore,
226
+ SessionCookieOptions,
227
+ } from './session/types.ts'
228
+
229
+ // ───── WebSocket adapter (optional `ws` peer dep) ──────────────────────────
230
+ export {
231
+ enableWebSockets,
232
+ createWebSocketRegistrar,
233
+ peerHasWs,
234
+ WsNodeAdapter,
235
+ type EnableWebSocketsOptions,
236
+ type WebSocketHandler,
237
+ type WebSocketHandlerOptions,
238
+ type WsIntegrator,
239
+ type WsRegistrar,
240
+ type WebSocket,
241
+ } from './ws/index.ts'
242
+
243
+ // ───── Plugin system ───────────────────────────────────────────────────────
244
+ export type {
245
+ IngeniumPlugin,
246
+ PluginTarget,
247
+ Hooks,
248
+ RegistrationEvent,
249
+ Decorator,
250
+ LazyDecorator,
251
+ EagerDecorator,
252
+ OnRouteHook,
253
+ OnComposeHook,
254
+ OnRequestHook,
255
+ OnResponseHook,
256
+ OnErrorHook,
257
+ } from './plugin/types.ts'
258
+ export { HooksRegistry } from './plugin/hooks.ts'
259
+ export { DecoratorRegistry } from './plugin/decorators.ts'
260
+ export { ScopedApp } from './app/scope.ts'
261
+
262
+ // ───── Default factory + body parsers ──────────────────────────────────────
263
+
264
+ /**
265
+ * Create a new Ingenium application.
266
+ *
267
+ * @example
268
+ * import { ingenium } from 'ingenium'
269
+ *
270
+ * const app = ingenium()
271
+ * app.get('/', (ctx) => ({ hello: 'world' }))
272
+ * await app.listen(3000)
273
+ */
274
+ const ingeniumCore: IngeniumFactory = makeIngeniumFactory()
275
+
276
+ /**
277
+ * The `ingenium` export is callable AND has static helpers attached:
278
+ *
279
+ * - `ingenium(opts?)` — create an app
280
+ * - `ingenium.Router()` — create a mountable router
281
+ * - `ingenium.json(opts?)` — Express-compat body-parser shim (no-op; parsing is lazy)
282
+ * - `ingenium.urlencoded(opts?)` — same, for `application/x-www-form-urlencoded`
283
+ * - `ingenium.static(root, opts?)` — serve files from a directory
284
+ * - `ingenium.cors(opts?)` — CORS middleware (simple + preflight)
285
+ */
286
+ // ───── Sinatra-style top-level ──────────────────────────────────────────────
287
+ //
288
+ // Lets users skip the app object entirely:
289
+ //
290
+ // import { get, listen } from 'ingenium'
291
+ // get('/', () => 'hi')
292
+ // await listen(3000)
293
+ //
294
+ // Every verb routes to a lazy singleton `IngeniumApp` (see `defaultApp()`).
295
+ // `_resetDefaultApp` is test-only and throws under NODE_ENV=production.
296
+ export {
297
+ defaultApp,
298
+ _resetDefaultApp,
299
+ get,
300
+ post,
301
+ put,
302
+ patch,
303
+ del as delete,
304
+ head,
305
+ options,
306
+ use,
307
+ onError,
308
+ listen,
309
+ before,
310
+ after,
311
+ } from './sinatra/top-level.ts'
312
+
313
+ export const ingenium = Object.assign(ingeniumCore, {
314
+ json: jsonMiddleware,
315
+ urlencoded: urlencodedMiddleware,
316
+ static: staticMiddleware,
317
+ cors: corsMiddleware,
318
+ csrf: csrfMiddleware,
319
+ sse,
320
+ rateLimit,
321
+ problemDetails: problemDetailsMiddleware,
322
+ idempotency: idempotencyMiddleware,
323
+ jwt: jwtMiddleware,
324
+ apiKey: apiKeyMiddleware,
325
+ openapiHandler,
326
+ })
327
+
328
+ export default ingenium