@nifrajs/core 3.1.0 → 3.2.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 (146) hide show
  1. package/README.md +21 -0
  2. package/dist/durable-adapters.d.ts.map +1 -1
  3. package/dist/durable-adapters.js +26 -3
  4. package/dist/durable-adapters.js.map +1 -1
  5. package/dist/durable-approval.d.ts +56 -0
  6. package/dist/durable-approval.d.ts.map +1 -0
  7. package/dist/durable-approval.js +233 -0
  8. package/dist/durable-approval.js.map +1 -0
  9. package/dist/durable-effect.d.ts +42 -0
  10. package/dist/durable-effect.d.ts.map +1 -0
  11. package/dist/durable-effect.js +234 -0
  12. package/dist/durable-effect.js.map +1 -0
  13. package/dist/durable-execution.d.ts +8 -346
  14. package/dist/durable-execution.d.ts.map +1 -1
  15. package/dist/durable-execution.js +7 -1150
  16. package/dist/durable-execution.js.map +1 -1
  17. package/dist/durable-saga.d.ts +94 -0
  18. package/dist/durable-saga.d.ts.map +1 -0
  19. package/dist/durable-saga.js +550 -0
  20. package/dist/durable-saga.js.map +1 -0
  21. package/dist/edge-kit.d.ts +2 -2
  22. package/dist/edge-kit.d.ts.map +1 -1
  23. package/dist/edge-kit.js +2 -2
  24. package/dist/edge-kit.js.map +1 -1
  25. package/dist/index.d.ts +23 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +18 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/internal/durable-approval-protocol.d.ts +1 -1
  30. package/dist/internal/durable-approval-protocol.d.ts.map +1 -1
  31. package/dist/internal/durable-effect-protocol.d.ts +1 -1
  32. package/dist/internal/durable-effect-protocol.d.ts.map +1 -1
  33. package/dist/internal/durable-saga-protocol.d.ts +1 -1
  34. package/dist/internal/durable-saga-protocol.d.ts.map +1 -1
  35. package/dist/internal/durable-shared.d.ts +26 -0
  36. package/dist/internal/durable-shared.d.ts.map +1 -0
  37. package/dist/internal/durable-shared.js +131 -0
  38. package/dist/internal/durable-shared.js.map +1 -0
  39. package/dist/internal/durable-types.d.ts +159 -0
  40. package/dist/internal/durable-types.d.ts.map +1 -0
  41. package/dist/internal/durable-types.js +2 -0
  42. package/dist/internal/durable-types.js.map +1 -0
  43. package/dist/internal/route-compiler.d.ts +61 -0
  44. package/dist/internal/route-compiler.d.ts.map +1 -0
  45. package/dist/internal/route-compiler.js +121 -0
  46. package/dist/internal/route-compiler.js.map +1 -0
  47. package/dist/internal/route-execution.d.ts +6 -4
  48. package/dist/internal/route-execution.d.ts.map +1 -1
  49. package/dist/internal/route-execution.js +2 -17
  50. package/dist/internal/route-execution.js.map +1 -1
  51. package/dist/internal/route-lanes.d.ts +35 -0
  52. package/dist/internal/route-lanes.d.ts.map +1 -0
  53. package/dist/internal/route-lanes.js +150 -0
  54. package/dist/internal/route-lanes.js.map +1 -0
  55. package/dist/internal/route-program.d.ts +67 -0
  56. package/dist/internal/route-program.d.ts.map +1 -0
  57. package/dist/internal/route-program.js +199 -0
  58. package/dist/internal/route-program.js.map +1 -0
  59. package/dist/server/bare-error-lane.d.ts +1 -1
  60. package/dist/server/bare-error-lane.d.ts.map +1 -1
  61. package/dist/server/bare-error-lane.js +3 -3
  62. package/dist/server/bare-error-lane.js.map +1 -1
  63. package/dist/server/body-lane.d.ts +13 -0
  64. package/dist/server/body-lane.d.ts.map +1 -0
  65. package/dist/server/body-lane.js +134 -0
  66. package/dist/server/body-lane.js.map +1 -0
  67. package/dist/server/contract.d.ts +40 -14
  68. package/dist/server/contract.d.ts.map +1 -1
  69. package/dist/server/contract.js +7 -0
  70. package/dist/server/contract.js.map +1 -1
  71. package/dist/server/cookies.d.ts.map +1 -1
  72. package/dist/server/cookies.js +16 -2
  73. package/dist/server/cookies.js.map +1 -1
  74. package/dist/server/plugin.d.ts +2 -2
  75. package/dist/server/plugin.d.ts.map +1 -1
  76. package/dist/server/plugin.js +6 -1
  77. package/dist/server/plugin.js.map +1 -1
  78. package/dist/server/registry.d.ts +40 -12
  79. package/dist/server/registry.d.ts.map +1 -1
  80. package/dist/server/request-context.d.ts +4 -28
  81. package/dist/server/request-context.d.ts.map +1 -1
  82. package/dist/server/request-context.js +10 -185
  83. package/dist/server/request-context.js.map +1 -1
  84. package/dist/server/respond.d.ts.map +1 -1
  85. package/dist/server/respond.js +39 -2
  86. package/dist/server/respond.js.map +1 -1
  87. package/dist/server/runtime-core.d.ts +18 -2
  88. package/dist/server/runtime-core.d.ts.map +1 -1
  89. package/dist/server/runtime-core.js +0 -23
  90. package/dist/server/runtime-core.js.map +1 -1
  91. package/dist/server/server-types.d.ts +3 -0
  92. package/dist/server/server-types.d.ts.map +1 -1
  93. package/dist/server/server.d.ts +113 -84
  94. package/dist/server/server.d.ts.map +1 -1
  95. package/dist/server/server.js +506 -639
  96. package/dist/server/server.js.map +1 -1
  97. package/dist/server/validation.d.ts +5 -0
  98. package/dist/server/validation.d.ts.map +1 -0
  99. package/dist/server/validation.js +13 -0
  100. package/dist/server/validation.js.map +1 -0
  101. package/dist/server/ws-bun.d.ts +11 -3
  102. package/dist/server/ws-bun.d.ts.map +1 -1
  103. package/dist/server/ws-bun.js +42 -12
  104. package/dist/server/ws-bun.js.map +1 -1
  105. package/dist/server/ws-hook.d.ts +3 -2
  106. package/dist/server/ws-hook.d.ts.map +1 -1
  107. package/dist/server.d.ts +2 -2
  108. package/dist/server.d.ts.map +1 -1
  109. package/dist/server.js +1 -1
  110. package/dist/server.js.map +1 -1
  111. package/dist/single-copy.d.ts.map +1 -1
  112. package/dist/single-copy.js +3 -1
  113. package/dist/single-copy.js.map +1 -1
  114. package/package.json +6 -3
  115. package/src/durable-adapters.ts +20 -0
  116. package/src/durable-approval.ts +281 -0
  117. package/src/durable-effect.ts +300 -0
  118. package/src/durable-execution.ts +8 -1627
  119. package/src/durable-saga.ts +744 -0
  120. package/src/edge-kit.ts +2 -2
  121. package/src/index.ts +129 -2
  122. package/src/internal/durable-approval-protocol.ts +1 -1
  123. package/src/internal/durable-effect-protocol.ts +1 -1
  124. package/src/internal/durable-saga-protocol.ts +1 -1
  125. package/src/internal/durable-shared.ts +157 -0
  126. package/src/internal/durable-types.ts +194 -0
  127. package/src/internal/route-compiler.ts +234 -0
  128. package/src/internal/route-execution.ts +34 -84
  129. package/src/internal/route-lanes.ts +221 -0
  130. package/src/internal/route-program.ts +404 -0
  131. package/src/server/bare-error-lane.ts +3 -2
  132. package/src/server/body-lane.ts +203 -0
  133. package/src/server/contract.ts +70 -20
  134. package/src/server/cookies.ts +16 -2
  135. package/src/server/plugin.ts +11 -5
  136. package/src/server/registry.ts +71 -16
  137. package/src/server/request-context.ts +7 -262
  138. package/src/server/respond.ts +45 -2
  139. package/src/server/runtime-core.ts +28 -2
  140. package/src/server/server-types.ts +3 -0
  141. package/src/server/server.ts +1152 -1260
  142. package/src/server/validation.ts +19 -0
  143. package/src/server/ws-bun.ts +44 -11
  144. package/src/server/ws-hook.ts +3 -2
  145. package/src/server.ts +7 -2
  146. package/src/single-copy.ts +3 -1
