@query-farm/vgi-rpc 0.6.4 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-log.d.ts +55 -0
- package/dist/access-log.d.ts.map +1 -0
- package/dist/arrow/impl-arrowjs/index.d.ts +96 -0
- package/dist/arrow/impl-arrowjs/index.d.ts.map +1 -0
- package/dist/arrow/impl-flechette/index.d.ts +102 -0
- package/dist/arrow/impl-flechette/index.d.ts.map +1 -0
- package/dist/arrow/impl-flechette/message-meta.d.ts +11 -0
- package/dist/arrow/impl-flechette/message-meta.d.ts.map +1 -0
- package/dist/arrow/index.d.ts +4 -0
- package/dist/arrow/index.d.ts.map +1 -0
- package/dist/arrow/predicates.d.ts +44 -0
- package/dist/arrow/predicates.d.ts.map +1 -0
- package/dist/arrow/types.d.ts +62 -0
- package/dist/arrow/types.d.ts.map +1 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/client/capabilities.d.ts +25 -0
- package/dist/client/capabilities.d.ts.map +1 -0
- package/dist/client/connect.d.ts +10 -0
- package/dist/client/connect.d.ts.map +1 -1
- package/dist/client/introspect.d.ts +21 -0
- package/dist/client/introspect.d.ts.map +1 -1
- package/dist/client/ipc.d.ts +8 -2
- package/dist/client/ipc.d.ts.map +1 -1
- package/dist/client/oauth.d.ts +9 -0
- package/dist/client/oauth.d.ts.map +1 -1
- package/dist/client/pipe.d.ts +24 -0
- package/dist/client/pipe.d.ts.map +1 -1
- package/dist/client/stream.d.ts +19 -2
- package/dist/client/stream.d.ts.map +1 -1
- package/dist/client/types.d.ts +23 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/uploadUrl.d.ts +25 -0
- package/dist/client/uploadUrl.d.ts.map +1 -0
- package/dist/constants.d.ts +30 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/crypto.d.ts +22 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/dispatch/describe.d.ts +10 -6
- package/dist/dispatch/describe.d.ts.map +1 -1
- package/dist/dispatch/stream.d.ts +2 -2
- package/dist/dispatch/stream.d.ts.map +1 -1
- package/dist/dispatch/unary.d.ts +2 -2
- package/dist/dispatch/unary.d.ts.map +1 -1
- package/dist/errors.d.ts +64 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/external.d.ts +27 -5
- package/dist/external.d.ts.map +1 -1
- package/dist/http/auth.d.ts +13 -0
- package/dist/http/auth.d.ts.map +1 -1
- package/dist/http/bearer.d.ts.map +1 -1
- package/dist/http/common.d.ts +43 -7
- package/dist/http/common.d.ts.map +1 -1
- package/dist/http/dispatch.d.ts +20 -2
- package/dist/http/dispatch.d.ts.map +1 -1
- package/dist/http/handler.d.ts.map +1 -1
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/jwt.d.ts +1 -0
- package/dist/http/jwt.d.ts.map +1 -1
- package/dist/http/mtls.d.ts +9 -1
- package/dist/http/mtls.d.ts.map +1 -1
- package/dist/http/oauth-pkce.d.ts +141 -0
- package/dist/http/oauth-pkce.d.ts.map +1 -0
- package/dist/http/pages.d.ts +3 -0
- package/dist/http/pages.d.ts.map +1 -1
- package/dist/http/sticky.d.ts +124 -0
- package/dist/http/sticky.d.ts.map +1 -0
- package/dist/http/token.d.ts +43 -12
- package/dist/http/token.d.ts.map +1 -1
- package/dist/http/types.d.ts +68 -5
- package/dist/http/types.d.ts.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1275 -3511
- package/dist/index.js.map +20 -38
- package/dist/launcher/hash.d.ts +22 -0
- package/dist/launcher/hash.d.ts.map +1 -0
- package/dist/launcher/index.d.ts +23 -0
- package/dist/launcher/index.d.ts.map +1 -0
- package/dist/launcher/launch.d.ts +27 -0
- package/dist/launcher/launch.d.ts.map +1 -0
- package/dist/launcher/lock.d.ts +19 -0
- package/dist/launcher/lock.d.ts.map +1 -0
- package/dist/launcher/serve-unix.d.ts +55 -0
- package/dist/launcher/serve-unix.d.ts.map +1 -0
- package/dist/launcher/state.d.ts +71 -0
- package/dist/launcher/state.d.ts.map +1 -0
- package/dist/otel.d.ts.map +1 -1
- package/dist/protocol.d.ts +19 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/schema.d.ts +45 -18
- package/dist/schema.d.ts.map +1 -1
- package/dist/server.d.ts +23 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/types.d.ts +270 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/util/gzip.d.ts +10 -0
- package/dist/util/gzip.d.ts.map +1 -0
- package/dist/util/schema.d.ts +3 -15
- package/dist/util/schema.d.ts.map +1 -1
- package/dist/util/web-crypto.d.ts +22 -0
- package/dist/util/web-crypto.d.ts.map +1 -0
- package/dist/util/zstd.d.ts +26 -3
- package/dist/util/zstd.d.ts.map +1 -1
- package/dist/wire/opaque.d.ts +11 -0
- package/dist/wire/opaque.d.ts.map +1 -0
- package/dist/wire/reader.d.ts +5 -5
- package/dist/wire/reader.d.ts.map +1 -1
- package/dist/wire/request.d.ts +11 -3
- package/dist/wire/request.d.ts.map +1 -1
- package/dist/wire/response.d.ts +6 -6
- package/dist/wire/response.d.ts.map +1 -1
- package/dist/wire/writer.d.ts +49 -39
- package/dist/wire/writer.d.ts.map +1 -1
- package/package.json +35 -21
- package/src/access-log.ts +200 -0
- package/src/arrow/impl-arrowjs/index.ts +433 -0
- package/src/arrow/impl-flechette/index.ts +414 -0
- package/src/arrow/impl-flechette/message-meta.ts +174 -0
- package/src/arrow/index.ts +89 -0
- package/src/arrow/predicates.ts +56 -0
- package/src/arrow/types.ts +73 -0
- package/src/auth.ts +5 -0
- package/src/client/capabilities.ts +84 -0
- package/src/client/connect.ts +113 -26
- package/src/client/introspect.ts +74 -38
- package/src/client/ipc.ts +37 -27
- package/src/client/oauth.ts +9 -0
- package/src/client/pipe.ts +36 -9
- package/src/client/stream.ts +43 -20
- package/src/client/types.ts +23 -0
- package/src/client/uploadUrl.ts +169 -0
- package/src/constants.ts +34 -2
- package/src/crypto.ts +95 -0
- package/src/dispatch/describe.ts +146 -107
- package/src/dispatch/stream.ts +53 -24
- package/src/dispatch/unary.ts +5 -4
- package/src/errors.ts +87 -0
- package/src/external.ts +49 -30
- package/src/http/auth.ts +13 -0
- package/src/http/bearer.ts +2 -5
- package/src/http/common.ts +91 -23
- package/src/http/dispatch.ts +373 -46
- package/src/http/handler.ts +790 -68
- package/src/http/index.ts +1 -0
- package/src/http/jwt.ts +1 -0
- package/src/http/mtls.ts +25 -3
- package/src/http/oauth-pkce.ts +1035 -0
- package/src/http/pages.ts +30 -15
- package/src/http/sticky.ts +429 -0
- package/src/http/token.ts +170 -75
- package/src/http/types.ts +69 -5
- package/src/index.ts +40 -1
- package/src/launcher/hash.ts +104 -0
- package/src/launcher/index.ts +35 -0
- package/src/launcher/launch.ts +284 -0
- package/src/launcher/lock.ts +171 -0
- package/src/launcher/serve-unix.ts +386 -0
- package/src/launcher/state.ts +257 -0
- package/src/otel.ts +39 -33
- package/src/protocol.ts +30 -3
- package/src/schema.ts +107 -56
- package/src/server.ts +196 -20
- package/src/types.ts +376 -18
- package/src/util/gzip.ts +63 -0
- package/src/util/schema.ts +4 -22
- package/src/util/web-crypto.ts +98 -0
- package/src/util/zstd.ts +133 -14
- package/src/wire/opaque.ts +37 -0
- package/src/wire/reader.ts +5 -4
- package/src/wire/request.ts +67 -8
- package/src/wire/response.ts +51 -85
- package/src/wire/writer.ts +165 -69
- package/dist/util/conform.d.ts +0 -18
- package/dist/util/conform.d.ts.map +0 -1
- package/src/util/conform.ts +0 -94
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Backend-agnostic Arrow type predicates. typeId values match the Arrow Type
|
|
2
|
+
// enum and agree across arrow-js and flechette.
|
|
3
|
+
|
|
4
|
+
import type { VgiDataType } from "./types.js";
|
|
5
|
+
|
|
6
|
+
export const TypeId = {
|
|
7
|
+
Null: 1,
|
|
8
|
+
Int: 2,
|
|
9
|
+
Float: 3,
|
|
10
|
+
Binary: 4,
|
|
11
|
+
Utf8: 5,
|
|
12
|
+
Bool: 6,
|
|
13
|
+
Decimal: 7,
|
|
14
|
+
Date: 8,
|
|
15
|
+
Time: 9,
|
|
16
|
+
Timestamp: 10,
|
|
17
|
+
Interval: 11,
|
|
18
|
+
List: 12,
|
|
19
|
+
Struct: 13,
|
|
20
|
+
Union: 14,
|
|
21
|
+
FixedSizeBinary: 15,
|
|
22
|
+
FixedSizeList: 16,
|
|
23
|
+
Map: 17,
|
|
24
|
+
Duration: 18,
|
|
25
|
+
LargeBinary: 19,
|
|
26
|
+
LargeUtf8: 20,
|
|
27
|
+
Dictionary: -1,
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
export const isNull = (t: VgiDataType): boolean => t.typeId === TypeId.Null;
|
|
31
|
+
export const isInt = (t: VgiDataType): boolean => t.typeId === TypeId.Int;
|
|
32
|
+
export const isFloat = (t: VgiDataType): boolean => t.typeId === TypeId.Float;
|
|
33
|
+
export const isBinary = (t: VgiDataType): boolean => t.typeId === TypeId.Binary || t.typeId === TypeId.LargeBinary;
|
|
34
|
+
export const isUtf8 = (t: VgiDataType): boolean => t.typeId === TypeId.Utf8 || t.typeId === TypeId.LargeUtf8;
|
|
35
|
+
export const isLargeUtf8 = (t: VgiDataType): boolean => t.typeId === TypeId.LargeUtf8;
|
|
36
|
+
export const isLargeBinary = (t: VgiDataType): boolean => t.typeId === TypeId.LargeBinary;
|
|
37
|
+
export const isBool = (t: VgiDataType): boolean => t.typeId === TypeId.Bool;
|
|
38
|
+
export const isDecimal = (t: VgiDataType): boolean => t.typeId === TypeId.Decimal;
|
|
39
|
+
export const isDate = (t: VgiDataType): boolean => t.typeId === TypeId.Date;
|
|
40
|
+
export const isTime = (t: VgiDataType): boolean => t.typeId === TypeId.Time;
|
|
41
|
+
export const isTimestamp = (t: VgiDataType): boolean => t.typeId === TypeId.Timestamp;
|
|
42
|
+
export const isDuration = (t: VgiDataType): boolean => t.typeId === TypeId.Duration;
|
|
43
|
+
export const isList = (t: VgiDataType): boolean => t.typeId === TypeId.List;
|
|
44
|
+
export const isStruct = (t: VgiDataType): boolean => t.typeId === TypeId.Struct;
|
|
45
|
+
export const isMap = (t: VgiDataType): boolean => t.typeId === TypeId.Map;
|
|
46
|
+
export const isFixedSizeBinary = (t: VgiDataType): boolean => t.typeId === TypeId.FixedSizeBinary;
|
|
47
|
+
export const isDictionary = (t: VgiDataType): boolean => t.typeId === TypeId.Dictionary;
|
|
48
|
+
|
|
49
|
+
export function isBatch(x: unknown): x is import("./types.js").VgiBatch {
|
|
50
|
+
return (
|
|
51
|
+
x != null &&
|
|
52
|
+
typeof (x as any).numRows === "number" &&
|
|
53
|
+
(x as any).schema != null &&
|
|
54
|
+
Array.isArray((x as any).schema.fields)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Backend-agnostic Arrow type surface used inside vgi-rpc-typescript.
|
|
2
|
+
// Mirrors vgi-typescript's facade so structurally compatible values flow
|
|
3
|
+
// freely between the two packages.
|
|
4
|
+
|
|
5
|
+
export type VgiTypeId = number;
|
|
6
|
+
|
|
7
|
+
export interface VgiDataType {
|
|
8
|
+
readonly typeId: VgiTypeId;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface VgiField {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly type: VgiDataType;
|
|
14
|
+
readonly nullable: boolean;
|
|
15
|
+
readonly metadata: Map<string, string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface VgiSchema {
|
|
19
|
+
readonly fields: readonly VgiField[];
|
|
20
|
+
readonly metadata: Map<string, string>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface VgiColumn {
|
|
24
|
+
readonly type: VgiDataType;
|
|
25
|
+
readonly length: number;
|
|
26
|
+
get(index: number): unknown;
|
|
27
|
+
[Symbol.iterator](): Iterator<unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface VgiBatch {
|
|
31
|
+
readonly schema: VgiSchema;
|
|
32
|
+
readonly numRows: number;
|
|
33
|
+
readonly metadata?: Map<string, string> | null;
|
|
34
|
+
getChild(name: string): VgiColumn | null;
|
|
35
|
+
getChildAt(index: number): VgiColumn | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface VgiBackendInfo {
|
|
39
|
+
readonly name: "arrow-js" | "flechette";
|
|
40
|
+
/**
|
|
41
|
+
* Whether this backend's `.get(0)` round-trip is unreliable for opaque
|
|
42
|
+
* column types (Map/Date/Time/Timestamp/Duration/Decimal/LargeUtf8/
|
|
43
|
+
* LargeBinary/FixedSizeBinary/Dictionary), requiring the request parser to
|
|
44
|
+
* pass the raw column data straight through instead of materializing a
|
|
45
|
+
* value. True for arrow-js; false for flechette (which extracts cleanly).
|
|
46
|
+
*/
|
|
47
|
+
readonly opaquePassthrough: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type VgiColumnData = unknown;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Incremental IPC stream encoder for the lockstep stdio transport.
|
|
54
|
+
*
|
|
55
|
+
* The stdio exchange protocol is lockstep — the client reads each response
|
|
56
|
+
* batch (and its framing bytes) before sending the next input — so we
|
|
57
|
+
* cannot buffer-then-emit at close. Each call returns the wire bytes to
|
|
58
|
+
* flush immediately. Only the stdio server uses this; HTTP serializes
|
|
59
|
+
* whole responses via {@link serializeBatches}.
|
|
60
|
+
*
|
|
61
|
+
* The arrow-js backend implements this over `RecordBatchStreamWriter`.
|
|
62
|
+
* The flechette backend has no incremental-writer surface and its
|
|
63
|
+
* factory throws — keeping arrow-js out of the flechette (workerd/
|
|
64
|
+
* browser) bundle, which is HTTP-only anyway.
|
|
65
|
+
*/
|
|
66
|
+
export interface IncrementalEncoder {
|
|
67
|
+
/** Bytes for the schema preamble (continuation + schema message). */
|
|
68
|
+
start(): Uint8Array;
|
|
69
|
+
/** Bytes for one record batch message. */
|
|
70
|
+
writeBatch(batch: VgiBatch): Uint8Array;
|
|
71
|
+
/** Bytes for the end-of-stream marker. */
|
|
72
|
+
finish(): Uint8Array;
|
|
73
|
+
}
|
package/src/auth.ts
CHANGED
|
@@ -5,9 +5,14 @@ import { RpcError } from "./errors.js";
|
|
|
5
5
|
|
|
6
6
|
/** Authentication context available to RPC handlers. */
|
|
7
7
|
export class AuthContext {
|
|
8
|
+
/** Authentication domain/realm that vouched for the principal; empty string
|
|
9
|
+
* when anonymous. */
|
|
8
10
|
readonly domain: string;
|
|
11
|
+
/** True when the request carried valid credentials. */
|
|
9
12
|
readonly authenticated: boolean;
|
|
13
|
+
/** Authenticated principal identifier, or `null` when anonymous. */
|
|
10
14
|
readonly principal: string | null;
|
|
15
|
+
/** Arbitrary verified claims about the principal (e.g. decoded JWT claims). */
|
|
11
16
|
readonly claims: Record<string, any>;
|
|
12
17
|
|
|
13
18
|
constructor(domain: string, authenticated: boolean, principal: string | null, claims: Record<string, any> = {}) {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// © Copyright 2025-2026, Query.Farm LLC - https://query.farm
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* HTTP server capability discovery.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors Python's `http_capabilities()`: probes `OPTIONS {prefix}/health`
|
|
8
|
+
* and reads three response headers:
|
|
9
|
+
* - `VGI-Max-Request-Bytes` — server-enforced inline request cap
|
|
10
|
+
* - `VGI-Upload-URL-Support` — "true" when the server vends upload URLs
|
|
11
|
+
* - `VGI-Max-Upload-Bytes` — cap on out-of-band upload size
|
|
12
|
+
*
|
|
13
|
+
* Honours `Cache-Control: max-age=N` for refresh scheduling.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface HttpServerCapabilities {
|
|
17
|
+
/** Server's advertised max inline request body size (bytes). */
|
|
18
|
+
maxRequestBytes: number | null;
|
|
19
|
+
/** Whether the server vends upload URLs via `__upload_url__/init`. */
|
|
20
|
+
uploadUrlSupport: boolean;
|
|
21
|
+
/** Cap on the size of an externalized upload (bytes). */
|
|
22
|
+
maxUploadBytes: number | null;
|
|
23
|
+
/** Monotonic-time-ish epoch (ms) at which this snapshot should be re-probed. */
|
|
24
|
+
cacheExpiresAt: number | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const MAX_REQUEST_BYTES_HEADER = "VGI-Max-Request-Bytes";
|
|
28
|
+
const UPLOAD_URL_HEADER = "VGI-Upload-URL-Support";
|
|
29
|
+
const MAX_UPLOAD_BYTES_HEADER = "VGI-Max-Upload-Bytes";
|
|
30
|
+
|
|
31
|
+
function parseHeaderInt(headers: Headers, name: string): number | null {
|
|
32
|
+
const raw = headers.get(name) ?? headers.get(name.toLowerCase());
|
|
33
|
+
if (raw == null) return null;
|
|
34
|
+
const parsed = Number.parseInt(raw, 10);
|
|
35
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function parseCapabilitiesFromHeaders(headers: Headers): HttpServerCapabilities {
|
|
39
|
+
const uploadRaw = headers.get(UPLOAD_URL_HEADER) ?? headers.get(UPLOAD_URL_HEADER.toLowerCase());
|
|
40
|
+
const uploadUrlSupport = uploadRaw === "true";
|
|
41
|
+
|
|
42
|
+
let cacheExpiresAt: number | null = null;
|
|
43
|
+
const cc = headers.get("Cache-Control") ?? headers.get("cache-control");
|
|
44
|
+
if (cc) {
|
|
45
|
+
for (const token of cc.split(",")) {
|
|
46
|
+
const t = token.trim().toLowerCase();
|
|
47
|
+
if (t.startsWith("max-age=")) {
|
|
48
|
+
const seconds = Number.parseFloat(t.slice("max-age=".length));
|
|
49
|
+
if (Number.isFinite(seconds)) {
|
|
50
|
+
cacheExpiresAt = Date.now() + seconds * 1000;
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
maxRequestBytes: parseHeaderInt(headers, MAX_REQUEST_BYTES_HEADER),
|
|
59
|
+
uploadUrlSupport,
|
|
60
|
+
maxUploadBytes: parseHeaderInt(headers, MAX_UPLOAD_BYTES_HEADER),
|
|
61
|
+
cacheExpiresAt,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function discoverHttpCapabilities(
|
|
66
|
+
baseUrl: string,
|
|
67
|
+
prefix: string,
|
|
68
|
+
authorization?: string,
|
|
69
|
+
): Promise<HttpServerCapabilities> {
|
|
70
|
+
const headers: Record<string, string> = {};
|
|
71
|
+
if (authorization) headers.Authorization = authorization;
|
|
72
|
+
const resp = await fetch(`${baseUrl}${prefix}/health`, {
|
|
73
|
+
method: "OPTIONS",
|
|
74
|
+
headers,
|
|
75
|
+
});
|
|
76
|
+
// Capability headers are advertised on every response; we don't require 200.
|
|
77
|
+
return parseCapabilitiesFromHeaders(resp.headers);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function isCapabilitySnapshotFresh(snapshot: HttpServerCapabilities | null): boolean {
|
|
81
|
+
if (!snapshot) return false;
|
|
82
|
+
if (snapshot.cacheExpiresAt == null) return true;
|
|
83
|
+
return Date.now() < snapshot.cacheExpiresAt;
|
|
84
|
+
}
|
package/src/client/connect.ts
CHANGED
|
@@ -6,6 +6,11 @@ import { LOG_LEVEL_KEY, STATE_KEY } from "../constants.js";
|
|
|
6
6
|
import { RpcError } from "../errors.js";
|
|
7
7
|
import { isExternalLocationBatch, resolveExternalLocation } from "../external.js";
|
|
8
8
|
import { ARROW_CONTENT_TYPE } from "../http/common.js";
|
|
9
|
+
import {
|
|
10
|
+
type HttpServerCapabilities,
|
|
11
|
+
isCapabilitySnapshotFresh,
|
|
12
|
+
parseCapabilitiesFromHeaders,
|
|
13
|
+
} from "./capabilities.js";
|
|
9
14
|
import { httpIntrospect, type MethodInfo, type ServiceDescription } from "./introspect.js";
|
|
10
15
|
import {
|
|
11
16
|
buildRequestIpc,
|
|
@@ -16,17 +21,28 @@ import {
|
|
|
16
21
|
} from "./ipc.js";
|
|
17
22
|
import { HttpStreamSession } from "./stream.js";
|
|
18
23
|
import type { HttpConnectOptions, StreamSession } from "./types.js";
|
|
24
|
+
import { externalizeRequestBody } from "./uploadUrl.js";
|
|
19
25
|
|
|
20
|
-
type CompressFn = (data: Uint8Array, level: number) => Uint8Array
|
|
21
|
-
type DecompressFn = (data: Uint8Array) => Uint8Array
|
|
26
|
+
type CompressFn = (data: Uint8Array, level: number) => Promise<Uint8Array>;
|
|
27
|
+
type DecompressFn = (data: Uint8Array) => Promise<Uint8Array>;
|
|
22
28
|
|
|
29
|
+
/** A connected RPC client, returned by {@link httpConnect}, {@link pipeConnect}, and {@link subprocessConnect}. */
|
|
23
30
|
export interface RpcClient {
|
|
31
|
+
/** Invoke a unary method. Returns the single result row, or `null` for void methods. Parameter defaults from `__describe__` are applied automatically. */
|
|
24
32
|
call(method: string, params?: Record<string, any>): Promise<Record<string, any> | null>;
|
|
33
|
+
/** Open a streaming method, returning a {@link StreamSession} for exchange or producer iteration. */
|
|
25
34
|
stream(method: string, params?: Record<string, any>): Promise<StreamSession>;
|
|
35
|
+
/** Fetch the server's method/protocol description (cached after the first call). */
|
|
26
36
|
describe(): Promise<ServiceDescription>;
|
|
37
|
+
/** Release transport resources; for subprocess clients this also terminates the child process. */
|
|
27
38
|
close(): void;
|
|
28
39
|
}
|
|
29
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Connect to a vgi-rpc server over HTTP. The returned client lazily introspects
|
|
43
|
+
* the server (caching `__describe__`) on the first call and transparently handles
|
|
44
|
+
* zstd compression, authorization, and 413 request externalization.
|
|
45
|
+
*/
|
|
30
46
|
export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcClient {
|
|
31
47
|
const prefix = (options?.prefix ?? "").replace(/\/+$/, "");
|
|
32
48
|
const onLog = options?.onLog;
|
|
@@ -35,13 +51,73 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
|
|
|
35
51
|
const externalConfig = options?.externalLocation;
|
|
36
52
|
|
|
37
53
|
let methodCache: Map<string, MethodInfo> | null = null;
|
|
54
|
+
/** Application protocol surface version discovered via __describe__. When
|
|
55
|
+
* non-empty, the client emits it on every request as
|
|
56
|
+
* `vgi_rpc.protocol_version` so a versioned server can validate at the
|
|
57
|
+
* dispatch boundary. */
|
|
58
|
+
let serverProtocolVersion = "";
|
|
38
59
|
let compressFn: CompressFn | undefined;
|
|
39
60
|
let decompressFn: DecompressFn | undefined;
|
|
40
61
|
let compressionLoaded = false;
|
|
62
|
+
let capabilities: HttpServerCapabilities | null = null;
|
|
63
|
+
|
|
64
|
+
function updateCapabilitiesFromResponse(resp: Response): void {
|
|
65
|
+
const next = parseCapabilitiesFromHeaders(resp.headers);
|
|
66
|
+
// Only treat the snapshot as authoritative when the server actually
|
|
67
|
+
// emitted capability hints. Otherwise leave any prior cache in place.
|
|
68
|
+
if (next.maxRequestBytes != null || next.uploadUrlSupport) {
|
|
69
|
+
capabilities = next;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function maybeExternalize(body: Uint8Array): Promise<Uint8Array> {
|
|
74
|
+
const caps = isCapabilitySnapshotFresh(capabilities) ? capabilities : null;
|
|
75
|
+
if (!caps) return body;
|
|
76
|
+
if (!caps.uploadUrlSupport) return body;
|
|
77
|
+
if (caps.maxRequestBytes == null || body.byteLength <= caps.maxRequestBytes) return body;
|
|
78
|
+
return externalizeRequestBody(body, {
|
|
79
|
+
baseUrl,
|
|
80
|
+
prefix,
|
|
81
|
+
authorization,
|
|
82
|
+
urlValidator: externalConfig?.urlValidator ?? null,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Send a POST request, transparently retrying with externalization if
|
|
88
|
+
* the server returns 413 (Payload Too Large) and advertises upload-URL
|
|
89
|
+
* support. Mirrors Python's 413 fallback in `_HttpProxy._post_with_externalization`.
|
|
90
|
+
*/
|
|
91
|
+
async function postWithExternalization(url: string, body: Uint8Array): Promise<Response> {
|
|
92
|
+
const sendBody = await maybeExternalize(body);
|
|
93
|
+
let resp = await fetch(url, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: buildHeaders(),
|
|
96
|
+
body: (await prepareBody(sendBody)) as unknown as BodyInit,
|
|
97
|
+
});
|
|
98
|
+
updateCapabilitiesFromResponse(resp);
|
|
99
|
+
|
|
100
|
+
if (resp.status === 413 && capabilities?.uploadUrlSupport && body.byteLength > 0) {
|
|
101
|
+
// Refresh-and-retry: caps tell us we can externalize.
|
|
102
|
+
const externalized = await externalizeRequestBody(body, {
|
|
103
|
+
baseUrl,
|
|
104
|
+
prefix,
|
|
105
|
+
authorization,
|
|
106
|
+
urlValidator: externalConfig?.urlValidator ?? null,
|
|
107
|
+
});
|
|
108
|
+
resp = await fetch(url, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: buildHeaders(),
|
|
111
|
+
body: (await prepareBody(externalized)) as unknown as BodyInit,
|
|
112
|
+
});
|
|
113
|
+
updateCapabilitiesFromResponse(resp);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return resp;
|
|
117
|
+
}
|
|
41
118
|
|
|
42
119
|
async function ensureCompression(): Promise<void> {
|
|
43
120
|
if (compressionLoaded || compressionLevel == null) return;
|
|
44
|
-
compressionLoaded = true;
|
|
45
121
|
try {
|
|
46
122
|
const mod = await import("../util/zstd.js");
|
|
47
123
|
compressFn = mod.zstdCompress;
|
|
@@ -49,14 +125,17 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
|
|
|
49
125
|
} catch {
|
|
50
126
|
// zstd not available in this runtime
|
|
51
127
|
}
|
|
128
|
+
compressionLoaded = true;
|
|
52
129
|
}
|
|
53
130
|
|
|
54
131
|
function buildHeaders(): Record<string, string> {
|
|
55
132
|
const headers: Record<string, string> = {
|
|
56
133
|
"Content-Type": ARROW_CONTENT_TYPE,
|
|
57
134
|
};
|
|
58
|
-
if (compressionLevel != null) {
|
|
135
|
+
if (compressionLevel != null && compressFn) {
|
|
59
136
|
headers["Content-Encoding"] = "zstd";
|
|
137
|
+
}
|
|
138
|
+
if (compressionLevel != null && decompressFn) {
|
|
60
139
|
headers["Accept-Encoding"] = "zstd";
|
|
61
140
|
}
|
|
62
141
|
if (authorization) {
|
|
@@ -65,9 +144,9 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
|
|
|
65
144
|
return headers;
|
|
66
145
|
}
|
|
67
146
|
|
|
68
|
-
function prepareBody(content: Uint8Array): Uint8Array {
|
|
147
|
+
async function prepareBody(content: Uint8Array): Promise<Uint8Array> {
|
|
69
148
|
if (compressionLevel != null && compressFn) {
|
|
70
|
-
return compressFn(content, compressionLevel);
|
|
149
|
+
return await compressFn(content, compressionLevel);
|
|
71
150
|
}
|
|
72
151
|
return content;
|
|
73
152
|
}
|
|
@@ -81,15 +160,23 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
|
|
|
81
160
|
async function readResponse(resp: Response): Promise<Uint8Array<ArrayBuffer>> {
|
|
82
161
|
let body = new Uint8Array(await resp.arrayBuffer());
|
|
83
162
|
if (resp.headers.get("Content-Encoding") === "zstd" && decompressFn) {
|
|
84
|
-
body = new Uint8Array(decompressFn(body));
|
|
163
|
+
body = new Uint8Array(await decompressFn(body));
|
|
85
164
|
}
|
|
86
165
|
return body;
|
|
87
166
|
}
|
|
88
167
|
|
|
89
168
|
async function ensureMethodCache(): Promise<Map<string, MethodInfo>> {
|
|
90
169
|
if (methodCache) return methodCache;
|
|
91
|
-
|
|
170
|
+
await ensureCompression();
|
|
171
|
+
const desc = await httpIntrospect(baseUrl, {
|
|
172
|
+
prefix,
|
|
173
|
+
authorization,
|
|
174
|
+
compressionLevel,
|
|
175
|
+
compressFn,
|
|
176
|
+
decompressFn,
|
|
177
|
+
});
|
|
92
178
|
methodCache = new Map(desc.methods.map((m) => [m.name, m]));
|
|
179
|
+
serverProtocolVersion = desc.protocolVersion;
|
|
93
180
|
return methodCache;
|
|
94
181
|
}
|
|
95
182
|
|
|
@@ -105,12 +192,8 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
|
|
|
105
192
|
// Apply defaults
|
|
106
193
|
const fullParams = { ...(info.defaults ?? {}), ...(params ?? {}) };
|
|
107
194
|
|
|
108
|
-
const body = buildRequestIpc(info.paramsSchema, fullParams, method);
|
|
109
|
-
const resp = await
|
|
110
|
-
method: "POST",
|
|
111
|
-
headers: buildHeaders(),
|
|
112
|
-
body: prepareBody(body) as unknown as BodyInit,
|
|
113
|
-
});
|
|
195
|
+
const body = buildRequestIpc(info.paramsSchema, fullParams, method, { protocolVersion: serverProtocolVersion });
|
|
196
|
+
const resp = await postWithExternalization(`${baseUrl}${prefix}/${method}`, body);
|
|
114
197
|
checkAuth(resp);
|
|
115
198
|
|
|
116
199
|
const responseBody = await readResponse(resp);
|
|
@@ -121,8 +204,8 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
|
|
|
121
204
|
for (let batch of batches) {
|
|
122
205
|
if (batch.numRows === 0) {
|
|
123
206
|
// Check for external location pointer batch
|
|
124
|
-
if (isExternalLocationBatch(batch)) {
|
|
125
|
-
batch = await resolveExternalLocation(batch, externalConfig);
|
|
207
|
+
if (isExternalLocationBatch(batch as any)) {
|
|
208
|
+
batch = (await resolveExternalLocation(batch as any, externalConfig)) as any;
|
|
126
209
|
} else {
|
|
127
210
|
dispatchLogOrError(batch, onLog);
|
|
128
211
|
continue;
|
|
@@ -159,12 +242,8 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
|
|
|
159
242
|
// Apply defaults
|
|
160
243
|
const fullParams = { ...(info.defaults ?? {}), ...(params ?? {}) };
|
|
161
244
|
|
|
162
|
-
const body = buildRequestIpc(info.paramsSchema, fullParams, method);
|
|
163
|
-
const resp = await
|
|
164
|
-
method: "POST",
|
|
165
|
-
headers: buildHeaders(),
|
|
166
|
-
body: prepareBody(body) as unknown as BodyInit,
|
|
167
|
-
});
|
|
245
|
+
const body = buildRequestIpc(info.paramsSchema, fullParams, method, { protocolVersion: serverProtocolVersion });
|
|
246
|
+
const resp = await postWithExternalization(`${baseUrl}${prefix}/${method}/init`, body);
|
|
168
247
|
checkAuth(resp);
|
|
169
248
|
|
|
170
249
|
const responseBody = await readResponse(resp);
|
|
@@ -185,7 +264,7 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
|
|
|
185
264
|
// First stream: header
|
|
186
265
|
const headerStream = await reader.readStream();
|
|
187
266
|
if (headerStream) {
|
|
188
|
-
for (const batch of headerStream.batches) {
|
|
267
|
+
for (const batch of headerStream.batches as any[]) {
|
|
189
268
|
if (batch.numRows === 0) {
|
|
190
269
|
dispatchLogOrError(batch, onLog);
|
|
191
270
|
continue;
|
|
@@ -200,11 +279,11 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
|
|
|
200
279
|
// Second stream: data/state
|
|
201
280
|
const dataStream = await reader.readStream();
|
|
202
281
|
if (dataStream) {
|
|
203
|
-
streamSchema = dataStream.schema;
|
|
282
|
+
streamSchema = dataStream.schema as any;
|
|
204
283
|
}
|
|
205
284
|
const headerErrorBatches: RecordBatch[] = [];
|
|
206
285
|
if (dataStream) {
|
|
207
|
-
for (const batch of dataStream.batches) {
|
|
286
|
+
for (const batch of dataStream.batches as any[]) {
|
|
208
287
|
if (batch.numRows === 0) {
|
|
209
288
|
// Check for state token
|
|
210
289
|
const token = batch.metadata?.get(STATE_KEY);
|
|
@@ -310,11 +389,19 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
|
|
|
310
389
|
decompressFn,
|
|
311
390
|
authorization,
|
|
312
391
|
externalConfig,
|
|
392
|
+
postFn: postWithExternalization,
|
|
313
393
|
});
|
|
314
394
|
},
|
|
315
395
|
|
|
316
396
|
async describe(): Promise<ServiceDescription> {
|
|
317
|
-
|
|
397
|
+
await ensureCompression();
|
|
398
|
+
return httpIntrospect(baseUrl, {
|
|
399
|
+
prefix,
|
|
400
|
+
authorization,
|
|
401
|
+
compressionLevel,
|
|
402
|
+
compressFn,
|
|
403
|
+
decompressFn,
|
|
404
|
+
});
|
|
318
405
|
},
|
|
319
406
|
|
|
320
407
|
close(): void {
|
package/src/client/introspect.ts
CHANGED
|
@@ -1,36 +1,66 @@
|
|
|
1
1
|
// © Copyright 2025-2026, Query.Farm LLC - https://query.farm
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import { Schema as ArrowSchema, type RecordBatch,
|
|
5
|
-
import {
|
|
4
|
+
import { Schema as ArrowSchema, type RecordBatch, type Schema } from "@query-farm/apache-arrow";
|
|
5
|
+
import { deserializeSchema as deserializeSchemaImpl } from "#vgi-rpc-arrow";
|
|
6
|
+
import { DESCRIBE_METHOD_NAME, PROTOCOL_NAME_KEY, PROTOCOL_VERSION_KEY } from "../constants.js";
|
|
6
7
|
import { RpcError } from "../errors.js";
|
|
7
8
|
import { ARROW_CONTENT_TYPE } from "../http/common.js";
|
|
8
9
|
import { buildRequestIpc, dispatchLogOrError, readResponseBatches } from "./ipc.js";
|
|
9
10
|
import type { LogMessage } from "./types.js";
|
|
10
11
|
|
|
12
|
+
/** Describes a single RPC method as reported by the server's `__describe__` response. */
|
|
11
13
|
export interface MethodInfo {
|
|
14
|
+
/** The method name as invoked by {@link RpcClient.call} / {@link RpcClient.stream}. */
|
|
12
15
|
name: string;
|
|
16
|
+
/** Whether the method is a single request/response (`unary`) or a streaming method (`stream`). */
|
|
13
17
|
type: "unary" | "stream";
|
|
18
|
+
/** Arrow schema of the call parameters. */
|
|
14
19
|
paramsSchema: Schema;
|
|
20
|
+
/** Arrow schema of a unary result; for stream methods this holds the per-batch output schema. */
|
|
15
21
|
resultSchema: Schema;
|
|
22
|
+
/** Arrow schema of the per-batch input rows for exchange streams, when available. */
|
|
16
23
|
inputSchema?: Schema;
|
|
24
|
+
/** Arrow schema of the per-batch output rows for stream methods, when available. */
|
|
17
25
|
outputSchema?: Schema;
|
|
26
|
+
/** Arrow schema of the stream's one-time header row, when the method declares one. */
|
|
18
27
|
headerSchema?: Schema;
|
|
28
|
+
/** Human-readable documentation for the method, if the server provides it. */
|
|
19
29
|
doc?: string;
|
|
30
|
+
/** Per-parameter human-readable type names, if the server provides them. */
|
|
20
31
|
paramTypes?: Record<string, string>;
|
|
32
|
+
/** Default values applied to omitted parameters before a call is sent. */
|
|
21
33
|
defaults?: Record<string, any>;
|
|
22
34
|
}
|
|
23
35
|
|
|
36
|
+
/** The full set of methods and protocol metadata reported by a server's `__describe__`. */
|
|
24
37
|
export interface ServiceDescription {
|
|
38
|
+
/** The server's declared protocol/service name. */
|
|
25
39
|
protocolName: string;
|
|
40
|
+
/** Application protocol surface version surfaced by the server's
|
|
41
|
+
* __describe__ response. Empty string when the server did not declare
|
|
42
|
+
* a `protocolVersion`. */
|
|
43
|
+
protocolVersion: string;
|
|
44
|
+
/** Every method the server exposes (excluding the built-in `__describe__`). */
|
|
26
45
|
methods: MethodInfo[];
|
|
27
46
|
}
|
|
28
47
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Deserialize a schema from IPC bytes (schema message + EOS).
|
|
50
|
+
*
|
|
51
|
+
* Must dispatch via `#vgi-rpc-arrow` so the resulting type instances are
|
|
52
|
+
* the same impl (apache-arrow / flechette) as the rest of the active
|
|
53
|
+
* backend. Using apache-arrow's `RecordBatchReader` directly here used to
|
|
54
|
+
* silently mix impls: in browser builds the backend is flechette, and a
|
|
55
|
+
* flechette builder receiving an apache-arrow `Binary` type defaults to
|
|
56
|
+
* the wrong offsets buffer (Uint8Array instead of Int32Array) and emits
|
|
57
|
+
* a 0-byte value where a populated binary column was expected. The
|
|
58
|
+
* downstream symptom is "Tried reading schema message, was null or
|
|
59
|
+
* length 0" from the server when it tries to open the (empty) binary
|
|
60
|
+
* column as a nested IPC stream. See test/client/ipc-cross-impl.test.ts.
|
|
61
|
+
*/
|
|
62
|
+
function deserializeSchema(bytes: Uint8Array): Schema {
|
|
63
|
+
return deserializeSchemaImpl(bytes) as unknown as Schema;
|
|
34
64
|
}
|
|
35
65
|
|
|
36
66
|
/**
|
|
@@ -58,45 +88,30 @@ export async function parseDescribeResponse(
|
|
|
58
88
|
// Extract metadata from batch
|
|
59
89
|
const meta = dataBatch.metadata;
|
|
60
90
|
const protocolName = meta?.get(PROTOCOL_NAME_KEY) ?? "";
|
|
91
|
+
const protocolVersion = meta?.get(PROTOCOL_VERSION_KEY) ?? "";
|
|
61
92
|
|
|
93
|
+
// Slim DESCRIBE_VERSION 4 wire format (see dispatch/describe.ts):
|
|
94
|
+
// 0:name 1:method_type 2:has_return 3:params_schema_ipc
|
|
95
|
+
// 4:result_schema_ipc 5:has_header 6:header_schema_ipc 7:is_exchange
|
|
62
96
|
const methods: MethodInfo[] = [];
|
|
63
97
|
for (let i = 0; i < dataBatch.numRows; i++) {
|
|
64
|
-
const name = dataBatch.getChildAt(0)!.get(i) as string;
|
|
65
|
-
const methodType = dataBatch.getChildAt(1)!.get(i) as string;
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
const hasHeader = dataBatch.getChildAt(8)!.get(i) as boolean; // has_header
|
|
73
|
-
const headerIpc = dataBatch.getChildAt(9)?.get(i) as Uint8Array | null; // header_schema_ipc
|
|
98
|
+
const name = dataBatch.getChildAt(0)!.get(i) as string;
|
|
99
|
+
const methodType = dataBatch.getChildAt(1)!.get(i) as string;
|
|
100
|
+
const _hasReturn = dataBatch.getChildAt(2)!.get(i) as boolean;
|
|
101
|
+
const paramsIpc = dataBatch.getChildAt(3)!.get(i) as Uint8Array;
|
|
102
|
+
const resultIpc = dataBatch.getChildAt(4)!.get(i) as Uint8Array;
|
|
103
|
+
const hasHeader = dataBatch.getChildAt(5)!.get(i) as boolean;
|
|
104
|
+
const headerIpc = dataBatch.getChildAt(6)?.get(i) as Uint8Array | null;
|
|
105
|
+
// is_exchange (index 7) currently unused on the client side.
|
|
74
106
|
|
|
75
107
|
const paramsSchema = await deserializeSchema(paramsIpc);
|
|
76
108
|
const resultSchema = await deserializeSchema(resultIpc);
|
|
77
109
|
|
|
78
|
-
let paramTypes: Record<string, string> | undefined;
|
|
79
|
-
if (paramTypesJson) {
|
|
80
|
-
try {
|
|
81
|
-
paramTypes = JSON.parse(paramTypesJson);
|
|
82
|
-
} catch {}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
let defaults: Record<string, any> | undefined;
|
|
86
|
-
if (paramDefaultsJson) {
|
|
87
|
-
try {
|
|
88
|
-
defaults = JSON.parse(paramDefaultsJson);
|
|
89
|
-
} catch {}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
110
|
const info: MethodInfo = {
|
|
93
111
|
name,
|
|
94
112
|
type: methodType as "unary" | "stream",
|
|
95
113
|
paramsSchema,
|
|
96
114
|
resultSchema,
|
|
97
|
-
doc: doc ?? undefined,
|
|
98
|
-
paramTypes,
|
|
99
|
-
defaults,
|
|
100
115
|
};
|
|
101
116
|
|
|
102
117
|
// For stream methods, result_schema_ipc actually holds the output schema
|
|
@@ -111,7 +126,7 @@ export async function parseDescribeResponse(
|
|
|
111
126
|
methods.push(info);
|
|
112
127
|
}
|
|
113
128
|
|
|
114
|
-
return { protocolName, methods };
|
|
129
|
+
return { protocolName, protocolVersion, methods };
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
/**
|
|
@@ -119,7 +134,13 @@ export async function parseDescribeResponse(
|
|
|
119
134
|
*/
|
|
120
135
|
export async function httpIntrospect(
|
|
121
136
|
baseUrl: string,
|
|
122
|
-
options?: {
|
|
137
|
+
options?: {
|
|
138
|
+
prefix?: string;
|
|
139
|
+
authorization?: string;
|
|
140
|
+
compressionLevel?: number;
|
|
141
|
+
compressFn?: (data: Uint8Array, level: number) => Promise<Uint8Array>;
|
|
142
|
+
decompressFn?: (data: Uint8Array) => Promise<Uint8Array>;
|
|
143
|
+
},
|
|
123
144
|
): Promise<ServiceDescription> {
|
|
124
145
|
const prefix = options?.prefix ?? "";
|
|
125
146
|
const emptySchema = new ArrowSchema([]);
|
|
@@ -130,16 +151,31 @@ export async function httpIntrospect(
|
|
|
130
151
|
headers.Authorization = options.authorization;
|
|
131
152
|
}
|
|
132
153
|
|
|
154
|
+
const level = options?.compressionLevel;
|
|
155
|
+
const compressFn = options?.compressFn;
|
|
156
|
+
const decompressFn = options?.decompressFn;
|
|
157
|
+
let sendBody: Uint8Array = body;
|
|
158
|
+
if (level != null && compressFn) {
|
|
159
|
+
headers["Content-Encoding"] = "zstd";
|
|
160
|
+
sendBody = await compressFn(body, level);
|
|
161
|
+
}
|
|
162
|
+
if (level != null && decompressFn) {
|
|
163
|
+
headers["Accept-Encoding"] = "zstd";
|
|
164
|
+
}
|
|
165
|
+
|
|
133
166
|
const response = await fetch(`${baseUrl}${prefix}/${DESCRIBE_METHOD_NAME}`, {
|
|
134
167
|
method: "POST",
|
|
135
168
|
headers,
|
|
136
|
-
body:
|
|
169
|
+
body: sendBody as unknown as BodyInit,
|
|
137
170
|
});
|
|
138
171
|
if (response.status === 401) {
|
|
139
172
|
throw new RpcError("AuthenticationError", "Authentication required", "");
|
|
140
173
|
}
|
|
141
174
|
|
|
142
|
-
|
|
175
|
+
let responseBody = new Uint8Array(await response.arrayBuffer());
|
|
176
|
+
if (response.headers.get("Content-Encoding") === "zstd" && decompressFn) {
|
|
177
|
+
responseBody = new Uint8Array(await decompressFn(responseBody));
|
|
178
|
+
}
|
|
143
179
|
const { batches } = await readResponseBatches(responseBody);
|
|
144
180
|
|
|
145
181
|
return parseDescribeResponse(batches);
|