@bleedingdev/modern-js-bff-effect 3.9.0-ultramodern.4 → 3.9.0-ultramodern.6
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-client/guards.js → assembly.js} +15 -9
- package/dist/cjs/data-platform/trace.js +2 -2
- package/dist/cjs/effect/edge-dispatcher.js +7 -7
- package/dist/cjs/effect/edge.js +15 -2
- package/dist/cjs/effect/endpoint-contracts.js +3 -4
- package/dist/cjs/effect/entry-shape.js +2 -2
- package/dist/cjs/effect/generated-entry.js +57 -0
- package/dist/cjs/effect/handler/batch-handler.js +5 -4
- package/dist/cjs/effect/handler/batch.js +8 -1
- package/dist/cjs/effect/handler/definition.js +1 -21
- package/dist/cjs/effect/module.js +4 -4
- package/dist/cjs/effect/operation-context.js +7 -7
- package/dist/cjs/effect-client/cross-project.js +92 -0
- package/dist/cjs/effect-client/index.js +5 -3
- package/dist/cjs/microvertical-api.js +120 -0
- package/dist/esm/assembly.mjs +12 -0
- package/dist/esm/data-platform/trace.mjs +1 -1
- package/dist/esm/effect/edge-dispatcher.mjs +5 -5
- package/dist/esm/effect/edge.mjs +1 -0
- package/dist/esm/effect/endpoint-contracts.mjs +2 -3
- package/dist/esm/effect/entry-shape.mjs +2 -2
- package/dist/esm/effect/generated-entry.mjs +19 -0
- package/dist/esm/effect/handler/batch-handler.mjs +5 -4
- package/dist/esm/effect/handler/batch.mjs +8 -1
- package/dist/esm/effect/handler/definition.mjs +1 -21
- package/dist/esm/effect/module.mjs +4 -4
- package/dist/esm/effect/operation-context.mjs +1 -1
- package/dist/esm/effect-client/cross-project.mjs +54 -0
- package/dist/esm/effect-client/index.mjs +4 -2
- package/dist/esm/microvertical-api.mjs +67 -0
- package/dist/esm-node/assembly.mjs +13 -0
- package/dist/esm-node/data-platform/trace.mjs +1 -1
- package/dist/esm-node/effect/edge-dispatcher.mjs +5 -5
- package/dist/esm-node/effect/edge.mjs +1 -0
- package/dist/esm-node/effect/endpoint-contracts.mjs +2 -3
- package/dist/esm-node/effect/entry-shape.mjs +2 -2
- package/dist/esm-node/effect/generated-entry.mjs +20 -0
- package/dist/esm-node/effect/handler/batch-handler.mjs +5 -4
- package/dist/esm-node/effect/handler/batch.mjs +8 -1
- package/dist/esm-node/effect/handler/definition.mjs +1 -21
- package/dist/esm-node/effect/module.mjs +4 -4
- package/dist/esm-node/effect/operation-context.mjs +1 -1
- package/dist/esm-node/effect-client/cross-project.mjs +55 -0
- package/dist/esm-node/effect-client/index.mjs +4 -2
- package/dist/esm-node/microvertical-api.mjs +68 -0
- package/dist/types/assembly.d.ts +10 -0
- package/dist/types/effect/context.d.ts +1 -1
- package/dist/types/effect/edge-context.d.ts +1 -1
- package/dist/types/effect/edge-dispatcher.d.ts +2 -2
- package/dist/types/effect/edge.d.ts +1 -0
- package/dist/types/effect/endpoint-contracts.d.ts +2 -20
- package/dist/types/effect/entry-shape.d.ts +2 -2
- package/dist/types/effect/generated-entry.d.ts +7 -0
- package/dist/types/effect/handler/batch.d.ts +7 -1
- package/dist/types/effect/handler/types.d.ts +0 -15
- package/dist/types/effect/handler.d.ts +1 -1
- package/dist/types/effect/operation-context.d.ts +1 -1
- package/dist/types/effect-client/cross-project.d.ts +10 -0
- package/dist/types/effect-client/index.d.ts +4 -1
- package/dist/types/microvertical-api.d.ts +65 -0
- package/package.json +26 -13
- package/src/assembly.ts +43 -0
- package/src/data-platform/batch/queue.ts +2 -3
- package/src/data-platform/trace.ts +1 -1
- package/src/effect/edge-dispatcher.ts +7 -5
- package/src/effect/edge.ts +1 -0
- package/src/effect/endpoint-contracts.ts +3 -24
- package/src/effect/entry-shape.ts +10 -5
- package/src/effect/generated-entry.ts +36 -0
- package/src/effect/handler/batch-handler.ts +21 -16
- package/src/effect/handler/batch.ts +9 -1
- 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/operation-context.ts +1 -1
- package/src/effect-client/cross-project.ts +109 -0
- package/src/effect-client/index.ts +13 -3
- package/src/microvertical-api.ts +102 -0
- package/dist/cjs/effect-client/batch.js +0 -73
- package/dist/cjs/effect-client/envelope.js +0 -145
- 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
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.6",
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|
|
24
24
|
"src"
|
|
@@ -67,14 +67,14 @@
|
|
|
67
67
|
"import": "./dist/esm/effect-client/index.mjs",
|
|
68
68
|
"default": "./dist/esm/effect-client/index.mjs"
|
|
69
69
|
},
|
|
70
|
-
"./
|
|
71
|
-
"types": "./dist/types/
|
|
70
|
+
"./microvertical-api": {
|
|
71
|
+
"types": "./dist/types/microvertical-api.d.ts",
|
|
72
72
|
"node": {
|
|
73
|
-
"import": "./dist/esm-node/
|
|
74
|
-
"require": "./dist/cjs/
|
|
73
|
+
"import": "./dist/esm-node/microvertical-api.mjs",
|
|
74
|
+
"require": "./dist/cjs/microvertical-api.js"
|
|
75
75
|
},
|
|
76
|
-
"import": "./dist/esm/
|
|
77
|
-
"default": "./dist/esm/
|
|
76
|
+
"import": "./dist/esm/microvertical-api.mjs",
|
|
77
|
+
"default": "./dist/esm/microvertical-api.mjs"
|
|
78
78
|
},
|
|
79
79
|
"./data-platform": {
|
|
80
80
|
"types": "./dist/types/data-platform/index.d.ts",
|
|
@@ -84,6 +84,14 @@
|
|
|
84
84
|
},
|
|
85
85
|
"import": "./dist/esm/data-platform/index.mjs",
|
|
86
86
|
"default": "./dist/esm/data-platform/index.mjs"
|
|
87
|
+
},
|
|
88
|
+
"./assembly": {
|
|
89
|
+
"types": "./dist/types/assembly.d.ts",
|
|
90
|
+
"node": {
|
|
91
|
+
"import": "./dist/esm-node/assembly.mjs",
|
|
92
|
+
"require": "./dist/cjs/assembly.js"
|
|
93
|
+
},
|
|
94
|
+
"default": "./dist/esm/assembly.mjs"
|
|
87
95
|
}
|
|
88
96
|
},
|
|
89
97
|
"typesVersions": {
|
|
@@ -104,13 +112,17 @@
|
|
|
104
112
|
"./dist/types/effect-client/index.d.ts",
|
|
105
113
|
"./src/effect-client/index.ts"
|
|
106
114
|
],
|
|
107
|
-
"
|
|
108
|
-
"./dist/types/
|
|
109
|
-
"./src/
|
|
115
|
+
"microvertical-api": [
|
|
116
|
+
"./dist/types/microvertical-api.d.ts",
|
|
117
|
+
"./src/microvertical-api.ts"
|
|
110
118
|
],
|
|
111
119
|
"data-platform": [
|
|
112
120
|
"./dist/types/data-platform/index.d.ts",
|
|
113
121
|
"./src/data-platform/index.ts"
|
|
122
|
+
],
|
|
123
|
+
"assembly": [
|
|
124
|
+
"./dist/types/assembly.d.ts",
|
|
125
|
+
"./src/assembly.ts"
|
|
114
126
|
]
|
|
115
127
|
}
|
|
116
128
|
},
|
|
@@ -118,9 +130,10 @@
|
|
|
118
130
|
"node": ">=26.7.0"
|
|
119
131
|
},
|
|
120
132
|
"dependencies": {
|
|
121
|
-
"@modern-js/bff-core": "npm:@bleedingdev/modern-js-bff-core@3.9.0-ultramodern.
|
|
122
|
-
"@modern-js/create-request": "npm:@bleedingdev/modern-js-create-request@3.9.0-ultramodern.
|
|
123
|
-
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.
|
|
133
|
+
"@modern-js/bff-core": "npm:@bleedingdev/modern-js-bff-core@3.9.0-ultramodern.6",
|
|
134
|
+
"@modern-js/create-request": "npm:@bleedingdev/modern-js-create-request@3.9.0-ultramodern.6",
|
|
135
|
+
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.6",
|
|
136
|
+
"@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.6",
|
|
124
137
|
"@opentelemetry/api": "1.9.1",
|
|
125
138
|
"@opentelemetry/api-logs": "0.222.0",
|
|
126
139
|
"@opentelemetry/resources": "2.11.0",
|
package/src/assembly.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Server-only assembly for the invariant tail of a strict Effect BFF runtime factory. */
|
|
2
|
+
import * as Layer from 'effect/Layer';
|
|
3
|
+
import type { HttpApi, HttpApiGroup } from 'effect/unstable/httpapi';
|
|
4
|
+
import { HttpApiBuilder } from 'effect/unstable/httpapi';
|
|
5
|
+
import { defineEffectBff } from './effect/handler/definition';
|
|
6
|
+
import type { EffectRuntimeRequirements } from './effect/handler/types';
|
|
7
|
+
|
|
8
|
+
export interface EffectBffRuntimeAssembly<
|
|
9
|
+
ApiId extends string,
|
|
10
|
+
Groups extends HttpApiGroup.Constraint,
|
|
11
|
+
HandlerRequirements extends EffectRuntimeRequirements,
|
|
12
|
+
TransportRequirements extends EffectRuntimeRequirements = never,
|
|
13
|
+
> {
|
|
14
|
+
readonly api: HttpApi.HttpApi<ApiId, Groups>;
|
|
15
|
+
readonly handlers: Layer.Layer<
|
|
16
|
+
HttpApiGroup.ToService<ApiId, Groups>,
|
|
17
|
+
never,
|
|
18
|
+
HandlerRequirements
|
|
19
|
+
>;
|
|
20
|
+
readonly transport?: Layer.Layer<never, never, TransportRequirements>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const assembleEffectBffRuntime = <
|
|
24
|
+
ApiId extends string,
|
|
25
|
+
Groups extends HttpApiGroup.Constraint,
|
|
26
|
+
HandlerRequirements extends EffectRuntimeRequirements,
|
|
27
|
+
TransportRequirements extends EffectRuntimeRequirements = never,
|
|
28
|
+
>({
|
|
29
|
+
api,
|
|
30
|
+
handlers,
|
|
31
|
+
transport,
|
|
32
|
+
}: EffectBffRuntimeAssembly<
|
|
33
|
+
ApiId,
|
|
34
|
+
Groups,
|
|
35
|
+
HandlerRequirements,
|
|
36
|
+
TransportRequirements
|
|
37
|
+
>) => {
|
|
38
|
+
const apiLayer = HttpApiBuilder.layer(api).pipe(Layer.provide(handlers));
|
|
39
|
+
const layer =
|
|
40
|
+
transport === undefined ? apiLayer : apiLayer.pipe(Layer.merge(transport));
|
|
41
|
+
|
|
42
|
+
return defineEffectBff({ api, layer });
|
|
43
|
+
};
|
|
@@ -307,15 +307,14 @@ export function createBatchTransportQueue({
|
|
|
307
307
|
abortBatchIfUnused?.();
|
|
308
308
|
};
|
|
309
309
|
|
|
310
|
-
const runSingle = (request: QueuedBatchRequest) =>
|
|
311
|
-
|
|
310
|
+
const runSingle = (request: QueuedBatchRequest) =>
|
|
311
|
+
runWithSignal(request.lifetime.signal, async () => {
|
|
312
312
|
const response = await baseFetch(
|
|
313
313
|
request.requestInput,
|
|
314
314
|
request.requestInit,
|
|
315
315
|
);
|
|
316
316
|
return parseResponseLikeCreateRequest(response);
|
|
317
317
|
});
|
|
318
|
-
};
|
|
319
318
|
|
|
320
319
|
const settleRequests = async (
|
|
321
320
|
items: QueuedBatchRequest[],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @effect-diagnostics asyncFunction:off globalDate:off globalRandom:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
-
import { parseTraceparent } from '@modern-js/
|
|
2
|
+
import { parseTraceparent } from '@modern-js/runtime-extensions/request-context';
|
|
3
3
|
|
|
4
4
|
import type { TraceContext } from './types';
|
|
5
5
|
|
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
evaluateCrossProjectPolicy,
|
|
4
4
|
type NormalizedCrossProjectPolicy,
|
|
5
5
|
resolveCrossProjectRequestObservation,
|
|
6
|
-
} from '@modern-js/
|
|
6
|
+
} from '@modern-js/server-runtime-extensions/bff-policy';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
7
8
|
import { toHeaderRecord } from '../headers';
|
|
8
9
|
import {
|
|
9
10
|
type DispatchEffectBffRequestOptions,
|
|
@@ -91,7 +92,6 @@ export {
|
|
|
91
92
|
} from './edge-context';
|
|
92
93
|
export type {
|
|
93
94
|
EffectApiClientFromApi,
|
|
94
|
-
EffectApiPromiseClientFromApi,
|
|
95
95
|
EffectBffDefinition,
|
|
96
96
|
EffectBffHandlerFactory,
|
|
97
97
|
EffectBffOpenApiConfig,
|
|
@@ -285,9 +285,11 @@ export async function createEffectBffEdgeDispatcher(
|
|
|
285
285
|
disposePromise ??= (async () => {
|
|
286
286
|
acceptingRequests = false;
|
|
287
287
|
if (activeDispatches > 0) {
|
|
288
|
-
await
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
await Effect.runPromise(
|
|
289
|
+
Effect.callback<void>(resume => {
|
|
290
|
+
resolveDrain = () => resume(Effect.void);
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
291
293
|
}
|
|
292
294
|
await loaded.dispose?.();
|
|
293
295
|
})();
|
package/src/effect/edge.ts
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
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,
|
|
18
|
-
} from '@modern-js/bff-
|
|
6
|
+
} from '@modern-js/server-runtime-extensions/bff-policy/node';
|
|
19
7
|
import {
|
|
20
8
|
classifyEffectBffEntryModule,
|
|
21
9
|
isValidatorAwareHandlerFactory,
|
|
@@ -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)
|
|
@@ -42,8 +42,11 @@ function loadNodeValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandlerFa
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
declare const __MODERN_EFFECT_NODE_RUNTIME__: boolean;
|
|
46
|
+
|
|
45
47
|
const validatorAwareHandlerFactoryRegistry =
|
|
46
|
-
|
|
48
|
+
typeof __MODERN_EFFECT_NODE_RUNTIME__ !== 'undefined' &&
|
|
49
|
+
__MODERN_EFFECT_NODE_RUNTIME__
|
|
47
50
|
? loadNodeValidatorAwareHandlerFactoryRegistry()
|
|
48
51
|
: createLocalValidatorAwareHandlerFactoryRegistry();
|
|
49
52
|
|
|
@@ -55,11 +58,13 @@ type EffectBffEntryModule = {
|
|
|
55
58
|
default?: unknown;
|
|
56
59
|
};
|
|
57
60
|
|
|
58
|
-
type
|
|
61
|
+
type UnsupportedEffectBffEntryShape =
|
|
62
|
+
| '`handler` export'
|
|
63
|
+
| 'default request handler';
|
|
59
64
|
|
|
60
65
|
export type EffectBffEntryShapeFacts = {
|
|
61
66
|
module: EffectBffEntryModule;
|
|
62
|
-
|
|
67
|
+
unsupportedShape?: UnsupportedEffectBffEntryShape;
|
|
63
68
|
createHandler?: unknown;
|
|
64
69
|
createHandlerValidatorAware: boolean;
|
|
65
70
|
api?: unknown;
|
|
@@ -132,7 +137,7 @@ export function classifyEffectBffEntryModule(
|
|
|
132
137
|
function createEntryShapeFacts(
|
|
133
138
|
module: EffectBffEntryModule,
|
|
134
139
|
predicates: EffectBffEntryShapePredicates,
|
|
135
|
-
|
|
140
|
+
unsupportedShape?: UnsupportedEffectBffEntryShape,
|
|
136
141
|
): EffectBffEntryShapeFacts {
|
|
137
142
|
const createHandler =
|
|
138
143
|
typeof module.createHandler === 'function'
|
|
@@ -143,7 +148,7 @@ function createEntryShapeFacts(
|
|
|
143
148
|
|
|
144
149
|
return {
|
|
145
150
|
module,
|
|
146
|
-
|
|
151
|
+
unsupportedShape,
|
|
147
152
|
createHandler,
|
|
148
153
|
createHandlerValidatorAware:
|
|
149
154
|
createHandler !== undefined &&
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEffectBffEdgeDispatcher,
|
|
3
|
+
type EffectBffEdgeHandlerOptions,
|
|
4
|
+
} from './edge-dispatcher';
|
|
5
|
+
|
|
6
|
+
type OperationContracts = NonNullable<
|
|
7
|
+
EffectBffEdgeHandlerOptions['crossProjectPolicy']
|
|
8
|
+
>['expectedOperationContracts'];
|
|
9
|
+
|
|
10
|
+
export function createEffectBffEdgeDispatcherFactory(
|
|
11
|
+
module: EffectBffEdgeHandlerOptions['module'],
|
|
12
|
+
generatedOperationContracts: OperationContracts,
|
|
13
|
+
) {
|
|
14
|
+
return (options?: Omit<EffectBffEdgeHandlerOptions, 'module'>) => {
|
|
15
|
+
const policy = options?.crossProjectPolicy;
|
|
16
|
+
const crossProjectPolicy =
|
|
17
|
+
policy === undefined ||
|
|
18
|
+
policy === null ||
|
|
19
|
+
policy.expectedOperationContracts === null ||
|
|
20
|
+
typeof policy.expectedOperationContracts !== 'object' ||
|
|
21
|
+
Array.isArray(policy.expectedOperationContracts)
|
|
22
|
+
? policy
|
|
23
|
+
: {
|
|
24
|
+
...policy,
|
|
25
|
+
expectedOperationContracts: {
|
|
26
|
+
...policy.expectedOperationContracts,
|
|
27
|
+
...generatedOperationContracts,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
return createEffectBffEdgeDispatcher({
|
|
31
|
+
...options,
|
|
32
|
+
crossProjectPolicy,
|
|
33
|
+
module,
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @effect-diagnostics anyUnknownInErrorContext:off asyncFunction:off globalDate:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
+
import * as Effect from 'effect/Effect';
|
|
2
3
|
import {
|
|
3
4
|
type DataBatchResponsePayload,
|
|
4
5
|
DEFAULT_DATA_BATCH_HEADER,
|
|
@@ -417,14 +418,16 @@ export function createDataPlatformBatchRequestHandler<TContext>(options: {
|
|
|
417
418
|
);
|
|
418
419
|
} catch (error) {
|
|
419
420
|
if (error instanceof BatchItemTimeoutError) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
421
|
+
Effect.runSync(
|
|
422
|
+
Effect.logError({
|
|
423
|
+
event: 'bff.batch.item.timeout',
|
|
424
|
+
batchId: payload.batchId,
|
|
425
|
+
itemId,
|
|
426
|
+
method: itemMethod,
|
|
427
|
+
path: itemPathname,
|
|
428
|
+
errorName: error.name,
|
|
429
|
+
}),
|
|
430
|
+
);
|
|
428
431
|
return toBatchItemError(
|
|
429
432
|
itemId,
|
|
430
433
|
504,
|
|
@@ -435,14 +438,16 @@ export function createDataPlatformBatchRequestHandler<TContext>(options: {
|
|
|
435
438
|
return toBatchResponseItem(itemId, error, itemMethod === 'HEAD');
|
|
436
439
|
}
|
|
437
440
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
441
|
+
Effect.runSync(
|
|
442
|
+
Effect.logError({
|
|
443
|
+
event: 'bff.batch.item.failure',
|
|
444
|
+
batchId: payload.batchId,
|
|
445
|
+
itemId,
|
|
446
|
+
method: itemMethod,
|
|
447
|
+
path: itemPathname,
|
|
448
|
+
errorName: error instanceof Error ? error.name : 'UnknownError',
|
|
449
|
+
}),
|
|
450
|
+
);
|
|
446
451
|
return toBatchItemError(itemId, 500, 'Internal Server Error');
|
|
447
452
|
} finally {
|
|
448
453
|
request.signal.removeEventListener('abort', abortFromBatchRequest);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @effect-diagnostics anyUnknownInErrorContext:off asyncFunction:off globalDate:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
2
|
|
|
3
|
+
import * as Data from 'effect/Data';
|
|
3
4
|
import {
|
|
4
5
|
type DataBatchRequestPayload,
|
|
5
6
|
type DataBatchResponseItem,
|
|
@@ -117,7 +118,14 @@ export function toBatchItemError(
|
|
|
117
118
|
};
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
export class BatchItemTimeoutError extends
|
|
121
|
+
export class BatchItemTimeoutError extends Data.TaggedError(
|
|
122
|
+
'BatchItemTimeoutError',
|
|
123
|
+
)<{ readonly message: string }> {
|
|
124
|
+
constructor(message: string) {
|
|
125
|
+
super({ message });
|
|
126
|
+
this.name = 'Error';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
121
129
|
|
|
122
130
|
export function promiseWithTimeout<T>(
|
|
123
131
|
effect: Promise<T>,
|
|
@@ -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
|
);
|