@floegence/flowersec-core 0.19.10 → 0.20.0

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 (44) hide show
  1. package/README.md +8 -3
  2. package/dist/browser/index.d.ts +2 -0
  3. package/dist/browser/index.js +1 -0
  4. package/dist/browser/reconnectConfig.d.ts +8 -36
  5. package/dist/browser/reconnectConfig.js +13 -70
  6. package/dist/client-connect/connectCore.d.ts +17 -5
  7. package/dist/client-connect/connectCore.js +131 -32
  8. package/dist/client-connect/transportSecurity.d.ts +19 -0
  9. package/dist/client-connect/transportSecurity.js +114 -0
  10. package/dist/client.d.ts +2 -0
  11. package/dist/e2ee/handshake.d.ts +4 -0
  12. package/dist/e2ee/handshake.js +2 -0
  13. package/dist/e2ee/secureChannel.d.ts +4 -0
  14. package/dist/e2ee/secureChannel.js +18 -9
  15. package/dist/facade.d.ts +5 -0
  16. package/dist/facade.js +1 -0
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/node/index.d.ts +2 -0
  20. package/dist/node/index.js +1 -0
  21. package/dist/node/reconnectConfig.d.ts +8 -34
  22. package/dist/node/reconnectConfig.js +13 -71
  23. package/dist/node/wsFactory.js +3 -0
  24. package/dist/observability/observer.d.ts +5 -2
  25. package/dist/observability/observer.js +3 -0
  26. package/dist/reconnect/artifactControlplane.d.ts +9 -6
  27. package/dist/reconnect/artifactControlplane.js +28 -27
  28. package/dist/reconnect/index.d.ts +2 -0
  29. package/dist/reconnect/index.js +7 -0
  30. package/dist/rpc/server.d.ts +29 -4
  31. package/dist/rpc/server.js +142 -34
  32. package/dist/tunnel-client/connect.js +6 -6
  33. package/dist/utils/errors.d.ts +1 -1
  34. package/dist/ws-client/binaryTransport.d.ts +20 -5
  35. package/dist/ws-client/binaryTransport.js +110 -8
  36. package/dist/yamux/byteReader.d.ts +1 -0
  37. package/dist/yamux/byteReader.js +26 -0
  38. package/dist/yamux/errors.d.ts +7 -0
  39. package/dist/yamux/errors.js +15 -0
  40. package/dist/yamux/session.d.ts +31 -1
  41. package/dist/yamux/session.js +191 -12
  42. package/dist/yamux/stream.d.ts +10 -3
  43. package/dist/yamux/stream.js +61 -19
  44. package/package.json +1 -1
