@nifrajs/core 1.13.0 → 2.0.0

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 (157) hide show
  1. package/README.md +10 -1
  2. package/dist/assurance.d.ts +11 -2
  3. package/dist/assurance.d.ts.map +1 -1
  4. package/dist/assurance.js +20 -0
  5. package/dist/assurance.js.map +1 -1
  6. package/dist/effect-ledger.d.ts +9 -0
  7. package/dist/effect-ledger.d.ts.map +1 -0
  8. package/dist/effect-ledger.js +8 -0
  9. package/dist/effect-ledger.js.map +1 -0
  10. package/dist/idempotency-plugin.d.ts +8 -0
  11. package/dist/idempotency-plugin.d.ts.map +1 -0
  12. package/dist/idempotency-plugin.js +8 -0
  13. package/dist/idempotency-plugin.js.map +1 -0
  14. package/dist/index.d.ts +6 -35
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6 -31
  17. package/dist/index.js.map +1 -1
  18. package/dist/mcp.d.ts +7 -0
  19. package/dist/mcp.d.ts.map +1 -0
  20. package/dist/mcp.js +56 -0
  21. package/dist/mcp.js.map +1 -0
  22. package/dist/node-direct.d.ts +5 -0
  23. package/dist/node-direct.d.ts.map +1 -0
  24. package/dist/node-direct.js +27 -0
  25. package/dist/node-direct.js.map +1 -0
  26. package/dist/reflection.d.ts +5 -0
  27. package/dist/reflection.d.ts.map +1 -1
  28. package/dist/reflection.js +2 -0
  29. package/dist/reflection.js.map +1 -1
  30. package/dist/server/client-ip.d.ts +35 -0
  31. package/dist/server/client-ip.d.ts.map +1 -0
  32. package/dist/server/client-ip.js +42 -0
  33. package/dist/server/client-ip.js.map +1 -0
  34. package/dist/server/context.d.ts +47 -2
  35. package/dist/server/context.d.ts.map +1 -1
  36. package/dist/server/contract.d.ts +14 -4
  37. package/dist/server/contract.d.ts.map +1 -1
  38. package/dist/server/contract.js +8 -2
  39. package/dist/server/contract.js.map +1 -1
  40. package/dist/server/http.d.ts +14 -0
  41. package/dist/server/http.d.ts.map +1 -0
  42. package/dist/server/http.js +45 -0
  43. package/dist/server/http.js.map +1 -0
  44. package/dist/server/idempotency-lane.d.ts +44 -0
  45. package/dist/server/idempotency-lane.d.ts.map +1 -0
  46. package/dist/server/idempotency-lane.js +181 -0
  47. package/dist/server/idempotency-lane.js.map +1 -0
  48. package/dist/server/install.d.ts +21 -0
  49. package/dist/server/install.d.ts.map +1 -0
  50. package/dist/server/install.js +21 -0
  51. package/dist/server/install.js.map +1 -0
  52. package/dist/server/ledger-lane.d.ts +53 -0
  53. package/dist/server/ledger-lane.d.ts.map +1 -0
  54. package/dist/server/ledger-lane.js +82 -0
  55. package/dist/server/ledger-lane.js.map +1 -0
  56. package/dist/server/mcp-hook.d.ts +28 -0
  57. package/dist/server/mcp-hook.d.ts.map +1 -0
  58. package/dist/server/mcp-hook.js +2 -0
  59. package/dist/server/mcp-hook.js.map +1 -0
  60. package/dist/server/node-outcome-hook.d.ts +9 -0
  61. package/dist/server/node-outcome-hook.d.ts.map +1 -0
  62. package/dist/server/node-outcome-hook.js +2 -0
  63. package/dist/server/node-outcome-hook.js.map +1 -0
  64. package/dist/server/node-outcome.d.ts +37 -0
  65. package/dist/server/node-outcome.d.ts.map +1 -0
  66. package/dist/server/node-outcome.js +75 -0
  67. package/dist/server/node-outcome.js.map +1 -0
  68. package/dist/server/plugin.d.ts +78 -0
  69. package/dist/server/plugin.d.ts.map +1 -0
  70. package/dist/server/plugin.js +57 -0
  71. package/dist/server/plugin.js.map +1 -0
  72. package/dist/server/query.d.ts +18 -0
  73. package/dist/server/query.d.ts.map +1 -0
  74. package/dist/server/query.js +102 -0
  75. package/dist/server/query.js.map +1 -0
  76. package/dist/server/registry.d.ts +31 -6
  77. package/dist/server/registry.d.ts.map +1 -1
  78. package/dist/server/request-context.d.ts +51 -0
  79. package/dist/server/request-context.d.ts.map +1 -0
  80. package/dist/server/request-context.js +184 -0
  81. package/dist/server/request-context.js.map +1 -0
  82. package/dist/server/respond.d.ts +17 -0
  83. package/dist/server/respond.d.ts.map +1 -0
  84. package/dist/server/respond.js +81 -0
  85. package/dist/server/respond.js.map +1 -0
  86. package/dist/server/runtime-core.d.ts +36 -0
  87. package/dist/server/runtime-core.d.ts.map +1 -0
  88. package/dist/server/runtime-core.js +51 -0
  89. package/dist/server/runtime-core.js.map +1 -0
  90. package/dist/server/server-types.d.ts +203 -0
  91. package/dist/server/server-types.d.ts.map +1 -0
  92. package/dist/server/server-types.js +2 -0
  93. package/dist/server/server-types.js.map +1 -0
  94. package/dist/server/server.d.ts +107 -345
  95. package/dist/server/server.d.ts.map +1 -1
  96. package/dist/server/server.js +214 -831
  97. package/dist/server/server.js.map +1 -1
  98. package/dist/server/sse-hook.d.ts +10 -0
  99. package/dist/server/sse-hook.d.ts.map +1 -0
  100. package/dist/server/sse-hook.js +2 -0
  101. package/dist/server/sse-hook.js.map +1 -0
  102. package/dist/server/sse.d.ts +7 -23
  103. package/dist/server/sse.d.ts.map +1 -1
  104. package/dist/server/sse.js +18 -0
  105. package/dist/server/sse.js.map +1 -1
  106. package/dist/server/websocket.d.ts +17 -1
  107. package/dist/server/websocket.d.ts.map +1 -1
  108. package/dist/server/websocket.js.map +1 -1
  109. package/dist/server/ws-hook.d.ts +8 -11
  110. package/dist/server/ws-hook.d.ts.map +1 -1
  111. package/dist/server/ws-hook.js +7 -14
  112. package/dist/server/ws-hook.js.map +1 -1
  113. package/dist/server.d.ts +21 -0
  114. package/dist/server.d.ts.map +1 -0
  115. package/dist/server.js +16 -0
  116. package/dist/server.js.map +1 -0
  117. package/dist/ws.d.ts +16 -7
  118. package/dist/ws.d.ts.map +1 -1
  119. package/dist/ws.js +25 -10
  120. package/dist/ws.js.map +1 -1
  121. package/package.json +77 -6
  122. package/src/assurance.ts +36 -1
  123. package/src/effect-ledger.ts +9 -0
  124. package/src/idempotency-plugin.ts +7 -0
  125. package/src/index.ts +6 -133
  126. package/src/mcp.ts +65 -0
  127. package/src/node-direct.ts +37 -0
  128. package/src/reflection.ts +7 -0
  129. package/src/server/client-ip.ts +60 -0
  130. package/src/server/context.ts +50 -2
  131. package/src/server/contract.ts +26 -7
  132. package/src/server/http.ts +58 -0
  133. package/src/server/idempotency-lane.ts +287 -0
  134. package/src/server/install.ts +28 -0
  135. package/src/server/ledger-lane.ts +125 -0
  136. package/src/server/mcp-hook.ts +41 -0
  137. package/src/server/node-outcome-hook.ts +9 -0
  138. package/src/server/node-outcome.ts +112 -0
  139. package/src/server/plugin.ts +92 -0
  140. package/src/server/query.ts +119 -0
  141. package/src/server/registry.ts +36 -6
  142. package/src/server/request-context.ts +271 -0
  143. package/src/server/respond.ts +93 -0
  144. package/src/server/runtime-core.ts +74 -0
  145. package/src/server/server-types.ts +204 -0
  146. package/src/server/server.ts +402 -1314
  147. package/src/server/sse-hook.ts +14 -0
  148. package/src/server/sse.ts +26 -0
  149. package/src/server/websocket.ts +20 -0
  150. package/src/server/ws-hook.ts +8 -19
  151. package/src/server.ts +92 -0
  152. package/src/ws.ts +33 -10
  153. package/dist/invariants.d.ts +0 -70
  154. package/dist/invariants.d.ts.map +0 -1
  155. package/dist/invariants.js +0 -487
  156. package/dist/invariants.js.map +0 -1
  157. package/src/invariants.ts +0 -604
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The `SseRuntime` contract - the streaming implementation that `.use(streaming())` installs (from
3
+ * `@nifrajs/core/sse`). `server.ts` type-imports only this, so the ReadableStream framing code stays
4
+ * out of a bare server bundle until a route opts in.
5
+ */
6
+ import type { SSEContext, SSEInit, TypedSSEStream } from "./sse.ts"
7
+
8
+ export interface SseRuntime {
9
+ response<Event>(
10
+ context: SSEContext,
11
+ run: (stream: TypedSSEStream<Event>) => void | Promise<void>,
12
+ init?: SSEInit,
13
+ ): Response
14
+ }
package/src/server/sse.ts CHANGED
@@ -21,6 +21,10 @@
21
21
  * stream.signal.addEventListener("abort", () => { off(); resolve() }, { once: true }))
