@bleedingdev/modern-js-bff-effect 3.8.3-ultramodern.2

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 (230) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/dist/cjs/data-platform/batch/protocol.js +130 -0
  4. package/dist/cjs/data-platform/batch/queue.js +623 -0
  5. package/dist/cjs/data-platform/batch/request.js +121 -0
  6. package/dist/cjs/data-platform/batch/response.js +65 -0
  7. package/dist/cjs/data-platform/batch/telemetry.js +71 -0
  8. package/dist/cjs/data-platform/batch/url.js +69 -0
  9. package/dist/cjs/data-platform/batch-transport.js +59 -0
  10. package/dist/cjs/data-platform/codec.js +155 -0
  11. package/dist/cjs/data-platform/index.js +124 -0
  12. package/dist/cjs/data-platform/invalidation.js +61 -0
  13. package/dist/cjs/data-platform/trace.js +73 -0
  14. package/dist/cjs/data-platform/types.js +48 -0
  15. package/dist/cjs/data-platform/validation.js +178 -0
  16. package/dist/cjs/effect/context.js +59 -0
  17. package/dist/cjs/effect/dispatch.js +101 -0
  18. package/dist/cjs/effect/edge-context.js +61 -0
  19. package/dist/cjs/effect/edge-dispatcher.js +354 -0
  20. package/dist/cjs/effect/edge.js +62 -0
  21. package/dist/cjs/effect/endpoint-contracts.js +121 -0
  22. package/dist/cjs/effect/entry-shape-registry.js +31 -0
  23. package/dist/cjs/effect/entry-shape.js +113 -0
  24. package/dist/cjs/effect/handler/batch-handler.js +270 -0
  25. package/dist/cjs/effect/handler/batch.js +170 -0
  26. package/dist/cjs/effect/handler/definition.js +99 -0
  27. package/dist/cjs/effect/handler/envelope.js +104 -0
  28. package/dist/cjs/effect/handler/http.js +107 -0
  29. package/dist/cjs/effect/handler/openapi.js +58 -0
  30. package/dist/cjs/effect/handler/routing.js +100 -0
  31. package/dist/cjs/effect/handler/rpc.js +63 -0
  32. package/dist/cjs/effect/handler/types.js +43 -0
  33. package/dist/cjs/effect/handler.js +173 -0
  34. package/dist/cjs/effect/index.js +137 -0
  35. package/dist/cjs/effect/module.js +118 -0
  36. package/dist/cjs/effect/operation-context.js +116 -0
  37. package/dist/cjs/effect/rpcSerialization.js +54 -0
  38. package/dist/cjs/effect-client/batch.js +73 -0
  39. package/dist/cjs/effect-client/envelope.js +145 -0
  40. package/dist/cjs/effect-client/guards.js +44 -0
  41. package/dist/cjs/effect-client/index.js +208 -0
  42. package/dist/cjs/effect-client/request-context.js +64 -0
  43. package/dist/cjs/effect-client/request.js +66 -0
  44. package/dist/cjs/effect-client/runtime.js +100 -0
  45. package/dist/cjs/effect-client/types.js +18 -0
  46. package/dist/cjs/headers.js +46 -0
  47. package/dist/cjs/index.js +83 -0
  48. package/dist/esm/data-platform/batch/protocol.mjs +71 -0
  49. package/dist/esm/data-platform/batch/queue.mjs +582 -0
  50. package/dist/esm/data-platform/batch/request.mjs +68 -0
  51. package/dist/esm/data-platform/batch/response.mjs +24 -0
  52. package/dist/esm/data-platform/batch/telemetry.mjs +27 -0
  53. package/dist/esm/data-platform/batch/url.mjs +25 -0
  54. package/dist/esm/data-platform/batch-transport.mjs +12 -0
  55. package/dist/esm/data-platform/codec.mjs +90 -0
  56. package/dist/esm/data-platform/index.mjs +7 -0
  57. package/dist/esm/data-platform/invalidation.mjs +20 -0
  58. package/dist/esm/data-platform/trace.mjs +29 -0
  59. package/dist/esm/data-platform/types.mjs +4 -0
  60. package/dist/esm/data-platform/validation.mjs +128 -0
  61. package/dist/esm/effect/context.mjs +12 -0
  62. package/dist/esm/effect/dispatch.mjs +63 -0
  63. package/dist/esm/effect/edge-context.mjs +14 -0
  64. package/dist/esm/effect/edge-dispatcher.mjs +115 -0
  65. package/dist/esm/effect/edge.mjs +1 -0
  66. package/dist/esm/effect/endpoint-contracts.mjs +68 -0
  67. package/dist/esm/effect/entry-shape-registry.mjs +17 -0
  68. package/dist/esm/effect/entry-shape.mjs +51 -0
  69. package/dist/esm/effect/handler/batch-handler.mjs +232 -0
  70. package/dist/esm/effect/handler/batch.mjs +105 -0
  71. package/dist/esm/effect/handler/definition.mjs +58 -0
  72. package/dist/esm/effect/handler/envelope.mjs +63 -0
  73. package/dist/esm/effect/handler/http.mjs +69 -0
  74. package/dist/esm/effect/handler/openapi.mjs +20 -0
  75. package/dist/esm/effect/handler/routing.mjs +50 -0
  76. package/dist/esm/effect/handler/rpc.mjs +22 -0
  77. package/dist/esm/effect/handler/types.mjs +5 -0
  78. package/dist/esm/effect/handler.mjs +13 -0
  79. package/dist/esm/effect/index.mjs +7 -0
  80. package/dist/esm/effect/module.mjs +80 -0
  81. package/dist/esm/effect/operation-context.mjs +75 -0
  82. package/dist/esm/effect/rpcSerialization.mjs +16 -0
  83. package/dist/esm/effect-client/batch.mjs +35 -0
  84. package/dist/esm/effect-client/envelope.mjs +104 -0
  85. package/dist/esm/effect-client/guards.mjs +3 -0
  86. package/dist/esm/effect-client/index.mjs +110 -0
  87. package/dist/esm/effect-client/request-context.mjs +23 -0
  88. package/dist/esm/effect-client/request.mjs +28 -0
  89. package/dist/esm/effect-client/runtime.mjs +62 -0
  90. package/dist/esm/effect-client/types.mjs +0 -0
  91. package/dist/esm/headers.mjs +8 -0
  92. package/dist/esm/index.mjs +4 -0
  93. package/dist/esm/rslib-runtime.mjs +18 -0
  94. package/dist/esm-node/data-platform/batch/protocol.mjs +72 -0
  95. package/dist/esm-node/data-platform/batch/queue.mjs +583 -0
  96. package/dist/esm-node/data-platform/batch/request.mjs +69 -0
  97. package/dist/esm-node/data-platform/batch/response.mjs +25 -0
  98. package/dist/esm-node/data-platform/batch/telemetry.mjs +28 -0
  99. package/dist/esm-node/data-platform/batch/url.mjs +26 -0
  100. package/dist/esm-node/data-platform/batch-transport.mjs +13 -0
  101. package/dist/esm-node/data-platform/codec.mjs +91 -0
  102. package/dist/esm-node/data-platform/index.mjs +8 -0
  103. package/dist/esm-node/data-platform/invalidation.mjs +21 -0
  104. package/dist/esm-node/data-platform/trace.mjs +30 -0
  105. package/dist/esm-node/data-platform/types.mjs +5 -0
  106. package/dist/esm-node/data-platform/validation.mjs +129 -0
  107. package/dist/esm-node/effect/context.mjs +13 -0
  108. package/dist/esm-node/effect/dispatch.mjs +64 -0
  109. package/dist/esm-node/effect/edge-context.mjs +15 -0
  110. package/dist/esm-node/effect/edge-dispatcher.mjs +116 -0
  111. package/dist/esm-node/effect/edge.mjs +2 -0
  112. package/dist/esm-node/effect/endpoint-contracts.mjs +69 -0
  113. package/dist/esm-node/effect/entry-shape-registry.mjs +18 -0
  114. package/dist/esm-node/effect/entry-shape.mjs +63 -0
  115. package/dist/esm-node/effect/handler/batch-handler.mjs +233 -0
  116. package/dist/esm-node/effect/handler/batch.mjs +106 -0
  117. package/dist/esm-node/effect/handler/definition.mjs +59 -0
  118. package/dist/esm-node/effect/handler/envelope.mjs +64 -0
  119. package/dist/esm-node/effect/handler/http.mjs +70 -0
  120. package/dist/esm-node/effect/handler/openapi.mjs +21 -0
  121. package/dist/esm-node/effect/handler/routing.mjs +51 -0
  122. package/dist/esm-node/effect/handler/rpc.mjs +23 -0
  123. package/dist/esm-node/effect/handler/types.mjs +6 -0
  124. package/dist/esm-node/effect/handler.mjs +14 -0
  125. package/dist/esm-node/effect/index.mjs +8 -0
  126. package/dist/esm-node/effect/module.mjs +81 -0
  127. package/dist/esm-node/effect/operation-context.mjs +76 -0
  128. package/dist/esm-node/effect/rpcSerialization.mjs +17 -0
  129. package/dist/esm-node/effect-client/batch.mjs +36 -0
  130. package/dist/esm-node/effect-client/envelope.mjs +105 -0
  131. package/dist/esm-node/effect-client/guards.mjs +4 -0
  132. package/dist/esm-node/effect-client/index.mjs +111 -0
  133. package/dist/esm-node/effect-client/request-context.mjs +24 -0
  134. package/dist/esm-node/effect-client/request.mjs +29 -0
  135. package/dist/esm-node/effect-client/runtime.mjs +63 -0
  136. package/dist/esm-node/effect-client/types.mjs +1 -0
  137. package/dist/esm-node/headers.mjs +9 -0
  138. package/dist/esm-node/index.mjs +5 -0
  139. package/dist/esm-node/rslib-runtime.mjs +19 -0
  140. package/dist/types/data-platform/batch/protocol.d.ts +9 -0
  141. package/dist/types/data-platform/batch/queue.d.ts +47 -0
  142. package/dist/types/data-platform/batch/request.d.ts +19 -0
  143. package/dist/types/data-platform/batch/response.d.ts +3 -0
  144. package/dist/types/data-platform/batch/telemetry.d.ts +4 -0
  145. package/dist/types/data-platform/batch/url.d.ts +4 -0
  146. package/dist/types/data-platform/batch-transport.d.ts +3 -0
  147. package/dist/types/data-platform/codec.d.ts +17 -0
  148. package/dist/types/data-platform/index.d.ts +8 -0
  149. package/dist/types/data-platform/invalidation.d.ts +8 -0
  150. package/dist/types/data-platform/trace.d.ts +4 -0
  151. package/dist/types/data-platform/types.d.ts +151 -0
  152. package/dist/types/data-platform/validation.d.ts +29 -0
  153. package/dist/types/effect/context.d.ts +5 -0
  154. package/dist/types/effect/dispatch.d.ts +13 -0
  155. package/dist/types/effect/edge-context.d.ts +5 -0
  156. package/dist/types/effect/edge-dispatcher.d.ts +37 -0
  157. package/dist/types/effect/edge.d.ts +1 -0
  158. package/dist/types/effect/endpoint-contracts.d.ts +67 -0
  159. package/dist/types/effect/entry-shape.d.ts +32 -0
  160. package/dist/types/effect/handler/batch-handler.d.ts +11 -0
  161. package/dist/types/effect/handler/batch.d.ts +15 -0
  162. package/dist/types/effect/handler/definition.d.ts +7 -0
  163. package/dist/types/effect/handler/envelope.d.ts +13 -0
  164. package/dist/types/effect/handler/http.d.ts +15 -0
  165. package/dist/types/effect/handler/openapi.d.ts +4 -0
  166. package/dist/types/effect/handler/routing.d.ts +5 -0
  167. package/dist/types/effect/handler/rpc.d.ts +7 -0
  168. package/dist/types/effect/handler/types.d.ts +153 -0
  169. package/dist/types/effect/handler.d.ts +13 -0
  170. package/dist/types/effect/index.d.ts +6 -0
  171. package/dist/types/effect/module.d.ts +36 -0
  172. package/dist/types/effect/operation-context.d.ts +15 -0
  173. package/dist/types/effect/rpcSerialization.d.ts +3 -0
  174. package/dist/types/effect-client/batch.d.ts +2 -0
  175. package/dist/types/effect-client/envelope.d.ts +15 -0
  176. package/dist/types/effect-client/guards.d.ts +2 -0
  177. package/dist/types/effect-client/index.d.ts +58 -0
  178. package/dist/types/effect-client/request-context.d.ts +3 -0
  179. package/dist/types/effect-client/request.d.ts +1 -0
  180. package/dist/types/effect-client/runtime.d.ts +5 -0
  181. package/dist/types/effect-client/types.d.ts +68 -0
  182. package/dist/types/headers.d.ts +1 -0
  183. package/dist/types/index.d.ts +3 -0
  184. package/package.json +164 -0
  185. package/src/data-platform/batch/protocol.ts +112 -0
  186. package/src/data-platform/batch/queue.ts +876 -0
  187. package/src/data-platform/batch/request.ts +130 -0
  188. package/src/data-platform/batch/response.ts +74 -0
  189. package/src/data-platform/batch/telemetry.ts +47 -0
  190. package/src/data-platform/batch/url.ts +54 -0
  191. package/src/data-platform/batch-transport.ts +22 -0
  192. package/src/data-platform/codec.ts +145 -0
  193. package/src/data-platform/index.ts +63 -0
  194. package/src/data-platform/invalidation.ts +57 -0
  195. package/src/data-platform/trace.ts +49 -0
  196. package/src/data-platform/types.ts +186 -0
  197. package/src/data-platform/validation.ts +303 -0
  198. package/src/effect/context.ts +37 -0
  199. package/src/effect/dispatch.ts +132 -0
  200. package/src/effect/edge-context.ts +51 -0
  201. package/src/effect/edge-dispatcher.ts +310 -0
  202. package/src/effect/edge.ts +1 -0
  203. package/src/effect/endpoint-contracts.ts +191 -0
  204. package/src/effect/entry-shape-registry.cjs +16 -0
  205. package/src/effect/entry-shape-registry.d.cts +6 -0
  206. package/src/effect/entry-shape.ts +155 -0
  207. package/src/effect/handler/batch-handler.ts +475 -0
  208. package/src/effect/handler/batch.ts +182 -0
  209. package/src/effect/handler/definition.ts +121 -0
  210. package/src/effect/handler/envelope.ts +133 -0
  211. package/src/effect/handler/http.ts +133 -0
  212. package/src/effect/handler/openapi.ts +45 -0
  213. package/src/effect/handler/routing.ts +91 -0
  214. package/src/effect/handler/rpc.ts +45 -0
  215. package/src/effect/handler/types.ts +255 -0
  216. package/src/effect/handler.ts +33 -0
  217. package/src/effect/index.ts +22 -0
  218. package/src/effect/module.ts +186 -0
  219. package/src/effect/operation-context.ts +144 -0
  220. package/src/effect/rpcSerialization.ts +20 -0
  221. package/src/effect-client/batch.ts +50 -0
  222. package/src/effect-client/envelope.ts +238 -0
  223. package/src/effect-client/guards.ts +6 -0
  224. package/src/effect-client/index.ts +325 -0
  225. package/src/effect-client/request-context.ts +54 -0
  226. package/src/effect-client/request.ts +51 -0
  227. package/src/effect-client/runtime.ts +122 -0
  228. package/src/effect-client/types.ts +84 -0
  229. package/src/headers.ts +7 -0
  230. package/src/index.ts +3 -0
