@bleedingdev/modern-js-bff-effect 3.9.0-ultramodern.11 → 3.9.0-ultramodern.13

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.
Files changed (51) hide show
  1. package/dist/cjs/data-platform/batch/options.js +48 -0
  2. package/dist/cjs/data-platform/batch/queue.js +48 -83
  3. package/dist/cjs/effect/entry-shape.js +4 -1
  4. package/dist/cjs/effect/handler/batch-handler.js +2 -3
  5. package/dist/cjs/effect/handler/batch.js +0 -9
  6. package/dist/cjs/effect/module.js +4 -0
  7. package/dist/cjs/effect/operation-context.js +1 -1
  8. package/dist/cjs/effect/rpcSerialization.js +0 -2
  9. package/dist/cjs/effect-client/cross-project.js +5 -8
  10. package/dist/cjs/microvertical-api.js +1 -1
  11. package/dist/esm/data-platform/batch/options.mjs +10 -0
  12. package/dist/esm/data-platform/batch/queue.mjs +48 -83
  13. package/dist/esm/effect/entry-shape-registry.mjs +2 -2
  14. package/dist/esm/effect/handler/batch-handler.mjs +3 -4
  15. package/dist/esm/effect/handler/batch.mjs +1 -7
  16. package/dist/esm/effect/module.mjs +4 -0
  17. package/dist/esm/effect/operation-context.mjs +1 -1
  18. package/dist/esm/effect/rpcSerialization.mjs +0 -2
  19. package/dist/esm/effect-client/cross-project.mjs +6 -9
  20. package/dist/esm/microvertical-api.mjs +1 -1
  21. package/dist/esm-node/data-platform/batch/options.mjs +11 -0
  22. package/dist/esm-node/data-platform/batch/queue.mjs +48 -83
  23. package/dist/esm-node/effect/entry-shape.mjs +4 -1
  24. package/dist/esm-node/effect/handler/batch-handler.mjs +3 -4
  25. package/dist/esm-node/effect/handler/batch.mjs +1 -7
  26. package/dist/esm-node/effect/module.mjs +4 -0
  27. package/dist/esm-node/effect/operation-context.mjs +1 -1
  28. package/dist/esm-node/effect/rpcSerialization.mjs +0 -2
  29. package/dist/esm-node/effect-client/cross-project.mjs +6 -9
  30. package/dist/esm-node/microvertical-api.mjs +1 -1
  31. package/dist/types/data-platform/batch/options.d.ts +7 -0
  32. package/dist/types/data-platform/batch/queue.d.ts +0 -1
  33. package/dist/types/effect/entry-shape.d.ts +2 -0
  34. package/dist/types/effect/handler/batch.d.ts +0 -1
  35. package/dist/types/effect/handler/types.d.ts +1 -1
  36. package/dist/types/effect/module.d.ts +2 -1
  37. package/dist/types/effect-client/index.d.ts +3 -3
  38. package/dist/types/microvertical-api.d.ts +4 -4
  39. package/package.json +12 -12
  40. package/src/data-platform/batch/options.ts +20 -0
  41. package/src/data-platform/batch/queue.ts +61 -89
  42. package/src/effect/entry-shape.ts +12 -0
  43. package/src/effect/handler/batch-handler.ts +6 -12
  44. package/src/effect/handler/batch.ts +0 -10
  45. package/src/effect/handler/types.ts +1 -2
  46. package/src/effect/module.ts +7 -0
  47. package/src/effect/operation-context.ts +1 -1
  48. package/src/effect/rpcSerialization.ts +0 -2
  49. package/src/effect-client/cross-project.ts +9 -14
  50. package/src/effect-client/index.ts +4 -4
  51. package/src/microvertical-api.ts +1 -1
@@ -1,7 +1,8 @@
1
1
  import "node:module";
2
2
  import { DEFAULT_DATA_BATCH_HEADER, DEFAULT_DATA_ENVELOPE_HEADER, decodeRequestEnvelopeHeader, isPlainObject, normalizeMethod } from "../../data-platform/index.mjs";
3
+ import { normalizeBatchLimits } from "../../data-platform/batch/options.mjs";
3
4
  import { decodeBatchBody, isBatchBody, toBatchResponseItem } from "../../data-platform/batch/protocol.mjs";
4
- import { BatchItemTimeoutError, createBatchValidationResponse, isBatchRequestPayload, mapWithConcurrency, normalizeBatchAllowedMethods, normalizeBatchPath, promiseWithTimeout, toBatchItemError } from "./batch.mjs";
5
+ import { BatchItemTimeoutError, createBatchValidationResponse, isBatchRequestPayload, mapWithConcurrency, normalizeBatchPath, promiseWithTimeout, toBatchItemError } from "./batch.mjs";
5
6
  import { getMountedPrefixFromContext, removeMountedPrefixFromBatchPath } from "./routing.mjs";
6
7
  import * as __rspack_external_effect_Effect_41060786 from "effect/Effect";