@@ -0,0 +1,19 @@
1
+ import type { StandardIssue } from "../schema/standard.ts"
2
+ import { type ResponseResult, status } from "./runtime-core.ts"
3
+
4
+ function validationIssues(issues: ReadonlyArray<StandardIssue>): {
5
+ ok: false
6
+ error: string
7
+ issues: unknown[]
8
+ } {
9
+ const serialized = issues.map((issue) => {
10
+ const path = issue.path?.map((seg) => String(typeof seg === "object" ? seg.key : seg))
11
+ return path !== undefined ? { message: issue.message, path } : { message: issue.message }
12
+ })
13
+ return { ok: false, error: "validation", issues: serialized }
14
+ }
15
+
16
+ /** The shared 422 response result used by every body-validation lane. */
17
+ export function plainValidationError(issues: ReadonlyArray<StandardIssue>): ResponseResult {
18
+ return status(422, validationIssues(issues))
19
+ }
@@ -28,6 +28,10 @@ export interface BunSocket {
28
28
  send(data: string | ArrayBufferView | ArrayBuffer, compress?: boolean): number
29
29
  close(code?: number, reason?: string): void
30
30
  readonly readyState: number
31
+ // Bun's native (uWebSockets) topic pub/sub - used only in native-pubsub mode (no per-socket
32
+ // outbound validation), where `app.publish` routes through the Bun server's `publish`.
33
+ subscribe(topic: string): void
34
+ unsubscribe(topic: string): void
31
35
  // readonly: we never reassign `ws.data` wholesale, only mutate its fields (`.nifra`, `.data`).
32
36
  readonly data: BunWsData
33
37
  }
