@bleedingdev/modern-js-bff-effect 3.9.0-ultramodern.5 → 3.9.0-ultramodern.7
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.
- package/README.md +20 -4
- package/dist/cjs/effect/endpoint-contracts.js +1 -2
- package/dist/cjs/effect/entry-shape.js +2 -2
- package/dist/cjs/effect/handler/definition.js +1 -21
- package/dist/cjs/effect/module.js +4 -4
- package/dist/cjs/effect-client/cross-project.js +92 -0
- package/dist/cjs/effect-client/index.js +3 -1
- package/dist/esm/effect/endpoint-contracts.mjs +1 -2
- package/dist/esm/effect/entry-shape.mjs +2 -2
- package/dist/esm/effect/handler/definition.mjs +1 -21
- package/dist/esm/effect/module.mjs +4 -4
- package/dist/esm/effect-client/cross-project.mjs +54 -0
- package/dist/esm/effect-client/index.mjs +3 -1
- package/dist/esm-node/effect/endpoint-contracts.mjs +1 -2
- package/dist/esm-node/effect/entry-shape.mjs +2 -2
- package/dist/esm-node/effect/handler/definition.mjs +1 -21
- package/dist/esm-node/effect/module.mjs +4 -4
- package/dist/esm-node/effect-client/cross-project.mjs +55 -0
- package/dist/esm-node/effect-client/index.mjs +3 -1
- package/dist/types/effect/edge-dispatcher.d.ts +1 -1
- package/dist/types/effect/endpoint-contracts.d.ts +1 -19
- package/dist/types/effect/entry-shape.d.ts +2 -2
- package/dist/types/effect/handler/types.d.ts +0 -15
- package/dist/types/effect/handler.d.ts +1 -1
- package/dist/types/effect-client/cross-project.d.ts +10 -0
- package/dist/types/effect-client/index.d.ts +3 -0
- package/package.json +5 -18
- package/src/effect/edge-dispatcher.ts +0 -1
- package/src/effect/endpoint-contracts.ts +2 -23
- package/src/effect/entry-shape.ts +6 -4
- package/src/effect/handler/definition.ts +0 -44
- package/src/effect/handler/http.ts +2 -2
- package/src/effect/handler/types.ts +0 -27
- package/src/effect/handler.ts +0 -1
- package/src/effect/module.ts +4 -4
- package/src/effect-client/cross-project.ts +109 -0
- package/src/effect-client/index.ts +12 -2
- package/dist/cjs/effect-client/batch.js +0 -73
- package/dist/cjs/effect-client/envelope.js +0 -145
- package/dist/cjs/effect-client/guards.js +0 -44
- package/dist/cjs/effect-client/request-context.js +0 -64
- package/dist/cjs/effect-client/request.js +0 -66
- package/dist/cjs/effect-client/runtime.js +0 -100
- package/dist/cjs/effect-client/types.js +0 -18
- package/dist/esm/effect-client/batch.mjs +0 -35
- package/dist/esm/effect-client/envelope.mjs +0 -104
- package/dist/esm/effect-client/guards.mjs +0 -3
- package/dist/esm/effect-client/request-context.mjs +0 -23
- package/dist/esm/effect-client/request.mjs +0 -28
- package/dist/esm/effect-client/runtime.mjs +0 -62
- package/dist/esm/effect-client/types.mjs +0 -0
- package/dist/esm-node/effect-client/batch.mjs +0 -36
- package/dist/esm-node/effect-client/envelope.mjs +0 -105
- package/dist/esm-node/effect-client/guards.mjs +0 -4
- package/dist/esm-node/effect-client/request-context.mjs +0 -24
- package/dist/esm-node/effect-client/request.mjs +0 -29
- package/dist/esm-node/effect-client/runtime.mjs +0 -63
- package/dist/esm-node/effect-client/types.mjs +0 -1
- package/dist/types/effect-client/batch.d.ts +0 -2
- package/dist/types/effect-client/envelope.d.ts +0 -15
- package/dist/types/effect-client/guards.d.ts +0 -2
- package/dist/types/effect-client/request-context.d.ts +0 -3
- package/dist/types/effect-client/request.d.ts +0 -1
- package/dist/types/effect-client/runtime.d.ts +0 -5
- package/dist/types/effect-client/types.d.ts +0 -68
- package/src/effect-client/batch.ts +0 -50
- package/src/effect-client/envelope.ts +0 -238
- package/src/effect-client/guards.ts +0 -6
- package/src/effect-client/request-context.ts +0 -54
- package/src/effect-client/request.ts +0 -51
- package/src/effect-client/runtime.ts +0 -122
- package/src/effect-client/types.ts +0 -84
|
@@ -5,10 +5,10 @@ type EffectBffEntryModule = {
|
|
|
5
5
|
createHandler?: unknown;
|
|
6
6
|
default?: unknown;
|
|
7
7
|
};
|
|
8
|
-
type
|
|
8
|
+
type UnsupportedEffectBffEntryShape = '`handler` export' | 'default request handler';
|
|
9
9
|
export type EffectBffEntryShapeFacts = {
|
|
10
10
|
module: EffectBffEntryModule;
|
|
11
|
-
|
|
11
|
+
unsupportedShape?: UnsupportedEffectBffEntryShape;
|
|
12
12
|
createHandler?: unknown;
|
|
13
13
|
createHandlerValidatorAware: boolean;
|
|
14
14
|
api?: unknown;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { FileSystem, Path } from 'effect';
|
|
2
2
|
import * as Context from 'effect/Context';
|
|
3
|
-
import type * as EffectType from 'effect/Effect';
|
|
4
3
|
import type * as Layer from 'effect/Layer';
|
|
5
4
|
import type { Etag, HttpPlatform, HttpRouter } from 'effect/unstable/http';
|
|
6
5
|
import type { HttpApi, HttpApiClient, HttpApiGroup } from 'effect/unstable/httpapi';
|
|
@@ -23,13 +22,7 @@ export type EffectRpcBffDefinition<TRpcs extends Rpc.Any = Rpc.Any, TRpcLayer ex
|
|
|
23
22
|
disableFatalDefects?: boolean;
|
|
24
23
|
};
|
|
25
24
|
export type EffectRpcBffHandlerOptions = Pick<EffectRpcBffDefinition, 'path' | 'serialization' | 'disableTracing' | 'spanPrefix' | 'spanAttributes' | 'disableFatalDefects'>;
|
|
26
|
-
type EffectApiPromiseClient<TClient> = {
|
|
27
|
-
[GroupName in keyof TClient]: {
|
|
28
|
-
[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;
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
25
|
export type EffectApiClientFromApi<TApi extends HttpApi.Constraint = HttpApi.Top> = TApi extends HttpApi.HttpApi<infer _ApiId, infer Groups> ? HttpApiClient.Client<Extract<Groups, HttpApiGroup.Constraint>, never, never> : never;
|
|
32
|
-
export type EffectApiPromiseClientFromApi<TApi extends HttpApi.Constraint = HttpApi.Top> = EffectApiPromiseClient<EffectApiClientFromApi<TApi>>;
|
|
33
26
|
export type EffectBffDefinition<TApi extends HttpApi.Constraint = HttpApi.Top, TLayer extends EffectRuntimeLayer = EffectRuntimeLayer, TRpcs extends Rpc.Any = Rpc.Any> = {
|
|
34
27
|
api: TApi;
|
|
35
28
|
layer: TLayer;
|
|
@@ -63,12 +56,6 @@ export type EffectDataPlatformBatchOptions = {
|
|
|
63
56
|
* Defaults to `65536` (64KiB).
|
|
64
57
|
*/
|
|
65
58
|
maxBatchBytes?: number;
|
|
66
|
-
/**
|
|
67
|
-
* Client-side micro-batch flush window in milliseconds.
|
|
68
|
-
* Server runtime ignores this value and passes it through for codegen.
|
|
69
|
-
* Defaults to `8`.
|
|
70
|
-
*/
|
|
71
|
-
flushIntervalMs?: number;
|
|
72
59
|
/**
|
|
73
60
|
* Maximum per-batch internal request concurrency.
|
|
74
61
|
* Defaults to `4`.
|
|
@@ -144,10 +131,8 @@ export type EffectBffHandlerFactory<TApi extends HttpApi.Constraint = HttpApi.To
|
|
|
144
131
|
}) => ReturnType<typeof createHttpApiHandler>;
|
|
145
132
|
export type EffectBffRuntime<TApi extends HttpApi.Constraint = HttpApi.Top, TLayer extends EffectRuntimeLayer = EffectRuntimeLayer> = {
|
|
146
133
|
createHandler: EffectBffHandlerFactory<TApi, TLayer>;
|
|
147
|
-
client: EffectApiPromiseClientFromApi<TApi>;
|
|
148
134
|
};
|
|
149
135
|
export type EffectBffOpenApiConfig = boolean | {
|
|
150
136
|
path?: string;
|
|
151
137
|
};
|
|
152
138
|
export type EffectRpcBffHandlerFactory<TRpcs extends Rpc.Any = Rpc.Any> = (options?: Partial<EffectRpcBffHandlerOptions>) => ReturnType<typeof createRpcApiHandler<TRpcs>>;
|
|
153
|
-
export {};
|
|
@@ -10,4 +10,4 @@ export { HttpApiBuilder } from 'effect/unstable/httpapi';
|
|
|
10
10
|
export * from 'effect/unstable/rpc';
|
|
11
11
|
export { defineEffectBff, defineEffectRpcBff } from './handler/definition.js';
|
|
12
12
|
export { createHttpApiHandler } from './handler/http.js';
|
|
13
|
-
export type { EffectApiClientFromApi,
|
|
13
|
+
export type { EffectApiClientFromApi, EffectBffDefinition, EffectBffHandlerFactory, EffectBffOpenApiConfig, EffectBffRuntime, EffectDataPlatformBatchOptions, EffectDataPlatformSelectionValidationOptions, EffectDataPlatformValidationOptions, EffectRequestValidator, EffectRpcBffDefinition, EffectRpcBffHandlerFactory, EffectRpcBffHandlerOptions, EffectRpcRuntimeLayer, EffectRpcSerialization, EffectRuntimeLayer, EffectRuntimeRequirements, } from './handler/types.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { HttpClient } from 'effect/unstable/http';
|
|
2
|
+
import { HttpApi, type HttpApiGroup } from 'effect/unstable/httpapi';
|
|
3
|
+
export interface EffectCrossProjectClientOptions {
|
|
4
|
+
requestId: string;
|
|
5
|
+
operationVersion: number;
|
|
6
|
+
/** Server mount path, independent of the API origin. */
|
|
7
|
+
prefix: string;
|
|
8
|
+
}
|
|
9
|
+
/** Native transport middleware; endpoint encoding and inference stay in Effect. */
|
|
10
|
+
export declare function withCrossProjectPolicy<Id extends string, Groups extends HttpApiGroup.Constraint>(api: HttpApi.HttpApi<Id, Groups>, options: EffectCrossProjectClientOptions, client: HttpClient.HttpClient): HttpClient.HttpClient;
|
|
@@ -4,12 +4,15 @@ import * as Layer from 'effect/Layer';
|
|
|
4
4
|
import { HttpClient } from 'effect/unstable/http';
|
|
5
5
|
import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from 'effect/unstable/httpapi';
|
|
6
6
|
import { Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization } from 'effect/unstable/rpc';
|
|
7
|
+
import { type EffectCrossProjectClientOptions } from './cross-project.js';
|
|
7
8
|
export * as Effect from 'effect/Effect';
|
|
8
9
|
export * as Layer from 'effect/Layer';
|
|
9
10
|
export * as Schema from 'effect/Schema';
|
|
10
11
|
export * as HttpClientError from 'effect/unstable/http/HttpClientError';
|
|
12
|
+
export type { EffectCrossProjectClientOptions } from './cross-project.js';
|
|
11
13
|
export { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization, };
|
|
12
14
|
export type EffectHttpApiClientOptions = {
|
|
15
|
+
crossProject?: EffectCrossProjectClientOptions;
|
|
13
16
|
baseUrl?: URL | string;
|
|
14
17
|
requestContext?: RequestContextInput;
|
|
15
18
|
transformClient?: (client: HttpClient.HttpClient) => HttpClient.HttpClient;
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"modern.js",
|
|
19
19
|
"ultramodern.js"
|
|
20
20
|
],
|
|
21
|
-
"version": "3.9.0-ultramodern.
|
|
21
|
+
"version": "3.9.0-ultramodern.7",
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|
|
24
24
|
"src"
|
|
@@ -76,15 +76,6 @@
|
|
|
76
76
|
"import": "./dist/esm/microvertical-api.mjs",
|
|
77
77
|
"default": "./dist/esm/microvertical-api.mjs"
|
|
78
78
|
},
|
|
79
|
-
"./effect-client-runtime": {
|
|
80
|
-
"types": "./dist/types/effect-client/runtime.d.ts",
|
|
81
|
-
"node": {
|
|
82
|
-
"import": "./dist/esm-node/effect-client/runtime.mjs",
|
|
83
|
-
"require": "./dist/cjs/effect-client/runtime.js"
|
|
84
|
-
},
|
|
85
|
-
"import": "./dist/esm/effect-client/runtime.mjs",
|
|
86
|
-
"default": "./dist/esm/effect-client/runtime.mjs"
|
|
87
|
-
},
|
|
88
79
|
"./data-platform": {
|
|
89
80
|
"types": "./dist/types/data-platform/index.d.ts",
|
|
90
81
|
"node": {
|
|
@@ -125,10 +116,6 @@
|
|
|
125
116
|
"./dist/types/microvertical-api.d.ts",
|
|
126
117
|
"./src/microvertical-api.ts"
|
|
127
118
|
],
|
|
128
|
-
"effect-client-runtime": [
|
|
129
|
-
"./dist/types/effect-client/runtime.d.ts",
|
|
130
|
-
"./src/effect-client/runtime.ts"
|
|
131
|
-
],
|
|
132
119
|
"data-platform": [
|
|
133
120
|
"./dist/types/data-platform/index.d.ts",
|
|
134
121
|
"./src/data-platform/index.ts"
|
|
@@ -143,10 +130,10 @@
|
|
|
143
130
|
"node": ">=26.7.0"
|
|
144
131
|
},
|
|
145
132
|
"dependencies": {
|
|
146
|
-
"@modern-js/bff-core": "npm:@bleedingdev/modern-js-bff-core@3.9.0-ultramodern.
|
|
147
|
-
"@modern-js/create-request": "npm:@bleedingdev/modern-js-create-request@3.9.0-ultramodern.
|
|
148
|
-
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.
|
|
149
|
-
"@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.
|
|
133
|
+
"@modern-js/bff-core": "npm:@bleedingdev/modern-js-bff-core@3.9.0-ultramodern.7",
|
|
134
|
+
"@modern-js/create-request": "npm:@bleedingdev/modern-js-create-request@3.9.0-ultramodern.7",
|
|
135
|
+
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.7",
|
|
136
|
+
"@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.7",
|
|
150
137
|
"@opentelemetry/api": "1.9.1",
|
|
151
138
|
"@opentelemetry/api-logs": "0.222.0",
|
|
152
139
|
"@opentelemetry/resources": "2.11.0",
|
|
@@ -1,17 +1,5 @@
|
|
|
1
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
|
-
*/
|
|
2
|
+
/** Shared endpoint normalization and identity hashing for server policy metadata. */
|
|
15
3
|
import {
|
|
16
4
|
createOperationContractHash,
|
|
17
5
|
type OperationContractSource,
|
|
@@ -38,13 +26,7 @@ type HttpApiGroupLike = {
|
|
|
38
26
|
};
|
|
39
27
|
|
|
40
28
|
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
29
|
identifier?: unknown;
|
|
47
|
-
name?: unknown;
|
|
48
30
|
method?: unknown;
|
|
49
31
|
path?: unknown;
|
|
50
32
|
};
|
|
@@ -53,9 +35,6 @@ function resolveEffectEndpointName(endpoint: HttpApiEndpointLike): string {
|
|
|
53
35
|
if (typeof endpoint.identifier === 'string' && endpoint.identifier) {
|
|
54
36
|
return endpoint.identifier;
|
|
55
37
|
}
|
|
56
|
-
if (typeof endpoint.name === 'string' && endpoint.name) {
|
|
57
|
-
return endpoint.name;
|
|
58
|
-
}
|
|
59
38
|
return '';
|
|
60
39
|
}
|
|
61
40
|
|
|
@@ -168,7 +147,7 @@ export async function extractHttpApiFromModule(
|
|
|
168
147
|
isValidatorAwareHandlerFactory,
|
|
169
148
|
isHttpApi,
|
|
170
149
|
});
|
|
171
|
-
return facts?.
|
|
150
|
+
return facts?.unsupportedShape ||
|
|
172
151
|
facts?.api === undefined ||
|
|
173
152
|
!facts.hasRuntimeLayer ||
|
|
174
153
|
(facts.createHandler !== undefined && !facts.createHandlerValidatorAware)
|
|
@@ -58,11 +58,13 @@ type EffectBffEntryModule = {
|
|
|
58
58
|
default?: unknown;
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
type
|
|
61
|
+
type UnsupportedEffectBffEntryShape =
|
|
62
|
+
| '`handler` export'
|
|
63
|
+
| 'default request handler';
|
|
62
64
|
|
|
63
65
|
export type EffectBffEntryShapeFacts = {
|
|
64
66
|
module: EffectBffEntryModule;
|
|
65
|
-
|
|
67
|
+
unsupportedShape?: UnsupportedEffectBffEntryShape;
|
|
66
68
|
createHandler?: unknown;
|
|
67
69
|
createHandlerValidatorAware: boolean;
|
|
68
70
|
api?: unknown;
|
|
@@ -135,7 +137,7 @@ export function classifyEffectBffEntryModule(
|
|
|
135
137
|
function createEntryShapeFacts(
|
|
136
138
|
module: EffectBffEntryModule,
|
|
137
139
|
predicates: EffectBffEntryShapePredicates,
|
|
138
|
-
|
|
140
|
+
unsupportedShape?: UnsupportedEffectBffEntryShape,
|
|
139
141
|
): EffectBffEntryShapeFacts {
|
|
140
142
|
const createHandler =
|
|
141
143
|
typeof module.createHandler === 'function'
|
|
@@ -146,7 +148,7 @@ function createEntryShapeFacts(
|
|
|
146
148
|
|
|
147
149
|
return {
|
|
148
150
|
module,
|
|
149
|
-
|
|
151
|
+
unsupportedShape,
|
|
150
152
|
createHandler,
|
|
151
153
|
createHandlerValidatorAware:
|
|
152
154
|
createHandler !== undefined &&
|
|
@@ -7,7 +7,6 @@ import { mergeDataPlatformOptions } from './envelope';
|
|
|
7
7
|
import { createHttpApiHandler } from './http';
|
|
8
8
|
import { createRpcApiHandler } from './rpc';
|
|
9
9
|
import type {
|
|
10
|
-
EffectApiPromiseClientFromApi,
|
|
11
10
|
EffectBffDefinition,
|
|
12
11
|
EffectBffHandlerFactory,
|
|
13
12
|
EffectBffRuntime,
|
|
@@ -51,55 +50,12 @@ export function defineEffectBff<
|
|
|
51
50
|
validateRequest: options?.validateRequest,
|
|
52
51
|
});
|
|
53
52
|
});
|
|
54
|
-
const client = createLoaderMaterializedClientPlaceholder<TApi>();
|
|
55
53
|
return {
|
|
56
54
|
...definition,
|
|
57
55
|
createHandler,
|
|
58
|
-
client,
|
|
59
56
|
};
|
|
60
57
|
}
|
|
61
58
|
|
|
62
|
-
const LOADER_CLIENT_IGNORED_KEYS = new Set<PropertyKey>([
|
|
63
|
-
'then',
|
|
64
|
-
'catch',
|
|
65
|
-
'finally',
|
|
66
|
-
'toJSON',
|
|
67
|
-
'$$typeof',
|
|
68
|
-
]);
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* `defineEffectBff(...).client` is only materialized when the entry module
|
|
72
|
-
* is imported through the `@api/index` transform (the webpack/rspack
|
|
73
|
-
* loader swaps the module for generated client code). Importing the entry
|
|
74
|
-
* directly (server code, scripts, tests) used to return a `client` typed as
|
|
75
|
-
* fully functional but `undefined` at runtime — a bare TypeError with zero
|
|
76
|
-
* explanation. This placeholder keeps the type contract while failing with
|
|
77
|
-
* an actionable error on first property access.
|
|
78
|
-
*/
|
|
79
|
-
function createLoaderMaterializedClientPlaceholder<
|
|
80
|
-
TApi extends HttpApi.Constraint,
|
|
81
|
-
>(): EffectApiPromiseClientFromApi<TApi> {
|
|
82
|
-
const explain = (property: PropertyKey): never => {
|
|
83
|
-
throw new Error(
|
|
84
|
-
`[BFF][Effect] client.${String(property)} is not available here: the typed client only exists when the API entry is imported through the "@api/index" transformed path (the BFF loader replaces it with generated client code). On the server, use HttpApiClient or call the Effect layer directly.`,
|
|
85
|
-
);
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
return new Proxy(Object.create(null), {
|
|
89
|
-
get(_target, property) {
|
|
90
|
-
// Keep async/inspection protocols (await, console.log, React internals)
|
|
91
|
-
// from throwing so the placeholder stays inert until really used.
|
|
92
|
-
if (
|
|
93
|
-
typeof property === 'symbol' ||
|
|
94
|
-
LOADER_CLIENT_IGNORED_KEYS.has(property)
|
|
95
|
-
) {
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
return explain(property);
|
|
99
|
-
},
|
|
100
|
-
}) as EffectApiPromiseClientFromApi<TApi>;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
59
|
export function defineEffectRpcBff<
|
|
104
60
|
TRpcs extends Rpc.Any = Rpc.Any,
|
|
105
61
|
TLayer extends EffectRpcRuntimeLayer<TRpcs> = EffectRpcRuntimeLayer<TRpcs>,
|
|
@@ -66,8 +66,8 @@ export function createHttpApiHandler<
|
|
|
66
66
|
) => {
|
|
67
67
|
// Policy seam first: every HttpApi request, direct or batched item,
|
|
68
68
|
// passes through here, so batch fan-out cannot bypass validator. The
|
|
69
|
-
// interceptor receives the original request
|
|
70
|
-
//
|
|
69
|
+
// interceptor receives the original request; delegated requests still
|
|
70
|
+
// pass through body validation.
|
|
71
71
|
const policyDenial = options.validateRequest?.(request);
|
|
72
72
|
if (policyDenial) {
|
|
73
73
|
return policyDenial;
|
|
@@ -77,22 +77,6 @@ export type EffectRpcBffHandlerOptions = Pick<
|
|
|
77
77
|
| 'disableFatalDefects'
|
|
78
78
|
>;
|
|
79
79
|
|
|
80
|
-
type EffectApiPromiseClient<TClient> = {
|
|
81
|
-
[GroupName in keyof TClient]: {
|
|
82
|
-
[EndpointName in keyof TClient[GroupName]]: TClient[GroupName][EndpointName] extends (
|
|
83
|
-
...args: infer TArgs
|
|
84
|
-
) => infer TResult
|
|
85
|
-
? TResult extends EffectType.Effect<unknown, unknown, unknown>
|
|
86
|
-
? (
|
|
87
|
-
...args: TArgs
|
|
88
|
-
) => Promise<
|
|
89
|
-
Exclude<EffectType.Success<TResult>, readonly [unknown, unknown]>
|
|
90
|
-
>
|
|
91
|
-
: never
|
|
92
|
-
: never;
|
|
93
|
-
};
|
|
94
|
-
};
|
|
95
|
-
|
|
96
80
|
export type EffectApiClientFromApi<
|
|
97
81
|
TApi extends HttpApi.Constraint = HttpApi.Top,
|
|
98
82
|
> =
|
|
@@ -104,10 +88,6 @@ export type EffectApiClientFromApi<
|
|
|
104
88
|
>
|
|
105
89
|
: never;
|
|
106
90
|
|
|
107
|
-
export type EffectApiPromiseClientFromApi<
|
|
108
|
-
TApi extends HttpApi.Constraint = HttpApi.Top,
|
|
109
|
-
> = EffectApiPromiseClient<EffectApiClientFromApi<TApi>>;
|
|
110
|
-
|
|
111
91
|
export type EffectBffDefinition<
|
|
112
92
|
TApi extends HttpApi.Constraint = HttpApi.Top,
|
|
113
93
|
TLayer extends EffectRuntimeLayer = EffectRuntimeLayer,
|
|
@@ -147,12 +127,6 @@ export type EffectDataPlatformBatchOptions = {
|
|
|
147
127
|
* Defaults to `65536` (64KiB).
|
|
148
128
|
*/
|
|
149
129
|
maxBatchBytes?: number;
|
|
150
|
-
/**
|
|
151
|
-
* Client-side micro-batch flush window in milliseconds.
|
|
152
|
-
* Server runtime ignores this value and passes it through for codegen.
|
|
153
|
-
* Defaults to `8`.
|
|
154
|
-
*/
|
|
155
|
-
flushIntervalMs?: number;
|
|
156
130
|
/**
|
|
157
131
|
* Maximum per-batch internal request concurrency.
|
|
158
132
|
* Defaults to `4`.
|
|
@@ -241,7 +215,6 @@ export type EffectBffRuntime<
|
|
|
241
215
|
TLayer extends EffectRuntimeLayer = EffectRuntimeLayer,
|
|
242
216
|
> = {
|
|
243
217
|
createHandler: EffectBffHandlerFactory<TApi, TLayer>;
|
|
244
|
-
client: EffectApiPromiseClientFromApi<TApi>;
|
|
245
218
|
};
|
|
246
219
|
|
|
247
220
|
export type EffectBffOpenApiConfig =
|
package/src/effect/handler.ts
CHANGED
|
@@ -14,7 +14,6 @@ export { defineEffectBff, defineEffectRpcBff } from './handler/definition';
|
|
|
14
14
|
export { createHttpApiHandler } from './handler/http';
|
|
15
15
|
export type {
|
|
16
16
|
EffectApiClientFromApi,
|
|
17
|
-
EffectApiPromiseClientFromApi,
|
|
18
17
|
EffectBffDefinition,
|
|
19
18
|
EffectBffHandlerFactory,
|
|
20
19
|
EffectBffOpenApiConfig,
|
package/src/effect/module.ts
CHANGED
|
@@ -67,7 +67,7 @@ function shareDispose(dispose: () => Promise<void>) {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function
|
|
70
|
+
function rejectUnsupportedEffectModuleShape(
|
|
71
71
|
options: ResolveEffectBffModuleHandlerOptions,
|
|
72
72
|
shape: string,
|
|
73
73
|
) {
|
|
@@ -119,8 +119,8 @@ function resolveClassifiedEffectBffModuleHandler(
|
|
|
119
119
|
return null;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
if (facts.
|
|
123
|
-
if (
|
|
122
|
+
if (facts.unsupportedShape) {
|
|
123
|
+
if (rejectUnsupportedEffectModuleShape(options, facts.unsupportedShape)) {
|
|
124
124
|
return null;
|
|
125
125
|
}
|
|
126
126
|
}
|
|
@@ -137,7 +137,7 @@ function resolveClassifiedEffectBffModuleHandler(
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
if (facts.api === undefined || !facts.hasRuntimeLayer) {
|
|
140
|
-
|
|
140
|
+
rejectUnsupportedEffectModuleShape(
|
|
141
141
|
options,
|
|
142
142
|
'unbranded `createHandler` export',
|
|
143
143
|
);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BFF_ENVELOPE_HEADER,
|
|
3
|
+
BFF_OPERATION_CONTEXT_DETAIL_HEADER,
|
|
4
|
+
BFF_OPERATION_CONTEXT_HEADER,
|
|
5
|
+
resolveCrossProjectRequestObservation,
|
|
6
|
+
} from '@modern-js/server-runtime-extensions/bff-policy';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import * as Schema from 'effect/Schema';
|
|
9
|
+
|
|
10
|
+
const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));
|
|
11
|
+
|
|
12
|
+
import { HttpClient, HttpClientRequest } from 'effect/unstable/http';
|
|
13
|
+
import { HttpApi, type HttpApiGroup } from 'effect/unstable/httpapi';
|
|
14
|
+
|
|
15
|
+
export interface EffectCrossProjectClientOptions {
|
|
16
|
+
requestId: string;
|
|
17
|
+
operationVersion: number;
|
|
18
|
+
/** Server mount path, independent of the API origin. */
|
|
19
|
+
prefix: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Native transport middleware; endpoint encoding and inference stay in Effect. */
|
|
23
|
+
export function withCrossProjectPolicy<
|
|
24
|
+
Id extends string,
|
|
25
|
+
Groups extends HttpApiGroup.Constraint,
|
|
26
|
+
>(
|
|
27
|
+
api: HttpApi.HttpApi<Id, Groups>,
|
|
28
|
+
options: EffectCrossProjectClientOptions,
|
|
29
|
+
client: HttpClient.HttpClient,
|
|
30
|
+
): HttpClient.HttpClient {
|
|
31
|
+
const contracts: Record<
|
|
32
|
+
string,
|
|
33
|
+
{ operationId: string; name: string; method: string; routePath: string }
|
|
34
|
+
> = {};
|
|
35
|
+
const prefix = options.prefix.replace(/\/$/, '');
|
|
36
|
+
HttpApi.reflect(api, {
|
|
37
|
+
onGroup: () => {},
|
|
38
|
+
onEndpoint: ({ endpoint }) => {
|
|
39
|
+
const routePath =
|
|
40
|
+
`${prefix}${endpoint.path === '/' ? '' : endpoint.path}` || '/';
|
|
41
|
+
const name = endpoint.identifier;
|
|
42
|
+
contracts[`${endpoint.method}:${routePath}`] = {
|
|
43
|
+
operationId: `${options.requestId}:${name}`,
|
|
44
|
+
name,
|
|
45
|
+
method: endpoint.method,
|
|
46
|
+
routePath,
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
return client.pipe(
|
|
51
|
+
HttpClient.mapRequestEffect(request => {
|
|
52
|
+
const observed = resolveCrossProjectRequestObservation(
|
|
53
|
+
{
|
|
54
|
+
method: request.method,
|
|
55
|
+
pathname: new URL(request.url, 'http://effect.invalid').pathname,
|
|
56
|
+
},
|
|
57
|
+
{ expectedOperationContracts: contracts },
|
|
58
|
+
);
|
|
59
|
+
const contract =
|
|
60
|
+
observed && contracts[`${observed.method}:${observed.routePath}`];
|
|
61
|
+
if (contract === undefined) {
|
|
62
|
+
return Effect.die(
|
|
63
|
+
new Error(
|
|
64
|
+
'[BFF][Effect] Request has no matching shared operation contract.',
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return Effect.gen(function* () {
|
|
69
|
+
// Canonical field order matches the server's route-identity SHA-256.
|
|
70
|
+
const input = encodeJson({
|
|
71
|
+
httpMethod: contract.method,
|
|
72
|
+
name: contract.name,
|
|
73
|
+
requestId: options.requestId,
|
|
74
|
+
routePath: contract.routePath,
|
|
75
|
+
});
|
|
76
|
+
const digest = yield* Effect.promise(() =>
|
|
77
|
+
globalThis.crypto.subtle.digest(
|
|
78
|
+
'SHA-256',
|
|
79
|
+
new TextEncoder().encode(input),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
const schemaHash = Array.from(new Uint8Array(digest), value =>
|
|
83
|
+
value.toString(16).padStart(2, '0'),
|
|
84
|
+
).join('');
|
|
85
|
+
return request.pipe(
|
|
86
|
+
HttpClientRequest.setHeader(
|
|
87
|
+
BFF_ENVELOPE_HEADER,
|
|
88
|
+
encodeJson({ requestId: options.requestId }),
|
|
89
|
+
),
|
|
90
|
+
HttpClientRequest.setHeader(
|
|
91
|
+
BFF_OPERATION_CONTEXT_HEADER,
|
|
92
|
+
contract.operationId,
|
|
93
|
+
),
|
|
94
|
+
HttpClientRequest.setHeader(
|
|
95
|
+
BFF_OPERATION_CONTEXT_DETAIL_HEADER,
|
|
96
|
+
encodeJson({
|
|
97
|
+
requestId: options.requestId,
|
|
98
|
+
operationId: contract.operationId,
|
|
99
|
+
method: contract.method,
|
|
100
|
+
routePath: contract.routePath,
|
|
101
|
+
schemaHash,
|
|
102
|
+
operationVersion: options.operationVersion,
|
|
103
|
+
}),
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -30,11 +30,16 @@ import {
|
|
|
30
30
|
} from 'effect/unstable/rpc';
|
|
31
31
|
|
|
32
32
|
import { getRpcSerializationLayer } from '../effect/rpcSerialization';
|
|
33
|
+
import {
|
|
34
|
+
type EffectCrossProjectClientOptions,
|
|
35
|
+
withCrossProjectPolicy,
|
|
36
|
+
} from './cross-project';
|
|
33
37
|
|
|
34
38
|
export * as Effect from 'effect/Effect';
|
|
35
39
|
export * as Layer from 'effect/Layer';
|
|
36
40
|
export * as Schema from 'effect/Schema';
|
|
37
41
|
export * as HttpClientError from 'effect/unstable/http/HttpClientError';
|
|
42
|
+
export type { EffectCrossProjectClientOptions } from './cross-project';
|
|
38
43
|
export {
|
|
39
44
|
HttpApi,
|
|
40
45
|
HttpApiClient,
|
|
@@ -49,6 +54,7 @@ export {
|
|
|
49
54
|
};
|
|
50
55
|
|
|
51
56
|
export type EffectHttpApiClientOptions = {
|
|
57
|
+
crossProject?: EffectCrossProjectClientOptions;
|
|
52
58
|
baseUrl?: URL | string;
|
|
53
59
|
requestContext?: RequestContextInput;
|
|
54
60
|
transformClient?: (client: HttpClient.HttpClient) => HttpClient.HttpClient;
|
|
@@ -233,9 +239,13 @@ export function makeEffectHttpApiClient<
|
|
|
233
239
|
}),
|
|
234
240
|
);
|
|
235
241
|
|
|
242
|
+
const policyClient =
|
|
243
|
+
options?.crossProject !== undefined
|
|
244
|
+
? withCrossProjectPolicy(api, options.crossProject, contextClient)
|
|
245
|
+
: contextClient;
|
|
236
246
|
return typeof options?.transformClient === 'function'
|
|
237
|
-
? options.transformClient(
|
|
238
|
-
:
|
|
247
|
+
? options.transformClient(policyClient)
|
|
248
|
+
: policyClient;
|
|
239
249
|
};
|
|
240
250
|
|
|
241
251
|
return HttpApiClient.make(api, {
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __webpack_require__ = {};
|
|
3
|
-
(()=>{
|
|
4
|
-
__webpack_require__.d = (exports1, getters, values)=>{
|
|
5
|
-
var define = (defs, kind)=>{
|
|
6
|
-
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
[kind]: defs[key]
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
define(getters, "get");
|
|
12
|
-
define(values, "value");
|
|
13
|
-
};
|
|
14
|
-
})();
|
|
15
|
-
(()=>{
|
|
16
|
-
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
17
|
-
})();
|
|
18
|
-
(()=>{
|
|
19
|
-
__webpack_require__.r = (exports1)=>{
|
|
20
|
-
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
21
|
-
value: 'Module'
|
|
22
|
-
});
|
|
23
|
-
Object.defineProperty(exports1, '__esModule', {
|
|
24
|
-
value: true
|
|
25
|
-
});
|
|
26
|
-
};
|
|
27
|
-
})();
|
|
28
|
-
var __webpack_exports__ = {};
|
|
29
|
-
__webpack_require__.r(__webpack_exports__);
|
|
30
|
-
__webpack_require__.d(__webpack_exports__, {
|
|
31
|
-
configureGeneratedEffectClientRuntime: ()=>configureGeneratedEffectClientRuntime
|
|
32
|
-
});
|
|
33
|
-
const index_js_namespaceObject = require("../data-platform/index.js");
|
|
34
|
-
const external_envelope_js_namespaceObject = require("./envelope.js");
|
|
35
|
-
const resolveRuntimeFetch = ()=>'function' == typeof fetch ? fetch.bind(globalThis) : void 0;
|
|
36
|
-
const configureGeneratedEffectClientRuntime = (config, configureRequest)=>{
|
|
37
|
-
const defaultOrigin = config.defaultOrigin;
|
|
38
|
-
if (config.requestId && configureRequest) {
|
|
39
|
-
const configurePayload = {
|
|
40
|
-
requestId: config.requestId,
|
|
41
|
-
requireEnvelope: true,
|
|
42
|
-
identityBinding: {
|
|
43
|
-
enabled: true,
|
|
44
|
-
strict: true
|
|
45
|
-
},
|
|
46
|
-
operationContract: {
|
|
47
|
-
enabled: true,
|
|
48
|
-
strict: true,
|
|
49
|
-
requireSchemaHash: true,
|
|
50
|
-
requireOperationVersion: true
|
|
51
|
-
},
|
|
52
|
-
setDomain: ()=>(0, external_envelope_js_namespaceObject.resolveOrigin)(defaultOrigin)
|
|
53
|
-
};
|
|
54
|
-
const runtimeFetch = resolveRuntimeFetch();
|
|
55
|
-
if (false !== config.batch.enabled && runtimeFetch) configurePayload.request = (0, index_js_namespaceObject.createDataBatchTransport)({
|
|
56
|
-
fetch: runtimeFetch,
|
|
57
|
-
endpoint: config.batch.endpoint,
|
|
58
|
-
flushIntervalMs: config.batch.flushIntervalMs,
|
|
59
|
-
maxBatchSize: config.batch.maxBatchSize,
|
|
60
|
-
maxBatchBytes: config.batch.maxBatchBytes,
|
|
61
|
-
requestTimeoutMs: config.batch.requestTimeoutMs,
|
|
62
|
-
allowedMethods: config.batch.allowedMethods
|
|
63
|
-
});
|
|
64
|
-
configureRequest(configurePayload);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
exports.configureGeneratedEffectClientRuntime = __webpack_exports__.configureGeneratedEffectClientRuntime;
|
|
68
|
-
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
69
|
-
"configureGeneratedEffectClientRuntime"
|
|
70
|
-
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
71
|
-
Object.defineProperty(exports, '__esModule', {
|
|
72
|
-
value: true
|
|
73
|
-
});
|