7
8
  const HOP_BY_HOP_BATCH_ITEM_HEADERS = new Set([
@@ -92,11 +93,9 @@ function createDataPlatformBatchRequestHandler(options) {
92
93
  const dataPlatformBatchOptions = options.dataPlatform?.batch;
93
94
  const batchEnabled = dataPlatformBatchOptions?.enabled !== false;
94
95
  const batchPath = normalizeBatchPath(dataPlatformBatchOptions?.endpoint);
95
- const batchMaxSize = Math.max(1, dataPlatformBatchOptions?.maxBatchSize ?? 16);
96
- const batchMaxBytes = Math.max(1024, dataPlatformBatchOptions?.maxBatchBytes ?? 65536);
96
+ const { maxBatchSize: batchMaxSize, maxBatchBytes: batchMaxBytes, allowedMethods: batchAllowedMethods } = normalizeBatchLimits(dataPlatformBatchOptions);
97
97
  const batchConcurrency = Math.max(1, dataPlatformBatchOptions?.maxConcurrency ?? 4);
98
98
  const batchItemTimeoutMs = Math.max(0, dataPlatformBatchOptions?.requestTimeoutMs ?? 10000);
99
- const batchAllowedMethods = normalizeBatchAllowedMethods(dataPlatformBatchOptions?.allowedMethods);
100
99
  const envelopeHeader = options.dataPlatform?.envelopeHeader || DEFAULT_DATA_ENVELOPE_HEADER;
101
100
  const normalizedEnvelopeHeader = envelopeHeader.toLowerCase();
102
101
  const handle = async (request, context)=>{
@@ -7,12 +7,6 @@ function normalizeBatchPath(pathname) {
7
7
  if (!pathname.startsWith('/')) return `/${pathname}`;
8
8
  return pathname;
9
9
  }
10
- function normalizeBatchAllowedMethods(allowedMethods) {
11
- const source = Array.isArray(allowedMethods) && allowedMethods.length > 0 ? allowedMethods : [
12
- 'GET'
13
- ];
14
- return new Set(source.map((method)=>method.toUpperCase()));
15
- }
16
10
  function isBatchRequestPayload(value) {
17
11
  return isPlainObject(value) && 2 === value.protocolVersion && 'string' == typeof value.batchId && 'number' == typeof value.sentAt && Array.isArray(value.items);
18
12
  }
@@ -110,4 +104,4 @@ async function mapWithConcurrency(items, concurrency, mapper) {
110
104
  return output;
111
105
  }
112
106
  export { toHeaderRecord } from "../../headers.mjs";
113
- export { BatchItemTimeoutError, createBatchValidationResponse, isBatchRequestPayload, mapWithConcurrency, normalizeBatchAllowedMethods, normalizeBatchPath, prepareJsonRequestBody, promiseWithTimeout, toBatchItemError };
107
+ export { BatchItemTimeoutError, createBatchValidationResponse, isBatchRequestPayload, mapWithConcurrency, normalizeBatchPath, prepareJsonRequestBody, promiseWithTimeout, toBatchItemError };
@@ -43,6 +43,10 @@ function resolveClassifiedEffectBffModuleHandler(facts, options = {}) {
43
43
  if (facts.unsupportedShape) {
44
44
  if (rejectUnsupportedEffectModuleShape(options, facts.unsupportedShape)) return null;
45
45
  }
46
+ if (void 0 !== facts.module.rpc && !facts.createHandlerValidatorAware) {
47
+ rejectUnsupportedEffectModuleShape(options, 'unbranded RPC runtime');
48
+ return null;
49
+ }
46
50
  if ('function' == typeof facts.createHandler) {
47
51
  if (facts.createHandlerValidatorAware) {
48
52
  const factory = facts.createHandler;
@@ -55,7 +55,7 @@ const createEffectOperationContext = ({ request, path, method })=>{
55
55
  } : {},
56
56
  routePath: servicePath,
57
57
  method: (method || request.method || 'GET').toUpperCase(),
58
- source: 'effect-adapter',
58
+ source: 'effect-server',
59
59
  ...path && path !== servicePath ? {
60
60
  attributes: {
61
61
  mountedPath: path
@@ -8,8 +8,6 @@ function getRpcSerializationLayer(serialization) {
8
8
  return RpcSerialization.layerJsonRpc();
9
9
  case 'ndJsonRpc':
10
10
  return RpcSerialization.layerNdJsonRpc();
11
- case 'msgPack':
12
- return RpcSerialization.layerMsgPack;
13
11
  default:
14
12
  return RpcSerialization.layerJsonRpc();
15
13
  }
@@ -1,5 +1,5 @@
1
1
  import "node:module";
2
- import { BFF_ENVELOPE_HEADER, BFF_OPERATION_CONTEXT_DETAIL_HEADER, BFF_OPERATION_CONTEXT_HEADER, resolveCrossProjectRequestObservation } from "@modern-js/server-runtime-extensions/bff-policy";
2
+ import { BFF_ENVELOPE_HEADER, BFF_OPERATION_CONTEXT_DETAIL_HEADER, BFF_OPERATION_CONTEXT_HEADER, digestOperationContract, resolveCrossProjectRequestObservation } from "@modern-js/server-runtime-extensions/bff-policy";
3
3
  import { HttpClient, HttpClientRequest } from "effect/unstable/http";
4
4
  import { HttpApi } from "effect/unstable/httpapi";
5
5
  import * as __rspack_external_effect_Effect_41060786 from "effect/Effect";
@@ -31,14 +31,11 @@ function withCrossProjectPolicy(api, options, client) {
31
31
  const contract = observed && contracts[`${observed.method}:${observed.routePath}`];
32
32
  if (void 0 === contract) return __rspack_external_effect_Effect_41060786.die(new Error('[BFF][Effect] Request has no matching shared operation contract.'));
33
33
  return __rspack_external_effect_Effect_41060786.gen(function*() {
34
- const input = encodeJson({
35
- httpMethod: contract.method,
36
- name: contract.name,
37
- requestId: options.requestId,
38
- routePath: contract.routePath
39
- });
40
- const digest = yield* __rspack_external_effect_Effect_41060786.promise(()=>globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(input)));
41
- const schemaHash = Array.from(new Uint8Array(digest), (value)=>value.toString(16).padStart(2, '0')).join('');
34
+ const schemaHash = yield* __rspack_external_effect_Effect_41060786.promise(()=>digestOperationContract({
35
+ httpMethod: contract.method,
36
+ name: contract.name,
37
+ routePath: contract.routePath
38
+ }, options.requestId));
42
39
  return request.pipe(HttpClientRequest.setHeader(BFF_ENVELOPE_HEADER, encodeJson({
43
40
  requestId: options.requestId
44
41
  })), HttpClientRequest.setHeader(BFF_OPERATION_CONTEXT_HEADER, contract.operationId), HttpClientRequest.setHeader(BFF_OPERATION_CONTEXT_DETAIL_HEADER, encodeJson({
@@ -30,7 +30,7 @@ const MicroVerticalOperationSourceSchema = Schema.Literals([
30
30
  'client',
31
31
  'server',
32
32
  'generated-client',
33
- 'effect-adapter',
33
+ 'effect-server',
34
34
  'data-platform',
35
35
  'unknown'
36
36
  ]);
@@ -0,0 +1,7 @@
1
+ import type { DataBatchTransportOptions } from '../types.js';
2
+ /** Wire limits and method eligibility are identical on both sides. */
3
+ export declare function normalizeBatchLimits(options?: Pick<DataBatchTransportOptions, 'maxBatchSize' | 'maxBatchBytes' | 'allowedMethods'>): {
4
+ maxBatchSize: number;
5
+ maxBatchBytes: number;
6
+ allowedMethods: Set<string>;
7
+ };
@@ -30,7 +30,6 @@ type QueuedBatchRequest = {
30
30
  };
31
31
  type BatchBucket = {
32
32
  items: QueuedBatchRequest[];
33
- bytes: number;
34
33
  batchId: string;
35
34
  sentAt: number;
36
35
  timer: ReturnType<typeof setTimeout> | null;
@@ -1,6 +1,8 @@
1
+ import type { EffectRpcBffDefinition } from './handler/types.js';
1
2
  type EffectBffEntryModule = {
2
3
  api?: unknown;
3
4
  layer?: unknown;
5
+ rpc?: EffectRpcBffDefinition;
4
6
  handler?: unknown;
5
7
  createHandler?: unknown;
6
8
  default?: unknown;
@@ -1,7 +1,6 @@
1
1
  import { type DataBatchRequestPayload, type DataBatchResponseItem } from '../../data-platform/index.js';
2
2
  export { toHeaderRecord } from '../../headers.js';
3
3
  export declare function normalizeBatchPath(pathname: string | undefined): `/${string}`;
4
- export declare function normalizeBatchAllowedMethods(allowedMethods: string[] | undefined): Set<string>;
5
4
  type ParsedBatchRequestPayload = Omit<DataBatchRequestPayload, 'items'> & {
6
5
  items: unknown[];
7
6
  };
@@ -9,7 +9,7 @@ import type { createRpcApiHandler } from './rpc.js';
9
9
  export type EffectRuntimeRequirements = Etag.Generator | FileSystem.FileSystem | HttpPlatform.HttpPlatform | HttpRouter.HttpRouter | HttpRouter.Request<'Error', unknown> | HttpRouter.Request<'GlobalError', unknown> | HttpRouter.Request<'GlobalRequires', unknown> | HttpRouter.Request<'Requires', unknown> | Path.Path;
10
10
  export type EffectRuntimeLayer = Layer.Layer<never, never, EffectRuntimeRequirements>;
11
11
  export declare const toEffectServiceContext: (context: unknown) => Context.Context<any>;
12
- export type EffectRpcSerialization = 'json' | 'ndjson' | 'jsonRpc' | 'ndJsonRpc' | 'msgPack';
12
+ export type EffectRpcSerialization = 'json' | 'ndjson' | 'jsonRpc' | 'ndJsonRpc';
13
13
  export type EffectRpcRuntimeLayer<TRpcs extends Rpc.Any = Rpc.Any> = Layer.Layer<Rpc.ToHandler<TRpcs> | Rpc.Middleware<TRpcs> | Rpc.ServicesServer<TRpcs>, unknown, never>;
14
14
  export type EffectRpcBffDefinition<TRpcs extends Rpc.Any = Rpc.Any, TRpcLayer extends EffectRpcRuntimeLayer<TRpcs> = EffectRpcRuntimeLayer<TRpcs>> = {
15
15
  group: RpcGroup.RpcGroup<TRpcs>;
@@ -1,5 +1,5 @@
1
1
  import type * as EffectServiceContext from 'effect/Context';
2
- import { type EffectBffOpenApiConfig, type EffectDataPlatformValidationOptions, type EffectRequestValidator } from './handler.js';
2
+ import { type EffectBffOpenApiConfig, type EffectDataPlatformValidationOptions, type EffectRequestValidator, type EffectRpcBffDefinition } from './handler.js';
3
3
  import type { EffectContext } from './operation-context.js';
4
4
  export type EffectBffRequestHandler = (request: Request, context?: EffectServiceContext.Context<any> | EffectContext) => Promise<Response> | Response;
5
5
  export type EffectBffHandlerFactory = (options?: {
@@ -13,6 +13,7 @@ export type EffectBffHandlerFactory = (options?: {
13
13
  export type EffectApiModule = {
14
14
  api?: unknown;
15
15
  layer?: unknown;
16
+ rpc?: EffectRpcBffDefinition;
16
17
  handler?: EffectBffRequestHandler;
17
18
  createHandler?: EffectBffHandlerFactory;
18
19
  default?: unknown;
@@ -3,7 +3,7 @@ import * as Effect from 'effect/Effect';
3
3
  import * as Layer from 'effect/Layer';
4
4
  import { HttpClient } from 'effect/unstable/http';
5
5
  import { HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from 'effect/unstable/httpapi';
6
- import { Rpc, RpcClient, RpcGroup, RpcSchema, RpcSerialization } from 'effect/unstable/rpc';
6
+ import { Rpc, RpcClient, RpcClientError, RpcGroup, RpcSchema, RpcSerialization } from 'effect/unstable/rpc';
7
7
  import { type EffectCrossProjectClientOptions } from './cross-project.js';
8
8
  export * as Effect from 'effect/Effect';
9
9
  export * as Layer from 'effect/Layer';
@@ -30,7 +30,7 @@ export type EffectViewSelection<T> = {
30
30
  export type EffectViewData<T, Selection> = Selection extends true ? T : Selection extends Record<string, unknown> ? {
31
31
  [K in keyof Selection & keyof T]: EffectMaskedValue<T[K], Selection[K]>;
32
32
  } : T;
33
- export type EffectRpcSerialization = 'json' | 'ndjson' | 'jsonRpc' | 'ndJsonRpc' | 'msgPack';
33
+ export type EffectRpcSerialization = 'json' | 'ndjson' | 'jsonRpc' | 'ndJsonRpc';
34
34
  declare const EffectRpcClientError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
35
35
  readonly _tag: "EffectRpcClientError";
36
36
  } & Readonly<A>;
@@ -50,7 +50,7 @@ export type EffectRpcClientOptions<Rpcs extends Rpc.Any, Flatten extends boolean
50
50
  flatten?: Flatten;
51
51
  serialization?: EffectRpcSerialization;
52
52
  };
53
- export type EffectRpcClientHandle<Rpcs extends Rpc.Any, Flatten extends boolean = false> = (Flatten extends true ? RpcClient.RpcClient.Flat<Rpcs, unknown> : RpcClient.RpcClient<Rpcs, unknown>) & {
53
+ export type EffectRpcClientHandle<Rpcs extends Rpc.Any, Flatten extends boolean = false> = (Flatten extends true ? RpcClient.RpcClient.Flat<Rpcs, RpcClientError.RpcClientError> : RpcClient.RpcClient<Rpcs, RpcClientError.RpcClientError>) & {
54
54
  dispose: () => Promise<void>;
55
55
  };
56
56
  export declare function view<T>(): <const Selection extends EffectViewSelection<T>>(selection: Selection) => Selection;
@@ -33,13 +33,13 @@ export declare const MicroVerticalReadinessSchema: Schema.Struct<{
33
33
  readonly versionSkew: Schema.Literal<"none">;
34
34
  }>;
35
35
  export type MicroVerticalReadiness = typeof MicroVerticalReadinessSchema.Type;
36
- export declare const MicroVerticalOperationSourceSchema: Schema.Literals<readonly ["client", "server", "generated-client", "effect-adapter", "data-platform", "unknown"]>;
36
+ export declare const MicroVerticalOperationSourceSchema: Schema.Literals<readonly ["client", "server", "generated-client", "effect-server", "data-platform", "unknown"]>;
37
37
  export type MicroVerticalOperationSource = typeof MicroVerticalOperationSourceSchema.Type;
38
38
  export declare const MicroVerticalOperationContextSchema: Schema.Struct<{
39
39
  readonly method: Schema.String;
40
40
  readonly operationId: Schema.compose<Schema.String, Schema.brand<Schema.String, "MicroVerticalOperationId">>;
41
41
  readonly routePath: Schema.String;
42
- readonly source: Schema.Literals<readonly ["client", "server", "generated-client", "effect-adapter", "data-platform", "unknown"]>;
42
+ readonly source: Schema.Literals<readonly ["client", "server", "generated-client", "effect-server", "data-platform", "unknown"]>;
43
43
  readonly traceId: Schema.optionalKey<Schema.compose<Schema.String, Schema.brand<Schema.String, "MicroVerticalTraceId">>>;
44
44
  }>;
45
45
  export type MicroVerticalOperationContext = typeof MicroVerticalOperationContextSchema.Type;
@@ -55,11 +55,11 @@ export declare const microVerticalOperationAttributes: (operationContext: MicroV
55
55
  'modernjs.operation.id': string;
56
56
  'modernjs.operation.method': string;
57
57
  'modernjs.operation.route': string;
58
- 'modernjs.operation.source': "client" | "data-platform" | "effect-adapter" | "generated-client" | "server" | "unknown";
58
+ 'modernjs.operation.source': "client" | "data-platform" | "effect-server" | "generated-client" | "server" | "unknown";
59
59
  } | {
60
60
  'modernjs.operation.id': string;
61
61
  'modernjs.operation.method': string;
62
62
  'modernjs.operation.route': string;
63
- 'modernjs.operation.source': "client" | "data-platform" | "effect-adapter" | "generated-client" | "server" | "unknown";
63
+ 'modernjs.operation.source': "client" | "data-platform" | "effect-server" | "generated-client" | "server" | "unknown";
64
64
  'modernjs.trace.id': string;
65
65
  };
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "modern.js",
19
19
  "ultramodern.js"
20
20
  ],
21
- "version": "3.9.0-ultramodern.11",
21
+ "version": "3.9.0-ultramodern.13",
22
22
  "files": [
23
23
  "dist",
24
24
  "src"
@@ -130,10 +130,10 @@
130
130
  "node": ">=26.7.0"
131
131
  },
132
132
  "dependencies": {
133
- "@modern-js/bff-core": "npm:@bleedingdev/modern-js-bff-core@3.9.0-ultramodern.11",
134
- "@modern-js/create-request": "npm:@bleedingdev/modern-js-create-request@3.9.0-ultramodern.11",
135
- "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.11",
136
- "@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.11",
133
+ "@modern-js/bff-core": "npm:@bleedingdev/modern-js-bff-core@3.9.0-ultramodern.13",
134
+ "@modern-js/create-request": "npm:@bleedingdev/modern-js-create-request@3.9.0-ultramodern.13",
135
+ "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.13",
136
+ "@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.13",
137
137
  "@opentelemetry/api": "1.9.1",
138
138
  "@opentelemetry/api-logs": "0.222.0",
139
139
  "@opentelemetry/resources": "2.11.0",
@@ -145,8 +145,8 @@
145
145
  "@opentelemetry/semantic-conventions": "1.43.0"
146
146
  },
147
147
  "peerDependencies": {
148
- "@effect/opentelemetry": "4.0.0-rc.112",
149
- "effect": "4.0.0-rc.112"
148
+ "@effect/opentelemetry": "4.0.0-rc.117",
149
+ "effect": "4.0.0-rc.117"
150
150
  },
151
151
  "peerDependenciesMeta": {
152
152
  "@effect/opentelemetry": {
@@ -157,13 +157,13 @@
157
157
  }
158
158
  },
159
159
  "devDependencies": {
160
- "@effect/opentelemetry": "4.0.0-rc.112",
161
- "@rslib/core": "1.0.0",
160
+ "@effect/opentelemetry": "4.0.0-rc.117",
161
+ "@rslib/core": "1.0.2",
162
162
  "@scripts/rstest-config": "2.66.0",
163
- "@types/node": "^26.4.1",
164
- "@types/react": "^19.2.18",
163
+ "@types/node": "^26.6.2",
164
+ "@types/react": "^19.3.0",
165
165
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
166
- "effect": "4.0.0-rc.112"
166
+ "effect": "npm:@bleedingdev/effect@4.0.0-rc.117"
167
167
  },
168
168
  "sideEffects": false,
169
169
  "publishConfig": {
@@ -0,0 +1,20 @@
1
+ import type { DataBatchTransportOptions } from '../types';
2
+
3
+ /** Wire limits and method eligibility are identical on both sides. */
4
+ export function normalizeBatchLimits(
5
+ options: Pick<
6
+ DataBatchTransportOptions,
7
+ 'maxBatchSize' | 'maxBatchBytes' | 'allowedMethods'
8
+ > = {},
9
+ ) {
10
+ return {
11
+ maxBatchSize: Math.max(1, options.maxBatchSize ?? 16),
12
+ maxBatchBytes: Math.max(1024, options.maxBatchBytes ?? 64 * 1024),
13
+ allowedMethods: new Set(
14
+ (options.allowedMethods !== undefined && options.allowedMethods.length > 0
15
+ ? options.allowedMethods
16
+ : ['GET']
17
+ ).map(method => method.toUpperCase()),
18
+ ),
19
+ };
20
+ }
@@ -8,6 +8,7 @@ import type {
8
8
  DataTransportRequestInfo,
9
9
  } from '../types';
10
10
  import { DEFAULT_DATA_BATCH_HEADER } from '../types';
11
+ import { normalizeBatchLimits } from './options';
11
12
  import { decodeBatchBody, isNullBodyStatus } from './protocol';
12
13
  import {
13
14
  createBatchId,
@@ -37,6 +38,9 @@ type NormalizedBatchTransportRequest = {
37
38
  endpoint: string;
38
39
  method: string;
39
40
  headers: Record<string, string>;
41
+ itemHeaders: Record<string, string>;
42
+ bucketKey: string;
43
+ itemPath: string;
40
44
  credentials: RequestCredentials;
41
45
  body: BodyInit | null;
42
46
  hasOpaqueSourceBody: boolean;
@@ -76,7 +80,6 @@ type QueuedBatchRequest = {
76
80
 
77
81
  type BatchBucket = {
78
82
  items: QueuedBatchRequest[];
79
- bytes: number;
80
83
  batchId: string;
81
84
  sentAt: number;
82
85
  timer: ReturnType<typeof setTimeout> | null;
@@ -185,7 +188,6 @@ export class BatchBucketRegistry {
185
188
 
186
189
  const next: BatchBucket = {
187
190
  items: [],
188
- bytes: 0,
189
191
  batchId: createBatchId(),
190
192
  sentAt: Date.now(),
191
193
  timer: null,
@@ -215,15 +217,9 @@ export function createBatchTransportQueue({
215
217
  bucketRegistry,
216
218
  }: CreateBatchTransportQueueOptions) {
217
219
  const flushIntervalMs = Math.max(0, options.flushIntervalMs ?? 8);
218
- const maxBatchSize = Math.max(1, options.maxBatchSize ?? 16);
219
- const maxBatchBytes = Math.max(1024, options.maxBatchBytes ?? 64 * 1024);
220
+ const { maxBatchSize, maxBatchBytes, allowedMethods } =
221
+ normalizeBatchLimits(options);
220
222
  const requestTimeoutMs = options.requestTimeoutMs;
221
- const allowedMethods = new Set(
222
- (options.allowedMethods && options.allowedMethods.length > 0
223
- ? options.allowedMethods
224
- : ['GET']
225
- ).map(method => method.toUpperCase()),
226
- );
227
223
  const onEvent = options.onEvent;
228
224
 
229
225
  const buckets = bucketRegistry ?? new BatchBucketRegistry();
@@ -284,7 +280,6 @@ export function createBatchTransportQueue({
284
280
  return;
285
281
  }
286
282
  bucket.items.splice(index, 1);
287
- bucket.bytes = measurePayload(bucket, bucket.items);
288
283
  if (bucket.items.length === 0 && bucket.timer) {
289
284
  clearTimeout(bucket.timer);
290
285
  bucket.timer = null;
@@ -347,11 +342,11 @@ export function createBatchTransportQueue({
347
342
  return runSingle(request);
348
343
  };
349
344
 
350
- const settleAmbiguousBatchRequests = (
351
- items: QueuedBatchRequest[],
352
- reason: string,
353
- ) =>
354
- settleRequests(items, request => runAfterAmbiguousBatch(request, reason));
345
+ const finishFlush = (bucketKey: string, bucket: BatchBucket) => {
346
+ bucket.flushing = false;
347
+ if (bucket.items.length > 0 && !bucket.timer) void flushBucket(bucketKey);
348
+ buckets.releaseIfIdle(bucketKey, bucket);
349
+ };
355
350
 
356
351
  const flushBucket = async (bucketKey: string) => {
357
352
  const bucket = buckets.get(bucketKey);
@@ -379,7 +374,6 @@ export function createBatchTransportQueue({
379
374
  sentAt: bucket.sentAt,
380
375
  };
381
376
  bucket.items = [];
382
- bucket.bytes = 0;
383
377
  rotatePayloadIdentity(bucket);
384
378
  const firstItem = items[0];
385
379
  if (!firstItem) {
@@ -396,17 +390,28 @@ export function createBatchTransportQueue({
396
390
  size: items.length,
397
391
  reason: disabledEndpoints.has(endpoint) ? 'batch-disabled' : undefined,
398
392
  });
399
- await settleRequests(items, runSingle);
400
- bucket.flushing = false;
401
- if (bucket.items.length > 0 && !bucket.timer) {
402
- void flushBucket(bucketKey);
393
+ try {
394
+ await settleRequests(items, runSingle);
395
+ } finally {
396
+ finishFlush(bucketKey, bucket);
403
397
  }
404
- buckets.releaseIfIdle(bucketKey, bucket);
405
398
  return;
406
399
  }
407
400
 
408
401
  const payload = createPayload(payloadIdentity, items);
409
402
  const batchId = payload.batchId;
403
+ const settleAmbiguousBatchRequests = (reason: string) => {
404
+ emitDataBatchTransportEvent(onEvent, {
405
+ type: 'fallback',
406
+ endpoint,
407
+ batchId,
408
+ size: items.length,
409
+ reason,
410
+ });
411
+ return settleRequests(items, request =>
412
+ runAfterAmbiguousBatch(request, reason),
413
+ );
414
+ };
410
415
 
411
416
  emitDataBatchTransportEvent(onEvent, {
412
417
  type: 'flush',
@@ -473,20 +478,17 @@ export function createBatchTransportQueue({
473
478
  batchId,
474
479
  reason: `batch-endpoint-unavailable-${String(response.status)}`,
475
480
  });
481
+ await settleRequests(items, request =>
482
+ runAfterAmbiguousBatch(
483
+ request,
484
+ `batch-response-${String(response.status)}`,
485
+ ),
486
+ );
476
487
  } else {
477
- emitDataBatchTransportEvent(onEvent, {
478
- type: 'fallback',
479
- endpoint,
480
- batchId,
481
- size: items.length,
482
- reason: `batch-response-${String(response.status)}`,
483
- });
488
+ await settleAmbiguousBatchRequests(
489
+ `batch-response-${String(response.status)}`,
490
+ );
484
491
  }
485
- await settleAmbiguousBatchRequests(
486
- items,
487
- `batch-response-${String(response.status)}`,
488
- );
489
- bucket.flushing = false;
490
492
  return;
491
493
  }
492
494
 
@@ -494,28 +496,12 @@ export function createBatchTransportQueue({
494
496
  response.json(),
495
497
  );
496
498
  if (!isBatchResponsePayload(result)) {
497
- emitDataBatchTransportEvent(onEvent, {
498
- type: 'fallback',
499
- endpoint,
500
- batchId,
501
- size: items.length,
502
- reason: 'invalid-batch-response',
503
- });
504
- await settleAmbiguousBatchRequests(items, 'invalid-batch-response');
505
- bucket.flushing = false;
499
+ await settleAmbiguousBatchRequests('invalid-batch-response');
506
500
  return;
507
501
  }
508
502
 
509
503
  if (result.batchId !== batchId) {
510
- emitDataBatchTransportEvent(onEvent, {
511
- type: 'fallback',
512
- endpoint,
513
- batchId,
514
- size: items.length,
515
- reason: 'mismatched-batch-id',
516
- });
517
- await settleAmbiguousBatchRequests(items, 'mismatched-batch-id');
518
- bucket.flushing = false;
504
+ await settleAmbiguousBatchRequests('mismatched-batch-id');
519
505
  return;
520
506
  }
521
507
 
@@ -555,23 +541,12 @@ export function createBatchTransportQueue({
555
541
  }
556
542
  return;
557
543
  }
558
- emitDataBatchTransportEvent(onEvent, {
559
- type: 'fallback',
560
- endpoint,
561
- batchId,
562
- size: items.length,
563
- reason: 'batch-transport-error',
564
- });
565
- await settleAmbiguousBatchRequests(items, 'batch-transport-error');
544
+ await settleAmbiguousBatchRequests('batch-transport-error');
566
545
  } finally {
567
546
  for (const item of items) {
568
547
  item.abortBatchIfUnused = undefined;
569
548
  }
570
- bucket.flushing = false;
571
- if (bucket.items.length > 0 && !bucket.timer) {
572
- void flushBucket(bucketKey);
573
- }
574
- buckets.releaseIfIdle(bucketKey, bucket);
549
+ finishFlush(bucketKey, bucket);
575
550
  }
576
551
  };
577
552
 
@@ -617,7 +592,18 @@ export function createBatchTransportQueue({
617
592
  options.endpoint,
618
593
  ).toString();
619
594
 
595
+ const itemHeaders = { ...headers };
596
+ delete itemHeaders.authorization;
597
+ delete itemHeaders.cookie;
620
598
  return {
599
+ itemHeaders,
600
+ itemPath: `${requestUrl.pathname}${requestUrl.search}`,
601
+ bucketKey: stableStringify({
602
+ endpoint,
603
+ authorization: headers.authorization ?? null,
604
+ cookie: headers.cookie ?? null,
605
+ credentials,
606
+ }),
621
607
  requestUrl,
622
608
  endpoint,
623
609
  method,
@@ -660,20 +646,11 @@ export function createBatchTransportQueue({
660
646
  return undefined;
661
647
  }
662
648
 
663
- const itemHeaders = { ...request.headers };
664
- delete itemHeaders.authorization;
665
- delete itemHeaders.cookie;
666
- const bucketKey = stableStringify({
667
- endpoint: request.endpoint,
668
- authorization: request.headers.authorization ?? null,
669
- cookie: request.headers.cookie ?? null,
670
- credentials: request.credentials,
671
- });
672
649
  return stableStringify({
673
- bucketKey,
674
- path: `${request.requestUrl.pathname}${request.requestUrl.search}`,
650
+ bucketKey: request.bucketKey,
651
+ path: request.itemPath,
675
652
  method: request.method,
676
- headers: itemHeaders,
653
+ headers: request.itemHeaders,
677
654
  body: null,
678
655
  });
679
656
  };
@@ -704,9 +681,7 @@ export function createBatchTransportQueue({
704
681
  .slice(2, 8)}`;
705
682
  nextItemId += 1;
706
683
 
707
- const itemHeaders = { ...request.headers };
708
- delete itemHeaders.authorization;
709
- delete itemHeaders.cookie;
684
+ const itemHeaders = { ...request.itemHeaders };
710
685
  if (
711
686
  preparedBody.body &&
712
687
  preparedBody.inferredContentType &&
@@ -716,18 +691,13 @@ export function createBatchTransportQueue({
716
691
  }
717
692
  const item: DataBatchRequestItem = {
718
693
  id: itemId,
719
- path: `${request.requestUrl.pathname}${request.requestUrl.search}`,
694
+ path: request.itemPath,
720
695
  method: request.method,
721
696
  headers: itemHeaders,
722
697
  ...(preparedBody.body ? { body: preparedBody.body } : {}),
723
698
  };
724
699
 
725
- const bucketKey = stableStringify({
726
- endpoint: request.endpoint,
727
- authorization: request.headers.authorization ?? null,
728
- cookie: request.headers.cookie ?? null,
729
- credentials: request.credentials,
730
- });
700
+ const { bucketKey } = request;
731
701
 
732
702
  const key = stableStringify({
733
703
  bucketKey,
@@ -824,14 +794,16 @@ export function createBatchTransportQueue({
824
794
  }
825
795
 
826
796
  bucket.items.push(queued);
827
- bucket.bytes = measurePayload(bucket, bucket.items);
828
797
  emitDataBatchTransportEvent(onEvent, {
829
798
  type: 'enqueue',
830
799
  endpoint: request.endpoint,
831
800
  size: bucket.items.length,
832
801
  });
833
802
 
834
- if (bucket.items.length >= maxBatchSize || bucket.bytes >= maxBatchBytes) {
803
+ if (
804
+ bucket.items.length >= maxBatchSize ||
805
+ measurePayload(bucket, bucket.items) >= maxBatchBytes
806
+ ) {
835
807
  void flushBucket(bucketKey);
836
808
  return queued.promise;
837
809
  }
@@ -1,8 +1,14 @@
1
+ import type { EffectRpcBffDefinition } from './handler/types';
2
+
1
3
  type ValidatorAwareHandlerFactoryRegistry = {
2
4
  register<TFactory extends Function>(factory: TFactory): TFactory;
3
5
  is(factory: unknown): boolean;
4
6
  };
5
7
 
8
+ declare const __modernjs_backend_private_capability__:
9
+ | ValidatorAwareHandlerFactoryRegistry
10
+ | undefined;
11
+
6
12
  function createLocalValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandlerFactoryRegistry {
7
13
  const factories = new WeakSet<Function>();
8
14
  return {
@@ -19,6 +25,11 @@ function createLocalValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandle
19
25
  function loadNodeValidatorAwareHandlerFactoryRegistry(): ValidatorAwareHandlerFactoryRegistry {
20
26
  const moduleUrl = import.meta.url;
21
27
  if (typeof moduleUrl !== 'string' || !moduleUrl.startsWith('file:')) {
28
+ // A verified CommonJS backend bundle has no import.meta.url. Its Node
29
+ // evaluator supplies the package-private registry directly.
30
+ if (typeof __modernjs_backend_private_capability__ !== 'undefined') {
31
+ return __modernjs_backend_private_capability__;
32
+ }
22
33
  return createLocalValidatorAwareHandlerFactoryRegistry();
23
34
  }
24
35
 
@@ -53,6 +64,7 @@ const validatorAwareHandlerFactoryRegistry =
53
64
  type EffectBffEntryModule = {
54
65
  api?: unknown;
55
66
  layer?: unknown;
67
+ rpc?: EffectRpcBffDefinition;
56
68
  handler?: unknown;
57
69
  createHandler?: unknown;
58
70
  default?: unknown;