@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.
Files changed (80) hide show
  1. package/README.md +47 -0
  2. package/dist/client/connect.d.ts.map +1 -1
  3. package/dist/client/index.d.ts +1 -1
  4. package/dist/client/index.d.ts.map +1 -1
  5. package/dist/client/oauth.d.ts +36 -0
  6. package/dist/client/oauth.d.ts.map +1 -1
  7. package/dist/client/pipe.d.ts +3 -0
  8. package/dist/client/pipe.d.ts.map +1 -1
  9. package/dist/client/stream.d.ts +3 -0
  10. package/dist/client/stream.d.ts.map +1 -1
  11. package/dist/client/types.d.ts +4 -0
  12. package/dist/client/types.d.ts.map +1 -1
  13. package/dist/constants.d.ts +3 -1
  14. package/dist/constants.d.ts.map +1 -1
  15. package/dist/dispatch/describe.d.ts.map +1 -1
  16. package/dist/dispatch/stream.d.ts +2 -1
  17. package/dist/dispatch/stream.d.ts.map +1 -1
  18. package/dist/dispatch/unary.d.ts +2 -1
  19. package/dist/dispatch/unary.d.ts.map +1 -1
  20. package/dist/external.d.ts +45 -0
  21. package/dist/external.d.ts.map +1 -0
  22. package/dist/gcs.d.ts +38 -0
  23. package/dist/gcs.d.ts.map +1 -0
  24. package/dist/http/auth.d.ts +13 -2
  25. package/dist/http/auth.d.ts.map +1 -1
  26. package/dist/http/bearer.d.ts +34 -0
  27. package/dist/http/bearer.d.ts.map +1 -0
  28. package/dist/http/dispatch.d.ts +2 -0
  29. package/dist/http/dispatch.d.ts.map +1 -1
  30. package/dist/http/handler.d.ts.map +1 -1
  31. package/dist/http/index.d.ts +4 -0
  32. package/dist/http/index.d.ts.map +1 -1
  33. package/dist/http/jwt.d.ts +2 -2
  34. package/dist/http/jwt.d.ts.map +1 -1
  35. package/dist/http/mtls.d.ts +78 -0
  36. package/dist/http/mtls.d.ts.map +1 -0
  37. package/dist/http/pages.d.ts +9 -0
  38. package/dist/http/pages.d.ts.map +1 -0
  39. package/dist/http/types.d.ts +17 -1
  40. package/dist/http/types.d.ts.map +1 -1
  41. package/dist/index.d.ts +3 -2
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +1119 -230
  44. package/dist/index.js.map +24 -20
  45. package/dist/otel.d.ts +47 -0
  46. package/dist/otel.d.ts.map +1 -0
  47. package/dist/s3.d.ts +43 -0
  48. package/dist/s3.d.ts.map +1 -0
  49. package/dist/server.d.ts +6 -0
  50. package/dist/server.d.ts.map +1 -1
  51. package/dist/types.d.ts +30 -0
  52. package/dist/types.d.ts.map +1 -1
  53. package/package.json +44 -1
  54. package/src/client/connect.ts +13 -5
  55. package/src/client/index.ts +10 -1
  56. package/src/client/introspect.ts +1 -1
  57. package/src/client/oauth.ts +94 -1
  58. package/src/client/pipe.ts +19 -4
  59. package/src/client/stream.ts +20 -7
  60. package/src/client/types.ts +4 -0
  61. package/src/constants.ts +4 -1
  62. package/src/dispatch/describe.ts +20 -0
  63. package/src/dispatch/stream.ts +7 -1
  64. package/src/dispatch/unary.ts +6 -1
  65. package/src/external.ts +209 -0
  66. package/src/gcs.ts +86 -0
  67. package/src/http/auth.ts +67 -4
  68. package/src/http/bearer.ts +107 -0
  69. package/src/http/dispatch.ts +26 -6
  70. package/src/http/handler.ts +81 -4
  71. package/src/http/index.ts +10 -0
  72. package/src/http/jwt.ts +17 -3
  73. package/src/http/mtls.ts +298 -0
  74. package/src/http/pages.ts +298 -0
  75. package/src/http/types.ts +17 -1
  76. package/src/index.ts +25 -0
  77. package/src/otel.ts +161 -0
  78. package/src/s3.ts +94 -0
  79. package/src/server.ts +42 -8
  80. package/src/types.ts +34 -0
