@bleedingdev/modern-js-plugin-bff 3.2.0-ultramodern.12 → 3.2.0-ultramodern.121

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 (80) hide show
  1. package/dist/cjs/cli.js +9 -5
  2. package/dist/cjs/constants.js +13 -9
  3. package/dist/cjs/index.js +9 -5
  4. package/dist/cjs/loader.js +32 -5
  5. package/dist/cjs/runtime/create-request/index.js +9 -5
  6. package/dist/cjs/runtime/data-platform/index.js +50 -26
  7. package/dist/cjs/runtime/effect/adapter.js +99 -93
  8. package/dist/cjs/runtime/effect/context.js +19 -7
  9. package/dist/cjs/runtime/effect/edge.js +169 -0
  10. package/dist/cjs/runtime/effect/endpoint-contracts.js +130 -0
  11. package/dist/cjs/runtime/effect/handler.js +642 -0
  12. package/dist/cjs/runtime/effect/index.js +30 -547
  13. package/dist/cjs/runtime/effect/module.js +151 -0
  14. package/dist/cjs/runtime/effect/operation-context.js +103 -0
  15. package/dist/cjs/runtime/effect-client/index.js +22 -6
  16. package/dist/cjs/runtime/effect-client/runtime.js +266 -0
  17. package/dist/cjs/runtime/hono/adapter.js +30 -14
  18. package/dist/cjs/runtime/hono/index.js +9 -5
  19. package/dist/cjs/runtime/hono/operators.js +9 -5
  20. package/dist/cjs/runtime/safe-failure.js +83 -0
  21. package/dist/cjs/server.js +9 -5
  22. package/dist/cjs/utils/clientGenerator.js +13 -9
  23. package/dist/cjs/utils/createHonoRoutes.js +9 -5
  24. package/dist/cjs/utils/crossProjectApiPlugin.js +9 -5
  25. package/dist/cjs/utils/crossProjectServerPolicy.js +104 -0
  26. package/dist/cjs/utils/effectClientGenerator.js +116 -488
  27. package/dist/cjs/utils/pluginGenerator.js +9 -5
  28. package/dist/cjs/utils/runtimeGenerator.js +9 -5
  29. package/dist/esm/loader.mjs +23 -0
  30. package/dist/esm/runtime/data-platform/index.mjs +33 -22
  31. package/dist/esm/runtime/effect/adapter.mjs +91 -89
  32. package/dist/esm/runtime/effect/context.mjs +3 -1
  33. package/dist/esm/runtime/effect/edge.mjs +83 -0
  34. package/dist/esm/runtime/effect/endpoint-contracts.mjs +68 -0
  35. package/dist/esm/runtime/effect/handler.mjs +470 -0
  36. package/dist/esm/runtime/effect/index.mjs +3 -437
  37. package/dist/esm/runtime/effect/module.mjs +113 -0
  38. package/dist/esm/runtime/effect/operation-context.mjs +65 -0
  39. package/dist/esm/runtime/effect-client/index.mjs +14 -2
  40. package/dist/esm/runtime/effect-client/runtime.mjs +228 -0
  41. package/dist/esm/runtime/hono/adapter.mjs +21 -9
  42. package/dist/esm/runtime/safe-failure.mjs +45 -0
  43. package/dist/esm/utils/clientGenerator.mjs +5 -5
  44. package/dist/esm/utils/crossProjectServerPolicy.mjs +50 -0
  45. package/dist/esm/utils/effectClientGenerator.mjs +105 -484
  46. package/dist/esm-node/loader.mjs +23 -0
  47. package/dist/esm-node/runtime/data-platform/index.mjs +33 -22
  48. package/dist/esm-node/runtime/effect/adapter.mjs +91 -89
  49. package/dist/esm-node/runtime/effect/context.mjs +3 -1
  50. package/dist/esm-node/runtime/effect/edge.mjs +84 -0
  51. package/dist/esm-node/runtime/effect/endpoint-contracts.mjs +69 -0
  52. package/dist/esm-node/runtime/effect/handler.mjs +471 -0
  53. package/dist/esm-node/runtime/effect/index.mjs +3 -437
  54. package/dist/esm-node/runtime/effect/module.mjs +114 -0
  55. package/dist/esm-node/runtime/effect/operation-context.mjs +66 -0
  56. package/dist/esm-node/runtime/effect-client/index.mjs +14 -2
  57. package/dist/esm-node/runtime/effect-client/runtime.mjs +229 -0
  58. package/dist/esm-node/runtime/hono/adapter.mjs +21 -9
  59. package/dist/esm-node/runtime/safe-failure.mjs +46 -0
  60. package/dist/esm-node/utils/clientGenerator.mjs +5 -5
  61. package/dist/esm-node/utils/crossProjectServerPolicy.mjs +52 -0
  62. package/dist/esm-node/utils/effectClientGenerator.mjs +105 -484
  63. package/dist/types/runtime/create-request/index.d.ts +1 -0
  64. package/dist/types/runtime/data-platform/index.d.ts +4 -0
  65. package/dist/types/runtime/effect/adapter.d.ts +25 -0
  66. package/dist/types/runtime/effect/context.d.ts +3 -6
  67. package/dist/types/runtime/effect/edge.d.ts +25 -0
  68. package/dist/types/runtime/effect/endpoint-contracts.d.ts +62 -0
  69. package/dist/types/runtime/effect/handler.d.ts +203 -0
  70. package/dist/types/runtime/effect/index.d.ts +2 -170
  71. package/dist/types/runtime/effect/module.d.ts +48 -0
  72. package/dist/types/runtime/effect/operation-context.d.ts +10 -0
  73. package/dist/types/runtime/effect-client/index.d.ts +6 -1
  74. package/dist/types/runtime/effect-client/runtime.d.ts +71 -0
  75. package/dist/types/runtime/hono/adapter.d.ts +3 -0
  76. package/dist/types/runtime/safe-failure.d.ts +1 -0
  77. package/dist/types/utils/createHonoRoutes.d.ts +3 -3
  78. package/dist/types/utils/crossProjectServerPolicy.d.ts +35 -0
  79. package/dist/types/utils/effectClientGenerator.d.ts +16 -2
  80. package/package.json +41 -20
