@query-farm/vgi-rpc 0.3.4 → 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 (87) hide show
  1. package/README.md +47 -0
  2. package/dist/auth.d.ts +13 -0
  3. package/dist/auth.d.ts.map +1 -0
  4. package/dist/client/connect.d.ts.map +1 -1
  5. package/dist/client/index.d.ts +2 -0
  6. package/dist/client/index.d.ts.map +1 -1
  7. package/dist/client/introspect.d.ts +1 -0
  8. package/dist/client/introspect.d.ts.map +1 -1
  9. package/dist/client/oauth.d.ts +62 -0
  10. package/dist/client/oauth.d.ts.map +1 -0
  11. package/dist/client/pipe.d.ts +3 -0
  12. package/dist/client/pipe.d.ts.map +1 -1
  13. package/dist/client/stream.d.ts +5 -0
  14. package/dist/client/stream.d.ts.map +1 -1
  15. package/dist/client/types.d.ts +6 -0
  16. package/dist/client/types.d.ts.map +1 -1
  17. package/dist/constants.d.ts +3 -1
  18. package/dist/constants.d.ts.map +1 -1
  19. package/dist/dispatch/describe.d.ts.map +1 -1
  20. package/dist/dispatch/stream.d.ts +2 -1
  21. package/dist/dispatch/stream.d.ts.map +1 -1
  22. package/dist/dispatch/unary.d.ts +2 -1
  23. package/dist/dispatch/unary.d.ts.map +1 -1
  24. package/dist/external.d.ts +45 -0
  25. package/dist/external.d.ts.map +1 -0
  26. package/dist/gcs.d.ts +38 -0
  27. package/dist/gcs.d.ts.map +1 -0
  28. package/dist/http/auth.d.ts +32 -0
  29. package/dist/http/auth.d.ts.map +1 -0
  30. package/dist/http/bearer.d.ts +34 -0
  31. package/dist/http/bearer.d.ts.map +1 -0
  32. package/dist/http/dispatch.d.ts +4 -0
  33. package/dist/http/dispatch.d.ts.map +1 -1
  34. package/dist/http/handler.d.ts.map +1 -1
  35. package/dist/http/index.d.ts +8 -0
  36. package/dist/http/index.d.ts.map +1 -1
  37. package/dist/http/jwt.d.ts +21 -0
  38. package/dist/http/jwt.d.ts.map +1 -0
  39. package/dist/http/mtls.d.ts +78 -0
  40. package/dist/http/mtls.d.ts.map +1 -0
  41. package/dist/http/pages.d.ts +9 -0
  42. package/dist/http/pages.d.ts.map +1 -0
  43. package/dist/http/types.d.ts +22 -1
  44. package/dist/http/types.d.ts.map +1 -1
  45. package/dist/index.d.ts +4 -2
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +2576 -317
  48. package/dist/index.js.map +27 -18
  49. package/dist/otel.d.ts +47 -0
  50. package/dist/otel.d.ts.map +1 -0
  51. package/dist/s3.d.ts +43 -0
  52. package/dist/s3.d.ts.map +1 -0
  53. package/dist/server.d.ts +6 -0
  54. package/dist/server.d.ts.map +1 -1
  55. package/dist/types.d.ts +38 -2
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/wire/response.d.ts.map +1 -1
  58. package/package.json +46 -2
  59. package/src/auth.ts +31 -0
  60. package/src/client/connect.ts +28 -6
  61. package/src/client/index.ts +11 -0
  62. package/src/client/introspect.ts +15 -3
  63. package/src/client/oauth.ts +167 -0
  64. package/src/client/pipe.ts +19 -4
  65. package/src/client/stream.ts +32 -7
  66. package/src/client/types.ts +6 -0
  67. package/src/constants.ts +4 -1
  68. package/src/dispatch/describe.ts +20 -0
  69. package/src/dispatch/stream.ts +18 -4
  70. package/src/dispatch/unary.ts +6 -1
  71. package/src/external.ts +209 -0
  72. package/src/gcs.ts +86 -0
  73. package/src/http/auth.ts +110 -0
  74. package/src/http/bearer.ts +107 -0
  75. package/src/http/dispatch.ts +32 -10
  76. package/src/http/handler.ts +120 -3
  77. package/src/http/index.ts +14 -0
  78. package/src/http/jwt.ts +80 -0
  79. package/src/http/mtls.ts +298 -0
  80. package/src/http/pages.ts +298 -0
  81. package/src/http/types.ts +23 -1
  82. package/src/index.ts +32 -0
  83. package/src/otel.ts +161 -0
  84. package/src/s3.ts +94 -0
  85. package/src/server.ts +42 -8
  86. package/src/types.ts +51 -3
  87. package/src/wire/response.ts +28 -14
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
@@ -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. */
@@ -37,6 +42,36 @@ export interface MethodDefinition {
37
42
  defaults?: Record<string, any>;
38
43
  paramTypes?: Record<string, string>;
39
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
+ }
40
75
  export interface EmittedBatch {
41
76
  batch: RecordBatch;
42
77
  metadata?: Map<string, string>;
@@ -45,7 +80,7 @@ export interface EmittedBatch {
45
80
  * Accumulates output batches during a produce/exchange call.
46
81
  * Enforces that exactly one data batch is emitted per call (plus any number of log batches).
47
82
  */
48
- export declare class OutputCollector implements LogContext {
83
+ export declare class OutputCollector implements CallContext {
49
84
  private _batches;
50
85
  private _dataBatchIdx;
51
86
  private _finished;
@@ -53,7 +88,8 @@ export declare class OutputCollector implements LogContext {
53
88
  private _outputSchema;
54
89
  private _serverId;
55
90
  private _requestId;
56
- constructor(outputSchema: Schema, producerMode?: boolean, serverId?: string, requestId?: string | null);
91
+ readonly auth: AuthContext;
92
+ constructor(outputSchema: Schema, producerMode?: boolean, serverId?: string, requestId?: string | null, authContext?: AuthContext);
57
93
  get outputSchema(): Schema;
58
94
  get finished(): boolean;
59
95
  get batches(): EmittedBatch[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAyB,KAAK,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAG3F,oBAAY,UAAU;IACpB,KAAK,UAAU;IACf,MAAM,WAAW;CAClB;AAED,+CAA+C;AAC/C,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CACjF;AAED,wDAAwD;AACxD,MAAM,MAAM,YAAY,GAAG,CACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,GAAG,EAAE,UAAU,KACZ,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAExD,sFAAsF;AACtF,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACpF,0FAA0F;AAC1F,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE3F,sFAAsF;AACtF,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACpF,gFAAgF;AAChF,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE/G,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE3G,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;;GAGG;AACH,qBAAa,eAAgB,YAAW,UAAU;IAChD,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAgB;gBAEtB,YAAY,EAAE,MAAM,EAAE,YAAY,UAAO,EAAE,QAAQ,SAAK,EAAE,SAAS,GAAE,MAAM,GAAG,IAAW;IAOrG,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,IAAI,OAAO,IAAI,YAAY,EAAE,CAE5B;IAED,oEAAoE;IACpE,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAC9D,2GAA2G;IAC3G,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI;IAgB1C,iFAAiF;IACjF,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAQ1C,2FAA2F;IAC3F,MAAM,IAAI,IAAI;IASd,iDAAiD;IACjD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;CAIhF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -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.4",
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": [
@@ -22,10 +37,39 @@
22
37
  "src"
23
38
  ],
24
39
  "dependencies": {
25
- "@query-farm/apache-arrow": "*"
40
+ "@query-farm/apache-arrow": "*",
41
+ "oauth4webapi": "^3.8.5"
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
+ }
26
62
  },
27
63
  "devDependencies": {
28
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",
29
73
  "@types/bun": "latest"
30
74
  },
31
75
  "scripts": {
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,8 @@
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";
7
+ import { isExternalLocationBatch, resolveExternalLocation } from "../external.js";
6
8
  import { ARROW_CONTENT_TYPE } from "../http/common.js";
7
9
  import { httpIntrospect, type MethodInfo, type ServiceDescription } from "./introspect.js";
8
10
  import {
@@ -26,9 +28,11 @@ export interface RpcClient {
26
28
  }
27
29
 
28
30
  export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcClient {
29
- const prefix = (options?.prefix ?? "/vgi").replace(/\/+$/, "");
31
+ const prefix = (options?.prefix ?? "").replace(/\/+$/, "");
30
32
  const onLog = options?.onLog;
31
33
  const compressionLevel = options?.compressionLevel;
34
+ const authorization = options?.authorization;
35
+ const externalConfig = options?.externalLocation;
32
36
 
33
37
  let methodCache: Map<string, MethodInfo> | null = null;
34
38
  let compressFn: CompressFn | undefined;
@@ -55,6 +59,9 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
55
59
  headers["Content-Encoding"] = "zstd";
56
60
  headers["Accept-Encoding"] = "zstd";
57
61
  }
62
+ if (authorization) {
63
+ headers.Authorization = authorization;
64
+ }
58
65
  return headers;
59
66
  }
60
67
 
@@ -65,6 +72,12 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
65
72
  return content;
66
73
  }
67
74
 
75
+ function checkAuth(resp: Response): void {
76
+ if (resp.status === 401) {
77
+ throw new RpcError("AuthenticationError", "Authentication required", "");
78
+ }
79
+ }
80
+
68
81
  async function readResponse(resp: Response): Promise<Uint8Array<ArrayBuffer>> {
69
82
  let body = new Uint8Array(await resp.arrayBuffer());
70
83
  if (resp.headers.get("Content-Encoding") === "zstd" && decompressFn) {
@@ -75,7 +88,7 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
75
88
 
76
89
  async function ensureMethodCache(): Promise<Map<string, MethodInfo>> {
77
90
  if (methodCache) return methodCache;
78
- const desc = await httpIntrospect(baseUrl, { prefix });
91
+ const desc = await httpIntrospect(baseUrl, { prefix, authorization });
79
92
  methodCache = new Map(desc.methods.map((m) => [m.name, m]));
80
93
  return methodCache;
81
94
  }
@@ -98,16 +111,22 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
98
111
  headers: buildHeaders(),
99
112
  body: prepareBody(body) as unknown as BodyInit,
100
113
  });
114
+ checkAuth(resp);
101
115
 
102
116
  const responseBody = await readResponse(resp);
103
117
  const { batches } = await readResponseBatches(responseBody);
104
118
 
105
- // Process batches: dispatch logs, find result
119
+ // Process batches: dispatch logs, resolve external pointers, find result
106
120
  let resultBatch: RecordBatch | null = null;
107
- for (const batch of batches) {
121
+ for (let batch of batches) {
108
122
  if (batch.numRows === 0) {
109
- dispatchLogOrError(batch, onLog);
110
- 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
+ }
111
130
  }
112
131
  resultBatch = batch;
113
132
  }
@@ -146,6 +165,7 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
146
165
  headers: buildHeaders(),
147
166
  body: prepareBody(body) as unknown as BodyInit,
148
167
  });
168
+ checkAuth(resp);
149
169
 
150
170
  const responseBody = await readResponse(resp);
151
171
 
@@ -288,6 +308,8 @@ export function httpConnect(baseUrl: string, options?: HttpConnectOptions): RpcC
288
308
  compressionLevel,
289
309
  compressFn,
290
310
  decompressFn,
311
+ authorization,
312
+ externalConfig,
291
313
  });
