@query-farm/vgi-rpc 0.7.3 → 0.7.5

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/dist/index.js CHANGED
@@ -8168,1204 +8168,1197 @@ function mtlsAuthenticateSubject(options) {
8168
8168
  }
8169
8169
  return mtlsAuthenticate({ validate, header, checkExpiry });
8170
8170
  }
8171
- // src/launcher/hash.ts
8172
- var HASH_LEN = 16;
8173
- function canonicalJson(value) {
8174
- if (value === null)
8175
- return "null";
8176
- if (typeof value === "boolean")
8177
- return value ? "true" : "false";
8178
- if (typeof value === "number") {
8179
- return JSON.stringify(value);
8180
- }
8181
- if (typeof value === "string")
8182
- return JSON.stringify(value);
8183
- if (Array.isArray(value)) {
8184
- return `[${value.map(canonicalJson).join(",")}]`;
8185
- }
8186
- if (typeof value === "object") {
8187
- const keys = Object.keys(value).sort();
8188
- const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`);
8189
- return `{${parts.join(",")}}`;
8190
- }
8191
- throw new TypeError(`canonicalJson: unsupported type ${typeof value}`);
8171
+ // src/schema.ts
8172
+ var str = utf8();
8173
+ var bytes = binary();
8174
+ var int = int64();
8175
+ var int322 = int32();
8176
+ var int162 = int16();
8177
+ var int82 = int8();
8178
+ var uint82 = uint8();
8179
+ var uint162 = uint16();
8180
+ var uint322 = uint32();
8181
+ var uint642 = uint64();
8182
+ var float = float64();
8183
+ var float322 = float32();
8184
+ var bool2 = bool();
8185
+ function isField(x) {
8186
+ return x != null && typeof x.name === "string" && x.type != null && typeof x.nullable === "boolean";
8192
8187
  }
8193
- async function sha256Hex3(data) {
8194
- const buf2 = new ArrayBuffer(data.byteLength);
8195
- new Uint8Array(buf2).set(data);
8196
- const digest = await crypto.subtle.digest("SHA-256", buf2);
8197
- return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
8188
+ function isDataType(x) {
8189
+ return x != null && typeof x.typeId === "number";
8198
8190
  }
8199
- async function computeHash(workerArgv, cwd, env) {
8200
- const cwdValue = cwd !== undefined ? cwd : process.cwd();
8201
- const sourceEnv = env ?? process.env;
8202
- const filteredEnv = {};
8203
- for (const key of Object.keys(sourceEnv)) {
8204
- if (key.startsWith("VGI_RPC_")) {
8205
- const v = sourceEnv[key];
8206
- if (v !== undefined)
8207
- filteredEnv[key] = v;
8191
+ function toSchema(spec) {
8192
+ const maybeFields = spec.fields;
8193
+ if (Array.isArray(maybeFields)) {
8194
+ const out = [];
8195
+ for (const f of maybeFields) {
8196
+ if (isField(f)) {
8197
+ out.push(f);
8198
+ } else {
8199
+ out.push(field(f.name, f.type, f.nullable ?? true, f.metadata));
8200
+ }
8208
8201
  }
8202
+ return schema(out);
8209
8203
  }
8210
- const canonical = {
8211
- cmd: [...workerArgv],
8212
- cwd: cwdValue,
8213
- env: filteredEnv
8214
- };
8215
- const payload = new TextEncoder().encode(canonicalJson(canonical));
8216
- const hex = await sha256Hex3(payload);
8217
- return hex.slice(0, HASH_LEN);
8218
- }
8219
- // src/launcher/launch.ts
8220
- import { spawn } from "node:child_process";
8221
- import { createWriteStream, unlinkSync as unlinkSync2 } from "node:fs";
8222
-
8223
- // src/launcher/lock.ts
8224
- import { closeSync, constants as FS, openSync, readSync, statSync, writeSync } from "node:fs";
8225
- var POLL_MS = 50;
8226
- var VERIFY_RETRIES = 5;
8227
- function pidAlive(pid) {
8228
- if (!Number.isInteger(pid) || pid <= 0)
8229
- return false;
8230
- try {
8231
- process.kill(pid, 0);
8232
- return true;
8233
- } catch (err2) {
8234
- return err2?.code === "EPERM";
8204
+ const fields = [];
8205
+ for (const [name, value] of Object.entries(spec)) {
8206
+ if (isField(value)) {
8207
+ fields.push(value);
8208
+ } else if (isDataType(value)) {
8209
+ fields.push(field(name, value, false));
8210
+ } else {
8211
+ throw new TypeError(`Invalid schema value for "${name}": expected DataType or Field, got ${typeof value}`);
8212
+ }
8235
8213
  }
8214
+ return schema(fields);
8236
8215
  }
8237
- function readPid(path) {
8238
- try {
8239
- const fd = openSync(path, FS.O_RDONLY);
8240
- try {
8241
- const buf2 = Buffer.alloc(64);
8242
- const n = readSync(fd, buf2, 0, buf2.length, 0);
8243
- const text = buf2.subarray(0, n).toString("utf8").trim();
8244
- if (text === "")
8245
- return 0;
8246
- const parsed = Number(text);
8247
- return Number.isInteger(parsed) ? parsed : 0;
8248
- } finally {
8249
- closeSync(fd);
8250
- }
8251
- } catch {
8252
- return 0;
8216
+ function inferParamTypes(spec) {
8217
+ const sch = toSchema(spec);
8218
+ if (sch.fields.length === 0)
8219
+ return;
8220
+ const result = {};
8221
+ for (const f of sch.fields) {
8222
+ let mapped;
8223
+ if (isUtf8(f.type))
8224
+ mapped = "str";
8225
+ else if (isBinary(f.type))
8226
+ mapped = "bytes";
8227
+ else if (isBool(f.type))
8228
+ mapped = "bool";
8229
+ else if (isFloat(f.type))
8230
+ mapped = "float";
8231
+ else if (isInt(f.type))
8232
+ mapped = "int";
8233
+ if (!mapped)
8234
+ return;
8235
+ result[f.name] = mapped;
8253
8236
  }
8237
+ return result;
8254
8238
  }
8255
- function tryStampPid(path) {
8256
- const fd = openSync(path, FS.O_RDWR | FS.O_CREAT, 384);
8257
- try {
8258
- const stamp = Buffer.from(String(process.pid), "utf8");
8259
- const { ftruncateSync } = __require("node:fs");
8260
- ftruncateSync(fd, 0);
8261
- let written = 0;
8262
- while (written < stamp.length) {
8263
- const n = writeSync(fd, stamp, written, stamp.length - written, 0 + written);
8264
- if (n <= 0)
8265
- throw new Error(`writeSync returned ${n}`);
8266
- written += n;
8239
+
8240
+ // src/protocol.ts
8241
+ var EMPTY_SCHEMA3 = schema([]);
8242
+
8243
+ class Protocol {
8244
+ name;
8245
+ protocolVersion;
8246
+ protocolVersionParts;
8247
+ _methods = new Map;
8248
+ constructor(name, options) {
8249
+ this.name = name;
8250
+ const raw = options?.protocolVersion;
8251
+ if (raw === undefined || raw === "") {
8252
+ this.protocolVersion = "";
8253
+ this.protocolVersionParts = null;
8254
+ } else {
8255
+ this.protocolVersion = raw;
8256
+ this.protocolVersionParts = parseProtocolVersion(raw);
8267
8257
  }
8268
- const st = statSync(path);
8269
- if (st.size !== stamp.length)
8270
- return false;
8271
- return true;
8272
- } finally {
8273
- closeSync(fd);
8258
+ }
8259
+ unary(name, config) {
8260
+ const params = toSchema(config.params);
8261
+ this._methods.set(name, {
8262
+ name,
8263
+ type: "unary" /* UNARY */,
8264
+ paramsSchema: params,
8265
+ resultSchema: toSchema(config.result),
8266
+ handler: config.handler,
8267
+ doc: config.doc,
8268
+ defaults: config.defaults,
8269
+ paramTypes: config.paramTypes ?? inferParamTypes(params)
8270
+ });
8271
+ return this;
8272
+ }
8273
+ producer(name, config) {
8274
+ const params = toSchema(config.params);
8275
+ this._methods.set(name, {
8276
+ name,
8277
+ type: "stream" /* STREAM */,
8278
+ paramsSchema: params,
8279
+ resultSchema: EMPTY_SCHEMA3,
8280
+ outputSchema: toSchema(config.outputSchema),
8281
+ inputSchema: EMPTY_SCHEMA3,
8282
+ producerInit: config.init,
8283
+ producerFn: config.produce,
8284
+ onCancel: config.onCancel,
8285
+ headerSchema: config.headerSchema ? toSchema(config.headerSchema) : undefined,
8286
+ headerInit: config.headerInit,
8287
+ doc: config.doc,
8288
+ defaults: config.defaults,
8289
+ paramTypes: config.paramTypes ?? inferParamTypes(params)
8290
+ });
8291
+ return this;
8292
+ }
8293
+ exchange(name, config) {
8294
+ const params = toSchema(config.params);
8295
+ this._methods.set(name, {
8296
+ name,
8297
+ type: "stream" /* STREAM */,
8298
+ paramsSchema: params,
8299
+ resultSchema: EMPTY_SCHEMA3,
8300
+ inputSchema: toSchema(config.inputSchema),
8301
+ outputSchema: toSchema(config.outputSchema),
8302
+ exchangeInit: config.init,
8303
+ exchangeFn: config.exchange,
8304
+ onCancel: config.onCancel,
8305
+ headerSchema: config.headerSchema ? toSchema(config.headerSchema) : undefined,
8306
+ headerInit: config.headerInit,
8307
+ doc: config.doc,
8308
+ defaults: config.defaults,
8309
+ paramTypes: config.paramTypes ?? inferParamTypes(params)
8310
+ });
8311
+ return this;
8312
+ }
8313
+ getMethods() {
8314
+ return new Map(this._methods);
8274
8315
  }
8275
8316
  }
8276
- function clearStamp(path) {
8317
+ // src/dispatch/stream.ts
8318
+ var EMPTY_SCHEMA4 = schema([]);
8319
+ async function dispatchStream(method, params, writer, reader, serverId, requestId, externalConfig, kind) {
8320
+ const isProducer = !!method.producerFn;
8321
+ let state;
8277
8322
  try {
8278
- const fd = openSync(path, FS.O_RDWR);
8279
- try {
8280
- const { ftruncateSync } = __require("node:fs");
8281
- ftruncateSync(fd, 0);
8282
- } finally {
8283
- closeSync(fd);
8323
+ if (isProducer) {
8324
+ state = await method.producerInit(params);
8325
+ } else {
8326
+ state = await method.exchangeInit(params);
8284
8327
  }
8285
- } catch {}
8286
- }
8287
- function tryAcquireLock(lockPath) {
8288
- for (let attempt = 0;attempt < VERIFY_RETRIES; attempt++) {
8289
- const existingPid = readPid(lockPath);
8290
- if (existingPid > 0 && pidAlive(existingPid)) {
8291
- return null;
8328
+ } catch (error) {
8329
+ const errSchema = method.headerSchema ?? EMPTY_SCHEMA4;
8330
+ const errBatch = buildErrorBatch(errSchema, error, serverId, requestId);
8331
+ await writer.writeStream(errSchema, [errBatch]);
8332
+ const inputSchema2 = await reader.openNextStream();
8333
+ if (inputSchema2) {
8334
+ while (await reader.readNextBatch() !== null) {}
8292
8335
  }
8293
- if (!tryStampPid(lockPath))
8294
- continue;
8295
- const verifyPid = readPid(lockPath);
8296
- if (verifyPid !== process.pid) {
8297
- continue;
8298
- }
8299
- let released = false;
8300
- return {
8301
- path: lockPath,
8302
- release() {
8303
- if (released)
8304
- return;
8305
- released = true;
8306
- clearStamp(lockPath);
8307
- }
8308
- };
8336
+ return;
8309
8337
  }
8310
- return null;
8311
- }
8312
- async function acquireLock(lockPath, timeoutMs) {
8313
- const deadline = Date.now() + Math.max(0, timeoutMs);
8314
- for (;; ) {
8315
- const handle = tryAcquireLock(lockPath);
8316
- if (handle)
8317
- return handle;
8318
- if (Date.now() >= deadline) {
8319
- throw new Error(`failed to acquire ${lockPath} within ${timeoutMs}ms`);
8338
+ const outputSchema = state?.__outputSchema ?? method.outputSchema;
8339
+ const effectiveProducer = state?.__isProducer ?? isProducer;
8340
+ if (method.headerSchema && method.headerInit) {
8341
+ try {
8342
+ const headerOut = new OutputCollector(method.headerSchema, true, serverId, requestId, undefined, undefined, kind);
8343
+ const headerValues = method.headerInit(params, state, headerOut);
8344
+ const headerBatch = buildResultBatch(method.headerSchema, headerValues, serverId, requestId);
8345
+ const headerBatches = [...headerOut.batches.map((b) => b.batch), headerBatch];
8346
+ await writer.writeStream(method.headerSchema, headerBatches);
8347
+ } catch (error) {
8348
+ const errBatch = buildErrorBatch(method.headerSchema, error, serverId, requestId);
8349
+ await writer.writeStream(method.headerSchema, [errBatch]);
8350
+ const inputSchema2 = await reader.openNextStream();
8351
+ if (inputSchema2) {
8352
+ while (await reader.readNextBatch() !== null) {}
8353
+ }
8354
+ return;
8320
8355
  }
8321
- await new Promise((r) => setTimeout(r, POLL_MS));
8322
8356
  }
8323
- }
8324
-
8325
- // src/launcher/state.ts
8326
- import { existsSync, mkdirSync, readFileSync, statSync as statSync2, unlinkSync, writeFileSync } from "node:fs";
8327
- import { tmpdir } from "node:os";
8328
- import * as path from "node:path";
8329
- function socketPaths(stateDir, hashId) {
8330
- return {
8331
- lockPath: path.join(stateDir, `${hashId}.lock`),
8332
- sockPath: path.join(stateDir, `${hashId}.sock`),
8333
- metaPath: path.join(stateDir, `${hashId}.meta`)
8334
- };
8335
- }
8336
- function defaultStateDir() {
8337
- let base;
8338
- if (process.platform === "win32") {
8339
- base = path.join(tmpdir(), "vgi-rpc");
8340
- } else {
8341
- const xdg = process.env.XDG_RUNTIME_DIR;
8342
- if (xdg) {
8343
- base = path.join(xdg, "vgi-rpc");
8344
- } else {
8345
- const uid = typeof process.geteuid === "function" ? process.geteuid() : 0;
8346
- base = path.join(tmpdir(), `vgi-rpc-${uid}`);
8347
- }
8357
+ const inputSchema = await reader.openNextStream();
8358
+ if (!inputSchema) {
8359
+ const errBatch = buildErrorBatch(outputSchema, new Error("Expected input stream but got EOF"), serverId, requestId);
8360
+ await writer.writeStream(outputSchema, [errBatch]);
8361
+ return;
8348
8362
  }
8349
- mkdirSync(base, { recursive: true, mode: 448 });
8350
- if (process.platform !== "win32" && typeof process.geteuid === "function") {
8351
- try {
8352
- const st = statSync2(base);
8353
- if (st.uid !== process.geteuid()) {
8354
- throw new Error(`state directory ${base} is not owned by current user`);
8363
+ const stream = writer.openStream(outputSchema);
8364
+ const expectedInputSchema = state?.__inputSchema ?? method.inputSchema;
8365
+ try {
8366
+ while (true) {
8367
+ let inputBatch = await reader.readNextBatch();
8368
+ if (!inputBatch)
8369
+ break;
8370
+ if (inputBatch.metadata?.get(CANCEL_KEY)) {
8371
+ if (method.onCancel) {
8372
+ try {
8373
+ await method.onCancel(state);
8374
+ } catch (err2) {
8375
+ console.debug?.(`onCancel hook failed: ${err2 instanceof Error ? err2.message : err2}`);
8376
+ }
8377
+ }
8378
+ break;
8355
8379
  }
8356
- } catch (err2) {
8357
- if (err2?.code === "ENOENT") {} else {
8358
- throw err2;
8380
+ if (expectedInputSchema && !effectiveProducer && inputBatch.schema !== expectedInputSchema) {
8381
+ try {
8382
+ inputBatch = conformBatchToSchema(inputBatch, expectedInputSchema);
8383
+ } catch (e) {
8384
+ if (e instanceof TypeError)
8385
+ throw e;
8386
+ console.debug?.(`Schema conformance skipped: ${e instanceof Error ? e.message : e}`);
8387
+ }
8388
+ }
8389
+ const out = new OutputCollector(outputSchema, effectiveProducer, serverId, requestId, undefined, undefined, kind);
8390
+ if (isProducer) {
8391
+ await method.producerFn(state, out);
8392
+ } else {
8393
+ await method.exchangeFn(state, inputBatch, out);
8394
+ }
8395
+ for (const emitted of out.batches) {
8396
+ let batch = emitted.batch;
8397
+ if (externalConfig) {
8398
+ batch = await maybeExternalizeBatch(batch, externalConfig);
8399
+ }
8400
+ if (emitted.metadata && emitted.metadata.size > 0) {
8401
+ batch = withBatchMetadata(batch, emitted.metadata);
8402
+ }
8403
+ await stream.write(batch);
8404
+ }
8405
+ if (out.finished) {
8406
+ break;
8359
8407
  }
8360
8408
  }
8409
+ } catch (error) {
8410
+ await stream.write(buildErrorBatch(outputSchema, error, serverId, requestId));
8361
8411
  }
8362
- return base;
8363
- }
8364
- function writeMeta(metaPath, workerArgv, cwd, sockPath) {
8365
- const payload = {
8366
- cmd: [...workerArgv],
8367
- cwd,
8368
- socket: sockPath,
8369
- started_at: Date.now() / 1000,
8370
- launcher_pid: process.pid
8371
- };
8412
+ await stream.close();
8372
8413
  try {
8373
- writeFileSync(metaPath, JSON.stringify(payload, null, 2), { encoding: "utf8", mode: 384 });
8414
+ while (await reader.readNextBatch() !== null) {}
8374
8415
  } catch {}
8375
8416
  }
8376
- async function probeSocket(sockPath, timeoutMs = 2000) {
8377
- if (!existsSync(sockPath))
8378
- return false;
8379
- const net = await import("node:net");
8380
- return new Promise((resolve) => {
8381
- const sock = net.createConnection({ path: sockPath });
8382
- const timer = setTimeout(() => {
8383
- sock.destroy();
8384
- resolve(false);
8385
- }, timeoutMs);
8386
- sock.once("connect", () => {
8387
- clearTimeout(timer);
8388
- sock.end();
8389
- resolve(true);
8390
- });
8391
- sock.once("error", () => {
8392
- clearTimeout(timer);
8393
- resolve(false);
8394
- });
8395
- });
8396
- }
8397
- function tryReadMeta(metaPath) {
8417
+
8418
+ // src/dispatch/unary.ts
8419
+ async function dispatchUnary(method, params, writer, serverId, requestId, externalConfig, kind) {
8420
+ const schema2 = method.resultSchema;
8421
+ const out = new OutputCollector(schema2, true, serverId, requestId, undefined, undefined, kind);
8398
8422
  try {
8399
- const raw = readFileSync(metaPath, "utf8");
8400
- const meta = JSON.parse(raw);
8401
- return {
8402
- cmd: Array.isArray(meta.cmd) ? meta.cmd.map(String) : [],
8403
- cwd: typeof meta.cwd === "string" ? meta.cwd : "",
8404
- startedAt: typeof meta.started_at === "number" ? meta.started_at : null
8405
- };
8406
- } catch {
8407
- return { cmd: [], cwd: "", startedAt: null };
8423
+ const result = await method.handler(params, out);
8424
+ let resultBatch = buildResultBatch(schema2, result, serverId, requestId);
8425
+ if (externalConfig) {
8426
+ resultBatch = await maybeExternalizeBatch(resultBatch, externalConfig);
8427
+ }
8428
+ const batches = [...out.batches.map((b) => b.batch), resultBatch];
8429
+ await writer.writeStream(schema2, batches);
8430
+ } catch (error) {
8431
+ const batch = buildErrorBatch(schema2, error, serverId, requestId);
8432
+ await writer.writeStream(schema2, [batch]);
8408
8433
  }
8409
8434
  }
8410
- async function statusRows(stateDir) {
8411
- const { readdirSync } = await import("node:fs");
8412
- const rows = [];
8413
- let entries;
8414
- try {
8415
- entries = readdirSync(stateDir);
8416
- } catch {
8417
- return rows;
8435
+
8436
+ // src/wire/writer.ts
8437
+ var STDOUT_FD = 1;
8438
+ var _NODE_FS_MOD2 = "node:fs";
8439
+ var _writeSync = null;
8440
+ function _loadWriteSync2() {
8441
+ if (_writeSync)
8442
+ return _writeSync;
8443
+ const req = import.meta.require ?? globalThis.require ?? null;
8444
+ if (!req) {
8445
+ throw new Error("IpcStreamWriter requires Bun or Node.js CJS for sync node:fs.writeSync. " + "Subprocess transport is not available in this runtime.");
8418
8446
  }
8419
- for (const name of entries.sort()) {
8420
- if (!name.endsWith(".lock"))
8421
- continue;
8422
- const hashId = name.slice(0, -5);
8423
- const { sockPath, metaPath } = socketPaths(stateDir, hashId);
8424
- const meta = tryReadMeta(metaPath);
8425
- rows.push({
8426
- hashId,
8427
- cmd: meta.cmd,
8428
- cwd: meta.cwd,
8429
- socket: sockPath,
8430
- startedAt: meta.startedAt,
8431
- alive: await probeSocket(sockPath)
8432
- });
8447
+ const fs = req(_NODE_FS_MOD2);
8448
+ _writeSync = fs.writeSync.bind(fs);
8449
+ return _writeSync;
8450
+ }
8451
+ function writeAll(fd, data) {
8452
+ const writeSync = _loadWriteSync2();
8453
+ let offset = 0;
8454
+ while (offset < data.length) {
8455
+ try {
8456
+ const written = writeSync(fd, data, offset, data.length - offset);
8457
+ if (written <= 0)
8458
+ throw new Error(`writeSync returned ${written}`);
8459
+ offset += written;
8460
+ } catch (e) {
8461
+ if (e.code === "EAGAIN") {
8462
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1);
8463
+ continue;
8464
+ }
8465
+ throw e;
8466
+ }
8433
8467
  }
8434
- return rows;
8435
8468
  }
8436
- async function gcStateDir(stateDir, tryAcquire, options) {
8437
- const { readdirSync } = await import("node:fs");
8438
- const cleaned = [];
8439
- const skipped = [];
8440
- const limit = options?.limit ?? null;
8441
- const excludeHash = options?.excludeHash ?? null;
8442
- let entries;
8443
- try {
8444
- entries = readdirSync(stateDir);
8445
- } catch {
8446
- return { cleaned, skippedInUse: skipped };
8469
+ async function socketWriteAll(socket, data) {
8470
+ if (socket.destroyed || socket.writableEnded) {
8471
+ throw new Error("socketWriteAll: socket is already closed");
8447
8472
  }
8448
- let seen = 0;
8449
- for (const name of entries.sort()) {
8450
- if (!name.endsWith(".lock"))
8451
- continue;
8452
- if (limit !== null && seen >= limit)
8453
- break;
8454
- seen += 1;
8455
- const hashId = name.slice(0, -5);
8456
- if (excludeHash !== null && hashId === excludeHash)
8457
- continue;
8458
- const { lockPath, sockPath, metaPath } = socketPaths(stateDir, hashId);
8459
- const release = await tryAcquire(lockPath);
8460
- if (release === null) {
8461
- skipped.push(hashId);
8462
- continue;
8473
+ const ok = socket.write(data);
8474
+ if (ok)
8475
+ return;
8476
+ await new Promise((resolve, reject) => {
8477
+ const cleanup = () => {
8478
+ socket.off("drain", onDrain);
8479
+ socket.off("error", onError);
8480
+ socket.off("close", onClose);
8481
+ };
8482
+ const onDrain = () => {
8483
+ cleanup();
8484
+ resolve();
8485
+ };
8486
+ const onError = (err2) => {
8487
+ cleanup();
8488
+ reject(err2);
8489
+ };
8490
+ const onClose = () => {
8491
+ cleanup();
8492
+ resolve();
8493
+ };
8494
+ socket.once("drain", onDrain);
8495
+ socket.once("error", onError);
8496
+ socket.once("close", onClose);
8497
+ });
8498
+ }
8499
+
8500
+ class IpcStreamWriter {
8501
+ target;
8502
+ constructor(fdOrSocket = STDOUT_FD) {
8503
+ if (typeof fdOrSocket === "number") {
8504
+ this.target = { kind: "fd", fd: fdOrSocket };
8505
+ } else {
8506
+ this.target = { kind: "socket", socket: fdOrSocket };
8463
8507
  }
8464
- try {
8465
- if (await probeSocket(sockPath)) {
8466
- continue;
8467
- }
8468
- for (const p of [sockPath, metaPath, lockPath]) {
8469
- try {
8470
- unlinkSync(p);
8471
- } catch {}
8472
- }
8473
- cleaned.push(hashId);
8474
- } finally {
8475
- release();
8508
+ }
8509
+ async writeStream(schema2, batches) {
8510
+ const bytes2 = serializeBatches(schema2, batches);
8511
+ if (this.target.kind === "fd") {
8512
+ writeAll(this.target.fd, bytes2);
8513
+ } else {
8514
+ await socketWriteAll(this.target.socket, bytes2);
8476
8515
  }
8477
8516
  }
8478
- return { cleaned, skippedInUse: skipped };
8517
+ openStream(schema2) {
8518
+ return new IncrementalStream(this.target, schema2);
8519
+ }
8479
8520
  }
8480
8521
 
8481
- // src/launcher/launch.ts
8482
- var DEFAULT_GC_LIMIT = 16;
8483
- async function launch(config) {
8484
- if (!config.workerArgv || config.workerArgv.length === 0) {
8485
- throw new Error("workerArgv must be non-empty");
8522
+ class IncrementalStream {
8523
+ encoder;
8524
+ target;
8525
+ closed = false;
8526
+ writeChain = Promise.resolve();
8527
+ constructor(target, schema2) {
8528
+ this.target = target;
8529
+ this.encoder = createIncrementalEncoder(schema2);
8530
+ this.enqueue(this.encoder.start());
8486
8531
  }
8487
- const stateDir = config.stateDir ?? defaultStateDir();
8488
- const idleTimeout = config.idleTimeout ?? 300;
8489
- const connectTimeoutMs = (config.connectTimeout ?? 30) * 1000;
8490
- const startupTimeoutMs = (config.workerStartupTimeout ?? 60) * 1000;
8491
- let lockPath;
8492
- let sockPath;
8493
- let metaPath;
8494
- let hashId;
8495
- if (config.socketPath !== undefined) {
8496
- const { resolve } = await import("node:path");
8497
- sockPath = resolve(config.socketPath);
8498
- lockPath = `${sockPath}.lock`;
8499
- metaPath = null;
8500
- hashId = null;
8501
- } else {
8502
- hashId = await computeHash(config.workerArgv);
8503
- const paths = socketPaths(stateDir, hashId);
8504
- lockPath = paths.lockPath;
8505
- sockPath = paths.sockPath;
8506
- metaPath = paths.metaPath;
8532
+ async write(batch) {
8533
+ if (this.closed)
8534
+ throw new Error("Stream already closed");
8535
+ return this.enqueue(this.encoder.writeBatch(batch));
8507
8536
  }
8508
- const handle = await acquireLock(lockPath, connectTimeoutMs);
8509
- try {
8510
- if (await probeSocket(sockPath)) {
8511
- return sockPath;
8512
- }
8513
- try {
8514
- unlinkSync2(sockPath);
8515
- } catch {}
8516
- if (metaPath !== null) {
8517
- writeMeta(metaPath, config.workerArgv, process.cwd(), sockPath);
8518
- }
8519
- await spawnWorker(config.workerArgv, sockPath, idleTimeout, config.workerStderr ?? null, startupTimeoutMs);
8520
- return sockPath;
8521
- } finally {
8522
- handle.release();
8523
- if (hashId !== null) {
8524
- try {
8525
- await gcStateDir(stateDir, async (p) => {
8526
- const h = tryAcquireLock(p);
8527
- return h ? () => h.release() : null;
8528
- }, { limit: DEFAULT_GC_LIMIT, excludeHash: hashId });
8529
- } catch {}
8530
- }
8537
+ async close() {
8538
+ if (this.closed)
8539
+ return;
8540
+ this.closed = true;
8541
+ return this.enqueue(this.encoder.finish());
8542
+ }
8543
+ enqueue(bytes2) {
8544
+ const next = this.writeChain.then(() => {
8545
+ if (this.target.kind === "fd") {
8546
+ writeAll(this.target.fd, bytes2);
8547
+ return;
8548
+ }
8549
+ return socketWriteAll(this.target.socket, bytes2);
8550
+ });
8551
+ this.writeChain = next.catch(() => {
8552
+ return;
8553
+ });
8554
+ return next;
8531
8555
  }
8532
8556
  }
8533
- async function spawnWorker(workerArgv, sockPath, idleTimeout, workerStderr, startupTimeoutMs) {
8534
- const fullArgv = [...workerArgv, "--unix", sockPath, "--idle-timeout", String(idleTimeout)];
8535
- const [cmd, ...rest] = fullArgv;
8536
- const stderrTarget = workerStderr === null ? "ignore" : "pipe";
8537
- const proc = spawn(cmd, rest, {
8538
- stdio: ["ignore", "pipe", stderrTarget],
8539
- detached: false
8540
- });
8541
- if (workerStderr !== null && proc.stderr) {
8542
- const sink = createWriteStream(workerStderr, { flags: "a" });
8543
- proc.stderr.pipe(sink);
8557
+
8558
+ // src/server.ts
8559
+ var EMPTY_SCHEMA5 = schema([]);
8560
+ function randomStreamId() {
8561
+ const bytes2 = new Uint8Array(16);
8562
+ crypto.getRandomValues(bytes2);
8563
+ let out = "";
8564
+ for (let i = 0;i < bytes2.length; i++) {
8565
+ out += bytes2[i].toString(16).padStart(2, "0");
8544
8566
  }
8545
- const expectedPrefix = `UNIX:${sockPath}`;
8546
- const reader = lineReader(proc.stdout);
8547
- const deadline = Date.now() + startupTimeoutMs;
8548
- while (Date.now() < deadline) {
8549
- const remaining = deadline - Date.now();
8550
- const result = await Promise.race([
8551
- reader.next().then((r) => ({ kind: "line", value: r })),
8552
- onceExit(proc).then((rc) => ({ kind: "exit", rc })),
8553
- delay(remaining).then(() => ({ kind: "timeout" }))
8554
- ]);
8555
- if (result.kind === "exit") {
8556
- throw new Error(`worker exited before readiness (rc=${result.rc})`);
8567
+ return out;
8568
+ }
8569
+
8570
+ class VgiRpcServer {
8571
+ protocol;
8572
+ enableDescribe;
8573
+ serverId;
8574
+ _describePromise = null;
8575
+ protocolVersion;
8576
+ dispatchHook = null;
8577
+ externalConfig;
8578
+ onServeStart = null;
8579
+ serveStartFired = false;
8580
+ constructor(protocol, options) {
8581
+ this.protocol = protocol;
8582
+ this.enableDescribe = options?.enableDescribe ?? true;
8583
+ this.serverId = options?.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
8584
+ this.dispatchHook = options?.dispatchHook ?? null;
8585
+ this.externalConfig = options?.externalLocation;
8586
+ this.protocolVersion = options?.protocolVersion ?? "";
8587
+ this.onServeStart = options?.onServeStart ?? null;
8588
+ }
8589
+ async notifyTransport(kind) {
8590
+ if (this.serveStartFired)
8591
+ return;
8592
+ if (this.onServeStart) {
8593
+ await this.onServeStart(kind);
8557
8594
  }
8558
- if (result.kind === "timeout") {
8559
- proc.kill("SIGTERM");
8560
- throw new Error(`worker did not emit UNIX:<path> within ${startupTimeoutMs}ms`);
8595
+ this.serveStartFired = true;
8596
+ }
8597
+ async describeInfo() {
8598
+ if (!this._describePromise) {
8599
+ this._describePromise = buildDescribeBatch(this.protocol.name, this.protocol.getMethods(), this.serverId, this.protocol.protocolVersion || undefined).then(({ batch, metadata }) => ({
8600
+ batch,
8601
+ protocolHash: metadata.get("vgi_rpc.protocol_hash") ?? ""
8602
+ }));
8561
8603
  }
8562
- if (result.value.done) {
8563
- const rc = await onceExit(proc);
8564
- throw new Error(`worker exited before readiness (rc=${rc})`);
8604
+ return this._describePromise;
8605
+ }
8606
+ checkProtocolVersion(clientVersion) {
8607
+ const serverParts = this.protocol.protocolVersionParts;
8608
+ const serverVersion = this.protocol.protocolVersion;
8609
+ if (clientVersion === undefined) {
8610
+ throw new ProtocolVersionError(`VGI client/worker protocol_version mismatch.
8611
+ ` + ` Client: <not declared>
8612
+ ` + ` Server: ${serverVersion}
8613
+ ` + " Direction: the client did not send a vgi_rpc.protocol_version " + "metadata key. This is either a vgi-rpc framework bug or a " + "non-VGI client connecting to a VGI worker.");
8565
8614
  }
8566
- const line = result.value.value;
8567
- if (line.startsWith("UNIX:")) {
8568
- if (line !== expectedPrefix) {
8569
- proc.kill("SIGTERM");
8570
- throw new Error(`worker bound to unexpected path: ${JSON.stringify(line)} (expected ${JSON.stringify(expectedPrefix)})`);
8571
- }
8572
- reader.drainAndDiscard();
8615
+ let clientParts;
8616
+ try {
8617
+ clientParts = parseProtocolVersion(clientVersion);
8618
+ } catch {
8619
+ throw new ProtocolVersionError(`VGI client/worker protocol_version mismatch.
8620
+ ` + ` Client: ${clientVersion}
8621
+ ` + ` Server: ${serverVersion}
8622
+ ` + " Direction: client sent a malformed protocol_version. " + "Expected canonical semver MAJOR.MINOR.PATCH.");
8623
+ }
8624
+ if (clientParts[0] === serverParts[0] && clientParts[1] === serverParts[1]) {
8573
8625
  return;
8574
8626
  }
8575
- process.env.VGI_RPC_LAUNCHER_DEBUG && process.stderr.write(`launcher: skipping pre-bind stdout line: ${JSON.stringify(line)}
8576
- `);
8627
+ const clientOlder = clientParts[0] < serverParts[0] || clientParts[0] === serverParts[0] && clientParts[1] < serverParts[1];
8628
+ const direction = clientOlder ? `client is too old; upgrade the VGI extension/client to a version supporting protocol_version ${serverVersion}.` : `server is too old; upgrade the VGI worker to a version supporting protocol_version ${clientVersion}.`;
8629
+ throw new ProtocolVersionError(`VGI client/worker protocol_version mismatch.
8630
+ ` + ` Client: ${clientVersion}
8631
+ ` + ` Server: ${serverVersion}
8632
+ ` + ` Direction: ${direction}`);
8577
8633
  }
8578
- proc.kill("SIGTERM");
8579
- throw new Error(`worker did not emit UNIX:<path> within ${startupTimeoutMs}ms`);
8580
- }
8581
- function lineReader(stream) {
8582
- let buffer = "";
8583
- let ended = false;
8584
- const queued = [];
8585
- const waiters = [];
8586
- let discardMode = false;
8587
- const flushWaiter = () => {
8588
- if (waiters.length === 0)
8589
- return;
8590
- if (queued.length > 0) {
8591
- const w = waiters.shift();
8592
- w?.({ done: false, value: queued.shift() ?? "" });
8593
- } else if (ended) {
8594
- const w = waiters.shift();
8595
- w?.({ done: true, value: "" });
8596
- }
8597
- };
8598
- stream.setEncoding?.("utf8");
8599
- stream.on("data", (chunk) => {
8600
- if (discardMode)
8601
- return;
8602
- buffer += String(chunk);
8603
- for (;; ) {
8604
- const nl = buffer.indexOf(`
8634
+ async run() {
8635
+ const stdin = process.stdin;
8636
+ if (process.stdin.isTTY || process.stdout.isTTY) {
8637
+ process.stderr.write("WARNING: This process communicates via Arrow IPC on stdin/stdout " + `and is not intended to be run interactively.
8638
+ ` + "It should be launched as a subprocess by an RPC client " + `(e.g. vgi_rpc.connect()).
8605
8639
  `);
8606
- if (nl < 0)
8607
- break;
8608
- const line = buffer.slice(0, nl).replace(/\r$/, "");
8609
- buffer = buffer.slice(nl + 1);
8610
- queued.push(line);
8611
- }
8612
- flushWaiter();
8613
- });
8614
- stream.on("end", () => {
8615
- ended = true;
8616
- if (buffer.length > 0) {
8617
- queued.push(buffer.replace(/\r$/, ""));
8618
- buffer = "";
8619
8640
  }
8620
- flushWaiter();
8621
- });
8622
- stream.on("error", () => {
8623
- ended = true;
8624
- flushWaiter();
8625
- });
8626
- return {
8627
- next() {
8628
- return new Promise((resolve) => {
8629
- waiters.push(resolve);
8630
- flushWaiter();
8631
- });
8632
- },
8633
- drainAndDiscard() {
8634
- discardMode = true;
8635
- queued.length = 0;
8636
- stream.resume?.();
8641
+ const reader = await IpcStreamReader.create(stdin);
8642
+ const writer = new IpcStreamWriter;
8643
+ try {
8644
+ while (true) {
8645
+ await this.notifyTransport("pipe" /* PIPE */);
8646
+ await this.serveOne(reader, writer);
8647
+ }
8648
+ } catch (e) {
8649
+ if (e.message?.includes("closed") || e.message?.includes("Expected Schema Message") || e.message?.includes("null or length 0") || e.code === "EPIPE" || e.code === "ERR_STREAM_PREMATURE_CLOSE" || e.code === "ERR_STREAM_DESTROYED" || e instanceof Error && e.message.includes("EOF")) {
8650
+ return;
8651
+ }
8652
+ throw e;
8653
+ } finally {
8654
+ await reader.cancel();
8637
8655
  }
8638
- };
8639
- }
8640
- function onceExit(proc) {
8641
- return new Promise((resolve) => {
8642
- proc.once("exit", (code) => resolve(code));
8643
- });
8644
- }
8645
- function delay(ms) {
8646
- return new Promise((r) => setTimeout(r, Math.max(0, ms)));
8647
- }
8648
- // src/launcher/serve-unix.ts
8649
- import { existsSync as existsSync2, unlinkSync as unlinkSync3 } from "node:fs";
8650
- import { createServer } from "node:net";
8651
- import * as path2 from "node:path";
8652
-
8653
- // src/dispatch/stream.ts
8654
- var EMPTY_SCHEMA3 = schema([]);
8655
- async function dispatchStream(method, params, writer, reader, serverId, requestId, externalConfig, kind) {
8656
- const isProducer = !!method.producerFn;
8657
- let state;
8658
- try {
8659
- if (isProducer) {
8660
- state = await method.producerInit(params);
8661
- } else {
8662
- state = await method.exchangeInit(params);
8656
+ }
8657
+ async serveOne(reader, writer) {
8658
+ const stream = await reader.readStream();
8659
+ if (!stream) {
8660
+ throw new Error("EOF");
8663
8661
  }
8664
- } catch (error) {
8665
- const errSchema = method.headerSchema ?? EMPTY_SCHEMA3;
8666
- const errBatch = buildErrorBatch(errSchema, error, serverId, requestId);
8667
- await writer.writeStream(errSchema, [errBatch]);
8668
- const inputSchema2 = await reader.openNextStream();
8669
- if (inputSchema2) {
8670
- while (await reader.readNextBatch() !== null) {}
8662
+ const { schema: schema2, batches } = stream;
8663
+ if (batches.length === 0) {
8664
+ const err2 = new RpcError("ProtocolError", "Request stream contains no batches", "");
8665
+ const errBatch = buildErrorBatch(EMPTY_SCHEMA5, err2, this.serverId, null);
8666
+ await writer.writeStream(EMPTY_SCHEMA5, [errBatch]);
8667
+ return;
8671
8668
  }
8672
- return;
8673
- }
8674
- const outputSchema = state?.__outputSchema ?? method.outputSchema;
8675
- const effectiveProducer = state?.__isProducer ?? isProducer;
8676
- if (method.headerSchema && method.headerInit) {
8669
+ const batch = batches[0];
8670
+ let methodName;
8671
+ let params;
8672
+ let requestId;
8677
8673
  try {
8678
- const headerOut = new OutputCollector(method.headerSchema, true, serverId, requestId, undefined, undefined, kind);
8679
- const headerValues = method.headerInit(params, state, headerOut);
8680
- const headerBatch = buildResultBatch(method.headerSchema, headerValues, serverId, requestId);
8681
- const headerBatches = [...headerOut.batches.map((b) => b.batch), headerBatch];
8682
- await writer.writeStream(method.headerSchema, headerBatches);
8683
- } catch (error) {
8684
- const errBatch = buildErrorBatch(method.headerSchema, error, serverId, requestId);
8685
- await writer.writeStream(method.headerSchema, [errBatch]);
8686
- const inputSchema2 = await reader.openNextStream();
8687
- if (inputSchema2) {
8688
- while (await reader.readNextBatch() !== null) {}
8674
+ const parsed = parseRequest(schema2, batch);
8675
+ methodName = parsed.methodName;
8676
+ params = parsed.params;
8677
+ requestId = parsed.requestId;
8678
+ } catch (e) {
8679
+ const errBatch = buildErrorBatch(EMPTY_SCHEMA5, e, this.serverId, null);
8680
+ await writer.writeStream(EMPTY_SCHEMA5, [errBatch]);
8681
+ if (e instanceof VersionError || e instanceof RpcError) {
8682
+ return;
8689
8683
  }
8684
+ throw e;
8685
+ }
8686
+ if (methodName === DESCRIBE_METHOD_NAME && this.enableDescribe) {
8687
+ const { batch: batch2 } = await this.describeInfo();
8688
+ await writer.writeStream(batch2.schema, [batch2]);
8690
8689
  return;
8691
8690
  }
8692
- }
8693
- const inputSchema = await reader.openNextStream();
8694
- if (!inputSchema) {
8695
- const errBatch = buildErrorBatch(outputSchema, new Error("Expected input stream but got EOF"), serverId, requestId);
8696
- await writer.writeStream(outputSchema, [errBatch]);
8697
- return;
8698
- }
8699
- const stream = writer.openStream(outputSchema);
8700
- const expectedInputSchema = state?.__inputSchema ?? method.inputSchema;
8701
- try {
8702
- while (true) {
8703
- let inputBatch = await reader.readNextBatch();
8704
- if (!inputBatch)
8705
- break;
8706
- if (inputBatch.metadata?.get(CANCEL_KEY)) {
8707
- if (method.onCancel) {
8708
- try {
8709
- await method.onCancel(state);
8710
- } catch (err2) {
8711
- console.debug?.(`onCancel hook failed: ${err2 instanceof Error ? err2.message : err2}`);
8712
- }
8713
- }
8714
- break;
8715
- }
8716
- if (expectedInputSchema && !effectiveProducer && inputBatch.schema !== expectedInputSchema) {
8717
- try {
8718
- inputBatch = conformBatchToSchema(inputBatch, expectedInputSchema);
8719
- } catch (e) {
8720
- if (e instanceof TypeError)
8721
- throw e;
8722
- console.debug?.(`Schema conformance skipped: ${e instanceof Error ? e.message : e}`);
8723
- }
8691
+ const methods = this.protocol.getMethods();
8692
+ const method = methods.get(methodName);
8693
+ if (!method) {
8694
+ const available = [...methods.keys()].sort();
8695
+ const err2 = new MethodNotImplementedError(`Unknown method: '${methodName}'. Available methods: [${available.join(", ")}]`);
8696
+ const errBatch = buildErrorBatch(EMPTY_SCHEMA5, err2, this.serverId, requestId);
8697
+ await writer.writeStream(EMPTY_SCHEMA5, [errBatch]);
8698
+ return;
8699
+ }
8700
+ if (this.protocol.protocolVersionParts !== null) {
8701
+ try {
8702
+ const md = batch.metadata;
8703
+ this.checkProtocolVersion(md?.get(PROTOCOL_VERSION_KEY));
8704
+ } catch (exc) {
8705
+ const errSchema = method.type === "unary" /* UNARY */ ? method.resultSchema : EMPTY_SCHEMA5;
8706
+ const errBatch = buildErrorBatch(errSchema, exc, this.serverId, requestId);
8707
+ await writer.writeStream(errSchema, [errBatch]);
8708
+ return;
8724
8709
  }
8725
- const out = new OutputCollector(outputSchema, effectiveProducer, serverId, requestId, undefined, undefined, kind);
8726
- if (isProducer) {
8727
- await method.producerFn(state, out);
8710
+ }
8711
+ const methodType = method.type === "unary" /* UNARY */ ? "unary" : "stream";
8712
+ let requestData;
8713
+ try {
8714
+ requestData = serializeBatch(batch);
8715
+ } catch {}
8716
+ let streamId;
8717
+ if (methodType === "stream") {
8718
+ streamId = randomStreamId();
8719
+ }
8720
+ const { protocolHash } = await this.describeInfo();
8721
+ const info = {
8722
+ method: methodName,
8723
+ methodType,
8724
+ serverId: this.serverId,
8725
+ requestId,
8726
+ protocol: this.protocol.name,
8727
+ protocolHash,
8728
+ protocolVersion: this.protocolVersion,
8729
+ kind: "pipe" /* PIPE */,
8730
+ principal: "",
8731
+ authDomain: "",
8732
+ authenticated: false,
8733
+ remoteAddr: "",
8734
+ requestData,
8735
+ streamId
8736
+ };
8737
+ const stats = {
8738
+ inputBatches: 0,
8739
+ outputBatches: 0,
8740
+ inputRows: 0,
8741
+ outputRows: 0,
8742
+ inputBytes: 0,
8743
+ outputBytes: 0
8744
+ };
8745
+ const token = this.dispatchHook?.onDispatchStart(info);
8746
+ let dispatchError;
8747
+ applyDefaults(params, method.defaults);
8748
+ try {
8749
+ if (method.type === "unary" /* UNARY */) {
8750
+ await dispatchUnary(method, params, writer, this.serverId, requestId, this.externalConfig, "pipe" /* PIPE */);
8728
8751
  } else {
8729
- await method.exchangeFn(state, inputBatch, out);
8730
- }
8731
- for (const emitted of out.batches) {
8732
- let batch = emitted.batch;
8733
- if (externalConfig) {
8734
- batch = await maybeExternalizeBatch(batch, externalConfig);
8735
- }
8736
- if (emitted.metadata && emitted.metadata.size > 0) {
8737
- batch = withBatchMetadata(batch, emitted.metadata);
8738
- }
8739
- await stream.write(batch);
8740
- }
8741
- if (out.finished) {
8742
- break;
8752
+ await dispatchStream(method, params, writer, reader, this.serverId, requestId, this.externalConfig, "pipe" /* PIPE */);
8743
8753
  }
8754
+ } catch (e) {
8755
+ dispatchError = e instanceof Error ? e : new Error(String(e));
8756
+ throw e;
8757
+ } finally {
8758
+ this.dispatchHook?.onDispatchEnd(token, info, stats, dispatchError);
8744
8759
  }
8745
- } catch (error) {
8746
- await stream.write(buildErrorBatch(outputSchema, error, serverId, requestId));
8747
8760
  }
8748
- await stream.close();
8749
- try {
8750
- while (await reader.readNextBatch() !== null) {}
8751
- } catch {}
8752
8761
  }
8753
-
8754
- // src/dispatch/unary.ts
8755
- async function dispatchUnary(method, params, writer, serverId, requestId, externalConfig, kind) {
8756
- const schema2 = method.resultSchema;
8757
- const out = new OutputCollector(schema2, true, serverId, requestId, undefined, undefined, kind);
8758
- try {
8759
- const result = await method.handler(params, out);
8760
- let resultBatch = buildResultBatch(schema2, result, serverId, requestId);
8761
- if (externalConfig) {
8762
- resultBatch = await maybeExternalizeBatch(resultBatch, externalConfig);
8762
+ // src/launcher/hash.ts
8763
+ var HASH_LEN = 16;
8764
+ function canonicalJson(value) {
8765
+ if (value === null)
8766
+ return "null";
8767
+ if (typeof value === "boolean")
8768
+ return value ? "true" : "false";
8769
+ if (typeof value === "number") {
8770
+ return JSON.stringify(value);
8771
+ }
8772
+ if (typeof value === "string")
8773
+ return JSON.stringify(value);
8774
+ if (Array.isArray(value)) {
8775
+ return `[${value.map(canonicalJson).join(",")}]`;
8776
+ }
8777
+ if (typeof value === "object") {
8778
+ const keys = Object.keys(value).sort();
8779
+ const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`);
8780
+ return `{${parts.join(",")}}`;
8781
+ }
8782
+ throw new TypeError(`canonicalJson: unsupported type ${typeof value}`);
8783
+ }
8784
+ async function sha256Hex3(data) {
8785
+ const buf2 = new ArrayBuffer(data.byteLength);
8786
+ new Uint8Array(buf2).set(data);
8787
+ const digest = await crypto.subtle.digest("SHA-256", buf2);
8788
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
8789
+ }
8790
+ async function computeHash(workerArgv, cwd, env) {
8791
+ const cwdValue = cwd !== undefined ? cwd : process.cwd();
8792
+ const sourceEnv = env ?? process.env;
8793
+ const filteredEnv = {};
8794
+ for (const key of Object.keys(sourceEnv)) {
8795
+ if (key.startsWith("VGI_RPC_")) {
8796
+ const v = sourceEnv[key];
8797
+ if (v !== undefined)
8798
+ filteredEnv[key] = v;
8763
8799
  }
8764
- const batches = [...out.batches.map((b) => b.batch), resultBatch];
8765
- await writer.writeStream(schema2, batches);
8766
- } catch (error) {
8767
- const batch = buildErrorBatch(schema2, error, serverId, requestId);
8768
- await writer.writeStream(schema2, [batch]);
8769
8800
  }
8801
+ const canonical = {
8802
+ cmd: [...workerArgv],
8803
+ cwd: cwdValue,
8804
+ env: filteredEnv
8805
+ };
8806
+ const payload = new TextEncoder().encode(canonicalJson(canonical));
8807
+ const hex = await sha256Hex3(payload);
8808
+ return hex.slice(0, HASH_LEN);
8770
8809
  }
8810
+ // src/launcher/launch.ts
8811
+ import { spawn } from "node:child_process";
8812
+ import { createWriteStream, unlinkSync as unlinkSync2 } from "node:fs";
8771
8813
 
8772
- // src/wire/writer.ts
8773
- var STDOUT_FD = 1;
8774
- var _NODE_FS_MOD2 = "node:fs";
8775
- var _writeSync = null;
8776
- function _loadWriteSync2() {
8777
- if (_writeSync)
8778
- return _writeSync;
8779
- const req = import.meta.require ?? globalThis.require ?? null;
8780
- if (!req) {
8781
- throw new Error("IpcStreamWriter requires Bun or Node.js CJS for sync node:fs.writeSync. " + "Subprocess transport is not available in this runtime.");
8814
+ // src/launcher/lock.ts
8815
+ import { closeSync, constants as FS, openSync, readSync, statSync, writeSync } from "node:fs";
8816
+ var POLL_MS = 50;
8817
+ var VERIFY_RETRIES = 5;
8818
+ function pidAlive(pid) {
8819
+ if (!Number.isInteger(pid) || pid <= 0)
8820
+ return false;
8821
+ try {
8822
+ process.kill(pid, 0);
8823
+ return true;
8824
+ } catch (err2) {
8825
+ return err2?.code === "EPERM";
8782
8826
  }
8783
- const fs = req(_NODE_FS_MOD2);
8784
- _writeSync = fs.writeSync.bind(fs);
8785
- return _writeSync;
8786
8827
  }
8787
- function writeAll(fd, data) {
8788
- const writeSync2 = _loadWriteSync2();
8789
- let offset = 0;
8790
- while (offset < data.length) {
8828
+ function readPid(path) {
8829
+ try {
8830
+ const fd = openSync(path, FS.O_RDONLY);
8791
8831
  try {
8792
- const written = writeSync2(fd, data, offset, data.length - offset);
8793
- if (written <= 0)
8794
- throw new Error(`writeSync returned ${written}`);
8795
- offset += written;
8796
- } catch (e) {
8797
- if (e.code === "EAGAIN") {
8798
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1);
8799
- continue;
8800
- }
8801
- throw e;
8832
+ const buf2 = Buffer.alloc(64);
8833
+ const n = readSync(fd, buf2, 0, buf2.length, 0);
8834
+ const text = buf2.subarray(0, n).toString("utf8").trim();
8835
+ if (text === "")
8836
+ return 0;
8837
+ const parsed = Number(text);
8838
+ return Number.isInteger(parsed) ? parsed : 0;
8839
+ } finally {
8840
+ closeSync(fd);
8802
8841
  }
8842
+ } catch {
8843
+ return 0;
8803
8844
  }
8804
8845
  }
8805
- async function socketWriteAll(socket, data) {
8806
- if (socket.destroyed || socket.writableEnded) {
8807
- throw new Error("socketWriteAll: socket is already closed");
8846
+ function tryStampPid(path) {
8847
+ const fd = openSync(path, FS.O_RDWR | FS.O_CREAT, 384);
8848
+ try {
8849
+ const stamp = Buffer.from(String(process.pid), "utf8");
8850
+ const { ftruncateSync } = __require("node:fs");
8851
+ ftruncateSync(fd, 0);
8852
+ let written = 0;
8853
+ while (written < stamp.length) {
8854
+ const n = writeSync(fd, stamp, written, stamp.length - written, 0 + written);
8855
+ if (n <= 0)
8856
+ throw new Error(`writeSync returned ${n}`);
8857
+ written += n;
8858
+ }
8859
+ const st = statSync(path);
8860
+ if (st.size !== stamp.length)
8861
+ return false;
8862
+ return true;
8863
+ } finally {
8864
+ closeSync(fd);
8808
8865
  }
8809
- const ok = socket.write(data);
8810
- if (ok)
8811
- return;
8812
- await new Promise((resolve, reject) => {
8813
- const cleanup = () => {
8814
- socket.off("drain", onDrain);
8815
- socket.off("error", onError);
8816
- socket.off("close", onClose);
8817
- };
8818
- const onDrain = () => {
8819
- cleanup();
8820
- resolve();
8821
- };
8822
- const onError = (err2) => {
8823
- cleanup();
8824
- reject(err2);
8825
- };
8826
- const onClose = () => {
8827
- cleanup();
8828
- resolve();
8866
+ }
8867
+ function clearStamp(path) {
8868
+ try {
8869
+ const fd = openSync(path, FS.O_RDWR);
8870
+ try {
8871
+ const { ftruncateSync } = __require("node:fs");
8872
+ ftruncateSync(fd, 0);
8873
+ } finally {
8874
+ closeSync(fd);
8875
+ }
8876
+ } catch {}
8877
+ }
8878
+ function tryAcquireLock(lockPath) {
8879
+ for (let attempt = 0;attempt < VERIFY_RETRIES; attempt++) {
8880
+ const existingPid = readPid(lockPath);
8881
+ if (existingPid > 0 && pidAlive(existingPid)) {
8882
+ return null;
8883
+ }
8884
+ if (!tryStampPid(lockPath))
8885
+ continue;
8886
+ const verifyPid = readPid(lockPath);
8887
+ if (verifyPid !== process.pid) {
8888
+ continue;
8889
+ }
8890
+ let released = false;
8891
+ return {
8892
+ path: lockPath,
8893
+ release() {
8894
+ if (released)
8895
+ return;
8896
+ released = true;
8897
+ clearStamp(lockPath);
8898
+ }
8829
8899
  };
8830
- socket.once("drain", onDrain);
8831
- socket.once("error", onError);
8832
- socket.once("close", onClose);
8833
- });
8900
+ }
8901
+ return null;
8834
8902
  }
8835
-
8836
- class IpcStreamWriter {
8837
- target;
8838
- constructor(fdOrSocket = STDOUT_FD) {
8839
- if (typeof fdOrSocket === "number") {
8840
- this.target = { kind: "fd", fd: fdOrSocket };
8841
- } else {
8842
- this.target = { kind: "socket", socket: fdOrSocket };
8903
+ async function acquireLock(lockPath, timeoutMs) {
8904
+ const deadline = Date.now() + Math.max(0, timeoutMs);
8905
+ for (;; ) {
8906
+ const handle = tryAcquireLock(lockPath);
8907
+ if (handle)
8908
+ return handle;
8909
+ if (Date.now() >= deadline) {
8910
+ throw new Error(`failed to acquire ${lockPath} within ${timeoutMs}ms`);
8843
8911
  }
8912
+ await new Promise((r) => setTimeout(r, POLL_MS));
8844
8913
  }
8845
- async writeStream(schema2, batches) {
8846
- const bytes = serializeBatches(schema2, batches);
8847
- if (this.target.kind === "fd") {
8848
- writeAll(this.target.fd, bytes);
8914
+ }
8915
+
8916
+ // src/launcher/state.ts
8917
+ import { existsSync, mkdirSync, readFileSync, statSync as statSync2, unlinkSync, writeFileSync } from "node:fs";
8918
+ import { tmpdir } from "node:os";
8919
+ import * as path from "node:path";
8920
+ function socketPaths(stateDir, hashId) {
8921
+ return {
8922
+ lockPath: path.join(stateDir, `${hashId}.lock`),
8923
+ sockPath: path.join(stateDir, `${hashId}.sock`),
8924
+ metaPath: path.join(stateDir, `${hashId}.meta`)
8925
+ };
8926
+ }
8927
+ function defaultStateDir() {
8928
+ let base;
8929
+ if (process.platform === "win32") {
8930
+ base = path.join(tmpdir(), "vgi-rpc");
8931
+ } else {
8932
+ const xdg = process.env.XDG_RUNTIME_DIR;
8933
+ if (xdg) {
8934
+ base = path.join(xdg, "vgi-rpc");
8849
8935
  } else {
8850
- await socketWriteAll(this.target.socket, bytes);
8936
+ const uid = typeof process.geteuid === "function" ? process.geteuid() : 0;
8937
+ base = path.join(tmpdir(), `vgi-rpc-${uid}`);
8851
8938
  }
8852
8939
  }
8853
- openStream(schema2) {
8854
- return new IncrementalStream(this.target, schema2);
8940
+ mkdirSync(base, { recursive: true, mode: 448 });
8941
+ if (process.platform !== "win32" && typeof process.geteuid === "function") {
8942
+ try {
8943
+ const st = statSync2(base);
8944
+ if (st.uid !== process.geteuid()) {
8945
+ throw new Error(`state directory ${base} is not owned by current user`);
8946
+ }
8947
+ } catch (err2) {
8948
+ if (err2?.code === "ENOENT") {} else {
8949
+ throw err2;
8950
+ }
8951
+ }
8855
8952
  }
8953
+ return base;
8856
8954
  }
8857
-
8858
- class IncrementalStream {
8859
- encoder;
8860
- target;
8861
- closed = false;
8862
- writeChain = Promise.resolve();
8863
- constructor(target, schema2) {
8864
- this.target = target;
8865
- this.encoder = createIncrementalEncoder(schema2);
8866
- this.enqueue(this.encoder.start());
8867
- }
8868
- async write(batch) {
8869
- if (this.closed)
8870
- throw new Error("Stream already closed");
8871
- return this.enqueue(this.encoder.writeBatch(batch));
8872
- }
8873
- async close() {
8874
- if (this.closed)
8875
- return;
8876
- this.closed = true;
8877
- return this.enqueue(this.encoder.finish());
8878
- }
8879
- enqueue(bytes) {
8880
- const next = this.writeChain.then(() => {
8881
- if (this.target.kind === "fd") {
8882
- writeAll(this.target.fd, bytes);
8883
- return;
8884
- }
8885
- return socketWriteAll(this.target.socket, bytes);
8955
+ function writeMeta(metaPath, workerArgv, cwd, sockPath) {
8956
+ const payload = {
8957
+ cmd: [...workerArgv],
8958
+ cwd,
8959
+ socket: sockPath,
8960
+ started_at: Date.now() / 1000,
8961
+ launcher_pid: process.pid
8962
+ };
8963
+ try {
8964
+ writeFileSync(metaPath, JSON.stringify(payload, null, 2), { encoding: "utf8", mode: 384 });
8965
+ } catch {}
8966
+ }
8967
+ async function probeSocket(sockPath, timeoutMs = 2000) {
8968
+ if (!existsSync(sockPath))
8969
+ return false;
8970
+ const net = await import("node:net");
8971
+ return new Promise((resolve) => {
8972
+ const sock = net.createConnection({ path: sockPath });
8973
+ const timer = setTimeout(() => {
8974
+ sock.destroy();
8975
+ resolve(false);
8976
+ }, timeoutMs);
8977
+ sock.once("connect", () => {
8978
+ clearTimeout(timer);
8979
+ sock.end();
8980
+ resolve(true);
8886
8981
  });
8887
- this.writeChain = next.catch(() => {
8888
- return;
8982
+ sock.once("error", () => {
8983
+ clearTimeout(timer);
8984
+ resolve(false);
8889
8985
  });
8890
- return next;
8891
- }
8986
+ });
8892
8987
  }
8893
-
8894
- // src/launcher/serve-unix.ts
8895
- var EMPTY_SCHEMA4 = schema([]);
8896
- async function serveUnix(protocol, options) {
8897
- const sockPath = path2.resolve(options.unixPath);
8898
- const idleTimeoutS = options.idleTimeout ?? 300;
8899
- const startupGraceS = options.startupGraceSeconds ?? 5;
8900
- const protocolVersion = options.protocolVersion ?? "";
8901
- const serverId = options.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
8902
- const enableDescribe = options.enableDescribe ?? true;
8903
- const dispatchHook = options.dispatchHook ?? null;
8904
- const externalConfig = options.externalLocation;
8905
- const onServeStart = options.onServeStart ?? null;
8906
- const backlog = options.backlog ?? 16;
8907
- const announcementSink = options.announcementSink ?? process.stdout;
8908
- if (existsSync2(sockPath)) {
8909
- try {
8910
- unlinkSync3(sockPath);
8911
- } catch {}
8988
+ function tryReadMeta(metaPath) {
8989
+ try {
8990
+ const raw = readFileSync(metaPath, "utf8");
8991
+ const meta = JSON.parse(raw);
8992
+ return {
8993
+ cmd: Array.isArray(meta.cmd) ? meta.cmd.map(String) : [],
8994
+ cwd: typeof meta.cwd === "string" ? meta.cwd : "",
8995
+ startedAt: typeof meta.started_at === "number" ? meta.started_at : null
8996
+ };
8997
+ } catch {
8998
+ return { cmd: [], cwd: "", startedAt: null };
8912
8999
  }
8913
- let describePromise = null;
8914
- function describeInfo() {
8915
- if (!describePromise) {
8916
- describePromise = buildDescribeBatch(protocol.name, protocol.getMethods(), serverId).then(({ batch, metadata }) => ({
8917
- batch,
8918
- protocolHash: metadata.get("vgi_rpc.protocol_hash") ?? ""
8919
- }));
8920
- }
8921
- return describePromise;
9000
+ }
9001
+ async function statusRows(stateDir) {
9002
+ const { readdirSync } = await import("node:fs");
9003
+ const rows = [];
9004
+ let entries;
9005
+ try {
9006
+ entries = readdirSync(stateDir);
9007
+ } catch {
9008
+ return rows;
8922
9009
  }
8923
- let serveStartFired = false;
8924
- async function notifyTransport() {
8925
- if (serveStartFired)
8926
- return;
8927
- if (onServeStart) {
8928
- await onServeStart("unix" /* UNIX */);
8929
- }
8930
- serveStartFired = true;
9010
+ for (const name of entries.sort()) {
9011
+ if (!name.endsWith(".lock"))
9012
+ continue;
9013
+ const hashId = name.slice(0, -5);
9014
+ const { sockPath, metaPath } = socketPaths(stateDir, hashId);
9015
+ const meta = tryReadMeta(metaPath);
9016
+ rows.push({
9017
+ hashId,
9018
+ cmd: meta.cmd,
9019
+ cwd: meta.cwd,
9020
+ socket: sockPath,
9021
+ startedAt: meta.startedAt,
9022
+ alive: await probeSocket(sockPath)
9023
+ });
8931
9024
  }
8932
- const server = createServer({ allowHalfOpen: false });
8933
- let activeConnections = 0;
8934
- let idleTimer = null;
8935
- let resolveDone = () => {};
8936
- let rejectDone = () => {};
8937
- const done = new Promise((resolve2, reject) => {
8938
- resolveDone = resolve2;
8939
- rejectDone = reject;
8940
- });
8941
- let stopped = false;
8942
- function armIdleTimer() {
8943
- if (idleTimeoutS <= 0)
8944
- return;
8945
- if (idleTimer)
8946
- clearTimeout(idleTimer);
8947
- idleTimer = setTimeout(() => {
8948
- if (activeConnections === 0 && !stopped) {
8949
- shutdown();
8950
- }
8951
- }, idleTimeoutS * 1000);
9025
+ return rows;
9026
+ }
9027
+ async function gcStateDir(stateDir, tryAcquire, options) {
9028
+ const { readdirSync } = await import("node:fs");
9029
+ const cleaned = [];
9030
+ const skipped = [];
9031
+ const limit = options?.limit ?? null;
9032
+ const excludeHash = options?.excludeHash ?? null;
9033
+ let entries;
9034
+ try {
9035
+ entries = readdirSync(stateDir);
9036
+ } catch {
9037
+ return { cleaned, skippedInUse: skipped };
8952
9038
  }
8953
- function disarmIdleTimer() {
8954
- if (idleTimer) {
8955
- clearTimeout(idleTimer);
8956
- idleTimer = null;
9039
+ let seen = 0;
9040
+ for (const name of entries.sort()) {
9041
+ if (!name.endsWith(".lock"))
9042
+ continue;
9043
+ if (limit !== null && seen >= limit)
9044
+ break;
9045
+ seen += 1;
9046
+ const hashId = name.slice(0, -5);
9047
+ if (excludeHash !== null && hashId === excludeHash)
9048
+ continue;
9049
+ const { lockPath, sockPath, metaPath } = socketPaths(stateDir, hashId);
9050
+ const release = await tryAcquire(lockPath);
9051
+ if (release === null) {
9052
+ skipped.push(hashId);
9053
+ continue;
8957
9054
  }
8958
- }
8959
- async function shutdown() {
8960
- if (stopped)
8961
- return;
8962
- stopped = true;
8963
- disarmIdleTimer();
8964
- await new Promise((resolve2) => {
8965
- server.close(() => resolve2());
8966
- });
8967
9055
  try {
8968
- unlinkSync3(sockPath);
8969
- } catch {}
8970
- resolveDone();
8971
- }
8972
- server.on("connection", (socket) => {
8973
- activeConnections += 1;
8974
- disarmIdleTimer();
8975
- handleConnection(socket).catch((err2) => {
8976
- process.stderr.write(`vgi-rpc/unix: connection failed: ${err2?.message ?? err2}
8977
- `);
8978
- }).finally(() => {
8979
- activeConnections -= 1;
8980
- socket.destroy();
8981
- if (activeConnections === 0 && !stopped) {
8982
- armIdleTimer();
9056
+ if (await probeSocket(sockPath)) {
9057
+ continue;
8983
9058
  }
8984
- });
8985
- });
8986
- server.on("error", (err2) => {
8987
- if (stopped)
8988
- return;
8989
- rejectDone(err2);
8990
- });
8991
- async function handleConnection(socket) {
8992
- const reader = await IpcStreamReader.create(socket);
8993
- const writer = new IpcStreamWriter(socket);
8994
- try {
8995
- await notifyTransport();
8996
- while (true) {
9059
+ for (const p of [sockPath, metaPath, lockPath]) {
8997
9060
  try {
8998
- await serveOnce(reader, writer);
8999
- } catch (e) {
9000
- const err2 = e;
9001
- if (err2?.message?.includes("closed") || err2?.message?.includes("Expected Schema Message") || err2?.message?.includes("null or length 0") || err2?.message?.includes("EOF") || err2?.code === "EPIPE" || err2?.code === "ERR_STREAM_PREMATURE_CLOSE" || err2?.code === "ERR_STREAM_DESTROYED") {
9002
- return;
9003
- }
9004
- throw e;
9005
- }
9061
+ unlinkSync(p);
9062
+ } catch {}
9006
9063
  }
9064
+ cleaned.push(hashId);
9007
9065
  } finally {
9008
- try {
9009
- await reader.cancel();
9010
- } catch {}
9066
+ release();
9011
9067
  }
9012
9068
  }
9013
- async function serveOnce(reader, writer) {
9014
- const stream = await reader.readStream();
9015
- if (!stream) {
9016
- throw new Error("EOF");
9017
- }
9018
- const { schema: schema2, batches } = stream;
9019
- if (batches.length === 0) {
9020
- const err2 = new RpcError("ProtocolError", "Request stream contains no batches", "");
9021
- const errBatch = buildErrorBatch(EMPTY_SCHEMA4, err2, serverId, null);
9022
- await writer.writeStream(EMPTY_SCHEMA4, [errBatch]);
9023
- return;
9024
- }
9025
- const batch = batches[0];
9026
- let methodName;
9027
- let params;
9028
- let requestId;
9029
- try {
9030
- const parsed = parseRequest(schema2, batch);
9031
- methodName = parsed.methodName;
9032
- params = parsed.params;
9033
- requestId = parsed.requestId;
9034
- } catch (e) {
9035
- const errBatch = buildErrorBatch(EMPTY_SCHEMA4, e, serverId, null);
9036
- await writer.writeStream(EMPTY_SCHEMA4, [errBatch]);
9037
- if (e instanceof VersionError || e instanceof RpcError)
9038
- return;
9039
- throw e;
9040
- }
9041
- if (methodName === DESCRIBE_METHOD_NAME && enableDescribe) {
9042
- const { batch: descBatch } = await describeInfo();
9043
- await writer.writeStream(descBatch.schema, [descBatch]);
9044
- return;
9045
- }
9046
- const methods = protocol.getMethods();
9047
- const method = methods.get(methodName);
9048
- if (!method) {
9049
- const available = [...methods.keys()].sort();
9050
- const err2 = new Error(`Unknown method: '${methodName}'. Available methods: [${available.join(", ")}]`);
9051
- const errBatch = buildErrorBatch(EMPTY_SCHEMA4, err2, serverId, requestId);
9052
- await writer.writeStream(EMPTY_SCHEMA4, [errBatch]);
9053
- return;
9069
+ return { cleaned, skippedInUse: skipped };
9070
+ }
9071
+
9072
+ // src/launcher/launch.ts
9073
+ var DEFAULT_GC_LIMIT = 16;
9074
+ async function launch(config) {
9075
+ if (!config.workerArgv || config.workerArgv.length === 0) {
9076
+ throw new Error("workerArgv must be non-empty");
9077
+ }
9078
+ const stateDir = config.stateDir ?? defaultStateDir();
9079
+ const idleTimeout = config.idleTimeout ?? 300;
9080
+ const connectTimeoutMs = (config.connectTimeout ?? 30) * 1000;
9081
+ const startupTimeoutMs = (config.workerStartupTimeout ?? 60) * 1000;
9082
+ let lockPath;
9083
+ let sockPath;
9084
+ let metaPath;
9085
+ let hashId;
9086
+ if (config.socketPath !== undefined) {
9087
+ const { resolve } = await import("node:path");
9088
+ sockPath = resolve(config.socketPath);
9089
+ lockPath = `${sockPath}.lock`;
9090
+ metaPath = null;
9091
+ hashId = null;
9092
+ } else {
9093
+ hashId = await computeHash(config.workerArgv);
9094
+ const paths = socketPaths(stateDir, hashId);
9095
+ lockPath = paths.lockPath;
9096
+ sockPath = paths.sockPath;
9097
+ metaPath = paths.metaPath;
9098
+ }
9099
+ const handle = await acquireLock(lockPath, connectTimeoutMs);
9100
+ try {
9101
+ if (await probeSocket(sockPath)) {
9102
+ return sockPath;
9054
9103
  }
9055
- const methodType = method.type === "unary" /* UNARY */ ? "unary" : "stream";
9056
- let requestData;
9057
9104
  try {
9058
- requestData = serializeBatch(batch);
9105
+ unlinkSync2(sockPath);
9059
9106
  } catch {}
9060
- const { protocolHash } = await describeInfo();
9061
- const info = {
9062
- method: methodName,
9063
- methodType,
9064
- serverId,
9065
- requestId,
9066
- protocol: protocol.name,
9067
- protocolHash,
9068
- protocolVersion,
9069
- kind: "unix" /* UNIX */,
9070
- principal: "",
9071
- authDomain: "",
9072
- authenticated: false,
9073
- remoteAddr: "",
9074
- requestData
9075
- };
9076
- const stats = {
9077
- inputBatches: 0,
9078
- outputBatches: 0,
9079
- inputRows: 0,
9080
- outputRows: 0,
9081
- inputBytes: 0,
9082
- outputBytes: 0
9083
- };
9084
- const token = dispatchHook?.onDispatchStart(info);
9085
- let dispatchError;
9086
- applyDefaults(params, method.defaults);
9087
- try {
9088
- if (method.type === "unary" /* UNARY */) {
9089
- await dispatchUnary(method, params, writer, serverId, requestId, externalConfig, "unix" /* UNIX */);
9090
- } else {
9091
- await dispatchStream(method, params, writer, reader, serverId, requestId, externalConfig, "unix" /* UNIX */);
9092
- }
9093
- } catch (e) {
9094
- dispatchError = e instanceof Error ? e : new Error(String(e));
9095
- throw e;
9096
- } finally {
9097
- dispatchHook?.onDispatchEnd(token, info, stats, dispatchError);
9107
+ if (metaPath !== null) {
9108
+ writeMeta(metaPath, config.workerArgv, process.cwd(), sockPath);
9109
+ }
9110
+ await spawnWorker(config.workerArgv, sockPath, idleTimeout, config.workerStderr ?? null, startupTimeoutMs);
9111
+ return sockPath;
9112
+ } finally {
9113
+ handle.release();
9114
+ if (hashId !== null) {
9115
+ try {
9116
+ await gcStateDir(stateDir, async (p) => {
9117
+ const h = tryAcquireLock(p);
9118
+ return h ? () => h.release() : null;
9119
+ }, { limit: DEFAULT_GC_LIMIT, excludeHash: hashId });
9120
+ } catch {}
9098
9121
  }
9099
9122
  }
9100
- await new Promise((resolve2, reject) => {
9101
- server.listen({ path: sockPath, backlog }, () => resolve2());
9102
- server.once("error", (err2) => reject(err2));
9123
+ }
9124
+ async function spawnWorker(workerArgv, sockPath, idleTimeout, workerStderr, startupTimeoutMs) {
9125
+ const fullArgv = [...workerArgv, "--unix", sockPath, "--idle-timeout", String(idleTimeout)];
9126
+ const [cmd, ...rest] = fullArgv;
9127
+ const stderrTarget = workerStderr === null ? "ignore" : "pipe";
9128
+ const proc = spawn(cmd, rest, {
9129
+ stdio: ["ignore", "pipe", stderrTarget],
9130
+ detached: false
9103
9131
  });
9104
- try {
9105
- const { chmodSync } = await import("node:fs");
9106
- chmodSync(sockPath, 384);
9107
- } catch {}
9108
- options.onBound?.(sockPath);
9109
- announcementSink.write(`UNIX:${sockPath}
9110
- `);
9111
- if (idleTimeoutS > 0) {
9112
- setTimeout(() => {
9113
- if (activeConnections === 0 && !stopped)
9114
- armIdleTimer();
9115
- }, startupGraceS * 1000).unref?.();
9132
+ if (workerStderr !== null && proc.stderr) {
9133
+ const sink = createWriteStream(workerStderr, { flags: "a" });
9134
+ proc.stderr.pipe(sink);
9116
9135
  }
9117
- return {
9118
- socketPath: sockPath,
9119
- stop: shutdown,
9120
- done
9121
- };
9122
- }
9123
- // src/schema.ts
9124
- var str = utf8();
9125
- var bytes = binary();
9126
- var int = int64();
9127
- var int322 = int32();
9128
- var int162 = int16();
9129
- var int82 = int8();
9130
- var uint82 = uint8();
9131
- var uint162 = uint16();
9132
- var uint322 = uint32();
9133
- var uint642 = uint64();
9134
- var float = float64();
9135
- var float322 = float32();
9136
- var bool2 = bool();
9137
- function isField(x) {
9138
- return x != null && typeof x.name === "string" && x.type != null && typeof x.nullable === "boolean";
9139
- }
9140
- function isDataType(x) {
9141
- return x != null && typeof x.typeId === "number";
9142
- }
9143
- function toSchema(spec) {
9144
- const maybeFields = spec.fields;
9145
- if (Array.isArray(maybeFields)) {
9146
- const out = [];
9147
- for (const f of maybeFields) {
9148
- if (isField(f)) {
9149
- out.push(f);
9150
- } else {
9151
- out.push(field(f.name, f.type, f.nullable ?? true, f.metadata));
9152
- }
9136
+ const expectedPrefix = `UNIX:${sockPath}`;
9137
+ const reader = lineReader(proc.stdout);
9138
+ const deadline = Date.now() + startupTimeoutMs;
9139
+ while (Date.now() < deadline) {
9140
+ const remaining = deadline - Date.now();
9141
+ const result = await Promise.race([
9142
+ reader.next().then((r) => ({ kind: "line", value: r })),
9143
+ onceExit(proc).then((rc) => ({ kind: "exit", rc })),
9144
+ delay(remaining).then(() => ({ kind: "timeout" }))
9145
+ ]);
9146
+ if (result.kind === "exit") {
9147
+ throw new Error(`worker exited before readiness (rc=${result.rc})`);
9153
9148
  }
9154
- return schema(out);
9155
- }
9156
- const fields = [];
9157
- for (const [name, value] of Object.entries(spec)) {
9158
- if (isField(value)) {
9159
- fields.push(value);
9160
- } else if (isDataType(value)) {
9161
- fields.push(field(name, value, false));
9162
- } else {
9163
- throw new TypeError(`Invalid schema value for "${name}": expected DataType or Field, got ${typeof value}`);
9149
+ if (result.kind === "timeout") {
9150
+ proc.kill("SIGTERM");
9151
+ throw new Error(`worker did not emit UNIX:<path> within ${startupTimeoutMs}ms`);
9164
9152
  }
9165
- }
9166
- return schema(fields);
9167
- }
9168
- function inferParamTypes(spec) {
9169
- const sch = toSchema(spec);
9170
- if (sch.fields.length === 0)
9171
- return;
9172
- const result = {};
9173
- for (const f of sch.fields) {
9174
- let mapped;
9175
- if (isUtf8(f.type))
9176
- mapped = "str";
9177
- else if (isBinary(f.type))
9178
- mapped = "bytes";
9179
- else if (isBool(f.type))
9180
- mapped = "bool";
9181
- else if (isFloat(f.type))
9182
- mapped = "float";
9183
- else if (isInt(f.type))
9184
- mapped = "int";
9185
- if (!mapped)
9153
+ if (result.value.done) {
9154
+ const rc = await onceExit(proc);
9155
+ throw new Error(`worker exited before readiness (rc=${rc})`);
9156
+ }
9157
+ const line = result.value.value;
9158
+ if (line.startsWith("UNIX:")) {
9159
+ if (line !== expectedPrefix) {
9160
+ proc.kill("SIGTERM");
9161
+ throw new Error(`worker bound to unexpected path: ${JSON.stringify(line)} (expected ${JSON.stringify(expectedPrefix)})`);
9162
+ }
9163
+ reader.drainAndDiscard();
9186
9164
  return;
9187
- result[f.name] = mapped;
9188
- }
9189
- return result;
9190
- }
9191
-
9192
- // src/protocol.ts
9193
- var EMPTY_SCHEMA5 = schema([]);
9194
-
9195
- class Protocol {
9196
- name;
9197
- protocolVersion;
9198
- protocolVersionParts;
9199
- _methods = new Map;
9200
- constructor(name, options) {
9201
- this.name = name;
9202
- const raw = options?.protocolVersion;
9203
- if (raw === undefined || raw === "") {
9204
- this.protocolVersion = "";
9205
- this.protocolVersionParts = null;
9206
- } else {
9207
- this.protocolVersion = raw;
9208
- this.protocolVersionParts = parseProtocolVersion(raw);
9209
9165
  }
9166
+ process.env.VGI_RPC_LAUNCHER_DEBUG && process.stderr.write(`launcher: skipping pre-bind stdout line: ${JSON.stringify(line)}
9167
+ `);
9210
9168
  }
9211
- unary(name, config) {
9212
- const params = toSchema(config.params);
9213
- this._methods.set(name, {
9214
- name,
9215
- type: "unary" /* UNARY */,
9216
- paramsSchema: params,
9217
- resultSchema: toSchema(config.result),
9218
- handler: config.handler,
9219
- doc: config.doc,
9220
- defaults: config.defaults,
9221
- paramTypes: config.paramTypes ?? inferParamTypes(params)
9222
- });
9223
- return this;
9224
- }
9225
- producer(name, config) {
9226
- const params = toSchema(config.params);
9227
- this._methods.set(name, {
9228
- name,
9229
- type: "stream" /* STREAM */,
9230
- paramsSchema: params,
9231
- resultSchema: EMPTY_SCHEMA5,
9232
- outputSchema: toSchema(config.outputSchema),
9233
- inputSchema: EMPTY_SCHEMA5,
9234
- producerInit: config.init,
9235
- producerFn: config.produce,
9236
- onCancel: config.onCancel,
9237
- headerSchema: config.headerSchema ? toSchema(config.headerSchema) : undefined,
9238
- headerInit: config.headerInit,
9239
- doc: config.doc,
9240
- defaults: config.defaults,
9241
- paramTypes: config.paramTypes ?? inferParamTypes(params)
9242
- });
9243
- return this;
9244
- }
9245
- exchange(name, config) {
9246
- const params = toSchema(config.params);
9247
- this._methods.set(name, {
9248
- name,
9249
- type: "stream" /* STREAM */,
9250
- paramsSchema: params,
9251
- resultSchema: EMPTY_SCHEMA5,
9252
- inputSchema: toSchema(config.inputSchema),
9253
- outputSchema: toSchema(config.outputSchema),
9254
- exchangeInit: config.init,
9255
- exchangeFn: config.exchange,
9256
- onCancel: config.onCancel,
9257
- headerSchema: config.headerSchema ? toSchema(config.headerSchema) : undefined,
9258
- headerInit: config.headerInit,
9259
- doc: config.doc,
9260
- defaults: config.defaults,
9261
- paramTypes: config.paramTypes ?? inferParamTypes(params)
9262
- });
9263
- return this;
9264
- }
9265
- getMethods() {
9266
- return new Map(this._methods);
9267
- }
9268
- }
9269
- // src/server.ts
9270
- var EMPTY_SCHEMA6 = schema([]);
9271
- function randomStreamId() {
9272
- const bytes2 = new Uint8Array(16);
9273
- crypto.getRandomValues(bytes2);
9274
- let out = "";
9275
- for (let i = 0;i < bytes2.length; i++) {
9276
- out += bytes2[i].toString(16).padStart(2, "0");
9277
- }
9278
- return out;
9169
+ proc.kill("SIGTERM");
9170
+ throw new Error(`worker did not emit UNIX:<path> within ${startupTimeoutMs}ms`);
9279
9171
  }
9280
-
9281
- class VgiRpcServer {
9282
- protocol;
9283
- enableDescribe;
9284
- serverId;
9285
- _describePromise = null;
9286
- protocolVersion;
9287
- dispatchHook = null;
9288
- externalConfig;
9289
- onServeStart = null;
9290
- serveStartFired = false;
9291
- constructor(protocol, options) {
9292
- this.protocol = protocol;
9293
- this.enableDescribe = options?.enableDescribe ?? true;
9294
- this.serverId = options?.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
9295
- this.dispatchHook = options?.dispatchHook ?? null;
9296
- this.externalConfig = options?.externalLocation;
9297
- this.protocolVersion = options?.protocolVersion ?? "";
9298
- this.onServeStart = options?.onServeStart ?? null;
9299
- }
9300
- async notifyTransport(kind) {
9301
- if (this.serveStartFired)
9172
+ function lineReader(stream) {
9173
+ let buffer = "";
9174
+ let ended = false;
9175
+ const queued = [];
9176
+ const waiters = [];
9177
+ let discardMode = false;
9178
+ const flushWaiter = () => {
9179
+ if (waiters.length === 0)
9302
9180
  return;
9303
- if (this.onServeStart) {
9304
- await this.onServeStart(kind);
9181
+ if (queued.length > 0) {
9182
+ const w = waiters.shift();
9183
+ w?.({ done: false, value: queued.shift() ?? "" });
9184
+ } else if (ended) {
9185
+ const w = waiters.shift();
9186
+ w?.({ done: true, value: "" });
9187
+ }
9188
+ };
9189
+ stream.setEncoding?.("utf8");
9190
+ stream.on("data", (chunk) => {
9191
+ if (discardMode)
9192
+ return;
9193
+ buffer += String(chunk);
9194
+ for (;; ) {
9195
+ const nl = buffer.indexOf(`
9196
+ `);
9197
+ if (nl < 0)
9198
+ break;
9199
+ const line = buffer.slice(0, nl).replace(/\r$/, "");
9200
+ buffer = buffer.slice(nl + 1);
9201
+ queued.push(line);
9202
+ }
9203
+ flushWaiter();
9204
+ });
9205
+ stream.on("end", () => {
9206
+ ended = true;
9207
+ if (buffer.length > 0) {
9208
+ queued.push(buffer.replace(/\r$/, ""));
9209
+ buffer = "";
9305
9210
  }
9306
- this.serveStartFired = true;
9211
+ flushWaiter();
9212
+ });
9213
+ stream.on("error", () => {
9214
+ ended = true;
9215
+ flushWaiter();
9216
+ });
9217
+ return {
9218
+ next() {
9219
+ return new Promise((resolve) => {
9220
+ waiters.push(resolve);
9221
+ flushWaiter();
9222
+ });
9223
+ },
9224
+ drainAndDiscard() {
9225
+ discardMode = true;
9226
+ queued.length = 0;
9227
+ stream.resume?.();
9228
+ }
9229
+ };
9230
+ }
9231
+ function onceExit(proc) {
9232
+ return new Promise((resolve) => {
9233
+ proc.once("exit", (code) => resolve(code));
9234
+ });
9235
+ }
9236
+ function delay(ms) {
9237
+ return new Promise((r) => setTimeout(r, Math.max(0, ms)));
9238
+ }
9239
+ // src/launcher/serve-unix.ts
9240
+ import { existsSync as existsSync2, unlinkSync as unlinkSync3 } from "node:fs";
9241
+ import { createServer } from "node:net";
9242
+ import * as path2 from "node:path";
9243
+ var EMPTY_SCHEMA6 = schema([]);
9244
+ async function serveUnix(protocol, options) {
9245
+ const sockPath = path2.resolve(options.unixPath);
9246
+ const idleTimeoutS = options.idleTimeout ?? 300;
9247
+ const startupGraceS = options.startupGraceSeconds ?? 5;
9248
+ const protocolVersion = options.protocolVersion ?? "";
9249
+ const serverId = options.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
9250
+ const enableDescribe = options.enableDescribe ?? true;
9251
+ const dispatchHook = options.dispatchHook ?? null;
9252
+ const externalConfig = options.externalLocation;
9253
+ const onServeStart = options.onServeStart ?? null;
9254
+ const backlog = options.backlog ?? 16;
9255
+ const announcementSink = options.announcementSink ?? process.stdout;
9256
+ if (existsSync2(sockPath)) {
9257
+ try {
9258
+ unlinkSync3(sockPath);
9259
+ } catch {}
9307
9260
  }
9308
- async describeInfo() {
9309
- if (!this._describePromise) {
9310
- this._describePromise = buildDescribeBatch(this.protocol.name, this.protocol.getMethods(), this.serverId, this.protocol.protocolVersion || undefined).then(({ batch, metadata }) => ({
9261
+ let describePromise = null;
9262
+ function describeInfo() {
9263
+ if (!describePromise) {
9264
+ describePromise = buildDescribeBatch(protocol.name, protocol.getMethods(), serverId).then(({ batch, metadata }) => ({
9311
9265
  batch,
9312
9266
  protocolHash: metadata.get("vgi_rpc.protocol_hash") ?? ""
9313
9267
  }));
9314
9268
  }
9315
- return this._describePromise;
9269
+ return describePromise;
9316
9270
  }
9317
- checkProtocolVersion(clientVersion) {
9318
- const serverParts = this.protocol.protocolVersionParts;
9319
- const serverVersion = this.protocol.protocolVersion;
9320
- if (clientVersion === undefined) {
9321
- throw new ProtocolVersionError(`VGI client/worker protocol_version mismatch.
9322
- ` + ` Client: <not declared>
9323
- ` + ` Server: ${serverVersion}
9324
- ` + " Direction: the client did not send a vgi_rpc.protocol_version " + "metadata key. This is either a vgi-rpc framework bug or a " + "non-VGI client connecting to a VGI worker.");
9325
- }
9326
- let clientParts;
9327
- try {
9328
- clientParts = parseProtocolVersion(clientVersion);
9329
- } catch {
9330
- throw new ProtocolVersionError(`VGI client/worker protocol_version mismatch.
9331
- ` + ` Client: ${clientVersion}
9332
- ` + ` Server: ${serverVersion}
9333
- ` + " Direction: client sent a malformed protocol_version. " + "Expected canonical semver MAJOR.MINOR.PATCH.");
9271
+ let serveStartFired = false;
9272
+ async function notifyTransport() {
9273
+ if (serveStartFired)
9274
+ return;
9275
+ if (onServeStart) {
9276
+ await onServeStart("unix" /* UNIX */);
9334
9277
  }
9335
- if (clientParts[0] === serverParts[0] && clientParts[1] === serverParts[1]) {
9278
+ serveStartFired = true;
9279
+ }
9280
+ const server = createServer({ allowHalfOpen: false });
9281
+ let activeConnections = 0;
9282
+ let idleTimer = null;
9283
+ let resolveDone = () => {};
9284
+ let rejectDone = () => {};
9285
+ const done = new Promise((resolve2, reject) => {
9286
+ resolveDone = resolve2;
9287
+ rejectDone = reject;
9288
+ });
9289
+ let stopped = false;
9290
+ function armIdleTimer() {
9291
+ if (idleTimeoutS <= 0)
9336
9292
  return;
9293
+ if (idleTimer)
9294
+ clearTimeout(idleTimer);
9295
+ idleTimer = setTimeout(() => {
9296
+ if (activeConnections === 0 && !stopped) {
9297
+ shutdown();
9298
+ }
9299
+ }, idleTimeoutS * 1000);
9300
+ }
9301
+ function disarmIdleTimer() {
9302
+ if (idleTimer) {
9303
+ clearTimeout(idleTimer);
9304
+ idleTimer = null;
9337
9305
  }
9338
- const clientOlder = clientParts[0] < serverParts[0] || clientParts[0] === serverParts[0] && clientParts[1] < serverParts[1];
9339
- const direction = clientOlder ? `client is too old; upgrade the VGI extension/client to a version supporting protocol_version ${serverVersion}.` : `server is too old; upgrade the VGI worker to a version supporting protocol_version ${clientVersion}.`;
9340
- throw new ProtocolVersionError(`VGI client/worker protocol_version mismatch.
9341
- ` + ` Client: ${clientVersion}
9342
- ` + ` Server: ${serverVersion}
9343
- ` + ` Direction: ${direction}`);
9344
9306
  }
9345
- async run() {
9346
- const stdin = process.stdin;
9347
- if (process.stdin.isTTY || process.stdout.isTTY) {
9348
- process.stderr.write("WARNING: This process communicates via Arrow IPC on stdin/stdout " + `and is not intended to be run interactively.
9349
- ` + "It should be launched as a subprocess by an RPC client " + `(e.g. vgi_rpc.connect()).
9307
+ async function shutdown() {
9308
+ if (stopped)
9309
+ return;
9310
+ stopped = true;
9311
+ disarmIdleTimer();
9312
+ await new Promise((resolve2) => {
9313
+ server.close(() => resolve2());
9314
+ });
9315
+ try {
9316
+ unlinkSync3(sockPath);
9317
+ } catch {}
9318
+ resolveDone();
9319
+ }
9320
+ server.on("connection", (socket) => {
9321
+ activeConnections += 1;
9322
+ disarmIdleTimer();
9323
+ handleConnection(socket).catch((err2) => {
9324
+ process.stderr.write(`vgi-rpc/unix: connection failed: ${err2?.message ?? err2}
9350
9325
  `);
9351
- }
9352
- const reader = await IpcStreamReader.create(stdin);
9353
- const writer = new IpcStreamWriter;
9326
+ }).finally(() => {
9327
+ activeConnections -= 1;
9328
+ socket.destroy();
9329
+ if (activeConnections === 0 && !stopped) {
9330
+ armIdleTimer();
9331
+ }
9332
+ });
9333
+ });
9334
+ server.on("error", (err2) => {
9335
+ if (stopped)
9336
+ return;
9337
+ rejectDone(err2);
9338
+ });
9339
+ async function handleConnection(socket) {
9340
+ const reader = await IpcStreamReader.create(socket);
9341
+ const writer = new IpcStreamWriter(socket);
9354
9342
  try {
9343
+ await notifyTransport();
9355
9344
  while (true) {
9356
- await this.notifyTransport("pipe" /* PIPE */);
9357
- await this.serveOne(reader, writer);
9358
- }
9359
- } catch (e) {
9360
- if (e.message?.includes("closed") || e.message?.includes("Expected Schema Message") || e.message?.includes("null or length 0") || e.code === "EPIPE" || e.code === "ERR_STREAM_PREMATURE_CLOSE" || e.code === "ERR_STREAM_DESTROYED" || e instanceof Error && e.message.includes("EOF")) {
9361
- return;
9345
+ try {
9346
+ await serveOnce(reader, writer);
9347
+ } catch (e) {
9348
+ const err2 = e;
9349
+ if (err2?.message?.includes("closed") || err2?.message?.includes("Expected Schema Message") || err2?.message?.includes("null or length 0") || err2?.message?.includes("EOF") || err2?.code === "EPIPE" || err2?.code === "ERR_STREAM_PREMATURE_CLOSE" || err2?.code === "ERR_STREAM_DESTROYED") {
9350
+ return;
9351
+ }
9352
+ throw e;
9353
+ }
9362
9354
  }
9363
- throw e;
9364
9355
  } finally {
9365
- await reader.cancel();
9356
+ try {
9357
+ await reader.cancel();
9358
+ } catch {}
9366
9359
  }
9367
9360
  }
9368
- async serveOne(reader, writer) {
9361
+ async function serveOnce(reader, writer) {
9369
9362
  const stream = await reader.readStream();
9370
9363
  if (!stream) {
9371
9364
  throw new Error("EOF");
@@ -9373,7 +9366,7 @@ class VgiRpcServer {
9373
9366
  const { schema: schema2, batches } = stream;
9374
9367
  if (batches.length === 0) {
9375
9368
  const err2 = new RpcError("ProtocolError", "Request stream contains no batches", "");
9376
- const errBatch = buildErrorBatch(EMPTY_SCHEMA6, err2, this.serverId, null);
9369
+ const errBatch = buildErrorBatch(EMPTY_SCHEMA6, err2, serverId, null);
9377
9370
  await writer.writeStream(EMPTY_SCHEMA6, [errBatch]);
9378
9371
  return;
9379
9372
  }
@@ -9387,63 +9380,46 @@ class VgiRpcServer {
9387
9380
  params = parsed.params;
9388
9381
  requestId = parsed.requestId;
9389
9382
  } catch (e) {
9390
- const errBatch = buildErrorBatch(EMPTY_SCHEMA6, e, this.serverId, null);
9383
+ const errBatch = buildErrorBatch(EMPTY_SCHEMA6, e, serverId, null);
9391
9384
  await writer.writeStream(EMPTY_SCHEMA6, [errBatch]);
9392
- if (e instanceof VersionError || e instanceof RpcError) {
9385
+ if (e instanceof VersionError || e instanceof RpcError)
9393
9386
  return;
9394
- }
9395
9387
  throw e;
9396
9388
  }
9397
- if (methodName === DESCRIBE_METHOD_NAME && this.enableDescribe) {
9398
- const { batch: batch2 } = await this.describeInfo();
9399
- await writer.writeStream(batch2.schema, [batch2]);
9389
+ if (methodName === DESCRIBE_METHOD_NAME && enableDescribe) {
9390
+ const { batch: descBatch } = await describeInfo();
9391
+ await writer.writeStream(descBatch.schema, [descBatch]);
9400
9392
  return;
9401
9393
  }
9402
- const methods = this.protocol.getMethods();
9394
+ const methods = protocol.getMethods();
9403
9395
  const method = methods.get(methodName);
9404
9396
  if (!method) {
9405
9397
  const available = [...methods.keys()].sort();
9406
- const err2 = new MethodNotImplementedError(`Unknown method: '${methodName}'. Available methods: [${available.join(", ")}]`);
9407
- const errBatch = buildErrorBatch(EMPTY_SCHEMA6, err2, this.serverId, requestId);
9398
+ const err2 = new Error(`Unknown method: '${methodName}'. Available methods: [${available.join(", ")}]`);
9399
+ const errBatch = buildErrorBatch(EMPTY_SCHEMA6, err2, serverId, requestId);
9408
9400
  await writer.writeStream(EMPTY_SCHEMA6, [errBatch]);
9409
9401
  return;
9410
9402
  }
9411
- if (this.protocol.protocolVersionParts !== null) {
9412
- try {
9413
- const md = batch.metadata;
9414
- this.checkProtocolVersion(md?.get(PROTOCOL_VERSION_KEY));
9415
- } catch (exc) {
9416
- const errSchema = method.type === "unary" /* UNARY */ ? method.resultSchema : EMPTY_SCHEMA6;
9417
- const errBatch = buildErrorBatch(errSchema, exc, this.serverId, requestId);
9418
- await writer.writeStream(errSchema, [errBatch]);
9419
- return;
9420
- }
9421
- }
9422
9403
  const methodType = method.type === "unary" /* UNARY */ ? "unary" : "stream";
9423
9404
  let requestData;
9424
9405
  try {
9425
9406
  requestData = serializeBatch(batch);
9426
9407
  } catch {}
9427
- let streamId;
9428
- if (methodType === "stream") {
9429
- streamId = randomStreamId();
9430
- }
9431
- const { protocolHash } = await this.describeInfo();
9408
+ const { protocolHash } = await describeInfo();
9432
9409
  const info = {
9433
9410
  method: methodName,
9434
9411
  methodType,
9435
- serverId: this.serverId,
9412
+ serverId,
9436
9413
  requestId,
9437
- protocol: this.protocol.name,
9414
+ protocol: protocol.name,
9438
9415
  protocolHash,
9439
- protocolVersion: this.protocolVersion,
9440
- kind: "pipe" /* PIPE */,
9416
+ protocolVersion,
9417
+ kind: "unix" /* UNIX */,
9441
9418
  principal: "",
9442
9419
  authDomain: "",
9443
9420
  authenticated: false,
9444
9421
  remoteAddr: "",
9445
- requestData,
9446
- streamId
9422
+ requestData
9447
9423
  };
9448
9424
  const stats = {
9449
9425
  inputBatches: 0,
@@ -9453,22 +9429,44 @@ class VgiRpcServer {
9453
9429
  inputBytes: 0,
9454
9430
  outputBytes: 0
9455
9431
  };
9456
- const token = this.dispatchHook?.onDispatchStart(info);
9432
+ const token = dispatchHook?.onDispatchStart(info);
9457
9433
  let dispatchError;
9458
9434
  applyDefaults(params, method.defaults);
9459
9435
  try {
9460
9436
  if (method.type === "unary" /* UNARY */) {
9461
- await dispatchUnary(method, params, writer, this.serverId, requestId, this.externalConfig, "pipe" /* PIPE */);
9437
+ await dispatchUnary(method, params, writer, serverId, requestId, externalConfig, "unix" /* UNIX */);
9462
9438
  } else {
9463
- await dispatchStream(method, params, writer, reader, this.serverId, requestId, this.externalConfig, "pipe" /* PIPE */);
9439
+ await dispatchStream(method, params, writer, reader, serverId, requestId, externalConfig, "unix" /* UNIX */);
9464
9440
  }
9465
9441
  } catch (e) {
9466
9442
  dispatchError = e instanceof Error ? e : new Error(String(e));
9467
9443
  throw e;
9468
9444
  } finally {
9469
- this.dispatchHook?.onDispatchEnd(token, info, stats, dispatchError);
9445
+ dispatchHook?.onDispatchEnd(token, info, stats, dispatchError);
9470
9446
  }
9471
9447
  }
9448
+ await new Promise((resolve2, reject) => {
9449
+ server.listen({ path: sockPath, backlog }, () => resolve2());
9450
+ server.once("error", (err2) => reject(err2));
9451
+ });
9452
+ try {
9453
+ const { chmodSync } = await import("node:fs");
9454
+ chmodSync(sockPath, 384);
9455
+ } catch {}
9456
+ options.onBound?.(sockPath);
9457
+ announcementSink.write(`UNIX:${sockPath}
9458
+ `);
9459
+ if (idleTimeoutS > 0) {
9460
+ setTimeout(() => {
9461
+ if (activeConnections === 0 && !stopped)
9462
+ armIdleTimer();
9463
+ }, startupGraceS * 1000).unref?.();
9464
+ }
9465
+ return {
9466
+ socketPath: sockPath,
9467
+ stop: shutdown,
9468
+ done
9469
+ };
9472
9470
  }
9473
9471
  export {
9474
9472
  unpackStateToken,
@@ -9563,4 +9561,4 @@ export {
9563
9561
  ARROW_CONTENT_TYPE
9564
9562
  };
9565
9563
 
9566
- //# debugId=03000096478B827564756E2164756E21
9564
+ //# debugId=4D9D32B49233679B64756E2164756E21