@query-farm/vgi-rpc 0.4.0 → 0.6.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/README.md +47 -0
- package/dist/client/connect.d.ts.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/oauth.d.ts +36 -0
- package/dist/client/oauth.d.ts.map +1 -1
- package/dist/client/pipe.d.ts +3 -0
- package/dist/client/pipe.d.ts.map +1 -1
- package/dist/client/stream.d.ts +3 -0
- package/dist/client/stream.d.ts.map +1 -1
- package/dist/client/types.d.ts +4 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/constants.d.ts +3 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/dispatch/describe.d.ts.map +1 -1
- package/dist/dispatch/stream.d.ts +2 -1
- package/dist/dispatch/stream.d.ts.map +1 -1
- package/dist/dispatch/unary.d.ts +2 -1
- package/dist/dispatch/unary.d.ts.map +1 -1
- package/dist/external.d.ts +45 -0
- package/dist/external.d.ts.map +1 -0
- package/dist/gcs.d.ts +38 -0
- package/dist/gcs.d.ts.map +1 -0
- package/dist/http/auth.d.ts +13 -2
- package/dist/http/auth.d.ts.map +1 -1
- package/dist/http/bearer.d.ts +34 -0
- package/dist/http/bearer.d.ts.map +1 -0
- package/dist/http/dispatch.d.ts +2 -0
- package/dist/http/dispatch.d.ts.map +1 -1
- package/dist/http/handler.d.ts.map +1 -1
- package/dist/http/index.d.ts +4 -0
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/jwt.d.ts +2 -2
- package/dist/http/jwt.d.ts.map +1 -1
- package/dist/http/mtls.d.ts +78 -0
- package/dist/http/mtls.d.ts.map +1 -0
- package/dist/http/pages.d.ts +9 -0
- package/dist/http/pages.d.ts.map +1 -0
- package/dist/http/types.d.ts +17 -1
- package/dist/http/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1119 -230
- package/dist/index.js.map +24 -20
- package/dist/otel.d.ts +47 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/s3.d.ts +43 -0
- package/dist/s3.d.ts.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +44 -1
- package/src/client/connect.ts +13 -5
- package/src/client/index.ts +10 -1
- package/src/client/introspect.ts +1 -1
- package/src/client/oauth.ts +94 -1
- package/src/client/pipe.ts +19 -4
- package/src/client/stream.ts +20 -7
- package/src/client/types.ts +4 -0
- package/src/constants.ts +4 -1
- package/src/dispatch/describe.ts +20 -0
- package/src/dispatch/stream.ts +7 -1
- package/src/dispatch/unary.ts +6 -1
- package/src/external.ts +209 -0
- package/src/gcs.ts +86 -0
- package/src/http/auth.ts +67 -4
- package/src/http/bearer.ts +107 -0
- package/src/http/dispatch.ts +26 -6
- package/src/http/handler.ts +81 -4
- package/src/http/index.ts +10 -0
- package/src/http/jwt.ts +17 -3
- package/src/http/mtls.ts +298 -0
- package/src/http/pages.ts +298 -0
- package/src/http/types.ts +17 -1
- package/src/index.ts +25 -0
- package/src/otel.ts +161 -0
- package/src/s3.ts +94 -0
- package/src/server.ts +42 -8
- package/src/types.ts +34 -0
package/dist/index.js
CHANGED
|
@@ -103,21 +103,247 @@ var SERVER_ID_KEY = "vgi_rpc.server_id";
|
|
|
103
103
|
var REQUEST_ID_KEY = "vgi_rpc.request_id";
|
|
104
104
|
var PROTOCOL_NAME_KEY = "vgi_rpc.protocol_name";
|
|
105
105
|
var DESCRIBE_VERSION_KEY = "vgi_rpc.describe_version";
|
|
106
|
-
var DESCRIBE_VERSION = "
|
|
106
|
+
var DESCRIBE_VERSION = "3";
|
|
107
107
|
var DESCRIBE_METHOD_NAME = "__describe__";
|
|
108
108
|
var STATE_KEY = "vgi_rpc.stream_state#b64";
|
|
109
|
+
var LOCATION_KEY = "vgi_rpc.location";
|
|
110
|
+
var LOCATION_SHA256_KEY = "vgi_rpc.location.sha256";
|
|
109
111
|
|
|
110
|
-
// src/
|
|
112
|
+
// src/external.ts
|
|
111
113
|
import { RecordBatchReader, RecordBatchStreamWriter } from "@query-farm/apache-arrow";
|
|
114
|
+
init_zstd();
|
|
112
115
|
|
|
113
|
-
// src/
|
|
116
|
+
// src/wire/response.ts
|
|
114
117
|
import {
|
|
118
|
+
Data,
|
|
119
|
+
DataType,
|
|
115
120
|
makeData,
|
|
116
121
|
RecordBatch,
|
|
117
122
|
Struct,
|
|
118
|
-
Type,
|
|
119
123
|
vectorFromArray
|
|
120
124
|
} from "@query-farm/apache-arrow";
|
|
125
|
+
function coerceInt64(schema, values) {
|
|
126
|
+
const result = { ...values };
|
|
127
|
+
for (const field of schema.fields) {
|
|
128
|
+
const val = result[field.name];
|
|
129
|
+
if (val === undefined)
|
|
130
|
+
continue;
|
|
131
|
+
if (!DataType.isInt(field.type) || field.type.bitWidth !== 64)
|
|
132
|
+
continue;
|
|
133
|
+
if (Array.isArray(val)) {
|
|
134
|
+
result[field.name] = val.map((v) => typeof v === "number" ? BigInt(v) : v);
|
|
135
|
+
} else if (typeof val === "number") {
|
|
136
|
+
result[field.name] = BigInt(val);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
function buildResultBatch(schema, values, serverId, requestId) {
|
|
142
|
+
const metadata = new Map;
|
|
143
|
+
metadata.set(SERVER_ID_KEY, serverId);
|
|
144
|
+
if (requestId !== null) {
|
|
145
|
+
metadata.set(REQUEST_ID_KEY, requestId);
|
|
146
|
+
}
|
|
147
|
+
if (schema.fields.length === 0) {
|
|
148
|
+
return buildEmptyBatch(schema, metadata);
|
|
149
|
+
}
|
|
150
|
+
for (const field of schema.fields) {
|
|
151
|
+
if (values[field.name] === undefined && !field.nullable) {
|
|
152
|
+
const got = Object.keys(values);
|
|
153
|
+
throw new TypeError(`Handler result missing required field '${field.name}'. Got keys: [${got.join(", ")}]`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const coerced = coerceInt64(schema, values);
|
|
157
|
+
const children = schema.fields.map((f) => {
|
|
158
|
+
const val = coerced[f.name];
|
|
159
|
+
if (val instanceof Data) {
|
|
160
|
+
return val;
|
|
161
|
+
}
|
|
162
|
+
const arr = vectorFromArray([val], f.type);
|
|
163
|
+
return arr.data[0];
|
|
164
|
+
});
|
|
165
|
+
const structType = new Struct(schema.fields);
|
|
166
|
+
const data = makeData({
|
|
167
|
+
type: structType,
|
|
168
|
+
length: 1,
|
|
169
|
+
children,
|
|
170
|
+
nullCount: 0
|
|
171
|
+
});
|
|
172
|
+
return new RecordBatch(schema, data, metadata);
|
|
173
|
+
}
|
|
174
|
+
function buildErrorBatch(schema, error, serverId, requestId) {
|
|
175
|
+
const metadata = new Map;
|
|
176
|
+
metadata.set(LOG_LEVEL_KEY, "EXCEPTION");
|
|
177
|
+
metadata.set(LOG_MESSAGE_KEY, `${error.constructor.name}: ${error.message}`);
|
|
178
|
+
const extra = {
|
|
179
|
+
exception_type: error.constructor.name,
|
|
180
|
+
exception_message: error.message,
|
|
181
|
+
traceback: error.stack ?? ""
|
|
182
|
+
};
|
|
183
|
+
metadata.set(LOG_EXTRA_KEY, JSON.stringify(extra));
|
|
184
|
+
metadata.set(SERVER_ID_KEY, serverId);
|
|
185
|
+
if (requestId !== null) {
|
|
186
|
+
metadata.set(REQUEST_ID_KEY, requestId);
|
|
187
|
+
}
|
|
188
|
+
return buildEmptyBatch(schema, metadata);
|
|
189
|
+
}
|
|
190
|
+
function buildLogBatch(schema, level, message, extra, serverId, requestId) {
|
|
191
|
+
const metadata = new Map;
|
|
192
|
+
metadata.set(LOG_LEVEL_KEY, level);
|
|
193
|
+
metadata.set(LOG_MESSAGE_KEY, message);
|
|
194
|
+
if (extra) {
|
|
195
|
+
metadata.set(LOG_EXTRA_KEY, JSON.stringify(extra));
|
|
196
|
+
}
|
|
197
|
+
if (serverId != null) {
|
|
198
|
+
metadata.set(SERVER_ID_KEY, serverId);
|
|
199
|
+
}
|
|
200
|
+
if (requestId != null) {
|
|
201
|
+
metadata.set(REQUEST_ID_KEY, requestId);
|
|
202
|
+
}
|
|
203
|
+
return buildEmptyBatch(schema, metadata);
|
|
204
|
+
}
|
|
205
|
+
function makeEmptyData(type) {
|
|
206
|
+
if (DataType.isStruct(type)) {
|
|
207
|
+
const children = type.children.map((f) => makeEmptyData(f.type));
|
|
208
|
+
return makeData({ type, length: 0, children, nullCount: 0 });
|
|
209
|
+
}
|
|
210
|
+
if (DataType.isList(type)) {
|
|
211
|
+
const childData = makeEmptyData(type.children[0].type);
|
|
212
|
+
return makeData({ type, length: 0, children: [childData], nullCount: 0, valueOffsets: new Int32Array([0]) });
|
|
213
|
+
}
|
|
214
|
+
if (DataType.isFixedSizeList(type)) {
|
|
215
|
+
const childData = makeEmptyData(type.children[0].type);
|
|
216
|
+
return makeData({ type, length: 0, child: childData, nullCount: 0 });
|
|
217
|
+
}
|
|
218
|
+
if (DataType.isMap(type)) {
|
|
219
|
+
const entryType = type.children[0]?.type;
|
|
220
|
+
const entryData = entryType ? makeEmptyData(entryType) : makeData({ type: new Struct([]), length: 0, children: [], nullCount: 0 });
|
|
221
|
+
return makeData({ type, length: 0, children: [entryData], nullCount: 0, valueOffsets: new Int32Array([0]) });
|
|
222
|
+
}
|
|
223
|
+
return makeData({ type, length: 0, nullCount: 0 });
|
|
224
|
+
}
|
|
225
|
+
function buildEmptyBatch(schema, metadata) {
|
|
226
|
+
const children = schema.fields.map((f) => makeEmptyData(f.type));
|
|
227
|
+
const structType = new Struct(schema.fields);
|
|
228
|
+
const data = makeData({
|
|
229
|
+
type: structType,
|
|
230
|
+
length: 0,
|
|
231
|
+
children,
|
|
232
|
+
nullCount: 0
|
|
233
|
+
});
|
|
234
|
+
return new RecordBatch(schema, data, metadata);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/external.ts
|
|
238
|
+
var DEFAULT_THRESHOLD = 1048576;
|
|
239
|
+
function httpsOnlyValidator(url) {
|
|
240
|
+
const parsed = new URL(url);
|
|
241
|
+
if (parsed.protocol !== "https:") {
|
|
242
|
+
throw new Error(`External location URL must use HTTPS, got "${parsed.protocol}"`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async function sha256Hex(data) {
|
|
246
|
+
const buf = new ArrayBuffer(data.byteLength);
|
|
247
|
+
new Uint8Array(buf).set(data);
|
|
248
|
+
const hash = await crypto.subtle.digest("SHA-256", buf);
|
|
249
|
+
return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
250
|
+
}
|
|
251
|
+
function isExternalLocationBatch(batch) {
|
|
252
|
+
if (batch.numRows !== 0)
|
|
253
|
+
return false;
|
|
254
|
+
const meta = batch.metadata;
|
|
255
|
+
if (!meta)
|
|
256
|
+
return false;
|
|
257
|
+
return meta.has(LOCATION_KEY) && !meta.has(LOG_LEVEL_KEY);
|
|
258
|
+
}
|
|
259
|
+
function makeExternalLocationBatch(schema, url, sha256) {
|
|
260
|
+
const metadata = new Map;
|
|
261
|
+
metadata.set(LOCATION_KEY, url);
|
|
262
|
+
if (sha256) {
|
|
263
|
+
metadata.set(LOCATION_SHA256_KEY, sha256);
|
|
264
|
+
}
|
|
265
|
+
return buildEmptyBatch(schema, metadata);
|
|
266
|
+
}
|
|
267
|
+
function serializeBatchToIpc(batch) {
|
|
268
|
+
const writer = new RecordBatchStreamWriter;
|
|
269
|
+
writer.reset(undefined, batch.schema);
|
|
270
|
+
writer.write(batch);
|
|
271
|
+
writer.close();
|
|
272
|
+
return writer.toUint8Array(true);
|
|
273
|
+
}
|
|
274
|
+
function batchByteSize(batch) {
|
|
275
|
+
const writer = new RecordBatchStreamWriter;
|
|
276
|
+
writer.reset(undefined, batch.schema);
|
|
277
|
+
writer.write(batch);
|
|
278
|
+
writer.close();
|
|
279
|
+
return writer.toUint8Array(true).byteLength;
|
|
280
|
+
}
|
|
281
|
+
async function maybeExternalizeBatch(batch, config) {
|
|
282
|
+
if (!config?.storage)
|
|
283
|
+
return batch;
|
|
284
|
+
if (batch.numRows === 0)
|
|
285
|
+
return batch;
|
|
286
|
+
const threshold = config.externalizeThresholdBytes ?? DEFAULT_THRESHOLD;
|
|
287
|
+
if (batchByteSize(batch) < threshold)
|
|
288
|
+
return batch;
|
|
289
|
+
let ipcData = serializeBatchToIpc(batch);
|
|
290
|
+
const checksum = await sha256Hex(ipcData);
|
|
291
|
+
let contentEncoding = "";
|
|
292
|
+
if (config.compression?.algorithm === "zstd") {
|
|
293
|
+
ipcData = zstdCompress(ipcData, config.compression.level ?? 3);
|
|
294
|
+
contentEncoding = "zstd";
|
|
295
|
+
}
|
|
296
|
+
const url = await config.storage.upload(ipcData, contentEncoding);
|
|
297
|
+
return makeExternalLocationBatch(batch.schema, url, checksum);
|
|
298
|
+
}
|
|
299
|
+
async function resolveExternalLocation(batch, config) {
|
|
300
|
+
if (!config)
|
|
301
|
+
return batch;
|
|
302
|
+
if (!isExternalLocationBatch(batch))
|
|
303
|
+
return batch;
|
|
304
|
+
const url = batch.metadata?.get(LOCATION_KEY);
|
|
305
|
+
if (!url)
|
|
306
|
+
return batch;
|
|
307
|
+
const validator = config.urlValidator === null ? undefined : config.urlValidator ?? httpsOnlyValidator;
|
|
308
|
+
if (validator) {
|
|
309
|
+
validator(url);
|
|
310
|
+
}
|
|
311
|
+
const response = await fetch(url);
|
|
312
|
+
if (!response.ok) {
|
|
313
|
+
throw new Error(`External location fetch failed: ${response.status} ${response.statusText} [url: ${url}]`);
|
|
314
|
+
}
|
|
315
|
+
let data = new Uint8Array(await response.arrayBuffer());
|
|
316
|
+
const contentEncoding = response.headers.get("Content-Encoding");
|
|
317
|
+
if (contentEncoding === "zstd") {
|
|
318
|
+
data = new Uint8Array(zstdDecompress(data));
|
|
319
|
+
}
|
|
320
|
+
const expectedSha256 = batch.metadata?.get(LOCATION_SHA256_KEY);
|
|
321
|
+
if (expectedSha256) {
|
|
322
|
+
const actualSha256 = await sha256Hex(data);
|
|
323
|
+
if (actualSha256 !== expectedSha256) {
|
|
324
|
+
throw new Error(`SHA-256 checksum mismatch for ${url}: expected ${expectedSha256}, got ${actualSha256}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const reader = await RecordBatchReader.from(data);
|
|
328
|
+
await reader.open();
|
|
329
|
+
const resolved = reader.next();
|
|
330
|
+
if (!resolved || resolved.done || !resolved.value) {
|
|
331
|
+
throw new Error(`No data batch found in external IPC stream from ${url}`);
|
|
332
|
+
}
|
|
333
|
+
return resolved.value;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/http/common.ts
|
|
337
|
+
import { RecordBatchReader as RecordBatchReader2, RecordBatchStreamWriter as RecordBatchStreamWriter2 } from "@query-farm/apache-arrow";
|
|
338
|
+
|
|
339
|
+
// src/util/conform.ts
|
|
340
|
+
import {
|
|
341
|
+
makeData as makeData2,
|
|
342
|
+
RecordBatch as RecordBatch2,
|
|
343
|
+
Struct as Struct2,
|
|
344
|
+
Type,
|
|
345
|
+
vectorFromArray as vectorFromArray2
|
|
346
|
+
} from "@query-farm/apache-arrow";
|
|
121
347
|
function needsValueCast(src, dst) {
|
|
122
348
|
if (src.typeId === dst.typeId)
|
|
123
349
|
return false;
|
|
@@ -153,19 +379,19 @@ function conformBatchToSchema(batch, schema) {
|
|
|
153
379
|
const v = col.get(r);
|
|
154
380
|
values.push(typeof v === "bigint" ? Number(v) : v);
|
|
155
381
|
}
|
|
156
|
-
return
|
|
382
|
+
return vectorFromArray2(values, dstType).data[0];
|
|
157
383
|
}
|
|
158
384
|
return srcChild.clone(dstType);
|
|
159
385
|
});
|
|
160
|
-
const structType = new
|
|
161
|
-
const data =
|
|
386
|
+
const structType = new Struct2(schema.fields);
|
|
387
|
+
const data = makeData2({
|
|
162
388
|
type: structType,
|
|
163
389
|
length: batch.numRows,
|
|
164
390
|
children,
|
|
165
391
|
nullCount: batch.data.nullCount,
|
|
166
392
|
nullBitmap: batch.data.nullBitmap
|
|
167
393
|
});
|
|
168
|
-
return new
|
|
394
|
+
return new RecordBatch2(schema, data, batch.metadata);
|
|
169
395
|
}
|
|
170
396
|
|
|
171
397
|
// src/http/common.ts
|
|
@@ -180,7 +406,7 @@ class HttpRpcError extends Error {
|
|
|
180
406
|
}
|
|
181
407
|
}
|
|
182
408
|
function serializeIpcStream(schema, batches) {
|
|
183
|
-
const writer = new
|
|
409
|
+
const writer = new RecordBatchStreamWriter2;
|
|
184
410
|
writer.reset(undefined, schema);
|
|
185
411
|
for (const batch of batches) {
|
|
186
412
|
writer.write(conformBatchToSchema(batch, schema));
|
|
@@ -194,7 +420,7 @@ function arrowResponse(body, status = 200, extraHeaders) {
|
|
|
194
420
|
return new Response(body, { status, headers });
|
|
195
421
|
}
|
|
196
422
|
async function readRequestFromBody(body) {
|
|
197
|
-
const reader = await
|
|
423
|
+
const reader = await RecordBatchReader2.from(body);
|
|
198
424
|
await reader.open();
|
|
199
425
|
const schema = reader.schema;
|
|
200
426
|
if (!schema) {
|
|
@@ -208,25 +434,25 @@ async function readRequestFromBody(body) {
|
|
|
208
434
|
}
|
|
209
435
|
|
|
210
436
|
// src/client/introspect.ts
|
|
211
|
-
import { Schema as ArrowSchema, RecordBatchReader as
|
|
437
|
+
import { Schema as ArrowSchema, RecordBatchReader as RecordBatchReader5 } from "@query-farm/apache-arrow";
|
|
212
438
|
|
|
213
439
|
// src/client/ipc.ts
|
|
214
440
|
import {
|
|
215
441
|
Binary,
|
|
216
442
|
Bool,
|
|
217
|
-
DataType,
|
|
443
|
+
DataType as DataType2,
|
|
218
444
|
Float64,
|
|
219
445
|
Int64,
|
|
220
|
-
makeData as
|
|
221
|
-
RecordBatch as
|
|
222
|
-
RecordBatchReader as
|
|
223
|
-
Struct as
|
|
446
|
+
makeData as makeData3,
|
|
447
|
+
RecordBatch as RecordBatch3,
|
|
448
|
+
RecordBatchReader as RecordBatchReader4,
|
|
449
|
+
Struct as Struct3,
|
|
224
450
|
Utf8,
|
|
225
|
-
vectorFromArray as
|
|
451
|
+
vectorFromArray as vectorFromArray3
|
|
226
452
|
} from "@query-farm/apache-arrow";
|
|
227
453
|
|
|
228
454
|
// src/wire/reader.ts
|
|
229
|
-
import { RecordBatchReader as
|
|
455
|
+
import { RecordBatchReader as RecordBatchReader3 } from "@query-farm/apache-arrow";
|
|
230
456
|
|
|
231
457
|
class IpcStreamReader {
|
|
232
458
|
reader;
|
|
@@ -236,7 +462,7 @@ class IpcStreamReader {
|
|
|
236
462
|
this.reader = reader;
|
|
237
463
|
}
|
|
238
464
|
static async create(input) {
|
|
239
|
-
const reader = await
|
|
465
|
+
const reader = await RecordBatchReader3.from(input);
|
|
240
466
|
await reader.open({ autoDestroy: false });
|
|
241
467
|
if (reader.closed) {
|
|
242
468
|
throw new Error("Input stream closed before first IPC message");
|
|
@@ -313,12 +539,12 @@ function inferArrowType(value) {
|
|
|
313
539
|
function coerceForArrow(type, value) {
|
|
314
540
|
if (value == null)
|
|
315
541
|
return value;
|
|
316
|
-
if (
|
|
542
|
+
if (DataType2.isInt(type) && type.bitWidth === 64) {
|
|
317
543
|
if (typeof value === "number")
|
|
318
544
|
return BigInt(value);
|
|
319
545
|
return value;
|
|
320
546
|
}
|
|
321
|
-
if (
|
|
547
|
+
if (DataType2.isMap(type)) {
|
|
322
548
|
if (value instanceof Map) {
|
|
323
549
|
const entriesField = type.children[0];
|
|
324
550
|
const valueType = entriesField.type.children[1].type;
|
|
@@ -330,7 +556,7 @@ function coerceForArrow(type, value) {
|
|
|
330
556
|
}
|
|
331
557
|
return value;
|
|
332
558
|
}
|
|
333
|
-
if (
|
|
559
|
+
if (DataType2.isList(type)) {
|
|
334
560
|
if (Array.isArray(value)) {
|
|
335
561
|
const elemType = type.children[0].type;
|
|
336
562
|
return value.map((v) => coerceForArrow(elemType, v));
|
|
@@ -344,32 +570,32 @@ function buildRequestIpc(schema, params, method) {
|
|
|
344
570
|
metadata.set(RPC_METHOD_KEY, method);
|
|
345
571
|
metadata.set(REQUEST_VERSION_KEY, REQUEST_VERSION);
|
|
346
572
|
if (schema.fields.length === 0) {
|
|
347
|
-
const structType2 = new
|
|
348
|
-
const data2 =
|
|
573
|
+
const structType2 = new Struct3(schema.fields);
|
|
574
|
+
const data2 = makeData3({
|
|
349
575
|
type: structType2,
|
|
350
576
|
length: 1,
|
|
351
577
|
children: [],
|
|
352
578
|
nullCount: 0
|
|
353
579
|
});
|
|
354
|
-
const batch2 = new
|
|
580
|
+
const batch2 = new RecordBatch3(schema, data2, metadata);
|
|
355
581
|
return serializeIpcStream(schema, [batch2]);
|
|
356
582
|
}
|
|
357
583
|
const children = schema.fields.map((f) => {
|
|
358
584
|
const val = coerceForArrow(f.type, params[f.name]);
|
|
359
|
-
return
|
|
585
|
+
return vectorFromArray3([val], f.type).data[0];
|
|
360
586
|
});
|
|
361
|
-
const structType = new
|
|
362
|
-
const data =
|
|
587
|
+
const structType = new Struct3(schema.fields);
|
|
588
|
+
const data = makeData3({
|
|
363
589
|
type: structType,
|
|
364
590
|
length: 1,
|
|
365
591
|
children,
|
|
366
592
|
nullCount: 0
|
|
367
593
|
});
|
|
368
|
-
const batch = new
|
|
594
|
+
const batch = new RecordBatch3(schema, data, metadata);
|
|
369
595
|
return serializeIpcStream(schema, [batch]);
|
|
370
596
|
}
|
|
371
597
|
async function readResponseBatches(body) {
|
|
372
|
-
const reader = await
|
|
598
|
+
const reader = await RecordBatchReader4.from(body);
|
|
373
599
|
await reader.open();
|
|
374
600
|
const schema = reader.schema;
|
|
375
601
|
if (!schema) {
|
|
@@ -443,7 +669,7 @@ async function readSequentialStreams(body) {
|
|
|
443
669
|
|
|
444
670
|
// src/client/introspect.ts
|
|
445
671
|
async function deserializeSchema(bytes) {
|
|
446
|
-
const reader = await
|
|
672
|
+
const reader = await RecordBatchReader5.from(bytes);
|
|
447
673
|
await reader.open();
|
|
448
674
|
return reader.schema;
|
|
449
675
|
}
|
|
@@ -507,7 +733,7 @@ async function parseDescribeResponse(batches, onLog) {
|
|
|
507
733
|
return { protocolName, methods };
|
|
508
734
|
}
|
|
509
735
|
async function httpIntrospect(baseUrl, options) {
|
|
510
|
-
const prefix = options?.prefix ?? "
|
|
736
|
+
const prefix = options?.prefix ?? "";
|
|
511
737
|
const emptySchema = new ArrowSchema([]);
|
|
512
738
|
const body = buildRequestIpc(emptySchema, {}, DESCRIBE_METHOD_NAME);
|
|
513
739
|
const headers = { "Content-Type": ARROW_CONTENT_TYPE };
|
|
@@ -528,7 +754,7 @@ async function httpIntrospect(baseUrl, options) {
|
|
|
528
754
|
}
|
|
529
755
|
|
|
530
756
|
// src/client/stream.ts
|
|
531
|
-
import { Field, makeData as
|
|
757
|
+
import { Field, makeData as makeData4, RecordBatch as RecordBatch4, Schema, Struct as Struct4, vectorFromArray as vectorFromArray4 } from "@query-farm/apache-arrow";
|
|
532
758
|
class HttpStreamSession {
|
|
533
759
|
_baseUrl;
|
|
534
760
|
_prefix;
|
|
@@ -544,6 +770,7 @@ class HttpStreamSession {
|
|
|
544
770
|
_compressFn;
|
|
545
771
|
_decompressFn;
|
|
546
772
|
_authorization;
|
|
773
|
+
_externalConfig;
|
|
547
774
|
constructor(opts) {
|
|
548
775
|
this._baseUrl = opts.baseUrl;
|
|
549
776
|
this._prefix = opts.prefix;
|
|
@@ -559,6 +786,7 @@ class HttpStreamSession {
|
|
|
559
786
|
this._compressFn = opts.compressFn;
|
|
560
787
|
this._decompressFn = opts.decompressFn;
|
|
561
788
|
this._authorization = opts.authorization;
|
|
789
|
+
this._externalConfig = opts.externalConfig;
|
|
562
790
|
}
|
|
563
791
|
get header() {
|
|
564
792
|
return this._header;
|
|
@@ -598,7 +826,7 @@ class HttpStreamSession {
|
|
|
598
826
|
const emptyBatch = this._buildEmptyBatch(zeroSchema);
|
|
599
827
|
const metadata2 = new Map;
|
|
600
828
|
metadata2.set(STATE_KEY, this._stateToken);
|
|
601
|
-
const batchWithMeta = new
|
|
829
|
+
const batchWithMeta = new RecordBatch4(zeroSchema, emptyBatch.data, metadata2);
|
|
602
830
|
return this._doExchange(zeroSchema, [batchWithMeta]);
|
|
603
831
|
}
|
|
604
832
|
const keys = Object.keys(input[0]);
|
|
@@ -617,10 +845,10 @@ class HttpStreamSession {
|
|
|
617
845
|
const inputSchema = new Schema(fields);
|
|
618
846
|
const children = inputSchema.fields.map((f) => {
|
|
619
847
|
const values = input.map((row) => row[f.name]);
|
|
620
|
-
return
|
|
848
|
+
return vectorFromArray4(values, f.type).data[0];
|
|
621
849
|
});
|
|
622
|
-
const structType = new
|
|
623
|
-
const data =
|
|
850
|
+
const structType = new Struct4(inputSchema.fields);
|
|
851
|
+
const data = makeData4({
|
|
624
852
|
type: structType,
|
|
625
853
|
length: input.length,
|
|
626
854
|
children,
|
|
@@ -628,7 +856,7 @@ class HttpStreamSession {
|
|
|
628
856
|
});
|
|
629
857
|
const metadata = new Map;
|
|
630
858
|
metadata.set(STATE_KEY, this._stateToken);
|
|
631
|
-
const batch = new
|
|
859
|
+
const batch = new RecordBatch4(inputSchema, data, metadata);
|
|
632
860
|
return this._doExchange(inputSchema, [batch]);
|
|
633
861
|
}
|
|
634
862
|
async _doExchange(schema, batches) {
|
|
@@ -663,22 +891,26 @@ class HttpStreamSession {
|
|
|
663
891
|
}
|
|
664
892
|
_buildEmptyBatch(schema) {
|
|
665
893
|
const children = schema.fields.map((f) => {
|
|
666
|
-
return
|
|
894
|
+
return makeData4({ type: f.type, length: 0, nullCount: 0 });
|
|
667
895
|
});
|
|
668
|
-
const structType = new
|
|
669
|
-
const data =
|
|
896
|
+
const structType = new Struct4(schema.fields);
|
|
897
|
+
const data = makeData4({
|
|
670
898
|
type: structType,
|
|
671
899
|
length: 0,
|
|
672
900
|
children,
|
|
673
901
|
nullCount: 0
|
|
674
902
|
});
|
|
675
|
-
return new
|
|
903
|
+
return new RecordBatch4(schema, data);
|
|
676
904
|
}
|
|
677
905
|
async* [Symbol.asyncIterator]() {
|
|
678
|
-
for (
|
|
906
|
+
for (let batch of this._pendingBatches) {
|
|
679
907
|
if (batch.numRows === 0) {
|
|
680
|
-
|
|
681
|
-
|
|
908
|
+
if (isExternalLocationBatch(batch)) {
|
|
909
|
+
batch = await resolveExternalLocation(batch, this._externalConfig);
|
|
910
|
+
} else {
|
|
911
|
+
dispatchLogOrError(batch, this._onLog);
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
682
914
|
}
|
|
683
915
|
yield extractBatchRows(batch);
|
|
684
916
|
}
|
|
@@ -691,7 +923,7 @@ class HttpStreamSession {
|
|
|
691
923
|
const responseBody = await this._sendContinuation(this._stateToken);
|
|
692
924
|
const { batches } = await readResponseBatches(responseBody);
|
|
693
925
|
let gotContinuation = false;
|
|
694
|
-
for (
|
|
926
|
+
for (let batch of batches) {
|
|
695
927
|
if (batch.numRows === 0) {
|
|
696
928
|
const token = batch.metadata?.get(STATE_KEY);
|
|
697
929
|
if (token) {
|
|
@@ -699,8 +931,12 @@ class HttpStreamSession {
|
|
|
699
931
|
gotContinuation = true;
|
|
700
932
|
continue;
|
|
701
933
|
}
|
|
702
|
-
|
|
703
|
-
|
|
934
|
+
if (isExternalLocationBatch(batch)) {
|
|
935
|
+
batch = await resolveExternalLocation(batch, this._externalConfig);
|
|
936
|
+
} else {
|
|
937
|
+
dispatchLogOrError(batch, this._onLog);
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
704
940
|
}
|
|
705
941
|
yield extractBatchRows(batch);
|
|
706
942
|
}
|
|
@@ -712,14 +948,14 @@ class HttpStreamSession {
|
|
|
712
948
|
const emptySchema = new Schema([]);
|
|
713
949
|
const metadata = new Map;
|
|
714
950
|
metadata.set(STATE_KEY, token);
|
|
715
|
-
const structType = new
|
|
716
|
-
const data =
|
|
951
|
+
const structType = new Struct4(emptySchema.fields);
|
|
952
|
+
const data = makeData4({
|
|
717
953
|
type: structType,
|
|
718
954
|
length: 1,
|
|
719
955
|
children: [],
|
|
720
956
|
nullCount: 0
|
|
721
957
|
});
|
|
722
|
-
const batch = new
|
|
958
|
+
const batch = new RecordBatch4(emptySchema, data, metadata);
|
|
723
959
|
const body = serializeIpcStream(emptySchema, [batch]);
|
|
724
960
|
const resp = await fetch(`${this._baseUrl}${this._prefix}/${this._method}/exchange`, {
|
|
725
961
|
method: "POST",
|
|
@@ -736,10 +972,11 @@ class HttpStreamSession {
|
|
|
736
972
|
|
|
737
973
|
// src/client/connect.ts
|
|
738
974
|
function httpConnect(baseUrl, options) {
|
|
739
|
-
const prefix = (options?.prefix ?? "
|
|
975
|
+
const prefix = (options?.prefix ?? "").replace(/\/+$/, "");
|
|
740
976
|
const onLog = options?.onLog;
|
|
741
977
|
const compressionLevel = options?.compressionLevel;
|
|
742
978
|
const authorization = options?.authorization;
|
|
979
|
+
const externalConfig = options?.externalLocation;
|
|
743
980
|
let methodCache = null;
|
|
744
981
|
let compressFn;
|
|
745
982
|
let decompressFn;
|
|
@@ -811,10 +1048,14 @@ function httpConnect(baseUrl, options) {
|
|
|
811
1048
|
const responseBody = await readResponse(resp);
|
|
812
1049
|
const { batches } = await readResponseBatches(responseBody);
|
|
813
1050
|
let resultBatch = null;
|
|
814
|
-
for (
|
|
1051
|
+
for (let batch of batches) {
|
|
815
1052
|
if (batch.numRows === 0) {
|
|
816
|
-
|
|
817
|
-
|
|
1053
|
+
if (isExternalLocationBatch(batch)) {
|
|
1054
|
+
batch = await resolveExternalLocation(batch, externalConfig);
|
|
1055
|
+
} else {
|
|
1056
|
+
dispatchLogOrError(batch, onLog);
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
818
1059
|
}
|
|
819
1060
|
resultBatch = batch;
|
|
820
1061
|
}
|
|
@@ -950,7 +1191,8 @@ function httpConnect(baseUrl, options) {
|
|
|
950
1191
|
compressionLevel,
|
|
951
1192
|
compressFn,
|
|
952
1193
|
decompressFn,
|
|
953
|
-
authorization
|
|
1194
|
+
authorization,
|
|
1195
|
+
externalConfig
|
|
954
1196
|
});
|
|
955
1197
|
},
|
|
956
1198
|
async describe() {
|
|
@@ -969,6 +1211,8 @@ function parseMetadataJson(json) {
|
|
|
969
1211
|
result.scopesSupported = json.scopes_supported;
|
|
970
1212
|
if (json.bearer_methods_supported)
|
|
971
1213
|
result.bearerMethodsSupported = json.bearer_methods_supported;
|
|
1214
|
+
if (json.resource_signing_alg_values_supported)
|
|
1215
|
+
result.resourceSigningAlgValuesSupported = json.resource_signing_alg_values_supported;
|
|
972
1216
|
if (json.resource_name)
|
|
973
1217
|
result.resourceName = json.resource_name;
|
|
974
1218
|
if (json.resource_documentation)
|
|
@@ -977,10 +1221,20 @@ function parseMetadataJson(json) {
|
|
|
977
1221
|
result.resourcePolicyUri = json.resource_policy_uri;
|
|
978
1222
|
if (json.resource_tos_uri)
|
|
979
1223
|
result.resourceTosUri = json.resource_tos_uri;
|
|
1224
|
+
if (json.client_id)
|
|
1225
|
+
result.clientId = json.client_id;
|
|
1226
|
+
if (json.client_secret)
|
|
1227
|
+
result.clientSecret = json.client_secret;
|
|
1228
|
+
if (json.use_id_token_as_bearer)
|
|
1229
|
+
result.useIdTokenAsBearer = json.use_id_token_as_bearer;
|
|
1230
|
+
if (json.device_code_client_id)
|
|
1231
|
+
result.deviceCodeClientId = json.device_code_client_id;
|
|
1232
|
+
if (json.device_code_client_secret)
|
|
1233
|
+
result.deviceCodeClientSecret = json.device_code_client_secret;
|
|
980
1234
|
return result;
|
|
981
1235
|
}
|
|
982
1236
|
async function httpOAuthMetadata(baseUrl, prefix) {
|
|
983
|
-
const effectivePrefix = (prefix ?? "
|
|
1237
|
+
const effectivePrefix = (prefix ?? "").replace(/\/+$/, "");
|
|
984
1238
|
const metadataUrl = `${baseUrl.replace(/\/+$/, "")}/.well-known/oauth-protected-resource${effectivePrefix}`;
|
|
985
1239
|
try {
|
|
986
1240
|
return await fetchOAuthMetadata(metadataUrl);
|
|
@@ -1006,15 +1260,65 @@ function parseResourceMetadataUrl(wwwAuthenticate) {
|
|
|
1006
1260
|
return null;
|
|
1007
1261
|
return metadataMatch[1];
|
|
1008
1262
|
}
|
|
1263
|
+
function parseClientId(wwwAuthenticate) {
|
|
1264
|
+
const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
|
|
1265
|
+
if (!bearerMatch)
|
|
1266
|
+
return null;
|
|
1267
|
+
const params = bearerMatch[1];
|
|
1268
|
+
const clientIdMatch = params.match(/client_id="([^"]+)"/);
|
|
1269
|
+
if (!clientIdMatch)
|
|
1270
|
+
return null;
|
|
1271
|
+
return clientIdMatch[1];
|
|
1272
|
+
}
|
|
1273
|
+
function parseClientSecret(wwwAuthenticate) {
|
|
1274
|
+
const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
|
|
1275
|
+
if (!bearerMatch)
|
|
1276
|
+
return null;
|
|
1277
|
+
const params = bearerMatch[1];
|
|
1278
|
+
const match = params.match(/client_secret="([^"]+)"/);
|
|
1279
|
+
if (!match)
|
|
1280
|
+
return null;
|
|
1281
|
+
return match[1];
|
|
1282
|
+
}
|
|
1283
|
+
function parseUseIdTokenAsBearer(wwwAuthenticate) {
|
|
1284
|
+
const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
|
|
1285
|
+
if (!bearerMatch)
|
|
1286
|
+
return false;
|
|
1287
|
+
const params = bearerMatch[1];
|
|
1288
|
+
const match = params.match(/use_id_token_as_bearer="([^"]+)"/);
|
|
1289
|
+
if (!match)
|
|
1290
|
+
return false;
|
|
1291
|
+
return match[1] === "true";
|
|
1292
|
+
}
|
|
1293
|
+
function parseDeviceCodeClientId(wwwAuthenticate) {
|
|
1294
|
+
const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
|
|
1295
|
+
if (!bearerMatch)
|
|
1296
|
+
return null;
|
|
1297
|
+
const params = bearerMatch[1];
|
|
1298
|
+
const match = params.match(/device_code_client_id="([^"]+)"/);
|
|
1299
|
+
if (!match)
|
|
1300
|
+
return null;
|
|
1301
|
+
return match[1];
|
|
1302
|
+
}
|
|
1303
|
+
function parseDeviceCodeClientSecret(wwwAuthenticate) {
|
|
1304
|
+
const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
|
|
1305
|
+
if (!bearerMatch)
|
|
1306
|
+
return null;
|
|
1307
|
+
const params = bearerMatch[1];
|
|
1308
|
+
const match = params.match(/device_code_client_secret="([^"]+)"/);
|
|
1309
|
+
if (!match)
|
|
1310
|
+
return null;
|
|
1311
|
+
return match[1];
|
|
1312
|
+
}
|
|
1009
1313
|
// src/client/pipe.ts
|
|
1010
1314
|
import {
|
|
1011
1315
|
Field as Field2,
|
|
1012
|
-
makeData as
|
|
1013
|
-
RecordBatch as
|
|
1014
|
-
RecordBatchStreamWriter as
|
|
1316
|
+
makeData as makeData5,
|
|
1317
|
+
RecordBatch as RecordBatch5,
|
|
1318
|
+
RecordBatchStreamWriter as RecordBatchStreamWriter3,
|
|
1015
1319
|
Schema as Schema2,
|
|
1016
|
-
Struct as
|
|
1017
|
-
vectorFromArray as
|
|
1320
|
+
Struct as Struct5,
|
|
1321
|
+
vectorFromArray as vectorFromArray5
|
|
1018
1322
|
} from "@query-farm/apache-arrow";
|
|
1019
1323
|
class PipeIncrementalWriter {
|
|
1020
1324
|
writer;
|
|
@@ -1022,7 +1326,7 @@ class PipeIncrementalWriter {
|
|
|
1022
1326
|
closed = false;
|
|
1023
1327
|
constructor(writeFn, schema) {
|
|
1024
1328
|
this.writeFn = writeFn;
|
|
1025
|
-
this.writer = new
|
|
1329
|
+
this.writer = new RecordBatchStreamWriter3;
|
|
1026
1330
|
this.writer.reset(undefined, schema);
|
|
1027
1331
|
this.drain();
|
|
1028
1332
|
}
|
|
@@ -1060,6 +1364,7 @@ class PipeStreamSession {
|
|
|
1060
1364
|
_outputSchema;
|
|
1061
1365
|
_releaseBusy;
|
|
1062
1366
|
_setDrainPromise;
|
|
1367
|
+
_externalConfig;
|
|
1063
1368
|
constructor(opts) {
|
|
1064
1369
|
this._reader = opts.reader;
|
|
1065
1370
|
this._writeFn = opts.writeFn;
|
|
@@ -1068,6 +1373,7 @@ class PipeStreamSession {
|
|
|
1068
1373
|
this._outputSchema = opts.outputSchema;
|
|
1069
1374
|
this._releaseBusy = opts.releaseBusy;
|
|
1070
1375
|
this._setDrainPromise = opts.setDrainPromise;
|
|
1376
|
+
this._externalConfig = opts.externalConfig;
|
|
1071
1377
|
}
|
|
1072
1378
|
get header() {
|
|
1073
1379
|
return this._header;
|
|
@@ -1078,6 +1384,9 @@ class PipeStreamSession {
|
|
|
1078
1384
|
if (batch === null)
|
|
1079
1385
|
return null;
|
|
1080
1386
|
if (batch.numRows === 0) {
|
|
1387
|
+
if (isExternalLocationBatch(batch)) {
|
|
1388
|
+
return await resolveExternalLocation(batch, this._externalConfig);
|
|
1389
|
+
}
|
|
1081
1390
|
if (dispatchLogOrError(batch, this._onLog)) {
|
|
1082
1391
|
continue;
|
|
1083
1392
|
}
|
|
@@ -1103,16 +1412,16 @@ class PipeStreamSession {
|
|
|
1103
1412
|
if (input.length === 0) {
|
|
1104
1413
|
inputSchema = this._inputSchema ?? this._outputSchema;
|
|
1105
1414
|
const children = inputSchema.fields.map((f) => {
|
|
1106
|
-
return
|
|
1415
|
+
return makeData5({ type: f.type, length: 0, nullCount: 0 });
|
|
1107
1416
|
});
|
|
1108
|
-
const structType = new
|
|
1109
|
-
const data =
|
|
1417
|
+
const structType = new Struct5(inputSchema.fields);
|
|
1418
|
+
const data = makeData5({
|
|
1110
1419
|
type: structType,
|
|
1111
1420
|
length: 0,
|
|
1112
1421
|
children,
|
|
1113
1422
|
nullCount: 0
|
|
1114
1423
|
});
|
|
1115
|
-
batch = new
|
|
1424
|
+
batch = new RecordBatch5(inputSchema, data);
|
|
1116
1425
|
} else {
|
|
1117
1426
|
const keys = Object.keys(input[0]);
|
|
1118
1427
|
const fields = keys.map((key) => {
|
|
@@ -1137,16 +1446,16 @@ class PipeStreamSession {
|
|
|
1137
1446
|
}
|
|
1138
1447
|
const children = inputSchema.fields.map((f) => {
|
|
1139
1448
|
const values = input.map((row) => row[f.name]);
|
|
1140
|
-
return
|
|
1449
|
+
return vectorFromArray5(values, f.type).data[0];
|
|
1141
1450
|
});
|
|
1142
|
-
const structType = new
|
|
1143
|
-
const data =
|
|
1451
|
+
const structType = new Struct5(inputSchema.fields);
|
|
1452
|
+
const data = makeData5({
|
|
1144
1453
|
type: structType,
|
|
1145
1454
|
length: input.length,
|
|
1146
1455
|
children,
|
|
1147
1456
|
nullCount: 0
|
|
1148
1457
|
});
|
|
1149
|
-
batch = new
|
|
1458
|
+
batch = new RecordBatch5(inputSchema, data);
|
|
1150
1459
|
}
|
|
1151
1460
|
if (!this._inputWriter) {
|
|
1152
1461
|
this._inputWriter = new PipeIncrementalWriter(this._writeFn, inputSchema);
|
|
@@ -1185,14 +1494,14 @@ class PipeStreamSession {
|
|
|
1185
1494
|
try {
|
|
1186
1495
|
const tickSchema = new Schema2([]);
|
|
1187
1496
|
this._inputWriter = new PipeIncrementalWriter(this._writeFn, tickSchema);
|
|
1188
|
-
const structType = new
|
|
1189
|
-
const tickData =
|
|
1497
|
+
const structType = new Struct5(tickSchema.fields);
|
|
1498
|
+
const tickData = makeData5({
|
|
1190
1499
|
type: structType,
|
|
1191
1500
|
length: 0,
|
|
1192
1501
|
children: [],
|
|
1193
1502
|
nullCount: 0
|
|
1194
1503
|
});
|
|
1195
|
-
const tickBatch = new
|
|
1504
|
+
const tickBatch = new RecordBatch5(tickSchema, tickData);
|
|
1196
1505
|
while (true) {
|
|
1197
1506
|
this._inputWriter.write(tickBatch);
|
|
1198
1507
|
await this._ensureOutputStream();
|
|
@@ -1247,6 +1556,7 @@ class PipeStreamSession {
|
|
|
1247
1556
|
}
|
|
1248
1557
|
function pipeConnect(readable, writable, options) {
|
|
1249
1558
|
const onLog = options?.onLog;
|
|
1559
|
+
const externalConfig = options?.externalLocation;
|
|
1250
1560
|
let reader = null;
|
|
1251
1561
|
let readerPromise = null;
|
|
1252
1562
|
let methodCache = null;
|
|
@@ -1322,10 +1632,14 @@ function pipeConnect(readable, writable, options) {
|
|
|
1322
1632
|
throw new Error("EOF reading response");
|
|
1323
1633
|
}
|
|
1324
1634
|
let resultBatch = null;
|
|
1325
|
-
for (
|
|
1635
|
+
for (let batch of response.batches) {
|
|
1326
1636
|
if (batch.numRows === 0) {
|
|
1327
|
-
|
|
1328
|
-
|
|
1637
|
+
if (isExternalLocationBatch(batch)) {
|
|
1638
|
+
batch = await resolveExternalLocation(batch, externalConfig);
|
|
1639
|
+
} else {
|
|
1640
|
+
dispatchLogOrError(batch, onLog);
|
|
1641
|
+
continue;
|
|
1642
|
+
}
|
|
1329
1643
|
}
|
|
1330
1644
|
resultBatch = batch;
|
|
1331
1645
|
}
|
|
@@ -1378,7 +1692,8 @@ function pipeConnect(readable, writable, options) {
|
|
|
1378
1692
|
header,
|
|
1379
1693
|
outputSchema,
|
|
1380
1694
|
releaseBusy,
|
|
1381
|
-
setDrainPromise
|
|
1695
|
+
setDrainPromise,
|
|
1696
|
+
externalConfig
|
|
1382
1697
|
});
|
|
1383
1698
|
} catch (e) {
|
|
1384
1699
|
try {
|
|
@@ -1428,7 +1743,8 @@ function subprocessConnect(cmd, options) {
|
|
|
1428
1743
|
}
|
|
1429
1744
|
};
|
|
1430
1745
|
const client = pipeConnect(stdout, writable, {
|
|
1431
|
-
onLog: options?.onLog
|
|
1746
|
+
onLog: options?.onLog,
|
|
1747
|
+
externalLocation: options?.externalLocation
|
|
1432
1748
|
});
|
|
1433
1749
|
const originalClose = client.close;
|
|
1434
1750
|
client.close = () => {
|
|
@@ -1449,6 +1765,8 @@ function oauthResourceMetadataToJson(metadata) {
|
|
|
1449
1765
|
json.scopes_supported = metadata.scopesSupported;
|
|
1450
1766
|
if (metadata.bearerMethodsSupported)
|
|
1451
1767
|
json.bearer_methods_supported = metadata.bearerMethodsSupported;
|
|
1768
|
+
if (metadata.resourceSigningAlgValuesSupported)
|
|
1769
|
+
json.resource_signing_alg_values_supported = metadata.resourceSigningAlgValuesSupported;
|
|
1452
1770
|
if (metadata.resourceName)
|
|
1453
1771
|
json.resource_name = metadata.resourceName;
|
|
1454
1772
|
if (metadata.resourceDocumentation)
|
|
@@ -1457,146 +1775,124 @@ function oauthResourceMetadataToJson(metadata) {
|
|
|
1457
1775
|
json.resource_policy_uri = metadata.resourcePolicyUri;
|
|
1458
1776
|
if (metadata.resourceTosUri)
|
|
1459
1777
|
json.resource_tos_uri = metadata.resourceTosUri;
|
|
1778
|
+
if (metadata.clientId) {
|
|
1779
|
+
if (!/^[A-Za-z0-9\-._~]+$/.test(metadata.clientId)) {
|
|
1780
|
+
throw new Error(`Invalid client_id: must contain only URL-safe characters [A-Za-z0-9\\-._~]`);
|
|
1781
|
+
}
|
|
1782
|
+
json.client_id = metadata.clientId;
|
|
1783
|
+
}
|
|
1784
|
+
if (metadata.clientSecret) {
|
|
1785
|
+
if (!/^[A-Za-z0-9\-._~]+$/.test(metadata.clientSecret)) {
|
|
1786
|
+
throw new Error(`Invalid client_secret: must contain only URL-safe characters [A-Za-z0-9\\-._~]`);
|
|
1787
|
+
}
|
|
1788
|
+
json.client_secret = metadata.clientSecret;
|
|
1789
|
+
}
|
|
1790
|
+
if (metadata.deviceCodeClientId) {
|
|
1791
|
+
if (!/^[A-Za-z0-9\-._~]+$/.test(metadata.deviceCodeClientId)) {
|
|
1792
|
+
throw new Error(`Invalid device_code_client_id: must contain only URL-safe characters [A-Za-z0-9\\-._~]`);
|
|
1793
|
+
}
|
|
1794
|
+
json.device_code_client_id = metadata.deviceCodeClientId;
|
|
1795
|
+
}
|
|
1796
|
+
if (metadata.deviceCodeClientSecret) {
|
|
1797
|
+
if (!/^[A-Za-z0-9\-._~]+$/.test(metadata.deviceCodeClientSecret)) {
|
|
1798
|
+
throw new Error(`Invalid device_code_client_secret: must contain only URL-safe characters [A-Za-z0-9\\-._~]`);
|
|
1799
|
+
}
|
|
1800
|
+
json.device_code_client_secret = metadata.deviceCodeClientSecret;
|
|
1801
|
+
}
|
|
1802
|
+
if (metadata.useIdTokenAsBearer) {
|
|
1803
|
+
json.use_id_token_as_bearer = true;
|
|
1804
|
+
}
|
|
1460
1805
|
return json;
|
|
1461
1806
|
}
|
|
1462
1807
|
function wellKnownPath(prefix) {
|
|
1463
1808
|
return `/.well-known/oauth-protected-resource${prefix}`;
|
|
1464
1809
|
}
|
|
1465
|
-
function buildWwwAuthenticateHeader(metadataUrl) {
|
|
1810
|
+
function buildWwwAuthenticateHeader(metadataUrl, clientId, clientSecret, useIdTokenAsBearer, deviceCodeClientId, deviceCodeClientSecret) {
|
|
1811
|
+
let header = "Bearer";
|
|
1466
1812
|
if (metadataUrl) {
|
|
1467
|
-
|
|
1813
|
+
header += ` resource_metadata="${metadataUrl}"`;
|
|
1468
1814
|
}
|
|
1469
|
-
|
|
1470
|
-
}
|
|
1471
|
-
// src/http/handler.ts
|
|
1472
|
-
import { randomBytes } from "node:crypto";
|
|
1473
|
-
import { Schema as Schema5 } from "@query-farm/apache-arrow";
|
|
1474
|
-
|
|
1475
|
-
// src/types.ts
|
|
1476
|
-
import { RecordBatch as RecordBatch6, recordBatchFromArrays } from "@query-farm/apache-arrow";
|
|
1477
|
-
|
|
1478
|
-
// src/wire/response.ts
|
|
1479
|
-
import {
|
|
1480
|
-
Data,
|
|
1481
|
-
DataType as DataType2,
|
|
1482
|
-
makeData as makeData5,
|
|
1483
|
-
RecordBatch as RecordBatch5,
|
|
1484
|
-
Struct as Struct5,
|
|
1485
|
-
vectorFromArray as vectorFromArray5
|
|
1486
|
-
} from "@query-farm/apache-arrow";
|
|
1487
|
-
function coerceInt64(schema, values) {
|
|
1488
|
-
const result = { ...values };
|
|
1489
|
-
for (const field of schema.fields) {
|
|
1490
|
-
const val = result[field.name];
|
|
1491
|
-
if (val === undefined)
|
|
1492
|
-
continue;
|
|
1493
|
-
if (!DataType2.isInt(field.type) || field.type.bitWidth !== 64)
|
|
1494
|
-
continue;
|
|
1495
|
-
if (Array.isArray(val)) {
|
|
1496
|
-
result[field.name] = val.map((v) => typeof v === "number" ? BigInt(v) : v);
|
|
1497
|
-
} else if (typeof val === "number") {
|
|
1498
|
-
result[field.name] = BigInt(val);
|
|
1499
|
-
}
|
|
1815
|
+
if (clientId) {
|
|
1816
|
+
header += `, client_id="${clientId}"`;
|
|
1500
1817
|
}
|
|
1501
|
-
|
|
1502
|
-
}
|
|
1503
|
-
function buildResultBatch(schema, values, serverId, requestId) {
|
|
1504
|
-
const metadata = new Map;
|
|
1505
|
-
metadata.set(SERVER_ID_KEY, serverId);
|
|
1506
|
-
if (requestId !== null) {
|
|
1507
|
-
metadata.set(REQUEST_ID_KEY, requestId);
|
|
1818
|
+
if (clientSecret) {
|
|
1819
|
+
header += `, client_secret="${clientSecret}"`;
|
|
1508
1820
|
}
|
|
1509
|
-
if (
|
|
1510
|
-
|
|
1821
|
+
if (deviceCodeClientId) {
|
|
1822
|
+
header += `, device_code_client_id="${deviceCodeClientId}"`;
|
|
1511
1823
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
const got = Object.keys(values);
|
|
1515
|
-
throw new TypeError(`Handler result missing required field '${field.name}'. Got keys: [${got.join(", ")}]`);
|
|
1516
|
-
}
|
|
1824
|
+
if (deviceCodeClientSecret) {
|
|
1825
|
+
header += `, device_code_client_secret="${deviceCodeClientSecret}"`;
|
|
1517
1826
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
return val;
|
|
1523
|
-
}
|
|
1524
|
-
const arr = vectorFromArray5([val], f.type);
|
|
1525
|
-
return arr.data[0];
|
|
1526
|
-
});
|
|
1527
|
-
const structType = new Struct5(schema.fields);
|
|
1528
|
-
const data = makeData5({
|
|
1529
|
-
type: structType,
|
|
1530
|
-
length: 1,
|
|
1531
|
-
children,
|
|
1532
|
-
nullCount: 0
|
|
1533
|
-
});
|
|
1534
|
-
return new RecordBatch5(schema, data, metadata);
|
|
1827
|
+
if (useIdTokenAsBearer) {
|
|
1828
|
+
header += `, use_id_token_as_bearer="true"`;
|
|
1829
|
+
}
|
|
1830
|
+
return header;
|
|
1535
1831
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1832
|
+
// src/http/bearer.ts
|
|
1833
|
+
import { timingSafeEqual } from "node:crypto";
|
|
1834
|
+
function bearerAuthenticate(options) {
|
|
1835
|
+
const { validate } = options;
|
|
1836
|
+
return async function authenticate(request) {
|
|
1837
|
+
const authHeader = request.headers.get("Authorization") ?? "";
|
|
1838
|
+
if (!authHeader.startsWith("Bearer ")) {
|
|
1839
|
+
throw new Error("Missing or invalid Authorization header");
|
|
1840
|
+
}
|
|
1841
|
+
const token = authHeader.slice(7);
|
|
1842
|
+
return validate(token);
|
|
1544
1843
|
};
|
|
1545
|
-
metadata.set(LOG_EXTRA_KEY, JSON.stringify(extra));
|
|
1546
|
-
metadata.set(SERVER_ID_KEY, serverId);
|
|
1547
|
-
if (requestId !== null) {
|
|
1548
|
-
metadata.set(REQUEST_ID_KEY, requestId);
|
|
1549
|
-
}
|
|
1550
|
-
return buildEmptyBatch(schema, metadata);
|
|
1551
1844
|
}
|
|
1552
|
-
function
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
if (
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
if (serverId != null) {
|
|
1560
|
-
metadata.set(SERVER_ID_KEY, serverId);
|
|
1561
|
-
}
|
|
1562
|
-
if (requestId != null) {
|
|
1563
|
-
metadata.set(REQUEST_ID_KEY, requestId);
|
|
1564
|
-
}
|
|
1565
|
-
return buildEmptyBatch(schema, metadata);
|
|
1845
|
+
function safeEqual(a, b) {
|
|
1846
|
+
const enc = new TextEncoder;
|
|
1847
|
+
const bufA = enc.encode(a);
|
|
1848
|
+
const bufB = enc.encode(b);
|
|
1849
|
+
if (bufA.byteLength !== bufB.byteLength)
|
|
1850
|
+
return false;
|
|
1851
|
+
return timingSafeEqual(bufA, bufB);
|
|
1566
1852
|
}
|
|
1567
|
-
function
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
}
|
|
1576
|
-
if (DataType2.isFixedSizeList(type)) {
|
|
1577
|
-
const childData = makeEmptyData(type.children[0].type);
|
|
1578
|
-
return makeData5({ type, length: 0, child: childData, nullCount: 0 });
|
|
1579
|
-
}
|
|
1580
|
-
if (DataType2.isMap(type)) {
|
|
1581
|
-
const entryType = type.children[0]?.type;
|
|
1582
|
-
const entryData = entryType ? makeEmptyData(entryType) : makeData5({ type: new Struct5([]), length: 0, children: [], nullCount: 0 });
|
|
1583
|
-
return makeData5({ type, length: 0, children: [entryData], nullCount: 0, valueOffsets: new Int32Array([0]) });
|
|
1853
|
+
function bearerAuthenticateStatic(options) {
|
|
1854
|
+
const entries = options.tokens instanceof Map ? [...options.tokens.entries()] : Object.entries(options.tokens);
|
|
1855
|
+
function validate(token) {
|
|
1856
|
+
for (const [key, ctx] of entries) {
|
|
1857
|
+
if (safeEqual(token, key))
|
|
1858
|
+
return ctx;
|
|
1859
|
+
}
|
|
1860
|
+
throw new Error("Unknown bearer token");
|
|
1584
1861
|
}
|
|
1585
|
-
return
|
|
1862
|
+
return bearerAuthenticate({ validate });
|
|
1586
1863
|
}
|
|
1587
|
-
function
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1864
|
+
function isCredentialError(err) {
|
|
1865
|
+
return err instanceof Error && err.constructor === Error && err.name !== "PermissionError";
|
|
1866
|
+
}
|
|
1867
|
+
function chainAuthenticate(...authenticators) {
|
|
1868
|
+
if (authenticators.length === 0) {
|
|
1869
|
+
throw new Error("chainAuthenticate requires at least one authenticator");
|
|
1870
|
+
}
|
|
1871
|
+
return async function authenticate(request) {
|
|
1872
|
+
let lastError = null;
|
|
1873
|
+
for (const authFn of authenticators) {
|
|
1874
|
+
try {
|
|
1875
|
+
return await authFn(request);
|
|
1876
|
+
} catch (err) {
|
|
1877
|
+
if (isCredentialError(err)) {
|
|
1878
|
+
lastError = err;
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
throw err;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
const error = new Error("No authenticator accepted the request");
|
|
1885
|
+
if (lastError)
|
|
1886
|
+
error.cause = lastError;
|
|
1887
|
+
throw error;
|
|
1888
|
+
};
|
|
1597
1889
|
}
|
|
1890
|
+
// src/http/handler.ts
|
|
1891
|
+
import { randomBytes } from "node:crypto";
|
|
1892
|
+
import { Schema as Schema5 } from "@query-farm/apache-arrow";
|
|
1598
1893
|
|
|
1599
1894
|
// src/types.ts
|
|
1895
|
+
import { RecordBatch as RecordBatch6, recordBatchFromArrays } from "@query-farm/apache-arrow";
|
|
1600
1896
|
var MethodType;
|
|
1601
1897
|
((MethodType2) => {
|
|
1602
1898
|
MethodType2["UNARY"] = "unary";
|
|
@@ -1665,7 +1961,7 @@ class OutputCollector {
|
|
|
1665
1961
|
init_zstd();
|
|
1666
1962
|
|
|
1667
1963
|
// src/http/dispatch.ts
|
|
1668
|
-
import { RecordBatch as RecordBatch8, RecordBatchReader as
|
|
1964
|
+
import { RecordBatch as RecordBatch8, RecordBatchReader as RecordBatchReader6, Schema as Schema4 } from "@query-farm/apache-arrow";
|
|
1669
1965
|
|
|
1670
1966
|
// src/dispatch/describe.ts
|
|
1671
1967
|
import {
|
|
@@ -1681,9 +1977,9 @@ import {
|
|
|
1681
1977
|
} from "@query-farm/apache-arrow";
|
|
1682
1978
|
|
|
1683
1979
|
// src/util/schema.ts
|
|
1684
|
-
import { RecordBatchStreamWriter as
|
|
1980
|
+
import { RecordBatchStreamWriter as RecordBatchStreamWriter4 } from "@query-farm/apache-arrow";
|
|
1685
1981
|
function serializeSchema(schema) {
|
|
1686
|
-
const writer = new
|
|
1982
|
+
const writer = new RecordBatchStreamWriter4;
|
|
1687
1983
|
writer.reset(undefined, schema);
|
|
1688
1984
|
writer.close();
|
|
1689
1985
|
return writer.toUint8Array(true);
|
|
@@ -1700,7 +1996,9 @@ var DESCRIBE_SCHEMA = new Schema3([
|
|
|
1700
1996
|
new Field3("param_types_json", new Utf82, true),
|
|
1701
1997
|
new Field3("param_defaults_json", new Utf82, true),
|
|
1702
1998
|
new Field3("has_header", new Bool2, false),
|
|
1703
|
-
new Field3("header_schema_ipc", new Binary2, true)
|
|
1999
|
+
new Field3("header_schema_ipc", new Binary2, true),
|
|
2000
|
+
new Field3("is_exchange", new Bool2, true),
|
|
2001
|
+
new Field3("param_docs_json", new Utf82, true)
|
|
1704
2002
|
]);
|
|
1705
2003
|
function buildDescribeBatch(protocolName, methods, serverId) {
|
|
1706
2004
|
const sortedEntries = [...methods.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
@@ -1714,6 +2012,8 @@ function buildDescribeBatch(protocolName, methods, serverId) {
|
|
|
1714
2012
|
const paramDefaultsJsons = [];
|
|
1715
2013
|
const hasHeaders = [];
|
|
1716
2014
|
const headerSchemas = [];
|
|
2015
|
+
const isExchanges = [];
|
|
2016
|
+
const paramDocsJsons = [];
|
|
1717
2017
|
for (const [name, method] of sortedEntries) {
|
|
1718
2018
|
names.push(name);
|
|
1719
2019
|
methodTypes.push(method.type);
|
|
@@ -1740,6 +2040,14 @@ function buildDescribeBatch(protocolName, methods, serverId) {
|
|
|
1740
2040
|
}
|
|
1741
2041
|
hasHeaders.push(!!method.headerSchema);
|
|
1742
2042
|
headerSchemas.push(method.headerSchema ? serializeSchema(method.headerSchema) : null);
|
|
2043
|
+
if (method.exchangeFn) {
|
|
2044
|
+
isExchanges.push(true);
|
|
2045
|
+
} else if (method.producerFn) {
|
|
2046
|
+
isExchanges.push(false);
|
|
2047
|
+
} else {
|
|
2048
|
+
isExchanges.push(null);
|
|
2049
|
+
}
|
|
2050
|
+
paramDocsJsons.push(null);
|
|
1743
2051
|
}
|
|
1744
2052
|
const nameArr = vectorFromArray6(names, new Utf82);
|
|
1745
2053
|
const methodTypeArr = vectorFromArray6(methodTypes, new Utf82);
|
|
@@ -1751,6 +2059,8 @@ function buildDescribeBatch(protocolName, methods, serverId) {
|
|
|
1751
2059
|
const paramDefaultsArr = vectorFromArray6(paramDefaultsJsons, new Utf82);
|
|
1752
2060
|
const hasHeaderArr = vectorFromArray6(hasHeaders, new Bool2);
|
|
1753
2061
|
const headerSchemaArr = vectorFromArray6(headerSchemas, new Binary2);
|
|
2062
|
+
const isExchangeArr = vectorFromArray6(isExchanges, new Bool2);
|
|
2063
|
+
const paramDocsArr = vectorFromArray6(paramDocsJsons, new Utf82);
|
|
1754
2064
|
const children = [
|
|
1755
2065
|
nameArr.data[0],
|
|
1756
2066
|
methodTypeArr.data[0],
|
|
@@ -1761,7 +2071,9 @@ function buildDescribeBatch(protocolName, methods, serverId) {
|
|
|
1761
2071
|
paramTypesArr.data[0],
|
|
1762
2072
|
paramDefaultsArr.data[0],
|
|
1763
2073
|
hasHeaderArr.data[0],
|
|
1764
|
-
headerSchemaArr.data[0]
|
|
2074
|
+
headerSchemaArr.data[0],
|
|
2075
|
+
isExchangeArr.data[0],
|
|
2076
|
+
paramDocsArr.data[0]
|
|
1765
2077
|
];
|
|
1766
2078
|
const structType = new Struct6(DESCRIBE_SCHEMA.fields);
|
|
1767
2079
|
const data = makeData6({
|
|
@@ -1824,7 +2136,7 @@ function parseRequest(schema, batch) {
|
|
|
1824
2136
|
}
|
|
1825
2137
|
|
|
1826
2138
|
// src/http/token.ts
|
|
1827
|
-
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2139
|
+
import { createHmac, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
1828
2140
|
var TOKEN_VERSION = 2;
|
|
1829
2141
|
var HMAC_LEN = 32;
|
|
1830
2142
|
var MIN_TOKEN_LEN = 1 + 8 + 12 + HMAC_LEN;
|
|
@@ -1861,7 +2173,7 @@ function unpackStateToken(tokenBase64, signingKey, tokenTtl) {
|
|
|
1861
2173
|
const payload = token.subarray(0, token.length - HMAC_LEN);
|
|
1862
2174
|
const receivedMac = token.subarray(token.length - HMAC_LEN);
|
|
1863
2175
|
const expectedMac = createHmac("sha256", signingKey).update(payload).digest();
|
|
1864
|
-
if (!
|
|
2176
|
+
if (!timingSafeEqual2(receivedMac, expectedMac)) {
|
|
1865
2177
|
throw new Error("State token HMAC verification failed");
|
|
1866
2178
|
}
|
|
1867
2179
|
let offset = 0;
|
|
@@ -1903,7 +2215,7 @@ function unpackStateToken(tokenBase64, signingKey, tokenTtl) {
|
|
|
1903
2215
|
|
|
1904
2216
|
// src/http/dispatch.ts
|
|
1905
2217
|
async function deserializeSchema2(bytes) {
|
|
1906
|
-
const reader = await
|
|
2218
|
+
const reader = await RecordBatchReader6.from(bytes);
|
|
1907
2219
|
await reader.open();
|
|
1908
2220
|
return reader.schema;
|
|
1909
2221
|
}
|
|
@@ -1923,12 +2235,17 @@ async function httpDispatchUnary(method, body, ctx) {
|
|
|
1923
2235
|
const out = new OutputCollector(schema, true, ctx.serverId, parsed.requestId, ctx.authContext);
|
|
1924
2236
|
try {
|
|
1925
2237
|
const result = await method.handler(parsed.params, out);
|
|
1926
|
-
|
|
2238
|
+
let resultBatch = buildResultBatch(schema, result, ctx.serverId, parsed.requestId);
|
|
2239
|
+
if (ctx.externalLocation) {
|
|
2240
|
+
resultBatch = await maybeExternalizeBatch(resultBatch, ctx.externalLocation);
|
|
2241
|
+
}
|
|
1927
2242
|
const batches = [...out.batches.map((b) => b.batch), resultBatch];
|
|
1928
2243
|
return arrowResponse(serializeIpcStream(schema, batches));
|
|
1929
2244
|
} catch (error) {
|
|
1930
2245
|
const errBatch = buildErrorBatch(schema, error, ctx.serverId, parsed.requestId);
|
|
1931
|
-
|
|
2246
|
+
const response = arrowResponse(serializeIpcStream(schema, [errBatch]), 500);
|
|
2247
|
+
response.__dispatchError = error;
|
|
2248
|
+
return response;
|
|
1932
2249
|
}
|
|
1933
2250
|
}
|
|
1934
2251
|
async function httpDispatchStreamInit(method, body, ctx) {
|
|
@@ -1950,7 +2267,9 @@ async function httpDispatchStreamInit(method, body, ctx) {
|
|
|
1950
2267
|
} catch (error) {
|
|
1951
2268
|
const errSchema = method.headerSchema ?? EMPTY_SCHEMA;
|
|
1952
2269
|
const errBatch = buildErrorBatch(errSchema, error, ctx.serverId, parsed.requestId);
|
|
1953
|
-
|
|
2270
|
+
const response = arrowResponse(serializeIpcStream(errSchema, [errBatch]), 500);
|
|
2271
|
+
response.__dispatchError = error;
|
|
2272
|
+
return response;
|
|
1954
2273
|
}
|
|
1955
2274
|
const resolvedOutputSchema = state?.__outputSchema ?? outputSchema;
|
|
1956
2275
|
const effectiveProducer = state?.__isProducer ?? isProducer;
|
|
@@ -1964,7 +2283,9 @@ async function httpDispatchStreamInit(method, body, ctx) {
|
|
|
1964
2283
|
headerBytes = serializeIpcStream(method.headerSchema, headerBatches);
|
|
1965
2284
|
} catch (error) {
|
|
1966
2285
|
const errBatch = buildErrorBatch(method.headerSchema, error, ctx.serverId, parsed.requestId);
|
|
1967
|
-
|
|
2286
|
+
const response = arrowResponse(serializeIpcStream(method.headerSchema, [errBatch]), 500);
|
|
2287
|
+
response.__dispatchError = error;
|
|
2288
|
+
return response;
|
|
1968
2289
|
}
|
|
1969
2290
|
}
|
|
1970
2291
|
if (effectiveProducer) {
|
|
@@ -2039,7 +2360,9 @@ async function httpDispatchStreamExchange(method, body, ctx) {
|
|
|
2039
2360
|
`).slice(0, 5).join(`
|
|
2040
2361
|
`));
|
|
2041
2362
|
const errBatch = buildErrorBatch(outputSchema, error, ctx.serverId, null);
|
|
2042
|
-
|
|
2363
|
+
const response = arrowResponse(serializeIpcStream(outputSchema, [errBatch]), 500);
|
|
2364
|
+
response.__dispatchError = error;
|
|
2365
|
+
return response;
|
|
2043
2366
|
}
|
|
2044
2367
|
const batches = [];
|
|
2045
2368
|
if (out.finished) {
|
|
@@ -2074,6 +2397,7 @@ async function produceStreamResponse(method, state, outputSchema, inputSchema, c
|
|
|
2074
2397
|
const allBatches = [];
|
|
2075
2398
|
const maxBytes = ctx.maxStreamResponseBytes;
|
|
2076
2399
|
let estimatedBytes = 0;
|
|
2400
|
+
let producerError;
|
|
2077
2401
|
while (true) {
|
|
2078
2402
|
const out = new OutputCollector(outputSchema, true, ctx.serverId, requestId, ctx.authContext);
|
|
2079
2403
|
try {
|
|
@@ -2089,6 +2413,7 @@ async function produceStreamResponse(method, state, outputSchema, inputSchema, c
|
|
|
2089
2413
|
`).slice(0, 3).join(`
|
|
2090
2414
|
`));
|
|
2091
2415
|
allBatches.push(buildErrorBatch(outputSchema, error, ctx.serverId, requestId));
|
|
2416
|
+
producerError = error instanceof Error ? error : new Error(String(error));
|
|
2092
2417
|
break;
|
|
2093
2418
|
}
|
|
2094
2419
|
for (const emitted of out.batches) {
|
|
@@ -2118,7 +2443,11 @@ async function produceStreamResponse(method, state, outputSchema, inputSchema, c
|
|
|
2118
2443
|
} else {
|
|
2119
2444
|
responseBody = dataBytes;
|
|
2120
2445
|
}
|
|
2121
|
-
|
|
2446
|
+
const response = arrowResponse(responseBody);
|
|
2447
|
+
if (producerError) {
|
|
2448
|
+
response.__dispatchError = producerError;
|
|
2449
|
+
}
|
|
2450
|
+
return response;
|
|
2122
2451
|
}
|
|
2123
2452
|
function concatBytes(...arrays) {
|
|
2124
2453
|
const totalLen = arrays.reduce((sum, a) => sum + a.length, 0);
|
|
@@ -2131,6 +2460,261 @@ function concatBytes(...arrays) {
|
|
|
2131
2460
|
return result;
|
|
2132
2461
|
}
|
|
2133
2462
|
|
|
2463
|
+
// src/http/pages.ts
|
|
2464
|
+
var LOGO_URL = "https://vgi-rpc-python.query.farm/assets/logo-hero.png";
|
|
2465
|
+
var FONTS = `<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
2466
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
2467
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">`;
|
|
2468
|
+
function escapeHtml(s) {
|
|
2469
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2470
|
+
}
|
|
2471
|
+
function arrowTypeToString(type) {
|
|
2472
|
+
const id = type.typeId;
|
|
2473
|
+
if (id === 5)
|
|
2474
|
+
return "str";
|
|
2475
|
+
if (id === 4)
|
|
2476
|
+
return "bytes";
|
|
2477
|
+
if (id === 2)
|
|
2478
|
+
return "int";
|
|
2479
|
+
if (id === 3)
|
|
2480
|
+
return "float";
|
|
2481
|
+
if (id === 6)
|
|
2482
|
+
return "bool";
|
|
2483
|
+
if (id === 12)
|
|
2484
|
+
return "list";
|
|
2485
|
+
if (id === 17)
|
|
2486
|
+
return "map";
|
|
2487
|
+
if (id === 24)
|
|
2488
|
+
return "enum";
|
|
2489
|
+
return type.toString();
|
|
2490
|
+
}
|
|
2491
|
+
function buildLandingPage(protocolName, serverId, describePath, repoUrl) {
|
|
2492
|
+
const links = [];
|
|
2493
|
+
if (describePath) {
|
|
2494
|
+
links.push(`<a class="primary" href="${escapeHtml(describePath)}">View service API</a>`);
|
|
2495
|
+
}
|
|
2496
|
+
if (repoUrl) {
|
|
2497
|
+
links.push(`<a href="${escapeHtml(repoUrl)}">Source repository</a>`);
|
|
2498
|
+
}
|
|
2499
|
+
links.push(`<a href="https://vgi-rpc.query.farm">Learn more about <code>vgi-rpc</code></a>`);
|
|
2500
|
+
return `<!DOCTYPE html>
|
|
2501
|
+
<html lang="en">
|
|
2502
|
+
<head>
|
|
2503
|
+
<meta charset="utf-8">
|
|
2504
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2505
|
+
<title>${escapeHtml(protocolName)} — vgi-rpc</title>
|
|
2506
|
+
${FONTS}
|
|
2507
|
+
<style>
|
|
2508
|
+
body { font-family: 'Inter', system-ui, -apple-system, sans-serif; max-width: 600px;
|
|
2509
|
+
margin: 0 auto; padding: 60px 20px 0; color: #2c2c1e; text-align: center;
|
|
2510
|
+
background: #faf8f0; }
|
|
2511
|
+
.logo { margin-bottom: 24px; }
|
|
2512
|
+
.logo img { width: 140px; height: 140px; border-radius: 50%;
|
|
2513
|
+
box-shadow: 0 4px 24px rgba(0,0,0,0.12); }
|
|
2514
|
+
h1 { color: #2d5016; margin-bottom: 8px; font-weight: 700; }
|
|
2515
|
+
code { font-family: 'JetBrains Mono', monospace; background: #f0ece0;
|
|
2516
|
+
padding: 2px 6px; border-radius: 3px; font-size: 0.9em; color: #2c2c1e; }
|
|
2517
|
+
a { color: #2d5016; text-decoration: none; }
|
|
2518
|
+
a:hover { color: #4a7c23; }
|
|
2519
|
+
p { line-height: 1.7; color: #6b6b5a; }
|
|
2520
|
+
.meta { font-size: 0.9em; color: #6b6b5a; }
|
|
2521
|
+
.links { margin-top: 28px; display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
|
|
2522
|
+
.links a { display: inline-block; padding: 8px 18px; border-radius: 6px;
|
|
2523
|
+
border: 1px solid #4a7c23; color: #2d5016; font-weight: 600;
|
|
2524
|
+
font-size: 0.9em; transition: all 0.2s ease; }
|
|
2525
|
+
.links a:hover { background: #4a7c23; color: #fff; }
|
|
2526
|
+
.links a.primary { background: #2d5016; color: #fff; border-color: #2d5016; }
|
|
2527
|
+
.links a.primary:hover { background: #4a7c23; border-color: #4a7c23; }
|
|
2528
|
+
footer { margin-top: 48px; padding: 20px 0; border-top: 1px solid #f0ece0;
|
|
2529
|
+
color: #6b6b5a; font-size: 0.85em; }
|
|
2530
|
+
footer a { color: #2d5016; font-weight: 600; }
|
|
2531
|
+
footer a:hover { color: #4a7c23; }
|
|
2532
|
+
</style>
|
|
2533
|
+
</head>
|
|
2534
|
+
<body>
|
|
2535
|
+
<div class="logo">
|
|
2536
|
+
<img src="${LOGO_URL}" alt="vgi-rpc logo">
|
|
2537
|
+
</div>
|
|
2538
|
+
<h1>${escapeHtml(protocolName)}</h1>
|
|
2539
|
+
<p class="meta">Powered by <code>vgi-rpc</code> (TypeScript) · server <code>${escapeHtml(serverId)}</code></p>
|
|
2540
|
+
<p>This is a <code>vgi-rpc</code> service endpoint.</p>
|
|
2541
|
+
<div class="links">
|
|
2542
|
+
${links.join(`
|
|
2543
|
+
`)}
|
|
2544
|
+
</div>
|
|
2545
|
+
<footer>
|
|
2546
|
+
© 2026 🚜 <a href="https://query.farm">Query.Farm LLC</a>
|
|
2547
|
+
</footer>
|
|
2548
|
+
</body>
|
|
2549
|
+
</html>`;
|
|
2550
|
+
}
|
|
2551
|
+
function buildNotFoundPage(prefix, protocolName) {
|
|
2552
|
+
const nameFragment = protocolName ? ` (<strong>${escapeHtml(protocolName)}</strong>)` : "";
|
|
2553
|
+
const prefixDisplay = prefix || "/";
|
|
2554
|
+
return `<!DOCTYPE html>
|
|
2555
|
+
<html lang="en">
|
|
2556
|
+
<head>
|
|
2557
|
+
<meta charset="utf-8">
|
|
2558
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2559
|
+
<title>404 — vgi-rpc endpoint</title>
|
|
2560
|
+
<style>
|
|
2561
|
+
body { font-family: system-ui, -apple-system, sans-serif; max-width: 600px;
|
|
2562
|
+
margin: 60px auto; padding: 0 20px; color: #333; text-align: center; }
|
|
2563
|
+
.logo { margin-bottom: 24px; }
|
|
2564
|
+
.logo img { width: 120px; height: 120px; }
|
|
2565
|
+
h1 { color: #555; }
|
|
2566
|
+
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-size: 0.95em; }
|
|
2567
|
+
a { color: #0066cc; }
|
|
2568
|
+
p { line-height: 1.6; }
|
|
2569
|
+
</style>
|
|
2570
|
+
</head>
|
|
2571
|
+
<body>
|
|
2572
|
+
<div class="logo">
|
|
2573
|
+
<img src="${LOGO_URL}" alt="vgi-rpc logo">
|
|
2574
|
+
</div>
|
|
2575
|
+
<h1>404 — Not Found</h1>
|
|
2576
|
+
<p>This is a <code>vgi-rpc</code> service endpoint${nameFragment}.</p>
|
|
2577
|
+
<p>RPC methods are available under <code>${escapeHtml(prefixDisplay)}/<method></code>.</p>
|
|
2578
|
+
<p>Learn more at <a href="https://vgi-rpc.query.farm">vgi-rpc.query.farm</a>.</p>
|
|
2579
|
+
</body>
|
|
2580
|
+
</html>`;
|
|
2581
|
+
}
|
|
2582
|
+
function buildMethodCard(method) {
|
|
2583
|
+
const name = escapeHtml(method.name);
|
|
2584
|
+
const isUnary = method.type === "unary";
|
|
2585
|
+
const hasHeader = !!method.headerSchema;
|
|
2586
|
+
const badges = [];
|
|
2587
|
+
badges.push(isUnary ? `<span class="badge badge-unary">unary</span>` : `<span class="badge badge-stream">stream</span>`);
|
|
2588
|
+
if (hasHeader)
|
|
2589
|
+
badges.push(`<span class="badge badge-header">header</span>`);
|
|
2590
|
+
let paramsHtml = "";
|
|
2591
|
+
const paramsSchema = method.paramsSchema;
|
|
2592
|
+
if (paramsSchema.fields.length > 0) {
|
|
2593
|
+
const rows = paramsSchema.fields.map((f) => {
|
|
2594
|
+
const paramName = escapeHtml(f.name);
|
|
2595
|
+
const paramType = escapeHtml(arrowTypeToString(f.type));
|
|
2596
|
+
const defaultVal = method.defaults && f.name in method.defaults ? escapeHtml(JSON.stringify(method.defaults[f.name])) : "—";
|
|
2597
|
+
return `<tr><td><code>${paramName}</code></td><td><code>${paramType}</code></td><td>${defaultVal}</td><td>—</td></tr>`;
|
|
2598
|
+
});
|
|
2599
|
+
paramsHtml = `<div class="section-label">Parameters</div>
|
|
2600
|
+
<table><tr><th>Name</th><th>Type</th><th>Default</th><th>Description</th></tr>
|
|
2601
|
+
${rows.join(`
|
|
2602
|
+
`)}
|
|
2603
|
+
</table>`;
|
|
2604
|
+
} else {
|
|
2605
|
+
paramsHtml = `<p class="no-params">No parameters</p>`;
|
|
2606
|
+
}
|
|
2607
|
+
let returnsHtml = "";
|
|
2608
|
+
if (isUnary && method.resultSchema.fields.length > 0) {
|
|
2609
|
+
const rows = method.resultSchema.fields.map((f) => {
|
|
2610
|
+
return `<tr><td><code>${escapeHtml(f.name)}</code></td><td><code>${escapeHtml(arrowTypeToString(f.type))}</code></td></tr>`;
|
|
2611
|
+
});
|
|
2612
|
+
returnsHtml = `<div class="section-label">Returns</div>
|
|
2613
|
+
<table><tr><th>Name</th><th>Type</th></tr>
|
|
2614
|
+
${rows.join(`
|
|
2615
|
+
`)}
|
|
2616
|
+
</table>`;
|
|
2617
|
+
}
|
|
2618
|
+
let headerHtml = "";
|
|
2619
|
+
if (hasHeader && method.headerSchema && method.headerSchema.fields.length > 0) {
|
|
2620
|
+
const rows = method.headerSchema.fields.map((f) => {
|
|
2621
|
+
return `<tr><td><code>${escapeHtml(f.name)}</code></td><td><code>${escapeHtml(arrowTypeToString(f.type))}</code></td></tr>`;
|
|
2622
|
+
});
|
|
2623
|
+
headerHtml = `<div class="section-label">Stream Header</div>
|
|
2624
|
+
<table><tr><th>Name</th><th>Type</th></tr>
|
|
2625
|
+
${rows.join(`
|
|
2626
|
+
`)}
|
|
2627
|
+
</table>`;
|
|
2628
|
+
}
|
|
2629
|
+
const docHtml = method.doc ? `<p class="docstring">${escapeHtml(method.doc)}</p>` : "";
|
|
2630
|
+
return `<div class="card">
|
|
2631
|
+
<div class="card-header">
|
|
2632
|
+
<span class="method-name">${name}</span>
|
|
2633
|
+
${badges.join(`
|
|
2634
|
+
`)}
|
|
2635
|
+
</div>
|
|
2636
|
+
${docHtml}
|
|
2637
|
+
${paramsHtml}
|
|
2638
|
+
${returnsHtml}
|
|
2639
|
+
${headerHtml}
|
|
2640
|
+
</div>`;
|
|
2641
|
+
}
|
|
2642
|
+
function buildDescribePage(protocolName, serverId, methods, repoUrl) {
|
|
2643
|
+
const sortedMethods = [...methods.entries()].filter(([name]) => name !== "__describe__").sort(([a], [b]) => a.localeCompare(b));
|
|
2644
|
+
const cards = sortedMethods.map(([, method]) => buildMethodCard(method)).join(`
|
|
2645
|
+
`);
|
|
2646
|
+
const repoLink = repoUrl ? ` · <a href="${escapeHtml(repoUrl)}">Source</a>` : "";
|
|
2647
|
+
return `<!DOCTYPE html>
|
|
2648
|
+
<html lang="en">
|
|
2649
|
+
<head>
|
|
2650
|
+
<meta charset="utf-8">
|
|
2651
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2652
|
+
<title>${escapeHtml(protocolName)} API Reference — vgi-rpc</title>
|
|
2653
|
+
${FONTS}
|
|
2654
|
+
<style>
|
|
2655
|
+
body { font-family: 'Inter', system-ui, -apple-system, sans-serif; max-width: 900px;
|
|
2656
|
+
margin: 0 auto; padding: 40px 20px 0; color: #2c2c1e; background: #faf8f0; }
|
|
2657
|
+
.header { text-align: center; margin-bottom: 40px; }
|
|
2658
|
+
.header .logo img { width: 80px; height: 80px; border-radius: 50%;
|
|
2659
|
+
box-shadow: 0 3px 16px rgba(0,0,0,0.10); }
|
|
2660
|
+
.header h1 { margin-bottom: 4px; color: #2d5016; font-weight: 700; }
|
|
2661
|
+
.header .subtitle { color: #6b6b5a; font-size: 1.1em; margin-top: 0; }
|
|
2662
|
+
.header .meta { color: #6b6b5a; font-size: 0.9em; }
|
|
2663
|
+
.header .meta a { color: #2d5016; font-weight: 600; }
|
|
2664
|
+
.header .meta a:hover { color: #4a7c23; }
|
|
2665
|
+
code { font-family: 'JetBrains Mono', monospace; background: #f0ece0;
|
|
2666
|
+
padding: 2px 6px; border-radius: 3px; font-size: 0.85em; color: #2c2c1e; }
|
|
2667
|
+
a { color: #2d5016; text-decoration: none; }
|
|
2668
|
+
a:hover { color: #4a7c23; }
|
|
2669
|
+
.card { border: 1px solid #f0ece0; border-radius: 8px; padding: 20px;
|
|
2670
|
+
margin-bottom: 16px; background: #fff; }
|
|
2671
|
+
.card:hover { border-color: #c8a43a; }
|
|
2672
|
+
.card-header { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
|
|
2673
|
+
.method-name { font-family: 'JetBrains Mono', monospace; font-size: 1.1em; font-weight: 600;
|
|
2674
|
+
color: #2d5016; }
|
|
2675
|
+
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px;
|
|
2676
|
+
font-size: 0.75em; font-weight: 600; text-transform: uppercase;
|
|
2677
|
+
letter-spacing: 0.03em; }
|
|
2678
|
+
.badge-unary { background: #e8f5e0; color: #2d5016; }
|
|
2679
|
+
.badge-stream { background: #e0ecf5; color: #1a4a6b; }
|
|
2680
|
+
.badge-exchange { background: #f5e6f0; color: #6b234a; }
|
|
2681
|
+
.badge-producer { background: #e0f0f5; color: #1a5a6b; }
|
|
2682
|
+
.badge-header { background: #f5eee0; color: #6b4423; }
|
|
2683
|
+
.docstring { color: #6b6b5a; font-size: 0.9em; margin-top: 0; }
|
|
2684
|
+
table { width: 100%; border-collapse: collapse; font-size: 0.9em; }
|
|
2685
|
+
th { text-align: left; padding: 8px 10px; background: #f0ece0; color: #2c2c1e;
|
|
2686
|
+
font-weight: 600; border-bottom: 2px solid #e0dcd0; }
|
|
2687
|
+
td { padding: 8px 10px; border-bottom: 1px solid #f0ece0; }
|
|
2688
|
+
td code { font-size: 0.85em; }
|
|
2689
|
+
.no-params { color: #6b6b5a; font-style: italic; font-size: 0.9em; }
|
|
2690
|
+
.section-label { font-size: 0.8em; font-weight: 600; text-transform: uppercase;
|
|
2691
|
+
letter-spacing: 0.05em; color: #6b6b5a; margin-top: 14px;
|
|
2692
|
+
margin-bottom: 6px; }
|
|
2693
|
+
footer { text-align: center; margin-top: 48px; padding: 20px 0;
|
|
2694
|
+
border-top: 1px solid #f0ece0; color: #6b6b5a; font-size: 0.85em; }
|
|
2695
|
+
footer a { color: #2d5016; font-weight: 600; }
|
|
2696
|
+
footer a:hover { color: #4a7c23; }
|
|
2697
|
+
</style>
|
|
2698
|
+
</head>
|
|
2699
|
+
<body>
|
|
2700
|
+
<div class="header">
|
|
2701
|
+
<div class="logo">
|
|
2702
|
+
<img src="${LOGO_URL}" alt="vgi-rpc logo">
|
|
2703
|
+
</div>
|
|
2704
|
+
<h1>${escapeHtml(protocolName)}</h1>
|
|
2705
|
+
<p class="subtitle">API Reference</p>
|
|
2706
|
+
<p class="meta">Powered by <code>vgi-rpc</code> (TypeScript) · server <code>${escapeHtml(serverId)}</code>${repoLink}</p>
|
|
2707
|
+
</div>
|
|
2708
|
+
${cards}
|
|
2709
|
+
<footer>
|
|
2710
|
+
<a href="https://vgi-rpc.query.farm">Learn more about <code>vgi-rpc</code></a>
|
|
2711
|
+
·
|
|
2712
|
+
© 2026 🚜 <a href="https://query.farm">Query.Farm LLC</a>
|
|
2713
|
+
</footer>
|
|
2714
|
+
</body>
|
|
2715
|
+
</html>`;
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2134
2718
|
// src/http/types.ts
|
|
2135
2719
|
var jsonStateSerializer = {
|
|
2136
2720
|
serialize(state) {
|
|
@@ -2144,7 +2728,7 @@ var jsonStateSerializer = {
|
|
|
2144
2728
|
// src/http/handler.ts
|
|
2145
2729
|
var EMPTY_SCHEMA2 = new Schema5([]);
|
|
2146
2730
|
function createHttpHandler(protocol, options) {
|
|
2147
|
-
const prefix = (options?.prefix ?? "
|
|
2731
|
+
const prefix = (options?.prefix ?? "").replace(/\/+$/, "");
|
|
2148
2732
|
const signingKey = options?.signingKey ?? randomBytes(32);
|
|
2149
2733
|
const tokenTtl = options?.tokenTtl ?? 3600;
|
|
2150
2734
|
const corsOrigins = options?.corsOrigins;
|
|
@@ -2156,12 +2740,23 @@ function createHttpHandler(protocol, options) {
|
|
|
2156
2740
|
const methods = protocol.getMethods();
|
|
2157
2741
|
const compressionLevel = options?.compressionLevel;
|
|
2158
2742
|
const stateSerializer = options?.stateSerializer ?? jsonStateSerializer;
|
|
2743
|
+
const dispatchHook = options?.dispatchHook;
|
|
2744
|
+
const enableLandingPage = options?.enableLandingPage ?? true;
|
|
2745
|
+
const enableDescribePage = options?.enableDescribePage ?? true;
|
|
2746
|
+
const enableNotFoundPage = options?.enableNotFoundPage ?? true;
|
|
2747
|
+
const displayName = options?.protocolName ?? protocol.name;
|
|
2748
|
+
const repoUrl = options?.repositoryUrl ?? null;
|
|
2749
|
+
const landingHtml = enableLandingPage ? buildLandingPage(displayName, serverId, enableDescribePage ? `${prefix}/describe` : null, repoUrl) : null;
|
|
2750
|
+
const describeHtml = enableDescribePage ? buildDescribePage(displayName, serverId, methods, repoUrl) : null;
|
|
2751
|
+
const notFoundHtml = enableNotFoundPage ? buildNotFoundPage(prefix, displayName) : null;
|
|
2752
|
+
const externalLocation = options?.externalLocation;
|
|
2159
2753
|
const baseCtx = {
|
|
2160
2754
|
signingKey,
|
|
2161
2755
|
tokenTtl,
|
|
2162
2756
|
serverId,
|
|
2163
2757
|
maxStreamResponseBytes,
|
|
2164
|
-
stateSerializer
|
|
2758
|
+
stateSerializer,
|
|
2759
|
+
externalLocation
|
|
2165
2760
|
};
|
|
2166
2761
|
function addCorsHeaders(headers) {
|
|
2167
2762
|
if (corsOrigins) {
|
|
@@ -2199,7 +2794,7 @@ function createHttpHandler(protocol, options) {
|
|
|
2199
2794
|
const body2 = JSON.stringify(oauthResourceMetadataToJson(oauthMetadata));
|
|
2200
2795
|
const headers = new Headers({
|
|
2201
2796
|
"Content-Type": "application/json",
|
|
2202
|
-
"Cache-Control": "public, max-age=
|
|
2797
|
+
"Cache-Control": "public, max-age=60"
|
|
2203
2798
|
});
|
|
2204
2799
|
addCorsHeaders(headers);
|
|
2205
2800
|
return new Response(body2, { status: 200, headers });
|
|
@@ -2220,6 +2815,24 @@ function createHttpHandler(protocol, options) {
|
|
|
2220
2815
|
}
|
|
2221
2816
|
return new Response(null, { status: 405 });
|
|
2222
2817
|
}
|
|
2818
|
+
if (request.method === "GET") {
|
|
2819
|
+
if (landingHtml && (path === prefix || path === `${prefix}/`)) {
|
|
2820
|
+
const headers = new Headers({ "Content-Type": "text/html; charset=utf-8" });
|
|
2821
|
+
addCorsHeaders(headers);
|
|
2822
|
+
return new Response(landingHtml, { status: 200, headers });
|
|
2823
|
+
}
|
|
2824
|
+
if (describeHtml && path === `${prefix}/describe`) {
|
|
2825
|
+
const headers = new Headers({ "Content-Type": "text/html; charset=utf-8" });
|
|
2826
|
+
addCorsHeaders(headers);
|
|
2827
|
+
return new Response(describeHtml, { status: 200, headers });
|
|
2828
|
+
}
|
|
2829
|
+
if (notFoundHtml) {
|
|
2830
|
+
const headers = new Headers({ "Content-Type": "text/html; charset=utf-8" });
|
|
2831
|
+
addCorsHeaders(headers);
|
|
2832
|
+
return new Response(notFoundHtml, { status: 404, headers });
|
|
2833
|
+
}
|
|
2834
|
+
return new Response("Not Found", { status: 404 });
|
|
2835
|
+
}
|
|
2223
2836
|
if (request.method !== "POST") {
|
|
2224
2837
|
return new Response("Method Not Allowed", { status: 405 });
|
|
2225
2838
|
}
|
|
@@ -2250,7 +2863,7 @@ function createHttpHandler(protocol, options) {
|
|
|
2250
2863
|
const metadataUrl = new URL(request.url);
|
|
2251
2864
|
metadataUrl.pathname = wellKnownPath(prefix);
|
|
2252
2865
|
metadataUrl.search = "";
|
|
2253
|
-
headers.set("WWW-Authenticate", buildWwwAuthenticateHeader(metadataUrl.toString()));
|
|
2866
|
+
headers.set("WWW-Authenticate", buildWwwAuthenticateHeader(metadataUrl.toString(), oauthMetadata.clientId, oauthMetadata.clientSecret, oauthMetadata.useIdTokenAsBearer, oauthMetadata.deviceCodeClientId, oauthMetadata.deviceCodeClientSecret));
|
|
2254
2867
|
}
|
|
2255
2868
|
return new Response(error.message || "Unauthorized", { status: 401, headers });
|
|
2256
2869
|
}
|
|
@@ -2286,6 +2899,18 @@ function createHttpHandler(protocol, options) {
|
|
|
2286
2899
|
const err = new Error(`Unknown method: '${methodName}'. Available methods: [${available.join(", ")}]`);
|
|
2287
2900
|
return compressIfAccepted(makeErrorResponse(err, 404), clientAcceptsZstd);
|
|
2288
2901
|
}
|
|
2902
|
+
const methodType = method.type === "unary" /* UNARY */ ? "unary" : "stream";
|
|
2903
|
+
const info = { method: methodName, methodType, serverId, requestId: null };
|
|
2904
|
+
const stats = {
|
|
2905
|
+
inputBatches: 0,
|
|
2906
|
+
outputBatches: 0,
|
|
2907
|
+
inputRows: 0,
|
|
2908
|
+
outputRows: 0,
|
|
2909
|
+
inputBytes: 0,
|
|
2910
|
+
outputBytes: 0
|
|
2911
|
+
};
|
|
2912
|
+
const hookToken = dispatchHook?.onDispatchStart(info);
|
|
2913
|
+
let dispatchError;
|
|
2289
2914
|
try {
|
|
2290
2915
|
let response;
|
|
2291
2916
|
if (action === "call") {
|
|
@@ -2304,13 +2929,20 @@ function createHttpHandler(protocol, options) {
|
|
|
2304
2929
|
}
|
|
2305
2930
|
response = await httpDispatchStreamExchange(method, body, ctx);
|
|
2306
2931
|
}
|
|
2932
|
+
const internalError = response.__dispatchError;
|
|
2933
|
+
if (internalError) {
|
|
2934
|
+
dispatchError = internalError instanceof Error ? internalError : new Error(String(internalError));
|
|
2935
|
+
}
|
|
2307
2936
|
addCorsHeaders(response.headers);
|
|
2308
2937
|
return compressIfAccepted(response, clientAcceptsZstd);
|
|
2309
2938
|
} catch (error) {
|
|
2939
|
+
dispatchError = error instanceof Error ? error : new Error(String(error));
|
|
2310
2940
|
if (error instanceof HttpRpcError) {
|
|
2311
2941
|
return compressIfAccepted(makeErrorResponse(error, error.statusCode), clientAcceptsZstd);
|
|
2312
2942
|
}
|
|
2313
2943
|
return compressIfAccepted(makeErrorResponse(error, 500), clientAcceptsZstd);
|
|
2944
|
+
} finally {
|
|
2945
|
+
dispatchHook?.onDispatchEnd(hookToken, info, stats, dispatchError);
|
|
2314
2946
|
}
|
|
2315
2947
|
};
|
|
2316
2948
|
}
|
|
@@ -3503,11 +4135,220 @@ function jwtAuthenticate(options) {
|
|
|
3503
4135
|
asPromise = null;
|
|
3504
4136
|
throw error;
|
|
3505
4137
|
}
|
|
3506
|
-
const
|
|
4138
|
+
const audiences = Array.isArray(audience) ? audience : [audience];
|
|
4139
|
+
let claims;
|
|
4140
|
+
let lastError;
|
|
4141
|
+
for (const aud of audiences) {
|
|
4142
|
+
try {
|
|
4143
|
+
claims = await validateJwtAccessToken(as, request, aud);
|
|
4144
|
+
break;
|
|
4145
|
+
} catch (error) {
|
|
4146
|
+
lastError = error;
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
if (!claims) {
|
|
4150
|
+
throw lastError;
|
|
4151
|
+
}
|
|
3507
4152
|
const principal = claims[principalClaim] ?? null;
|
|
3508
4153
|
return new AuthContext(domain, true, principal, claims);
|
|
3509
4154
|
};
|
|
3510
4155
|
}
|
|
4156
|
+
// src/http/mtls.ts
|
|
4157
|
+
import { createHash, X509Certificate } from "node:crypto";
|
|
4158
|
+
function splitRespectingQuotes(text, delimiter) {
|
|
4159
|
+
const parts = [];
|
|
4160
|
+
let current = [];
|
|
4161
|
+
let inQuotes = false;
|
|
4162
|
+
let i = 0;
|
|
4163
|
+
while (i < text.length) {
|
|
4164
|
+
const ch = text[i];
|
|
4165
|
+
if (ch === '"') {
|
|
4166
|
+
inQuotes = !inQuotes;
|
|
4167
|
+
current.push(ch);
|
|
4168
|
+
} else if (ch === "\\" && inQuotes && i + 1 < text.length) {
|
|
4169
|
+
current.push(ch);
|
|
4170
|
+
current.push(text[i + 1]);
|
|
4171
|
+
i++;
|
|
4172
|
+
} else if (ch === delimiter && !inQuotes) {
|
|
4173
|
+
parts.push(current.join(""));
|
|
4174
|
+
current = [];
|
|
4175
|
+
} else {
|
|
4176
|
+
current.push(ch);
|
|
4177
|
+
}
|
|
4178
|
+
i++;
|
|
4179
|
+
}
|
|
4180
|
+
parts.push(current.join(""));
|
|
4181
|
+
return parts;
|
|
4182
|
+
}
|
|
4183
|
+
function unescapeQuoted(text) {
|
|
4184
|
+
return text.replace(/\\(.)/g, "$1");
|
|
4185
|
+
}
|
|
4186
|
+
function extractCn(subject) {
|
|
4187
|
+
for (const part of subject.split(/(?<!\\),/)) {
|
|
4188
|
+
const trimmed = part.trim();
|
|
4189
|
+
if (trimmed.toUpperCase().startsWith("CN=")) {
|
|
4190
|
+
return trimmed.slice(3);
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
return "";
|
|
4194
|
+
}
|
|
4195
|
+
function parseXfcc(headerValue) {
|
|
4196
|
+
const elements = [];
|
|
4197
|
+
for (const rawElement of splitRespectingQuotes(headerValue, ",")) {
|
|
4198
|
+
const trimmed = rawElement.trim();
|
|
4199
|
+
if (!trimmed)
|
|
4200
|
+
continue;
|
|
4201
|
+
const pairs = splitRespectingQuotes(trimmed, ";");
|
|
4202
|
+
const fields = {};
|
|
4203
|
+
for (const pair of pairs) {
|
|
4204
|
+
const p = pair.trim();
|
|
4205
|
+
if (!p)
|
|
4206
|
+
continue;
|
|
4207
|
+
const eqIdx = p.indexOf("=");
|
|
4208
|
+
if (eqIdx < 0)
|
|
4209
|
+
continue;
|
|
4210
|
+
const key = p.slice(0, eqIdx).trim().toLowerCase();
|
|
4211
|
+
let value = p.slice(eqIdx + 1).trim();
|
|
4212
|
+
if (value.length >= 2 && value[0] === '"' && value[value.length - 1] === '"') {
|
|
4213
|
+
value = unescapeQuoted(value.slice(1, -1));
|
|
4214
|
+
}
|
|
4215
|
+
if (key === "cert" || key === "uri" || key === "by") {
|
|
4216
|
+
value = decodeURIComponent(value);
|
|
4217
|
+
}
|
|
4218
|
+
if (key === "dns") {
|
|
4219
|
+
const existing = fields.dns;
|
|
4220
|
+
if (Array.isArray(existing)) {
|
|
4221
|
+
existing.push(value);
|
|
4222
|
+
} else {
|
|
4223
|
+
fields.dns = [value];
|
|
4224
|
+
}
|
|
4225
|
+
} else {
|
|
4226
|
+
fields[key] = value;
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
const dns = Array.isArray(fields.dns) ? fields.dns : [];
|
|
4230
|
+
elements.push({
|
|
4231
|
+
hash: typeof fields.hash === "string" ? fields.hash : null,
|
|
4232
|
+
cert: typeof fields.cert === "string" ? fields.cert : null,
|
|
4233
|
+
subject: typeof fields.subject === "string" ? fields.subject : null,
|
|
4234
|
+
uri: typeof fields.uri === "string" ? fields.uri : null,
|
|
4235
|
+
dns,
|
|
4236
|
+
by: typeof fields.by === "string" ? fields.by : null
|
|
4237
|
+
});
|
|
4238
|
+
}
|
|
4239
|
+
return elements;
|
|
4240
|
+
}
|
|
4241
|
+
function mtlsAuthenticateXfcc(options) {
|
|
4242
|
+
const validate = options?.validate;
|
|
4243
|
+
const domain = options?.domain ?? "mtls";
|
|
4244
|
+
const selectElement = options?.selectElement ?? "first";
|
|
4245
|
+
return async function authenticate(request) {
|
|
4246
|
+
const headerValue = request.headers.get("x-forwarded-client-cert");
|
|
4247
|
+
if (!headerValue) {
|
|
4248
|
+
throw new Error("Missing x-forwarded-client-cert header");
|
|
4249
|
+
}
|
|
4250
|
+
const elements = parseXfcc(headerValue);
|
|
4251
|
+
if (elements.length === 0) {
|
|
4252
|
+
throw new Error("Empty x-forwarded-client-cert header");
|
|
4253
|
+
}
|
|
4254
|
+
const element = selectElement === "first" ? elements[0] : elements[elements.length - 1];
|
|
4255
|
+
if (validate) {
|
|
4256
|
+
return validate(element);
|
|
4257
|
+
}
|
|
4258
|
+
const principal = element.subject ? extractCn(element.subject) : "";
|
|
4259
|
+
const claims = {};
|
|
4260
|
+
if (element.hash)
|
|
4261
|
+
claims.hash = element.hash;
|
|
4262
|
+
if (element.subject)
|
|
4263
|
+
claims.subject = element.subject;
|
|
4264
|
+
if (element.uri)
|
|
4265
|
+
claims.uri = element.uri;
|
|
4266
|
+
if (element.dns.length > 0)
|
|
4267
|
+
claims.dns = [...element.dns];
|
|
4268
|
+
if (element.by)
|
|
4269
|
+
claims.by = element.by;
|
|
4270
|
+
return new AuthContext(domain, true, principal, claims);
|
|
4271
|
+
};
|
|
4272
|
+
}
|
|
4273
|
+
function parseCertFromHeader(request, header) {
|
|
4274
|
+
const raw = request.headers.get(header);
|
|
4275
|
+
if (!raw) {
|
|
4276
|
+
throw new Error(`Missing ${header} header`);
|
|
4277
|
+
}
|
|
4278
|
+
const pemStr = decodeURIComponent(raw);
|
|
4279
|
+
if (!pemStr.startsWith("-----BEGIN CERTIFICATE-----")) {
|
|
4280
|
+
throw new Error("Header value is not a PEM certificate");
|
|
4281
|
+
}
|
|
4282
|
+
try {
|
|
4283
|
+
return new X509Certificate(pemStr);
|
|
4284
|
+
} catch (exc) {
|
|
4285
|
+
throw new Error(`Failed to parse PEM certificate: ${exc}`);
|
|
4286
|
+
}
|
|
4287
|
+
}
|
|
4288
|
+
function checkCertExpiry(cert) {
|
|
4289
|
+
const now = new Date;
|
|
4290
|
+
const notBefore = new Date(cert.validFrom);
|
|
4291
|
+
const notAfter = new Date(cert.validTo);
|
|
4292
|
+
if (now < notBefore) {
|
|
4293
|
+
throw new Error("Certificate is not yet valid");
|
|
4294
|
+
}
|
|
4295
|
+
if (now > notAfter) {
|
|
4296
|
+
throw new Error("Certificate has expired");
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
function mtlsAuthenticate(options) {
|
|
4300
|
+
const { validate, header = "X-SSL-Client-Cert", checkExpiry = false } = options;
|
|
4301
|
+
return async function authenticate(request) {
|
|
4302
|
+
const cert = parseCertFromHeader(request, header);
|
|
4303
|
+
if (checkExpiry) {
|
|
4304
|
+
checkCertExpiry(cert);
|
|
4305
|
+
}
|
|
4306
|
+
return validate(cert);
|
|
4307
|
+
};
|
|
4308
|
+
}
|
|
4309
|
+
var SUPPORTED_ALGORITHMS = new Set(["sha256", "sha1", "sha384", "sha512"]);
|
|
4310
|
+
function mtlsAuthenticateFingerprint(options) {
|
|
4311
|
+
const { fingerprints, header, algorithm = "sha256", checkExpiry } = options;
|
|
4312
|
+
if (!SUPPORTED_ALGORITHMS.has(algorithm)) {
|
|
4313
|
+
throw new Error(`Unsupported hash algorithm: ${algorithm}`);
|
|
4314
|
+
}
|
|
4315
|
+
const entries = fingerprints instanceof Map ? fingerprints : new Map(Object.entries(fingerprints));
|
|
4316
|
+
function validate(cert) {
|
|
4317
|
+
const fp = createHash(algorithm).update(cert.raw).digest("hex");
|
|
4318
|
+
const ctx = entries.get(fp);
|
|
4319
|
+
if (!ctx) {
|
|
4320
|
+
throw new Error(`Unknown certificate fingerprint: ${fp}`);
|
|
4321
|
+
}
|
|
4322
|
+
return ctx;
|
|
4323
|
+
}
|
|
4324
|
+
return mtlsAuthenticate({ validate, header, checkExpiry });
|
|
4325
|
+
}
|
|
4326
|
+
function mtlsAuthenticateSubject(options) {
|
|
4327
|
+
const { header, domain = "mtls", allowedSubjects = null, checkExpiry } = options ?? {};
|
|
4328
|
+
function validate(cert) {
|
|
4329
|
+
const subjectParts = cert.subject.split(`
|
|
4330
|
+
`).map((s) => s.trim()).filter(Boolean);
|
|
4331
|
+
const subjectDn = subjectParts.join(", ");
|
|
4332
|
+
let cn = "";
|
|
4333
|
+
for (const part of subjectParts) {
|
|
4334
|
+
if (part.toUpperCase().startsWith("CN=")) {
|
|
4335
|
+
cn = part.slice(3);
|
|
4336
|
+
break;
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
if (allowedSubjects !== null && !allowedSubjects.has(cn)) {
|
|
4340
|
+
throw new Error(`Subject CN '${cn}' not in allowed subjects`);
|
|
4341
|
+
}
|
|
4342
|
+
const serialHex = BigInt(`0x${cert.serialNumber}`).toString(16);
|
|
4343
|
+
const notValidAfter = new Date(cert.validTo).toISOString();
|
|
4344
|
+
return new AuthContext(domain, true, cn, {
|
|
4345
|
+
subject_dn: subjectDn,
|
|
4346
|
+
serial: serialHex,
|
|
4347
|
+
not_valid_after: notValidAfter
|
|
4348
|
+
});
|
|
4349
|
+
}
|
|
4350
|
+
return mtlsAuthenticate({ validate, header, checkExpiry });
|
|
4351
|
+
}
|
|
3511
4352
|
// src/protocol.ts
|
|
3512
4353
|
import { Schema as Schema7 } from "@query-farm/apache-arrow";
|
|
3513
4354
|
|
|
@@ -3648,7 +4489,7 @@ import { Schema as Schema9 } from "@query-farm/apache-arrow";
|
|
|
3648
4489
|
// src/dispatch/stream.ts
|
|
3649
4490
|
import { Schema as Schema8 } from "@query-farm/apache-arrow";
|
|
3650
4491
|
var EMPTY_SCHEMA4 = new Schema8([]);
|
|
3651
|
-
async function dispatchStream(method, params, writer, reader, serverId, requestId) {
|
|
4492
|
+
async function dispatchStream(method, params, writer, reader, serverId, requestId, externalConfig) {
|
|
3652
4493
|
const isProducer = !!method.producerFn;
|
|
3653
4494
|
let state;
|
|
3654
4495
|
try {
|
|
@@ -3716,7 +4557,11 @@ async function dispatchStream(method, params, writer, reader, serverId, requestI
|
|
|
3716
4557
|
await method.exchangeFn(state, inputBatch, out);
|
|
3717
4558
|
}
|
|
3718
4559
|
for (const emitted of out.batches) {
|
|
3719
|
-
|
|
4560
|
+
let batch = emitted.batch;
|
|
4561
|
+
if (externalConfig) {
|
|
4562
|
+
batch = await maybeExternalizeBatch(batch, externalConfig);
|
|
4563
|
+
}
|
|
4564
|
+
stream.write(batch);
|
|
3720
4565
|
}
|
|
3721
4566
|
if (out.finished) {
|
|
3722
4567
|
break;
|
|
@@ -3732,12 +4577,15 @@ async function dispatchStream(method, params, writer, reader, serverId, requestI
|
|
|
3732
4577
|
}
|
|
3733
4578
|
|
|
3734
4579
|
// src/dispatch/unary.ts
|
|
3735
|
-
async function dispatchUnary(method, params, writer, serverId, requestId) {
|
|
4580
|
+
async function dispatchUnary(method, params, writer, serverId, requestId, externalConfig) {
|
|
3736
4581
|
const schema = method.resultSchema;
|
|
3737
4582
|
const out = new OutputCollector(schema, true, serverId, requestId);
|
|
3738
4583
|
try {
|
|
3739
4584
|
const result = await method.handler(params, out);
|
|
3740
|
-
|
|
4585
|
+
let resultBatch = buildResultBatch(schema, result, serverId, requestId);
|
|
4586
|
+
if (externalConfig) {
|
|
4587
|
+
resultBatch = await maybeExternalizeBatch(resultBatch, externalConfig);
|
|
4588
|
+
}
|
|
3741
4589
|
const batches = [...out.batches.map((b) => b.batch), resultBatch];
|
|
3742
4590
|
writer.writeStream(schema, batches);
|
|
3743
4591
|
} catch (error) {
|
|
@@ -3748,7 +4596,7 @@ async function dispatchUnary(method, params, writer, serverId, requestId) {
|
|
|
3748
4596
|
|
|
3749
4597
|
// src/wire/writer.ts
|
|
3750
4598
|
import { writeSync } from "node:fs";
|
|
3751
|
-
import { RecordBatchStreamWriter as
|
|
4599
|
+
import { RecordBatchStreamWriter as RecordBatchStreamWriter5 } from "@query-farm/apache-arrow";
|
|
3752
4600
|
var STDOUT_FD = 1;
|
|
3753
4601
|
function writeAll(fd, data) {
|
|
3754
4602
|
let offset = 0;
|
|
@@ -3774,7 +4622,7 @@ class IpcStreamWriter {
|
|
|
3774
4622
|
this.fd = fd;
|
|
3775
4623
|
}
|
|
3776
4624
|
writeStream(schema, batches) {
|
|
3777
|
-
const writer = new
|
|
4625
|
+
const writer = new RecordBatchStreamWriter5;
|
|
3778
4626
|
writer.reset(undefined, schema);
|
|
3779
4627
|
for (const batch of batches) {
|
|
3780
4628
|
writer._writeRecordBatch(batch);
|
|
@@ -3794,7 +4642,7 @@ class IncrementalStream {
|
|
|
3794
4642
|
closed = false;
|
|
3795
4643
|
constructor(fd, schema) {
|
|
3796
4644
|
this.fd = fd;
|
|
3797
|
-
this.writer = new
|
|
4645
|
+
this.writer = new RecordBatchStreamWriter5;
|
|
3798
4646
|
this.writer.reset(undefined, schema);
|
|
3799
4647
|
this.drain();
|
|
3800
4648
|
}
|
|
@@ -3828,10 +4676,14 @@ class VgiRpcServer {
|
|
|
3828
4676
|
enableDescribe;
|
|
3829
4677
|
serverId;
|
|
3830
4678
|
describeBatch = null;
|
|
4679
|
+
dispatchHook = null;
|
|
4680
|
+
externalConfig;
|
|
3831
4681
|
constructor(protocol, options) {
|
|
3832
4682
|
this.protocol = protocol;
|
|
3833
4683
|
this.enableDescribe = options?.enableDescribe ?? true;
|
|
3834
4684
|
this.serverId = options?.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
4685
|
+
this.dispatchHook = options?.dispatchHook ?? null;
|
|
4686
|
+
this.externalConfig = options?.externalLocation;
|
|
3835
4687
|
if (this.enableDescribe) {
|
|
3836
4688
|
const { batch } = buildDescribeBatch(protocol.name, protocol.getMethods(), this.serverId);
|
|
3837
4689
|
this.describeBatch = batch;
|
|
@@ -3901,10 +4753,29 @@ class VgiRpcServer {
|
|
|
3901
4753
|
writer.writeStream(EMPTY_SCHEMA5, [errBatch]);
|
|
3902
4754
|
return;
|
|
3903
4755
|
}
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
4756
|
+
const methodType = method.type === "unary" /* UNARY */ ? "unary" : "stream";
|
|
4757
|
+
const info = { method: methodName, methodType, serverId: this.serverId, requestId };
|
|
4758
|
+
const stats = {
|
|
4759
|
+
inputBatches: 0,
|
|
4760
|
+
outputBatches: 0,
|
|
4761
|
+
inputRows: 0,
|
|
4762
|
+
outputRows: 0,
|
|
4763
|
+
inputBytes: 0,
|
|
4764
|
+
outputBytes: 0
|
|
4765
|
+
};
|
|
4766
|
+
const token = this.dispatchHook?.onDispatchStart(info);
|
|
4767
|
+
let dispatchError;
|
|
4768
|
+
try {
|
|
4769
|
+
if (method.type === "unary" /* UNARY */) {
|
|
4770
|
+
await dispatchUnary(method, params, writer, this.serverId, requestId, this.externalConfig);
|
|
4771
|
+
} else {
|
|
4772
|
+
await dispatchStream(method, params, writer, reader, this.serverId, requestId, this.externalConfig);
|
|
4773
|
+
}
|
|
4774
|
+
} catch (e) {
|
|
4775
|
+
dispatchError = e instanceof Error ? e : new Error(String(e));
|
|
4776
|
+
throw e;
|
|
4777
|
+
} finally {
|
|
4778
|
+
this.dispatchHook?.onDispatchEnd(token, info, stats, dispatchError);
|
|
3908
4779
|
}
|
|
3909
4780
|
}
|
|
3910
4781
|
}
|
|
@@ -3913,15 +4784,30 @@ export {
|
|
|
3913
4784
|
toSchema,
|
|
3914
4785
|
subprocessConnect,
|
|
3915
4786
|
str,
|
|
4787
|
+
resolveExternalLocation,
|
|
3916
4788
|
pipeConnect,
|
|
4789
|
+
parseXfcc,
|
|
4790
|
+
parseUseIdTokenAsBearer,
|
|
3917
4791
|
parseResourceMetadataUrl,
|
|
4792
|
+
parseDeviceCodeClientSecret,
|
|
4793
|
+
parseDeviceCodeClientId,
|
|
3918
4794
|
parseDescribeResponse,
|
|
4795
|
+
parseClientSecret,
|
|
4796
|
+
parseClientId,
|
|
3919
4797
|
oauthResourceMetadataToJson,
|
|
4798
|
+
mtlsAuthenticateXfcc,
|
|
4799
|
+
mtlsAuthenticateSubject,
|
|
4800
|
+
mtlsAuthenticateFingerprint,
|
|
4801
|
+
mtlsAuthenticate,
|
|
4802
|
+
maybeExternalizeBatch,
|
|
4803
|
+
makeExternalLocationBatch,
|
|
3920
4804
|
jwtAuthenticate,
|
|
3921
4805
|
jsonStateSerializer,
|
|
4806
|
+
isExternalLocationBatch,
|
|
3922
4807
|
int32,
|
|
3923
4808
|
int,
|
|
3924
4809
|
inferParamTypes,
|
|
4810
|
+
httpsOnlyValidator,
|
|
3925
4811
|
httpOAuthMetadata,
|
|
3926
4812
|
httpIntrospect,
|
|
3927
4813
|
httpConnect,
|
|
@@ -3929,8 +4815,11 @@ export {
|
|
|
3929
4815
|
float,
|
|
3930
4816
|
fetchOAuthMetadata,
|
|
3931
4817
|
createHttpHandler,
|
|
4818
|
+
chainAuthenticate,
|
|
3932
4819
|
bytes,
|
|
3933
4820
|
bool,
|
|
4821
|
+
bearerAuthenticateStatic,
|
|
4822
|
+
bearerAuthenticate,
|
|
3934
4823
|
VgiRpcServer,
|
|
3935
4824
|
VersionError,
|
|
3936
4825
|
STATE_KEY,
|
|
@@ -3956,4 +4845,4 @@ export {
|
|
|
3956
4845
|
ARROW_CONTENT_TYPE
|
|
3957
4846
|
};
|
|
3958
4847
|
|
|
3959
|
-
//# debugId=
|
|
4848
|
+
//# debugId=37CD76108224132A64756E2164756E21
|