@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,130 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off globalDate:off globalRandom:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
+
import type { DataBatchBody } from '../types';
|
|
3
|
+
import { DEFAULT_DATA_BATCH_HEADER } from '../types';
|
|
4
|
+
import { encodeBatchBody } from './protocol';
|
|
5
|
+
|
|
6
|
+
export function toHeaderRecord(
|
|
7
|
+
headers: HeadersInit | undefined,
|
|
8
|
+
): Record<string, string> {
|
|
9
|
+
if (!headers) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (headers instanceof Headers) {
|
|
14
|
+
const next: Record<string, string> = {};
|
|
15
|
+
headers.forEach((value, key) => {
|
|
16
|
+
next[key.toLowerCase()] = value;
|
|
17
|
+
});
|
|
18
|
+
return next;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (Array.isArray(headers)) {
|
|
22
|
+
return headers.reduce<Record<string, string>>((acc, [key, value]) => {
|
|
23
|
+
acc[String(key).toLowerCase()] = String(value);
|
|
24
|
+
return acc;
|
|
25
|
+
}, {});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return Object.entries(headers).reduce<Record<string, string>>(
|
|
29
|
+
(acc, [key, value]) => {
|
|
30
|
+
if (typeof value === 'undefined') {
|
|
31
|
+
return acc;
|
|
32
|
+
}
|
|
33
|
+
acc[String(key).toLowerCase()] = Array.isArray(value)
|
|
34
|
+
? value.join(', ')
|
|
35
|
+
: String(value);
|
|
36
|
+
return acc;
|
|
37
|
+
},
|
|
38
|
+
{},
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function measureTextBytes(value: string) {
|
|
43
|
+
if (typeof TextEncoder !== 'undefined') {
|
|
44
|
+
return new TextEncoder().encode(value).length;
|
|
45
|
+
}
|
|
46
|
+
if (typeof Buffer !== 'undefined') {
|
|
47
|
+
return Buffer.byteLength(value);
|
|
48
|
+
}
|
|
49
|
+
return value.length;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createBatchId() {
|
|
53
|
+
const now = Date.now().toString(36);
|
|
54
|
+
const random = Math.random().toString(16).slice(2, 10);
|
|
55
|
+
return `batch_${now}_${random}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function normalizeMethod(method: string | undefined) {
|
|
59
|
+
return (method || 'GET').toUpperCase();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type PreparedBatchRequestBody = {
|
|
63
|
+
body: DataBatchBody | undefined;
|
|
64
|
+
inferredContentType: string | undefined;
|
|
65
|
+
batchable: boolean;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export async function toRequestBody(
|
|
69
|
+
initBody: BodyInit | null | undefined,
|
|
70
|
+
): Promise<PreparedBatchRequestBody> {
|
|
71
|
+
if (typeof initBody === 'undefined' || initBody === null) {
|
|
72
|
+
return {
|
|
73
|
+
body: undefined,
|
|
74
|
+
inferredContentType: undefined,
|
|
75
|
+
batchable: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (
|
|
79
|
+
typeof ReadableStream !== 'undefined' &&
|
|
80
|
+
initBody instanceof ReadableStream
|
|
81
|
+
) {
|
|
82
|
+
return {
|
|
83
|
+
body: undefined,
|
|
84
|
+
inferredContentType: undefined,
|
|
85
|
+
batchable: false,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const response = new Response(initBody);
|
|
91
|
+
return {
|
|
92
|
+
body: encodeBatchBody(new Uint8Array(await response.arrayBuffer())),
|
|
93
|
+
inferredContentType: response.headers.get('content-type') || undefined,
|
|
94
|
+
batchable: true,
|
|
95
|
+
};
|
|
96
|
+
} catch {
|
|
97
|
+
return {
|
|
98
|
+
body: undefined,
|
|
99
|
+
inferredContentType: undefined,
|
|
100
|
+
batchable: false,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function shouldBatchRequest(input: {
|
|
106
|
+
method: string;
|
|
107
|
+
body: string | undefined;
|
|
108
|
+
headers: Record<string, string>;
|
|
109
|
+
allowedMethods: Set<string>;
|
|
110
|
+
batchEndpoint: string;
|
|
111
|
+
requestUrl: URL;
|
|
112
|
+
}) {
|
|
113
|
+
if (input.requestUrl.href === input.batchEndpoint) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (input.headers[DEFAULT_DATA_BATCH_HEADER] === 'off') {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!input.allowedMethods.has(input.method)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (input.body !== undefined) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off globalDate:off globalRandom:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
+
import { isPlainObject } from '../codec';
|
|
3
|
+
import type { DataBatchResponseItem, DataBatchResponsePayload } from '../types';
|
|
4
|
+
import { isBatchBody, isBatchHeaderList, isNullBodyStatus } from './protocol';
|
|
5
|
+
|
|
6
|
+
function isBatchResponseItem(value: unknown): value is DataBatchResponseItem {
|
|
7
|
+
return (
|
|
8
|
+
isPlainObject(value) &&
|
|
9
|
+
typeof value.id === 'string' &&
|
|
10
|
+
typeof value.status === 'number' &&
|
|
11
|
+
(typeof value.headers === 'undefined' ||
|
|
12
|
+
isBatchHeaderList(value.headers)) &&
|
|
13
|
+
(typeof value.body === 'undefined' || isBatchBody(value.body)) &&
|
|
14
|
+
!(isNullBodyStatus(value.status) && typeof value.body !== 'undefined')
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isBatchResponsePayload(
|
|
19
|
+
value: unknown,
|
|
20
|
+
): value is DataBatchResponsePayload {
|
|
21
|
+
return (
|
|
22
|
+
isPlainObject(value) &&
|
|
23
|
+
value.protocolVersion === 2 &&
|
|
24
|
+
typeof value.batchId === 'string' &&
|
|
25
|
+
typeof value.receivedAt === 'number' &&
|
|
26
|
+
Array.isArray(value.items) &&
|
|
27
|
+
value.items.every(item => isBatchResponseItem(item))
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function parseResponseLikeCreateRequest(response: Response) {
|
|
32
|
+
const contentType = (
|
|
33
|
+
response.headers.get('content-type') || ''
|
|
34
|
+
).toLowerCase();
|
|
35
|
+
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
let data: unknown = null;
|
|
38
|
+
if (contentType.includes('application/json')) {
|
|
39
|
+
data = await response.json();
|
|
40
|
+
} else {
|
|
41
|
+
data = await response.text();
|
|
42
|
+
}
|
|
43
|
+
(response as Response & { data?: unknown }).data = data;
|
|
44
|
+
throw response;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (
|
|
48
|
+
contentType.includes('application/json') ||
|
|
49
|
+
contentType.includes('text/json')
|
|
50
|
+
) {
|
|
51
|
+
return response.json();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (contentType.includes('text/html') || contentType.includes('text/plain')) {
|
|
55
|
+
return response.text();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
contentType.includes('application/x-www-form-urlencoded') ||
|
|
60
|
+
contentType.includes('multipart/form-data')
|
|
61
|
+
) {
|
|
62
|
+
return response.formData();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (contentType.includes('application/octet-stream')) {
|
|
66
|
+
return response.arrayBuffer();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (contentType.includes('image/png')) {
|
|
70
|
+
return response;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return response.text();
|
|
74
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off globalDate:off globalRandom:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
+
import { trace } from '@opentelemetry/api';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
DataBatchTransportEvent,
|
|
6
|
+
DataBatchTransportTelemetryAttributes,
|
|
7
|
+
} from '../types';
|
|
8
|
+
|
|
9
|
+
export const DATA_BATCH_TRANSPORT_OTEL_EVENT = 'modernjs.data.batch';
|
|
10
|
+
|
|
11
|
+
export function createDataBatchTransportTelemetryAttributes(
|
|
12
|
+
event: DataBatchTransportEvent,
|
|
13
|
+
): DataBatchTransportTelemetryAttributes {
|
|
14
|
+
return {
|
|
15
|
+
'modernjs.data.batch.type': event.type,
|
|
16
|
+
'modernjs.data.batch.endpoint': event.endpoint,
|
|
17
|
+
'modernjs.data.batch.degraded':
|
|
18
|
+
event.type === 'fallback' || event.type === 'disable',
|
|
19
|
+
...(event.batchId ? { 'modernjs.data.batch.id': event.batchId } : {}),
|
|
20
|
+
...(typeof event.size === 'number'
|
|
21
|
+
? { 'modernjs.data.batch.size': event.size }
|
|
22
|
+
: {}),
|
|
23
|
+
...(event.reason ? { 'modernjs.data.batch.reason': event.reason } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function emitDataBatchTransportEvent(
|
|
28
|
+
onEvent: ((event: DataBatchTransportEvent) => void) | undefined,
|
|
29
|
+
event: DataBatchTransportEvent,
|
|
30
|
+
) {
|
|
31
|
+
try {
|
|
32
|
+
onEvent?.(event);
|
|
33
|
+
} catch {
|
|
34
|
+
// Observability callbacks must not affect transport state or settlement.
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
trace
|
|
39
|
+
.getActiveSpan()
|
|
40
|
+
?.addEvent(
|
|
41
|
+
DATA_BATCH_TRANSPORT_OTEL_EVENT,
|
|
42
|
+
createDataBatchTransportTelemetryAttributes(event),
|
|
43
|
+
);
|
|
44
|
+
} catch {
|
|
45
|
+
// OpenTelemetry exporters are observational and must remain non-fatal.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off globalDate:off globalRandom:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
+
import type { DataTransportRequestInfo } from '../types';
|
|
3
|
+
import { DEFAULT_DATA_BATCH_ENDPOINT } from '../types';
|
|
4
|
+
|
|
5
|
+
export function resolveRuntimeOrigin() {
|
|
6
|
+
if (
|
|
7
|
+
typeof window !== 'undefined' &&
|
|
8
|
+
window.location &&
|
|
9
|
+
typeof window.location.origin === 'string' &&
|
|
10
|
+
window.location.origin
|
|
11
|
+
) {
|
|
12
|
+
return window.location.origin;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
typeof globalThis !== 'undefined' &&
|
|
17
|
+
(globalThis as { location?: { origin?: string } }).location &&
|
|
18
|
+
typeof (globalThis as { location?: { origin?: string } }).location
|
|
19
|
+
?.origin === 'string'
|
|
20
|
+
) {
|
|
21
|
+
return (globalThis as { location?: { origin?: string } }).location!.origin!;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return 'http://localhost';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function toAbsoluteUrl(input: DataTransportRequestInfo) {
|
|
28
|
+
if (input instanceof URL) {
|
|
29
|
+
return input;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof Request !== 'undefined' && input instanceof Request) {
|
|
33
|
+
return new URL(input.url);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const value = String(input);
|
|
37
|
+
try {
|
|
38
|
+
return new URL(value);
|
|
39
|
+
} catch {
|
|
40
|
+
return new URL(value, resolveRuntimeOrigin());
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function normalizeBatchEndpoint(
|
|
45
|
+
requestUrl: URL,
|
|
46
|
+
endpoint: string | undefined,
|
|
47
|
+
): URL {
|
|
48
|
+
const value = endpoint || DEFAULT_DATA_BATCH_ENDPOINT;
|
|
49
|
+
try {
|
|
50
|
+
return new URL(value);
|
|
51
|
+
} catch {
|
|
52
|
+
return new URL(value, requestUrl.origin);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off globalDate:off globalRandom:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
+
|
|
3
|
+
import { createBatchTransportQueue } from './batch/queue';
|
|
4
|
+
import type { DataBatchTransportOptions } from './types';
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
createDataBatchTransportTelemetryAttributes,
|
|
8
|
+
DATA_BATCH_TRANSPORT_OTEL_EVENT,
|
|
9
|
+
emitDataBatchTransportEvent,
|
|
10
|
+
} from './batch/telemetry';
|
|
11
|
+
|
|
12
|
+
export function createDataBatchTransport(
|
|
13
|
+
options: DataBatchTransportOptions = {},
|
|
14
|
+
) {
|
|
15
|
+
const fallbackFetch =
|
|
16
|
+
typeof fetch === 'function' ? fetch.bind(globalThis) : undefined;
|
|
17
|
+
const baseFetch = options.fetch || fallbackFetch;
|
|
18
|
+
if (!baseFetch) {
|
|
19
|
+
throw new Error('createDataBatchTransport requires a fetch implementation');
|
|
20
|
+
}
|
|
21
|
+
return createBatchTransportQueue({ options, baseFetch });
|
|
22
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off globalDate:off globalRandom:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
+
import type {
|
|
3
|
+
CacheScope,
|
|
4
|
+
DataRequestMode,
|
|
5
|
+
OperationDescriptor,
|
|
6
|
+
RequestEnvelope,
|
|
7
|
+
SelectionPlan,
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
export function isPlainObject(
|
|
11
|
+
value: unknown,
|
|
12
|
+
): value is Record<string, unknown> {
|
|
13
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const proto = Object.getPrototypeOf(value);
|
|
17
|
+
return proto === Object.prototype || proto === null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function canonicalize(value: unknown): unknown {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return value.map(item => canonicalize(item));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (isPlainObject(value)) {
|
|
26
|
+
return Object.keys(value)
|
|
27
|
+
.sort()
|
|
28
|
+
.reduce<Record<string, unknown>>((acc, key) => {
|
|
29
|
+
acc[key] = canonicalize(value[key]);
|
|
30
|
+
return acc;
|
|
31
|
+
}, {});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function stableStringify(value: unknown): string {
|
|
38
|
+
return JSON.stringify(canonicalize(value));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function encodeRequestEnvelopeHeader(envelope: RequestEnvelope): string {
|
|
42
|
+
return encodeURIComponent(stableStringify(envelope));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isRequestEnvelopeShape(value: unknown): value is RequestEnvelope {
|
|
46
|
+
if (!isPlainObject(value)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
value.protocolVersion === 1 &&
|
|
52
|
+
typeof value.operationId === 'string' &&
|
|
53
|
+
typeof value.appNamespace === 'string' &&
|
|
54
|
+
typeof value.origin === 'string' &&
|
|
55
|
+
typeof value.scopeKey === 'string' &&
|
|
56
|
+
typeof value.inputHash === 'string' &&
|
|
57
|
+
typeof value.timestamp === 'number'
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function decodeRequestEnvelopeHeader(
|
|
62
|
+
value: string,
|
|
63
|
+
): RequestEnvelope | null {
|
|
64
|
+
try {
|
|
65
|
+
const decoded = decodeURIComponent(value);
|
|
66
|
+
const parsed = JSON.parse(decoded);
|
|
67
|
+
if (!isRequestEnvelopeShape(parsed)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return parsed;
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function hashString(value: string): string {
|
|
78
|
+
let hash = 0x811c9dc5;
|
|
79
|
+
for (let index = 0; index < value.length; index++) {
|
|
80
|
+
hash ^= value.charCodeAt(index);
|
|
81
|
+
hash = Math.imul(hash, 0x01000193);
|
|
82
|
+
}
|
|
83
|
+
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function sanitizeSegment(segment: string): string {
|
|
87
|
+
const normalized = segment.trim().replace(/[^a-zA-Z0-9_-]+/g, '_');
|
|
88
|
+
return normalized.length > 0 ? normalized : 'unknown';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function normalizeOrigin(origin: string): string {
|
|
92
|
+
const normalized = origin.trim();
|
|
93
|
+
try {
|
|
94
|
+
const url = new URL(normalized);
|
|
95
|
+
return `${url.protocol}//${url.host}`.toLowerCase();
|
|
96
|
+
} catch {
|
|
97
|
+
return normalized.toLowerCase();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizeOperationDescriptor(input: OperationDescriptor) {
|
|
102
|
+
return {
|
|
103
|
+
appNamespace: sanitizeSegment(input.appNamespace),
|
|
104
|
+
apiId: sanitizeSegment(input.apiId),
|
|
105
|
+
group: sanitizeSegment(input.group),
|
|
106
|
+
endpoint: sanitizeSegment(input.endpoint),
|
|
107
|
+
schemaHash: input.schemaHash || null,
|
|
108
|
+
version: input.version ?? 1,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createOperationId(input: OperationDescriptor): string {
|
|
113
|
+
const descriptor = normalizeOperationDescriptor(input);
|
|
114
|
+
const readable = `${descriptor.appNamespace}.${descriptor.apiId}.${descriptor.group}.${descriptor.endpoint}.v${String(descriptor.version)}`;
|
|
115
|
+
return `${readable}:${hashString(stableStringify(descriptor))}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function buildScopeKey(scope: CacheScope): string {
|
|
119
|
+
const canonical = {
|
|
120
|
+
appNamespace: sanitizeSegment(scope.appNamespace),
|
|
121
|
+
origin: normalizeOrigin(scope.origin),
|
|
122
|
+
tenantId: scope.tenantId ?? null,
|
|
123
|
+
userId: scope.userId ?? null,
|
|
124
|
+
sessionId: scope.sessionId ?? null,
|
|
125
|
+
};
|
|
126
|
+
return `${canonical.appNamespace}:${hashString(stableStringify(canonical))}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function buildQueryKey(input: {
|
|
130
|
+
operationId: string;
|
|
131
|
+
scopeKey: string;
|
|
132
|
+
requestMode?: DataRequestMode;
|
|
133
|
+
requestInput?: unknown;
|
|
134
|
+
selectionPlan?: SelectionPlan;
|
|
135
|
+
}): string {
|
|
136
|
+
const canonical = {
|
|
137
|
+
operationId: input.operationId,
|
|
138
|
+
scopeKey: input.scopeKey,
|
|
139
|
+
requestMode: input.requestMode ?? 'cache-first',
|
|
140
|
+
requestInput: input.requestInput ?? null,
|
|
141
|
+
selectionPlan: input.selectionPlan ?? null,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return `${input.operationId}:${hashString(stableStringify(canonical))}`;
|
|
145
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export { measureTextBytes, normalizeMethod } from './batch/request';
|
|
2
|
+
export {
|
|
3
|
+
createDataBatchTransport,
|
|
4
|
+
createDataBatchTransportTelemetryAttributes,
|
|
5
|
+
DATA_BATCH_TRANSPORT_OTEL_EVENT,
|
|
6
|
+
emitDataBatchTransportEvent,
|
|
7
|
+
} from './batch-transport';
|
|
8
|
+
export {
|
|
9
|
+
buildQueryKey,
|
|
10
|
+
buildScopeKey,
|
|
11
|
+
createOperationId,
|
|
12
|
+
decodeRequestEnvelopeHeader,
|
|
13
|
+
encodeRequestEnvelopeHeader,
|
|
14
|
+
isPlainObject,
|
|
15
|
+
normalizeOrigin,
|
|
16
|
+
stableStringify,
|
|
17
|
+
} from './codec';
|
|
18
|
+
export {
|
|
19
|
+
createInvalidationEvent,
|
|
20
|
+
shouldApplyInvalidation,
|
|
21
|
+
} from './invalidation';
|
|
22
|
+
export {
|
|
23
|
+
deriveChildTraceContext,
|
|
24
|
+
formatTraceparentHeader,
|
|
25
|
+
parseTraceparentHeader,
|
|
26
|
+
} from './trace';
|
|
27
|
+
export type {
|
|
28
|
+
CacheScope,
|
|
29
|
+
DataBatchBody,
|
|
30
|
+
DataBatchHeader,
|
|
31
|
+
DataBatchRequestItem,
|
|
32
|
+
DataBatchRequestPayload,
|
|
33
|
+
DataBatchResponseItem,
|
|
34
|
+
DataBatchResponsePayload,
|
|
35
|
+
DataBatchTransportEvent,
|
|
36
|
+
DataBatchTransportOptions,
|
|
37
|
+
DataBatchTransportTelemetryAttributes,
|
|
38
|
+
DataMutationMode,
|
|
39
|
+
DataRequestMode,
|
|
40
|
+
HydrationEnvelope,
|
|
41
|
+
HydrationEnvelopeValidationOptions,
|
|
42
|
+
InvalidationEvent,
|
|
43
|
+
InvalidationSubscriber,
|
|
44
|
+
OperationDescriptor,
|
|
45
|
+
RequestEnvelope,
|
|
46
|
+
RequestEnvelopeValidationOptions,
|
|
47
|
+
SelectionPlan,
|
|
48
|
+
SelectionPlanValidationOptions,
|
|
49
|
+
SelectionPlanValidationResult,
|
|
50
|
+
TraceContext,
|
|
51
|
+
} from './types';
|
|
52
|
+
export {
|
|
53
|
+
DEFAULT_DATA_BATCH_ENDPOINT,
|
|
54
|
+
DEFAULT_DATA_BATCH_HEADER,
|
|
55
|
+
DEFAULT_DATA_ENVELOPE_HEADER,
|
|
56
|
+
} from './types';
|
|
57
|
+
export {
|
|
58
|
+
createHydrationEnvelope,
|
|
59
|
+
createRequestEnvelope,
|
|
60
|
+
validateHydrationEnvelope,
|
|
61
|
+
validateRequestEnvelope,
|
|
62
|
+
validateSelectionPlan,
|
|
63
|
+
} from './validation';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off globalDate:off globalRandom:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
+
import { buildScopeKey, createOperationId, sanitizeSegment } from './codec';
|
|
3
|
+
import type {
|
|
4
|
+
CacheScope,
|
|
5
|
+
InvalidationEvent,
|
|
6
|
+
InvalidationSubscriber,
|
|
7
|
+
OperationDescriptor,
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
export function createInvalidationEvent(input: {
|
|
11
|
+
sourceOperation: OperationDescriptor;
|
|
12
|
+
sourceScope: CacheScope;
|
|
13
|
+
targetNamespaces?: string[];
|
|
14
|
+
targetOperations?: OperationDescriptor[];
|
|
15
|
+
}): InvalidationEvent {
|
|
16
|
+
return {
|
|
17
|
+
sourceNamespace: sanitizeSegment(input.sourceOperation.appNamespace),
|
|
18
|
+
sourceOperationId: createOperationId(input.sourceOperation),
|
|
19
|
+
scopeKey: buildScopeKey(input.sourceScope),
|
|
20
|
+
targetNamespaces: input.targetNamespaces?.map(namespace =>
|
|
21
|
+
sanitizeSegment(namespace),
|
|
22
|
+
),
|
|
23
|
+
targetOperationIds: input.targetOperations?.map(operation =>
|
|
24
|
+
createOperationId(operation),
|
|
25
|
+
),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function shouldApplyInvalidation(
|
|
30
|
+
event: InvalidationEvent,
|
|
31
|
+
subscriber: InvalidationSubscriber,
|
|
32
|
+
): boolean {
|
|
33
|
+
if (subscriber.scopeKey && subscriber.scopeKey !== event.scopeKey) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const sameNamespace = subscriber.namespace === event.sourceNamespace;
|
|
38
|
+
const allowedCrossNamespace =
|
|
39
|
+
subscriber.acceptCrossNamespace === true &&
|
|
40
|
+
event.targetNamespaces?.includes(subscriber.namespace) === true;
|
|
41
|
+
|
|
42
|
+
if (!sameNamespace && !allowedCrossNamespace) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!subscriber.operationIds || subscriber.operationIds.length === 0) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!event.targetOperationIds || event.targetOperationIds.length === 0) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return subscriber.operationIds.some(operationId =>
|
|
55
|
+
event.targetOperationIds?.includes(operationId),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off globalDate:off globalRandom:off globalTimers:off newPromise:off strictBooleanExpressions:off
|
|
2
|
+
import { parseTraceparent } from '@modern-js/create-request';
|
|
3
|
+
|
|
4
|
+
import type { TraceContext } from './types';
|
|
5
|
+
|
|
6
|
+
function isAllZeroHex(value: string): boolean {
|
|
7
|
+
return /^0+$/.test(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isValidHex(value: string, length: number): boolean {
|
|
11
|
+
return value.length === length && /^[0-9a-f]+$/.test(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parseTraceparentHeader(header: string): TraceContext | null {
|
|
15
|
+
return parseTraceparent(header) ?? null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function formatTraceparentHeader(trace: TraceContext): string {
|
|
19
|
+
const traceId = trace.traceId.toLowerCase();
|
|
20
|
+
const spanId = trace.spanId.toLowerCase();
|
|
21
|
+
|
|
22
|
+
if (!isValidHex(traceId, 32) || !isValidHex(spanId, 16)) {
|
|
23
|
+
throw new Error('Invalid trace context: traceId/spanId format mismatch');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (isAllZeroHex(traceId) || isAllZeroHex(spanId)) {
|
|
27
|
+
throw new Error('Invalid trace context: traceId/spanId cannot be zero');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const flags = trace.sampled ? '01' : '00';
|
|
31
|
+
return `00-${traceId}-${spanId}-${flags}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function deriveChildTraceContext(
|
|
35
|
+
parent: TraceContext,
|
|
36
|
+
childSpanId: string,
|
|
37
|
+
): TraceContext {
|
|
38
|
+
const normalizedSpanId = childSpanId.toLowerCase();
|
|
39
|
+
if (!isValidHex(normalizedSpanId, 16) || isAllZeroHex(normalizedSpanId)) {
|
|
40
|
+
throw new Error('Invalid child span id');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
traceId: parent.traceId.toLowerCase(),
|
|
45
|
+
spanId: normalizedSpanId,
|
|
46
|
+
sampled: parent.sampled,
|
|
47
|
+
parentSpanId: parent.spanId.toLowerCase(),
|
|
48
|
+
};
|
|
49
|
+
}
|