package/dist/otel.d.ts ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * OpenTelemetry instrumentation for vgi-rpc TypeScript servers.
3
+ *
4
+ * Implements {@link DispatchHook} to add distributed tracing (spans) and
5
+ * metrics (request counter, duration histogram) to RPC dispatch.
6
+ *
7
+ * Requires `@opentelemetry/api` as a peer dependency.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { createOtelHook } from "vgi-rpc/otel";
12
+ * import { createHttpHandler } from "vgi-rpc";
13
+ *
14
+ * const handler = createHttpHandler(protocol, {
15
+ * dispatchHook: createOtelHook(),
16
+ * });
17
+ * ```
18
+ */
19
+ import { type Meter, type Tracer } from "@opentelemetry/api";
20
+ import type { DispatchHook } from "./types.js";
21
+ /** Configuration for OpenTelemetry instrumentation. */
22
+ export interface OtelConfig {
23
+ /** Custom TracerProvider; uses the global provider when omitted. */
24
+ tracerProvider?: {
25
+ getTracer(name: string): Tracer;
26
+ };
27
+ /** Custom MeterProvider; uses the global provider when omitted. */
28
+ meterProvider?: {
29
+ getMeter(name: string): Meter;
30
+ };
31
+ /** Enable span creation. Default: true. */
32
+ enableTracing?: boolean;
33
+ /** Enable counter/histogram recording. Default: true. */
34
+ enableMetrics?: boolean;
35
+ /** Record exceptions on error spans. Default: true. */
36
+ recordExceptions?: boolean;
37
+ /** Service name for the rpc.service attribute. Default: "TypeScriptRpcServer". */
38
+ serviceName?: string;
39
+ }
40
+ /**
41
+ * Create a {@link DispatchHook} that instruments RPC calls with OpenTelemetry.
42
+ *
43
+ * Creates a span for each RPC call with method attributes, and records
44
+ * request count and duration metrics.
45
+ */
46
+ export declare function createOtelHook(config?: OtelConfig): DispatchHook;
47
+ //# sourceMappingURL=otel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel.d.ts","sourceRoot":"","sources":["../src/otel.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAIL,KAAK,KAAK,EAKV,KAAK,MAAM,EAEZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAkB,YAAY,EAA2B,MAAM,YAAY,CAAC;AAIxF,uDAAuD;AACvD,MAAM,WAAW,UAAU;IACzB,oEAAoE;IACpE,cAAc,CAAC,EAAE;QAAE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACrD,mEAAmE;IACnE,aAAa,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAA;KAAE,CAAC;IAClD,2CAA2C;IAC3C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,yDAAyD;IACzD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAOD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CA+FhE"}
package/dist/s3.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * S3 storage backend for external storage of large Arrow IPC batches.
3
+ *
4
+ * Requires `@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner`
5
+ * as peer dependencies.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { createS3Storage } from "@query-farm/vgi-rpc/s3";
10
+ *
11
+ * const storage = createS3Storage({
12
+ * bucket: "my-bucket",
13
+ * prefix: "vgi-rpc/",
14
+ * });
15
+ * const handler = createHttpHandler(protocol, {
16
+ * externalLocation: { storage, externalizeThresholdBytes: 1_048_576 },
17
+ * });
18
+ * ```
19
+ */
20
+ import type { ExternalStorage } from "./external.js";
21
+ /** Configuration for the S3 storage backend. */
22
+ export interface S3StorageConfig {
23
+ /** S3 bucket name. */
24
+ bucket: string;
25
+ /** Key prefix for uploaded objects. Default: "vgi-rpc/". */
26
+ prefix?: string;
27
+ /** Lifetime of pre-signed GET URLs in seconds. Default: 3600 (1 hour). */
28
+ presignExpirySeconds?: number;
29
+ /** AWS region. If omitted, uses default SDK config. */
30
+ region?: string;
31
+ /** Custom S3 endpoint URL (for MinIO, LocalStack, etc.). */
32
+ endpointUrl?: string;
33
+ /** Force path-style addressing (required for some S3-compatible services). */
34
+ forcePathStyle?: boolean;
35
+ }
36
+ /**
37
+ * Create an S3-backed ExternalStorage.
38
+ *
39
+ * Lazily imports `@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner`
40
+ * on first upload to avoid loading the AWS SDK unless needed.
41
+ */
42
+ export declare function createS3Storage(config: S3StorageConfig): ExternalStorage;
43
+ //# sourceMappingURL=s3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../src/s3.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC9B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CA8CxE"}
package/dist/server.d.ts CHANGED
@@ -1,4 +1,6 @@
1
+ import type { ExternalLocationConfig } from "./external.js";
1
2
  import type { Protocol } from "./protocol.js";