@@ -93,6 +93,7 @@ export interface DataBatchTransportEvent {
93
93
  size?: number;
94
94
  reason?: string;
95
95
  }
96
+ export type DataBatchTransportTelemetryAttributes = Record<string, string | number | boolean>;
96
97
  export interface DataBatchTransportOptions {
97
98
  endpoint?: string;
98
99
  fetch?: (input: DataTransportRequestInfo, init?: RequestInit) => Promise<Response>;
@@ -103,6 +104,9 @@ export interface DataBatchTransportOptions {
103
104
  allowedMethods?: string[];
104
105
  onEvent?: (event: DataBatchTransportEvent) => void;
105
106
  }
107
+ export declare const DATA_BATCH_TRANSPORT_OTEL_EVENT = "modernjs.data.batch";
108
+ export declare function createDataBatchTransportTelemetryAttributes(event: DataBatchTransportEvent): DataBatchTransportTelemetryAttributes;
109
+ export declare function emitDataBatchTransportEvent(onEvent: ((event: DataBatchTransportEvent) => void) | undefined, event: DataBatchTransportEvent): void;
106
110
  export interface SelectionPlanValidationOptions {
107
111
  maxDepth?: number;
108
112
  maxFields?: number;
@@ -1,4 +1,5 @@
1
1
  import type { ServerMiddleware, ServerPluginAPI } from '@modern-js/server-core';
2
+ import { type ResolvedCrossProjectPolicy } from '../../utils/crossProjectServerPolicy';
2
3
  interface MiddlewareOptions {
3
4
  prefix: string;
4
5
  enableHandleWeb?: boolean;
@@ -7,12 +8,36 @@ export declare class EffectAdapter {
7
8
  api: ServerPluginAPI;
8
9
  isEffect: boolean;
9
10
  effectMiddleware: ServerMiddleware | null;
11
+ crossProjectPolicy: ResolvedCrossProjectPolicy | undefined;
10
12
  private handler;
11
13
  private dispose;
14
+ private prefix;
15
+ /**
16
+ * True when the loaded handler does NOT run the policy seam internally
17
+ * (plain `handler` exports); the adapter middleware enforces it instead.
18
+ */
19
+ private policyEnforcedInMiddleware;
12
20
  constructor(api: ServerPluginAPI);
13
21
  registerMiddleware: (options: MiddlewareOptions) => Promise<void>;
14
22
  onApiHandlersUpdated: () => Promise<void>;
15
23
  private resolveEntryFile;
24
+ private isApiRequestPath;
25
+ /**
26
+ * Contract sources for the lambda-lane handlers hosted alongside the
27
+ * effect entry. Cross-project SDKs generate per-operation contracts for
28
+ * `api/lambda` handlers with the exact same `ApiRouter` derivation used
29
+ * here, so the server-side expected-contract map must include them —
30
+ * otherwise every lambda-lane operation of a hosted producer SDK would be
31
+ * denied as `unknown_operation_contract`.
32
+ */
33
+ private collectLambdaContractSources;
34
+ /**
35
+ * Resolves the cross-project policy from the reflected HttpApi endpoints
36
+ * (plus any hosted lambda-lane handlers) so the expected operation
37
+ * contracts match the hashes the client generators stamp into generated
38
+ * SDKs.
39
+ */
40
+ private refreshCrossProjectPolicy;
16
41
  private loadEffectHandlerFromModule;
17
42
  private reloadHandler;
18
43
  private disposeCurrentHandler;
@@ -1,8 +1,5 @@
1
- export type EffectContext = {
2
- request: Request;
3
- env: Record<string, unknown>;
4
- path: string;
5
- method: string;
6
- };
1
+ import type { EffectContext } from './operation-context';
2
+ export { type CreateEffectOperationContextOptions, createEffectOperationContext, type EffectContext, } from './operation-context';
7
3
  export declare const runWithEffectContext: <T>(context: EffectContext, cb: () => T) => T;
8
4
  export declare const useEffectContext: () => EffectContext;
5
+ export declare const useOperationContext: () => import("@modern-js/create-request").OperationContext;
@@ -0,0 +1,25 @@
1
+ import type { EffectBffOpenApiConfig, EffectDataPlatformValidationOptions } from './handler';
2
+ import { type EffectApiModule, type EffectBffRequestHandler } from './module';
3
+ import { type EffectContext } from './operation-context';
4
+ export * from './handler';
5
+ export { type CreateEffectOperationContextOptions, createEffectOperationContext, type EffectContext, } from './operation-context';
6
+ export type EffectBffEdgeDispatchOptions = {
7
+ prefix?: string;
8
+ env?: Record<string, unknown>;
9
+ path?: string;
10
+ method?: string;
11
+ onError?: (error: unknown, context: EffectContext) => Promise<Response> | Response;
12
+ };
13
+ export type EffectBffEdgeHandlerOptions = {
14
+ module: EffectApiModule;
15
+ prefix?: string;
16
+ openapi?: EffectBffOpenApiConfig;
17
+ dataPlatform?: EffectDataPlatformValidationOptions;
18
+ onError?: (error: unknown, context: EffectContext) => Promise<Response> | Response;
19
+ onWarning?: (message: string) => void;
20
+ };
21
+ export declare function dispatchEffectBffRequest(handler: EffectBffRequestHandler, request: Request, options?: EffectBffEdgeDispatchOptions): Promise<Response>;
22
+ export declare function createEffectBffEdgeHandler(options: EffectBffEdgeHandlerOptions): Promise<{
23
+ handler: (request: Request, dispatchOptions?: Omit<EffectBffEdgeDispatchOptions, 'prefix' | 'onError'>) => Promise<Response>;
24
+ dispose: () => Promise<void>;
25
+ }>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Shared Effect HttpApi endpoint reflection used by BOTH sides of the
3
+ * cross-project contract:
4
+ *
5
+ * - the client generator (`utils/effectClientGenerator`) stamps each
6
+ * generated operation with a per-endpoint contract hash;
7
+ * - the effect server adapter derives the expected operation-contract map
8
+ * for the cross-project policy from the same endpoints.
9
+ *
10
+ * Keeping route-path normalization and the endpoint -> contract mapping in
11
+ * one module is what guarantees the two hashes agree.
12
+ */
13
+ import { type OperationContractSource } from '@modern-js/bff-core';
14
+ export type EffectEndpointMeta = {
15
+ apiId: string;
16
+ groupName: string;
17
+ endpointName: string;
18
+ method: string;
19
+ routePath: string;
20
+ };
21
+ export type HttpApiLike = {
22
+ identifier?: unknown;
23
+ };
24
+ export type HttpApiGroupLike = {
25
+ identifier?: unknown;
26
+ };
27
+ export type HttpApiEndpointLike = {
28
+ name?: unknown;
29
+ method?: unknown;
30
+ path?: unknown;
31
+ };
32
+ export type HttpApiReflect = (api: unknown, handlers: {
33
+ /** Group metadata is unused by contract collection; kept as a no-op. */
34
+ onGroup?: () => void;
35
+ onEndpoint: (input: {
36
+ group: HttpApiGroupLike;
37
+ endpoint: HttpApiEndpointLike;
38
+ }) => void;
39
+ }) => void;
40
+ export declare function ensureLeadingSlash(pathname: string): string;
41
+ export declare function normalizeEffectPrefix(prefix: string): string;
42
+ export declare function getEffectRoutePath(prefix: string, endpointPath: string): string;
43
+ export declare function resolveEffectApiId(api: HttpApiLike): string;
44
+ export declare function collectEffectEndpoints(reflect: HttpApiReflect, api: HttpApiLike, prefix: string): EffectEndpointMeta[];
45
+ /**
46
+ * Maps reflected endpoints onto operation-contract sources for
47
+ * `buildOperationContractMap`/`resolveCrossProjectPolicy`. Effect endpoints
48
+ * carry no zod metadata, so the contract hash covers the endpoint identity.
49
+ */
50
+ export declare function toOperationContractSources(endpoints: EffectEndpointMeta[]): OperationContractSource[];
51
+ export declare function createEffectOperationContractSource(endpoint: EffectEndpointMeta): OperationContractSource;
52
+ /**
53
+ * Extracts the HttpApi instance from a loaded effect entry module. Mirrors
54
+ * the module shapes accepted by `resolveEffectBffModuleHandler`:
55
+ * `{ api }`, `{ default: { api } }` and zero-arg default factories.
56
+ */
57
+ export declare function extractHttpApiFromModule(mod: unknown, isHttpApi: (value: unknown) => boolean): Promise<HttpApiLike | null>;
58
+ /**
59
+ * Per-endpoint contract hash; MUST stay in sync with the server-side
60
+ * contract map (it is the same bff-core hash over the same inputs).
61
+ */
62
+ export declare function createEffectEndpointContractHash(endpoint: EffectEndpointMeta, requestId: string): string;
@@ -0,0 +1,203 @@
1
+ import type { FileSystem, Path } from 'effect';
2
+ import * as Context from 'effect/Context';
3
+ import type * as EffectType from 'effect/Effect';
4
+ import * as Layer from 'effect/Layer';
5
+ import { Etag, HttpPlatform, HttpRouter } from 'effect/unstable/http';
6
+ import { type HttpApi, type HttpApiClient, type HttpApiGroup } from 'effect/unstable/httpapi';
7
+ import { type Rpc, type RpcGroup } from 'effect/unstable/rpc';
8
+ export * as Config from 'effect/Config';
9
+ export * as Effect from 'effect/Effect';
10
+ export * as Layer from 'effect/Layer';
11
+ export * as Option from 'effect/Option';
12
+ export * as Schema from 'effect/Schema';
13
+ export * from 'effect/unstable/http';
14
+ export { HttpTraceContext } from 'effect/unstable/http';
15
+ export * from 'effect/unstable/httpapi';
16
+ export { HttpApiBuilder } from 'effect/unstable/httpapi';
17
+ export * from 'effect/unstable/rpc';
18
+ export type EffectRuntimeRequirements = Etag.Generator | FileSystem.FileSystem | HttpPlatform.HttpPlatform | HttpRouter.HttpRouter | HttpRouter.Request<'Error', any> | HttpRouter.Request<'GlobalError', any> | HttpRouter.Request<'GlobalRequires', any> | HttpRouter.Request<'Requires', any> | Path.Path;
19
+ export type EffectRuntimeLayer = Layer.Layer<never, never, EffectRuntimeRequirements>;
20
+ export type EffectRpcSerialization = 'json' | 'ndjson' | 'jsonRpc' | 'ndJsonRpc' | 'msgPack';
21
+ export type EffectRpcRuntimeLayer<TRpcs extends Rpc.Any = Rpc.Any> = Layer.Layer<Rpc.ToHandler<TRpcs> | Rpc.Middleware<TRpcs> | Rpc.ServicesServer<TRpcs>, unknown, never>;
22
+ export type EffectRpcBffDefinition<TRpcs extends Rpc.Any = Rpc.Any, TRpcLayer extends EffectRpcRuntimeLayer<TRpcs> = EffectRpcRuntimeLayer<TRpcs>> = {
23
+ group: RpcGroup.RpcGroup<TRpcs>;
24
+ layer: TRpcLayer;
25
+ path?: `/${string}`;
26
+ serialization?: EffectRpcSerialization;
27
+ disableTracing?: boolean;
28
+ spanPrefix?: string;
29
+ spanAttributes?: Record<string, unknown>;
30
+ disableFatalDefects?: boolean;
31
+ };
32
+ export type EffectRpcBffHandlerOptions = Pick<EffectRpcBffDefinition, 'path' | 'serialization' | 'disableTracing' | 'spanPrefix' | 'spanAttributes' | 'disableFatalDefects'>;
33
+ type EffectApiPromiseClient<TClient> = {
34
+ [GroupName in keyof TClient]: {
35
+ [EndpointName in keyof TClient[GroupName]]: TClient[GroupName][EndpointName] extends (...args: infer TArgs) => infer TResult ? TResult extends EffectType.Effect<unknown, unknown, unknown> ? (...args: TArgs) => Promise<Exclude<EffectType.Success<TResult>, readonly [unknown, unknown]>> : never : never;
36
+ };
37
+ };
38
+ export type EffectApiClientFromApi<TApi extends HttpApi.AnyWithProps = HttpApi.AnyWithProps> = TApi extends HttpApi.HttpApi<infer _ApiId, infer Groups> ? HttpApiClient.Client<Extract<Groups, HttpApiGroup.Any>, unknown, never> : never;
39
+ export type EffectApiPromiseClientFromApi<TApi extends HttpApi.AnyWithProps = HttpApi.AnyWithProps> = EffectApiPromiseClient<EffectApiClientFromApi<TApi>>;
40
+ export type EffectBffDefinition<TApi extends HttpApi.AnyWithProps = HttpApi.AnyWithProps, TLayer extends EffectRuntimeLayer = EffectRuntimeLayer, TRpcs extends Rpc.Any = Rpc.Any> = {
41
+ api: TApi;
42
+ layer: TLayer;
43
+ rpc?: EffectRpcBffDefinition<TRpcs>;
44
+ dataPlatform?: EffectDataPlatformValidationOptions;
45
+ };
46
+ export type EffectDataPlatformSelectionValidationOptions = {
47
+ maxDepth?: number;
48
+ maxFields?: number;
49
+ allowedLeafPaths?: string[];
50
+ };
51
+ export type EffectDataPlatformBatchOptions = {
52
+ /**
53
+ * Enable network batching endpoint for Effect HttpApi requests.
54
+ * Defaults to `true`.
55
+ */
56
+ enabled?: boolean;
57
+ /**
58
+ * Batch endpoint path mounted under BFF prefix.
59
+ * Defaults to `/_data/batch`.
60
+ */
61
+ endpoint?: `/${string}`;
62
+ /**
63
+ * Maximum request items accepted per batch call.
64
+ * Defaults to `16`.
65
+ */
66
+ maxBatchSize?: number;
67
+ /**
68
+ * Maximum serialized request payload size in bytes.
69
+ * Defaults to `65536` (64KiB).
70
+ */
71
+ maxBatchBytes?: number;
72
+ /**
73
+ * Client-side micro-batch flush window in milliseconds.
74
+ * Server runtime ignores this value and passes it through for codegen.
75
+ * Defaults to `8`.
76
+ */
77
+ flushIntervalMs?: number;
78
+ /**
79
+ * Maximum per-batch internal request concurrency.
80
+ * Defaults to `4`.
81
+ */
82
+ maxConcurrency?: number;
83
+ /**
84
+ * Optional timeout per internal request in milliseconds.
85
+ * Defaults to `10000`.
86
+ */
87
+ requestTimeoutMs?: number;
88
+ /**
89
+ * Allowed HTTP methods for internal batched dispatch.
90
+ * Defaults to `['GET']`.
91
+ */
92
+ allowedMethods?: string[];
93
+ };
94
+ export type EffectDataPlatformValidationOptions = {
95
+ /**
96
+ * Enable envelope validation for HttpApi requests.
97
+ * Defaults to `true`.
98
+ */
99
+ enabled?: boolean;
100
+ /**
101
+ * Require every HttpApi request to include an envelope header.
102
+ * Defaults to `false`.
103
+ */
104
+ requireEnvelope?: boolean;
105
+ /**
106
+ * Header name used by client/server for the serialized request envelope.
107
+ * Defaults to `x-modernjs-data-envelope`.
108
+ */
109
+ envelopeHeader?: string;
110
+ /**
111
+ * Optional namespace assertion for envelope validation.
112
+ */
113
+ expectedNamespace?: string;
114
+ /**
115
+ * Validate envelope origin against incoming request origin.
116
+ * Defaults to `true`.
117
+ */
118
+ validateOrigin?: boolean;
119
+ /**
120
+ * Require trace context inside request envelope.
121
+ * Defaults to `false`.
122
+ */
123
+ requireTraceContext?: boolean;
124
+ /**
125
+ * Selection plan guardrails for server-side validation.
126
+ */
127
+ selection?: EffectDataPlatformSelectionValidationOptions;
128
+ /**
129
+ * Network batching gateway configuration.
130
+ */
131
+ batch?: EffectDataPlatformBatchOptions;
132
+ };
133
+ /**
134
+ * Server-seam request validator applied to every HttpApi request — direct,
135
+ * batched (per item) and rpc. Returning a Response short-circuits the
136
+ * request; returning `null`/`undefined` lets it through. Used by the effect
137
+ * adapter to enforce the cross-project policy at one seam so batched items
138
+ * cannot bypass it.
139
+ */
140
+ export type EffectRequestValidator = (request: Request) => Response | null | undefined;
141
+ /**
142
+ * Brand stamped on `createHandler` factories that are guaranteed to forward
143
+ * `options.validateRequest` into `createHttpApiHandler` — i.e. the policy
144
+ * seam runs inside the produced handler, including per batched item.
145
+ *
146
+ * `defineEffectBff` brands its factory automatically. Hand-written factories
147
+ * are NOT assumed to honor `validateRequest` (the option is newer than the
148
+ * factory shape), so the effect adapter falls back to middleware enforcement
149
+ * for unbranded factories instead of silently skipping the policy. Custom
150
+ * factories that do forward `validateRequest` into `createHttpApiHandler`
151
+ * may opt in by setting this symbol property to `true`.
152
+ *
153
+ * Registered via `Symbol.for` so independently loaded runtime copies agree.
154
+ */
155
+ export declare const EFFECT_VALIDATOR_AWARE_FACTORY: symbol;
156
+ /**
157
+ * True when `factory` is branded as validator-aware (see
158
+ * {@link EFFECT_VALIDATOR_AWARE_FACTORY}).
159
+ */
160
+ export declare function isValidatorAwareHandlerFactory(factory: unknown): boolean;
161
+ export type EffectBffHandlerFactory<TApi extends HttpApi.AnyWithProps = HttpApi.AnyWithProps, TLayer extends EffectRuntimeLayer = EffectRuntimeLayer> = (options?: {
162
+ openapi?: EffectBffOpenApiConfig;
163
+ rpc?: Partial<EffectRpcBffHandlerOptions>;
164
+ dataPlatform?: Partial<EffectDataPlatformValidationOptions>;
165
+ validateRequest?: EffectRequestValidator;
166
+ }) => ReturnType<typeof createHttpApiHandler>;
167
+ export type EffectBffRuntime<TApi extends HttpApi.AnyWithProps = HttpApi.AnyWithProps, TLayer extends EffectRuntimeLayer = EffectRuntimeLayer> = {
168
+ createHandler: EffectBffHandlerFactory<TApi, TLayer>;
169
+ client: EffectApiPromiseClientFromApi<TApi>;
170
+ };
171
+ export type EffectBffOpenApiConfig = boolean | {
172
+ path?: string;
173
+ };
174
+ export type EffectRpcBffHandlerFactory<TRpcs extends Rpc.Any = Rpc.Any> = (options?: Partial<EffectRpcBffHandlerOptions>) => ReturnType<typeof createRpcApiHandler<TRpcs>>;
175
+ declare function createRpcApiHandler<TRpcs extends Rpc.Any = Rpc.Any>(options: EffectRpcBffDefinition<TRpcs>): {
176
+ readonly handler: (request: globalThis.Request, context?: Context.Context<never> | undefined) => Promise<Response>;
177
+ readonly dispose: () => Promise<void>;
178
+ };
179
+ export declare function defineEffectBff<TApi extends HttpApi.AnyWithProps, TLayer extends EffectRuntimeLayer, TRpcs extends Rpc.Any = Rpc.Any>(definition: {
180
+ api: TApi;
181
+ layer: TLayer;
182
+ rpc?: EffectRpcBffDefinition<TRpcs>;
183
+ dataPlatform?: EffectDataPlatformValidationOptions;
184
+ }): {
185
+ api: TApi;
186
+ layer: TLayer;
187
+ rpc?: EffectRpcBffDefinition<TRpcs>;
188
+ dataPlatform?: EffectDataPlatformValidationOptions;
189
+ } & EffectBffRuntime<TApi, TLayer>;
190
+ export declare function defineEffectRpcBff<TRpcs extends Rpc.Any = Rpc.Any, TLayer extends EffectRpcRuntimeLayer<TRpcs> = EffectRpcRuntimeLayer<TRpcs>>(definition: EffectRpcBffDefinition<TRpcs, TLayer>): EffectRpcBffDefinition<TRpcs, TLayer> & {
191
+ createHandler: EffectRpcBffHandlerFactory<TRpcs>;
192
+ };
193
+ export declare function createHttpApiHandler<TApi extends HttpApi.AnyWithProps = HttpApi.AnyWithProps, TRpcs extends Rpc.Any = Rpc.Any>(options: {
194
+ api: TApi;
195
+ layer: EffectRuntimeLayer;
196
+ openapi?: EffectBffOpenApiConfig;
197
+ rpc?: EffectRpcBffDefinition<TRpcs>;
198
+ dataPlatform?: EffectDataPlatformValidationOptions;
199
+ validateRequest?: EffectRequestValidator;
200
+ }): {
201
+ handler: (request: Request, context?: Parameters<(request: globalThis.Request, context: Context.Context<any>) => Promise<Response>>[1]) => Promise<Response>;
202
+ dispose: () => Promise<void>;
203
+ };
@@ -1,171 +1,3 @@
1
- import type * as EffectType from 'effect/Effect';
2
- import * as Layer from 'effect/Layer';
3
- import { type HttpApi, type HttpApiClient, type HttpApiGroup } from 'effect/unstable/httpapi';
4
- import { type Rpc, type RpcGroup } from 'effect/unstable/rpc';
5
1
  export * as OpenTelemetry from '@effect/opentelemetry';
6
- export * as Config from 'effect/Config';
7
- export * as Effect from 'effect/Effect';
8
- export * as Layer from 'effect/Layer';
9
- export * as Option from 'effect/Option';
10
- export * as Schema from 'effect/Schema';
11
- export * from 'effect/unstable/http';
12
- export { HttpTraceContext } from 'effect/unstable/http';
13
- export * from 'effect/unstable/httpapi';
14
- export { HttpApiBuilder } from 'effect/unstable/httpapi';
15
- export * from 'effect/unstable/rpc';
16
- export { type EffectContext, useEffectContext } from './context';
17
- export type EffectRuntimeLayer = Layer.Layer<never, unknown, unknown>;
18
- export type EffectRpcSerialization = 'json' | 'ndjson' | 'jsonRpc' | 'ndJsonRpc' | 'msgPack';
19
- export type EffectRpcRuntimeLayer<TRpcs extends Rpc.Any = Rpc.Any> = Layer.Layer<Rpc.ToHandler<TRpcs> | Rpc.Middleware<TRpcs> | Rpc.ServicesServer<TRpcs>, unknown, never>;
20
- export type EffectRpcBffDefinition<TRpcs extends Rpc.Any = Rpc.Any, TRpcLayer extends EffectRpcRuntimeLayer<TRpcs> = EffectRpcRuntimeLayer<TRpcs>> = {
21
- group: RpcGroup.RpcGroup<TRpcs>;
22
- layer: TRpcLayer;
23
- path?: `/${string}`;
24
- serialization?: EffectRpcSerialization;
25
- disableTracing?: boolean;
26
- spanPrefix?: string;
27
- spanAttributes?: Record<string, unknown>;
28
- disableFatalDefects?: boolean;
29
- };
30
- export type EffectRpcBffHandlerOptions = Pick<EffectRpcBffDefinition, 'path' | 'serialization' | 'disableTracing' | 'spanPrefix' | 'spanAttributes' | 'disableFatalDefects'>;
31
- type EffectApiPromiseClient<TClient> = {
32
- [GroupName in keyof TClient]: {
33
- [EndpointName in keyof TClient[GroupName]]: TClient[GroupName][EndpointName] extends (...args: infer TArgs) => infer TResult ? TResult extends EffectType.Effect<unknown, unknown, unknown> ? (...args: TArgs) => Promise<Exclude<EffectType.Success<TResult>, readonly [unknown, unknown]>> : never : never;
34
- };
35
- };
36
- export type EffectApiClientFromApi<TApi extends HttpApi.AnyWithProps = HttpApi.AnyWithProps> = TApi extends HttpApi.HttpApi<infer _ApiId, infer Groups> ? HttpApiClient.Client<Extract<Groups, HttpApiGroup.Any>, unknown, never> : never;
37
- export type EffectApiPromiseClientFromApi<TApi extends HttpApi.AnyWithProps = HttpApi.AnyWithProps> = EffectApiPromiseClient<EffectApiClientFromApi<TApi>>;
38
- export type EffectBffDefinition<TApi extends HttpApi.AnyWithProps = HttpApi.AnyWithProps, TLayer extends EffectRuntimeLayer = EffectRuntimeLayer, TRpcs extends Rpc.Any = Rpc.Any> = {
39
- api: TApi;
40
- layer: TLayer;
41
- rpc?: EffectRpcBffDefinition<TRpcs>;
42
- dataPlatform?: EffectDataPlatformValidationOptions;
43
- };
44
- export type EffectDataPlatformSelectionValidationOptions = {
45
- maxDepth?: number;
46
- maxFields?: number;
47
- allowedLeafPaths?: string[];
48
- };
49
- export type EffectDataPlatformBatchOptions = {
50
- /**
51
- * Enable network batching endpoint for Effect HttpApi requests.
52
- * Defaults to `true`.
53
- */
54
- enabled?: boolean;
55
- /**
56
- * Batch endpoint path mounted under BFF prefix.
57
- * Defaults to `/_data/batch`.
58
- */
59
- endpoint?: `/${string}`;
60
- /**
61
- * Maximum request items accepted per batch call.
62
- * Defaults to `16`.
63
- */
64
- maxBatchSize?: number;
65
- /**
66
- * Maximum serialized request payload size in bytes.
67
- * Defaults to `65536` (64KiB).
68
- */
69
- maxBatchBytes?: number;
70
- /**
71
- * Client-side micro-batch flush window in milliseconds.
72
- * Server runtime ignores this value and passes it through for codegen.
73
- * Defaults to `8`.
74
- */
75
- flushIntervalMs?: number;
76
- /**
77
- * Maximum per-batch internal request concurrency.
78
- * Defaults to `4`.
79
- */
80
- maxConcurrency?: number;
81
- /**
82
- * Optional timeout per internal request in milliseconds.
83
- * Defaults to `10000`.
84
- */
85
- requestTimeoutMs?: number;
86
- /**
87
- * Allowed HTTP methods for internal batched dispatch.
88
- * Defaults to `['GET']`.
89
- */
90
- allowedMethods?: string[];
91
- };
92
- export type EffectDataPlatformValidationOptions = {
93
- /**
94
- * Enable envelope validation for HttpApi requests.
95
- * Defaults to `true`.
96
- */
97
- enabled?: boolean;
98
- /**
99
- * Require every HttpApi request to include an envelope header.
100
- * Defaults to `false`.
101
- */
102
- requireEnvelope?: boolean;
103
- /**
104
- * Header name used by client/server for the serialized request envelope.
105
- * Defaults to `x-modernjs-data-envelope`.
106
- */
107
- envelopeHeader?: string;
108
- /**
109
- * Optional namespace assertion for envelope validation.
110
- */
111
- expectedNamespace?: string;
112
- /**
113
- * Validate envelope origin against incoming request origin.
114
- * Defaults to `true`.
115
- */
116
- validateOrigin?: boolean;
117
- /**
118
- * Require trace context inside request envelope.
119
- * Defaults to `false`.
120
- */
121
- requireTraceContext?: boolean;
122
- /**
123
- * Selection plan guardrails for server-side validation.
124
- */
125
- selection?: EffectDataPlatformSelectionValidationOptions;
126
- /**
127
- * Network batching gateway configuration.
128
- */
129
- batch?: EffectDataPlatformBatchOptions;
130
- };
131
- export type EffectBffHandlerFactory<TApi extends HttpApi.AnyWithProps = HttpApi.AnyWithProps, TLayer extends EffectRuntimeLayer = EffectRuntimeLayer> = (options?: {
132
- openapi?: EffectBffOpenApiConfig;
133
- rpc?: Partial<EffectRpcBffHandlerOptions>;
134
- dataPlatform?: Partial<EffectDataPlatformValidationOptions>;
135
- }) => ReturnType<typeof createHttpApiHandler>;
136
- export type EffectBffRuntime<TApi extends HttpApi.AnyWithProps = HttpApi.AnyWithProps, TLayer extends EffectRuntimeLayer = EffectRuntimeLayer> = {
137
- createHandler: EffectBffHandlerFactory<TApi, TLayer>;
138
- client: EffectApiPromiseClientFromApi<TApi>;
139
- };
140
- export type EffectBffOpenApiConfig = boolean | {
141
- path?: string;
142
- };
143
- export type EffectRpcBffHandlerFactory<TRpcs extends Rpc.Any = Rpc.Any> = (options?: Partial<EffectRpcBffHandlerOptions>) => ReturnType<typeof createRpcApiHandler<TRpcs>>;
144
- declare function createRpcApiHandler<TRpcs extends Rpc.Any = Rpc.Any>(options: EffectRpcBffDefinition<TRpcs>): {
145
- readonly handler: (request: globalThis.Request, context?: import("effect/Context").Context<never> | undefined) => Promise<Response>;
146
- readonly dispose: () => Promise<void>;
147
- };
148
- export declare function defineEffectBff<TApi extends HttpApi.AnyWithProps, TLayer extends EffectRuntimeLayer, TRpcs extends Rpc.Any = Rpc.Any>(definition: {
149
- api: TApi;
150
- layer: TLayer;
151
- rpc?: EffectRpcBffDefinition<TRpcs>;
152
- dataPlatform?: EffectDataPlatformValidationOptions;
153
- }): {
154
- api: TApi;
155
- layer: TLayer;
156
- rpc?: EffectRpcBffDefinition<TRpcs>;
157
- dataPlatform?: EffectDataPlatformValidationOptions;
158
- } & EffectBffRuntime<TApi, TLayer>;
159
- export declare function defineEffectRpcBff<TRpcs extends Rpc.Any = Rpc.Any, TLayer extends EffectRpcRuntimeLayer<TRpcs> = EffectRpcRuntimeLayer<TRpcs>>(definition: EffectRpcBffDefinition<TRpcs, TLayer>): EffectRpcBffDefinition<TRpcs, TLayer> & {
160
- createHandler: EffectRpcBffHandlerFactory<TRpcs>;
161
- };
162
- export declare function createHttpApiHandler<TApi extends HttpApi.AnyWithProps = HttpApi.AnyWithProps, TRpcs extends Rpc.Any = Rpc.Any>(options: {
163
- api: TApi;
164
- layer: EffectRuntimeLayer;
165
- openapi?: EffectBffOpenApiConfig;
166
- rpc?: EffectRpcBffDefinition<TRpcs>;
167
- dataPlatform?: EffectDataPlatformValidationOptions;
168
- }): {
169
- handler: (request: Request, context?: Parameters<(request: globalThis.Request, context: import("effect/Context").Context<any>) => Promise<Response>>[1]) => Promise<Response>;
170
- dispose: () => Promise<void>;
171
- };
2
+ export { type CreateEffectOperationContextOptions, createEffectOperationContext, type EffectContext, useEffectContext, useOperationContext, } from './context';
3
+ export * from './handler';
@@ -0,0 +1,48 @@
1
+ import type * as EffectServiceContext from 'effect/Context';
2
+ import { type EffectBffOpenApiConfig, type EffectDataPlatformValidationOptions, type EffectRequestValidator } from './handler';
3
+ import type { EffectContext } from './operation-context';
4
+ export type EffectBffRequestHandler = (request: Request, context?: EffectServiceContext.Context<any> | EffectContext) => Promise<Response> | Response;
5
+ export type EffectBffHandlerFactory = (options?: {
6
+ openapi?: EffectBffOpenApiConfig;
7
+ dataPlatform?: EffectDataPlatformValidationOptions;
8
+ validateRequest?: EffectRequestValidator;
9
+ }) => {
10
+ handler: EffectBffRequestHandler;
11
+ dispose: () => Promise<void>;
12
+ };
13
+ export type EffectApiModule = {
14
+ api?: unknown;
15
+ layer?: unknown;
16
+ handler?: EffectBffRequestHandler;
17
+ createHandler?: EffectBffHandlerFactory;
18
+ default?: unknown;
19
+ };
20
+ export type LoadedEffectBffHandler = {
21
+ handler: EffectBffRequestHandler;
22
+ dispose?: () => Promise<void>;
23
+ /**
24
+ * True when the handler is known to apply `validateRequest` internally
25
+ * (including per batched item): `{ api, layer }` modules resolved through
26
+ * `createHttpApiHandler`, and `createHandler` factories branded by
27
+ * `defineEffectBff` (see `EFFECT_VALIDATOR_AWARE_FACTORY`). Plain
28
+ * `handler` exports and unbranded custom factories leave this unset; the
29
+ * adapter middleware must enforce the policy itself for those.
30
+ */
31
+ appliesRequestValidator?: boolean;
32
+ };
33
+ export type ResolveEffectBffModuleHandlerOptions = {
34
+ openapi?: EffectBffOpenApiConfig;
35
+ dataPlatform?: EffectDataPlatformValidationOptions;
36
+ /**
37
+ * Server-seam request validator (cross-project policy). Applied by
38
+ * `createHttpApiHandler` to direct, batched and rpc requests.
39
+ *
40
+ * Note: plain `handler` exports and unbranded custom `createHandler`
41
+ * factories bypass this seam — the effect adapter enforces the policy in
42
+ * its middleware for those shapes instead (see
43
+ * `LoadedEffectBffHandler.appliesRequestValidator`).
44
+ */
45
+ validateRequest?: EffectRequestValidator;
46
+ onWarning?: (message: string) => void;
47
+ };
48
+ export declare function resolveEffectBffModuleHandler(mod: EffectApiModule, options?: ResolveEffectBffModuleHandlerOptions): Promise<LoadedEffectBffHandler | null>;
@@ -0,0 +1,10 @@
1
+ import { type OperationContext } from '@modern-js/create-request';
2
+ export type EffectContext = {
3
+ request: Request;
4
+ env: Record<string, unknown>;
5
+ path: string;
6
+ method: string;
7
+ operationContext: OperationContext;
8
+ };
9
+ export type CreateEffectOperationContextOptions = Omit<EffectContext, 'operationContext'>;
10
+ export declare const createEffectOperationContext: ({ request, path, method, }: CreateEffectOperationContextOptions) => OperationContext;
@@ -1,5 +1,7 @@
1
+ import { type RequestContextInput } from '@modern-js/create-request';
1
2
  import * as Effect from 'effect/Effect';
2
3
  import * as Layer from 'effect/Layer';
4
+ import { HttpClient } from 'effect/unstable/http';
3
5
  import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from 'effect/unstable/httpapi';
4
6
  import { Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization } from 'effect/unstable/rpc';
5
7
  export * as Effect from 'effect/Effect';
@@ -8,6 +10,9 @@ export * as Schema from 'effect/Schema';
8
10
  export { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization, };
9
11
  export type EffectHttpApiClientOptions = {
10
12
  baseUrl?: URL | string;
13
+ requestContext?: RequestContextInput;
14
+ transformClient?: (client: HttpClient.HttpClient) => HttpClient.HttpClient;
15
+ transformResponse?: ((effect: Effect.Effect<unknown, unknown, unknown>) => Effect.Effect<unknown, unknown, unknown>) | undefined;
11
16
  };
12
17
  type Nullish = null | undefined;
13
18
  type NonNullish<T> = Exclude<T, Nullish>;
@@ -49,6 +54,6 @@ export declare function mask<T, const Selection extends EffectViewSelection<T>>(
49
54
  export declare function runEffectView<T, const Selection extends EffectViewSelection<T>>(request: PromiseLike<T>, selection: Selection): Promise<EffectViewData<T, Selection>>;
50
55
  export declare function makeEffectHttpApiClient<ApiId extends string, Groups extends HttpApiGroup.Any>(api: HttpApi.HttpApi<ApiId, Groups>, options?: EffectHttpApiClientOptions): Effect.Effect<import("effect/Types").Simplify<{ readonly [Group in Extract<Groups, {
51
56
  readonly topLevel: false;
52
- }> as HttpApiGroup.Name<Group>]: HttpApiClient.Client.Group<Group, Group["identifier"], never, never>; } & { readonly [Method in HttpApiClient.Client.TopLevelMethods<Groups, never, never> as Method[0]]: Method[1]; }>, never, Exclude<import("effect/unstable/httpapi/HttpApiMiddleware").MiddlewareClient<HttpApiEndpoint.Middleware<HttpApiGroup.Endpoints<Groups>>>, import("effect/unstable/http/HttpClient").HttpClient>>;
57
+ }> as HttpApiGroup.Name<Group>]: HttpApiClient.Client.Group<Group, Group["identifier"], never, never>; } & { readonly [Method in HttpApiClient.Client.TopLevelMethods<Groups, never, never> as Method[0]]: Method[1]; }>, never, Exclude<import("effect/unstable/httpapi/HttpApiMiddleware").MiddlewareClient<HttpApiEndpoint.Middleware<HttpApiGroup.Endpoints<Groups>>>, HttpClient.HttpClient>>;
53
58
  export declare function makeEffectRpcClient<Rpcs extends Rpc.Any, const Flatten extends boolean = false>(group: RpcGroup.RpcGroup<Rpcs>, options: EffectRpcClientOptions<Rpcs, Flatten>): Effect.Effect<EffectRpcClientHandle<Rpcs, Flatten>, EffectRpcClientError, never>;
54
59
  export declare const runEffectRequest: <A, E>(effect: Effect.Effect<A, E>, options?: Effect.RunOptions | undefined) => Promise<A>;