@cortexkit/subc-client 0.4.1 → 0.8.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.
package/src/provider.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  type ReconnectBackoff,
8
8
  type RequestOptions,
9
9
  type RouteTarget,
10
+ type SubcCallErrorKind,
10
11
  } from "./client.js";
11
12
  import { ConnectionFileError, readConnectionFile, type ConnectionInfo } from "./connection-file.js";
12
13
  import {
@@ -33,6 +34,7 @@ import {
33
34
  SocketWriteNotQueuedError,
34
35
  SocketWriteQueuedError,
35
36
  SubcSocket,
37
+ writeBorrowed,
36
38
  } from "./socket.js";
37
39
 
38
40
  const DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000;
@@ -53,7 +55,6 @@ export type ObservabilityKind = "snapshot" | "stream";
53
55
  export type InternalTransport = "bulk";
54
56
  export type StorageKind = "sqlite";
55
57
  export type StorageScope = "project";
56
- export type LeaseScope = "project";
57
58
  export type HealthStatus = "ok" | "degraded" | "failing";
58
59
 
59
60
  export interface HealthReport {
@@ -69,7 +70,6 @@ export interface ManifestInput {
69
70
  trust_tier: TrustTier;
70
71
  provides: ProviderRoleInput[];
71
72
  consumes: ConsumerRoleInput[];
72
- scheduled_tasks: ScheduledTaskInput[];
73
73
  bindings: BindingsInput;
74
74
  }
75
75
 
@@ -97,6 +97,7 @@ export type ProviderRoleInput =
97
97
  config_schema: unknown;
98
98
  observability: ObservabilitySurfaceInput[];
99
99
  identity_scope: IdentityScope[];
100
+ concurrency: Concurrency;
100
101
  }
101
102
  | {
102
103
  role: "internal_service";
@@ -133,31 +134,6 @@ export type ConsumerRoleInput =
133
134
  | { role: "llm_client"; via: string; auth: string }
134
135
  | { role: "service_client"; of: string[] };
135
136
 
136
- export interface ScheduledTaskInput {
137
- task_id: string;
138
- eligibility: TaskEligibilityInput;
139
- lease_scope: LeaseScope;
140
- renews_during_calls: boolean;
141
- toolset: string[];
142
- model_policy: ModelPolicyInput;
143
- step_cap: number;
144
- circuit_breaker: CircuitBreakerInput;
145
- }
146
-
147
- export interface TaskEligibilityInput {
148
- cooldown: string;
149
- window: string;
150
- }
151
-
152
- export interface ModelPolicyInput {
153
- tier: string;
154
- fallback_chain: string[];
155
- }
156
-
157
- export interface CircuitBreakerInput {
158
- identical_failures: number;
159
- }
160
-
161
137
  export interface BindingsInput {
162
138
  storage: StorageBindingInput;
163
139
  vault_grants: VaultGrantInput[];
@@ -185,6 +161,7 @@ export interface ManagementSurfaceManifestOptions {
185
161
  moduleId: string;
186
162
  operations: Array<string | ManagementOperationInput>;
187
163
  moduleVersion?: string;
164
+ concurrency?: Concurrency;
188
165
  }
189
166
 
190
167
  /**
@@ -312,6 +289,13 @@ interface OpenedProviderConnection {
312
289
  ack: ModuleHelloAckBody;
313
290
  }
314
291
 
292
+ interface ReconnectCycle {
293
+ readonly generation: number;
294
+ socket: SubcSocket | null;
295
+ socketDied: boolean;
296
+ superseded: boolean;
297
+ }
298
+
315
299
  export interface ModuleHelloAckBody {
316
300
  negotiated_ver: number;
317
301
  subc_ops: string[];
@@ -367,6 +351,7 @@ export class SubcProviderError extends Error {
367
351
  constructor(
368
352
  message: string,
369
353
  readonly code?: string,
354
+ readonly kind: SubcCallErrorKind = "terminal",
370
355
  ) {
371
356
  super(message);
372
357
  }
@@ -391,10 +376,10 @@ export function managementSurfaceManifest(opts: ManagementSurfaceManifestOptions
391
376
  config_schema: { type: "object" },
392
377
  observability: [],
393
378
  identity_scope: [],
379
+ concurrency: opts.concurrency ?? "module_managed",
394
380
  },
395
381
  ],
396
382
  consumes: [],
397
- scheduled_tasks: [],
398
383
  bindings: {
399
384
  storage: {
400
385
  kind: "sqlite",
@@ -435,10 +420,10 @@ export class SubcProvider {
435
420
  private nextCorr = 1n;
436
421
  private ingressEpochDropCount = 0;
437
422
  private readonly requestGate = new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY);
438
- private reconnecting: Promise<void> | null = null;
423
+ private reconnecting: ReconnectCycle | null = null;
439
424
  private generation = 1;
440
425
  private connectionEpoch = 1;
441
- private stateQueue: ProviderConnectionState[] = [];
426
+ private stateQueue: Array<{ event: ProviderConnectionState; generation?: number; reconnect?: ReconnectCycle }> = [];
442
427
  private drainingStateQueue = false;
443
428
  private restoredDebounceToken = 0;
444
429
 
@@ -538,22 +523,22 @@ export class SubcProvider {
538
523
 
539
524
  cancel(handle: RouteHandle, corr: bigint): void {
540
525
  this.assertLiveHandle(handle);
541
- void this.sendOn(
526
+ this.sendOn(
542
527
  this.sock,
543
528
  this.generation,
544
529
  buildFrame(FrameType.Cancel, controlFlags(), handle.channel, handle.epoch, corr, new Uint8Array(0)),
545
- );
530
+ ).catch(() => undefined);
546
531
  }
547
532
 
548
533
  closeRoute(handle: RouteHandle): void {
549
534
  this.assertLiveHandle(handle);
550
535
  this.liveRoutes.delete(handle.channel);
551
536
  this.abortHandle(handle);
552
- void this.sendOn(
537
+ this.sendOn(
553
538
  this.sock,
554
539
  this.generation,
555
540
  buildFrame(FrameType.Goodbye, controlFlags(), handle.channel, handle.epoch, 0n, new Uint8Array(0)),
556
- );
541
+ ).catch(() => undefined);
557
542
  }
558
543
 
559
544
  /** Read the connection file, authenticate as a client, register the manifest with HELLO, and serve frames. */
@@ -587,12 +572,16 @@ export class SubcProvider {
587
572
  await this.closed;
588
573
  }
589
574
 
590
- private static async openConnection(opts: NormalizedSubcProviderConnectOptions): Promise<OpenedProviderConnection> {
575
+ private static async openConnection(
576
+ opts: NormalizedSubcProviderConnectOptions,
577
+ onSocket?: (sock: SubcSocket) => void,
578
+ ): Promise<OpenedProviderConnection> {
591
579
  const conn = await readConnectionFile(opts.connectionFile);
592
580
  const deadline = Date.now() + (opts.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS);
593
581
  const endpoint = conn.endpoints[0]!;
594
582
  const sock = await SubcSocket.connect(endpoint.host, endpoint.port, deadline);
595
583
  try {
584
+ onSocket?.(sock);
596
585
  await authenticateClient(sock, conn, deadline);
597
586
  await sendFrame(sock, buildHelloFrame(opts));
598
587
  const ack = await expectHelloAck(sock, deadline);
@@ -748,11 +737,32 @@ export class SubcProvider {
748
737
  throw new SubcProviderError(`unsupported module control request ${request.op ?? "<missing op>"}`);
749
738
  }
750
739
 
751
- const tentative = createRouteHandle(
752
- numberField(request.route_channel, "route_channel"),
753
- numberField(request.epoch, "epoch"),
754
- this.connectionToken,
755
- );
740
+ const boundChannel = numberField(request.route_channel, "route_channel");
741
+ const boundEpoch = numberField(request.epoch, "epoch");
742
+ // Implicit-replace rule (wire spec 3.3.0): the daemon never rebinds a live
743
+ // channel, but its route-gone GOODBYE to modules is best-effort, so a bind can
744
+ // arrive for a channel this endpoint still believes installed. A strictly
745
+ // higher epoch proves the daemon freed the old binding: tear the stale install
746
+ // down locally and proceed. Equal or lower epoch is a protocol violation the
747
+ // daemon cannot produce: reject the bind.
748
+ const stale = this.liveRoutes.get(boundChannel);
749
+ if (stale) {
750
+ if (boundEpoch <= stale.epoch) {
751
+ await this.sendError(
752
+ frame,
753
+ "route_rejected",
754
+ `route.bind epoch ${boundEpoch} does not supersede installed epoch ${stale.epoch} on channel ${boundChannel}`,
755
+ controlFlags(),
756
+ sock,
757
+ generation,
758
+ );
759
+ return;
760
+ }
761
+ this.liveRoutes.delete(stale.channel);
762
+ this.abortHandle(stale);
763
+ await this.opts.onRouteGone?.(stale);
764
+ }
765
+ const tentative = createRouteHandle(boundChannel, boundEpoch, this.connectionToken);
756
766
  const bindRequest: RouteBindRequest = {
757
767
  handle: tentative,
758
768
  target: request.target as RouteTarget,
@@ -856,8 +866,15 @@ export class SubcProvider {
856
866
  const releasePermit = await (this.requestGate ?? new AsyncPermitPool(DEFAULT_PROVIDER_HANDLER_CAPACITY)).acquire();
857
867
  const dataFlags = buildFlags(false, Priority.Interactive, false);
858
868
  try {
869
+ if (controller.signal.aborted) {
870
+ await this.sendError(frame, "cancelled", "request cancelled", dataFlags, sock, generation);
871
+ return;
872
+ }
859
873
  const body = await this.opts.handler(handle, frame.body, context);
860
- if (controller.signal.aborted) return;
874
+ if (controller.signal.aborted) {
875
+ await this.sendError(frame, "cancelled", "request cancelled", dataFlags, sock, generation);
876
+ return;
877
+ }
861
878
  this.assertLiveHandle(handle);
862
879
  if (body === undefined) {
863
880
  await this.sendOn(
@@ -891,7 +908,11 @@ export class SubcProvider {
891
908
  throw new SubcProviderError("provider handler must return a Uint8Array or void", "invalid_handler_response");
892
909
  }
893
910
  } catch (error) {
894
- if (error instanceof StaleRouteHandleError || controller.signal.aborted) return;
911
+ if (error instanceof StaleRouteHandleError) return;
912
+ if (controller.signal.aborted) {
913
+ await this.sendError(frame, "cancelled", "request cancelled", dataFlags, sock, generation);
914
+ return;
915
+ }
895
916
  await this.sendError(
896
917
  frame,
897
918
  error instanceof SubcProviderError && error.code ? error.code : "handler_error",
@@ -956,6 +977,10 @@ export class SubcProvider {
956
977
  sock: SubcSocket,
957
978
  generation: number,
958
979
  ): Promise<void> {
980
+ if (frame.header.channel !== 0) {
981
+ const handle = this.liveRoutes.get(frame.header.channel);
982
+ if (!handle || handle.epoch !== frame.header.epoch || !this.isLiveHandle(handle)) return;
983
+ }
959
984
  await this.sendOn(
960
985
  sock,
961
986
  generation,
@@ -977,43 +1002,79 @@ export class SubcProvider {
977
1002
  }
978
1003
 
979
1004
  private handleUnexpectedDrop(sock: SubcSocket, generation: number, cause: Error): void {
1005
+ if (this.closeStarted || this.sock !== sock || this.generation !== generation) return;
980
1006
  this.cancelRestoredDebounce();
981
1007
  this.abortGeneration(generation);
982
1008
  this.generation += 1;
983
1009
  sock.close();
984
- this.enqueueConnectionState({ state: "down", cause });
985
- this.scheduleReconnectAfterDrop(cause);
1010
+ this.scheduleReconnectAfterDrop(cause, this.generation, sock);
986
1011
  }
987
1012
 
988
- private scheduleReconnectAfterDrop(trigger: unknown): void {
989
- if (this.closeStarted || this.reconnecting) return;
990
- const promise = this.reconnectWithRetry(trigger)
1013
+ private scheduleReconnectAfterDrop(cause: Error, generation: number, droppedSocket: SubcSocket): void {
1014
+ if (this.closeStarted) return;
1015
+
1016
+ const previous = this.reconnecting;
1017
+ if (previous) {
1018
+ if (!this.shouldSupersedeReconnect(previous, generation, droppedSocket)) return;
1019
+ previous.superseded = true;
1020
+ previous.socket?.close();
1021
+ }
1022
+
1023
+ const cycle: ReconnectCycle = {
1024
+ generation,
1025
+ socket: null,
1026
+ socketDied: false,
1027
+ superseded: false,
1028
+ };
1029
+ this.reconnecting = cycle;
1030
+ this.enqueueConnectionState({ state: "down", cause });
1031
+ void this.reconnectWithRetry(cycle)
991
1032
  .catch((err) => {
992
- if (!this.closeStarted) this.failFatal(err instanceof Error ? err : new SubcProviderError(String(err)));
1033
+ if (this.isCurrentReconnect(cycle) && !this.closeStarted) {
1034
+ this.failFatal(err instanceof Error ? err : new SubcProviderError(String(err)));
1035
+ }
993
1036
  })
994
1037
  .finally(() => {
995
- if (this.reconnecting === promise) this.reconnecting = null;
1038
+ if (this.isCurrentReconnect(cycle)) this.reconnecting = null;
996
1039
  });
997
- this.reconnecting = promise;
998
1040
  }
999
1041
 
1000
- private async reconnectWithRetry(_trigger: unknown): Promise<void> {
1042
+ private async reconnectWithRetry(cycle: ReconnectCycle): Promise<void> {
1001
1043
  let attempt = 0;
1002
1044
  let delay = this.opts.reconnectBackoff.baseMs;
1003
1045
 
1004
1046
  for (;;) {
1047
+ if (!this.isCurrentReconnect(cycle)) return;
1005
1048
  if (this.closeStarted) throw new SubcProviderError("provider closed");
1049
+
1050
+ cycle.socket = null;
1051
+ cycle.socketDied = false;
1006
1052
  attempt += 1;
1007
- this.enqueueConnectionState({ state: "reconnecting", attempt });
1053
+ this.enqueueReconnectState(cycle, attempt);
1008
1054
  try {
1009
- const opened = await SubcProvider.openConnection(this.opts);
1010
- if (this.closeStarted) {
1055
+ const opened = await SubcProvider.openConnection(this.opts, (sock) => {
1056
+ if (!this.isCurrentReconnect(cycle)) {
1057
+ sock.close();
1058
+ return;
1059
+ }
1060
+ cycle.socket = sock;
1061
+ });
1062
+ if (!this.isCurrentReconnect(cycle) || this.closeStarted) {
1011
1063
  opened.sock.close();
1012
- throw new SubcProviderError("provider closed");
1064
+ if (this.closeStarted) throw new SubcProviderError("provider closed");
1065
+ return;
1066
+ }
1067
+
1068
+ const epoch = this.replaceConnection(opened, cycle.generation);
1069
+ this.reconnecting = null;
1070
+ if (this.reconnecting !== null) {
1071
+ throw new SubcProviderError("reconnect state must clear before restored", "reconnect_state");
1013
1072
  }
1014
- this.replaceConnection(opened);
1073
+ this.scheduleRestored(cycle.generation, epoch);
1015
1074
  return;
1016
1075
  } catch (err) {
1076
+ if (!this.isCurrentReconnect(cycle)) return;
1077
+ if (cycle.socket) cycle.socketDied = true;
1017
1078
  if (this.closeStarted) throw err;
1018
1079
  if (!isProviderReconnectTransient(err)) throw err;
1019
1080
  // Providers retry transient failures indefinitely by design (a module
@@ -1026,7 +1087,20 @@ export class SubcProvider {
1026
1087
  }
1027
1088
  }
1028
1089
 
1029
- private replaceConnection(opened: OpenedProviderConnection): void {
1090
+ private isCurrentReconnect(cycle: ReconnectCycle): boolean {
1091
+ return !cycle.superseded && this.reconnecting === cycle && this.generation === cycle.generation;
1092
+ }
1093
+
1094
+ private shouldSupersedeReconnect(cycle: ReconnectCycle, generation: number, droppedSocket: SubcSocket): boolean {
1095
+ return cycle.generation < generation || cycle.socketDied || cycle.socket === droppedSocket;
1096
+ }
1097
+
1098
+ private enqueueReconnectState(cycle: ReconnectCycle, attempt: number): void {
1099
+ if (!this.isCurrentReconnect(cycle)) return;
1100
+ this.enqueueConnectionState({ state: "reconnecting", attempt }, cycle.generation, cycle);
1101
+ }
1102
+
1103
+ private replaceConnection(opened: OpenedProviderConnection, generation: number): number {
1030
1104
  this.sock.close();
1031
1105
  this.sock = opened.sock;
1032
1106
  this.currentConn = opened.conn;
@@ -1036,9 +1110,8 @@ export class SubcProvider {
1036
1110
  this.connectionToken = newConnectionToken();
1037
1111
  this.liveRoutes.clear();
1038
1112
  this.nextCorr = 1n;
1039
- const generation = this.generation;
1040
1113
  void this.readLoop(opened.sock, generation);
1041
- this.scheduleRestored(generation, this.connectionEpoch);
1114
+ return this.connectionEpoch;
1042
1115
  }
1043
1116
 
1044
1117
  private scheduleRestored(generation: number, epoch: number): void {
@@ -1054,7 +1127,7 @@ export class SubcProvider {
1054
1127
  this.generation === generation &&
1055
1128
  this.connectionEpoch === epoch
1056
1129
  ) {
1057
- this.enqueueConnectionState({ state: "restored", epoch });
1130
+ this.enqueueConnectionState({ state: "restored", epoch }, generation);
1058
1131
  }
1059
1132
  })
1060
1133
  .catch((err) => {
@@ -1068,9 +1141,9 @@ export class SubcProvider {
1068
1141
  this.restoredDebounceToken += 1;
1069
1142
  }
1070
1143
 
1071
- private enqueueConnectionState(event: ProviderConnectionState): void {
1144
+ private enqueueConnectionState(event: ProviderConnectionState, generation?: number, reconnect?: ReconnectCycle): void {
1072
1145
  if (!this.opts.onConnectionState) return;
1073
- this.stateQueue.push(event);
1146
+ this.stateQueue.push({ event, generation, reconnect });
1074
1147
  if (!this.drainingStateQueue) void this.drainConnectionStateQueue();
1075
1148
  }
1076
1149
 
@@ -1079,7 +1152,17 @@ export class SubcProvider {
1079
1152
  this.drainingStateQueue = true;
1080
1153
  try {
1081
1154
  while (this.stateQueue.length > 0) {
1082
- const event = this.stateQueue[0]!;
1155
+ const queued = this.stateQueue[0]!;
1156
+ if (
1157
+ this.closeStarted ||
1158
+ (queued.generation !== undefined && queued.generation !== this.generation) ||
1159
+ queued.reconnect?.superseded
1160
+ ) {
1161
+ this.stateQueue.shift();
1162
+ continue;
1163
+ }
1164
+
1165
+ const { event } = queued;
1083
1166
  try {
1084
1167
  await this.opts.onConnectionState?.(event);
1085
1168
  this.stateQueue.shift();
@@ -1140,7 +1223,11 @@ function routeKey(handle: RouteHandle, corr: bigint): string {
1140
1223
  function providerErrorFromFrame(frame: Frame): SubcProviderError {
1141
1224
  try {
1142
1225
  const body = parseJson(frame.body) as { code?: string; message?: string };
1143
- return new SubcProviderError(body.message ?? "subc error", body.code);
1226
+ return new SubcProviderError(
1227
+ body.message ?? "subc error",
1228
+ body.code,
1229
+ body.code === "stale_route_epoch" || body.code === "unknown_channel" ? "not_sent" : "terminal",
1230
+ );
1144
1231
  } catch {
1145
1232
  return new SubcProviderError(Buffer.from(frame.body).toString("utf8") || "subc error");
1146
1233
  }
@@ -1234,7 +1321,7 @@ function controlFlags(): number {
1234
1321
  }
1235
1322
 
1236
1323
  async function sendFrame(sock: SubcSocket, frame: Frame): Promise<void> {
1237
- await sock.write(encodeFrame(frame), Date.now() + WRITE_TIMEOUT_MS);
1324
+ await writeBorrowed(sock, encodeFrame(frame), Date.now() + WRITE_TIMEOUT_MS);
1238
1325
  }
1239
1326
 
1240
1327
  async function expectHelloAck(sock: SubcSocket, deadline: number): Promise<ModuleHelloAckBody> {
@@ -1297,7 +1384,6 @@ function normalizeManifest(manifest: ManifestInput): ManifestInput {
1297
1384
  trust_tier: manifest.trust_tier,
1298
1385
  provides: manifest.provides.map(normalizeProviderRole),
1299
1386
  consumes: manifest.consumes.map(normalizeConsumerRole),
1300
- scheduled_tasks: manifest.scheduled_tasks.map(normalizeScheduledTask),
1301
1387
  bindings: {
1302
1388
  storage: {
1303
1389
  kind: manifest.bindings.storage.kind,
@@ -1358,6 +1444,7 @@ function normalizeProviderRole(role: ProviderRoleInput): ProviderRoleInput {
1358
1444
  kind: surface.kind,
1359
1445
  })),
1360
1446
  identity_scope: [...role.identity_scope],
1447
+ concurrency: role.concurrency,
1361
1448
  };
1362
1449
  case "internal_service":
1363
1450
  return {
@@ -1381,23 +1468,3 @@ function normalizeConsumerRole(role: ConsumerRoleInput): ConsumerRoleInput {
1381
1468
  }
1382
1469
  }
1383
1470
 
1384
- function normalizeScheduledTask(task: ScheduledTaskInput): ScheduledTaskInput {
1385
- return {
1386
- task_id: task.task_id,
1387
- eligibility: {
1388
- cooldown: task.eligibility.cooldown,
1389
- window: task.eligibility.window,
1390
- },
1391
- lease_scope: task.lease_scope,
1392
- renews_during_calls: task.renews_during_calls,
1393
- toolset: [...task.toolset],
1394
- model_policy: {
1395
- tier: task.model_policy.tier,
1396
- fallback_chain: [...task.model_policy.fallback_chain],
1397
- },
1398
- step_cap: task.step_cap,
1399
- circuit_breaker: {
1400
- identical_failures: task.circuit_breaker.identical_failures,
1401
- },
1402
- };
1403
- }
package/src/socket.ts CHANGED
@@ -44,6 +44,28 @@ export interface SocketWriteResult {
44
44
  completed: Promise<void>;
45
45
  }
46
46
 
47
+ export function toWriteBuffer(bytes: Uint8Array): Buffer {
48
+ // Internal frame writes pass encodeFrame's fresh, single-use Uint8Array.
49
+ // Authentication writes likewise use fresh prefix/JSON storage and await each
50
+ // write without mutating it, so this view remains stable while Node drains it.
51
+ return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
52
+ }
53
+
54
+ const WRITE_BORROWED = Symbol("subc.socket.writeBorrowed");
55
+ const WRITE_TRACKED_BORROWED = Symbol("subc.socket.writeTrackedBorrowed");
56
+
57
+ /** @internal Write fresh, single-use internal storage without copying it. */
58
+ export function writeBorrowed(socket: SubcSocket, bytes: Uint8Array, deadlineMs: number): Promise<void> {
59
+ const borrowed = socket[WRITE_BORROWED];
60
+ return borrowed ? borrowed.call(socket, bytes, deadlineMs) : socket.write(bytes, deadlineMs);
61
+ }
62
+
63
+ /** @internal Track a write of fresh, single-use internal storage without copying it. */
64
+ export function writeTrackedBorrowed(socket: SubcSocket, bytes: Uint8Array, deadlineMs: number): SocketWriteResult {
65
+ const borrowed = socket[WRITE_TRACKED_BORROWED];
66
+ return borrowed ? borrowed.call(socket, bytes, deadlineMs) : socket.writeTracked(bytes, deadlineMs);
67
+ }
68
+
47
69
  interface Waiter {
48
70
  need: number;
49
71
  resolve: (bytes: Uint8Array) => void;
@@ -174,8 +196,24 @@ export class SubcSocket {
174
196
  }
175
197
 
176
198
  async write(bytes: Uint8Array, deadlineMs: number): Promise<void> {
199
+ await this.writeBuffer(Buffer.from(bytes), deadlineMs);
200
+ }
201
+
202
+ writeTracked(bytes: Uint8Array, deadlineMs: number): SocketWriteResult {
203
+ return this.writeTrackedBuffer(Buffer.from(bytes), deadlineMs);
204
+ }
205
+
206
+ async [WRITE_BORROWED](bytes: Uint8Array, deadlineMs: number): Promise<void> {
207
+ await this.writeBuffer(toWriteBuffer(bytes), deadlineMs);
208
+ }
209
+
210
+ [WRITE_TRACKED_BORROWED](bytes: Uint8Array, deadlineMs: number): SocketWriteResult {
211
+ return this.writeTrackedBuffer(toWriteBuffer(bytes), deadlineMs);
212
+ }
213
+
214
+ private async writeBuffer(buffer: Buffer, deadlineMs: number): Promise<void> {
177
215
  try {
178
- await this.writeTracked(bytes, deadlineMs).completed;
216
+ await this.writeTrackedBuffer(buffer, deadlineMs).completed;
179
217
  } catch (err) {
180
218
  if (err instanceof SocketWriteNotQueuedError || err instanceof SocketWriteQueuedError) {
181
219
  throw err.cause ?? err;
@@ -184,7 +222,7 @@ export class SubcSocket {
184
222
  }
185
223
  }
186
224
 
187
- writeTracked(bytes: Uint8Array, deadlineMs: number): SocketWriteResult {
225
+ private writeTrackedBuffer(buffer: Buffer, deadlineMs: number): SocketWriteResult {
188
226
  if (this.closedErr) {
189
227
  return {
190
228
  queued: false,
@@ -230,7 +268,7 @@ export class SubcSocket {
230
268
  }, remaining);
231
269
 
232
270
  try {
233
- this.sock.write(Buffer.from(bytes), (err) => {
271
+ this.sock.write(buffer, (err) => {
234
272
  settle(() => {
235
273
  if (err) {
236
274
  reject(