3
+ import { type DispatchHook } from "./types.js";
2
4
  /**
3
5
  * RPC server that reads Arrow IPC requests from stdin and writes responses to stdout.
4
6
  * Supports unary and streaming (producer/exchange) methods.
@@ -8,9 +10,13 @@ export declare class VgiRpcServer {
8
10
  private enableDescribe;
9
11
  private serverId;
10
12
  private describeBatch;
13
+ private dispatchHook;
14
+ private externalConfig;
11
15
  constructor(protocol: Protocol, options?: {
12
16
  enableDescribe?: boolean;
13
17
  serverId?: string;
18
+ dispatchHook?: DispatchHook;
19
+ externalLocation?: ExternalLocationConfig;
14
20
  });
15
21
  /** Start the server loop. Reads requests until stdin closes. */
16
22
  run(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAS9C;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,cAAc,CAAU;IAChC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA+D;gBAExE,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;IAWzF,gEAAgE;IAC1D,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;YAwCZ,QAAQ;CA0DvB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAuB,KAAK,YAAY,EAAiC,MAAM,YAAY,CAAC;AAQnG;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,cAAc,CAAU;IAChC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA+D;IACpF,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,cAAc,CAAqC;gBAGzD,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE;QACR,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,YAAY,CAAC;QAC5B,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;KAC3C;IAcH,gEAAgE;IAC1D,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;YAwCZ,QAAQ;CA+EvB"}
package/dist/types.d.ts CHANGED
@@ -42,6 +42,36 @@ export interface MethodDefinition {
42
42
  defaults?: Record<string, any>;
43
43
  paramTypes?: Record<string, string>;
44
44
  }
45
+ /** Metadata passed to dispatch hooks before and after RPC method execution. */
46
+ export interface DispatchInfo {
47
+ /** RPC method name. */
48
+ method: string;
49
+ /** "unary" or "stream". */
50
+ methodType: string;
51
+ /** Server identifier. */
52
+ serverId: string;
53
+ /** Client-supplied request identifier, or null. */
54
+ requestId: string | null;
55
+ }
56
+ /** Per-call I/O counters, matching Python's CallStatistics. */
57
+ export interface CallStatistics {
58
+ inputBatches: number;
59
+ outputBatches: number;
60
+ inputRows: number;
61
+ outputRows: number;
62
+ inputBytes: number;
63
+ outputBytes: number;
64
+ }
65
+ /** Opaque token returned by onDispatchStart, passed back to onDispatchEnd. */
66
+ export type HookToken = unknown;
67
+ /**
68
+ * Observability hook called around RPC dispatch.
69
+ * Implementations must be safe for concurrent use (HTTP transport is concurrent).
70
+ */
71
+ export interface DispatchHook {
72
+ onDispatchStart(info: DispatchInfo): HookToken;
73
+ onDispatchEnd(token: HookToken, info: DispatchInfo, stats: CallStatistics, error?: Error): void;
74
+ }
45
75
  export interface EmittedBatch {
46
76
  batch: RecordBatch;
47
77
  metadata?: Map<string, string>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@query-farm/vgi-rpc",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://vgi-rpc-typescript.query.farm",
6
6
  "repository": {
@@ -15,6 +15,21 @@
15
15
  "import": "./dist/index.js",
16
16
  "types": "./dist/index.d.ts",
17
17
  "bun": "./src/index.ts"
18
+ },
19
+ "./otel": {
20
+ "import": "./dist/otel.js",
21
+ "types": "./dist/otel.d.ts",
22
+ "bun": "./src/otel.ts"
23
+ },
24
+ "./s3": {
25
+ "import": "./dist/s3.js",
26
+ "types": "./dist/s3.d.ts",
27
+ "bun": "./src/s3.ts"
28
+ },
29
+ "./gcs": {
30
+ "import": "./dist/gcs.js",
31
+ "types": "./dist/gcs.d.ts",
32
+ "bun": "./src/gcs.ts"
18
33
  }
19
34
  },
20
35
  "files": [
@@ -25,8 +40,36 @@
25
40
  "@query-farm/apache-arrow": "*",
26
41
  "oauth4webapi": "^3.8.5"
27
42
  },
43
+ "peerDependencies": {
44
+ "@opentelemetry/api": ">=1.4.0",
45
+ "@aws-sdk/client-s3": ">=3.0.0",
46
+ "@aws-sdk/s3-request-presigner": ">=3.0.0",
47
+ "@google-cloud/storage": ">=7.0.0"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "@opentelemetry/api": {
51
+ "optional": true
52
+ },
53
+ "@aws-sdk/client-s3": {
54
+ "optional": true
55
+ },
56
+ "@aws-sdk/s3-request-presigner": {
57
+ "optional": true
58
+ },
59
+ "@google-cloud/storage": {
60
+ "optional": true
61
+ }
62
+ },
28
63
  "devDependencies": {
29
64
  "@biomejs/biome": "^2.4.5",
65
+ "@opentelemetry/api": "^1.9.0",
66
+ "@opentelemetry/resources": "^2.6.0",
67
+ "@opentelemetry/sdk-metrics": "^2.6.0",
68
+ "@opentelemetry/sdk-trace-base": "^2.6.0",
69
+ "@opentelemetry/semantic-conventions": "^1.40.0",
70
+ "@aws-sdk/client-s3": "^3.750.0",
71
+ "@aws-sdk/s3-request-presigner": "^3.750.0",
72
+ "@google-cloud/storage": "^7.15.0",
30
73
  "@types/bun": "latest"
31
74
  },
32
75
  "scripts": {
@@ -4,6 +4,7 @@
4
4
  import type { RecordBatch, Schema } from "@query-farm/apache-arrow";
5
5
  import { LOG_LEVEL_KEY, STATE_KEY } from "../constants.js";
6
6
  import { RpcError } from "../errors.js";
7
+ import { isExternalLocationBatch, resolveExternalLocation } from "../external.js";
7
8
  import { ARROW_CONTENT_TYPE } from "../http/common.js";
8
9
  import { httpIntrospect, type MethodInfo, type ServiceDescription } from "./introspect.js";
9
10
  import {
@@ -27,10 +28,11 @@ export interface RpcClient {
27
28
  }
28
29
 
29
30
  export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcClient {
30
- const prefix = (options?.prefix ?? "/vgi").replace(/\/+$/, "");
31
+ const prefix = (options?.prefix ?? "").replace(/\/+$/, "");
31
32
  const onLog = options?.onLog;
32
33
  const compressionLevel = options?.compressionLevel;
33
34
  const authorization = options?.authorization;
35
+ const externalConfig = options?.externalLocation;
34
36
 
35
37
  let methodCache: Map<string, MethodInfo> | null = null;
36
38
  let compressFn: CompressFn | undefined;
@@ -114,12 +116,17 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
114
116
  const responseBody = await readResponse(resp);
115
117
  const { batches } = await readResponseBatches(responseBody);
116
118
 
117
- // Process batches: dispatch logs, find result
119
+ // Process batches: dispatch logs, resolve external pointers, find result
118
120
  let resultBatch: RecordBatch | null = null;
119
- for (const batch of batches) {
121
+ for (let batch of batches) {
120
122
  if (batch.numRows === 0) {
121
- dispatchLogOrError(batch, onLog);
122
- continue;
123
+ // Check for external location pointer batch
124
+ if (isExternalLocationBatch(batch)) {
125
+ batch = await resolveExternalLocation(batch, externalConfig);
126
+ } else {
127
+ dispatchLogOrError(batch, onLog);
128
+ continue;
129
+ }
123
130
  }
124
131
  resultBatch = batch;
125
132
  }
@@ -302,6 +309,7 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
302
309
  compressFn,
303
310
  decompressFn,
304
311
  authorization,
312
+ externalConfig,
305
313
  });
306
314
  },
307
315
 
@@ -4,7 +4,16 @@
4
4
  export { httpConnect, type RpcClient } from "./connect.js";
5
5
  export { httpIntrospect, type MethodInfo, parseDescribeResponse, type ServiceDescription } from "./introspect.js";
6
6
  export type { OAuthResourceMetadataResponse } from "./oauth.js";
7
- export { fetchOAuthMetadata, httpOAuthMetadata, parseResourceMetadataUrl } from "./oauth.js";
7
+ export {
8
+ fetchOAuthMetadata,
9
+ httpOAuthMetadata,
10
+ parseClientId,
11
+ parseClientSecret,
12
+ parseDeviceCodeClientId,
13
+ parseDeviceCodeClientSecret,
14
+ parseResourceMetadataUrl,
15
+ parseUseIdTokenAsBearer,
16
+ } from "./oauth.js";
8
17
  export { PipeStreamSession, pipeConnect, subprocessConnect } from "./pipe.js";
9
18
  export { HttpStreamSession } from "./stream.js";
10
19
  export type {
@@ -121,7 +121,7 @@ export async function httpIntrospect(
121
121
  baseUrl: string,
122
122
  options?: { prefix?: string; authorization?: string },
123
123
  ): Promise<ServiceDescription> {
124
- const prefix = options?.prefix ?? "/vgi";
124
+ const prefix = options?.prefix ?? "";
125
125
  const emptySchema = new ArrowSchema([]);
126
126
  const body = buildRequestIpc(emptySchema, {}, DESCRIBE_METHOD_NAME);
127
127
 
@@ -7,10 +7,21 @@ export interface OAuthResourceMetadataResponse {
7
7
  authorizationServers: string[];
8
8
  scopesSupported?: string[];
9
9
  bearerMethodsSupported?: string[];
10
+ resourceSigningAlgValuesSupported?: string[];
10
11
  resourceName?: string;
11
12
  resourceDocumentation?: string;
12
13
  resourcePolicyUri?: string;
13
14
  resourceTosUri?: string;
15
+ /** OAuth client_id advertised by the server. */
16
+ clientId?: string;
17
+ /** OAuth client_secret advertised by the server. */
18
+ clientSecret?: string;
19
+ /** When true, use the OIDC id_token as the Bearer token instead of access_token. */
20
+ useIdTokenAsBearer?: boolean;
21
+ /** OAuth client_id for device code flow. */
22
+ deviceCodeClientId?: string;
23
+ /** OAuth client_secret for device code flow. */
24
+ deviceCodeClientSecret?: string;
14
25
  }
15
26
 
16
27
  function parseMetadataJson(json: Record<string, any>): OAuthResourceMetadataResponse {
@@ -20,10 +31,17 @@ function parseMetadataJson(json: Record<string, any>): OAuthResourceMetadataResp
20
31
  };
21
32
  if (json.scopes_supported) result.scopesSupported = json.scopes_supported;
22
33
  if (json.bearer_methods_supported) result.bearerMethodsSupported = json.bearer_methods_supported;
34
+ if (json.resource_signing_alg_values_supported)
35
+ result.resourceSigningAlgValuesSupported = json.resource_signing_alg_values_supported;
23
36
  if (json.resource_name) result.resourceName = json.resource_name;
24
37
  if (json.resource_documentation) result.resourceDocumentation = json.resource_documentation;
25
38
  if (json.resource_policy_uri) result.resourcePolicyUri = json.resource_policy_uri;
26
39
  if (json.resource_tos_uri) result.resourceTosUri = json.resource_tos_uri;
40
+ if (json.client_id) result.clientId = json.client_id;
41
+ if (json.client_secret) result.clientSecret = json.client_secret;
42
+ if (json.use_id_token_as_bearer) result.useIdTokenAsBearer = json.use_id_token_as_bearer;
43
+ if (json.device_code_client_id) result.deviceCodeClientId = json.device_code_client_id;
44
+ if (json.device_code_client_secret) result.deviceCodeClientSecret = json.device_code_client_secret;
27
45
  return result;
28
46
  }
29
47
 
@@ -35,7 +53,7 @@ export async function httpOAuthMetadata(
35
53
  baseUrl: string,
36
54
  prefix?: string,
37
55
  ): Promise<OAuthResourceMetadataResponse | null> {
38
- const effectivePrefix = (prefix ?? "/vgi").replace(/\/+$/, "");
56
+ const effectivePrefix = (prefix ?? "").replace(/\/+$/, "");
39
57
  const metadataUrl = `${baseUrl.replace(/\/+$/, "")}/.well-known/oauth-protected-resource${effectivePrefix}`;
40
58
 
41
59
  try {
@@ -72,3 +90,78 @@ export function parseResourceMetadataUrl(wwwAuthenticate: string): string | null
72
90
 
73
91
  return metadataMatch[1];
74
92
  }
93
+
94
+ /**
95
+ * Extract the `client_id` from a WWW-Authenticate Bearer challenge.
96
+ * Returns `null` if no client_id parameter is found.
97
+ */
98
+ export function parseClientId(wwwAuthenticate: string): string | null {
99
+ const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
100
+ if (!bearerMatch) return null;
101
+
102
+ const params = bearerMatch[1];
103
+ const clientIdMatch = params.match(/client_id="([^"]+)"/);
104
+ if (!clientIdMatch) return null;
105
+
106
+ return clientIdMatch[1];
107
+ }
108
+
109
+ /**
110
+ * Extract the `client_secret` from a WWW-Authenticate Bearer challenge.
111
+ * Returns `null` if no client_secret parameter is found.
112
+ */
113
+ export function parseClientSecret(wwwAuthenticate: string): string | null {
114
+ const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
115
+ if (!bearerMatch) return null;
116
+
117
+ const params = bearerMatch[1];
118
+ const match = params.match(/client_secret="([^"]+)"/);
119
+ if (!match) return null;
120
+
121
+ return match[1];
122
+ }
123
+
124
+ /**
125
+ * Extract the `use_id_token_as_bearer` flag from a WWW-Authenticate Bearer challenge.
126
+ * Returns `true` if the parameter is present and set to "true", `false` otherwise.
127
+ */
128
+ export function parseUseIdTokenAsBearer(wwwAuthenticate: string): boolean {
129
+ const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
130
+ if (!bearerMatch) return false;
131
+
132
+ const params = bearerMatch[1];
133
+ const match = params.match(/use_id_token_as_bearer="([^"]+)"/);
134
+ if (!match) return false;
135
+
136
+ return match[1] === "true";
137
+ }
138
+
139
+ /**
140
+ * Extract the `device_code_client_id` from a WWW-Authenticate Bearer challenge.
141
+ * Returns `null` if no device_code_client_id parameter is found.
142
+ */
143
+ export function parseDeviceCodeClientId(wwwAuthenticate: string): string | null {
144
+ const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
145
+ if (!bearerMatch) return null;
146
+
147
+ const params = bearerMatch[1];
148
+ const match = params.match(/device_code_client_id="([^"]+)"/);
149
+ if (!match) return null;
150
+
151
+ return match[1];
152
+ }
153
+
154
+ /**
155
+ * Extract the `device_code_client_secret` from a WWW-Authenticate Bearer challenge.
156
+ * Returns `null` if no device_code_client_secret parameter is found.
157
+ */
158
+ export function parseDeviceCodeClientSecret(wwwAuthenticate: string): string | null {
159
+ const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
160
+ if (!bearerMatch) return null;
161
+
162
+ const params = bearerMatch[1];
163
+ const match = params.match(/device_code_client_secret="([^"]+)"/);
164
+ if (!match) return null;
165
+
166
+ return match[1];
167
+ }
@@ -12,6 +12,7 @@ import {
12
12
  } from "@query-farm/apache-arrow";