@@ -0,0 +1,310 @@
1
+ // @effect-diagnostics anyUnknownInErrorContext:off asyncFunction:off strictBooleanExpressions:off
2
+ import {
3
+ evaluateCrossProjectPolicy,
4
+ type NormalizedCrossProjectPolicy,
5
+ resolveCrossProjectRequestObservation,
6
+ } from '@modern-js/bff-core/security/cross-project-policy';
7
+ import { toHeaderRecord } from '../headers';
8
+ import {
9
+ type DispatchEffectBffRequestOptions,
10
+ dispatchEffectBffRequestWithContext,
11
+ } from './dispatch';
12
+ import { runWithEffectContext } from './edge-context';
13
+ import type {
14
+ EffectBffOpenApiConfig,
15
+ EffectDataPlatformValidationOptions,
16
+ } from './handler';
17
+ import {
18
+ type EffectApiModule,
19
+ type EffectBffRequestHandler,
20
+ resolveEffectBffModuleHandler,
21
+ } from './module';
22
+ import type { EffectContext } from './operation-context';
23
+
24
+ export * as Config from 'effect/Config';
25
+ export * as Effect from 'effect/Effect';
26
+ export * as Layer from 'effect/Layer';
27
+ export * as Option from 'effect/Option';
28
+ export * as Schema from 'effect/Schema';
29
+ export {
30
+ Cookies,
31
+ Etag,
32
+ FetchHttpClient,
33
+ FindMyWay,
34
+ Headers,
35
+ HttpBody,
36
+ HttpClient,
37
+ HttpClientError,
38
+ HttpClientRequest,
39
+ HttpClientResponse,
40
+ HttpEffect,
41
+ HttpIncomingMessage,
42
+ HttpMethod,
43
+ HttpMiddleware,
44
+ HttpPlatform,
45
+ HttpRouter,
46
+ HttpServer,
47
+ HttpServerError,
48
+ HttpServerRequest,
49
+ HttpServerRespondable,
50
+ HttpServerResponse,
51
+ HttpStaticServer,
52
+ HttpStatus,
53
+ HttpTraceContext,
54
+ Multipart,
55
+ MultipartParser,
56
+ Template,
57
+ Url,
58
+ UrlParams,
59
+ } from 'effect/unstable/http';
60
+ export {
61
+ HttpApi,
62
+ HttpApiBuilder,
63
+ HttpApiClient,
64
+ HttpApiEndpoint,
65
+ HttpApiError,
66
+ HttpApiGroup,
67
+ HttpApiMiddleware,
68
+ HttpApiSchema,
69
+ HttpApiSecurity,
70
+ HttpApiTest,
71
+ OpenApi,
72
+ } from 'effect/unstable/httpapi';
73
+ export {
74
+ Rpc,
75
+ RpcClient,
76
+ RpcClientError,
77
+ RpcGroup,
78
+ RpcMessage,
79
+ RpcMiddleware,
80
+ RpcSchema,
81
+ RpcSerialization,
82
+ RpcServer,
83
+ RpcTest,
84
+ RpcWorker,
85
+ Utils,
86
+ } from 'effect/unstable/rpc';
87
+ export {
88
+ runWithEffectContext,
89
+ useEffectContext,
90
+ useOperationContext,
91
+ } from './edge-context';
92
+ export type {
93
+ EffectApiClientFromApi,
94
+ EffectApiPromiseClientFromApi,
95
+ EffectBffDefinition,
96
+ EffectBffHandlerFactory,
97
+ EffectBffOpenApiConfig,
98
+ EffectBffRuntime,
99
+ EffectDataPlatformBatchOptions,
100
+ EffectDataPlatformSelectionValidationOptions,
101
+ EffectDataPlatformValidationOptions,
102
+ EffectRequestValidator,
103
+ EffectRpcBffDefinition,
104
+ EffectRpcBffHandlerFactory,
105
+ EffectRpcBffHandlerOptions,
106
+ EffectRpcRuntimeLayer,
107
+ EffectRpcSerialization,
108
+ EffectRuntimeLayer,
109
+ EffectRuntimeRequirements,
110
+ } from './handler';
111
+ export {
112
+ createHttpApiHandler,
113
+ defineEffectBff,
114
+ defineEffectRpcBff,
115
+ } from './handler';
116
+ export {
117
+ type CreateEffectOperationContextOptions,
118
+ createEffectOperationContext,
119
+ type EffectContext,
120
+ } from './operation-context';
121
+
122
+ export type EffectBffEdgeDispatchOptions = Omit<
123
+ DispatchEffectBffRequestOptions,
124
+ 'runWithEffectContext'
125
+ >;
126
+
127
+ export type EffectBffEdgeHandlerOptions = {
128
+ module: EffectApiModule;
129
+ prefix?: string;
130
+ openapi?: EffectBffOpenApiConfig;
131
+ dataPlatform?: EffectDataPlatformValidationOptions;
132
+ crossProjectPolicy?: NormalizedCrossProjectPolicy;
133
+ onError?: (
134
+ error: unknown,
135
+ context: EffectContext,
136
+ ) => Promise<Response> | Response;
137
+ onWarning?: (message: string) => void;
138
+ };
139
+
140
+ const NORMALIZED_POLICY_BOOLEAN_FIELDS = [
141
+ 'enabled',
142
+ 'requireEnvelope',
143
+ 'requireOperationContext',
144
+ 'requireOperationContextDetails',
145
+ 'requireOperationSchemaHash',
146
+ 'requireOperationVersion',
147
+ 'allowUnknownOperations',
148
+ ] as const;
149
+
150
+ function assertNormalizedCrossProjectPolicy(
151
+ policy: NormalizedCrossProjectPolicy | undefined,
152
+ ) {
153
+ if (policy === undefined) {
154
+ return;
155
+ }
156
+ if (!policy || typeof policy !== 'object' || Array.isArray(policy)) {
157
+ throw new Error(
158
+ '[BFF][Effect] Edge cross-project policy must be a normalized object.',
159
+ );
160
+ }
161
+ for (const field of NORMALIZED_POLICY_BOOLEAN_FIELDS) {
162
+ if (typeof policy[field] !== 'boolean') {
163
+ throw new Error(
164
+ `[BFF][Effect] Edge cross-project policy requires boolean ${field}.`,
165
+ );
166
+ }
167
+ }
168
+ if (
169
+ !policy.expectedOperationContracts ||
170
+ typeof policy.expectedOperationContracts !== 'object' ||
171
+ Array.isArray(policy.expectedOperationContracts)
172
+ ) {
173
+ throw new Error(
174
+ '[BFF][Effect] Edge cross-project policy requires expectedOperationContracts object.',
175
+ );
176
+ }
177
+ }
178
+
179
+ function restoreEffectBffMountPath(
180
+ request: Request,
181
+ prefix: string | undefined,
182
+ ) {
183
+ const pathname = new URL(request.url).pathname;
184
+ if (!prefix || prefix === '/') {
185
+ return pathname;
186
+ }
187
+ const normalizedPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
188
+ return pathname === '/' ? normalizedPrefix : `${normalizedPrefix}${pathname}`;
189
+ }
190
+
191
+ export async function dispatchEffectBffRequest(
192
+ handler: EffectBffRequestHandler,
193
+ request: Request,
194
+ options: EffectBffEdgeDispatchOptions = {},
195
+ ): Promise<Response> {
196
+ return dispatchEffectBffRequestWithContext(handler, request, {
197
+ ...options,
198
+ runWithEffectContext,
199
+ });
200
+ }
201
+
202
+ export async function createEffectBffEdgeDispatcher(
203
+ options: EffectBffEdgeHandlerOptions,
204
+ ) {
205
+ const crossProjectPolicy = options.crossProjectPolicy;
206
+ assertNormalizedCrossProjectPolicy(crossProjectPolicy);
207
+ const loaded = await resolveEffectBffModuleHandler(options.module, {
208
+ openapi: options.openapi,
209
+ dataPlatform: options.dataPlatform,
210
+ validateRequest:
211
+ crossProjectPolicy?.enabled === true
212
+ ? request => {
213
+ const pathname = new URL(request.url).pathname;
214
+ const observedRequest = resolveCrossProjectRequestObservation(
215
+ { method: request.method, pathname },
216
+ crossProjectPolicy,
217
+ ) ?? {
218
+ method: request.method,
219
+ routePath: restoreEffectBffMountPath(request, options.prefix),
220
+ };
221
+ const violation = evaluateCrossProjectPolicy(
222
+ toHeaderRecord(request.headers),
223
+ crossProjectPolicy,
224
+ observedRequest,
225
+ );
226
+ if (!violation) {
227
+ return null;
228
+ }
229
+ return new Response(
230
+ JSON.stringify({
231
+ code: violation.code,
232
+ reason: violation.reason,
233
+ message: violation.message,
234
+ }),
235
+ {
236
+ status: violation.status,
237
+ headers: {
238
+ 'content-type': 'application/json; charset=utf-8',
239
+ },
240
+ },
241
+ );
242
+ }
243
+ : undefined,
244
+ onWarning: options.onWarning,
245
+ });
246
+ if (!loaded) {
247
+ throw new Error(
248
+ '[BFF][Effect] Invalid Effect edge module. Export defineEffectBff(...) or a { api, layer } HttpApi module.',
249
+ );
250
+ }
251
+
252
+ const retiredError = new Error(
253
+ '[BFF][Effect] Edge dispatcher is disposing or has been disposed.',
254
+ );
255
+ let acceptingRequests = true;
256
+ let activeDispatches = 0;
257
+ let resolveDrain: (() => void) | undefined;
258
+ let disposePromise: Promise<void> | undefined;
259
+
260
+ return {
261
+ dispatch: (
262
+ request: Request,
263
+ dispatchOptions: Omit<
264
+ EffectBffEdgeDispatchOptions,
265
+ 'prefix' | 'onError'
266
+ > = {},
267
+ ) => {
268
+ if (!acceptingRequests) {
269
+ return Promise.reject(retiredError);
270
+ }
271
+ activeDispatches += 1;
272
+ return dispatchEffectBffRequest(loaded.handler, request, {
273
+ ...dispatchOptions,
274
+ prefix: options.prefix,
275
+ onError: options.onError,
276
+ }).finally(() => {
277
+ activeDispatches -= 1;
278
+ if (activeDispatches === 0) {
279
+ resolveDrain?.();
280
+ resolveDrain = undefined;
281
+ }
282
+ });
283
+ },
284
+ dispose: () => {
285
+ disposePromise ??= (async () => {
286
+ acceptingRequests = false;
287
+ if (activeDispatches > 0) {
288
+ await new Promise<void>(resolve => {
289
+ resolveDrain = resolve;
290
+ });
291
+ }
292
+ await loaded.dispose?.();
293
+ })();
294
+ return disposePromise;
295
+ },
296
+ };
297
+ }
298
+
299
+ export const createEffectBffTestHandler = createEffectBffEdgeHandler;
300
+
301
+ export async function createEffectBffEdgeHandler(
302
+ options: EffectBffEdgeHandlerOptions,
303
+ ) {
304
+ const dispatcher = await createEffectBffEdgeDispatcher(options);
305
+
306
+ return {
307
+ handler: dispatcher.dispatch,
308
+ dispose: dispatcher.dispose,
309
+ };
310
+ }
@@ -0,0 +1 @@
1
+ export * from './edge-dispatcher';
@@ -0,0 +1,191 @@
1
+ // @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
2
+ /**
3
+ * Shared Effect HttpApi endpoint reflection used by BOTH sides of the
4
+ * cross-project contract:
5
+ *
6
+ * - the `@modern-js/plugin-bff-extensions/client-generator` package stamps
7
+ * each generated operation with a per-endpoint contract hash;
8
+ * - the `@modern-js/plugin-bff-extensions/effect-adapter` package derives the
9
+ * expected operation-contract map for the cross-project policy from the
10
+ * same endpoints.
11
+ *
12
+ * Keeping route-path normalization and the endpoint -> contract mapping in
13
+ * one module is what guarantees the two hashes agree.
14
+ */
15
+ import {
16
+ createOperationContractHash,
17
+ type OperationContractSource,
18
+ } from '@modern-js/bff-core';
19
+ import {
20
+ classifyEffectBffEntryModule,
21
+ isValidatorAwareHandlerFactory,
22
+ } from './entry-shape';
23
+
24
+ export type EffectEndpointMeta = {
25
+ apiId: string;
26
+ groupName: string;
27
+ endpointName: string;
28
+ method: string;
29
+ routePath: string;
30
+ };
31
+
32
+ export type HttpApiLike = {
33
+ identifier?: unknown;
34
+ };
35
+
36
+ type HttpApiGroupLike = {
37
+ identifier?: unknown;
38
+ };
39
+
40
+ type HttpApiEndpointLike = {
41
+ /**
42
+ * effect 4.0.0-beta.98 renamed the endpoint `name` property to `identifier`,
43
+ * matching `HttpApiGroup`. `name` is kept as a fallback so reflection over an
44
+ * older Effect build does not silently degrade to the class name.
45
+ */
46
+ identifier?: unknown;
47
+ name?: unknown;
48
+ method?: unknown;
49
+ path?: unknown;
50
+ };
51
+
52
+ function resolveEffectEndpointName(endpoint: HttpApiEndpointLike): string {
53
+ if (typeof endpoint.identifier === 'string' && endpoint.identifier) {
54
+ return endpoint.identifier;
55
+ }
56
+ if (typeof endpoint.name === 'string' && endpoint.name) {
57
+ return endpoint.name;
58
+ }
59
+ return '';
60
+ }
61
+
62
+ export type HttpApiReflect = (
63
+ api: unknown,
64
+ handlers: {
65
+ /** Group metadata is unused by contract collection; kept as a no-op. */
66
+ onGroup?: () => void;
67
+ onEndpoint: (input: {
68
+ group: HttpApiGroupLike;
69
+ endpoint: HttpApiEndpointLike;
70
+ }) => void;
71
+ },
72
+ ) => void;
73
+
74
+ export function ensureLeadingSlash(pathname: string) {
75
+ return pathname.startsWith('/') ? pathname : `/${pathname}`;
76
+ }
77
+
78
+ export function normalizeEffectPrefix(prefix: string) {
79
+ if (prefix === '/') {
80
+ return '';
81
+ }
82
+ return ensureLeadingSlash(prefix || '/api');
83
+ }
84
+
85
+ function getEffectRoutePath(prefix: string, endpointPath: string) {
86
+ const normalizedPrefix = normalizeEffectPrefix(prefix);
87
+ const normalizedEndpointPath = ensureLeadingSlash(endpointPath);
88
+ const finalEndpointPath = normalizedEndpointPath === '/' ? '' : endpointPath;
89
+ if (!normalizedPrefix && !finalEndpointPath) {
90
+ return '/';
91
+ }
92
+ return `${normalizedPrefix}${finalEndpointPath || ''}`;
93
+ }
94
+
95
+ function resolveEffectApiId(api: HttpApiLike): string {
96
+ const fallback = 'EffectHttpApi';
97
+ if (
98
+ 'identifier' in api &&
99
+ typeof api.identifier === 'string' &&
100
+ api.identifier
101
+ ) {
102
+ return api.identifier;
103
+ }
104
+ return fallback;
105
+ }
106
+
107
+ export function collectEffectEndpoints(
108
+ reflect: HttpApiReflect,
109
+ api: HttpApiLike,
110
+ prefix: string,
111
+ ): EffectEndpointMeta[] {
112
+ const endpoints: EffectEndpointMeta[] = [];
113
+ const apiId = resolveEffectApiId(api);
114
+ reflect(api, {
115
+ onGroup: () => {
116
+ // no-op
117
+ },
118
+ onEndpoint: ({ group, endpoint }) => {
119
+ endpoints.push({
120
+ apiId,
121
+ groupName: String(group.identifier),
122
+ endpointName: resolveEffectEndpointName(endpoint),
123
+ method: String(endpoint.method).toUpperCase(),
124
+ routePath: getEffectRoutePath(prefix, String(endpoint.path)),
125
+ });
126
+ },
127
+ });
128
+ return endpoints.sort((a, b) => {
129
+ if (a.groupName === b.groupName) {
130
+ return a.endpointName.localeCompare(b.endpointName);
131
+ }
132
+ return a.groupName.localeCompare(b.groupName);
133
+ });
134
+ }
135
+
136
+ /**
137
+ * Maps reflected endpoints onto operation-contract sources for
138
+ * `buildOperationContractMap`/`resolveCrossProjectPolicy`. Effect endpoints
139
+ * carry no zod metadata, so the contract hash covers the endpoint identity.
140
+ */
141
+ export function toOperationContractSources(
142
+ endpoints: EffectEndpointMeta[],
143
+ ): OperationContractSource[] {
144
+ return endpoints.map(createEffectOperationContractSource);
145
+ }
146
+
147
+ function createEffectOperationContractSource(
148
+ endpoint: EffectEndpointMeta,
149
+ ): OperationContractSource {
150
+ return {
151
+ name: endpoint.endpointName,
152
+ httpMethod: endpoint.method,
153
+ routePath: endpoint.routePath,
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Extracts the HttpApi instance from a loaded effect entry module. Mirrors
159
+ * the shared entry-shape policy accepted by `resolveEffectBffModuleHandler`,
160
+ * without executing factories during contract extraction.
161
+ */
162
+ export async function extractHttpApiFromModule(
163
+ mod: unknown,
164
+ isHttpApi: (value: unknown) => boolean,
165
+ ): Promise<HttpApiLike | null> {
166
+ const facts = classifyEffectBffEntryModule(mod, {
167
+ isRequestHandler: value => typeof value === 'function',
168
+ isValidatorAwareHandlerFactory,
169
+ isHttpApi,
170
+ });
171
+ return facts?.legacyShape ||
172
+ facts?.api === undefined ||
173
+ !facts.hasRuntimeLayer ||
174
+ (facts.createHandler !== undefined && !facts.createHandlerValidatorAware)
175
+ ? null
176
+ : (facts.api as HttpApiLike);
177
+ }
178
+
179
+ /**
180
+ * Per-endpoint contract hash; MUST stay in sync with the server-side
181
+ * contract map (it is the same bff-core hash over the same inputs).
182
+ */
183
+ export function createEffectEndpointContractHash(
184
+ endpoint: EffectEndpointMeta,
185
+ requestId: string,
186
+ ): string {
187
+ return createOperationContractHash(
188
+ createEffectOperationContractSource(endpoint),
189
+ requestId,
190
+ );
191
+ }
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const validatorAwareHandlerFactories = new WeakSet();
4
+
5
+ module.exports = {
6
+ register(factory) {
7
+ validatorAwareHandlerFactories.add(factory);
8
+ return factory;
9
+ },
10
+ is(factory) {
11
+ return (
12
+ typeof factory === 'function' &&
13
+ validatorAwareHandlerFactories.has(factory)
14
+ );
15
+ },
16
+ };
@@ -0,0 +1,6 @@
1
+ declare const registry: {
2
+ register<TFactory extends Function>(factory: TFactory): TFactory;
3
+ is(factory: unknown): boolean;
4
+ };
5
+
6
+ export = registry;
@@ -0,0 +1,155 @@
1
+ type ValidatorAwareHandlerFactoryRegistry = {
2
+ register<TFactory extends Function>(factory: TFactory): TFactory;
3
+ is(factory: unknown): boolean;
4
+ };
5
+
6
+ function createLocalValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandlerFactoryRegistry {
7
+ const factories = new WeakSet<Function>();
8
+ return {
9
+ register<TFactory extends Function>(factory: TFactory): TFactory {
10
+ factories.add(factory);
11
+ return factory;
12
+ },
13
+ is(factory: unknown): boolean {
14
+ return typeof factory === 'function' && factories.has(factory);
15
+ },
16
+ };
17
+ }
18
+
19
+ function loadNodeValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandlerFactoryRegistry {
20
+ const moduleUrl = import.meta.url;
21
+ if (typeof moduleUrl !== 'string' || !moduleUrl.startsWith('file:')) {
22
+ return createLocalValidatorAwareHandlerFactoryRegistry();
23
+ }
24
+
25
+ const moduleBuiltin = process.getBuiltinModule(
26
+ 'node:module',
27
+ ) as typeof import('node:module');
28
+ try {
29
+ return moduleBuiltin.createRequire(moduleUrl)(
30
+ `#effect-entry-shape-${'registry'}`,
31
+ ) as ValidatorAwareHandlerFactoryRegistry;
32
+ } catch (error) {
33
+ if (
34
+ error !== null &&
35
+ typeof error === 'object' &&
36
+ 'code' in error &&
37
+ error.code === 'MODULE_NOT_FOUND'
38
+ ) {
39
+ return createLocalValidatorAwareHandlerFactoryRegistry();
40
+ }
41
+ throw error;
42
+ }
43
+ }
44
+
45
+ const validatorAwareHandlerFactoryRegistry =
46
+ process.env.MODERN_EFFECT_NODE_RUNTIME === 'true'
47
+ ? loadNodeValidatorAwareHandlerFactoryRegistry()
48
+ : createLocalValidatorAwareHandlerFactoryRegistry();
49
+
50
+ type EffectBffEntryModule = {
51
+ api?: unknown;
52
+ layer?: unknown;
53
+ handler?: unknown;
54
+ createHandler?: unknown;
55
+ default?: unknown;
56
+ };
57
+
58
+ type LegacyEffectBffEntryShape = '`handler` export' | 'default request handler';
59
+
60
+ export type EffectBffEntryShapeFacts = {
61
+ module: EffectBffEntryModule;
62
+ legacyShape?: LegacyEffectBffEntryShape;
63
+ createHandler?: unknown;
64
+ createHandlerValidatorAware: boolean;
65
+ api?: unknown;
66
+ layer?: unknown;
67
+ hasRuntimeLayer: boolean;
68
+ };
69
+
70
+ type EffectBffEntryShapePredicates = {
71
+ isRequestHandler: (value: unknown) => boolean;
72
+ isValidatorAwareHandlerFactory: (value: unknown) => boolean;
73
+ isHttpApi: (value: unknown) => boolean;
74
+ };
75
+
76
+ /** @internal Registers factories created by `defineEffectBff`. */
77
+ export function registerValidatorAwareHandlerFactory<TFactory extends Function>(
78
+ factory: TFactory,
79
+ ): TFactory {
80
+ return validatorAwareHandlerFactoryRegistry.register(factory);
81
+ }
82
+
83
+ /**
84
+ * True when a custom createHandler factory is produced by defineEffectBff and
85
+ * therefore forwards strict cross-project validation into createHttpApiHandler.
86
+ */
87
+ export function isValidatorAwareHandlerFactory(factory: unknown): boolean {
88
+ return validatorAwareHandlerFactoryRegistry.is(factory);
89
+ }
90
+
91
+ export const strictEffectApproachMessage =
92
+ '[BFF][Effect] strictEffectApproach is enforced: Effect API entries export defineEffectBff(...) or { api, layer } HttpApi module. Raw handler exports, default request handlers, unbranded custom createHandler factories not valid Effect API entries.';
93
+
94
+ function isEffectEntryRecord(value: unknown): value is Record<string, unknown> {
95
+ return typeof value === 'object' && value !== null;
96
+ }
97
+
98
+ export function classifyEffectBffEntryModule(
99
+ value: unknown,
100
+ predicates: EffectBffEntryShapePredicates,
101
+ ): EffectBffEntryShapeFacts | null {
102
+ if (!isEffectEntryRecord(value)) {
103
+ return null;
104
+ }
105
+
106
+ const rootModule = value;
107
+
108
+ if (predicates.isRequestHandler(rootModule.handler)) {
109
+ return createEntryShapeFacts(rootModule, predicates, '`handler` export');
110
+ }
111
+
112
+ const defaultEntry = rootModule.default;
113
+ if (predicates.isRequestHandler(defaultEntry)) {
114
+ return createEntryShapeFacts(
115
+ rootModule,
116
+ predicates,
117
+ 'default request handler',
118
+ );
119
+ }
120
+
121
+ const module = isEffectEntryRecord(defaultEntry)
122
+ ? { ...rootModule, ...defaultEntry }
123
+ : rootModule;
124
+
125
+ if (predicates.isRequestHandler(module.handler)) {
126
+ return createEntryShapeFacts(module, predicates, '`handler` export');
127
+ }
128
+
129
+ return createEntryShapeFacts(module, predicates);
130
+ }
131
+
132
+ function createEntryShapeFacts(
133
+ module: EffectBffEntryModule,
134
+ predicates: EffectBffEntryShapePredicates,
135
+ legacyShape?: LegacyEffectBffEntryShape,
136
+ ): EffectBffEntryShapeFacts {
137
+ const createHandler =
138
+ typeof module.createHandler === 'function'
139
+ ? module.createHandler
140
+ : undefined;
141
+ const api = predicates.isHttpApi(module.api) ? module.api : undefined;
142
+ const hasRuntimeLayer = module.layer !== undefined;
143
+
144
+ return {
145
+ module,
146
+ legacyShape,
147
+ createHandler,
148
+ createHandlerValidatorAware:
149
+ createHandler !== undefined &&
150
+ predicates.isValidatorAwareHandlerFactory(createHandler),
151
+ api,
152
+ layer: module.layer,
153
+ hasRuntimeLayer,
154
+ };
155
+ }