@query-farm/vgi-rpc 0.3.3 → 0.4.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 (55) hide show
  1. package/dist/auth.d.ts +13 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/client/connect.d.ts.map +1 -1
  4. package/dist/client/index.d.ts +2 -0
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/client/introspect.d.ts +1 -0
  7. package/dist/client/introspect.d.ts.map +1 -1
  8. package/dist/client/oauth.d.ts +26 -0
  9. package/dist/client/oauth.d.ts.map +1 -0
  10. package/dist/client/stream.d.ts +2 -0
  11. package/dist/client/stream.d.ts.map +1 -1
  12. package/dist/client/types.d.ts +2 -0
  13. package/dist/client/types.d.ts.map +1 -1
  14. package/dist/dispatch/stream.d.ts.map +1 -1
  15. package/dist/http/auth.d.ts +21 -0
  16. package/dist/http/auth.d.ts.map +1 -0
  17. package/dist/http/common.d.ts.map +1 -1
  18. package/dist/http/dispatch.d.ts +2 -0
  19. package/dist/http/dispatch.d.ts.map +1 -1
  20. package/dist/http/handler.d.ts.map +1 -1
  21. package/dist/http/index.d.ts +4 -0
  22. package/dist/http/index.d.ts.map +1 -1
  23. package/dist/http/jwt.d.ts +21 -0
  24. package/dist/http/jwt.d.ts.map +1 -0
  25. package/dist/http/types.d.ts +5 -0
  26. package/dist/http/types.d.ts.map +1 -1
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1479 -71
  30. package/dist/index.js.map +20 -15
  31. package/dist/types.d.ts +8 -2
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/util/conform.d.ts +5 -3
  34. package/dist/util/conform.d.ts.map +1 -1
  35. package/dist/wire/response.d.ts.map +1 -1
  36. package/package.json +3 -2
  37. package/src/auth.ts +31 -0
  38. package/src/client/connect.ts +15 -1
  39. package/src/client/index.ts +2 -0
  40. package/src/client/introspect.ts +14 -2
  41. package/src/client/oauth.ts +74 -0
  42. package/src/client/stream.ts +12 -0
  43. package/src/client/types.ts +2 -0
  44. package/src/dispatch/stream.ts +11 -5
  45. package/src/http/auth.ts +47 -0
  46. package/src/http/common.ts +1 -6
  47. package/src/http/dispatch.ts +6 -4
  48. package/src/http/handler.ts +41 -1
  49. package/src/http/index.ts +4 -0
  50. package/src/http/jwt.ts +66 -0
  51. package/src/http/types.ts +6 -0
  52. package/src/index.ts +7 -0
  53. package/src/types.ts +17 -3
  54. package/src/util/conform.ts +68 -5
  55. package/src/wire/response.ts +28 -14
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { RecordBatch, type Schema } from "@query-farm/apache-arrow";
2
+ import { AuthContext } from "./auth.js";
2
3
  export declare enum MethodType {
3
4
  UNARY = "unary",
4
5
  STREAM = "stream"
@@ -7,6 +8,10 @@ export declare enum MethodType {
7
8
  export interface LogContext {
8
9
  clientLog(level: string, message: string, extra?: Record<string, string>): void;
9
10
  }
11
+ /** Extended context with authentication info, available to handlers. */
12
+ export interface CallContext extends LogContext {
13
+ readonly auth: AuthContext;
14
+ }
10
15
  /** Handler for unary (request-response) RPC methods. */
11
16
  export type UnaryHandler = (params: Record<string, any>, ctx: LogContext) => Promise<Record<string, any>> | Record<string, any>;
12
17
  /** Initialization function for producer streams. Returns the initial state object. */
@@ -45,7 +50,7 @@ export interface EmittedBatch {
45
50
  * Accumulates output batches during a produce/exchange call.
46
51
  * Enforces that exactly one data batch is emitted per call (plus any number of log batches).
47
52
  */
48
- export declare class OutputCollector implements LogContext {
53
+ export declare class OutputCollector implements CallContext {
49
54
  private _batches;
50
55
  private _dataBatchIdx;
51
56
  private _finished;
@@ -53,7 +58,8 @@ export declare class OutputCollector implements LogContext {
53
58
  private _outputSchema;
54
59
  private _serverId;
55
60
  private _requestId;
56
- constructor(outputSchema: Schema, producerMode?: boolean, serverId?: string, requestId?: string | null);
61
+ readonly auth: AuthContext;
62
+ constructor(outputSchema: Schema, producerMode?: boolean, serverId?: string, requestId?: string | null, authContext?: AuthContext);
57
63
  get outputSchema(): Schema;
58
64
  get finished(): boolean;
59
65
  get batches(): EmittedBatch[];
@@ -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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"}
@@ -8,9 +8,11 @@ import { RecordBatch, type Schema } from "@query-farm/apache-arrow";
8
8
  * match the writer's schema. Cloning each child Data with the schema's field
9
9
  * type fixes the type metadata while preserving the underlying buffers.
10
10
  *
11
- * This is also used to cast compatible input types (e.g., decimaldouble,
12
- * int32int64) when the input batch schema doesn't exactly match the method's
13
- * declared input schema.
11
+ * This is also used to cast compatible input types (e.g., int32float64,
12
+ * float32float64) when the input batch schema doesn't exactly match the
13
+ * method's declared input schema. When the underlying buffer layout differs
14
+ * (e.g., 4-byte int32 vs 8-byte float64), we read the values and build a
15
+ * new vector with the target type.
14
16
  */
15
17
  export declare function conformBatchToSchema(batch: RecordBatch, schema: Schema): RecordBatch;
16
18
  //# sourceMappingURL=conform.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"conform.d.ts","sourceRoot":"","sources":["../../src/util/conform.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAoB,KAAK,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAEtF;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAYpF"}
1
+ {"version":3,"file":"conform.d.ts","sourceRoot":"","sources":["../../src/util/conform.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,WAAW,EACX,KAAK,MAAM,EAIZ,MAAM,0BAA0B,CAAC;AAkBlC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAiDpF"}
@@ -1 +1 @@
1
- {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/wire/response.ts"],"names":[],"mappings":"AAGA,OAAO,EAKL,WAAW,EACX,KAAK,MAAM,EAGZ,MAAM,0BAA0B,CAAC;AAGlC;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAc5F;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,WAAW,CAwCb;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAiBrH;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,QAAQ,CAAC,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,WAAW,CAeb;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,WAAW,CAyB3F"}
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/wire/response.ts"],"names":[],"mappings":"AAGA,OAAO,EAKL,WAAW,EACX,KAAK,MAAM,EAGZ,MAAM,0BAA0B,CAAC;AAGlC;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAc5F;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,WAAW,CAwCb;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAiBrH;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,QAAQ,CAAC,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,WAAW,CAeb;AA6BD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,WAAW,CAY3F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@query-farm/vgi-rpc",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://vgi-rpc-typescript.query.farm",
6
6
  "repository": {
@@ -22,7 +22,8 @@
22
22
  "src"
23
23
  ],
24
24
  "dependencies": {
25
- "@query-farm/apache-arrow": "*"
25
+ "@query-farm/apache-arrow": "*",
26
+ "oauth4webapi": "^3.8.5"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@biomejs/biome": "^2.4.5",
package/src/auth.ts ADDED
@@ -0,0 +1,31 @@
1
+ // © Copyright 2025-2026, Query.Farm LLC - https://query.farm
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { RpcError } from "./errors.js";
5
+
6
+ /** Authentication context available to RPC handlers. */
7
+ export class AuthContext {
8
+ readonly domain: string;
9
+ readonly authenticated: boolean;
10
+ readonly principal: string | null;
11
+ readonly claims: Record<string, any>;
12
+
13
+ constructor(domain: string, authenticated: boolean, principal: string | null, claims: Record<string, any> = {}) {
14
+ this.domain = domain;
15
+ this.authenticated = authenticated;
16
+ this.principal = principal;
17
+ this.claims = claims;
18
+ }
19
+
20
+ /** Create an unauthenticated (anonymous) context. */
21
+ static anonymous(): AuthContext {
22
+ return new AuthContext("", false, null);
23
+ }
24
+
25
+ /** Throw an RpcError if this context is not authenticated. */
26
+ requireAuthenticated(): void {
27
+ if (!this.authenticated) {
28
+ throw new RpcError("AuthenticationError", "Authentication required", "");
29
+ }
30
+ }
31
+ }
@@ -3,6 +3,7 @@
3
3
 
4
4
  import type { RecordBatch, Schema } from "@query-farm/apache-arrow";
5
5
  import { LOG_LEVEL_KEY, STATE_KEY } from "../constants.js";
6
+ import { RpcError } from "../errors.js";
6
7
  import { ARROW_CONTENT_TYPE } from "../http/common.js";
7
8
  import { httpIntrospect, type MethodInfo, type ServiceDescription } from "./introspect.js";
8
9
  import {
@@ -29,6 +30,7 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
29
30
  const prefix = (options?.prefix ?? "/vgi").replace(/\/+$/, "");
30
31
  const onLog = options?.onLog;
31
32
  const compressionLevel = options?.compressionLevel;
33
+ const authorization = options?.authorization;
32
34
 
33
35
  let methodCache: Map<string, MethodInfo> | null = null;
34
36
  let compressFn: CompressFn | undefined;
@@ -55,6 +57,9 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
55
57
  headers["Content-Encoding"] = "zstd";
56
58
  headers["Accept-Encoding"] = "zstd";
57
59
  }
60
+ if (authorization) {
61
+ headers.Authorization = authorization;
62
+ }
58
63
  return headers;
59
64
  }
60
65
 
@@ -65,6 +70,12 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
65
70
  return content;
66
71
  }
67
72
 
73
+ function checkAuth(resp: Response): void {
74
+ if (resp.status === 401) {
75
+ throw new RpcError("AuthenticationError", "Authentication required", "");
76
+ }
77
+ }
78
+
68
79
  async function readResponse(resp: Response): Promise<Uint8Array<ArrayBuffer>> {
69
80
  let body = new Uint8Array(await resp.arrayBuffer());
70
81
  if (resp.headers.get("Content-Encoding") === "zstd" && decompressFn) {
@@ -75,7 +86,7 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
75
86
 
76
87
  async function ensureMethodCache(): Promise<Map<string, MethodInfo>> {
77
88
  if (methodCache) return methodCache;
78
- const desc = await httpIntrospect(baseUrl, { prefix });
89
+ const desc = await httpIntrospect(baseUrl, { prefix, authorization });
79
90
  methodCache = new Map(desc.methods.map((m) => [m.name, m]));
80
91
  return methodCache;
81
92
  }
@@ -98,6 +109,7 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
98
109
  headers: buildHeaders(),
99
110
  body: prepareBody(body) as unknown as BodyInit,
100
111
  });
112
+ checkAuth(resp);
101
113
 
102
114
  const responseBody = await readResponse(resp);
103
115
  const { batches } = await readResponseBatches(responseBody);
@@ -146,6 +158,7 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
146
158
  headers: buildHeaders(),
147
159
  body: prepareBody(body) as unknown as BodyInit,
148
160
  });
161
+ checkAuth(resp);
149
162
 
150
163
  const responseBody = await readResponse(resp);
151
164
 
@@ -288,6 +301,7 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
288
301
  compressionLevel,
289
302
  compressFn,
290
303
  decompressFn,
304
+ authorization,
291
305
  });
292
306
  },
293
307
 
@@ -3,6 +3,8 @@
3
3
 
4
4
  export { httpConnect, type RpcClient } from "./connect.js";
5
5
  export { httpIntrospect, type MethodInfo, parseDescribeResponse, type ServiceDescription } from "./introspect.js";
6
+ export type { OAuthResourceMetadataResponse } from "./oauth.js";
7
+ export { fetchOAuthMetadata, httpOAuthMetadata, parseResourceMetadataUrl } from "./oauth.js";
6
8
  export { PipeStreamSession, pipeConnect, subprocessConnect } from "./pipe.js";
7
9
  export { HttpStreamSession } from "./stream.js";
8
10
  export type {
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { Schema as ArrowSchema, type RecordBatch, RecordBatchReader, type Schema } from "@query-farm/apache-arrow";
5
5
  import { DESCRIBE_METHOD_NAME, PROTOCOL_NAME_KEY } from "../constants.js";
6
+ import { RpcError } from "../errors.js";
6
7
  import { ARROW_CONTENT_TYPE } from "../http/common.js";
7
8
  import { buildRequestIpc, dispatchLogOrError, readResponseBatches } from "./ipc.js";
8
9
  import type { LogMessage } from "./types.js";
@@ -116,16 +117,27 @@ export async function parseDescribeResponse(
116
117
  /**
117
118
  * Send a __describe__ request and return a ServiceDescription.
118
119
  */
119
- export async function httpIntrospect(baseUrl: string, options?: { prefix?: string }): Promise<ServiceDescription> {
120
+ export async function httpIntrospect(
121
+ baseUrl: string,
122
+ options?: { prefix?: string; authorization?: string },
123
+ ): Promise<ServiceDescription> {
120
124
  const prefix = options?.prefix ?? "/vgi";
121
125
  const emptySchema = new ArrowSchema([]);
122
126
  const body = buildRequestIpc(emptySchema, {}, DESCRIBE_METHOD_NAME);
123
127
 
128
+ const headers: Record<string, string> = { "Content-Type": ARROW_CONTENT_TYPE };
129
+ if (options?.authorization) {
130
+ headers.Authorization = options.authorization;
131
+ }
132
+
124
133
  const response = await fetch(`${baseUrl}${prefix}/${DESCRIBE_METHOD_NAME}`, {
125
134
  method: "POST",
126
- headers: { "Content-Type": ARROW_CONTENT_TYPE },
135
+ headers,
127
136
  body: body as unknown as BodyInit,
128
137
  });
138
+ if (response.status === 401) {
139
+ throw new RpcError("AuthenticationError", "Authentication required", "");
140
+ }
129
141
 
130
142
  const responseBody = new Uint8Array(await response.arrayBuffer());
131
143
  const { batches } = await readResponseBatches(responseBody);
@@ -0,0 +1,74 @@
1
+ // © Copyright 2025-2026, Query.Farm LLC - https://query.farm
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /** RFC 9728 OAuth Protected Resource Metadata (client-side response). */
5
+ export interface OAuthResourceMetadataResponse {
6
+ resource: string;
7
+ authorizationServers: string[];
8
+ scopesSupported?: string[];
9
+ bearerMethodsSupported?: string[];
10
+ resourceName?: string;
11
+ resourceDocumentation?: string;
12
+ resourcePolicyUri?: string;
13
+ resourceTosUri?: string;
14
+ }
15
+
16
+ function parseMetadataJson(json: Record<string, any>): OAuthResourceMetadataResponse {
17
+ const result: OAuthResourceMetadataResponse = {
18
+ resource: json.resource,
19
+ authorizationServers: json.authorization_servers,
20
+ };
21
+ if (json.scopes_supported) result.scopesSupported = json.scopes_supported;
22
+ if (json.bearer_methods_supported) result.bearerMethodsSupported = json.bearer_methods_supported;
23
+ if (json.resource_name) result.resourceName = json.resource_name;
24
+ if (json.resource_documentation) result.resourceDocumentation = json.resource_documentation;
25
+ if (json.resource_policy_uri) result.resourcePolicyUri = json.resource_policy_uri;
26
+ if (json.resource_tos_uri) result.resourceTosUri = json.resource_tos_uri;
27
+ return result;
28
+ }
29
+
30
+ /**
31
+ * Discover OAuth Protected Resource Metadata (RFC 9728) from a vgi-rpc server.
32
+ * Returns `null` if the server does not serve the well-known endpoint.
33
+ */
34
+ export async function httpOAuthMetadata(
35
+ baseUrl: string,
36
+ prefix?: string,
37
+ ): Promise<OAuthResourceMetadataResponse | null> {
38
+ const effectivePrefix = (prefix ?? "/vgi").replace(/\/+$/, "");
39
+ const metadataUrl = `${baseUrl.replace(/\/+$/, "")}/.well-known/oauth-protected-resource${effectivePrefix}`;
40
+
41
+ try {
42
+ return await fetchOAuthMetadata(metadataUrl);
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Fetch OAuth Protected Resource Metadata from an explicit metadata URL.
50
+ */
51
+ export async function fetchOAuthMetadata(metadataUrl: string): Promise<OAuthResourceMetadataResponse> {
52
+ const response = await fetch(metadataUrl);
53
+ if (!response.ok) {
54
+ throw new Error(`Failed to fetch OAuth metadata from ${metadataUrl}: ${response.status}`);
55
+ }
56
+ const json = await response.json();
57
+ return parseMetadataJson(json);
58
+ }
59
+
60
+ /**
61
+ * Extract the `resource_metadata` URL from a WWW-Authenticate Bearer challenge.
62
+ * Returns `null` if no resource_metadata parameter is found.
63
+ */
64
+ export function parseResourceMetadataUrl(wwwAuthenticate: string): string | null {
65
+ // Parse Bearer challenge parameters per RFC 6750
66
+ const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
67
+ if (!bearerMatch) return null;
68
+
69
+ const params = bearerMatch[1];
70
+ const metadataMatch = params.match(/resource_metadata="([^"]+)"/);
71
+ if (!metadataMatch) return null;
72
+
73
+ return metadataMatch[1];
74
+ }
@@ -25,6 +25,7 @@ export class HttpStreamSession implements StreamSession {
25
25
  private _compressionLevel?: number;
26
26
  private _compressFn?: CompressFn;
27
27
  private _decompressFn?: DecompressFn;
28
+ private _authorization?: string;
28
29
 
29
30
  constructor(opts: {
30
31
  baseUrl: string;
@@ -40,6 +41,7 @@ export class HttpStreamSession implements StreamSession {
40
41
  compressionLevel?: number;
41
42
  compressFn?: CompressFn;
42
43
  decompressFn?: DecompressFn;
44
+ authorization?: string;
43
45
  }) {
44
46
  this._baseUrl = opts.baseUrl;
45
47
  this._prefix = opts.prefix;
@@ -54,6 +56,7 @@ export class HttpStreamSession implements StreamSession {
54
56
  this._compressionLevel = opts.compressionLevel;
55
57
  this._compressFn = opts.compressFn;
56
58
  this._decompressFn = opts.decompressFn;
59
+ this._authorization = opts.authorization;
57
60
  }
58
61
 
59
62
  get header(): Record<string, any> | null {
@@ -68,6 +71,9 @@ export class HttpStreamSession implements StreamSession {
68
71
  headers["Content-Encoding"] = "zstd";
69
72
  headers["Accept-Encoding"] = "zstd";
70
73
  }
74
+ if (this._authorization) {
75
+ headers.Authorization = this._authorization;
76
+ }
71
77
  return headers;
72
78
  }
73
79
 
@@ -154,6 +160,9 @@ export class HttpStreamSession implements StreamSession {
154
160
  headers: this._buildHeaders(),
155
161
  body: this._prepareBody(body) as unknown as BodyInit,
156
162
  });
163
+ if (resp.status === 401) {
164
+ throw new RpcError("AuthenticationError", "Authentication required", "");
165
+ }
157
166
 
158
167
  const responseBody = await this._readResponse(resp);
159
168
  const { batches: responseBatches } = await readResponseBatches(responseBody);
@@ -261,6 +270,9 @@ export class HttpStreamSession implements StreamSession {
261
270
  headers: this._buildHeaders(),
262
271
  body: this._prepareBody(body) as unknown as BodyInit,
263
272
  });
273
+ if (resp.status === 401) {
274
+ throw new RpcError("AuthenticationError", "Authentication required", "");
275
+ }
264
276
 
265
277
  return this._readResponse(resp);
266
278
  }
@@ -5,6 +5,8 @@ export interface HttpConnectOptions {
5
5
  prefix?: string;
6
6
  onLog?: (msg: LogMessage) => void;
7
7
  compressionLevel?: number;
8
+ /** Authorization header value (e.g. "Bearer <token>"). Sent with every request. */
9
+ authorization?: string;
8
10
  }
9
11
 
10
12
  export interface LogMessage {
@@ -107,14 +107,20 @@ export async function dispatchStream(
107
107
  let inputBatch = await reader.readNextBatch();
108
108
  if (!inputBatch) break;
109
109
 
110
- // Cast compatible input types when schema doesn't match exactly
110
+ // Cast compatible input types when schema doesn't match exactly.
111
+ // If conformance fails (e.g., completely different schemas like a dummy
112
+ // registration schema vs actual data), pass the original batch through —
113
+ // the exchange handler may handle dynamic schemas internally.
111
114
  if (expectedInputSchema && !isProducer && inputBatch.schema !== expectedInputSchema) {
112
115
  try {
113
116
  inputBatch = conformBatchToSchema(inputBatch, expectedInputSchema);
114
- } catch {
115
- throw new TypeError(
116
- `Input schema mismatch: expected ${expectedInputSchema}, got ${inputBatch.schema}`,
117
- );
117
+ } catch (e) {
118
+ if (e instanceof TypeError) {
119
+ // Field name/count mismatch propagate as error (matches Python behavior).
120
+ throw e;
121
+ }
122
+ // Other conformance failures: pass through for dynamic schema handlers.
123
+ console.debug?.(`Schema conformance skipped: ${e instanceof Error ? e.message : e}`);
118
124
  }
119
125
  }
120
126
 
@@ -0,0 +1,47 @@
1
+ // © Copyright 2025-2026, Query.Farm LLC - https://query.farm
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import type { AuthContext } from "../auth.js";
5
+
6
+ /** Async function that authenticates an incoming HTTP request. */
7
+ export type AuthenticateFn = (request: Request) => AuthContext | Promise<AuthContext>;
8
+
9
+ /** RFC 9728 OAuth Protected Resource Metadata. */
10
+ export interface OAuthResourceMetadata {
11
+ resource: string;
12
+ authorizationServers: string[];
13
+ scopesSupported?: string[];
14
+ bearerMethodsSupported?: string[];
15
+ resourceName?: string;
16
+ resourceDocumentation?: string;
17
+ resourcePolicyUri?: string;
18
+ resourceTosUri?: string;
19
+ }
20
+
21
+ /** Convert OAuthResourceMetadata to RFC 9728 snake_case JSON object. */
22
+ export function oauthResourceMetadataToJson(metadata: OAuthResourceMetadata): Record<string, any> {
23
+ const json: Record<string, any> = {
24
+ resource: metadata.resource,
25
+ authorization_servers: metadata.authorizationServers,
26
+ };
27
+ if (metadata.scopesSupported) json.scopes_supported = metadata.scopesSupported;
28
+ if (metadata.bearerMethodsSupported) json.bearer_methods_supported = metadata.bearerMethodsSupported;
29
+ if (metadata.resourceName) json.resource_name = metadata.resourceName;
30
+ if (metadata.resourceDocumentation) json.resource_documentation = metadata.resourceDocumentation;
31
+ if (metadata.resourcePolicyUri) json.resource_policy_uri = metadata.resourcePolicyUri;
32
+ if (metadata.resourceTosUri) json.resource_tos_uri = metadata.resourceTosUri;
33
+ return json;
34
+ }
35
+
36
+ /** Compute the well-known path for OAuth Protected Resource Metadata. */
37
+ export function wellKnownPath(prefix: string): string {
38
+ return `/.well-known/oauth-protected-resource${prefix}`;
39
+ }
40
+
41
+ /** Build a WWW-Authenticate header value with optional resource_metadata URL. */
42
+ export function buildWwwAuthenticateHeader(metadataUrl?: string): string {
43
+ if (metadataUrl) {
44
+ return `Bearer resource_metadata="${metadataUrl}"`;
45
+ }
46
+ return "Bearer";
47
+ }
@@ -1,12 +1,7 @@
1
1
  // © Copyright 2025-2026, Query.Farm LLC - https://query.farm
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import {
5
- type RecordBatch,
6
- RecordBatchReader,
7
- RecordBatchStreamWriter,
8
- type Schema,
9
- } from "@query-farm/apache-arrow";
4
+ import { type RecordBatch, RecordBatchReader, RecordBatchStreamWriter, type Schema } from "@query-farm/apache-arrow";
10
5
  import { conformBatchToSchema } from "../util/conform.js";
11
6
 
12
7
  export const ARROW_CONTENT_TYPE = "application/vnd.apache.arrow.stream";
@@ -2,6 +2,7 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import { RecordBatch, RecordBatchReader, Schema } from "@query-farm/apache-arrow";
5
+ import type { AuthContext } from "../auth.js";
5
6
  import { STATE_KEY } from "../constants.js";
6
7
  import { buildDescribeBatch, DESCRIBE_SCHEMA } from "../dispatch/describe.js";
7
8
  import type { MethodDefinition } from "../types.js";
@@ -28,6 +29,7 @@ export interface DispatchContext {
28
29
  serverId: string;
29
30
  maxStreamResponseBytes?: number;
30
31
  stateSerializer: StateSerializer;
32
+ authContext?: AuthContext;
31
33
  }
32
34
 
33
35
  /** Dispatch a __describe__ request. */
@@ -55,7 +57,7 @@ export async function httpDispatchUnary(
55
57
  throw new HttpRpcError(`Method name in request '${parsed.methodName}' does not match URL '${method.name}'`, 400);
56
58
  }
57
59
 
58
- const out = new OutputCollector(schema, true, ctx.serverId, parsed.requestId);
60
+ const out = new OutputCollector(schema, true, ctx.serverId, parsed.requestId, ctx.authContext);
59
61
 
60
62
  try {
61
63
  const result = await method.handler!(parsed.params, out);
@@ -107,7 +109,7 @@ export async function httpDispatchStreamInit(
107
109
  let headerBytes: Uint8Array | null = null;
108
110
  if (method.headerSchema && method.headerInit) {
109
111
  try {
110
- const headerOut = new OutputCollector(method.headerSchema, true, ctx.serverId, parsed.requestId);
112
+ const headerOut = new OutputCollector(method.headerSchema, true, ctx.serverId, parsed.requestId, ctx.authContext);
111
113
  const headerValues = method.headerInit(parsed.params, state, headerOut);
112
114
  const headerBatch = buildResultBatch(method.headerSchema, headerValues, ctx.serverId, parsed.requestId);
113
115
  const headerBatches = [...headerOut.batches.map((b) => b.batch), headerBatch];
@@ -205,7 +207,7 @@ export async function httpDispatchStreamExchange(
205
207
  // Exchange path — also handles exchange-registered methods acting as
206
208
  // producers (__isProducer=true). Use producer mode on the OutputCollector
207
209
  // when effectiveProducer so finish() is allowed.
208
- const out = new OutputCollector(outputSchema, effectiveProducer, ctx.serverId, null);
210
+ const out = new OutputCollector(outputSchema, effectiveProducer, ctx.serverId, null, ctx.authContext);
209
211
 
210
212
  // Cast compatible input types (e.g., decimal→double, int32→int64)
211
213
  const conformedBatch = conformBatchToSchema(reqBatch, inputSchema);
@@ -283,7 +285,7 @@ async function produceStreamResponse(
283
285
  let estimatedBytes = 0;
284
286
 
285
287
  while (true) {
286
- const out = new OutputCollector(outputSchema, true, ctx.serverId, requestId);
288
+ const out = new OutputCollector(outputSchema, true, ctx.serverId, requestId, ctx.authContext);
287
289
 
288
290
  try {
289
291
  if (method.producerFn) {
@@ -3,11 +3,13 @@
3
3
 
4
4
  import { randomBytes } from "node:crypto";
5
5
  import { Schema } from "@query-farm/apache-arrow";
6
+ import type { AuthContext } from "../auth.js";
6
7
  import { DESCRIBE_METHOD_NAME } from "../constants.js";
7
8
  import type { Protocol } from "../protocol.js";
8
9
  import { MethodType } from "../types.js";
9
10
  import { zstdCompress, zstdDecompress } from "../util/zstd.js";
10
11
  import { buildErrorBatch } from "../wire/response.js";
12
+ import { buildWwwAuthenticateHeader, oauthResourceMetadataToJson, wellKnownPath } from "./auth.js";
11
13
  import { ARROW_CONTENT_TYPE, arrowResponse, HttpRpcError, serializeIpcStream } from "./common.js";
12
14
  import {
13
15
  httpDispatchDescribe,
@@ -43,12 +45,16 @@ export function createHttpHandler(
43
45
  const maxStreamResponseBytes = options?.maxStreamResponseBytes;
44
46
  const serverId = options?.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
45
47
 
48
+ const authenticate = options?.authenticate;
49
+ const oauthMetadata = options?.oauthResourceMetadata;
50
+
46
51
  const methods = protocol.getMethods();
47
52
 
48
53
  const compressionLevel = options?.compressionLevel;
49
54
  const stateSerializer = options?.stateSerializer ?? jsonStateSerializer;
50
55
 
51
- const ctx = {
56
+ // ctx is built per-request to include authContext; base fields set here
57
+ const baseCtx = {
52
58
  signingKey,
53
59
  tokenTtl,
54
60
  serverId,
@@ -88,6 +94,20 @@ export function createHttpHandler(
88
94
  const url = new URL(request.url);
89
95
  const path = url.pathname;
90
96
 
97
+ // Well-known endpoint: RFC 9728 OAuth Protected Resource Metadata
98
+ if (oauthMetadata && path === wellKnownPath(prefix)) {
99
+ if (request.method !== "GET") {
100
+ return new Response("Method Not Allowed", { status: 405 });
101
+ }
102
+ const body = JSON.stringify(oauthResourceMetadataToJson(oauthMetadata));
103
+ const headers = new Headers({
104
+ "Content-Type": "application/json",
105
+ "Cache-Control": "public, max-age=3600",
106
+ });
107
+ addCorsHeaders(headers);
108
+ return new Response(body, { status: 200, headers });
109
+ }
110
+
91
111
  // CORS preflight
92
112
  if (request.method === "OPTIONS") {
93
113
  if (path === `${prefix}/__capabilities__`) {
@@ -135,6 +155,26 @@ export function createHttpHandler(
135
155
  body = zstdDecompress(body);
136
156
  }
137
157
 
158
+ // Build per-request dispatch context
159
+ const ctx = { ...baseCtx } as typeof baseCtx & { authContext?: AuthContext };
160
+
161
+ // Authentication
162
+ if (authenticate) {
163
+ try {
164
+ ctx.authContext = await authenticate(request);
165
+ } catch (error: any) {
166
+ const headers = new Headers({ "Content-Type": "text/plain" });
167
+ addCorsHeaders(headers);
168
+ if (oauthMetadata) {
169
+ const metadataUrl = new URL(request.url);
170
+ metadataUrl.pathname = wellKnownPath(prefix);
171
+ metadataUrl.search = "";
172
+ headers.set("WWW-Authenticate", buildWwwAuthenticateHeader(metadataUrl.toString()));
173
+ }
174
+ return new Response(error.message || "Unauthorized", { status: 401, headers });
175
+ }
176
+ }
177
+
138
178
  // Route: {prefix}/__describe__
139
179
  if (path === `${prefix}/${DESCRIBE_METHOD_NAME}`) {
140
180
  try {
package/src/http/index.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  // © Copyright 2025-2026, Query.Farm LLC - https://query.farm
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
+ export type { AuthenticateFn, OAuthResourceMetadata } from "./auth.js";
5
+ export { oauthResourceMetadataToJson } from "./auth.js";
4
6
  export { ARROW_CONTENT_TYPE } from "./common.js";
5
7
  export { createHttpHandler } from "./handler.js";
8
+ export type { JwtAuthenticateOptions } from "./jwt.js";
9
+ export { jwtAuthenticate } from "./jwt.js";
6
10
  export { type UnpackedToken, unpackStateToken } from "./token.js";
7
11
  export type { HttpHandlerOptions, StateSerializer } from "./types.js";
8
12
  export { jsonStateSerializer } from "./types.js";