@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,582 @@
|
|
|
1
|
+
import { stableStringify } from "../codec.mjs";
|
|
2
|
+
import { DEFAULT_DATA_BATCH_HEADER } from "../types.mjs";
|
|
3
|
+
import { decodeBatchBody, isNullBodyStatus } from "./protocol.mjs";
|
|
4
|
+
import { createBatchId, measureTextBytes, normalizeMethod, shouldBatchRequest, toHeaderRecord, toRequestBody } from "./request.mjs";
|
|
5
|
+
import { isBatchResponsePayload, parseResponseLikeCreateRequest } from "./response.mjs";
|
|
6
|
+
import { emitDataBatchTransportEvent } from "./telemetry.mjs";
|
|
7
|
+
import { normalizeBatchEndpoint, toAbsoluteUrl } from "./url.mjs";
|
|
8
|
+
const SAFE_REPLAY_METHODS = new Set([
|
|
9
|
+
'GET',
|
|
10
|
+
'HEAD'
|
|
11
|
+
]);
|
|
12
|
+
const abortReason = (signal)=>signal.reason ?? new DOMException('The request was aborted', 'AbortError');
|
|
13
|
+
const createRequestLifetime = (sourceSignal, timeoutMs)=>{
|
|
14
|
+
const controller = new AbortController();
|
|
15
|
+
let timedOut = false;
|
|
16
|
+
let timeoutHandle = null;
|
|
17
|
+
const abortFromSource = ()=>{
|
|
18
|
+
if (sourceSignal) controller.abort(abortReason(sourceSignal));
|
|
19
|
+
};
|
|
20
|
+
if (sourceSignal?.aborted) abortFromSource();
|
|
21
|
+
else if (sourceSignal) sourceSignal.addEventListener('abort', abortFromSource, {
|
|
22
|
+
once: true
|
|
23
|
+
});
|
|
24
|
+
if (!controller.signal.aborted && timeoutMs && timeoutMs > 0) timeoutHandle = setTimeout(()=>{
|
|
25
|
+
timedOut = true;
|
|
26
|
+
controller.abort(new DOMException('The batched request timed out', 'TimeoutError'));
|
|
27
|
+
}, timeoutMs);
|
|
28
|
+
return {
|
|
29
|
+
signal: controller.signal,
|
|
30
|
+
get timedOut () {
|
|
31
|
+
return timedOut;
|
|
32
|
+
},
|
|
33
|
+
dispose () {
|
|
34
|
+
if (null !== timeoutHandle) {
|
|
35
|
+
clearTimeout(timeoutHandle);
|
|
36
|
+
timeoutHandle = null;
|
|
37
|
+
}
|
|
38
|
+
sourceSignal?.removeEventListener('abort', abortFromSource);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
const runWithSignal = (signal, operation)=>{
|
|
43
|
+
if (signal.aborted) return Promise.reject(abortReason(signal));
|
|
44
|
+
return new Promise((resolve, reject)=>{
|
|
45
|
+
const onAbort = ()=>{
|
|
46
|
+
signal.removeEventListener('abort', onAbort);
|
|
47
|
+
reject(abortReason(signal));
|
|
48
|
+
};
|
|
49
|
+
signal.addEventListener('abort', onAbort, {
|
|
50
|
+
once: true
|
|
51
|
+
});
|
|
52
|
+
let pending;
|
|
53
|
+
try {
|
|
54
|
+
pending = operation();
|
|
55
|
+
} catch (error) {
|
|
56
|
+
signal.removeEventListener('abort', onAbort);
|
|
57
|
+
reject(error);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
pending.then((value)=>{
|
|
61
|
+
signal.removeEventListener('abort', onAbort);
|
|
62
|
+
resolve(value);
|
|
63
|
+
}, (error)=>{
|
|
64
|
+
signal.removeEventListener('abort', onAbort);
|
|
65
|
+
reject(error);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
class BatchBucketRegistry {
|
|
70
|
+
get size() {
|
|
71
|
+
return this.buckets.size;
|
|
72
|
+
}
|
|
73
|
+
get(bucketKey) {
|
|
74
|
+
return this.buckets.get(bucketKey);
|
|
75
|
+
}
|
|
76
|
+
ensure(bucketKey) {
|
|
77
|
+
const existing = this.buckets.get(bucketKey);
|
|
78
|
+
if (existing) return existing;
|
|
79
|
+
const next = {
|
|
80
|
+
items: [],
|
|
81
|
+
bytes: 0,
|
|
82
|
+
batchId: createBatchId(),
|
|
83
|
+
sentAt: Date.now(),
|
|
84
|
+
timer: null,
|
|
85
|
+
flushing: false
|
|
86
|
+
};
|
|
87
|
+
this.buckets.set(bucketKey, next);
|
|
88
|
+
return next;
|
|
89
|
+
}
|
|
90
|
+
releaseIfIdle(bucketKey, bucket) {
|
|
91
|
+
if (this.buckets.get(bucketKey) !== bucket || bucket.flushing || null !== bucket.timer || bucket.items.length > 0) return false;
|
|
92
|
+
return this.buckets.delete(bucketKey);
|
|
93
|
+
}
|
|
94
|
+
constructor(){
|
|
95
|
+
this.buckets = new Map();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function createBatchTransportQueue({ options, baseFetch, bucketRegistry }) {
|
|
99
|
+
const flushIntervalMs = Math.max(0, options.flushIntervalMs ?? 8);
|
|
100
|
+
const maxBatchSize = Math.max(1, options.maxBatchSize ?? 16);
|
|
101
|
+
const maxBatchBytes = Math.max(1024, options.maxBatchBytes ?? 65536);
|
|
102
|
+
const requestTimeoutMs = options.requestTimeoutMs;
|
|
103
|
+
const allowedMethods = new Set((options.allowedMethods && options.allowedMethods.length > 0 ? options.allowedMethods : [
|
|
104
|
+
'GET'
|
|
105
|
+
]).map((method)=>method.toUpperCase()));
|
|
106
|
+
const onEvent = options.onEvent;
|
|
107
|
+
const buckets = bucketRegistry ?? new BatchBucketRegistry();
|
|
108
|
+
const pendingByKey = new Map();
|
|
109
|
+
const disabledEndpoints = new Set();
|
|
110
|
+
let nextItemId = 0;
|
|
111
|
+
const createPayload = (bucket, items)=>({
|
|
112
|
+
protocolVersion: 2,
|
|
113
|
+
batchId: bucket.batchId,
|
|
114
|
+
sentAt: bucket.sentAt,
|
|
115
|
+
items: items.map((item)=>item.item)
|
|
116
|
+
});
|
|
117
|
+
const measurePayload = (bucket, items)=>measureTextBytes(JSON.stringify(createPayload(bucket, items)));
|
|
118
|
+
const rotatePayloadIdentity = (bucket)=>{
|
|
119
|
+
bucket.batchId = createBatchId();
|
|
120
|
+
bucket.sentAt = Date.now();
|
|
121
|
+
};
|
|
122
|
+
const settleRequest = (request, settlement)=>{
|
|
123
|
+
if ('settled' === request.phase) return false;
|
|
124
|
+
request.phase = 'settled';
|
|
125
|
+
request.lifetime.signal.removeEventListener('abort', request.abortListener);
|
|
126
|
+
request.lifetime.dispose();
|
|
127
|
+
if (pendingByKey.get(request.key) === request) pendingByKey.delete(request.key);
|
|
128
|
+
if ('resolve' === settlement.type) request.resolve(settlement.value);
|
|
129
|
+
else request.reject(settlement.reason);
|
|
130
|
+
return true;
|
|
131
|
+
};
|
|
132
|
+
const removeQueuedRequest = (request)=>{
|
|
133
|
+
const bucket = buckets.get(request.bucketKey);
|
|
134
|
+
if (!bucket) return;
|
|
135
|
+
const index = bucket.items.indexOf(request);
|
|
136
|
+
if (-1 === index) return;
|
|
137
|
+
bucket.items.splice(index, 1);
|
|
138
|
+
bucket.bytes = measurePayload(bucket, bucket.items);
|
|
139
|
+
if (0 === bucket.items.length && bucket.timer) {
|
|
140
|
+
clearTimeout(bucket.timer);
|
|
141
|
+
bucket.timer = null;
|
|
142
|
+
}
|
|
143
|
+
buckets.releaseIfIdle(request.bucketKey, bucket);
|
|
144
|
+
};
|
|
145
|
+
const abortQueuedRequest = (request)=>{
|
|
146
|
+
if ('settled' === request.phase) return;
|
|
147
|
+
if ('queued' === request.phase) removeQueuedRequest(request);
|
|
148
|
+
const abortBatchIfUnused = request.abortBatchIfUnused;
|
|
149
|
+
settleRequest(request, {
|
|
150
|
+
type: 'reject',
|
|
151
|
+
reason: abortReason(request.lifetime.signal)
|
|
152
|
+
});
|
|
153
|
+
abortBatchIfUnused?.();
|
|
154
|
+
};
|
|
155
|
+
const runSingle = (request)=>runWithSignal(request.lifetime.signal, async ()=>{
|
|
156
|
+
const response = await baseFetch(request.requestInput, request.requestInit);
|
|
157
|
+
return parseResponseLikeCreateRequest(response);
|
|
158
|
+
});
|
|
159
|
+
const settleRequests = async (items, runner)=>{
|
|
160
|
+
await Promise.all(items.map(async (item)=>{
|
|
161
|
+
if ('settled' === item.phase) return;
|
|
162
|
+
try {
|
|
163
|
+
const value = await runner(item);
|
|
164
|
+
settleRequest(item, {
|
|
165
|
+
type: 'resolve',
|
|
166
|
+
value
|
|
167
|
+
});
|
|
168
|
+
} catch (error) {
|
|
169
|
+
settleRequest(item, {
|
|
170
|
+
type: 'reject',
|
|
171
|
+
reason: error
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}));
|
|
175
|
+
};
|
|
176
|
+
const runAfterAmbiguousBatch = (request, reason)=>{
|
|
177
|
+
if (!SAFE_REPLAY_METHODS.has(request.item.method)) throw new Error(`Batch result is unknown for ${request.item.method}; automatic replay is disabled (${reason}).`);
|
|
178
|
+
return runSingle(request);
|
|
179
|
+
};
|
|
180
|
+
const settleAmbiguousBatchRequests = (items, reason)=>settleRequests(items, (request)=>runAfterAmbiguousBatch(request, reason));
|
|
181
|
+
const flushBucket = async (bucketKey)=>{
|
|
182
|
+
const bucket = buckets.get(bucketKey);
|
|
183
|
+
if (!bucket || bucket.flushing) return;
|
|
184
|
+
if (bucket.timer) {
|
|
185
|
+
clearTimeout(bucket.timer);
|
|
186
|
+
bucket.timer = null;
|
|
187
|
+
}
|
|
188
|
+
if (0 === bucket.items.length) return void buckets.releaseIfIdle(bucketKey, bucket);
|
|
189
|
+
bucket.flushing = true;
|
|
190
|
+
const items = bucket.items;
|
|
191
|
+
for (const item of items)item.phase = 'flushing';
|
|
192
|
+
const payloadIdentity = {
|
|
193
|
+
batchId: bucket.batchId,
|
|
194
|
+
sentAt: bucket.sentAt
|
|
195
|
+
};
|
|
196
|
+
bucket.items = [];
|
|
197
|
+
bucket.bytes = 0;
|
|
198
|
+
rotatePayloadIdentity(bucket);
|
|
199
|
+
const firstItem = items[0];
|
|
200
|
+
if (!firstItem) {
|
|
201
|
+
bucket.flushing = false;
|
|
202
|
+
buckets.releaseIfIdle(bucketKey, bucket);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const endpoint = firstItem.endpoint;
|
|
206
|
+
if (1 === items.length || disabledEndpoints.has(endpoint)) {
|
|
207
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
208
|
+
type: disabledEndpoints.has(endpoint) ? 'fallback' : 'flush',
|
|
209
|
+
endpoint,
|
|
210
|
+
size: items.length,
|
|
211
|
+
reason: disabledEndpoints.has(endpoint) ? 'batch-disabled' : void 0
|
|
212
|
+
});
|
|
213
|
+
await settleRequests(items, runSingle);
|
|
214
|
+
bucket.flushing = false;
|
|
215
|
+
if (bucket.items.length > 0 && !bucket.timer) flushBucket(bucketKey);
|
|
216
|
+
buckets.releaseIfIdle(bucketKey, bucket);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const payload = createPayload(payloadIdentity, items);
|
|
220
|
+
const batchId = payload.batchId;
|
|
221
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
222
|
+
type: 'flush',
|
|
223
|
+
endpoint,
|
|
224
|
+
batchId,
|
|
225
|
+
size: items.length
|
|
226
|
+
});
|
|
227
|
+
const payloadJson = JSON.stringify(payload);
|
|
228
|
+
const traceparent = items.find((item)=>'string' == typeof item.item.headers?.traceparent)?.item.headers?.traceparent || void 0;
|
|
229
|
+
const authorization = firstItem.authorization;
|
|
230
|
+
const cookie = firstItem.cookie;
|
|
231
|
+
const requestInit = {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
credentials: firstItem.credentials,
|
|
234
|
+
headers: {
|
|
235
|
+
accept: 'application/json, */*;q=0.8',
|
|
236
|
+
'content-type': 'application/json; charset=utf-8',
|
|
237
|
+
[DEFAULT_DATA_BATCH_HEADER]: '2',
|
|
238
|
+
...traceparent ? {
|
|
239
|
+
traceparent
|
|
240
|
+
} : {},
|
|
241
|
+
...'string' == typeof authorization ? {
|
|
242
|
+
authorization
|
|
243
|
+
} : {},
|
|
244
|
+
...'string' == typeof cookie ? {
|
|
245
|
+
cookie
|
|
246
|
+
} : {}
|
|
247
|
+
},
|
|
248
|
+
body: payloadJson
|
|
249
|
+
};
|
|
250
|
+
const controller = new AbortController();
|
|
251
|
+
let abortedBecauseUnused = false;
|
|
252
|
+
const abortBatchIfUnused = ()=>{
|
|
253
|
+
if (!controller.signal.aborted && items.every((item)=>'settled' === item.phase)) {
|
|
254
|
+
abortedBecauseUnused = true;
|
|
255
|
+
controller.abort(items.find((item)=>item.lifetime.timedOut) ? new DOMException('The batch timed out', 'TimeoutError') : new DOMException('Every batched request was aborted', 'AbortError'));
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
for (const item of items)item.abortBatchIfUnused = abortBatchIfUnused;
|
|
259
|
+
requestInit.signal = controller.signal;
|
|
260
|
+
try {
|
|
261
|
+
const response = await runWithSignal(controller.signal, ()=>baseFetch(endpoint, requestInit));
|
|
262
|
+
if (!response.ok) {
|
|
263
|
+
if (404 === response.status || 405 === response.status) {
|
|
264
|
+
disabledEndpoints.add(endpoint);
|
|
265
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
266
|
+
type: 'disable',
|
|
267
|
+
endpoint,
|
|
268
|
+
batchId,
|
|
269
|
+
reason: `batch-endpoint-unavailable-${String(response.status)}`
|
|
270
|
+
});
|
|
271
|
+
} else emitDataBatchTransportEvent(onEvent, {
|
|
272
|
+
type: 'fallback',
|
|
273
|
+
endpoint,
|
|
274
|
+
batchId,
|
|
275
|
+
size: items.length,
|
|
276
|
+
reason: `batch-response-${String(response.status)}`
|
|
277
|
+
});
|
|
278
|
+
await settleAmbiguousBatchRequests(items, `batch-response-${String(response.status)}`);
|
|
279
|
+
bucket.flushing = false;
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const result = await runWithSignal(controller.signal, async ()=>response.json());
|
|
283
|
+
if (!isBatchResponsePayload(result)) {
|
|
284
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
285
|
+
type: 'fallback',
|
|
286
|
+
endpoint,
|
|
287
|
+
batchId,
|
|
288
|
+
size: items.length,
|
|
289
|
+
reason: 'invalid-batch-response'
|
|
290
|
+
});
|
|
291
|
+
await settleAmbiguousBatchRequests(items, 'invalid-batch-response');
|
|
292
|
+
bucket.flushing = false;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (result.batchId !== batchId) {
|
|
296
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
297
|
+
type: 'fallback',
|
|
298
|
+
endpoint,
|
|
299
|
+
batchId,
|
|
300
|
+
size: items.length,
|
|
301
|
+
reason: 'mismatched-batch-id'
|
|
302
|
+
});
|
|
303
|
+
await settleAmbiguousBatchRequests(items, 'mismatched-batch-id');
|
|
304
|
+
bucket.flushing = false;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const itemMap = new Map();
|
|
308
|
+
for (const item of result.items)itemMap.set(item.id, item);
|
|
309
|
+
await settleRequests(items, async (request)=>{
|
|
310
|
+
const resultItem = itemMap.get(request.item.id);
|
|
311
|
+
if (!resultItem) return runAfterAmbiguousBatch(request, 'missing-batch-result');
|
|
312
|
+
const reconstructedBody = resultItem.body ? decodeBatchBody(resultItem.body) : null;
|
|
313
|
+
const reconstructedResponse = new Response(isNullBodyStatus(resultItem.status) ? null : reconstructedBody, {
|
|
314
|
+
status: resultItem.status,
|
|
315
|
+
headers: resultItem.headers
|
|
316
|
+
});
|
|
317
|
+
return parseResponseLikeCreateRequest(reconstructedResponse);
|
|
318
|
+
});
|
|
319
|
+
} catch {
|
|
320
|
+
if (abortedBecauseUnused) {
|
|
321
|
+
if (items.some((item)=>item.lifetime.timedOut)) emitDataBatchTransportEvent(onEvent, {
|
|
322
|
+
type: 'fallback',
|
|
323
|
+
endpoint,
|
|
324
|
+
batchId,
|
|
325
|
+
size: items.length,
|
|
326
|
+
reason: 'batch-timeout'
|
|
327
|
+
});
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
331
|
+
type: 'fallback',
|
|
332
|
+
endpoint,
|
|
333
|
+
batchId,
|
|
334
|
+
size: items.length,
|
|
335
|
+
reason: 'batch-transport-error'
|
|
336
|
+
});
|
|
337
|
+
await settleAmbiguousBatchRequests(items, 'batch-transport-error');
|
|
338
|
+
} finally{
|
|
339
|
+
for (const item of items)item.abortBatchIfUnused = void 0;
|
|
340
|
+
bucket.flushing = false;
|
|
341
|
+
if (bucket.items.length > 0 && !bucket.timer) flushBucket(bucketKey);
|
|
342
|
+
buckets.releaseIfIdle(bucketKey, bucket);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
const pendingReadPromises = new Map();
|
|
346
|
+
const normalizeRequest = (input, init)=>{
|
|
347
|
+
const sourceRequest = "u" > typeof Request && input instanceof Request ? input : void 0;
|
|
348
|
+
const initDefinesSignal = void 0 !== init && 'signal' in init;
|
|
349
|
+
const initDefinesBody = void 0 !== init && 'body' in init;
|
|
350
|
+
const initSnapshot = init ? {
|
|
351
|
+
...init
|
|
352
|
+
} : void 0;
|
|
353
|
+
const getInitValue = (key)=>{
|
|
354
|
+
if (!init) return;
|
|
355
|
+
return Object.prototype.hasOwnProperty.call(initSnapshot, key) ? initSnapshot?.[key] : init[key];
|
|
356
|
+
};
|
|
357
|
+
const method = normalizeMethod(getInitValue('method') ?? sourceRequest?.method);
|
|
358
|
+
const headers = toHeaderRecord(getInitValue('headers') ?? sourceRequest?.headers);
|
|
359
|
+
const credentials = getInitValue('credentials') ?? sourceRequest?.credentials ?? 'same-origin';
|
|
360
|
+
const initBody = getInitValue('body');
|
|
361
|
+
const body = initBody ?? null;
|
|
362
|
+
const initSignal = initDefinesSignal ? getInitValue('signal') : void 0;
|
|
363
|
+
const sourceSignal = initDefinesSignal ? initSignal ?? void 0 : sourceRequest?.signal;
|
|
364
|
+
const requestUrl = toAbsoluteUrl(input);
|
|
365
|
+
const endpoint = normalizeBatchEndpoint(requestUrl, options.endpoint).toString();
|
|
366
|
+
return {
|
|
367
|
+
requestUrl,
|
|
368
|
+
endpoint,
|
|
369
|
+
method,
|
|
370
|
+
headers,
|
|
371
|
+
credentials,
|
|
372
|
+
body,
|
|
373
|
+
hasOpaqueSourceBody: void 0 === initBody && void 0 !== sourceRequest && null !== sourceRequest.body,
|
|
374
|
+
requestInput: sourceRequest ?? requestUrl.toString(),
|
|
375
|
+
requestInit: {
|
|
376
|
+
...initSnapshot,
|
|
377
|
+
method,
|
|
378
|
+
headers,
|
|
379
|
+
credentials,
|
|
380
|
+
...initDefinesBody ? {
|
|
381
|
+
body: initBody
|
|
382
|
+
} : {},
|
|
383
|
+
...initDefinesSignal ? {
|
|
384
|
+
signal: initSignal
|
|
385
|
+
} : {}
|
|
386
|
+
},
|
|
387
|
+
sourceSignal
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
const getPendingReadKey = (request)=>{
|
|
391
|
+
if (!SAFE_REPLAY_METHODS.has(request.method) || void 0 !== request.sourceSignal || null !== request.body || request.hasOpaqueSourceBody || disabledEndpoints.has(request.endpoint) || !shouldBatchRequest({
|
|
392
|
+
method: request.method,
|
|
393
|
+
body: void 0,
|
|
394
|
+
headers: request.headers,
|
|
395
|
+
allowedMethods,
|
|
396
|
+
batchEndpoint: request.endpoint,
|
|
397
|
+
requestUrl: request.requestUrl
|
|
398
|
+
})) return;
|
|
399
|
+
const itemHeaders = {
|
|
400
|
+
...request.headers
|
|
401
|
+
};
|
|
402
|
+
delete itemHeaders.authorization;
|
|
403
|
+
delete itemHeaders.cookie;
|
|
404
|
+
const bucketKey = stableStringify({
|
|
405
|
+
endpoint: request.endpoint,
|
|
406
|
+
authorization: request.headers.authorization ?? null,
|
|
407
|
+
cookie: request.headers.cookie ?? null,
|
|
408
|
+
credentials: request.credentials
|
|
409
|
+
});
|
|
410
|
+
return stableStringify({
|
|
411
|
+
bucketKey,
|
|
412
|
+
path: `${request.requestUrl.pathname}${request.requestUrl.search}`,
|
|
413
|
+
method: request.method,
|
|
414
|
+
headers: itemHeaders,
|
|
415
|
+
body: null
|
|
416
|
+
});
|
|
417
|
+
};
|
|
418
|
+
const enqueue = async (request)=>{
|
|
419
|
+
const preparedBody = await toRequestBody(request.body);
|
|
420
|
+
const hasOpaqueBody = !preparedBody.batchable || request.hasOpaqueSourceBody;
|
|
421
|
+
if (disabledEndpoints.has(request.endpoint) || !shouldBatchRequest({
|
|
422
|
+
method: request.method,
|
|
423
|
+
body: hasOpaqueBody ? '[opaque request body]' : void 0,
|
|
424
|
+
headers: request.headers,
|
|
425
|
+
allowedMethods,
|
|
426
|
+
batchEndpoint: request.endpoint,
|
|
427
|
+
requestUrl: request.requestUrl
|
|
428
|
+
})) return baseFetch(request.requestInput, request.requestInit).then(parseResponseLikeCreateRequest);
|
|
429
|
+
const itemId = `${Date.now().toString(36)}_${nextItemId.toString(36)}_${Math.random().toString(16).slice(2, 8)}`;
|
|
430
|
+
nextItemId += 1;
|
|
431
|
+
const itemHeaders = {
|
|
432
|
+
...request.headers
|
|
433
|
+
};
|
|
434
|
+
delete itemHeaders.authorization;
|
|
435
|
+
delete itemHeaders.cookie;
|
|
436
|
+
if (preparedBody.body && preparedBody.inferredContentType && !itemHeaders['content-type']) itemHeaders['content-type'] = preparedBody.inferredContentType;
|
|
437
|
+
const item = {
|
|
438
|
+
id: itemId,
|
|
439
|
+
path: `${request.requestUrl.pathname}${request.requestUrl.search}`,
|
|
440
|
+
method: request.method,
|
|
441
|
+
headers: itemHeaders,
|
|
442
|
+
...preparedBody.body ? {
|
|
443
|
+
body: preparedBody.body
|
|
444
|
+
} : {}
|
|
445
|
+
};
|
|
446
|
+
const bucketKey = stableStringify({
|
|
447
|
+
endpoint: request.endpoint,
|
|
448
|
+
authorization: request.headers.authorization ?? null,
|
|
449
|
+
cookie: request.headers.cookie ?? null,
|
|
450
|
+
credentials: request.credentials
|
|
451
|
+
});
|
|
452
|
+
const key = stableStringify({
|
|
453
|
+
bucketKey,
|
|
454
|
+
path: item.path,
|
|
455
|
+
method: item.method,
|
|
456
|
+
headers: item.headers,
|
|
457
|
+
body: item.body ?? null
|
|
458
|
+
});
|
|
459
|
+
const canSharePending = SAFE_REPLAY_METHODS.has(request.method) && void 0 === request.sourceSignal;
|
|
460
|
+
const existing = canSharePending ? pendingByKey.get(key) : void 0;
|
|
461
|
+
if (existing) return existing.promise;
|
|
462
|
+
const lifetime = createRequestLifetime(request.sourceSignal, requestTimeoutMs);
|
|
463
|
+
request.requestInit.signal = lifetime.signal;
|
|
464
|
+
const deferred = Promise.withResolvers();
|
|
465
|
+
const queued = {
|
|
466
|
+
key,
|
|
467
|
+
bucketKey,
|
|
468
|
+
endpoint: request.endpoint,
|
|
469
|
+
requestInput: request.requestInput,
|
|
470
|
+
requestInit: request.requestInit,
|
|
471
|
+
authorization: request.headers.authorization,
|
|
472
|
+
cookie: request.headers.cookie,
|
|
473
|
+
credentials: request.credentials,
|
|
474
|
+
item,
|
|
475
|
+
lifetime,
|
|
476
|
+
phase: 'queued',
|
|
477
|
+
promise: deferred.promise,
|
|
478
|
+
abortListener: ()=>abortQueuedRequest(queued),
|
|
479
|
+
resolve: deferred.resolve,
|
|
480
|
+
reject: deferred.reject
|
|
481
|
+
};
|
|
482
|
+
lifetime.signal.addEventListener('abort', queued.abortListener, {
|
|
483
|
+
once: true
|
|
484
|
+
});
|
|
485
|
+
if (lifetime.signal.aborted) {
|
|
486
|
+
abortQueuedRequest(queued);
|
|
487
|
+
return queued.promise;
|
|
488
|
+
}
|
|
489
|
+
if (canSharePending) pendingByKey.set(key, queued);
|
|
490
|
+
const bucket = buckets.ensure(bucketKey);
|
|
491
|
+
const singletonBytes = measurePayload(bucket, [
|
|
492
|
+
queued
|
|
493
|
+
]);
|
|
494
|
+
if (singletonBytes > maxBatchBytes) {
|
|
495
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
496
|
+
type: 'fallback',
|
|
497
|
+
endpoint: request.endpoint,
|
|
498
|
+
size: 1,
|
|
499
|
+
reason: 'batch-item-too-large'
|
|
500
|
+
});
|
|
501
|
+
queued.phase = 'flushing';
|
|
502
|
+
settleRequests([
|
|
503
|
+
queued
|
|
504
|
+
], runSingle);
|
|
505
|
+
buckets.releaseIfIdle(bucketKey, bucket);
|
|
506
|
+
return queued.promise;
|
|
507
|
+
}
|
|
508
|
+
const candidateItems = [
|
|
509
|
+
...bucket.items,
|
|
510
|
+
queued
|
|
511
|
+
];
|
|
512
|
+
const candidateBytes = measurePayload(bucket, candidateItems);
|
|
513
|
+
if (bucket.items.length > 0 && candidateBytes > maxBatchBytes) {
|
|
514
|
+
if (bucket.flushing) {
|
|
515
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
516
|
+
type: 'fallback',
|
|
517
|
+
endpoint: request.endpoint,
|
|
518
|
+
size: 1,
|
|
519
|
+
reason: 'batch-capacity-during-flush'
|
|
520
|
+
});
|
|
521
|
+
queued.phase = 'flushing';
|
|
522
|
+
settleRequests([
|
|
523
|
+
queued
|
|
524
|
+
], runSingle);
|
|
525
|
+
return queued.promise;
|
|
526
|
+
}
|
|
527
|
+
flushBucket(bucketKey);
|
|
528
|
+
if (measurePayload(bucket, [
|
|
529
|
+
queued
|
|
530
|
+
]) > maxBatchBytes) {
|
|
531
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
532
|
+
type: 'fallback',
|
|
533
|
+
endpoint: request.endpoint,
|
|
534
|
+
size: 1,
|
|
535
|
+
reason: 'batch-item-too-large'
|
|
536
|
+
});
|
|
537
|
+
queued.phase = 'flushing';
|
|
538
|
+
settleRequests([
|
|
539
|
+
queued
|
|
540
|
+
], runSingle);
|
|
541
|
+
return queued.promise;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
bucket.items.push(queued);
|
|
545
|
+
bucket.bytes = measurePayload(bucket, bucket.items);
|
|
546
|
+
emitDataBatchTransportEvent(onEvent, {
|
|
547
|
+
type: 'enqueue',
|
|
548
|
+
endpoint: request.endpoint,
|
|
549
|
+
size: bucket.items.length
|
|
550
|
+
});
|
|
551
|
+
if (bucket.items.length >= maxBatchSize || bucket.bytes >= maxBatchBytes) {
|
|
552
|
+
flushBucket(bucketKey);
|
|
553
|
+
return queued.promise;
|
|
554
|
+
}
|
|
555
|
+
if (!bucket.timer) bucket.timer = setTimeout(()=>{
|
|
556
|
+
bucket.timer = null;
|
|
557
|
+
flushBucket(bucketKey);
|
|
558
|
+
}, flushIntervalMs);
|
|
559
|
+
return queued.promise;
|
|
560
|
+
};
|
|
561
|
+
return (input, init)=>{
|
|
562
|
+
let request;
|
|
563
|
+
try {
|
|
564
|
+
request = normalizeRequest(input, init);
|
|
565
|
+
} catch (error) {
|
|
566
|
+
return Promise.reject(error);
|
|
567
|
+
}
|
|
568
|
+
const pendingReadKey = getPendingReadKey(request);
|
|
569
|
+
const existing = pendingReadKey ? pendingReadPromises.get(pendingReadKey) : void 0;
|
|
570
|
+
if (existing) return existing;
|
|
571
|
+
const pending = enqueue(request);
|
|
572
|
+
if (pendingReadKey) {
|
|
573
|
+
pendingReadPromises.set(pendingReadKey, pending);
|
|
574
|
+
const clearPendingRead = ()=>{
|
|
575
|
+
if (pendingReadPromises.get(pendingReadKey) === pending) pendingReadPromises.delete(pendingReadKey);
|
|
576
|
+
};
|
|
577
|
+
pending.then(clearPendingRead, clearPendingRead);
|
|
578
|
+
}
|
|
579
|
+
return pending;
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
export { BatchBucketRegistry, createBatchTransportQueue };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { DEFAULT_DATA_BATCH_HEADER } from "../types.mjs";
|
|
2
|
+
import { encodeBatchBody } from "./protocol.mjs";
|
|
3
|
+
function toHeaderRecord(headers) {
|
|
4
|
+
if (!headers) return {};
|
|
5
|
+
if (headers instanceof Headers) {
|
|
6
|
+
const next = {};
|
|
7
|
+
headers.forEach((value, key)=>{
|
|
8
|
+
next[key.toLowerCase()] = value;
|
|
9
|
+
});
|
|
10
|
+
return next;
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(headers)) return headers.reduce((acc, [key, value])=>{
|
|
13
|
+
acc[String(key).toLowerCase()] = String(value);
|
|
14
|
+
return acc;
|
|
15
|
+
}, {});
|
|
16
|
+
return Object.entries(headers).reduce((acc, [key, value])=>{
|
|
17
|
+
if (void 0 === value) return acc;
|
|
18
|
+
acc[String(key).toLowerCase()] = Array.isArray(value) ? value.join(', ') : String(value);
|
|
19
|
+
return acc;
|
|
20
|
+
}, {});
|
|
21
|
+
}
|
|
22
|
+
function measureTextBytes(value) {
|
|
23
|
+
if ("u" > typeof TextEncoder) return new TextEncoder().encode(value).length;
|
|
24
|
+
if ("u" > typeof Buffer) return Buffer.byteLength(value);
|
|
25
|
+
return value.length;
|
|
26
|
+
}
|
|
27
|
+
function createBatchId() {
|
|
28
|
+
const now = Date.now().toString(36);
|
|
29
|
+
const random = Math.random().toString(16).slice(2, 10);
|
|
30
|
+
return `batch_${now}_${random}`;
|
|
31
|
+
}
|
|
32
|
+
function normalizeMethod(method) {
|
|
33
|
+
return (method || 'GET').toUpperCase();
|
|
34
|
+
}
|
|
35
|
+
async function toRequestBody(initBody) {
|
|
36
|
+
if (null == initBody) return {
|
|
37
|
+
body: void 0,
|
|
38
|
+
inferredContentType: void 0,
|
|
39
|
+
batchable: true
|
|
40
|
+
};
|
|
41
|
+
if ("u" > typeof ReadableStream && initBody instanceof ReadableStream) return {
|
|
42
|
+
body: void 0,
|
|
43
|
+
inferredContentType: void 0,
|
|
44
|
+
batchable: false
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
const response = new Response(initBody);
|
|
48
|
+
return {
|
|
49
|
+
body: encodeBatchBody(new Uint8Array(await response.arrayBuffer())),
|
|
50
|
+
inferredContentType: response.headers.get('content-type') || void 0,
|
|
51
|
+
batchable: true
|
|
52
|
+
};
|
|
53
|
+
} catch {
|
|
54
|
+
return {
|
|
55
|
+
body: void 0,
|
|
56
|
+
inferredContentType: void 0,
|
|
57
|
+
batchable: false
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function shouldBatchRequest(input) {
|
|
62
|
+
if (input.requestUrl.href === input.batchEndpoint) return false;
|
|
63
|
+
if ('off' === input.headers[DEFAULT_DATA_BATCH_HEADER]) return false;
|
|
64
|
+
if (!input.allowedMethods.has(input.method)) return false;
|
|
65
|
+
if (void 0 !== input.body) return false;
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
export { createBatchId, measureTextBytes, normalizeMethod, shouldBatchRequest, toHeaderRecord, toRequestBody };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isPlainObject } from "../codec.mjs";
|
|
2
|
+
import { isBatchBody, isBatchHeaderList, isNullBodyStatus } from "./protocol.mjs";
|
|
3
|
+
function isBatchResponseItem(value) {
|
|
4
|
+
return isPlainObject(value) && 'string' == typeof value.id && 'number' == typeof value.status && (void 0 === value.headers || isBatchHeaderList(value.headers)) && (void 0 === value.body || isBatchBody(value.body)) && !(isNullBodyStatus(value.status) && void 0 !== value.body);
|
|
5
|
+
}
|
|
6
|
+
function isBatchResponsePayload(value) {
|
|
7
|
+
return isPlainObject(value) && 2 === value.protocolVersion && 'string' == typeof value.batchId && 'number' == typeof value.receivedAt && Array.isArray(value.items) && value.items.every((item)=>isBatchResponseItem(item));
|
|
8
|
+
}
|
|
9
|
+
async function parseResponseLikeCreateRequest(response) {
|
|
10
|
+
const contentType = (response.headers.get('content-type') || '').toLowerCase();
|
|
11
|
+
if (!response.ok) {
|
|
12
|
+
let data = null;
|
|
13
|
+
data = contentType.includes('application/json') ? await response.json() : await response.text();
|
|
14
|
+
response.data = data;
|
|
15
|
+
throw response;
|
|
16
|
+
}
|
|
17
|
+
if (contentType.includes('application/json') || contentType.includes('text/json')) return response.json();
|
|
18
|
+
if (contentType.includes('text/html') || contentType.includes('text/plain')) return response.text();
|
|
19
|
+
if (contentType.includes('application/x-www-form-urlencoded') || contentType.includes('multipart/form-data')) return response.formData();
|
|
20
|
+
if (contentType.includes('application/octet-stream')) return response.arrayBuffer();
|
|
21
|
+
if (contentType.includes('image/png')) return response;
|
|
22
|
+
return response.text();
|
|
23
|
+
}
|
|
24
|
+
export { isBatchResponsePayload, parseResponseLikeCreateRequest };
|