@@ -14,6 +14,8 @@ export type HandshakeClientOptions = Readonly<{
14
14
  maxHandshakePayload: number;
15
15
  /** Maximum record size for encrypted frames after handshake. */
16
16
  maxRecordBytes: number;
17
+ /** Preferred plaintext bytes per outbound record. */
18
+ outboundRecordChunkBytes?: number;
17
19
  /** Maximum buffered plaintext bytes for the secure channel. */
18
20
  maxBufferedBytes?: number;
19
21
  /** Optional AbortSignal to cancel the handshake. */
@@ -38,6 +40,8 @@ export type HandshakeServerOptions = Readonly<{
38
40
  maxHandshakePayload: number;
39
41
  /** Maximum record size for encrypted frames after handshake. */
40
42
  maxRecordBytes: number;
43
+ /** Preferred plaintext bytes per outbound record. */
44
+ outboundRecordChunkBytes?: number;
41
45
  /** Maximum buffered plaintext bytes for the secure channel. */
42
46
  maxBufferedBytes?: number;
43
47
  /** Optional AbortSignal to cancel the handshake. */
@@ -159,6 +159,7 @@ export async function clientHandshake(transport, opts) {
159
159
  return new SecureChannel({
160
160
  transport,
161
161
  maxRecordBytes: opts.maxRecordBytes,
162
+ ...(opts.outboundRecordChunkBytes !== undefined ? { outboundRecordChunkBytes: opts.outboundRecordChunkBytes } : {}),
162
163
  ...(opts.maxBufferedBytes !== undefined ? { maxBufferedBytes: opts.maxBufferedBytes } : {}),
163
164
  sendKey: keys.c2sKey,
164
165
  recvKey: keys.s2cKey,
@@ -316,6 +317,7 @@ export async function serverHandshake(transport, cache, opts) {
316
317
  return new SecureChannel({
317
318
  transport,
318
319
  maxRecordBytes: opts.maxRecordBytes,
320
+ ...(opts.outboundRecordChunkBytes !== undefined ? { outboundRecordChunkBytes: opts.outboundRecordChunkBytes } : {}),
319
321
  ...(opts.maxBufferedBytes !== undefined ? { maxBufferedBytes: opts.maxBufferedBytes } : {}),
320
322
  sendKey: keys.s2cKey,
321
323
  recvKey: keys.c2sKey,
@@ -19,6 +19,8 @@ export type BinaryTransport = {
19
19
  export type SecureChannelOptions = Readonly<{
20
20
  /** Maximum encoded record size (header + ciphertext). */
21
21
  maxRecordBytes: number;
22
+ /** Preferred plaintext bytes per outbound record. */
23
+ outboundRecordChunkBytes?: number;
22
24
  /** Maximum queued plaintext bytes before backpressure/errors. */
23
25
  maxBufferedBytes?: number;
24
26
  }>;
@@ -26,6 +28,7 @@ type Direction = 1 | 2;
26
28
  export declare class SecureChannel {
27
29
  private readonly transport;
28
30
  private readonly maxRecordBytes;
31
+ private readonly outboundRecordChunkBytes;
29
32
  private readonly maxBufferedBytes;
30
33
  private sendKey;
31
34
  private recvKey;
@@ -52,6 +55,7 @@ export declare class SecureChannel {
52
55
  constructor(args: {
53
56
  transport: BinaryTransport;
54
57
  maxRecordBytes: number;
58
+ outboundRecordChunkBytes?: number;
55
59
  maxBufferedBytes?: number;
56
60
  sendKey: Uint8Array;
57
61
  recvKey: Uint8Array;
@@ -14,6 +14,7 @@ export class SecureChannel {
14
14
  transport;
15
15
  // Maximum allowed bytes per record frame.
16
16
  maxRecordBytes;
17
+ outboundRecordChunkBytes;
17
18
  // Upper bound for buffered plaintext in memory.
18
19
  maxBufferedBytes;
19
20
  // Active encryption keys and nonce prefixes for the current epoch.
@@ -48,6 +49,11 @@ export class SecureChannel {
48
49
  constructor(args) {
49
50
  this.transport = args.transport;
50
51
  this.maxRecordBytes = args.maxRecordBytes;
52
+ const maxPlain = Math.max(1, maxPlaintextBytes(this.maxRecordBytes));
53
+ this.outboundRecordChunkBytes = args.outboundRecordChunkBytes ?? Math.min(64 * 1024, maxPlain);
54
+ if (!Number.isSafeInteger(this.outboundRecordChunkBytes) || this.outboundRecordChunkBytes <= 0 || this.outboundRecordChunkBytes > maxPlain) {
55
+ throw new RangeError("outboundRecordChunkBytes must be a positive integer within the record plaintext limit");
56
+ }
51
57
  this.maxBufferedBytes = Math.max(0, args.maxBufferedBytes ?? 4 * (1 << 20));
52
58
  this.sendKey = args.sendKey;
53
59
  this.recvKey = args.recvKey;
@@ -64,13 +70,9 @@ export class SecureChannel {
64
70
  }
65
71
  // write splits payloads into record-sized chunks and queues them for send.
66
72
  async write(plaintext) {
67
- const maxPlain = Math.max(1, maxPlaintextBytes(this.maxRecordBytes) || plaintext.length);
68
- let off = 0;
69
- while (off < plaintext.length) {
70
- const chunk = plaintext.slice(off, Math.min(plaintext.length, off + maxPlain));
71
- await this.enqueueSend("app", chunk);
72
- off += chunk.length;
73
- }
73
+ if (plaintext.length === 0)
74
+ return;
75
+ await this.enqueueSend("app", plaintext.slice());
74
76
  }
75
77
  // read resolves with the next plaintext chunk or throws on errors/close.
76
78
  async read() {
@@ -216,8 +218,15 @@ export class SecureChannel {
216
218
  try {
217
219
  let frame;
218
220
  if (req.kind === "app") {
219
- const seq = this.reserveSendSeq();
220
- frame = encryptRecord(this.sendKey, this.sendNoncePrefix, RECORD_FLAG_APP, seq, req.payload ?? new Uint8Array(), this.maxRecordBytes);
221
+ const payload = req.payload ?? new Uint8Array();
222
+ for (let offset = 0; offset < payload.length; offset += this.outboundRecordChunkBytes) {
223
+ const chunk = payload.subarray(offset, Math.min(payload.length, offset + this.outboundRecordChunkBytes));
224
+ const seq = this.reserveSendSeq();
225
+ frame = encryptRecord(this.sendKey, this.sendNoncePrefix, RECORD_FLAG_APP, seq, chunk, this.maxRecordBytes);
226
+ await this.transport.writeBinary(frame);
227
+ }
228
+ req.resolve();
229
+ continue;
221
230
  }
222
231
  else if (req.kind === "ping") {
223
232
  const seq = this.reserveSendSeq();
package/dist/facade.d.ts CHANGED
@@ -12,8 +12,13 @@ export type { ConnectArtifact, CorrelationContext, CorrelationKV, DirectClientCo
12
12
  export { assertConnectArtifact } from "./connect/artifact.js";
13
13
  export type { ClientObserverLike } from "./observability/observer.js";
14
14
  export type { Client, ClientPath } from "./client.js";
15
+ export type { LivenessOptions } from "./client-connect/connectCore.js";
16
+ export type { WebSocketLimits } from "./ws-client/binaryTransport.js";
17
+ export type { YamuxLimits } from "./yamux/session.js";
15
18
  export type { FlowersecErrorCode, FlowersecPath, FlowersecStage } from "./utils/errors.js";
16
19
  export { FlowersecError } from "./utils/errors.js";
20
+ export { AllowPlaintext, AllowPlaintextForLoopback, RequireTLS, } from "./client-connect/transportSecurity.js";
21
+ export type { TransportSecurityPolicy, TransportSecurityPolicyInput, TransportSecurityPolicyPreset, } from "./client-connect/transportSecurity.js";
17
22
  export type { TunnelConnectOptions } from "./tunnel-client/connect.js";
18
23
  export type { DirectConnectOptions } from "./direct-client/connect.js";
19
24
  export type ConnectOptions = TunnelConnectOptions | DirectConnectOptions;
package/dist/facade.js CHANGED
@@ -6,6 +6,7 @@ export { assertChannelInitGrant } from "./gen/flowersec/controlplane/v1.gen.js";
6
6
  export { assertDirectConnectInfo } from "./gen/flowersec/direct/v1.gen.js";
7
7
  export { assertConnectArtifact } from "./connect/artifact.js";
8
8
  export { FlowersecError } from "./utils/errors.js";
9
+ export { AllowPlaintext, AllowPlaintextForLoopback, RequireTLS, } from "./client-connect/transportSecurity.js";
9
10
  export async function connectTunnel(grant, opts) {
10
11
  return await connectTunnelInternal(grant, opts);
11
12
  }
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export * from "./e2ee/secureChannel.js";
12
12
  export * from "./e2ee/handshake.js";
13
13
  export * from "./e2ee/errors.js";
14
14
  export * from "./client.js";
15
+ export * from "./client-connect/transportSecurity.js";
15
16
  export * from "./observability/index.js";
16
17
  export * from "./ws-client/binaryTransport.js";
17
18
  export * from "./yamux/index.js";
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export * from "./e2ee/secureChannel.js";
12
12
  export * from "./e2ee/handshake.js";
13
13
  export * from "./e2ee/errors.js";
14
14
  export * from "./client.js";
15
+ export * from "./client-connect/transportSecurity.js";
15
16
  export * from "./observability/index.js";
16
17
  export * from "./ws-client/binaryTransport.js";
17
18
  export * from "./yamux/index.js";
@@ -1,4 +1,6 @@
1
1
  export { createNodeWsFactory } from "./wsFactory.js";
2
+ export { AllowPlaintext, AllowPlaintextForLoopback, RequireTLS, } from "../client-connect/transportSecurity.js";
3
+ export type { TransportSecurityPolicy, TransportSecurityPolicyInput, TransportSecurityPolicyPreset, } from "../client-connect/transportSecurity.js";
2
4
  export { connectDirectNode, connectNode, connectTunnelNode } from "./connect.js";
3
5
  export type { DirectNodeReconnectConfig, NodeReconnectConfig, TunnelNodeReconnectConfig, } from "./reconnectConfig.js";
4
6
  export { createDirectNodeReconnectConfig, createNodeReconnectConfig, createTunnelNodeReconnectConfig, } from "./reconnectConfig.js";
@@ -1,4 +1,5 @@
1
1
  export { createNodeWsFactory } from "./wsFactory.js";
2
+ export { AllowPlaintext, AllowPlaintextForLoopback, RequireTLS, } from "../client-connect/transportSecurity.js";
2
3
  export { connectDirectNode, connectNode, connectTunnelNode } from "./connect.js";
3
4
  export { createDirectNodeReconnectConfig, createNodeReconnectConfig, createTunnelNodeReconnectConfig, } from "./reconnectConfig.js";
4
5
  export { assertConnectArtifact } from "../connect/artifact.js";
@@ -1,42 +1,16 @@
1
- import type { ConnectArtifact } from "../connect/artifact.js";
2
- import type { ChannelInitGrant } from "../gen/flowersec/controlplane/v1.gen.js";
3
- import type { DirectConnectInfo } from "../gen/flowersec/direct/v1.gen.js";
4
1
  import type { ClientObserverLike } from "../observability/observer.js";
5
2
  import type { DirectConnectOptions } from "../direct-client/connect.js";
6
3
  import type { TunnelConnectOptions } from "../tunnel-client/connect.js";
7
4
  import type { AutoReconnectConfig, ConnectConfig as ReconnectConnectConfig } from "../reconnect/index.js";
8
- import { type ArtifactAwareReconnectConfig, type ArtifactFactoryArgs } from "../reconnect/artifactControlplane.js";
9
- import type { RequestConnectArtifactInput, RequestEntryConnectArtifactInput } from "../controlplane/index.js";
10
- type SharedReconnectOptions = Readonly<{
5
+ import { type ArtifactSource } from "../reconnect/artifactControlplane.js";
6
+ export type NodeReconnectConfig = Readonly<{
7
+ source: ArtifactSource;
8
+ connect?: Omit<TunnelConnectOptions, "observer" | "signal"> | Omit<DirectConnectOptions, "observer" | "signal">;
11
9
  observer?: ClientObserverLike;
12
10
  autoReconnect?: AutoReconnectConfig;
13
11
  }>;
14
- type TunnelReconnectConnectOptions = Omit<TunnelConnectOptions, "observer" | "signal">;
15
- type DirectReconnectConnectOptions = Omit<DirectConnectOptions, "observer" | "signal">;
16
- type ArtifactAwareTunnelReconnectConfig = ArtifactAwareReconnectConfig & Readonly<{
17
- artifact?: ConnectArtifact;
18
- getArtifact?: (args: ArtifactFactoryArgs) => Promise<ConnectArtifact>;
19
- artifactControlplane?: RequestConnectArtifactInput | RequestEntryConnectArtifactInput;
20
- }>;
21
- type ArtifactAwareDirectReconnectConfig = ArtifactAwareReconnectConfig & Readonly<{
22
- artifact?: ConnectArtifact;
23
- getArtifact?: (args: ArtifactFactoryArgs) => Promise<ConnectArtifact>;
24
- artifactControlplane?: RequestConnectArtifactInput | RequestEntryConnectArtifactInput;
25
- }>;
26
- export type TunnelNodeReconnectConfig = SharedReconnectOptions & ArtifactAwareTunnelReconnectConfig & Readonly<{
27
- mode?: "tunnel";
28
- connect?: TunnelReconnectConnectOptions;
29
- grant?: ChannelInitGrant;
30
- getGrant?: () => Promise<ChannelInitGrant>;
31
- }>;
32
- export type DirectNodeReconnectConfig = SharedReconnectOptions & ArtifactAwareDirectReconnectConfig & Readonly<{
33
- mode: "direct";
34
- connect?: DirectReconnectConnectOptions;
35
- directInfo?: DirectConnectInfo;
36
- getDirectInfo?: () => Promise<DirectConnectInfo>;
37
- }>;
38
- export type NodeReconnectConfig = TunnelNodeReconnectConfig | DirectNodeReconnectConfig;
39
- export declare function createTunnelNodeReconnectConfig(config: TunnelNodeReconnectConfig): ReconnectConnectConfig;
40
- export declare function createDirectNodeReconnectConfig(config: DirectNodeReconnectConfig): ReconnectConnectConfig;
12
+ export type TunnelNodeReconnectConfig = NodeReconnectConfig;
13
+ export type DirectNodeReconnectConfig = NodeReconnectConfig;
41
14
  export declare function createNodeReconnectConfig(config: NodeReconnectConfig): ReconnectConnectConfig;
42
- export {};
15
+ export declare const createTunnelNodeReconnectConfig: typeof createNodeReconnectConfig;
16
+ export declare const createDirectNodeReconnectConfig: typeof createNodeReconnectConfig;
@@ -1,78 +1,20 @@
1
- import { resolveConnectArtifact, updateTraceId, } from "../reconnect/artifactControlplane.js";
2
- import { connectDirectNode, connectNode, connectTunnelNode } from "./connect.js";
3
- async function resolveTunnelGrant(config) {
4
- if (config.getGrant)
5
- return await config.getGrant();
6
- if (config.grant)
7
- return config.grant;
8
- throw new Error("Tunnel reconnect config requires `getGrant` or `grant`");
9
- }
10
- async function resolveDirectInfo(config) {
11
- if (config.getDirectInfo)
12
- return await config.getDirectInfo();
13
- if (config.directInfo)
14
- return config.directInfo;
15
- throw new Error("Direct reconnect config requires `getDirectInfo` or `directInfo`");
16
- }
17
- export function createTunnelNodeReconnectConfig(config) {
18
- let traceId = config.artifact?.correlation?.trace_id;
19
- return {
20
- ...(config.observer === undefined ? {} : { observer: config.observer }),
21
- ...(config.autoReconnect === undefined ? {} : { autoReconnect: config.autoReconnect }),
22
- connectOnce: async ({ signal, observer }) => {
23
- if (config.getArtifact || config.artifact || config.artifactControlplane) {
24
- const artifact = await resolveConnectArtifact(config, traceId, signal);
25
- if (artifact.transport !== "tunnel") {
26
- throw new Error("Tunnel reconnect config requires a tunnel ConnectArtifact");
27
- }
28
- traceId = updateTraceId(traceId, artifact);
29
- const connectOptions = {
30
- ...(config.connect === undefined ? {} : config.connect),
31
- signal,
32
- observer,
33
- };
34
- return await connectNode(artifact, connectOptions);
35
- }
36
- const connectOptions = {
37
- ...(config.connect === undefined ? {} : config.connect),
38
- signal,
39
- observer,
40
- };
41
- return await connectTunnelNode(await resolveTunnelGrant(config), connectOptions);
42
- },
43
- };
44
- }
45
- export function createDirectNodeReconnectConfig(config) {
46
- let traceId = config.artifact?.correlation?.trace_id;
1
+ import { createArtifactResolver, updateTraceId } from "../reconnect/artifactControlplane.js";
2
+ import { connectNode } from "./connect.js";
3
+ export function createNodeReconnectConfig(config) {
4
+ if (config.source.kind === "once" && config.autoReconnect?.enabled) {
5
+ throw new Error("automatic reconnect requires a refreshable artifact source");
6
+ }
7
+ let traceId = config.source.kind === "once" ? config.source.artifact.correlation?.trace_id : undefined;
8
+ const acquire = createArtifactResolver(config.source);
47
9
  return {
48
10
  ...(config.observer === undefined ? {} : { observer: config.observer }),
49
11
  ...(config.autoReconnect === undefined ? {} : { autoReconnect: config.autoReconnect }),
50
12
  connectOnce: async ({ signal, observer }) => {
51
- if (config.getArtifact || config.artifact || config.artifactControlplane) {
52
- const artifact = await resolveConnectArtifact(config, traceId, signal);
53
- if (artifact.transport !== "direct") {
54
- throw new Error("Direct reconnect config requires a direct ConnectArtifact");
55
- }
56
- traceId = updateTraceId(traceId, artifact);
57
- const connectOptions = {
58
- ...(config.connect === undefined ? {} : config.connect),
59
- signal,
60
- observer,
61
- };
62
- return await connectNode(artifact, connectOptions);
63
- }
64
- const connectOptions = {
65
- ...(config.connect === undefined ? {} : config.connect),
66
- signal,
67
- observer,
68
- };
69
- return await connectDirectNode(await resolveDirectInfo(config), connectOptions);
13
+ const artifact = await acquire({ ...(traceId === undefined ? {} : { traceId }), signal });
14
+ traceId = updateTraceId(traceId, artifact);
15
+ return await connectNode(artifact, { ...(config.connect ?? {}), signal, observer });
70
16
  },
71
17
  };
72
18
  }
73
- export function createNodeReconnectConfig(config) {
74
- if (config.mode === "direct") {
75
- return createDirectNodeReconnectConfig(config);
76
- }
77
- return createTunnelNodeReconnectConfig(config);
78
- }
19
+ export const createTunnelNodeReconnectConfig = createNodeReconnectConfig;
20
+ export const createDirectNodeReconnectConfig = createNodeReconnectConfig;
@@ -67,6 +67,9 @@ export function createNodeWsFactory(opts = {}) {
67
67
  get readyState() {
68
68
  return raw.readyState;
69
69
  },
70
+ get bufferedAmount() {
71
+ return raw.bufferedAmount;
72
+ },
70
73
  send(data) {
71
74
  raw.send(data);
72
75
  },
@@ -6,13 +6,13 @@ export type AttachReason = "send_failed" | "too_many_connections" | "expected_at
6
6
  export type HandshakeResult = "ok" | "fail";
7
7
  export type HandshakeReason = "auth_tag_mismatch" | "handshake_failed" | "invalid_suite" | "invalid_version" | "timestamp_after_init_exp" | "timestamp_out_of_skew" | "timeout" | "canceled";
8
8
  export type WsCloseKind = "local" | "peer_or_error";
9
- export type WsErrorReason = "error" | "recv_buffer_exceeded" | "unexpected_text_frame" | "unexpected_message_type";
9
+ export type WsErrorReason = "error" | "recv_buffer_exceeded" | "send_buffer_exceeded" | "send_buffer_timeout" | "unexpected_text_frame" | "unexpected_message_type";
10
10
  export type RpcCallResult = "ok" | "rpc_error" | "handler_not_found" | "transport_error" | "canceled";
11
11
  export type DiagnosticEvent = Readonly<{
12
12
  v: 1;
13
13
  namespace: "connect";
14
14
  path: ClientPath | "auto";
15
- stage: "validate" | "normalize" | "scope" | "connect" | "attach" | "handshake" | "close" | "reconnect";
15
+ stage: "validate" | "normalize" | "scope" | "connect" | "attach" | "handshake" | "transport" | "yamux" | "rpc" | "close" | "reconnect";
16
16
  code_domain: "error" | "event";
17
17
  code: string;
18
18
  result: "ok" | "fail" | "retry" | "skip";
@@ -20,6 +20,9 @@ export type DiagnosticEvent = Readonly<{
20
20
  attempt_seq: number;
21
21
  trace_id?: string;
22
22
  session_id?: string;
23
+ resource?: string;
24
+ current?: number;
25
+ limit?: number;
23
26
  }>;
24
27
  type ObserverContext = Readonly<{
25
28
  path?: ClientPath | "auto";
@@ -54,6 +54,9 @@ function buildDiagnosticEvent(context, event) {
54
54
  ...(context.sessionId === undefined
55
55
  ? {}
56
56
  : { session_id: context.sessionId }),
57
+ ...(event.resource === undefined ? {} : { resource: event.resource }),
58
+ ...(event.current === undefined ? {} : { current: event.current }),
59
+ ...(event.limit === undefined ? {} : { limit: event.limit }),
57
60
  });
58
61
  }
59
62
  function mapConnectDiagnostic(path, result, reason) {
@@ -1,13 +1,16 @@
1
1
  import type { ConnectArtifact } from "../connect/artifact.js";
2
2
  import { type RequestConnectArtifactInput, type RequestEntryConnectArtifactInput } from "../controlplane/index.js";
3
- export type ArtifactFactoryArgs = Readonly<{
3
+ export type ArtifactAcquireContext = Readonly<{
4
4
  traceId?: string;
5
5
  signal?: AbortSignal;
6
6
  }>;
7
- export type ArtifactAwareReconnectConfig = Readonly<{
8
- artifact?: ConnectArtifact;
9
- getArtifact?: (args: ArtifactFactoryArgs) => Promise<ConnectArtifact>;
10
- artifactControlplane?: RequestConnectArtifactInput | RequestEntryConnectArtifactInput;
7
+ export type ArtifactSource = Readonly<{
8
+ kind: "once";
9
+ artifact: ConnectArtifact;
10
+ }> | Readonly<{
11
+ kind: "refreshable";
12
+ acquire: (context: ArtifactAcquireContext) => Promise<ConnectArtifact>;
11
13
  }>;
12
- export declare function resolveConnectArtifact(config: ArtifactAwareReconnectConfig, traceId?: string, signal?: AbortSignal): Promise<ConnectArtifact>;
14
+ export declare function createControlplaneArtifactSource(input: RequestConnectArtifactInput | RequestEntryConnectArtifactInput): ArtifactSource;
15
+ export declare function createArtifactResolver(source: ArtifactSource): (context: ArtifactAcquireContext) => Promise<ConnectArtifact>;
13
16
  export declare function updateTraceId(current: string | undefined, artifact: ConnectArtifact): string | undefined;
@@ -1,33 +1,34 @@
1
1
  import { requestConnectArtifact, requestEntryConnectArtifact, } from "../controlplane/index.js";
2
- export async function resolveConnectArtifact(config, traceId, signal) {
3
- if (config.getArtifact) {
4
- return await config.getArtifact({
5
- ...(traceId === undefined ? {} : { traceId }),
6
- ...(signal === undefined ? {} : { signal }),
7
- });
8
- }
9
- if (config.artifact)
10
- return config.artifact;
11
- if (config.artifactControlplane) {
12
- const correlation = traceId === undefined
13
- ? config.artifactControlplane.correlation
14
- : { traceId };
15
- if ("entryTicket" in config.artifactControlplane) {
16
- const input = {
17
- ...config.artifactControlplane,
2
+ export function createControlplaneArtifactSource(input) {
3
+ return {
4
+ kind: "refreshable",
5
+ acquire: async ({ traceId, signal }) => {
6
+ const correlation = traceId === undefined ? input.correlation : { traceId };
7
+ if ("entryTicket" in input) {
8
+ return await requestEntryConnectArtifact({
9
+ ...input,
10
+ ...(correlation === undefined ? {} : { correlation }),
11
+ ...(signal === undefined ? {} : { signal }),
12
+ });
13
+ }
14
+ return await requestConnectArtifact({
15
+ ...input,
18
16
  ...(correlation === undefined ? {} : { correlation }),
19
17
  ...(signal === undefined ? {} : { signal }),
20
- };
21
- return await requestEntryConnectArtifact(input);
22
- }
23
- const input = {
24
- ...config.artifactControlplane,
25
- ...(correlation === undefined ? {} : { correlation }),
26
- ...(signal === undefined ? {} : { signal }),
27
- };
28
- return await requestConnectArtifact(input);
29
- }
30
- throw new Error("Artifact reconnect config requires `getArtifact`, `artifact`, or `artifactControlplane`");
18
+ });
19
+ },
20
+ };
21
+ }
22
+ export function createArtifactResolver(source) {
23
+ let consumed = false;
24
+ return async (context) => {
25
+ if (source.kind === "refreshable")
26
+ return await source.acquire(context);
27
+ if (consumed)
28
+ throw new Error("one-time artifact source has already been consumed");
29
+ consumed = true;
30
+ return source.artifact;
31
+ };
31
32
  }
32
33
  export function updateTraceId(current, artifact) {
33
34
  return artifact.correlation?.trace_id ?? current;
@@ -1,5 +1,7 @@
1
1
  import type { Client } from "../client.js";
2
2
  import { type ClientObserverLike } from "../observability/observer.js";
3
+ export type { ArtifactAcquireContext, ArtifactSource } from "./artifactControlplane.js";
4
+ export { createArtifactResolver, createControlplaneArtifactSource } from "./artifactControlplane.js";
3
5
  export type ConnectionStatus = "disconnected" | "connecting" | "connected" | "error";
4
6
  export type AutoReconnectConfig = Readonly<{
5
7
  enabled?: boolean;
@@ -1,4 +1,5 @@
1
1
  import { emitObserverDiagnostic, withObserverContext } from "../observability/observer.js";
2
+ export { createArtifactResolver, createControlplaneArtifactSource } from "./artifactControlplane.js";
2
3
  function normalizeAutoReconnect(cfg) {
3
4
  if (!cfg?.enabled) {
4
5
  return {
@@ -159,6 +160,12 @@ export function createReconnectManager() {
159
160
  },
160
161
  onRpcCall: (...args) => user?.onRpcCall?.(...args),
161
162
  onRpcNotify: (...args) => user?.onRpcNotify?.(...args),
163
+ onDiagnosticEvent: (event) => {
164
+ user?.onDiagnosticEvent?.(event);
165
+ if (event.code === "liveness_timeout") {
166
+ startReconnect(t, cfg, new Error("liveness timeout"));
167
+ }
168
+ },
162
169
  }, {
163
170
  attemptSeq: currentAttemptSeq,
164
171
  });
@@ -3,13 +3,38 @@ export type RpcHandler = (payload: unknown) => Promise<{
3
3
  payload: unknown;
4
4
  error?: RpcError;
5
5
  }>;
6
+ export type RpcServerOptions = Readonly<{
7
+ maxConcurrentRequests?: number;
8
+ maxQueuedRequests?: number;
9
+ maxQueuedNotifications?: number;
10
+ }>;
11
+ export type RpcServerTransport = Readonly<{
12
+ readExactly(n: number): Promise<Uint8Array>;
13
+ write(bytes: Uint8Array): Promise<void>;
14
+ close(error: unknown): void;
15
+ }>;
6
16
  export declare class RpcServer {
7
- private readonly readExactly;
8
- private readonly write;
17
+ private readonly transport;
9
18
  private readonly handlers;
10
19
  private closed;
11
- constructor(readExactly: (n: number) => Promise<Uint8Array>, write: (b: Uint8Array) => Promise<void>);
20
+ private readonly options;
21
+ private readonly requests;
22
+ private readonly notifications;
23
+ private requestWaiters;
24
+ private notificationWaiters;
25
+ private writeChain;
26
+ private terminalError;
27
+ private readonly terminalSignal;
28
+ private signalTerminal;
29
+ private transportClosed;
30
+ constructor(transport: RpcServerTransport, options?: RpcServerOptions);
12
31
  register(typeId: number, h: RpcHandler): void;
13
32
  serve(signal?: AbortSignal): Promise<void>;
14
- close(): void;
33
+ close(error?: unknown): void;
34
+ private fail;
35
+ private requestWorker;
36
+ private notificationWorker;
37
+ private nextWork;
38
+ private wakeOne;
39
+ private writeResponse;
15
40
  }