22
22
  * }, { keepAlive: 15_000 }))
23
23
  */
24
+ import { INSTALL_SSE } from "./install.ts"
25
+ import type { IdentityPlugin } from "./plugin.ts"
26
+ import type { AnyServer } from "./server.ts"
27
+ import type { SseRuntime } from "./sse-hook.ts"
24
28
 
25
29
  /** One SSE frame. Every field is optional; `data` may be multi-line (emitted as multiple `data:` lines). */
26
30
  export interface SSEMessage {
@@ -188,3 +192,25 @@ export function sse(
188
192
  if (!headers.has("x-accel-buffering")) headers.set("x-accel-buffering", "no") // defeat proxy buffering
189
193
  return new Response(body, { status: init.status ?? 200, headers })
190
194
  }
195
+
196
+ /** The install seam a server exposes so the `streaming()` plugin can hand it the runtime. */
197
+ interface SseInstallable {
198
+ [INSTALL_SSE](runtime: SseRuntime): void
199
+ }
200
+
201
+ /**
202
+ * Enable `.sse()` streaming routes: `.use(streaming())` installs the SSE runtime. Without it, an
203
+ * `.sse()` route is a registration error, so the ReadableStream framing stays out of non-SSE bundles.
204
+ * The `sse()` / `typedSSEStream()` helpers ship from this same subpath for use inside handlers.
205
+ */
206
+ export function streaming(): IdentityPlugin {
207
+ const apply = <S extends AnyServer>(app: S): S => {
208
+ ;(app as unknown as SseInstallable)[INSTALL_SSE]({
209
+ response(context, run, init) {
210
+ return sse(context, (stream) => run(typedSSEStream(stream)), init)
211
+ },
212
+ })
213
+ return app
214
+ }
215
+ return Object.assign(apply, { pluginName: "nifra:streaming" }) as IdentityPlugin
216
+ }
@@ -130,6 +130,7 @@ export interface WebSocketHandler<
130
130
  Data = unknown,
131
131
  Env = unknown,
132
132
  Schema extends StandardSchemaV1 | undefined = undefined,
133
+ Send extends StandardSchemaV1 | undefined = undefined,
133
134
  > {
134
135
  /**
135
136
  * Cross-site WebSocket hijacking (CSWSH) guard — checked BEFORE `upgrade()`. A browser does not
@@ -159,6 +160,13 @@ export interface WebSocketHandler<
159
160
  * non-JSON or schema-invalid frame is routed to `onInvalidMessage` (or dropped if that's omitted).
160
161
  */
161
162
  messageSchema?: Schema
163
+ /**
164
+ * The **outbound** frame contract (server → client), a Standard Schema. Purely type-level: it types
165
+ * the frames the typed client's `.ws()` handle receives, and documents what this route pushes. The
166
+ * server does NOT runtime-validate its own sends - the inbound `messageSchema` guards the trust
167
+ * boundary; outbound honesty is the handler author's code, checked by tests.
168
+ */
169
+ sendSchema?: Send
162
170
  open?(ws: NifraWebSocket<Data>): MaybePromise<void>
163
171
  message?(ws: NifraWebSocket<Data>, data: WsMessageInput<Schema>): MaybePromise<void>
164
172
  /** Inbound frame that failed JSON parse or `messageSchema` validation (only fires when a schema is
@@ -189,8 +197,20 @@ export type WebSocketUpgradeOutcome =
189
197
  readonly data: unknown
190
198
  /** The app's pub/sub registry — the adapter wires `ws.subscribe` + close-cleanup to it. */
191
199
  readonly pubsub: TopicRegistry
200
+ /** The installed runtime's {@link attachWebSocket}, carried on the outcome so an adapter can wire
201
+ * a standard socket without a static import of the WS implementation (which would defeat the
202
+ * `.use(websocket())` tree-shaking). `@nifrajs/workers` may import `attachWebSocket` directly instead. */
203
+ readonly attach: WsAttach
192
204
  }
193
205
 
206
+ /** The socket-wiring signature the upgrade outcome carries (the installed runtime's `attach`). */
207
+ export type WsAttach = (
208
+ socket: StandardWebSocket,
209
+ handler: WebSocketHandler,
210
+ data: unknown,
211
+ options: { openNow: boolean; pubsub: TopicRegistry },
212
+ ) => NifraWebSocket
213
+
194
214
  /**
195
215
  * A standard server-side `WebSocket` — the half returned by Deno's `Deno.upgradeWebSocket` and the
196
216
  * Workers `WebSocketPair`. {@link attachWebSocket} wires one to a nifra handler, so the Deno and Workers
@@ -1,10 +1,11 @@
1
1
  /**
2
- * Lazy WebSocket runtime seam. `server.ts` never imports the WS implementation statically — the
3
- * `@nifrajs/core/ws` subpath entry registers it here on import. That keeps the WS dispatcher, pub/sub
4
- * registry, Bun socket plumbing, and message-schema validation out of the base server bundle for
5
- * apps that never call `app.ws()` (~1.5 KB gzipped on the minimal-app benchmark).
2
+ * WebSocket runtime contract. `server.ts` type-imports only this; the `.use(websocket())` plugin
3
+ * (from `@nifrajs/core/ws`) installs the implementation on a server instance via the `INSTALL_WS`
4
+ * symbol seam. That keeps the WS dispatcher, pub/sub registry, Bun socket plumbing, and
5
+ * message-schema validation out of the base server bundle for apps that never call `app.ws()`
6
+ * (~1.5 KB gzipped on the minimal-app benchmark).
6
7
  *
7
- * Only type imports below — this module must stay weightless, it is always in the base bundle.
8
+ * Only type declarations below - this module must stay weightless, it is always in the base bundle.
8
9
  */
9
10
 
10
11
  import type {
@@ -25,13 +26,13 @@ export interface BunWsHandlers {
25
26
 
26
27
  /** What `@nifrajs/core/ws` registers: everything `server.ts` and `toFetchHandler` need at runtime. */
27
28
  export interface WsRuntime {
28
- /** `wrapWebSocketMessageValidation` — applied once at `app.ws()` registration. */
29
+ /** `wrapWebSocketMessageValidation` - applied once at `app.ws()` registration. */
29
30
  wrapHandler(handler: WebSocketHandler): WebSocketHandler
30
31
  /** One in-process pub/sub registry per app (backs `ws.subscribe` + `app.publish`). */
31
32
  createTopics(): TopicRegistry
32
33
  /** The Bun `websocket` config for `listen()` when the app has WS routes. */
33
34
  bunHandlers(topics: TopicRegistry): BunWsHandlers
34
- /** `attachWebSocket` — wires a standard server socket (Workers `WebSocketPair`) to a handler. */
35
+ /** `attachWebSocket` - wires a standard server socket (Workers `WebSocketPair`) to a handler. */
35
36
  attach(
36
37
  socket: StandardWebSocket,
37
38
  handler: WebSocketHandler,
@@ -39,15 +40,3 @@ export interface WsRuntime {
39
40
  options: { openNow: boolean; pubsub: TopicRegistry },
40
41
  ): NifraWebSocket
41
42
  }
42
-
43
- let runtime: WsRuntime | undefined
44
-
45
- /** Called by `@nifrajs/core/ws` on import. Last registration wins (idempotent in practice — there is
46
- * one implementation; double-import is a no-op re-set of the same functions). */
47
- export function setWsRuntime(impl: WsRuntime): void {
48
- runtime = impl
49
- }
50
-
51
- export function getWsRuntime(): WsRuntime | undefined {
52
- return runtime
53
- }
package/src/server.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Lean server entry for the common Nifra runtime.
3
+ *
4
+ * Use `@nifrajs/core/server` or the equivalent lean package root when building an HTTP app.
5
+ * Optional systems such as
6
+ * causality, invariants, manifests, reflection, and capability tooling live at
7
+ * their dedicated subpaths. Keeping this entry curated is intentional: do not
8
+ * export an opt-in module here unless the Server implementation already loads it.
9
+ */
10
+
11
+ export { FrameworkError, RouteConfigError, type RouteConfigErrorCode } from "./errors.ts"
12
+ export { FRAMEWORK_NAME, type FrameworkName } from "./internal/brand.ts"
13
+ export { METHODS, type Method, Router, type RouterMatch } from "./router/router.ts"
14
+ export type {
15
+ InferInput,
16
+ InferOutput,
17
+ StandardIssue,
18
+ StandardResult,
19
+ StandardSchemaV1,
20
+ StandardTypes,
21
+ ValidationOutcome,
22
+ } from "./schema/standard.ts"
23
+ export type {
24
+ Context,
25
+ Params,
26
+ Platform,
27
+ Prettify,
28
+ ResponseControls,
29
+ RouteSchema,
30
+ } from "./server/context.ts"
31
+ export {
32
+ type CookieOptions,
33
+ parseCookies,
34
+ serializeCookie,
35
+ signValue,
36
+ unsignValue,
37
+ } from "./server/cookies.ts"
38
+ export {
39
+ commonSecretPatterns,
40
+ jsonLogger,
41
+ type LogFields,
42
+ type Logger,
43
+ type RedactOptions,
44
+ redactLogFields,
45
+ silentLogger,
46
+ } from "./server/logger.ts"
47
+ export type { Registry, RouteInfo } from "./server/registry.ts"
48
+ export {
49
+ type AdmissionController,
50
+ type AdmissionDecision,
51
+ type AnyServer,
52
+ type DurableObjectNamespaceLike,
53
+ defineIdentityPlugin,
54
+ definePlugin,
55
+ defineRouterPlugin,
56
+ type ExecutionContext,
57
+ type Handler,
58
+ type IdentityPlugin,
59
+ type McpPromptDescriptor,
60
+ type McpResourceDescriptor,
61
+ type Middleware,
62
+ type NifraPlugin,
63
+ type NodeServeOutcome,
64
+ type OnRequestResult,
65
+ type PromptArgument,
66
+ type PromptMessage,
67
+ type ResponseFinalization,
68
+ type RouteDescriptor,
69
+ type RunningServer,
70
+ type ScheduledController,
71
+ type ScheduledHandler,
72
+ Server,
73
+ type ServerOptions,
74
+ server,
75
+ type ToolAnnotations,
76
+ toFetchHandler,
77
+ } from "./server/server.ts"
78
+ export type {
79
+ SSEContext,
80
+ SSEInit,
81
+ SSEMessage,
82
+ SSEStream,
83
+ TypedSSEStream,
84
+ } from "./server/sse.ts"
85
+ export type {
86
+ NifraWebSocket,
87
+ StandardWebSocket,
88
+ WebSocketContext,
89
+ WebSocketData,
90
+ WebSocketHandler,
91
+ WebSocketUpgradeOutcome,
92
+ } from "./server/websocket.ts"
package/src/ws.ts CHANGED
@@ -1,33 +1,56 @@
1
1
  /**
2
- * WebSocket runtime for `@nifrajs/core` — import once at your server entry to enable `app.ws()`:
2
+ * The opt-in WebSocket runtime plugin.
3
3
  *
4
- * import "@nifrajs/core/ws"
4
+ * `.use(websocket())` enables `app.ws()`:
5
5
  *
6
- * const app = server().ws("/chat", { message: (ws, data) => ws.send(data) })
6
+ * import { websocket } from "@nifrajs/core/ws"
7
7
  *
8
- * Why a subpath: an app that never uses WebSockets shouldn't ship the WS dispatcher, pub/sub
9
- * registry, and Bun socket plumbing in its server bundle (~1.5 KB gzipped on the minimal-app size
10
- * benchmark). Importing this module registers the runtime with the core server (idempotent);
11
- * calling `app.ws()` without it fails loud at boot with a `WS_RUNTIME_MISSING` error pointing here.
8
+ * const app = server().use(websocket()).ws("/chat", { message: (ws, data) => ws.send(data) })
9
+ *
10
+ * Without it, `app.ws()` throws at registration (fail-closed), so an app that never uses WebSockets
11
+ * never evaluates or bundles the WS dispatcher, pub/sub registry, and Bun socket plumbing (~1.5 KB
12
+ * gzipped on the minimal-app size benchmark). The runtime installs on that server instance only -
13
+ * the same `.use()` install seam as `mcp()` / `streaming()` / `idempotency()`, not a process-global
14
+ * or a side-effect import.
12
15
  *
13
16
  * Serving adapters that wire standard sockets themselves (`@nifrajs/workers`) import
14
17
  * `attachWebSocket` / `TopicRegistry` from here too.
15
18
  */
16
19
 
20
+ import { INSTALL_WS } from "./server/install.ts"
21
+ import type { IdentityPlugin } from "./server/plugin.ts"
22
+ import type { AnyServer } from "./server/server.ts"
17
23
  import {
18
24
  attachWebSocket,
19
25
  TopicRegistry,
20
26
  wrapWebSocketMessageValidation,
21
27
  } from "./server/websocket.ts"
22
28
  import { createBunWsHandlers } from "./server/ws-bun.ts"
23
- import { setWsRuntime } from "./server/ws-hook.ts"
29
+ import type { WsRuntime } from "./server/ws-hook.ts"
24
30
 
25
- setWsRuntime({
31
+ const WS_RUNTIME: WsRuntime = {
26
32
  wrapHandler: wrapWebSocketMessageValidation,
27
33
  createTopics: () => new TopicRegistry(),
28
34
  bunHandlers: createBunWsHandlers,
29
35
  attach: attachWebSocket,
30
- })
36
+ }
37
+
38
+ /** The install seam a server exposes so the `websocket()` plugin can hand it the runtime. */
39
+ interface WsInstallable {
40
+ [INSTALL_WS](runtime: WsRuntime): void
41
+ }
42
+
43
+ /**
44
+ * Enable WebSocket routes on a server: `.use(websocket())` turns on `app.ws()`. Applying it twice is
45
+ * a no-op (named plugin dedupe).
46
+ */
47
+ export function websocket(): IdentityPlugin {
48
+ const apply = <S extends AnyServer>(app: S): S => {
49
+ ;(app as unknown as WsInstallable)[INSTALL_WS](WS_RUNTIME)
50
+ return app
51
+ }
52
+ return Object.assign(apply, { pluginName: "nifra:websocket" }) as IdentityPlugin
53
+ }
31
54
 
32
55
  export type {
33
56
  NifraWebSocket,
@@ -1,70 +0,0 @@
1
- /**
2
- * Contract-generated invariant tests: the declared contract *is* the test suite.
3
- *
4
- * `runContractInvariants(app)` reads each route's reflected contract (schemas, classification) and
5
- * verifies, with deterministic seeded fuzzing, the properties the contract already promises:
6
- *
7
- * 1. **Classification consistency** (static) — a route whose response fields are tagged more
8
- * sensitive than its declared route-level classification is understating what it emits.
9
- * 2. **Valid input never crashes** (dynamic) — schema-generated valid requests must not produce 5xx.
10
- * 3. **Response conformance** (dynamic) — a 2xx response body must validate against the declared
11
- * response schema; an undeclared response shape is exactly where sensitive data leaks.
12
- * 4. **Validation fails closed** (dynamic) — schema-violating bodies must be rejected (4xx), never
13
- * accepted (2xx) and never crash (5xx).
14
- *
15
- * Everything is deterministic: a seeded PRNG (no ambient randomness, no wall clock), so a failing
16
- * case reproduces from its seed. Routes whose inputs cannot be generated from their JSON Schema are
17
- * reported in `skipped` — never silently dropped.
18
- */
19
- export type InvariantFindingCode = "classification-understated" | "server-error-on-valid-input" | "response-schema-violation" | "validation-bypass" | "server-error-on-invalid-input";
20
- export interface InvariantFinding {
21
- readonly code: InvariantFindingCode;
22
- readonly method: string;
23
- readonly path: string;
24
- /** The case's deterministic seed — rerun with it to reproduce the exact request. */
25
- readonly seed?: number;
26
- readonly message: string;
27
- }
28
- export interface SkippedRoute {
29
- readonly method: string;
30
- readonly path: string;
31
- readonly reason: string;
32
- }
33
- export interface InvariantReport {
34
- readonly ok: boolean;
35
- /** Routes exercised dynamically. */
36
- readonly tested: readonly {
37
- readonly method: string;
38
- readonly path: string;
39
- }[];
40
- /** Routes that could not be exercised, with the reason — reported, never silently dropped. */
41
- readonly skipped: readonly SkippedRoute[];
42
- readonly findings: readonly InvariantFinding[];
43
- }
44
- export interface RunInvariantsOptions {
45
- /** Deterministic seed for the fuzzer. Default 1. */
46
- readonly seed?: number;
47
- /** Valid-input cases per route. Default 8. */
48
- readonly casesPerRoute?: number;
49
- /** Invalid-input (mutated) cases per route with a body schema. Default 4. */
50
- readonly invalidCasesPerRoute?: number;
51
- /**
52
- * Explicit isolated executor for dynamic cases. It must point at a disposable test app, sandbox,
53
- * or transaction that is rolled back. The runner never calls `source.fetch()` implicitly: doing so
54
- * could charge cards, send mail, or mutate production state merely by running a verification tool.
55
- */
56
- readonly executor?: InvariantExecutor;
57
- }
58
- export type InvariantExecutor = (request: Request) => Response | Promise<Response>;
59
- /** Opaque route-reflection source. The runner never invokes an app's live fetch method implicitly. */
60
- type ReflectableApp = object;
61
- /** Deterministic PRNG (mulberry32). Ambient randomness would make failures unreproducible. */
62
- export declare function createSeededRandom(seed: number): () => number;
63
- /** Run the contract-derived invariant suite against a reflectable app. Pure; no ambient state. */
64
- /**
65
- * @deprecated Use `runAdversarialContract` from `@nifrajs/testing`. The testing package is the
66
- * authoritative deep contract laboratory and powers CLI L4; this export remains for compatibility.
67
- */
68
- export declare function runContractInvariants(app: ReflectableApp, options?: RunInvariantsOptions): Promise<InvariantReport>;
69
- export {};
70
- //# sourceMappingURL=invariants.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"invariants.d.ts","sourceRoot":"","sources":["../src/invariants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAWH,MAAM,MAAM,oBAAoB,GAC5B,4BAA4B,GAC5B,6BAA6B,GAC7B,2BAA2B,GAC3B,mBAAmB,GACnB,+BAA+B,CAAA;AAEnC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAA;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,oFAAoF;IACpF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAA;IACpB,oCAAoC;IACpC,QAAQ,CAAC,MAAM,EAAE,SAAS;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC9E,8FAA8F;IAC9F,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IACzC,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAC/C;AAED,MAAM,WAAW,oBAAoB;IACnC,oDAAoD;IACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,8CAA8C;IAC9C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IACtC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAA;CACtC;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAElF,sGAAsG;AACtG,KAAK,cAAc,GAAG,MAAM,CAAA;AA0B5B,8FAA8F;AAC9F,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,MAAM,CAS7D;AA8ND,kGAAkG;AAClG;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,cAAc,EACnB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,eAAe,CAAC,CAmG1B"}