@bleedingdev/modern-js-bff-effect 3.8.3-ultramodern.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/cjs/data-platform/batch/protocol.js +130 -0
- package/dist/cjs/data-platform/batch/queue.js +623 -0
- package/dist/cjs/data-platform/batch/request.js +121 -0
- package/dist/cjs/data-platform/batch/response.js +65 -0
- package/dist/cjs/data-platform/batch/telemetry.js +71 -0
- package/dist/cjs/data-platform/batch/url.js +69 -0
- package/dist/cjs/data-platform/batch-transport.js +59 -0
- package/dist/cjs/data-platform/codec.js +155 -0
- package/dist/cjs/data-platform/index.js +124 -0
- package/dist/cjs/data-platform/invalidation.js +61 -0
- package/dist/cjs/data-platform/trace.js +73 -0
- package/dist/cjs/data-platform/types.js +48 -0
- package/dist/cjs/data-platform/validation.js +178 -0
- package/dist/cjs/effect/context.js +59 -0
- package/dist/cjs/effect/dispatch.js +101 -0
- package/dist/cjs/effect/edge-context.js +61 -0
- package/dist/cjs/effect/edge-dispatcher.js +354 -0
- package/dist/cjs/effect/edge.js +62 -0
- package/dist/cjs/effect/endpoint-contracts.js +121 -0
- package/dist/cjs/effect/entry-shape-registry.js +31 -0
- package/dist/cjs/effect/entry-shape.js +113 -0
- package/dist/cjs/effect/handler/batch-handler.js +270 -0
- package/dist/cjs/effect/handler/batch.js +170 -0
- package/dist/cjs/effect/handler/definition.js +99 -0
- package/dist/cjs/effect/handler/envelope.js +104 -0
- package/dist/cjs/effect/handler/http.js +107 -0
- package/dist/cjs/effect/handler/openapi.js +58 -0
- package/dist/cjs/effect/handler/routing.js +100 -0
- package/dist/cjs/effect/handler/rpc.js +63 -0
- package/dist/cjs/effect/handler/types.js +43 -0
- package/dist/cjs/effect/handler.js +173 -0
- package/dist/cjs/effect/index.js +137 -0
- package/dist/cjs/effect/module.js +118 -0
- package/dist/cjs/effect/operation-context.js +116 -0
- package/dist/cjs/effect/rpcSerialization.js +54 -0
- package/dist/cjs/effect-client/batch.js +73 -0
- package/dist/cjs/effect-client/envelope.js +145 -0
- package/dist/cjs/effect-client/guards.js +44 -0
- package/dist/cjs/effect-client/index.js +208 -0
- package/dist/cjs/effect-client/request-context.js +64 -0
- package/dist/cjs/effect-client/request.js +66 -0
- package/dist/cjs/effect-client/runtime.js +100 -0
- package/dist/cjs/effect-client/types.js +18 -0
- package/dist/cjs/headers.js +46 -0
- package/dist/cjs/index.js +83 -0
- package/dist/esm/data-platform/batch/protocol.mjs +71 -0
- package/dist/esm/data-platform/batch/queue.mjs +582 -0
- package/dist/esm/data-platform/batch/request.mjs +68 -0
- package/dist/esm/data-platform/batch/response.mjs +24 -0
- package/dist/esm/data-platform/batch/telemetry.mjs +27 -0
- package/dist/esm/data-platform/batch/url.mjs +25 -0
- package/dist/esm/data-platform/batch-transport.mjs +12 -0
- package/dist/esm/data-platform/codec.mjs +90 -0
- package/dist/esm/data-platform/index.mjs +7 -0
- package/dist/esm/data-platform/invalidation.mjs +20 -0
- package/dist/esm/data-platform/trace.mjs +29 -0
- package/dist/esm/data-platform/types.mjs +4 -0
- package/dist/esm/data-platform/validation.mjs +128 -0
- package/dist/esm/effect/context.mjs +12 -0
- package/dist/esm/effect/dispatch.mjs +63 -0
- package/dist/esm/effect/edge-context.mjs +14 -0
- package/dist/esm/effect/edge-dispatcher.mjs +115 -0
- package/dist/esm/effect/edge.mjs +1 -0
- package/dist/esm/effect/endpoint-contracts.mjs +68 -0
- package/dist/esm/effect/entry-shape-registry.mjs +17 -0
- package/dist/esm/effect/entry-shape.mjs +51 -0
- package/dist/esm/effect/handler/batch-handler.mjs +232 -0
- package/dist/esm/effect/handler/batch.mjs +105 -0
- package/dist/esm/effect/handler/definition.mjs +58 -0
- package/dist/esm/effect/handler/envelope.mjs +63 -0
- package/dist/esm/effect/handler/http.mjs +69 -0
- package/dist/esm/effect/handler/openapi.mjs +20 -0
- package/dist/esm/effect/handler/routing.mjs +50 -0
- package/dist/esm/effect/handler/rpc.mjs +22 -0
- package/dist/esm/effect/handler/types.mjs +5 -0
- package/dist/esm/effect/handler.mjs +13 -0
- package/dist/esm/effect/index.mjs +7 -0
- package/dist/esm/effect/module.mjs +80 -0
- package/dist/esm/effect/operation-context.mjs +75 -0
- package/dist/esm/effect/rpcSerialization.mjs +16 -0
- package/dist/esm/effect-client/batch.mjs +35 -0
- package/dist/esm/effect-client/envelope.mjs +104 -0
- package/dist/esm/effect-client/guards.mjs +3 -0
- package/dist/esm/effect-client/index.mjs +110 -0
- package/dist/esm/effect-client/request-context.mjs +23 -0
- package/dist/esm/effect-client/request.mjs +28 -0
- package/dist/esm/effect-client/runtime.mjs +62 -0
- package/dist/esm/effect-client/types.mjs +0 -0
- package/dist/esm/headers.mjs +8 -0
- package/dist/esm/index.mjs +4 -0
- package/dist/esm/rslib-runtime.mjs +18 -0
- package/dist/esm-node/data-platform/batch/protocol.mjs +72 -0
- package/dist/esm-node/data-platform/batch/queue.mjs +583 -0
- package/dist/esm-node/data-platform/batch/request.mjs +69 -0
- package/dist/esm-node/data-platform/batch/response.mjs +25 -0
- package/dist/esm-node/data-platform/batch/telemetry.mjs +28 -0
- package/dist/esm-node/data-platform/batch/url.mjs +26 -0
- package/dist/esm-node/data-platform/batch-transport.mjs +13 -0
- package/dist/esm-node/data-platform/codec.mjs +91 -0
- package/dist/esm-node/data-platform/index.mjs +8 -0
- package/dist/esm-node/data-platform/invalidation.mjs +21 -0
- package/dist/esm-node/data-platform/trace.mjs +30 -0
- package/dist/esm-node/data-platform/types.mjs +5 -0
- package/dist/esm-node/data-platform/validation.mjs +129 -0
- package/dist/esm-node/effect/context.mjs +13 -0
- package/dist/esm-node/effect/dispatch.mjs +64 -0
- package/dist/esm-node/effect/edge-context.mjs +15 -0
- package/dist/esm-node/effect/edge-dispatcher.mjs +116 -0
- package/dist/esm-node/effect/edge.mjs +2 -0
- package/dist/esm-node/effect/endpoint-contracts.mjs +69 -0
- package/dist/esm-node/effect/entry-shape-registry.mjs +18 -0
- package/dist/esm-node/effect/entry-shape.mjs +63 -0
- package/dist/esm-node/effect/handler/batch-handler.mjs +233 -0
- package/dist/esm-node/effect/handler/batch.mjs +106 -0
- package/dist/esm-node/effect/handler/definition.mjs +59 -0
- package/dist/esm-node/effect/handler/envelope.mjs +64 -0
- package/dist/esm-node/effect/handler/http.mjs +70 -0
- package/dist/esm-node/effect/handler/openapi.mjs +21 -0
- package/dist/esm-node/effect/handler/routing.mjs +51 -0
- package/dist/esm-node/effect/handler/rpc.mjs +23 -0
- package/dist/esm-node/effect/handler/types.mjs +6 -0
- package/dist/esm-node/effect/handler.mjs +14 -0
- package/dist/esm-node/effect/index.mjs +8 -0
- package/dist/esm-node/effect/module.mjs +81 -0
- package/dist/esm-node/effect/operation-context.mjs +76 -0
- package/dist/esm-node/effect/rpcSerialization.mjs +17 -0
- package/dist/esm-node/effect-client/batch.mjs +36 -0
- package/dist/esm-node/effect-client/envelope.mjs +105 -0
- package/dist/esm-node/effect-client/guards.mjs +4 -0
- package/dist/esm-node/effect-client/index.mjs +111 -0
- package/dist/esm-node/effect-client/request-context.mjs +24 -0
- package/dist/esm-node/effect-client/request.mjs +29 -0
- package/dist/esm-node/effect-client/runtime.mjs +63 -0
- package/dist/esm-node/effect-client/types.mjs +1 -0
- package/dist/esm-node/headers.mjs +9 -0
- package/dist/esm-node/index.mjs +5 -0
- package/dist/esm-node/rslib-runtime.mjs +19 -0
- package/dist/types/data-platform/batch/protocol.d.ts +9 -0
- package/dist/types/data-platform/batch/queue.d.ts +47 -0
- package/dist/types/data-platform/batch/request.d.ts +19 -0
- package/dist/types/data-platform/batch/response.d.ts +3 -0
- package/dist/types/data-platform/batch/telemetry.d.ts +4 -0
- package/dist/types/data-platform/batch/url.d.ts +4 -0
- package/dist/types/data-platform/batch-transport.d.ts +3 -0
- package/dist/types/data-platform/codec.d.ts +17 -0
- package/dist/types/data-platform/index.d.ts +8 -0
- package/dist/types/data-platform/invalidation.d.ts +8 -0
- package/dist/types/data-platform/trace.d.ts +4 -0
- package/dist/types/data-platform/types.d.ts +151 -0
- package/dist/types/data-platform/validation.d.ts +29 -0
- package/dist/types/effect/context.d.ts +5 -0
- package/dist/types/effect/dispatch.d.ts +13 -0
- package/dist/types/effect/edge-context.d.ts +5 -0
- package/dist/types/effect/edge-dispatcher.d.ts +37 -0
- package/dist/types/effect/edge.d.ts +1 -0
- package/dist/types/effect/endpoint-contracts.d.ts +67 -0
- package/dist/types/effect/entry-shape.d.ts +32 -0
- package/dist/types/effect/handler/batch-handler.d.ts +11 -0
- package/dist/types/effect/handler/batch.d.ts +15 -0
- package/dist/types/effect/handler/definition.d.ts +7 -0
- package/dist/types/effect/handler/envelope.d.ts +13 -0
- package/dist/types/effect/handler/http.d.ts +15 -0
- package/dist/types/effect/handler/openapi.d.ts +4 -0
- package/dist/types/effect/handler/routing.d.ts +5 -0
- package/dist/types/effect/handler/rpc.d.ts +7 -0
- package/dist/types/effect/handler/types.d.ts +153 -0
- package/dist/types/effect/handler.d.ts +13 -0
- package/dist/types/effect/index.d.ts +6 -0
- package/dist/types/effect/module.d.ts +36 -0
- package/dist/types/effect/operation-context.d.ts +15 -0
- package/dist/types/effect/rpcSerialization.d.ts +3 -0
- package/dist/types/effect-client/batch.d.ts +2 -0
- package/dist/types/effect-client/envelope.d.ts +15 -0
- package/dist/types/effect-client/guards.d.ts +2 -0
- package/dist/types/effect-client/index.d.ts +58 -0
- package/dist/types/effect-client/request-context.d.ts +3 -0
- package/dist/types/effect-client/request.d.ts +1 -0
- package/dist/types/effect-client/runtime.d.ts +5 -0
- package/dist/types/effect-client/types.d.ts +68 -0
- package/dist/types/headers.d.ts +1 -0
- package/dist/types/index.d.ts +3 -0
- package/package.json +164 -0
- package/src/data-platform/batch/protocol.ts +112 -0
- package/src/data-platform/batch/queue.ts +876 -0
- package/src/data-platform/batch/request.ts +130 -0
- package/src/data-platform/batch/response.ts +74 -0
- package/src/data-platform/batch/telemetry.ts +47 -0
- package/src/data-platform/batch/url.ts +54 -0
- package/src/data-platform/batch-transport.ts +22 -0
- package/src/data-platform/codec.ts +145 -0
- package/src/data-platform/index.ts +63 -0
- package/src/data-platform/invalidation.ts +57 -0
- package/src/data-platform/trace.ts +49 -0
- package/src/data-platform/types.ts +186 -0
- package/src/data-platform/validation.ts +303 -0
- package/src/effect/context.ts +37 -0
- package/src/effect/dispatch.ts +132 -0
- package/src/effect/edge-context.ts +51 -0
- package/src/effect/edge-dispatcher.ts +310 -0
- package/src/effect/edge.ts +1 -0
- package/src/effect/endpoint-contracts.ts +191 -0
- package/src/effect/entry-shape-registry.cjs +16 -0
- package/src/effect/entry-shape-registry.d.cts +6 -0
- package/src/effect/entry-shape.ts +155 -0
- package/src/effect/handler/batch-handler.ts +475 -0
- package/src/effect/handler/batch.ts +182 -0
- package/src/effect/handler/definition.ts +121 -0
- package/src/effect/handler/envelope.ts +133 -0
- package/src/effect/handler/http.ts +133 -0
- package/src/effect/handler/openapi.ts +45 -0
- package/src/effect/handler/routing.ts +91 -0
- package/src/effect/handler/rpc.ts +45 -0
- package/src/effect/handler/types.ts +255 -0
- package/src/effect/handler.ts +33 -0
- package/src/effect/index.ts +22 -0
- package/src/effect/module.ts +186 -0
- package/src/effect/operation-context.ts +144 -0
- package/src/effect/rpcSerialization.ts +20 -0
- package/src/effect-client/batch.ts +50 -0
- package/src/effect-client/envelope.ts +238 -0
- package/src/effect-client/guards.ts +6 -0
- package/src/effect-client/index.ts +325 -0
- package/src/effect-client/request-context.ts +54 -0
- package/src/effect-client/request.ts +51 -0
- package/src/effect-client/runtime.ts +122 -0
- package/src/effect-client/types.ts +84 -0
- package/src/headers.ts +7 -0
- package/src/index.ts +3 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// @effect-diagnostics processEnv:off strictBooleanExpressions:off
|
|
2
|
+
import { createDataBatchTransport } from '../data-platform';
|
|
3
|
+
import { resolveOrigin } from './envelope';
|
|
4
|
+
import type {
|
|
5
|
+
EffectRequestRuntime,
|
|
6
|
+
GeneratedEffectClientConfig,
|
|
7
|
+
} from './types';
|
|
8
|
+
|
|
9
|
+
const resolveRuntimeFetch = (): typeof fetch | undefined =>
|
|
10
|
+
typeof fetch === 'function' ? fetch.bind(globalThis) : undefined;
|
|
11
|
+
|
|
12
|
+
export const configureGeneratedEffectClientRuntime = (
|
|
13
|
+
config: GeneratedEffectClientConfig,
|
|
14
|
+
configureRequest: EffectRequestRuntime['configure'] | undefined,
|
|
15
|
+
): void => {
|
|
16
|
+
const defaultOrigin = config.defaultOrigin;
|
|
17
|
+
|
|
18
|
+
if (config.requestId && configureRequest) {
|
|
19
|
+
const configurePayload: Record<string, unknown> = {
|
|
20
|
+
requestId: config.requestId,
|
|
21
|
+
requireEnvelope: true,
|
|
22
|
+
identityBinding: {
|
|
23
|
+
enabled: true,
|
|
24
|
+
strict: true,
|
|
25
|
+
},
|
|
26
|
+
operationContract: {
|
|
27
|
+
enabled: true,
|
|
28
|
+
strict: true,
|
|
29
|
+
requireSchemaHash: true,
|
|
30
|
+
requireOperationVersion: true,
|
|
31
|
+
},
|
|
32
|
+
setDomain: () => resolveOrigin(defaultOrigin),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const runtimeFetch = resolveRuntimeFetch();
|
|
36
|
+
if (config.batch.enabled !== false && runtimeFetch) {
|
|
37
|
+
configurePayload.request = createDataBatchTransport({
|
|
38
|
+
fetch: runtimeFetch,
|
|
39
|
+
endpoint: config.batch.endpoint,
|
|
40
|
+
flushIntervalMs: config.batch.flushIntervalMs,
|
|
41
|
+
maxBatchSize: config.batch.maxBatchSize,
|
|
42
|
+
maxBatchBytes: config.batch.maxBatchBytes,
|
|
43
|
+
requestTimeoutMs: config.batch.requestTimeoutMs,
|
|
44
|
+
allowedMethods: config.batch.allowedMethods,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
configureRequest(configurePayload);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// @effect-diagnostics processEnv:off strictBooleanExpressions:off
|
|
2
|
+
import {
|
|
3
|
+
createRequestEnvelope,
|
|
4
|
+
DEFAULT_DATA_BATCH_HEADER,
|
|
5
|
+
DEFAULT_DATA_ENVELOPE_HEADER,
|
|
6
|
+
encodeRequestEnvelopeHeader,
|
|
7
|
+
type RequestEnvelope,
|
|
8
|
+
type SelectionPlan,
|
|
9
|
+
type TraceContext,
|
|
10
|
+
} from '../data-platform';
|
|
11
|
+
import { isRecord, stringOrUndefined } from './guards';
|
|
12
|
+
import { normalizeRequest } from './request';
|
|
13
|
+
import { applyRequestContext } from './request-context';
|
|
14
|
+
import type {
|
|
15
|
+
EffectOperationDescriptor,
|
|
16
|
+
EffectRequestContext,
|
|
17
|
+
EffectRequestContextInput,
|
|
18
|
+
GeneratedEffectClientConfig,
|
|
19
|
+
GeneratedEffectEndpoint,
|
|
20
|
+
} from './types';
|
|
21
|
+
|
|
22
|
+
const DATA_REQUEST_MODES = new Set([
|
|
23
|
+
'cache-first',
|
|
24
|
+
'stale-while-revalidate',
|
|
25
|
+
'network-only',
|
|
26
|
+
]);
|
|
27
|
+
const DATA_MUTATION_MODES = new Set([
|
|
28
|
+
'optimistic',
|
|
29
|
+
'pessimistic',
|
|
30
|
+
'fire-and-forget',
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
const isDataRequestMode = (value: unknown) =>
|
|
34
|
+
typeof value === 'string' && DATA_REQUEST_MODES.has(value);
|
|
35
|
+
|
|
36
|
+
const isDataMutationMode = (value: unknown) =>
|
|
37
|
+
typeof value === 'string' && DATA_MUTATION_MODES.has(value);
|
|
38
|
+
|
|
39
|
+
const normalizeOrigin = (value: unknown): string | undefined => {
|
|
40
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
return new URL(value).origin;
|
|
45
|
+
} catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const resolveRuntimeFetch = (): typeof fetch | undefined =>
|
|
51
|
+
typeof fetch === 'function' ? fetch.bind(globalThis) : undefined;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the current page origin with a configurable fallback. Single
|
|
55
|
+
* implementation shared by the configure() bootstrap and the data envelope
|
|
56
|
+
* (the generated template previously duplicated data-platform's
|
|
57
|
+
* `resolveRuntimeOrigin` with a different fallback).
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
export const resolveOrigin = (defaultOrigin: string): string => {
|
|
61
|
+
if (
|
|
62
|
+
typeof window !== 'undefined' &&
|
|
63
|
+
window.location &&
|
|
64
|
+
typeof window.location.origin === 'string' &&
|
|
65
|
+
window.location.origin
|
|
66
|
+
) {
|
|
67
|
+
return window.location.origin;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const globalLocation = (
|
|
71
|
+
globalThis as { location?: { origin?: unknown } } | undefined
|
|
72
|
+
)?.location;
|
|
73
|
+
if (
|
|
74
|
+
globalLocation &&
|
|
75
|
+
typeof globalLocation.origin === 'string' &&
|
|
76
|
+
globalLocation.origin
|
|
77
|
+
) {
|
|
78
|
+
return globalLocation.origin;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return defaultOrigin;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const resolveTargetOrigin = (
|
|
85
|
+
dataPlatform: Record<string, unknown>,
|
|
86
|
+
defaultOrigin: string,
|
|
87
|
+
): string => {
|
|
88
|
+
const explicitTargetOrigin =
|
|
89
|
+
stringOrUndefined(dataPlatform.targetOrigin) ||
|
|
90
|
+
stringOrUndefined(dataPlatform.endpointOrigin);
|
|
91
|
+
if (explicitTargetOrigin) {
|
|
92
|
+
return explicitTargetOrigin;
|
|
93
|
+
}
|
|
94
|
+
return defaultOrigin;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const shouldAttachEnvelopeHeader = (
|
|
98
|
+
dataPlatform: Record<string, unknown>,
|
|
99
|
+
defaultOrigin: string,
|
|
100
|
+
): boolean => {
|
|
101
|
+
if (dataPlatform.allowCrossOriginEnvelope === true) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
const currentOrigin = normalizeOrigin(resolveOrigin(defaultOrigin));
|
|
105
|
+
const targetOrigin = normalizeOrigin(
|
|
106
|
+
resolveTargetOrigin(dataPlatform, defaultOrigin),
|
|
107
|
+
);
|
|
108
|
+
if (!currentOrigin || !targetOrigin) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return currentOrigin === targetOrigin;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const toEnvelopeInput = (
|
|
115
|
+
normalizedRequest: Record<string, unknown>,
|
|
116
|
+
): Record<string, unknown> => {
|
|
117
|
+
const payload: Record<string, unknown> = {};
|
|
118
|
+
if (isRecord(normalizedRequest.params)) {
|
|
119
|
+
payload.path = normalizedRequest.params;
|
|
120
|
+
}
|
|
121
|
+
if (isRecord(normalizedRequest.query)) {
|
|
122
|
+
payload.query = normalizedRequest.query;
|
|
123
|
+
}
|
|
124
|
+
if ('data' in normalizedRequest && normalizedRequest.data !== undefined) {
|
|
125
|
+
payload.data = normalizedRequest.data;
|
|
126
|
+
}
|
|
127
|
+
if ('body' in normalizedRequest && normalizedRequest.body !== undefined) {
|
|
128
|
+
payload.body = normalizedRequest.body;
|
|
129
|
+
}
|
|
130
|
+
if (
|
|
131
|
+
typeof FormData !== 'undefined' &&
|
|
132
|
+
normalizedRequest.formData instanceof FormData
|
|
133
|
+
) {
|
|
134
|
+
payload.formData = Array.from(normalizedRequest.formData.entries()).map(
|
|
135
|
+
([key, value]) => [key, String(value)],
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (
|
|
139
|
+
typeof URLSearchParams !== 'undefined' &&
|
|
140
|
+
normalizedRequest.formUrlencoded instanceof URLSearchParams
|
|
141
|
+
) {
|
|
142
|
+
payload.formUrlencoded = normalizedRequest.formUrlencoded.toString();
|
|
143
|
+
}
|
|
144
|
+
return payload;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const prepareEffectRequest = (options: {
|
|
148
|
+
endpoint: GeneratedEffectEndpoint;
|
|
149
|
+
operation: EffectOperationDescriptor;
|
|
150
|
+
request: unknown;
|
|
151
|
+
config: GeneratedEffectClientConfig;
|
|
152
|
+
createEffectRequestContext: (
|
|
153
|
+
requestContext: EffectRequestContextInput,
|
|
154
|
+
) => EffectRequestContext;
|
|
155
|
+
}): Record<string, unknown> => {
|
|
156
|
+
const { endpoint, operation, request, config, createEffectRequestContext } =
|
|
157
|
+
options;
|
|
158
|
+
const defaultOrigin = config.defaultOrigin;
|
|
159
|
+
const normalizedRequest = applyRequestContext(
|
|
160
|
+
normalizeRequest(endpoint.method, request),
|
|
161
|
+
request,
|
|
162
|
+
createEffectRequestContext,
|
|
163
|
+
);
|
|
164
|
+
const dataPlatform =
|
|
165
|
+
isRecord(request) && isRecord(request.dataPlatform)
|
|
166
|
+
? request.dataPlatform
|
|
167
|
+
: {};
|
|
168
|
+
const strictEnvelope =
|
|
169
|
+
dataPlatform.requireEnvelope === true || dataPlatform.strict === true;
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
!strictEnvelope &&
|
|
173
|
+
!shouldAttachEnvelopeHeader(dataPlatform, defaultOrigin)
|
|
174
|
+
) {
|
|
175
|
+
return normalizedRequest;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const namespace =
|
|
180
|
+
stringOrUndefined(dataPlatform.appNamespace) || config.appNamespace;
|
|
181
|
+
const origin =
|
|
182
|
+
stringOrUndefined(dataPlatform.origin) || resolveOrigin(defaultOrigin);
|
|
183
|
+
const envelope: RequestEnvelope = createRequestEnvelope({
|
|
184
|
+
operation: {
|
|
185
|
+
...operation,
|
|
186
|
+
appNamespace: namespace,
|
|
187
|
+
},
|
|
188
|
+
scope: {
|
|
189
|
+
appNamespace: namespace,
|
|
190
|
+
origin,
|
|
191
|
+
tenantId: stringOrUndefined(dataPlatform.tenantId),
|
|
192
|
+
userId: stringOrUndefined(dataPlatform.userId),
|
|
193
|
+
sessionId: stringOrUndefined(dataPlatform.sessionId),
|
|
194
|
+
},
|
|
195
|
+
requestInput: {
|
|
196
|
+
method: endpoint.method,
|
|
197
|
+
routePath: endpoint.routePath,
|
|
198
|
+
payload: toEnvelopeInput(normalizedRequest),
|
|
199
|
+
},
|
|
200
|
+
requestMode: isDataRequestMode(dataPlatform.requestMode)
|
|
201
|
+
? (dataPlatform.requestMode as RequestEnvelope['requestMode'])
|
|
202
|
+
: undefined,
|
|
203
|
+
mutationMode: isDataMutationMode(dataPlatform.mutationMode)
|
|
204
|
+
? (dataPlatform.mutationMode as RequestEnvelope['mutationMode'])
|
|
205
|
+
: undefined,
|
|
206
|
+
selectionPlan: isRecord(dataPlatform.selectionPlan)
|
|
207
|
+
? (dataPlatform.selectionPlan as SelectionPlan)
|
|
208
|
+
: undefined,
|
|
209
|
+
traceContext: isRecord(dataPlatform.traceContext)
|
|
210
|
+
? (dataPlatform.traceContext as unknown as TraceContext)
|
|
211
|
+
: undefined,
|
|
212
|
+
requireTraceContext: dataPlatform.requireTraceContext === true,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const headerName =
|
|
216
|
+
stringOrUndefined(dataPlatform.envelopeHeader) ||
|
|
217
|
+
DEFAULT_DATA_ENVELOPE_HEADER;
|
|
218
|
+
const headers = isRecord(normalizedRequest.headers)
|
|
219
|
+
? { ...normalizedRequest.headers }
|
|
220
|
+
: {};
|
|
221
|
+
|
|
222
|
+
if (dataPlatform.batch === false) {
|
|
223
|
+
headers[DEFAULT_DATA_BATCH_HEADER] = 'off';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
headers[headerName] = encodeRequestEnvelopeHeader(envelope);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
...normalizedRequest,
|
|
230
|
+
headers,
|
|
231
|
+
};
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (strictEnvelope) {
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
return normalizedRequest;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @effect-diagnostics processEnv:off strictBooleanExpressions:off
|
|
2
|
+
export const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
3
|
+
typeof value === 'object' && value !== null;
|
|
4
|
+
|
|
5
|
+
export const stringOrUndefined = (value: unknown): string | undefined =>
|
|
6
|
+
typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// @effect-diagnostics anyUnknownInErrorContext:off asyncFunction:off newPromise:off strictEffectProvide:off
|
|
2
|
+
import {
|
|
3
|
+
createRequestContextHeaders,
|
|
4
|
+
type RequestContextInput,
|
|
5
|
+
} from '@modern-js/create-request';
|
|
6
|
+
import * as Data from 'effect/Data';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import * as Exit from 'effect/Exit';
|
|
9
|
+
import * as Layer from 'effect/Layer';
|
|
10
|
+
import * as ManagedRuntime from 'effect/ManagedRuntime';
|
|
11
|
+
import * as Scope from 'effect/Scope';
|
|
12
|
+
import {
|
|
13
|
+
FetchHttpClient,
|
|
14
|
+
HttpClient,
|
|
15
|
+
HttpClientRequest,
|
|
16
|
+
} from 'effect/unstable/http';
|
|
17
|
+
import {
|
|
18
|
+
HttpApi,
|
|
19
|
+
HttpApiClient,
|
|
20
|
+
HttpApiEndpoint,
|
|
21
|
+
HttpApiGroup,
|
|
22
|
+
HttpApiSchema,
|
|
23
|
+
} from 'effect/unstable/httpapi';
|
|
24
|
+
import {
|
|
25
|
+
Rpc,
|
|
26
|
+
RpcClient,
|
|
27
|
+
RpcGroup,
|
|
28
|
+
RpcSchema,
|
|
29
|
+
RpcSerialization,
|
|
30
|
+
} from 'effect/unstable/rpc';
|
|
31
|
+
|
|
32
|
+
import { getRpcSerializationLayer } from '../effect/rpcSerialization';
|
|
33
|
+
|
|
34
|
+
export * as Effect from 'effect/Effect';
|
|
35
|
+
export * as Layer from 'effect/Layer';
|
|
36
|
+
export * as Schema from 'effect/Schema';
|
|
37
|
+
export * as HttpClientError from 'effect/unstable/http/HttpClientError';
|
|
38
|
+
export {
|
|
39
|
+
HttpApi,
|
|
40
|
+
HttpApiClient,
|
|
41
|
+
HttpApiEndpoint,
|
|
42
|
+
HttpApiGroup,
|
|
43
|
+
HttpApiSchema,
|
|
44
|
+
Rpc,
|
|
45
|
+
RpcClient,
|
|
46
|
+
RpcGroup,
|
|
47
|
+
RpcSchema,
|
|
48
|
+
RpcSerialization,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type EffectHttpApiClientOptions = {
|
|
52
|
+
baseUrl?: URL | string;
|
|
53
|
+
requestContext?: RequestContextInput;
|
|
54
|
+
transformClient?: (client: HttpClient.HttpClient) => HttpClient.HttpClient;
|
|
55
|
+
transformResponse?:
|
|
56
|
+
| ((
|
|
57
|
+
effect: Effect.Effect<unknown, unknown, unknown>,
|
|
58
|
+
) => Effect.Effect<unknown, unknown, unknown>)
|
|
59
|
+
| undefined;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type Nullish = null | undefined;
|
|
63
|
+
type NonNullish<T> = Exclude<T, Nullish>;
|
|
64
|
+
type PreserveNullish<T, Next> =
|
|
65
|
+
Extract<T, Nullish> extends never ? Next : Next | Extract<T, Nullish>;
|
|
66
|
+
|
|
67
|
+
type EffectViewObjectSelection<T> =
|
|
68
|
+
NonNullish<T> extends Record<string, unknown>
|
|
69
|
+
? EffectViewSelection<NonNullish<T>>
|
|
70
|
+
: never;
|
|
71
|
+
|
|
72
|
+
type EffectViewSelectionValue<T> =
|
|
73
|
+
NonNullish<T> extends readonly (infer Item)[]
|
|
74
|
+
? true | EffectViewObjectSelection<Item>
|
|
75
|
+
: NonNullish<T> extends Record<string, unknown>
|
|
76
|
+
? true | EffectViewSelection<NonNullish<T>>
|
|
77
|
+
: true;
|
|
78
|
+
|
|
79
|
+
type EffectMaskedValue<T, Selection> = Selection extends true
|
|
80
|
+
? T
|
|
81
|
+
: NonNullish<T> extends readonly (infer Item)[]
|
|
82
|
+
? PreserveNullish<T, Array<EffectMaskedValue<Item, Selection>>>
|
|
83
|
+
: NonNullish<T> extends Record<string, unknown>
|
|
84
|
+
? PreserveNullish<T, EffectViewData<NonNullish<T>, Selection>>
|
|
85
|
+
: T;
|
|
86
|
+
|
|
87
|
+
export type EffectViewSelection<T> = {
|
|
88
|
+
[K in keyof T]?: EffectViewSelectionValue<T[K]>;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type EffectViewData<T, Selection> = Selection extends true
|
|
92
|
+
? T
|
|
93
|
+
: Selection extends Record<string, unknown>
|
|
94
|
+
? {
|
|
95
|
+
[K in keyof Selection & keyof T]: EffectMaskedValue<T[K], Selection[K]>;
|
|
96
|
+
}
|
|
97
|
+
: T;
|
|
98
|
+
|
|
99
|
+
export type EffectRpcSerialization =
|
|
100
|
+
| 'json'
|
|
101
|
+
| 'ndjson'
|
|
102
|
+
| 'jsonRpc'
|
|
103
|
+
| 'ndJsonRpc'
|
|
104
|
+
| 'msgPack';
|
|
105
|
+
|
|
106
|
+
export class EffectRpcClientError extends Data.TaggedError(
|
|
107
|
+
'EffectRpcClientError',
|
|
108
|
+
)<{
|
|
109
|
+
readonly cause: unknown;
|
|
110
|
+
}> {}
|
|
111
|
+
|
|
112
|
+
type EffectRpcMiddlewareLayerOption<Rpcs extends Rpc.Any> = [
|
|
113
|
+
Rpc.MiddlewareClient<Rpcs>,
|
|
114
|
+
] extends [never]
|
|
115
|
+
? {
|
|
116
|
+
middlewareLayer?: Layer.Layer<Rpc.MiddlewareClient<Rpcs>, never, never>;
|
|
117
|
+
}
|
|
118
|
+
: {
|
|
119
|
+
middlewareLayer: Layer.Layer<Rpc.MiddlewareClient<Rpcs>, unknown, never>;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export type EffectRpcClientOptions<
|
|
123
|
+
Rpcs extends Rpc.Any,
|
|
124
|
+
Flatten extends boolean = false,
|
|
125
|
+
> = EffectRpcMiddlewareLayerOption<Rpcs> & {
|
|
126
|
+
url: string;
|
|
127
|
+
flatten?: Flatten;
|
|
128
|
+
serialization?: EffectRpcSerialization;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export type EffectRpcClientHandle<
|
|
132
|
+
Rpcs extends Rpc.Any,
|
|
133
|
+
Flatten extends boolean = false,
|
|
134
|
+
> = (Flatten extends true
|
|
135
|
+
? RpcClient.RpcClient.Flat<Rpcs, unknown>
|
|
136
|
+
: RpcClient.RpcClient<Rpcs, unknown>) & {
|
|
137
|
+
dispose: () => Promise<void>;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
141
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function applySelection(value: unknown, selection: unknown): unknown {
|
|
145
|
+
if (selection === true || selection === undefined) {
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (Array.isArray(value)) {
|
|
150
|
+
return value.map(item => applySelection(item, selection));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!isRecord(value) || !isRecord(selection)) {
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const masked: Record<string, unknown> = {};
|
|
158
|
+
for (const key of Object.keys(selection)) {
|
|
159
|
+
const selected = selection[key];
|
|
160
|
+
if (
|
|
161
|
+
selected === undefined ||
|
|
162
|
+
!(key in value) ||
|
|
163
|
+
(selected !== true && !isRecord(selected))
|
|
164
|
+
) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
masked[key] = applySelection(value[key], selected);
|
|
168
|
+
}
|
|
169
|
+
return masked;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function view<T>() {
|
|
173
|
+
return <const Selection extends EffectViewSelection<T>>(
|
|
174
|
+
selection: Selection,
|
|
175
|
+
) => selection;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function mask<T, const Selection extends EffectViewSelection<T>>(
|
|
179
|
+
value: T,
|
|
180
|
+
selection: Selection,
|
|
181
|
+
): EffectViewData<T, Selection> {
|
|
182
|
+
return applySelection(value, selection) as EffectViewData<T, Selection>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function runEffectView<
|
|
186
|
+
T,
|
|
187
|
+
const Selection extends EffectViewSelection<T>,
|
|
188
|
+
>(
|
|
189
|
+
request: PromiseLike<T>,
|
|
190
|
+
selection: Selection,
|
|
191
|
+
): Promise<EffectViewData<T, Selection>> {
|
|
192
|
+
return new Promise((resolve, reject) => {
|
|
193
|
+
request.then(
|
|
194
|
+
value => resolve(mask(value, selection)),
|
|
195
|
+
reason => reject(reason),
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function makeEffectHttpApiClient<
|
|
201
|
+
ApiId extends string,
|
|
202
|
+
Groups extends HttpApiGroup.Constraint,
|
|
203
|
+
>(
|
|
204
|
+
api: HttpApi.HttpApi<ApiId, Groups>,
|
|
205
|
+
options?: EffectHttpApiClientOptions,
|
|
206
|
+
): Effect.Effect<
|
|
207
|
+
HttpApiClient.Client<Groups, never, never>,
|
|
208
|
+
never,
|
|
209
|
+
HttpApiGroup.MiddlewareClient<Groups>
|
|
210
|
+
> {
|
|
211
|
+
const requestContextHeaders = createRequestContextHeaders(
|
|
212
|
+
options?.requestContext,
|
|
213
|
+
);
|
|
214
|
+
const transformClient = (client: HttpClient.HttpClient) => {
|
|
215
|
+
const contextClient =
|
|
216
|
+
Object.keys(requestContextHeaders).length === 0
|
|
217
|
+
? client
|
|
218
|
+
: client.pipe(
|
|
219
|
+
HttpClient.mapRequest(request => {
|
|
220
|
+
let nextRequest = request;
|
|
221
|
+
for (const [header, value] of Object.entries(
|
|
222
|
+
requestContextHeaders,
|
|
223
|
+
)) {
|
|
224
|
+
if (nextRequest.headers[header.toLowerCase()] === undefined) {
|
|
225
|
+
nextRequest = HttpClientRequest.setHeader(
|
|
226
|
+
nextRequest,
|
|
227
|
+
header,
|
|
228
|
+
value,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return nextRequest;
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
return typeof options?.transformClient === 'function'
|
|
237
|
+
? options.transformClient(contextClient)
|
|
238
|
+
: contextClient;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return HttpApiClient.make(api, {
|
|
242
|
+
baseUrl: options?.baseUrl,
|
|
243
|
+
transformClient,
|
|
244
|
+
transformResponse: options?.transformResponse,
|
|
245
|
+
}).pipe(Effect.provide(FetchHttpClient.layer));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function makeEffectRpcClient<
|
|
249
|
+
Rpcs extends Rpc.Any,
|
|
250
|
+
const Flatten extends boolean = false,
|
|
251
|
+
>(
|
|
252
|
+
group: RpcGroup.RpcGroup<Rpcs>,
|
|
253
|
+
options: EffectRpcClientOptions<Rpcs, Flatten>,
|
|
254
|
+
) {
|
|
255
|
+
const protocolLayer = Layer.provide(
|
|
256
|
+
RpcClient.layerProtocolHttp({
|
|
257
|
+
url: options.url,
|
|
258
|
+
}),
|
|
259
|
+
Layer.mergeAll(
|
|
260
|
+
getRpcSerializationLayer(options.serialization),
|
|
261
|
+
FetchHttpClient.layer,
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
const middlewareLayer = options.middlewareLayer ?? Layer.empty;
|
|
265
|
+
const runtimeLayer = Layer.mergeAll(protocolLayer, middlewareLayer);
|
|
266
|
+
|
|
267
|
+
return Effect.tryPromise({
|
|
268
|
+
try: async () => {
|
|
269
|
+
const runtime = ManagedRuntime.make(runtimeLayer);
|
|
270
|
+
let scope: Scope.Closeable | undefined;
|
|
271
|
+
try {
|
|
272
|
+
const clientScope = await runtime.runPromise(Scope.make());
|
|
273
|
+
scope = clientScope;
|
|
274
|
+
const client = await runtime.runPromise(
|
|
275
|
+
RpcClient.make(group, {
|
|
276
|
+
flatten: options.flatten,
|
|
277
|
+
}).pipe(Effect.provideService(Scope.Scope, clientScope)),
|
|
278
|
+
);
|
|
279
|
+
let disposePromise: Promise<void> | undefined;
|
|
280
|
+
const clientWithDispose: EffectRpcClientHandle<Rpcs, Flatten> =
|
|
281
|
+
Object.assign(client, {
|
|
282
|
+
dispose: () => {
|
|
283
|
+
disposePromise ??= (async () => {
|
|
284
|
+
let scopeCloseError: unknown;
|
|
285
|
+
try {
|
|
286
|
+
await runtime.runPromise(Scope.close(clientScope, Exit.void));
|
|
287
|
+
} catch (error) {
|
|
288
|
+
scopeCloseError = error;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
await runtime.dispose();
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (scopeCloseError === undefined) {
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (scopeCloseError !== undefined) {
|
|
298
|
+
throw scopeCloseError;
|
|
299
|
+
}
|
|
300
|
+
})();
|
|
301
|
+
return disposePromise;
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
return clientWithDispose;
|
|
305
|
+
} catch (error) {
|
|
306
|
+
if (scope !== undefined) {
|
|
307
|
+
try {
|
|
308
|
+
await runtime.runPromise(Scope.close(scope, Exit.void));
|
|
309
|
+
} catch {
|
|
310
|
+
// ignore scope close errors and preserve the original construction error
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
await runtime.dispose();
|
|
315
|
+
} catch {
|
|
316
|
+
// ignore disposal errors and preserve the original construction error
|
|
317
|
+
}
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
catch: (error: unknown) => new EffectRpcClientError({ cause: error }),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export const runEffectRequest = Effect.runPromise;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @effect-diagnostics processEnv:off strictBooleanExpressions:off
|
|
2
|
+
import { isRecord } from './guards';
|
|
3
|
+
import type {
|
|
4
|
+
EffectRequestContext,
|
|
5
|
+
EffectRequestContextInput,
|
|
6
|
+
EffectRequestRuntime,
|
|
7
|
+
} from './types';
|
|
8
|
+
|
|
9
|
+
export const createEffectRequestContext = (
|
|
10
|
+
createRequestContextHeaders: EffectRequestRuntime['createRequestContextHeaders'],
|
|
11
|
+
requestContext: EffectRequestContextInput,
|
|
12
|
+
): EffectRequestContext => {
|
|
13
|
+
if (!isRecord(requestContext)) {
|
|
14
|
+
return {} as EffectRequestContext;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const headers = createRequestContextHeaders
|
|
18
|
+
? createRequestContextHeaders(requestContext)
|
|
19
|
+
: {};
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
...requestContext,
|
|
23
|
+
headers,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const applyRequestContext = (
|
|
28
|
+
normalizedRequest: Record<string, unknown>,
|
|
29
|
+
request: unknown,
|
|
30
|
+
createRequestContext: (
|
|
31
|
+
requestContext: EffectRequestContextInput,
|
|
32
|
+
) => EffectRequestContext,
|
|
33
|
+
): Record<string, unknown> => {
|
|
34
|
+
if (!isRecord(request) || !isRecord(request.requestContext)) {
|
|
35
|
+
return normalizedRequest;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const requestContext = createRequestContext(request.requestContext);
|
|
39
|
+
const requestHeaders = isRecord(requestContext.headers)
|
|
40
|
+
? requestContext.headers
|
|
41
|
+
: {};
|
|
42
|
+
|
|
43
|
+
if (Object.keys(requestHeaders).length === 0) {
|
|
44
|
+
return normalizedRequest;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
...normalizedRequest,
|
|
49
|
+
headers: {
|
|
50
|
+
...(isRecord(normalizedRequest.headers) ? normalizedRequest.headers : {}),
|
|
51
|
+
...requestHeaders,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
};
|