13
13
  import { DESCRIBE_METHOD_NAME } from "../constants.js";
14
14
  import { RpcError } from "../errors.js";
15
+ import { type ExternalLocationConfig, isExternalLocationBatch, resolveExternalLocation } from "../external.js";
15
16
  import { serializeIpcStream } from "../http/common.js";
16
17
  import { IpcStreamReader } from "../wire/reader.js";
17
18
  import type { RpcClient } from "./connect.js";
@@ -86,6 +87,7 @@ export class PipeStreamSession implements StreamSession {
86
87
  private _outputSchema: Schema;
87
88
  private _releaseBusy: () => void;
88
89
  private _setDrainPromise: (p: Promise<void>) => void;
90
+ private _externalConfig?: ExternalLocationConfig;
89
91
 
90
92
  constructor(opts: {
91
93
  reader: IpcStreamReader;
@@ -95,6 +97,7 @@ export class PipeStreamSession implements StreamSession {
95
97
  outputSchema: Schema;
96
98
  releaseBusy: () => void;
97
99
  setDrainPromise: (p: Promise<void>) => void;
100
+ externalConfig?: ExternalLocationConfig;
98
101
  }) {
99
102
  this._reader = opts.reader;
100
103
  this._writeFn = opts.writeFn;
@@ -103,6 +106,7 @@ export class PipeStreamSession implements StreamSession {
103
106
  this._outputSchema = opts.outputSchema;
104
107
  this._releaseBusy = opts.releaseBusy;
105
108
  this._setDrainPromise = opts.setDrainPromise;
109
+ this._externalConfig = opts.externalConfig;
106
110
  }
107
111
 
108
112
  get header(): Record<string, any> | null {
@@ -120,6 +124,10 @@ export class PipeStreamSession implements StreamSession {
120
124
  if (batch === null) return null; // Server closed output stream
121
125
 
122
126
  if (batch.numRows === 0) {
127
+ // Check for external location pointer batch
128
+ if (isExternalLocationBatch(batch)) {
129
+ return await resolveExternalLocation(batch, this._externalConfig);
130
+ }
123
131
  // Check if it's a log/error batch. If so, dispatch and continue.
124
132
  // Otherwise it's a zero-row data batch — return it.
125
133
  if (dispatchLogOrError(batch, this._onLog)) {
@@ -375,6 +383,7 @@ export function pipeConnect(
375
383
  options?: PipeConnectOptions,
376
384
  ): RpcClient {
377
385
  const onLog = options?.onLog;
386
+ const externalConfig = options?.externalLocation;
378
387
 
379
388
  let reader: IpcStreamReader | null = null;
380
389
  let readerPromise: Promise<IpcStreamReader> | null = null;
@@ -483,12 +492,16 @@ export function pipeConnect(
483
492
  throw new Error("EOF reading response");
484
493
  }
485
494
 
486
- // Process batches: dispatch logs, find result
495
+ // Process batches: dispatch logs, resolve external pointers, find result
487
496
  let resultBatch: RecordBatch | null = null;
488
- for (const batch of response.batches) {
497
+ for (let batch of response.batches) {
489
498
  if (batch.numRows === 0) {
490
- dispatchLogOrError(batch, onLog);
491
- continue;
499
+ if (isExternalLocationBatch(batch)) {
500
+ batch = await resolveExternalLocation(batch, externalConfig);
501
+ } else {
502
+ dispatchLogOrError(batch, onLog);
503
+ continue;
504
+ }
492
505
  }
493
506
  resultBatch = batch;
494
507
  }
@@ -557,6 +570,7 @@ export function pipeConnect(
557
570
  outputSchema,
558
571
  releaseBusy,
559
572
  setDrainPromise,
573
+ externalConfig,
560
574
  });
561
575
  } catch (e) {
562
576
  // Init error (e.g., server raised exception during init).
@@ -624,6 +638,7 @@ export function subprocessConnect(cmd: string[], options?: SubprocessConnectOpti
624
638
 
625
639
  const client = pipeConnect(stdout, writable, {
626
640
  onLog: options?.onLog,
641
+ externalLocation: options?.externalLocation,
627
642
  });
628
643
 
629
644
  // Wrap close to also kill the subprocess
@@ -4,6 +4,7 @@
4
4
  import { Field, makeData, RecordBatch, Schema, Struct, vectorFromArray } from "@query-farm/apache-arrow";
5
5
  import { STATE_KEY } from "../constants.js";
6
6
  import { RpcError } from "../errors.js";
7
+ import { type ExternalLocationConfig, isExternalLocationBatch, resolveExternalLocation } from "../external.js";
7
8
  import { ARROW_CONTENT_TYPE, serializeIpcStream } from "../http/common.js";
8
9
  import { dispatchLogOrError, extractBatchRows, inferArrowType, readResponseBatches } from "./ipc.js";
9
10
  import type { LogMessage, StreamSession } from "./types.js";
@@ -26,6 +27,7 @@ export class HttpStreamSession implements StreamSession {
26
27
  private _compressFn?: CompressFn;
27
28
  private _decompressFn?: DecompressFn;
28
29
  private _authorization?: string;
30
+ private _externalConfig?: ExternalLocationConfig;
29
31
 
30
32
  constructor(opts: {
31
33
  baseUrl: string;
@@ -42,6 +44,7 @@ export class HttpStreamSession implements StreamSession {
42
44
  compressFn?: CompressFn;
43
45
  decompressFn?: DecompressFn;
44
46
  authorization?: string;
47
+ externalConfig?: ExternalLocationConfig;
45
48
  }) {
46
49
  this._baseUrl = opts.baseUrl;
47
50
  this._prefix = opts.prefix;
@@ -57,6 +60,7 @@ export class HttpStreamSession implements StreamSession {
57
60
  this._compressFn = opts.compressFn;
58
61
  this._decompressFn = opts.decompressFn;
59
62
  this._authorization = opts.authorization;
63
+ this._externalConfig = opts.externalConfig;
60
64
  }
61
65
 
62
66
  get header(): Record<string, any> | null {
@@ -211,10 +215,14 @@ export class HttpStreamSession implements StreamSession {
211
215
  */
212
216
  async *[Symbol.asyncIterator](): AsyncIterableIterator<Record<string, any>[]> {
213
217
  // Yield pre-loaded batches from init
214
- for (const batch of this._pendingBatches) {
218
+ for (let batch of this._pendingBatches) {
215
219
  if (batch.numRows === 0) {
216
- dispatchLogOrError(batch, this._onLog);
217
- continue;
220
+ if (isExternalLocationBatch(batch)) {
221
+ batch = await resolveExternalLocation(batch, this._externalConfig);
222
+ } else {
223
+ dispatchLogOrError(batch, this._onLog);
224
+ continue;
225
+ }
218
226
  }
219
227
  yield extractBatchRows(batch);
220
228
  }
@@ -229,7 +237,7 @@ export class HttpStreamSession implements StreamSession {
229
237
  const { batches } = await readResponseBatches(responseBody);
230
238
 
231
239
  let gotContinuation = false;
232
- for (const batch of batches) {
240
+ for (let batch of batches) {
233
241
  if (batch.numRows === 0) {
234
242
  // Check for continuation token
235
243
  const token = batch.metadata?.get(STATE_KEY);
@@ -238,9 +246,14 @@ export class HttpStreamSession implements StreamSession {
238
246
  gotContinuation = true;
239
247
  continue;
240
248
  }
241
- // Log/error batch
242
- dispatchLogOrError(batch, this._onLog);
243
- continue;
249
+ // Check for external location pointer
250
+ if (isExternalLocationBatch(batch)) {
251
+ batch = await resolveExternalLocation(batch, this._externalConfig);
252
+ } else {
253
+ // Log/error batch
254
+ dispatchLogOrError(batch, this._onLog);
255
+ continue;
256
+ }
244
257
  }
245
258
 
246
259
  yield extractBatchRows(batch);
@@ -7,6 +7,8 @@ export interface HttpConnectOptions {
7
7
  compressionLevel?: number;
8
8
  /** Authorization header value (e.g. "Bearer <token>"). Sent with every request. */
9
9
  authorization?: string;
10
+ /** External storage config for resolving externalized batches. */
11
+ externalLocation?: import("../external.js").ExternalLocationConfig;
10
12
  }
11
13
 
12
14
  export interface LogMessage {
@@ -24,6 +26,8 @@ export interface StreamSession {
24
26
 
25
27
  export interface PipeConnectOptions {
26
28
  onLog?: (msg: LogMessage) => void;
29
+ /** External storage config for resolving externalized batches. */
30
+ externalLocation?: import("../external.js").ExternalLocationConfig;
27
31
  }
28
32
 
29
33
  export interface SubprocessConnectOptions extends PipeConnectOptions {
package/src/constants.ts CHANGED
@@ -15,8 +15,11 @@ export const REQUEST_ID_KEY = "vgi_rpc.request_id";
15
15
 
16
16
  export const PROTOCOL_NAME_KEY = "vgi_rpc.protocol_name";
17
17
  export const DESCRIBE_VERSION_KEY = "vgi_rpc.describe_version";
18
- export const DESCRIBE_VERSION = "2";
18
+ export const DESCRIBE_VERSION = "3";
19
19
 
20
20
  export const DESCRIBE_METHOD_NAME = "__describe__";
21
21
 
22
22
  export const STATE_KEY = "vgi_rpc.stream_state#b64";
23
+
24
+ export const LOCATION_KEY = "vgi_rpc.location";
25
+ export const LOCATION_SHA256_KEY = "vgi_rpc.location.sha256";