292
314
  },
293
315
 
@@ -3,6 +3,17 @@
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 {
8
+ fetchOAuthMetadata,
9
+ httpOAuthMetadata,
10
+ parseClientId,
11
+ parseClientSecret,
12
+ parseDeviceCodeClientId,
13
+ parseDeviceCodeClientSecret,
14
+ parseResourceMetadataUrl,
15
+ parseUseIdTokenAsBearer,
16
+ } from "./oauth.js";
6
17
  export { PipeStreamSession, pipeConnect, subprocessConnect } from "./pipe.js";
7
18
  export { HttpStreamSession } from "./stream.js";
8
19
  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
- const prefix = options?.prefix ?? "/vgi";
120
+ export async function httpIntrospect(
121
+ baseUrl: string,
122
+ options?: { prefix?: string; authorization?: string },
123
+ ): Promise<ServiceDescription> {
124
+ const prefix = options?.prefix ?? "";
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,167 @@
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
+ resourceSigningAlgValuesSupported?: string[];
11
+ resourceName?: string;
12
+ resourceDocumentation?: string;
13
+ resourcePolicyUri?: string;
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;
25
+ }
26
+
27
+ function parseMetadataJson(json: Record<string, any>): OAuthResourceMetadataResponse {
28
+ const result: OAuthResourceMetadataResponse = {
29
+ resource: json.resource,
30
+ authorizationServers: json.authorization_servers,
31
+ };
32
+ if (json.scopes_supported) result.scopesSupported = json.scopes_supported;
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;
36
+ if (json.resource_name) result.resourceName = json.resource_name;
37
+ if (json.resource_documentation) result.resourceDocumentation = json.resource_documentation;
38
+ if (json.resource_policy_uri) result.resourcePolicyUri = json.resource_policy_uri;
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;
45
+ return result;
46
+ }
47
+
48
+ /**
49
+ * Discover OAuth Protected Resource Metadata (RFC 9728) from a vgi-rpc server.
50
+ * Returns `null` if the server does not serve the well-known endpoint.
51
+ */
52
+ export async function httpOAuthMetadata(
53
+ baseUrl: string,
54
+ prefix?: string,
55
+ ): Promise<OAuthResourceMetadataResponse | null> {
56
+ const effectivePrefix = (prefix ?? "").replace(/\/+$/, "");
57
+ const metadataUrl = `${baseUrl.replace(/\/+$/, "")}/.well-known/oauth-protected-resource${effectivePrefix}`;
58
+
59
+ try {
60
+ return await fetchOAuthMetadata(metadataUrl);
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Fetch OAuth Protected Resource Metadata from an explicit metadata URL.
68
+ */
69
+ export async function fetchOAuthMetadata(metadataUrl: string): Promise<OAuthResourceMetadataResponse> {
70
+ const response = await fetch(metadataUrl);
71
+ if (!response.ok) {
72
+ throw new Error(`Failed to fetch OAuth metadata from ${metadataUrl}: ${response.status}`);
73
+ }
74
+ const json = await response.json();
75
+ return parseMetadataJson(json);
76
+ }
77
+
78
+ /**
79
+ * Extract the `resource_metadata` URL from a WWW-Authenticate Bearer challenge.
80
+ * Returns `null` if no resource_metadata parameter is found.
81
+ */
82
+ export function parseResourceMetadataUrl(wwwAuthenticate: string): string | null {
83
+ // Parse Bearer challenge parameters per RFC 6750
84
+ const bearerMatch = wwwAuthenticate.match(/^Bearer\s+(.*)/i);
85
+ if (!bearerMatch) return null;
86
+
87
+ const params = bearerMatch[1];
88
+ const metadataMatch = params.match(/resource_metadata="([^"]+)"/);
89
+ if (!metadataMatch) return null;
90
+
91
+ return metadataMatch[1];
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
+ }