@@ -38,11 +42,19 @@ function toBinaryData(message: ArrayBuffer | Uint8Array): Uint8Array {
38
42
  return message instanceof Uint8Array ? message : new Uint8Array(message)
39
43
  }
40
44
 
41
- /** Wrap a Bun `ServerWebSocket` as the portable {@link NifraWebSocket} handed to WS callbacks. */
45
+ /**
46
+ * Wrap a Bun `ServerWebSocket` as the portable {@link NifraWebSocket} handed to WS callbacks.
47
+ *
48
+ * `nativePubsub` selects the topic backend: in native mode `subscribe`/`unsubscribe` go straight to
49
+ * Bun's own (uWebSockets) pub/sub so a broadcast via the Bun server's `publish` reaches this socket
50
+ * with no per-connection JS loop; otherwise they use the runtime-neutral {@link TopicRegistry}. Native
51
+ * mode is used only when no route validates outbound frames, so raw and validated sends are identical.
52
+ */
42
53
  function wrapBunSocket(
43
54
  raw: BunSocket,
44
55
  topics: TopicRegistry,
45
56
  handler: WebSocketHandler,
57
+ nativePubsub: boolean,
46
58
  ): NifraWebSocket {
47
59
  let ws!: NifraWebSocket
48
60
  const reportError = (error: unknown): void => reportWsError(error, ws, handler)
@@ -61,8 +73,12 @@ function wrapBunSocket(
61
73
  get readyState() {
62
74
  return raw.readyState
63
75
  },
64
- subscribe: (topic) => topics.subscribe(topic, ws),
65
- unsubscribe: (topic) => topics.unsubscribe(topic, ws),
76
+ subscribe: nativePubsub
77
+ ? (topic) => raw.subscribe(topic)
78
+ : (topic) => topics.subscribe(topic, ws),
79
+ unsubscribe: nativePubsub
80
+ ? (topic) => raw.unsubscribe(topic)
81
+ : (topic) => topics.unsubscribe(topic, ws),
66
82
  get data() {
67
83
  return raw.data.data
68
84
  },
@@ -99,26 +115,43 @@ function dispatchWsCallback(
99
115
  }
100
116
  }
101
117
 
102
- /** The shared Bun `websocket` dispatcher config for one app - each connection's
103
- * `ws.data.handler` is the matched route's handler, set by `server.upgrade`. */
104
- export function createBunWsHandlers(topics: TopicRegistry): BunWsHandlers {
118
+ /**
119
+ * The shared Bun `websocket` dispatcher config for one app - each connection's `ws.data.handler` is
120
+ * the matched route's handler, set by `server.upgrade`.
121
+ *
122
+ * `nativePubsub` (set by `listen()` when the app has no validated-send route) makes subscriptions use
123
+ * Bun's own pub/sub; there the registry holds nothing and Bun auto-drops native subscriptions on
124
+ * close, so the `unsubscribeAll` sweep is skipped.
125
+ */
126
+ export function createBunWsHandlers(topics: TopicRegistry, nativePubsub = false): BunWsHandlers {
105
127
  return {
106
128
  open: (raw) => {
107
129
  const ws = raw as BunSocket
108
- const nifra = wrapBunSocket(ws, topics, ws.data.handler)
130
+ const nifra = wrapBunSocket(ws, topics, ws.data.handler, nativePubsub)
109
131
  ws.data.nifra = nifra
110
132
  dispatchWsCallback(() => ws.data.handler.open?.(nifra), nifra, ws.data.handler)
111
133
  },
112
134
  message: (raw, message) => {
113
135
  const ws = raw as BunSocket
114
- const nifra = ws.data.nifra ?? wrapBunSocket(ws, topics, ws.data.handler)
136
+ const handler = ws.data.handler
137
+ const nifra = ws.data.nifra ?? wrapBunSocket(ws, topics, handler, nativePubsub)
115
138
  const data: WebSocketData = typeof message === "string" ? message : toBinaryData(message)
116
- dispatchWsCallback(() => ws.data.handler.message?.(nifra, data), nifra, ws.data.handler)
139
+ // Hottest WS path - dispatch inline rather than through `dispatchWsCallback` so no per-frame
140
+ // closure is allocated. Same contract: a sync throw or async rejection routes to `handler.error`.
141
+ try {
142
+ const result = handler.message?.(nifra, data)
143
+ if (result instanceof Promise)
144
+ result.catch((e: unknown) => reportWsError(e, nifra, handler))
145
+ } catch (error) {
146
+ reportWsError(error, nifra, handler)
147
+ }
117
148
  },
118
149
  close: (raw, code, reason) => {
119
150
  const ws = raw as BunSocket
120
- const nifra = ws.data.nifra ?? wrapBunSocket(ws, topics, ws.data.handler)
121
- topics.unsubscribeAll(nifra) // drop topic subscriptions so the registry never holds a dead socket
151
+ const nifra = ws.data.nifra ?? wrapBunSocket(ws, topics, ws.data.handler, nativePubsub)
152
+ // Registry mode: drop topic subscriptions so it never holds a dead socket. Native mode: Bun
153
+ // unsubscribes the socket from every topic on close, so there is nothing to sweep here.
154
+ if (!nativePubsub) topics.unsubscribeAll(nifra)
122
155
  dispatchWsCallback(() => ws.data.handler.close?.(nifra, code, reason), nifra, ws.data.handler)
123
156
  },
124
157
  }
@@ -30,8 +30,9 @@ export interface WsRuntime {
30
30
  wrapHandler(handler: WebSocketHandler): WebSocketHandler
31
31
  /** One in-process pub/sub registry per app (backs `ws.subscribe` + `app.publish`). */
32
32
  createTopics(): TopicRegistry
33
- /** The Bun `websocket` config for `listen()` when the app has WS routes. */
34
- bunHandlers(topics: TopicRegistry): BunWsHandlers
33
+ /** The Bun `websocket` config for `listen()` when the app has WS routes. `nativePubsub` routes
34
+ * `ws.subscribe` through Bun's own pub/sub (set when no route validates outbound frames). */
35
+ bunHandlers(topics: TopicRegistry, nativePubsub?: boolean): BunWsHandlers
35
36
  /** `attachWebSocket` - wires a standard server socket (Workers `WebSocketPair`) to a handler. */
36
37
  attach(
37
38
  socket: StandardWebSocket,
package/src/server.ts CHANGED
@@ -70,8 +70,13 @@ export {
70
70
  toVercelHandler,
71
71
  type VercelHandler,
72
72
  } from "./server/platform-adapters.ts"
73
- export type { Registry, RouteInfo } from "./server/registry.ts"
74
- export { type PlainRender, type ResponseResult, status } from "./server/runtime-core.ts"
73
+ export type { Registry, ResponseMapFor, RouteInfo, RouteInfoFor } from "./server/registry.ts"
74
+ export {
75
+ type PlainRender,
76
+ type ResponseResult,
77
+ type StatusResponse,
78
+ status,
79
+ } from "./server/runtime-core.ts"
75
80
  export {
76
81
  type AdmissionController,
77
82
  type AdmissionDecision,
@@ -472,7 +472,9 @@ export function singleCopyPlugin(options: SingleCopyOptions = {}): SingleCopyPlu
472
472
  const scopes = declared
473
473
  .filter((entry) => entry.endsWith("/*"))
474
474
  .map((entry) => `${escapeRegExp(entry.slice(0, -1))}[^/]+`)
475
- const alternatives = [...exact.map((name) => `${name}(?:/.*)?`), ...scopes]
475
+ // Subpaths pin too, for exact names and scope patterns alike: `@nifrajs/core/server` must
476
+ // resolve to the same copy as `@nifrajs/core`, or a deep import quietly splits the package.
477
+ const alternatives = [...exact, ...scopes].map((name) => `${name}(?:/.*)?`)
476
478
  if (alternatives.length > 0) {
477
479
  const filter = new RegExp(`^(?:${alternatives.join("|")})$`)
478
480
  build.onResolve({ filter }, (args) => {