@bleedingdev/modern-js-plugin-bff 3.8.1-ultramodern.2 → 3.8.1-ultramodern.3

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.
@@ -45,6 +45,9 @@ __webpack_require__.d(__webpack_exports__, {
45
45
  emitDataBatchTransportEvent: ()=>external_batch_transport_js_namespaceObject.emitDataBatchTransportEvent,
46
46
  encodeRequestEnvelopeHeader: ()=>external_codec_js_namespaceObject.encodeRequestEnvelopeHeader,
47
47
  formatTraceparentHeader: ()=>external_trace_js_namespaceObject.formatTraceparentHeader,
48
+ isPlainObject: ()=>external_codec_js_namespaceObject.isPlainObject,
49
+ measureTextBytes: ()=>request_js_namespaceObject.measureTextBytes,
50
+ normalizeMethod: ()=>request_js_namespaceObject.normalizeMethod,
48
51
  normalizeOrigin: ()=>external_codec_js_namespaceObject.normalizeOrigin,
49
52
  parseTraceparentHeader: ()=>external_trace_js_namespaceObject.parseTraceparentHeader,
50
53
  shouldApplyInvalidation: ()=>external_invalidation_js_namespaceObject.shouldApplyInvalidation,
@@ -53,6 +56,7 @@ __webpack_require__.d(__webpack_exports__, {
53
56
  validateRequestEnvelope: ()=>external_validation_js_namespaceObject.validateRequestEnvelope,
54
57
  validateSelectionPlan: ()=>external_validation_js_namespaceObject.validateSelectionPlan
55
58
  });
59
+ const request_js_namespaceObject = require("./batch/request.js");
56
60
  const external_batch_transport_js_namespaceObject = require("./batch-transport.js");
57
61
  const external_codec_js_namespaceObject = require("./codec.js");
58
62
  const external_invalidation_js_namespaceObject = require("./invalidation.js");
@@ -76,6 +80,9 @@ exports.deriveChildTraceContext = __webpack_exports__.deriveChildTraceContext;
76
80
  exports.emitDataBatchTransportEvent = __webpack_exports__.emitDataBatchTransportEvent;
77
81
  exports.encodeRequestEnvelopeHeader = __webpack_exports__.encodeRequestEnvelopeHeader;
78
82
  exports.formatTraceparentHeader = __webpack_exports__.formatTraceparentHeader;
83
+ exports.isPlainObject = __webpack_exports__.isPlainObject;
84
+ exports.measureTextBytes = __webpack_exports__.measureTextBytes;
85
+ exports.normalizeMethod = __webpack_exports__.normalizeMethod;
79
86
  exports.normalizeOrigin = __webpack_exports__.normalizeOrigin;
80
87
  exports.parseTraceparentHeader = __webpack_exports__.parseTraceparentHeader;
81
88
  exports.shouldApplyInvalidation = __webpack_exports__.shouldApplyInvalidation;
@@ -101,6 +108,9 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
101
108
  "emitDataBatchTransportEvent",
102
109
  "encodeRequestEnvelopeHeader",
103
110
  "formatTraceparentHeader",
111
+ "isPlainObject",
112
+ "measureTextBytes",
113
+ "normalizeMethod",
104
114
  "normalizeOrigin",
105
115
  "parseTraceparentHeader",
106
116
  "shouldApplyInvalidation",
@@ -46,12 +46,12 @@ function createDataPlatformBatchRequestHandler(options) {
46
46
  const normalizedEnvelopeHeader = envelopeHeader.toLowerCase();
47
47
  const handle = async (request, context)=>{
48
48
  const mountedPrefix = (0, external_routing_js_namespaceObject.getMountedPrefixFromContext)(request, context);
49
- const method = (0, external_batch_js_namespaceObject.normalizeItemMethod)(request.method);
49
+ const method = (0, index_js_namespaceObject.normalizeMethod)(request.method);
50
50
  if ('POST' !== method) return (0, external_batch_js_namespaceObject.createBatchValidationResponse)('Batch endpoint only supports POST requests', 405);
51
51
  const contentType = request.headers.get('content-type') || '';
52
52
  if (!contentType.includes('application/json')) return (0, external_batch_js_namespaceObject.createBatchValidationResponse)('Batch endpoint requires application/json content-type', 415);
53
53
  const payloadText = await request.text();
54
- if ((0, external_batch_js_namespaceObject.toTextLength)(payloadText) > batchMaxBytes) return (0, external_batch_js_namespaceObject.createBatchValidationResponse)(`Batch payload exceeds max size (${String(batchMaxBytes)} bytes)`, 413);
54
+ if ((0, index_js_namespaceObject.measureTextBytes)(payloadText) > batchMaxBytes) return (0, external_batch_js_namespaceObject.createBatchValidationResponse)(`Batch payload exceeds max size (${String(batchMaxBytes)} bytes)`, 413);
55
55
  let payload;
56
56
  try {
57
57
  payload = JSON.parse(payloadText);
@@ -63,20 +63,20 @@ function createDataPlatformBatchRequestHandler(options) {
63
63
  if (payload.items.length > batchMaxSize) return (0, external_batch_js_namespaceObject.createBatchValidationResponse)(`Batch item count exceeds max size (${String(batchMaxSize)})`, 413);
64
64
  const responseItems = await (0, external_batch_js_namespaceObject.mapWithConcurrency)(payload.items, batchConcurrency, async (rawItem, index)=>{
65
65
  const fallbackId = `item_${String(index)}`;
66
- const itemId = (0, external_batch_js_namespaceObject.isPlainObject)(rawItem) && 'string' == typeof rawItem.id ? rawItem.id : fallbackId;
67
- if (!(0, external_batch_js_namespaceObject.isPlainObject)(rawItem)) return (0, external_batch_js_namespaceObject.toBatchItemError)(itemId, 400, 'Invalid batch item; expected object');
66
+ const itemId = (0, index_js_namespaceObject.isPlainObject)(rawItem) && 'string' == typeof rawItem.id ? rawItem.id : fallbackId;
67
+ if (!(0, index_js_namespaceObject.isPlainObject)(rawItem)) return (0, external_batch_js_namespaceObject.toBatchItemError)(itemId, 400, 'Invalid batch item; expected object');
68
68
  if ('string' != typeof rawItem.path || 0 === rawItem.path.length) return (0, external_batch_js_namespaceObject.toBatchItemError)(itemId, 400, 'Invalid batch item path');
69
69
  if (!rawItem.path.startsWith('/')) return (0, external_batch_js_namespaceObject.toBatchItemError)(itemId, 400, 'Batch item path must start with "/"');
70
70
  const normalizedItemPath = (0, external_routing_js_namespaceObject.removeMountedPrefixFromBatchPath)(rawItem.path, mountedPrefix);
71
71
  const itemPathname = normalizedItemPath.split('?')[0] || normalizedItemPath;
72
72
  if (itemPathname === batchPath || itemPathname.startsWith(`${batchPath}/`)) return (0, external_batch_js_namespaceObject.toBatchItemError)(itemId, 400, 'Batch item path cannot target batch endpoint');
73
- const itemMethod = (0, external_batch_js_namespaceObject.normalizeItemMethod)('string' == typeof rawItem.method ? rawItem.method : void 0);
73
+ const itemMethod = (0, index_js_namespaceObject.normalizeMethod)('string' == typeof rawItem.method ? rawItem.method : void 0);
74
74
  if (!batchAllowedMethods.has(itemMethod)) return (0, external_batch_js_namespaceObject.toBatchItemError)(itemId, 405, `Batch item method ${itemMethod} is not allowed`);
75
75
  if (void 0 !== rawItem.body && null !== rawItem.body && 'string' != typeof rawItem.body) return (0, external_batch_js_namespaceObject.toBatchItemError)(itemId, 400, 'Batch item body must be a string when provided');
76
76
  if (('GET' === itemMethod || 'HEAD' === itemMethod) && 'string' == typeof rawItem.body) return (0, external_batch_js_namespaceObject.toBatchItemError)(itemId, 400, `${itemMethod} batch item cannot include body`);
77
77
  const normalizedHeaders = {};
78
78
  if (void 0 !== rawItem.headers) {
79
- if (!(0, external_batch_js_namespaceObject.isPlainObject)(rawItem.headers)) return (0, external_batch_js_namespaceObject.toBatchItemError)(itemId, 400, 'Batch item headers must be an object');
79
+ if (!(0, index_js_namespaceObject.isPlainObject)(rawItem.headers)) return (0, external_batch_js_namespaceObject.toBatchItemError)(itemId, 400, 'Batch item headers must be an object');
80
80
  for (const [key, value] of Object.entries(rawItem.headers)){
81
81
  if ('string' != typeof value) return (0, external_batch_js_namespaceObject.toBatchItemError)(itemId, 400, `Invalid header "${key}" for batch item`);
82
82
  normalizedHeaders[key.toLowerCase()] = value;
@@ -30,41 +30,21 @@ __webpack_require__.r(__webpack_exports__);
30
30
  __webpack_require__.d(__webpack_exports__, {
31
31
  createBatchValidationResponse: ()=>createBatchValidationResponse,
32
32
  isBatchRequestPayload: ()=>isBatchRequestPayload,
33
- isPlainObject: ()=>isPlainObject,
34
33
  mapWithConcurrency: ()=>mapWithConcurrency,
35
34
  normalizeBatchAllowedMethods: ()=>normalizeBatchAllowedMethods,
36
35
  normalizeBatchPath: ()=>normalizeBatchPath,
37
- normalizeItemMethod: ()=>normalizeItemMethod,
38
36
  prepareJsonRequestBody: ()=>prepareJsonRequestBody,
39
37
  promiseWithTimeout: ()=>promiseWithTimeout,
40
38
  toBatchItemError: ()=>toBatchItemError,
41
- toHeaderRecord: ()=>toHeaderRecord,
42
- toTextLength: ()=>toTextLength
39
+ toHeaderRecord: ()=>headers_js_namespaceObject.toHeaderRecord
43
40
  });
44
41
  const index_js_namespaceObject = require("../../data-platform/index.js");
42
+ const headers_js_namespaceObject = require("../../../utils/headers.js");
45
43
  function normalizeBatchPath(pathname) {
46
44
  if (!pathname || '/' === pathname) return index_js_namespaceObject.DEFAULT_DATA_BATCH_ENDPOINT;
47
45
  if (!pathname.startsWith('/')) return `/${pathname}`;
48
46
  return pathname;
49
47
  }
50
- function isPlainObject(value) {
51
- return 'object' == typeof value && null !== value && !Array.isArray(value);
52
- }
53
- function toTextLength(value) {
54
- if ("u" > typeof TextEncoder) return new TextEncoder().encode(value).length;
55
- if ("u" > typeof Buffer) return Buffer.byteLength(value);
56
- return value.length;
57
- }
58
- function toHeaderRecord(headers) {
59
- const record = {};
60
- headers.forEach((value, key)=>{
61
- record[key] = value;
62
- });
63
- return record;
64
- }
65
- function normalizeItemMethod(method) {
66
- return (method || 'GET').toUpperCase();
67
- }
68
48
  function normalizeBatchAllowedMethods(allowedMethods) {
69
49
  const source = Array.isArray(allowedMethods) && allowedMethods.length > 0 ? allowedMethods : [
70
50
  'GET'
@@ -72,7 +52,7 @@ function normalizeBatchAllowedMethods(allowedMethods) {
72
52
  return new Set(source.map((method)=>method.toUpperCase()));
73
53
  }
74
54
  function isBatchRequestPayload(value) {
75
- return isPlainObject(value) && 1 === value.protocolVersion && 'string' == typeof value.batchId && 'number' == typeof value.sentAt && Array.isArray(value.items);
55
+ return (0, index_js_namespaceObject.isPlainObject)(value) && 1 === value.protocolVersion && 'string' == typeof value.batchId && 'number' == typeof value.sentAt && Array.isArray(value.items);
76
56
  }
77
57
  function createBatchValidationResponse(message, status = 400) {
78
58
  return new Response(JSON.stringify({
@@ -84,16 +64,6 @@ function createBatchValidationResponse(message, status = 400) {
84
64
  }
85
65
  });
86
66
  }
87
- function createJsonValidationResponse(message, status = 400) {
88
- return new Response(JSON.stringify({
89
- message
90
- }), {
91
- status,
92
- headers: {
93
- 'content-type': 'application/json; charset=utf-8'
94
- }
95
- });
96
- }
97
67
  function cloneWithoutJsonBodyHeaders(request) {
98
68
  const headers = new Headers(request.headers);
99
69
  headers.delete('content-type');
@@ -105,7 +75,7 @@ function cloneWithoutJsonBodyHeaders(request) {
105
75
  });
106
76
  }
107
77
  async function prepareJsonRequestBody(request) {
108
- const method = normalizeItemMethod(request.method);
78
+ const method = (0, index_js_namespaceObject.normalizeMethod)(request.method);
109
79
  if ('GET' === method || 'HEAD' === method) return request;
110
80
  const contentType = (request.headers.get('content-type') || '').toLowerCase();
111
81
  if (!contentType.includes('application/json')) return request;
@@ -115,7 +85,7 @@ async function prepareJsonRequestBody(request) {
115
85
  if ('' === bodyText) return cloneWithoutJsonBodyHeaders(request);
116
86
  JSON.parse(bodyText);
117
87
  } catch {
118
- return createJsonValidationResponse('Invalid JSON request body');
88
+ return createBatchValidationResponse('Invalid JSON request body');
119
89
  }
120
90
  return request;
121
91
  }
@@ -166,29 +136,23 @@ async function mapWithConcurrency(items, concurrency, mapper) {
166
136
  }
167
137
  exports.createBatchValidationResponse = __webpack_exports__.createBatchValidationResponse;
168
138
  exports.isBatchRequestPayload = __webpack_exports__.isBatchRequestPayload;
169
- exports.isPlainObject = __webpack_exports__.isPlainObject;
170
139
  exports.mapWithConcurrency = __webpack_exports__.mapWithConcurrency;
171
140
  exports.normalizeBatchAllowedMethods = __webpack_exports__.normalizeBatchAllowedMethods;
172
141
  exports.normalizeBatchPath = __webpack_exports__.normalizeBatchPath;
173
- exports.normalizeItemMethod = __webpack_exports__.normalizeItemMethod;
174
142
  exports.prepareJsonRequestBody = __webpack_exports__.prepareJsonRequestBody;
175
143
  exports.promiseWithTimeout = __webpack_exports__.promiseWithTimeout;
176
144
  exports.toBatchItemError = __webpack_exports__.toBatchItemError;
177
145
  exports.toHeaderRecord = __webpack_exports__.toHeaderRecord;
178
- exports.toTextLength = __webpack_exports__.toTextLength;
179
146
  for(var __rspack_i in __webpack_exports__)if (-1 === [
180
147
  "createBatchValidationResponse",
181
148
  "isBatchRequestPayload",
182
- "isPlainObject",
183
149
  "mapWithConcurrency",
184
150
  "normalizeBatchAllowedMethods",
185
151
  "normalizeBatchPath",
186
- "normalizeItemMethod",
187
152
  "prepareJsonRequestBody",
188
153
  "promiseWithTimeout",
189
154
  "toBatchItemError",
190
- "toHeaderRecord",
191
- "toTextLength"
155
+ "toHeaderRecord"
192
156
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
193
157
  Object.defineProperty(exports, '__esModule', {
194
158
  value: true
@@ -34,7 +34,7 @@ __webpack_require__.d(__webpack_exports__, {
34
34
  isRpcRequest: ()=>isRpcRequest,
35
35
  removeMountedPrefixFromBatchPath: ()=>removeMountedPrefixFromBatchPath
36
36
  });
37
- const external_batch_js_namespaceObject = require("./batch.js");
37
+ const index_js_namespaceObject = require("../../data-platform/index.js");
38
38
  function getRequestPathname(request) {
39
39
  try {
40
40
  return new URL(request.url).pathname;
@@ -47,7 +47,7 @@ function normalizeMountPrefix(prefix) {
47
47
  return prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
48
48
  }
49
49
  function getMountedPrefixFromContext(request, context) {
50
- if (!(0, external_batch_js_namespaceObject.isPlainObject)(context) || 'string' != typeof context.path) return '';
50
+ if (!(0, index_js_namespaceObject.isPlainObject)(context) || 'string' != typeof context.path) return '';
51
51
  const contextPath = normalizeMountPrefix(context.path);
52
52
  const requestPath = normalizeMountPrefix(getRequestPathname(request));
53
53
  if (!contextPath || !requestPath || contextPath === requestPath || !contextPath.endsWith(requestPath)) return '';
@@ -44,6 +44,7 @@ __webpack_require__.d(__webpack_exports__, {
44
44
  const bff_core_namespaceObject = require("@modern-js/bff-core");
45
45
  const external_path_namespaceObject = require("path");
46
46
  var external_path_default = /*#__PURE__*/ __webpack_require__.n(external_path_namespaceObject);
47
+ const external_headers_js_namespaceObject = require("./headers.js");
47
48
  const readNearestPackageVersion = (startDir)=>{
48
49
  if (!startDir) return;
49
50
  let current = external_path_default().resolve(startDir);
@@ -80,16 +81,9 @@ const checkCrossProjectPolicyResponse = (headers, policy)=>{
80
81
  headers: DENIAL_HEADERS
81
82
  });
82
83
  };
83
- const toHeaderRecord = (headers)=>{
84
- const record = {};
85
- headers.forEach((value, key)=>{
86
- record[key] = value;
87
- });
88
- return record;
89
- };
90
84
  const checkCrossProjectPolicyForRequest = (request, policy)=>{
91
85
  if (!policy?.enabled) return null;
92
- return checkCrossProjectPolicyResponse(toHeaderRecord(request.headers), policy);
86
+ return checkCrossProjectPolicyResponse((0, external_headers_js_namespaceObject.toHeaderRecord)(request.headers), policy);
93
87
  };
94
88
  exports.checkCrossProjectPolicyForRequest = __webpack_exports__.checkCrossProjectPolicyForRequest;
95
89
  exports.checkCrossProjectPolicyResponse = __webpack_exports__.checkCrossProjectPolicyResponse;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, getters, values)=>{
5
+ var define = (defs, kind)=>{
6
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
+ enumerable: true,
8
+ [kind]: defs[key]
9
+ });
10
+ };
11
+ define(getters, "get");
12
+ define(values, "value");
13
+ };
14
+ })();
15
+ (()=>{
16
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
+ })();
18
+ (()=>{
19
+ __webpack_require__.r = (exports1)=>{
20
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
+ value: 'Module'
22
+ });
23
+ Object.defineProperty(exports1, '__esModule', {
24
+ value: true
25
+ });
26
+ };
27
+ })();
28
+ var __webpack_exports__ = {};
29
+ __webpack_require__.r(__webpack_exports__);
30
+ function toHeaderRecord(headers) {
31
+ const record = {};
32
+ headers.forEach((value, key)=>{
33
+ record[key] = value;
34
+ });
35
+ return record;
36
+ }
37
+ __webpack_require__.d(__webpack_exports__, {
38
+ toHeaderRecord: ()=>toHeaderRecord
39
+ });
40
+ exports.toHeaderRecord = __webpack_exports__.toHeaderRecord;
41
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
42
+ "toHeaderRecord"
43
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
44
+ Object.defineProperty(exports, '__esModule', {
45
+ value: true
46
+ });
@@ -1,5 +1,6 @@
1
+ export { measureTextBytes, normalizeMethod } from "./batch/request.mjs";
1
2
  export { DATA_BATCH_TRANSPORT_OTEL_EVENT, createDataBatchTransport, createDataBatchTransportTelemetryAttributes, emitDataBatchTransportEvent } from "./batch-transport.mjs";
2
- export { buildQueryKey, buildScopeKey, createOperationId, decodeRequestEnvelopeHeader, encodeRequestEnvelopeHeader, normalizeOrigin, stableStringify } from "./codec.mjs";
3
+ export { buildQueryKey, buildScopeKey, createOperationId, decodeRequestEnvelopeHeader, encodeRequestEnvelopeHeader, isPlainObject, normalizeOrigin, stableStringify } from "./codec.mjs";
3
4
  export { createInvalidationEvent, shouldApplyInvalidation } from "./invalidation.mjs";
4
5
  export { deriveChildTraceContext, formatTraceparentHeader, parseTraceparentHeader } from "./trace.mjs";
5
6
  export { DEFAULT_DATA_BATCH_ENDPOINT, DEFAULT_DATA_BATCH_HEADER, DEFAULT_DATA_ENVELOPE_HEADER } from "./types.mjs";
@@ -1,5 +1,5 @@
1
- import { DEFAULT_DATA_BATCH_HEADER, DEFAULT_DATA_ENVELOPE_HEADER, decodeRequestEnvelopeHeader } from "../../data-platform/index.mjs";
2
- import { createBatchValidationResponse, isBatchRequestPayload, isPlainObject, mapWithConcurrency, normalizeBatchAllowedMethods, normalizeBatchPath, normalizeItemMethod, promiseWithTimeout, toBatchItemError, toHeaderRecord, toTextLength } from "./batch.mjs";
1
+ import { DEFAULT_DATA_BATCH_HEADER, DEFAULT_DATA_ENVELOPE_HEADER, decodeRequestEnvelopeHeader, isPlainObject, measureTextBytes, normalizeMethod } from "../../data-platform/index.mjs";
2
+ import { createBatchValidationResponse, isBatchRequestPayload, mapWithConcurrency, normalizeBatchAllowedMethods, normalizeBatchPath, promiseWithTimeout, toBatchItemError, toHeaderRecord } from "./batch.mjs";
3
3
  import { getMountedPrefixFromContext, removeMountedPrefixFromBatchPath } from "./routing.mjs";
4
4
  function createDataPlatformBatchRequestHandler(options) {
5
5
  const dataPlatformBatchOptions = options.dataPlatform?.batch;
@@ -14,12 +14,12 @@ function createDataPlatformBatchRequestHandler(options) {
14
14
  const normalizedEnvelopeHeader = envelopeHeader.toLowerCase();
15
15
  const handle = async (request, context)=>{
16
16
  const mountedPrefix = getMountedPrefixFromContext(request, context);
17
- const method = normalizeItemMethod(request.method);
17
+ const method = normalizeMethod(request.method);
18
18
  if ('POST' !== method) return createBatchValidationResponse('Batch endpoint only supports POST requests', 405);
19
19
  const contentType = request.headers.get('content-type') || '';
20
20
  if (!contentType.includes('application/json')) return createBatchValidationResponse('Batch endpoint requires application/json content-type', 415);
21
21
  const payloadText = await request.text();
22
- if (toTextLength(payloadText) > batchMaxBytes) return createBatchValidationResponse(`Batch payload exceeds max size (${String(batchMaxBytes)} bytes)`, 413);
22
+ if (measureTextBytes(payloadText) > batchMaxBytes) return createBatchValidationResponse(`Batch payload exceeds max size (${String(batchMaxBytes)} bytes)`, 413);
23
23
  let payload;
24
24
  try {
25
25
  payload = JSON.parse(payloadText);
@@ -38,7 +38,7 @@ function createDataPlatformBatchRequestHandler(options) {
38
38
  const normalizedItemPath = removeMountedPrefixFromBatchPath(rawItem.path, mountedPrefix);
39
39
  const itemPathname = normalizedItemPath.split('?')[0] || normalizedItemPath;
40
40
  if (itemPathname === batchPath || itemPathname.startsWith(`${batchPath}/`)) return toBatchItemError(itemId, 400, 'Batch item path cannot target batch endpoint');
41
- const itemMethod = normalizeItemMethod('string' == typeof rawItem.method ? rawItem.method : void 0);
41
+ const itemMethod = normalizeMethod('string' == typeof rawItem.method ? rawItem.method : void 0);
42
42
  if (!batchAllowedMethods.has(itemMethod)) return toBatchItemError(itemId, 405, `Batch item method ${itemMethod} is not allowed`);
43
43
  if (void 0 !== rawItem.body && null !== rawItem.body && 'string' != typeof rawItem.body) return toBatchItemError(itemId, 400, 'Batch item body must be a string when provided');
44
44
  if (('GET' === itemMethod || 'HEAD' === itemMethod) && 'string' == typeof rawItem.body) return toBatchItemError(itemId, 400, `${itemMethod} batch item cannot include body`);
@@ -1,27 +1,9 @@
1
- import { DEFAULT_DATA_BATCH_ENDPOINT } from "../../data-platform/index.mjs";
1
+ import { DEFAULT_DATA_BATCH_ENDPOINT, isPlainObject, normalizeMethod } from "../../data-platform/index.mjs";
2
2
  function normalizeBatchPath(pathname) {
3
3
  if (!pathname || '/' === pathname) return DEFAULT_DATA_BATCH_ENDPOINT;
4
4
  if (!pathname.startsWith('/')) return `/${pathname}`;
5
5
  return pathname;
6
6
  }
7
- function isPlainObject(value) {
8
- return 'object' == typeof value && null !== value && !Array.isArray(value);
9
- }
10
- function toTextLength(value) {
11
- if ("u" > typeof TextEncoder) return new TextEncoder().encode(value).length;
12
- if ("u" > typeof Buffer) return Buffer.byteLength(value);
13
- return value.length;
14
- }
15
- function toHeaderRecord(headers) {
16
- const record = {};
17
- headers.forEach((value, key)=>{
18
- record[key] = value;
19
- });
20
- return record;
21
- }
22
- function normalizeItemMethod(method) {
23
- return (method || 'GET').toUpperCase();
24
- }
25
7
  function normalizeBatchAllowedMethods(allowedMethods) {
26
8
  const source = Array.isArray(allowedMethods) && allowedMethods.length > 0 ? allowedMethods : [
27
9
  'GET'
@@ -41,16 +23,6 @@ function createBatchValidationResponse(message, status = 400) {
41
23
  }
42
24
  });
43
25
  }
44
- function createJsonValidationResponse(message, status = 400) {
45
- return new Response(JSON.stringify({
46
- message
47
- }), {
48
- status,
49
- headers: {
50
- 'content-type': 'application/json; charset=utf-8'
51
- }
52
- });
53
- }
54
26
  function cloneWithoutJsonBodyHeaders(request) {
55
27
  const headers = new Headers(request.headers);
56
28
  headers.delete('content-type');
@@ -62,7 +34,7 @@ function cloneWithoutJsonBodyHeaders(request) {
62
34
  });
63
35
  }
64
36
  async function prepareJsonRequestBody(request) {
65
- const method = normalizeItemMethod(request.method);
37
+ const method = normalizeMethod(request.method);
66
38
  if ('GET' === method || 'HEAD' === method) return request;
67
39
  const contentType = (request.headers.get('content-type') || '').toLowerCase();
68
40
  if (!contentType.includes('application/json')) return request;
@@ -72,7 +44,7 @@ async function prepareJsonRequestBody(request) {
72
44
  if ('' === bodyText) return cloneWithoutJsonBodyHeaders(request);
73
45
  JSON.parse(bodyText);
74
46
  } catch {
75
- return createJsonValidationResponse('Invalid JSON request body');
47
+ return createBatchValidationResponse('Invalid JSON request body');
76
48
  }
77
49
  return request;
78
50
  }
@@ -121,4 +93,5 @@ async function mapWithConcurrency(items, concurrency, mapper) {
121
93
  await Promise.all(workers);
122
94
  return output;
123
95
  }
124
- export { createBatchValidationResponse, isBatchRequestPayload, isPlainObject, mapWithConcurrency, normalizeBatchAllowedMethods, normalizeBatchPath, normalizeItemMethod, prepareJsonRequestBody, promiseWithTimeout, toBatchItemError, toHeaderRecord, toTextLength };
96
+ export { toHeaderRecord } from "../../../utils/headers.mjs";
97
+ export { createBatchValidationResponse, isBatchRequestPayload, mapWithConcurrency, normalizeBatchAllowedMethods, normalizeBatchPath, prepareJsonRequestBody, promiseWithTimeout, toBatchItemError };
@@ -1,4 +1,4 @@
1
- import { isPlainObject } from "./batch.mjs";
1
+ import { isPlainObject } from "../../data-platform/index.mjs";
2
2
  function getRequestPathname(request) {
3
3
  try {
4
4
  return new URL(request.url).pathname;
@@ -1,5 +1,6 @@
1
1
  import { checkCrossProjectPolicy, deriveOperationVersion, resolveCrossProjectPolicy } from "@modern-js/bff-core";
2
2
  import path from "path";
3
+ import { toHeaderRecord } from "./headers.mjs";
3
4
  const readNearestPackageVersion = (startDir)=>{
4
5
  if (!startDir) return;
5
6
  let current = path.resolve(startDir);
@@ -36,13 +37,6 @@ const checkCrossProjectPolicyResponse = (headers, policy)=>{
36
37
  headers: DENIAL_HEADERS
37
38
  });
38
39
  };
39
- const toHeaderRecord = (headers)=>{
40
- const record = {};
41
- headers.forEach((value, key)=>{
42
- record[key] = value;
43
- });
44
- return record;
45
- };
46
40
  const checkCrossProjectPolicyForRequest = (request, policy)=>{
47
41
  if (!policy?.enabled) return null;
48
42
  return checkCrossProjectPolicyResponse(toHeaderRecord(request.headers), policy);
@@ -0,0 +1,8 @@
1
+ function toHeaderRecord(headers) {
2
+ const record = {};
3
+ headers.forEach((value, key)=>{
4
+ record[key] = value;
5
+ });
6
+ return record;
7
+ }
8
+ export { toHeaderRecord };
@@ -1,6 +1,7 @@
1
1
  import "node:module";
2
+ export { measureTextBytes, normalizeMethod } from "./batch/request.mjs";
2
3
  export { DATA_BATCH_TRANSPORT_OTEL_EVENT, createDataBatchTransport, createDataBatchTransportTelemetryAttributes, emitDataBatchTransportEvent } from "./batch-transport.mjs";
3
- export { buildQueryKey, buildScopeKey, createOperationId, decodeRequestEnvelopeHeader, encodeRequestEnvelopeHeader, normalizeOrigin, stableStringify } from "./codec.mjs";
4
+ export { buildQueryKey, buildScopeKey, createOperationId, decodeRequestEnvelopeHeader, encodeRequestEnvelopeHeader, isPlainObject, normalizeOrigin, stableStringify } from "./codec.mjs";
4
5
  export { createInvalidationEvent, shouldApplyInvalidation } from "./invalidation.mjs";
5
6
  export { deriveChildTraceContext, formatTraceparentHeader, parseTraceparentHeader } from "./trace.mjs";
6
7
  export { DEFAULT_DATA_BATCH_ENDPOINT, DEFAULT_DATA_BATCH_HEADER, DEFAULT_DATA_ENVELOPE_HEADER } from "./types.mjs";
@@ -1,6 +1,6 @@
1
1
  import "node:module";
2
- import { DEFAULT_DATA_BATCH_HEADER, DEFAULT_DATA_ENVELOPE_HEADER, decodeRequestEnvelopeHeader } from "../../data-platform/index.mjs";
3
- import { createBatchValidationResponse, isBatchRequestPayload, isPlainObject, mapWithConcurrency, normalizeBatchAllowedMethods, normalizeBatchPath, normalizeItemMethod, promiseWithTimeout, toBatchItemError, toHeaderRecord, toTextLength } from "./batch.mjs";
2
+ import { DEFAULT_DATA_BATCH_HEADER, DEFAULT_DATA_ENVELOPE_HEADER, decodeRequestEnvelopeHeader, isPlainObject, measureTextBytes, normalizeMethod } from "../../data-platform/index.mjs";
3
+ import { createBatchValidationResponse, isBatchRequestPayload, mapWithConcurrency, normalizeBatchAllowedMethods, normalizeBatchPath, promiseWithTimeout, toBatchItemError, toHeaderRecord } from "./batch.mjs";
4
4
  import { getMountedPrefixFromContext, removeMountedPrefixFromBatchPath } from "./routing.mjs";
5
5
  function createDataPlatformBatchRequestHandler(options) {
6
6
  const dataPlatformBatchOptions = options.dataPlatform?.batch;
@@ -15,12 +15,12 @@ function createDataPlatformBatchRequestHandler(options) {
15
15
  const normalizedEnvelopeHeader = envelopeHeader.toLowerCase();
16
16
  const handle = async (request, context)=>{
17
17
  const mountedPrefix = getMountedPrefixFromContext(request, context);
18
- const method = normalizeItemMethod(request.method);
18
+ const method = normalizeMethod(request.method);
19
19
  if ('POST' !== method) return createBatchValidationResponse('Batch endpoint only supports POST requests', 405);
20
20
  const contentType = request.headers.get('content-type') || '';
21
21
  if (!contentType.includes('application/json')) return createBatchValidationResponse('Batch endpoint requires application/json content-type', 415);
22
22
  const payloadText = await request.text();
23
- if (toTextLength(payloadText) > batchMaxBytes) return createBatchValidationResponse(`Batch payload exceeds max size (${String(batchMaxBytes)} bytes)`, 413);
23
+ if (measureTextBytes(payloadText) > batchMaxBytes) return createBatchValidationResponse(`Batch payload exceeds max size (${String(batchMaxBytes)} bytes)`, 413);
24
24
  let payload;
25
25
  try {
26
26
  payload = JSON.parse(payloadText);
@@ -39,7 +39,7 @@ function createDataPlatformBatchRequestHandler(options) {
39
39
  const normalizedItemPath = removeMountedPrefixFromBatchPath(rawItem.path, mountedPrefix);
40
40
  const itemPathname = normalizedItemPath.split('?')[0] || normalizedItemPath;
41
41
  if (itemPathname === batchPath || itemPathname.startsWith(`${batchPath}/`)) return toBatchItemError(itemId, 400, 'Batch item path cannot target batch endpoint');
42
- const itemMethod = normalizeItemMethod('string' == typeof rawItem.method ? rawItem.method : void 0);
42
+ const itemMethod = normalizeMethod('string' == typeof rawItem.method ? rawItem.method : void 0);
43
43
  if (!batchAllowedMethods.has(itemMethod)) return toBatchItemError(itemId, 405, `Batch item method ${itemMethod} is not allowed`);
44
44
  if (void 0 !== rawItem.body && null !== rawItem.body && 'string' != typeof rawItem.body) return toBatchItemError(itemId, 400, 'Batch item body must be a string when provided');
45
45
  if (('GET' === itemMethod || 'HEAD' === itemMethod) && 'string' == typeof rawItem.body) return toBatchItemError(itemId, 400, `${itemMethod} batch item cannot include body`);
@@ -1,28 +1,10 @@
1
1
  import "node:module";
2
- import { DEFAULT_DATA_BATCH_ENDPOINT } from "../../data-platform/index.mjs";
2
+ import { DEFAULT_DATA_BATCH_ENDPOINT, isPlainObject, normalizeMethod } from "../../data-platform/index.mjs";
3
3
  function normalizeBatchPath(pathname) {
4
4
  if (!pathname || '/' === pathname) return DEFAULT_DATA_BATCH_ENDPOINT;
5
5
  if (!pathname.startsWith('/')) return `/${pathname}`;
6
6
  return pathname;
7
7
  }
8
- function isPlainObject(value) {
9
- return 'object' == typeof value && null !== value && !Array.isArray(value);
10
- }
11
- function toTextLength(value) {
12
- if ("u" > typeof TextEncoder) return new TextEncoder().encode(value).length;
13
- if ("u" > typeof Buffer) return Buffer.byteLength(value);
14
- return value.length;
15
- }
16
- function toHeaderRecord(headers) {
17
- const record = {};
18
- headers.forEach((value, key)=>{
19
- record[key] = value;
20
- });
21
- return record;
22
- }
23
- function normalizeItemMethod(method) {
24
- return (method || 'GET').toUpperCase();
25
- }
26
8
  function normalizeBatchAllowedMethods(allowedMethods) {
27
9
  const source = Array.isArray(allowedMethods) && allowedMethods.length > 0 ? allowedMethods : [
28
10
  'GET'
@@ -42,16 +24,6 @@ function createBatchValidationResponse(message, status = 400) {
42
24
  }
43
25
  });
44
26
  }
45
- function createJsonValidationResponse(message, status = 400) {
46
- return new Response(JSON.stringify({
47
- message
48
- }), {
49
- status,
50
- headers: {
51
- 'content-type': 'application/json; charset=utf-8'
52
- }
53
- });
54
- }
55
27
  function cloneWithoutJsonBodyHeaders(request) {
56
28
  const headers = new Headers(request.headers);
57
29
  headers.delete('content-type');
@@ -63,7 +35,7 @@ function cloneWithoutJsonBodyHeaders(request) {
63
35
  });
64
36
  }
65
37
  async function prepareJsonRequestBody(request) {
66
- const method = normalizeItemMethod(request.method);
38
+ const method = normalizeMethod(request.method);
67
39
  if ('GET' === method || 'HEAD' === method) return request;
68
40
  const contentType = (request.headers.get('content-type') || '').toLowerCase();
69
41
  if (!contentType.includes('application/json')) return request;
@@ -73,7 +45,7 @@ async function prepareJsonRequestBody(request) {
73
45
  if ('' === bodyText) return cloneWithoutJsonBodyHeaders(request);
74
46
  JSON.parse(bodyText);
75
47
  } catch {
76
- return createJsonValidationResponse('Invalid JSON request body');
48
+ return createBatchValidationResponse('Invalid JSON request body');
77
49
  }
78
50
  return request;
79
51
  }
@@ -122,4 +94,5 @@ async function mapWithConcurrency(items, concurrency, mapper) {
122
94
  await Promise.all(workers);
123
95
  return output;
124
96
  }
125
- export { createBatchValidationResponse, isBatchRequestPayload, isPlainObject, mapWithConcurrency, normalizeBatchAllowedMethods, normalizeBatchPath, normalizeItemMethod, prepareJsonRequestBody, promiseWithTimeout, toBatchItemError, toHeaderRecord, toTextLength };
97
+ export { toHeaderRecord } from "../../../utils/headers.mjs";
98
+ export { createBatchValidationResponse, isBatchRequestPayload, mapWithConcurrency, normalizeBatchAllowedMethods, normalizeBatchPath, prepareJsonRequestBody, promiseWithTimeout, toBatchItemError };
@@ -1,5 +1,5 @@
1
1
  import "node:module";
2
- import { isPlainObject } from "./batch.mjs";
2
+ import { isPlainObject } from "../../data-platform/index.mjs";
3
3
  function getRequestPathname(request) {
4
4
  try {
5
5
  return new URL(request.url).pathname;
@@ -2,6 +2,7 @@ import __rslib_shim_module__ from "node:module";
2
2
  const require = /*#__PURE__*/ __rslib_shim_module__.createRequire(/*#__PURE__*/ (()=>import.meta.url)());
3
3
  import { checkCrossProjectPolicy, deriveOperationVersion, resolveCrossProjectPolicy } from "@modern-js/bff-core";
4
4
  import path from "path";
5
+ import { toHeaderRecord } from "./headers.mjs";
5
6
  const readNearestPackageVersion = (startDir)=>{
6
7
  if (!startDir) return;
7
8
  let current = path.resolve(startDir);
@@ -38,13 +39,6 @@ const checkCrossProjectPolicyResponse = (headers, policy)=>{
38
39
  headers: DENIAL_HEADERS
39
40
  });
40
41
  };
41
- const toHeaderRecord = (headers)=>{
42
- const record = {};
43
- headers.forEach((value, key)=>{
44
- record[key] = value;
45
- });
46
- return record;
47
- };
48
42
  const checkCrossProjectPolicyForRequest = (request, policy)=>{
49
43
  if (!policy?.enabled) return null;
50
44
  return checkCrossProjectPolicyResponse(toHeaderRecord(request.headers), policy);
@@ -0,0 +1,9 @@
1
+ import "node:module";
2
+ function toHeaderRecord(headers) {
3
+ const record = {};
4
+ headers.forEach((value, key)=>{
5
+ record[key] = value;
6
+ });
7
+ return record;
8
+ }
9
+ export { toHeaderRecord };
@@ -1,5 +1,6 @@
1
+ export { measureTextBytes, normalizeMethod } from './batch/request';
1
2
  export { createDataBatchTransport, createDataBatchTransportTelemetryAttributes, DATA_BATCH_TRANSPORT_OTEL_EVENT, emitDataBatchTransportEvent, } from './batch-transport';
2
- export { buildQueryKey, buildScopeKey, createOperationId, decodeRequestEnvelopeHeader, encodeRequestEnvelopeHeader, normalizeOrigin, stableStringify, } from './codec';
3
+ export { buildQueryKey, buildScopeKey, createOperationId, decodeRequestEnvelopeHeader, encodeRequestEnvelopeHeader, isPlainObject, normalizeOrigin, stableStringify, } from './codec';
3
4
  export { createInvalidationEvent, shouldApplyInvalidation, } from './invalidation';
4
5
  export { deriveChildTraceContext, formatTraceparentHeader, parseTraceparentHeader, } from './trace';
5
6
  export type { CacheScope, DataBatchRequestItem, DataBatchRequestPayload, DataBatchResponseItem, DataBatchResponsePayload, DataBatchTransportEvent, DataBatchTransportOptions, DataBatchTransportTelemetryAttributes, DataMutationMode, DataRequestMode, HydrationEnvelope, HydrationEnvelopeValidationOptions, InvalidationEvent, InvalidationSubscriber, OperationDescriptor, RequestEnvelope, RequestEnvelopeValidationOptions, SelectionPlan, SelectionPlanValidationOptions, SelectionPlanValidationResult, TraceContext, } from './types';
@@ -1,9 +1,6 @@
1
1
  import { type DataBatchRequestPayload, type DataBatchResponseItem } from '../../data-platform';
2
+ export { toHeaderRecord } from '../../../utils/headers';
2
3
  export declare function normalizeBatchPath(pathname: string | undefined): `/${string}`;
3
- export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
4
- export declare function toTextLength(value: string): number;
5
- export declare function toHeaderRecord(headers: Headers): Record<string, string>;
6
- export declare function normalizeItemMethod(method: string | undefined): string;
7
4
  export declare function normalizeBatchAllowedMethods(allowedMethods: string[] | undefined): Set<string>;
8
5
  type ParsedBatchRequestPayload = Omit<DataBatchRequestPayload, 'items'> & {
9
6
  items: unknown[];
@@ -14,4 +11,3 @@ export declare function prepareJsonRequestBody(request: Request): Promise<Reques
14
11
  export declare function toBatchItemError(id: string, status: number, message: string): DataBatchResponseItem;
15
12
  export declare function promiseWithTimeout<T>(effect: Promise<T>, timeoutMs: number): Promise<T>;
16
13
  export declare function mapWithConcurrency<TItem, TResult>(items: readonly TItem[], concurrency: number, mapper: (item: TItem, index: number) => Promise<TResult>): Promise<TResult[]>;
17
- export {};
@@ -0,0 +1 @@
1
+ export declare function toHeaderRecord(headers: Headers): Record<string, string>;
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "modern",
18
18
  "modern.js"
19
19
  ],
20
- "version": "3.8.1-ultramodern.2",
20
+ "version": "3.8.1-ultramodern.3",
21
21
  "types": "./dist/types/cli.d.ts",
22
22
  "main": "./dist/cjs/cli.js",
23
23
  "exports": {
@@ -169,12 +169,12 @@
169
169
  }
170
170
  },
171
171
  "dependencies": {
172
- "@modern-js/bff-core": "npm:@bleedingdev/modern-js-bff-core@3.8.1-ultramodern.2",
173
- "@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.8.1-ultramodern.2",
174
- "@modern-js/create-request": "npm:@bleedingdev/modern-js-create-request@3.8.1-ultramodern.2",
175
- "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.8.1-ultramodern.2",
176
- "@modern-js/server-utils": "npm:@bleedingdev/modern-js-server-utils@3.8.1-ultramodern.2",
177
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.8.1-ultramodern.2",
172
+ "@modern-js/bff-core": "npm:@bleedingdev/modern-js-bff-core@3.8.1-ultramodern.3",
173
+ "@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.8.1-ultramodern.3",
174
+ "@modern-js/create-request": "npm:@bleedingdev/modern-js-create-request@3.8.1-ultramodern.3",
175
+ "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.8.1-ultramodern.3",
176
+ "@modern-js/server-utils": "npm:@bleedingdev/modern-js-server-utils@3.8.1-ultramodern.3",
177
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.8.1-ultramodern.3",
178
178
  "@module-federation/runtime": "2.8.2",
179
179
  "@opentelemetry/api": "1.9.1",
180
180
  "@opentelemetry/api-logs": "0.220.0",
@@ -205,11 +205,11 @@
205
205
  },
206
206
  "devDependencies": {
207
207
  "@effect/opentelemetry": "4.0.0-beta.107",
208
- "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.8.1-ultramodern.2",
209
- "@modern-js/bff-runtime": "npm:@bleedingdev/modern-js-bff-runtime@3.8.1-ultramodern.2",
210
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.8.1-ultramodern.2",
211
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.8.1-ultramodern.2",
212
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.8.1-ultramodern.2",
208
+ "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.8.1-ultramodern.3",
209
+ "@modern-js/bff-runtime": "npm:@bleedingdev/modern-js-bff-runtime@3.8.1-ultramodern.3",
210
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.8.1-ultramodern.3",
211
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.8.1-ultramodern.3",
212
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.8.1-ultramodern.3",
213
213
  "@rsbuild/core": "2.1.5",
214
214
  "@rslib/core": "0.23.2",
215
215
  "@scripts/rstest-config": "2.66.0",
@@ -1,61 +0,0 @@
1
- # UltraModern Data Platform Architecture (MF-safe)
2
-
3
- This document defines a clean, greenfield data architecture for Fate-style orchestration while preserving Module Federation and micro-frontend interoperability.
4
-
5
- ## Goals
6
-
7
- - One canonical data contract for requests, views, actions, hydration, and invalidation.
8
- - Deterministic behavior across SSR/CSR and host/remote applications.
9
- - Strong tenant/app isolation by default, explicit cross-app communication only.
10
- - Protocol-level observability and integrity checks.
11
-
12
- ## Core Contracts
13
-
14
- Implemented in `src/runtime/data-platform/index.ts`:
15
-
16
- - Operation identity: `createOperationId`
17
- - Cache/request scoping: `buildScopeKey`, `buildQueryKey`
18
- - Selection safety: `validateSelectionPlan`
19
- - Distributed tracing: `parseTraceparentHeader`, `formatTraceparentHeader`, `deriveChildTraceContext`
20
- - Request envelope: `createRequestEnvelope`, `validateRequestEnvelope`
21
- - Hydration integrity: `createHydrationEnvelope`, `validateHydrationEnvelope`
22
- - Invalidation routing: `createInvalidationEvent`, `shouldApplyInvalidation`
23
-
24
- ## Mitigations Applied
25
-
26
- 1. Operation ID collisions
27
- - IDs include namespace + api/group/endpoint + version + schema hash contribution.
28
- - IDs are deterministic and hashed from canonical payloads.
29
-
30
- 2. Cross-app cache contamination
31
- - Scope key includes normalized origin + app namespace + identity dimensions.
32
- - Query keys include scope key and selection/input fingerprints.
33
-
34
- 3. Invalid selection pushdown
35
- - Selection plans are depth-limited, field-limited, and optional allow-list validated.
36
-
37
- 4. Trace discontinuity across host/remotes
38
- - Trace context is first-class and validated through W3C traceparent format.
39
- - Child spans preserve trace id and parent linkage.
40
-
41
- 5. Hydration poisoning/staleness
42
- - Hydration payloads include checksum over canonical serialized payload + metadata.
43
- - Runtime/namespace/origin/protocol checks are validated.
44
-
45
- 6. Unintended cross-namespace invalidation
46
- - Default: no cross-namespace invalidation.
47
- - Cross-app invalidation requires explicit target namespace + opt-in subscriber.
48
-
49
- ## Test Strategy
50
-
51
- Contract and scenario coverage is in `tests/data-platform-contract.test.ts`:
52
-
53
- - Deterministic and collision-resistant operation IDs.
54
- - Scope/query key isolation across namespaces and normalized origins.
55
- - Selection validation guardrails (depth, count, allow-list, shape).
56
- - Request envelope integrity and required trace context checks.
57
- - Hydration checksum tamper detection.
58
- - Invalidation routing policy enforcement.
59
- - Simulated host/remote mutation and trace propagation scenarios.
60
-
61
- This suite is intended to be the non-negotiable compatibility gate for future data-runtime changes.