@bleedingdev/modern-js-bff-effect 3.9.0-ultramodern.2 → 3.9.0-ultramodern.21
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/assembly.js +50 -0
- package/dist/cjs/{effect-client/guards.js → data-platform/batch/options.js} +13 -9
- package/dist/cjs/data-platform/batch/queue.js +48 -83
- package/dist/cjs/data-platform/trace.js +2 -2
- package/dist/cjs/effect/context.js +28 -4
- package/dist/cjs/effect/edge-dispatcher.js +12 -12
- package/dist/cjs/effect/edge.js +36 -3
- package/dist/cjs/effect/endpoint-contracts.js +3 -4
- package/dist/cjs/effect/entry-shape.js +40 -12
- package/dist/cjs/effect/generated-entry.js +57 -0
- package/dist/cjs/effect/handler/batch-handler.js +7 -7
- package/dist/cjs/effect/handler/batch.js +8 -10
- package/dist/cjs/effect/handler/definition.js +1 -21
- package/dist/cjs/effect/index.js +8 -8
- package/dist/cjs/effect/module.js +8 -4
- package/dist/cjs/effect/operation-context.js +10 -14
- package/dist/cjs/effect/rpcSerialization.js +0 -2
- package/dist/cjs/effect-client/cross-project.js +89 -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/batch/options.mjs +10 -0
- package/dist/esm/data-platform/batch/queue.mjs +48 -83
- package/dist/esm/data-platform/trace.mjs +1 -1
- package/dist/esm/effect/context.mjs +21 -6
- package/dist/esm/effect/edge-dispatcher.mjs +6 -6
- package/dist/esm/effect/edge.mjs +3 -0
- package/dist/esm/effect/endpoint-contracts.mjs +2 -3
- package/dist/esm/effect/entry-shape-registry.mjs +2 -2
- package/dist/esm/effect/entry-shape.mjs +29 -6
- package/dist/esm/effect/generated-entry.mjs +19 -0
- package/dist/esm/effect/handler/batch-handler.mjs +8 -8
- package/dist/esm/effect/handler/batch.mjs +9 -8
- package/dist/esm/effect/handler/definition.mjs +1 -21
- package/dist/esm/effect/index.mjs +1 -1
- package/dist/esm/effect/module.mjs +8 -4
- package/dist/esm/effect/operation-context.mjs +3 -4
- package/dist/esm/effect/rpcSerialization.mjs +0 -2
- package/dist/esm/effect-client/cross-project.mjs +51 -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/batch/options.mjs +11 -0
- package/dist/esm-node/data-platform/batch/queue.mjs +48 -83
- package/dist/esm-node/data-platform/trace.mjs +1 -1
- package/dist/esm-node/effect/context.mjs +21 -6
- package/dist/esm-node/effect/edge-dispatcher.mjs +6 -6
- package/dist/esm-node/effect/edge.mjs +3 -0
- package/dist/esm-node/effect/endpoint-contracts.mjs +2 -3
- package/dist/esm-node/effect/entry-shape.mjs +29 -6
- package/dist/esm-node/effect/generated-entry.mjs +20 -0
- package/dist/esm-node/effect/handler/batch-handler.mjs +8 -8
- package/dist/esm-node/effect/handler/batch.mjs +9 -8
- package/dist/esm-node/effect/handler/definition.mjs +1 -21
- package/dist/esm-node/effect/index.mjs +1 -1
- package/dist/esm-node/effect/module.mjs +8 -4
- package/dist/esm-node/effect/operation-context.mjs +3 -4
- package/dist/esm-node/effect/rpcSerialization.mjs +0 -2
- package/dist/esm-node/effect-client/cross-project.mjs +52 -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/data-platform/batch/options.d.ts +7 -0
- package/dist/types/data-platform/batch/queue.d.ts +0 -1
- package/dist/types/effect/context.d.ts +15 -2
- package/dist/types/effect/edge-dispatcher.d.ts +3 -3
- package/dist/types/effect/edge.d.ts +3 -0
- package/dist/types/effect/endpoint-contracts.d.ts +2 -20
- package/dist/types/effect/entry-shape.d.ts +16 -2
- package/dist/types/effect/generated-entry.d.ts +7 -0
- package/dist/types/effect/handler/batch.d.ts +7 -2
- package/dist/types/effect/handler/types.d.ts +2 -17
- package/dist/types/effect/handler.d.ts +1 -1
- package/dist/types/effect/module.d.ts +2 -1
- package/dist/types/effect/operation-context.d.ts +1 -2
- package/dist/types/effect-client/cross-project.d.ts +10 -0
- package/dist/types/effect-client/index.d.ts +7 -4
- package/dist/types/microvertical-api.d.ts +65 -0
- package/package.json +47 -42
- package/src/assembly.ts +43 -0
- package/src/data-platform/batch/options.ts +20 -0
- package/src/data-platform/batch/queue.ts +63 -92
- package/src/data-platform/trace.ts +1 -1
- package/src/effect/context.ts +64 -12
- package/src/effect/edge-dispatcher.ts +14 -11
- package/src/effect/edge.ts +6 -0
- package/src/effect/endpoint-contracts.ts +3 -24
- package/src/effect/entry-shape.ts +105 -10
- package/src/effect/generated-entry.ts +36 -0
- package/src/effect/handler/batch-handler.ts +27 -28
- package/src/effect/handler/batch.ts +9 -13
- package/src/effect/handler/definition.ts +0 -45
- package/src/effect/handler/http.ts +2 -2
- package/src/effect/handler/types.ts +3 -32
- package/src/effect/handler.ts +0 -1
- package/src/effect/index.ts +1 -1
- package/src/effect/module.ts +11 -4
- package/src/effect/operation-context.ts +2 -6
- package/src/effect/rpcSerialization.ts +0 -2
- package/src/effect-client/cross-project.ts +104 -0
- package/src/effect-client/index.ts +17 -7
- package/src/microvertical-api.ts +102 -0
- package/dist/cjs/effect/edge-context.js +0 -61
- 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/edge-context.mjs +0 -14
- 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/edge-context.mjs +0 -15
- 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/edge-context.d.ts +0 -5
- 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/edge-context.ts +0 -51
- 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
|
@@ -1,8 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adoptEffectContextStorage,
|
|
3
|
+
EFFECT_BFF_CONTEXT_STORAGE_SHARE,
|
|
4
|
+
} from '@modern-js/bff-effect/context';
|
|
5
|
+
import type { EffectRpcBffDefinition } from './handler/types';
|
|
6
|
+
|
|
1
7
|
type ValidatorAwareHandlerFactoryRegistry = {
|
|
2
8
|
register<TFactory extends Function>(factory: TFactory): TFactory;
|
|
3
9
|
is(factory: unknown): boolean;
|
|
4
10
|
};
|
|
5
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Module Federation share through which a backend federation host hands its
|
|
14
|
+
* package-private handler factory registry to the containers it initializes.
|
|
15
|
+
*/
|
|
16
|
+
export const EFFECT_BFF_HANDLER_FACTORY_REGISTRY_SHARE =
|
|
17
|
+
'@modern-js/bff-effect/handler-factory-registry';
|
|
18
|
+
|
|
6
19
|
function createLocalValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandlerFactoryRegistry {
|
|
7
20
|
const factories = new WeakSet<Function>();
|
|
8
21
|
return {
|
|
@@ -19,6 +32,8 @@ function createLocalValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandle
|
|
|
19
32
|
function loadNodeValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandlerFactoryRegistry {
|
|
20
33
|
const moduleUrl = import.meta.url;
|
|
21
34
|
if (typeof moduleUrl !== 'string' || !moduleUrl.startsWith('file:')) {
|
|
35
|
+
// A bundled backend federation container has no file URL; its host share
|
|
36
|
+
// arrives through adoptEffectBffShareScope() during container init.
|
|
22
37
|
return createLocalValidatorAwareHandlerFactoryRegistry();
|
|
23
38
|
}
|
|
24
39
|
|
|
@@ -42,24 +57,104 @@ function loadNodeValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandlerFa
|
|
|
42
57
|
}
|
|
43
58
|
}
|
|
44
59
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
declare const __MODERN_EFFECT_NODE_RUNTIME__: boolean;
|
|
61
|
+
|
|
62
|
+
let validatorAwareHandlerFactoryRegistry:
|
|
63
|
+
| ValidatorAwareHandlerFactoryRegistry
|
|
64
|
+
| undefined;
|
|
65
|
+
|
|
66
|
+
function handlerFactoryRegistry(): ValidatorAwareHandlerFactoryRegistry {
|
|
67
|
+
validatorAwareHandlerFactoryRegistry ??=
|
|
68
|
+
typeof __MODERN_EFFECT_NODE_RUNTIME__ !== 'undefined' &&
|
|
69
|
+
__MODERN_EFFECT_NODE_RUNTIME__
|
|
70
|
+
? loadNodeValidatorAwareHandlerFactoryRegistry()
|
|
71
|
+
: createLocalValidatorAwareHandlerFactoryRegistry();
|
|
72
|
+
return validatorAwareHandlerFactoryRegistry;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type SharedVersions = Record<string, { get?: () => unknown } | undefined>;
|
|
76
|
+
|
|
77
|
+
const adoptShared = (
|
|
78
|
+
shareScope: unknown,
|
|
79
|
+
name: string,
|
|
80
|
+
adopt: (value: unknown) => void,
|
|
81
|
+
): Promise<void> => {
|
|
82
|
+
const versions =
|
|
83
|
+
typeof shareScope === 'object' && shareScope !== null
|
|
84
|
+
? (shareScope as Record<string, SharedVersions | undefined>)[name]
|
|
85
|
+
: undefined;
|
|
86
|
+
const shared =
|
|
87
|
+
versions === undefined ? undefined : Object.values(versions)[0];
|
|
88
|
+
if (typeof shared?.get !== 'function') {
|
|
89
|
+
return Promise.resolve();
|
|
90
|
+
}
|
|
91
|
+
return Promise.resolve(shared.get()).then(factory =>
|
|
92
|
+
adopt(typeof factory === 'function' ? factory() : undefined),
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @internal Called by generated backend federation containers from `init()`,
|
|
98
|
+
* before any expose evaluates, so factories created by `defineEffectBff`
|
|
99
|
+
* register with the host's registry and endpoints read the host's request
|
|
100
|
+
* storage instead of bundle-local ones.
|
|
101
|
+
*/
|
|
102
|
+
export function adoptEffectBffShareScope(shareScope: unknown): Promise<void> {
|
|
103
|
+
return Promise.all([
|
|
104
|
+
adoptShared(
|
|
105
|
+
shareScope,
|
|
106
|
+
EFFECT_BFF_HANDLER_FACTORY_REGISTRY_SHARE,
|
|
107
|
+
registry =>
|
|
108
|
+
adoptHandlerFactoryRegistry(
|
|
109
|
+
registry as ValidatorAwareHandlerFactoryRegistry | undefined,
|
|
110
|
+
),
|
|
111
|
+
),
|
|
112
|
+
adoptShared(
|
|
113
|
+
shareScope,
|
|
114
|
+
EFFECT_BFF_CONTEXT_STORAGE_SHARE,
|
|
115
|
+
adoptEffectContextStorage,
|
|
116
|
+
),
|
|
117
|
+
]).then(() => undefined);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function adoptHandlerFactoryRegistry(
|
|
121
|
+
registry: ValidatorAwareHandlerFactoryRegistry | undefined,
|
|
122
|
+
): void {
|
|
123
|
+
if (
|
|
124
|
+
typeof registry?.register !== 'function' ||
|
|
125
|
+
typeof registry.is !== 'function'
|
|
126
|
+
) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`[BFF][Effect] Share ${EFFECT_BFF_HANDLER_FACTORY_REGISTRY_SHARE} must provide a handler factory registry.`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (
|
|
132
|
+
validatorAwareHandlerFactoryRegistry !== undefined &&
|
|
133
|
+
validatorAwareHandlerFactoryRegistry !== registry
|
|
134
|
+
) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
'[BFF][Effect] A backend federation container received the host handler factory registry after an Effect API already used its own. Evaluate exposes only after container init().',
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
validatorAwareHandlerFactoryRegistry = registry;
|
|
140
|
+
}
|
|
49
141
|
|
|
50
142
|
type EffectBffEntryModule = {
|
|
51
143
|
api?: unknown;
|
|
52
144
|
layer?: unknown;
|
|
145
|
+
rpc?: EffectRpcBffDefinition;
|
|
53
146
|
handler?: unknown;
|
|
54
147
|
createHandler?: unknown;
|
|
55
148
|
default?: unknown;
|
|
56
149
|
};
|
|
57
150
|
|
|
58
|
-
type
|
|
151
|
+
type UnsupportedEffectBffEntryShape =
|
|
152
|
+
| '`handler` export'
|
|
153
|
+
| 'default request handler';
|
|
59
154
|
|
|
60
155
|
export type EffectBffEntryShapeFacts = {
|
|
61
156
|
module: EffectBffEntryModule;
|
|
62
|
-
|
|
157
|
+
unsupportedShape?: UnsupportedEffectBffEntryShape;
|
|
63
158
|
createHandler?: unknown;
|
|
64
159
|
createHandlerValidatorAware: boolean;
|
|
65
160
|
api?: unknown;
|
|
@@ -77,7 +172,7 @@ type EffectBffEntryShapePredicates = {
|
|
|
77
172
|
export function registerValidatorAwareHandlerFactory<TFactory extends Function>(
|
|
78
173
|
factory: TFactory,
|
|
79
174
|
): TFactory {
|
|
80
|
-
return
|
|
175
|
+
return handlerFactoryRegistry().register(factory);
|
|
81
176
|
}
|
|
82
177
|
|
|
83
178
|
/**
|
|
@@ -85,7 +180,7 @@ export function registerValidatorAwareHandlerFactory<TFactory extends Function>(
|
|
|
85
180
|
* therefore forwards strict cross-project validation into createHttpApiHandler.
|
|
86
181
|
*/
|
|
87
182
|
export function isValidatorAwareHandlerFactory(factory: unknown): boolean {
|
|
88
|
-
return
|
|
183
|
+
return handlerFactoryRegistry().is(factory);
|
|
89
184
|
}
|
|
90
185
|
|
|
91
186
|
export const strictEffectApproachMessage =
|
|
@@ -132,7 +227,7 @@ export function classifyEffectBffEntryModule(
|
|
|
132
227
|
function createEntryShapeFacts(
|
|
133
228
|
module: EffectBffEntryModule,
|
|
134
229
|
predicates: EffectBffEntryShapePredicates,
|
|
135
|
-
|
|
230
|
+
unsupportedShape?: UnsupportedEffectBffEntryShape,
|
|
136
231
|
): EffectBffEntryShapeFacts {
|
|
137
232
|
const createHandler =
|
|
138
233
|
typeof module.createHandler === 'function'
|
|
@@ -143,7 +238,7 @@ function createEntryShapeFacts(
|
|
|
143
238
|
|
|
144
239
|
return {
|
|
145
240
|
module,
|
|
146
|
-
|
|
241
|
+
unsupportedShape,
|
|
147
242
|
createHandler,
|
|
148
243
|
createHandlerValidatorAware:
|
|
149
244
|
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,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
isPlainObject,
|
|
8
9
|
normalizeMethod as normalizeItemMethod,
|
|
9
10
|
} from '../../data-platform';
|
|
11
|
+
import { normalizeBatchLimits } from '../../data-platform/batch/options';
|
|
10
12
|
import {
|
|
11
13
|
decodeBatchBody,
|
|
12
14
|
isBatchBody,
|
|
@@ -17,7 +19,6 @@ import {
|
|
|
17
19
|
createBatchValidationResponse,
|
|
18
20
|
isBatchRequestPayload,
|
|
19
21
|
mapWithConcurrency,
|
|
20
|
-
normalizeBatchAllowedMethods,
|
|
21
22
|
normalizeBatchPath,
|
|
22
23
|
promiseWithTimeout,
|
|
23
24
|
toBatchItemError,
|
|
@@ -128,14 +129,11 @@ export function createDataPlatformBatchRequestHandler<TContext>(options: {
|
|
|
128
129
|
const dataPlatformBatchOptions = options.dataPlatform?.batch;
|
|
129
130
|
const batchEnabled = dataPlatformBatchOptions?.enabled !== false;
|
|
130
131
|
const batchPath = normalizeBatchPath(dataPlatformBatchOptions?.endpoint);
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
1024,
|
|
137
|
-
dataPlatformBatchOptions?.maxBatchBytes ?? 64 * 1024,
|
|
138
|
-
);
|
|
132
|
+
const {
|
|
133
|
+
maxBatchSize: batchMaxSize,
|
|
134
|
+
maxBatchBytes: batchMaxBytes,
|
|
135
|
+
allowedMethods: batchAllowedMethods,
|
|
136
|
+
} = normalizeBatchLimits(dataPlatformBatchOptions);
|
|
139
137
|
const batchConcurrency = Math.max(
|
|
140
138
|
1,
|
|
141
139
|
dataPlatformBatchOptions?.maxConcurrency ?? 4,
|
|
@@ -144,9 +142,6 @@ export function createDataPlatformBatchRequestHandler<TContext>(options: {
|
|
|
144
142
|
0,
|
|
145
143
|
dataPlatformBatchOptions?.requestTimeoutMs ?? 10_000,
|
|
146
144
|
);
|
|
147
|
-
const batchAllowedMethods = normalizeBatchAllowedMethods(
|
|
148
|
-
dataPlatformBatchOptions?.allowedMethods,
|
|
149
|
-
);
|
|
150
145
|
const envelopeHeader =
|
|
151
146
|
options.dataPlatform?.envelopeHeader || DEFAULT_DATA_ENVELOPE_HEADER;
|
|
152
147
|
const normalizedEnvelopeHeader = envelopeHeader.toLowerCase();
|
|
@@ -417,14 +412,16 @@ export function createDataPlatformBatchRequestHandler<TContext>(options: {
|
|
|
417
412
|
);
|
|
418
413
|
} catch (error) {
|
|
419
414
|
if (error instanceof BatchItemTimeoutError) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
415
|
+
Effect.runSync(
|
|
416
|
+
Effect.logError({
|
|
417
|
+
event: 'bff.batch.item.timeout',
|
|
418
|
+
batchId: payload.batchId,
|
|
419
|
+
itemId,
|
|
420
|
+
method: itemMethod,
|
|
421
|
+
path: itemPathname,
|
|
422
|
+
errorName: error.name,
|
|
423
|
+
}),
|
|
424
|
+
);
|
|
428
425
|
return toBatchItemError(
|
|
429
426
|
itemId,
|
|
430
427
|
504,
|
|
@@ -435,14 +432,16 @@ export function createDataPlatformBatchRequestHandler<TContext>(options: {
|
|
|
435
432
|
return toBatchResponseItem(itemId, error, itemMethod === 'HEAD');
|
|
436
433
|
}
|
|
437
434
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
435
|
+
Effect.runSync(
|
|
436
|
+
Effect.logError({
|
|
437
|
+
event: 'bff.batch.item.failure',
|
|
438
|
+
batchId: payload.batchId,
|
|
439
|
+
itemId,
|
|
440
|
+
method: itemMethod,
|
|
441
|
+
path: itemPathname,
|
|
442
|
+
errorName: error instanceof Error ? error.name : 'UnknownError',
|
|
443
|
+
}),
|
|
444
|
+
);
|
|
446
445
|
return toBatchItemError(itemId, 500, 'Internal Server Error');
|
|
447
446
|
} finally {
|
|
448
447
|
request.signal.removeEventListener('abort', abortFromBatchRequest);
|
|
@@ -1,15 +1,14 @@
|
|
|
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,
|
|
6
7
|
DEFAULT_DATA_BATCH_ENDPOINT,
|
|
7
8
|
isPlainObject,
|
|
8
|
-
measureTextBytes,
|
|
9
9
|
normalizeMethod as normalizeItemMethod,
|
|
10
10
|
} from '../../data-platform';
|
|
11
11
|
import { encodeBatchText } from '../../data-platform/batch/protocol';
|
|
12
|
-
import { toHeaderRecord } from '../../headers';
|
|
13
12
|
|
|
14
13
|
export { toHeaderRecord } from '../../headers';
|
|
15
14
|
|
|
@@ -23,16 +22,6 @@ export function normalizeBatchPath(pathname: string | undefined) {
|
|
|
23
22
|
return pathname as `/${string}`;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
export function normalizeBatchAllowedMethods(
|
|
27
|
-
allowedMethods: string[] | undefined,
|
|
28
|
-
) {
|
|
29
|
-
const source =
|
|
30
|
-
Array.isArray(allowedMethods) && allowedMethods.length > 0
|
|
31
|
-
? allowedMethods
|
|
32
|
-
: ['GET'];
|
|
33
|
-
return new Set(source.map(method => method.toUpperCase()));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
25
|
type ParsedBatchRequestPayload = Omit<DataBatchRequestPayload, 'items'> & {
|
|
37
26
|
items: unknown[];
|
|
38
27
|
};
|
|
@@ -117,7 +106,14 @@ export function toBatchItemError(
|
|
|
117
106
|
};
|
|
118
107
|
}
|
|
119
108
|
|
|
120
|
-
export class BatchItemTimeoutError extends
|
|
109
|
+
export class BatchItemTimeoutError extends Data.TaggedError(
|
|
110
|
+
'BatchItemTimeoutError',
|
|
111
|
+
)<{ readonly message: string }> {
|
|
112
|
+
constructor(message: string) {
|
|
113
|
+
super({ message });
|
|
114
|
+
this.name = 'Error';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
121
117
|
|
|
122
118
|
export function promiseWithTimeout<T>(
|
|
123
119
|
effect: Promise<T>,
|
|
@@ -7,11 +7,9 @@ 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,
|
|
14
|
-
EffectDataPlatformValidationOptions,
|
|
15
13
|
EffectRpcBffDefinition,
|
|
16
14
|
EffectRpcBffHandlerFactory,
|
|
17
15
|
EffectRpcRuntimeLayer,
|
|
@@ -51,55 +49,12 @@ export function defineEffectBff<
|
|
|
51
49
|
validateRequest: options?.validateRequest,
|
|
52
50
|
});
|
|
53
51
|
});
|
|
54
|
-
const client = createLoaderMaterializedClientPlaceholder<TApi>();
|
|
55
52
|
return {
|
|
56
53
|
...definition,
|
|
57
54
|
createHandler,
|
|
58
|
-
client,
|
|
59
55
|
};
|
|
60
56
|
}
|
|
61
57
|
|
|
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
58
|
export function defineEffectRpcBff<
|
|
104
59
|
TRpcs extends Rpc.Any = Rpc.Any,
|
|
105
60
|
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;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// @effect-diagnostics anyUnknownInErrorContext:off asyncFunction:off globalDate:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
2
|
import type { FileSystem, Path } from 'effect';
|
|
3
3
|
import * as Context from 'effect/Context';
|
|
4
|
-
import type * as EffectType from 'effect/Effect';
|
|
5
4
|
import type * as Layer from 'effect/Layer';
|
|
6
5
|
import type { Etag, HttpPlatform, HttpRouter } from 'effect/unstable/http';
|
|
7
6
|
import type {
|
|
@@ -43,8 +42,7 @@ export type EffectRpcSerialization =
|
|
|
43
42
|
| 'json'
|
|
44
43
|
| 'ndjson'
|
|
45
44
|
| 'jsonRpc'
|
|
46
|
-
| 'ndJsonRpc'
|
|
47
|
-
| 'msgPack';
|
|
45
|
+
| 'ndJsonRpc';
|
|
48
46
|
|
|
49
47
|
export type EffectRpcRuntimeLayer<TRpcs extends Rpc.Any = Rpc.Any> =
|
|
50
48
|
Layer.Layer<
|
|
@@ -77,22 +75,6 @@ export type EffectRpcBffHandlerOptions = Pick<
|
|
|
77
75
|
| 'disableFatalDefects'
|
|
78
76
|
>;
|
|
79
77
|
|
|
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
78
|
export type EffectApiClientFromApi<
|
|
97
79
|
TApi extends HttpApi.Constraint = HttpApi.Top,
|
|
98
80
|
> =
|
|
@@ -104,10 +86,6 @@ export type EffectApiClientFromApi<
|
|
|
104
86
|
>
|
|
105
87
|
: never;
|
|
106
88
|
|
|
107
|
-
export type EffectApiPromiseClientFromApi<
|
|
108
|
-
TApi extends HttpApi.Constraint = HttpApi.Top,
|
|
109
|
-
> = EffectApiPromiseClient<EffectApiClientFromApi<TApi>>;
|
|
110
|
-
|
|
111
89
|
export type EffectBffDefinition<
|
|
112
90
|
TApi extends HttpApi.Constraint = HttpApi.Top,
|
|
113
91
|
TLayer extends EffectRuntimeLayer = EffectRuntimeLayer,
|
|
@@ -147,12 +125,6 @@ export type EffectDataPlatformBatchOptions = {
|
|
|
147
125
|
* Defaults to `65536` (64KiB).
|
|
148
126
|
*/
|
|
149
127
|
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
128
|
/**
|
|
157
129
|
* Maximum per-batch internal request concurrency.
|
|
158
130
|
* Defaults to `4`.
|
|
@@ -227,8 +199,8 @@ export type EffectRequestInterceptor = (options: {
|
|
|
227
199
|
}) => Response | Promise<Response>;
|
|
228
200
|
|
|
229
201
|
export type EffectBffHandlerFactory<
|
|
230
|
-
|
|
231
|
-
|
|
202
|
+
_TApi extends HttpApi.Constraint = HttpApi.Top,
|
|
203
|
+
_TLayer extends EffectRuntimeLayer = EffectRuntimeLayer,
|
|
232
204
|
> = (options?: {
|
|
233
205
|
openapi?: EffectBffOpenApiConfig;
|
|
234
206
|
rpc?: Partial<EffectRpcBffHandlerOptions>;
|
|
@@ -241,7 +213,6 @@ export type EffectBffRuntime<
|
|
|
241
213
|
TLayer extends EffectRuntimeLayer = EffectRuntimeLayer,
|
|
242
214
|
> = {
|
|
243
215
|
createHandler: EffectBffHandlerFactory<TApi, TLayer>;
|
|
244
|
-
client: EffectApiPromiseClientFromApi<TApi>;
|
|
245
216
|
};
|
|
246
217
|
|
|
247
218
|
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/index.ts
CHANGED
package/src/effect/module.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type EffectBffOpenApiConfig,
|
|
13
13
|
type EffectDataPlatformValidationOptions,
|
|
14
14
|
type EffectRequestValidator,
|
|
15
|
+
type EffectRpcBffDefinition,
|
|
15
16
|
type EffectRuntimeLayer,
|
|
16
17
|
} from './handler';
|
|
17
18
|
import type { EffectContext } from './operation-context';
|
|
@@ -33,6 +34,7 @@ export type EffectBffHandlerFactory = (options?: {
|
|
|
33
34
|
export type EffectApiModule = {
|
|
34
35
|
api?: unknown;
|
|
35
36
|
layer?: unknown;
|
|
37
|
+
rpc?: EffectRpcBffDefinition;
|
|
36
38
|
handler?: EffectBffRequestHandler;
|
|
37
39
|
createHandler?: EffectBffHandlerFactory;
|
|
38
40
|
default?: unknown;
|
|
@@ -67,7 +69,7 @@ function shareDispose(dispose: () => Promise<void>) {
|
|
|
67
69
|
};
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
function
|
|
72
|
+
function rejectUnsupportedEffectModuleShape(
|
|
71
73
|
options: ResolveEffectBffModuleHandlerOptions,
|
|
72
74
|
shape: string,
|
|
73
75
|
) {
|
|
@@ -119,12 +121,17 @@ function resolveClassifiedEffectBffModuleHandler(
|
|
|
119
121
|
return null;
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
if (facts.
|
|
123
|
-
if (
|
|
124
|
+
if (facts.unsupportedShape) {
|
|
125
|
+
if (rejectUnsupportedEffectModuleShape(options, facts.unsupportedShape)) {
|
|
124
126
|
return null;
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
|
|
130
|
+
if (facts.module.rpc !== undefined && !facts.createHandlerValidatorAware) {
|
|
131
|
+
rejectUnsupportedEffectModuleShape(options, 'unbranded RPC runtime');
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
128
135
|
if (typeof facts.createHandler === 'function') {
|
|
129
136
|
if (facts.createHandlerValidatorAware) {
|
|
130
137
|
const factory = facts.createHandler as EffectBffHandlerFactory;
|
|
@@ -137,7 +144,7 @@ function resolveClassifiedEffectBffModuleHandler(
|
|
|
137
144
|
}
|
|
138
145
|
|
|
139
146
|
if (facts.api === undefined || !facts.hasRuntimeLayer) {
|
|
140
|
-
|
|
147
|
+
rejectUnsupportedEffectModuleShape(
|
|
141
148
|
options,
|
|
142
149
|
'unbranded `createHandler` export',
|
|
143
150
|
);
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
BFF_TRACEPARENT_HEADER,
|
|
7
7
|
type OperationContext,
|
|
8
8
|
parseTraceparent,
|
|
9
|
-
} from '@modern-js/
|
|
9
|
+
} from '@modern-js/runtime-extensions/request-context';
|
|
10
10
|
|
|
11
11
|
export type EffectContext = {
|
|
12
12
|
request: Request;
|
|
@@ -21,10 +21,6 @@ export type EffectContextStorage = {
|
|
|
21
21
|
run: <TResult>(value: EffectContext, cb: () => TResult) => TResult;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
export const kEffectContextStorage = Symbol.for(
|
|
25
|
-
'modernjs.plugin-bff.effectContextStorage',
|
|
26
|
-
);
|
|
27
|
-
|
|
28
24
|
export type CreateEffectOperationContextOptions = Omit<
|
|
29
25
|
EffectContext,
|
|
30
26
|
'operationContext'
|
|
@@ -128,7 +124,7 @@ export const createEffectOperationContext = ({
|
|
|
128
124
|
: {}),
|
|
129
125
|
routePath: servicePath,
|
|
130
126
|
method: (method || request.method || 'GET').toUpperCase(),
|
|
131
|
-
source: 'effect-
|
|
127
|
+
source: 'effect-server',
|
|
132
128
|
...(path && path !== servicePath
|
|
133
129
|
? { attributes: { mountedPath: path } }
|
|
134
130
|
: {}),
|
|
@@ -12,8 +12,6 @@ export function getRpcSerializationLayer(
|
|
|
12
12
|
return RpcSerialization.layerJsonRpc();
|
|
13
13
|
case 'ndJsonRpc':
|
|
14
14
|
return RpcSerialization.layerNdJsonRpc();
|
|
15
|
-
case 'msgPack':
|
|
16
|
-
return RpcSerialization.layerMsgPack;
|
|
17
15
|
default:
|
|
18
16
|
return RpcSerialization.layerJsonRpc();
|
|
19
17
|
}
|