@query-farm/vgi-rpc 0.6.4 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/access-log.d.ts +55 -0
  2. package/dist/access-log.d.ts.map +1 -0
  3. package/dist/arrow/impl-arrowjs/index.d.ts +96 -0
  4. package/dist/arrow/impl-arrowjs/index.d.ts.map +1 -0
  5. package/dist/arrow/impl-flechette/index.d.ts +102 -0
  6. package/dist/arrow/impl-flechette/index.d.ts.map +1 -0
  7. package/dist/arrow/impl-flechette/message-meta.d.ts +11 -0
  8. package/dist/arrow/impl-flechette/message-meta.d.ts.map +1 -0
  9. package/dist/arrow/index.d.ts +4 -0
  10. package/dist/arrow/index.d.ts.map +1 -0
  11. package/dist/arrow/predicates.d.ts +44 -0
  12. package/dist/arrow/predicates.d.ts.map +1 -0
  13. package/dist/arrow/types.d.ts +62 -0
  14. package/dist/arrow/types.d.ts.map +1 -0
  15. package/dist/auth.d.ts +5 -0
  16. package/dist/auth.d.ts.map +1 -1
  17. package/dist/client/capabilities.d.ts +25 -0
  18. package/dist/client/capabilities.d.ts.map +1 -0
  19. package/dist/client/connect.d.ts +10 -0
  20. package/dist/client/connect.d.ts.map +1 -1
  21. package/dist/client/introspect.d.ts +21 -0
  22. package/dist/client/introspect.d.ts.map +1 -1
  23. package/dist/client/ipc.d.ts +8 -2
  24. package/dist/client/ipc.d.ts.map +1 -1
  25. package/dist/client/oauth.d.ts +9 -0
  26. package/dist/client/oauth.d.ts.map +1 -1
  27. package/dist/client/pipe.d.ts +24 -0
  28. package/dist/client/pipe.d.ts.map +1 -1
  29. package/dist/client/stream.d.ts +19 -2
  30. package/dist/client/stream.d.ts.map +1 -1
  31. package/dist/client/types.d.ts +23 -0
  32. package/dist/client/types.d.ts.map +1 -1
  33. package/dist/client/uploadUrl.d.ts +25 -0
  34. package/dist/client/uploadUrl.d.ts.map +1 -0
  35. package/dist/constants.d.ts +30 -2
  36. package/dist/constants.d.ts.map +1 -1
  37. package/dist/crypto.d.ts +22 -0
  38. package/dist/crypto.d.ts.map +1 -0
  39. package/dist/dispatch/describe.d.ts +10 -6
  40. package/dist/dispatch/describe.d.ts.map +1 -1
  41. package/dist/dispatch/stream.d.ts +2 -2
  42. package/dist/dispatch/stream.d.ts.map +1 -1
  43. package/dist/dispatch/unary.d.ts +2 -2
  44. package/dist/dispatch/unary.d.ts.map +1 -1
  45. package/dist/errors.d.ts +64 -1
  46. package/dist/errors.d.ts.map +1 -1
  47. package/dist/external.d.ts +27 -5
  48. package/dist/external.d.ts.map +1 -1
  49. package/dist/http/auth.d.ts +13 -0
  50. package/dist/http/auth.d.ts.map +1 -1
  51. package/dist/http/bearer.d.ts.map +1 -1
  52. package/dist/http/common.d.ts +43 -7
  53. package/dist/http/common.d.ts.map +1 -1
  54. package/dist/http/dispatch.d.ts +20 -2
  55. package/dist/http/dispatch.d.ts.map +1 -1
  56. package/dist/http/handler.d.ts.map +1 -1
  57. package/dist/http/index.d.ts +1 -0
  58. package/dist/http/index.d.ts.map +1 -1
  59. package/dist/http/jwt.d.ts +1 -0
  60. package/dist/http/jwt.d.ts.map +1 -1
  61. package/dist/http/mtls.d.ts +9 -1
  62. package/dist/http/mtls.d.ts.map +1 -1
  63. package/dist/http/oauth-pkce.d.ts +141 -0
  64. package/dist/http/oauth-pkce.d.ts.map +1 -0
  65. package/dist/http/pages.d.ts +3 -0
  66. package/dist/http/pages.d.ts.map +1 -1
  67. package/dist/http/sticky.d.ts +124 -0
  68. package/dist/http/sticky.d.ts.map +1 -0
  69. package/dist/http/token.d.ts +43 -12
  70. package/dist/http/token.d.ts.map +1 -1
  71. package/dist/http/types.d.ts +68 -5
  72. package/dist/http/types.d.ts.map +1 -1
  73. package/dist/index.d.ts +6 -4
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +1275 -3511
  76. package/dist/index.js.map +20 -38
  77. package/dist/launcher/hash.d.ts +22 -0
  78. package/dist/launcher/hash.d.ts.map +1 -0
  79. package/dist/launcher/index.d.ts +23 -0
  80. package/dist/launcher/index.d.ts.map +1 -0
  81. package/dist/launcher/launch.d.ts +27 -0
  82. package/dist/launcher/launch.d.ts.map +1 -0
  83. package/dist/launcher/lock.d.ts +19 -0
  84. package/dist/launcher/lock.d.ts.map +1 -0
  85. package/dist/launcher/serve-unix.d.ts +55 -0
  86. package/dist/launcher/serve-unix.d.ts.map +1 -0
  87. package/dist/launcher/state.d.ts +71 -0
  88. package/dist/launcher/state.d.ts.map +1 -0
  89. package/dist/otel.d.ts.map +1 -1
  90. package/dist/protocol.d.ts +19 -2
  91. package/dist/protocol.d.ts.map +1 -1
  92. package/dist/schema.d.ts +45 -18
  93. package/dist/schema.d.ts.map +1 -1
  94. package/dist/server.d.ts +23 -2
  95. package/dist/server.d.ts.map +1 -1
  96. package/dist/types.d.ts +270 -12
  97. package/dist/types.d.ts.map +1 -1
  98. package/dist/util/gzip.d.ts +10 -0
  99. package/dist/util/gzip.d.ts.map +1 -0
  100. package/dist/util/schema.d.ts +3 -15
  101. package/dist/util/schema.d.ts.map +1 -1
  102. package/dist/util/web-crypto.d.ts +22 -0
  103. package/dist/util/web-crypto.d.ts.map +1 -0
  104. package/dist/util/zstd.d.ts +26 -3
  105. package/dist/util/zstd.d.ts.map +1 -1
  106. package/dist/wire/opaque.d.ts +11 -0
  107. package/dist/wire/opaque.d.ts.map +1 -0
  108. package/dist/wire/reader.d.ts +5 -5
  109. package/dist/wire/reader.d.ts.map +1 -1
  110. package/dist/wire/request.d.ts +11 -3
  111. package/dist/wire/request.d.ts.map +1 -1
  112. package/dist/wire/response.d.ts +6 -6
  113. package/dist/wire/response.d.ts.map +1 -1
  114. package/dist/wire/writer.d.ts +49 -39
  115. package/dist/wire/writer.d.ts.map +1 -1
  116. package/package.json +35 -21
  117. package/src/access-log.ts +200 -0
  118. package/src/arrow/impl-arrowjs/index.ts +433 -0
  119. package/src/arrow/impl-flechette/index.ts +414 -0
  120. package/src/arrow/impl-flechette/message-meta.ts +174 -0
  121. package/src/arrow/index.ts +89 -0
  122. package/src/arrow/predicates.ts +56 -0
  123. package/src/arrow/types.ts +73 -0
  124. package/src/auth.ts +5 -0
  125. package/src/client/capabilities.ts +84 -0
  126. package/src/client/connect.ts +113 -26
  127. package/src/client/introspect.ts +74 -38
  128. package/src/client/ipc.ts +37 -27
  129. package/src/client/oauth.ts +9 -0
  130. package/src/client/pipe.ts +36 -9
  131. package/src/client/stream.ts +43 -20
  132. package/src/client/types.ts +23 -0
  133. package/src/client/uploadUrl.ts +169 -0
  134. package/src/constants.ts +34 -2
  135. package/src/crypto.ts +95 -0
  136. package/src/dispatch/describe.ts +146 -107
  137. package/src/dispatch/stream.ts +53 -24
  138. package/src/dispatch/unary.ts +5 -4
  139. package/src/errors.ts +87 -0
  140. package/src/external.ts +49 -30
  141. package/src/http/auth.ts +13 -0
  142. package/src/http/bearer.ts +2 -5
  143. package/src/http/common.ts +91 -23
  144. package/src/http/dispatch.ts +373 -46
  145. package/src/http/handler.ts +790 -68
  146. package/src/http/index.ts +1 -0
  147. package/src/http/jwt.ts +1 -0
  148. package/src/http/mtls.ts +25 -3
  149. package/src/http/oauth-pkce.ts +1035 -0
  150. package/src/http/pages.ts +30 -15
  151. package/src/http/sticky.ts +429 -0
  152. package/src/http/token.ts +170 -75
  153. package/src/http/types.ts +69 -5
  154. package/src/index.ts +40 -1
  155. package/src/launcher/hash.ts +104 -0
  156. package/src/launcher/index.ts +35 -0
  157. package/src/launcher/launch.ts +284 -0
  158. package/src/launcher/lock.ts +171 -0
  159. package/src/launcher/serve-unix.ts +386 -0
  160. package/src/launcher/state.ts +257 -0
  161. package/src/otel.ts +39 -33
  162. package/src/protocol.ts +30 -3
  163. package/src/schema.ts +107 -56
  164. package/src/server.ts +196 -20
  165. package/src/types.ts +376 -18
  166. package/src/util/gzip.ts +63 -0
  167. package/src/util/schema.ts +4 -22
  168. package/src/util/web-crypto.ts +98 -0
  169. package/src/util/zstd.ts +133 -14
  170. package/src/wire/opaque.ts +37 -0
  171. package/src/wire/reader.ts +5 -4
  172. package/src/wire/request.ts +67 -8
  173. package/src/wire/response.ts +51 -85
  174. package/src/wire/writer.ts +165 -69
  175. package/dist/util/conform.d.ts +0 -18
  176. package/dist/util/conform.d.ts.map +0 -1
  177. package/src/util/conform.ts +0 -94
package/src/otel.ts CHANGED
@@ -119,42 +119,48 @@ export function createOtelHook(config?: OtelConfig): DispatchHook {
119
119
  const durationS = (performance.now() - t.startTime) / 1000;
120
120
  const status = error ? "error" : "ok";
121
121
 
122
- // Finalize span
123
- if (t.span) {
124
- if (stats) {
125
- t.span.setAttributes({
126
- "rpc.vgi_rpc.input_batches": stats.inputBatches,
127
- "rpc.vgi_rpc.output_batches": stats.outputBatches,
128
- "rpc.vgi_rpc.input_rows": stats.inputRows,
129
- "rpc.vgi_rpc.output_rows": stats.outputRows,
130
- "rpc.vgi_rpc.input_bytes": stats.inputBytes,
131
- "rpc.vgi_rpc.output_bytes": stats.outputBytes,
132
- });
133
- }
122
+ // Finalize span — wrap in try/finally so metric recording still
123
+ // runs if an exporter inside span.end() raises. Mirrors the Python
124
+ // fix that detaches the otel context unconditionally; TS doesn't
125
+ // attach context, but the same shape protects metric counters from
126
+ // a single misbehaving exporter taking out request observability.
127
+ try {
128
+ if (t.span) {
129
+ if (stats) {
130
+ t.span.setAttributes({
131
+ "rpc.vgi_rpc.input_batches": stats.inputBatches,
132
+ "rpc.vgi_rpc.output_batches": stats.outputBatches,
133
+ "rpc.vgi_rpc.input_rows": stats.inputRows,
134
+ "rpc.vgi_rpc.output_rows": stats.outputRows,
135
+ "rpc.vgi_rpc.input_bytes": stats.inputBytes,
136
+ "rpc.vgi_rpc.output_bytes": stats.outputBytes,
137
+ });
138
+ }
134
139
 
135
- if (error) {
136
- t.span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
137
- t.span.setAttribute("rpc.vgi_rpc.error_type", error.constructor.name);
138
- if (recordExceptions) {
139
- t.span.recordException(error);
140
+ if (error) {
141
+ t.span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
142
+ t.span.setAttribute("rpc.vgi_rpc.error_type", error.constructor.name);
143
+ if (recordExceptions) {
144
+ t.span.recordException(error);
145
+ }
146
+ } else {
147
+ t.span.setStatus({ code: SpanStatusCode.OK });
140
148
  }
141
- } else {
142
- t.span.setStatus({ code: SpanStatusCode.OK });
149
+ t.span.end();
150
+ }
151
+ } finally {
152
+ // Record metrics — runs even if span finalisation above raises.
153
+ if (enableMetrics) {
154
+ const metricAttrs: Attributes = {
155
+ "rpc.system": "vgi_rpc",
156
+ "rpc.service": serviceName,
157
+ "rpc.method": info.method,
158
+ "rpc.vgi_rpc.method_type": info.methodType,
159
+ status,
160
+ };
161
+ requestCounter?.add(1, metricAttrs);
162
+ durationHistogram?.record(durationS, metricAttrs);
143
163
  }
144
- t.span.end();
145
- }
146
-
147
- // Record metrics
148
- if (enableMetrics) {
149
- const metricAttrs: Attributes = {
150
- "rpc.system": "vgi_rpc",
151
- "rpc.service": serviceName,
152
- "rpc.method": info.method,
153
- "rpc.vgi_rpc.method_type": info.methodType,
154
- status,
155
- };
156
- requestCounter?.add(1, metricAttrs);
157
- durationHistogram?.record(durationS, metricAttrs);
158
164
  }
159
165
  },
160
166
  };
package/src/protocol.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  // © Copyright 2025-2026, Query.Farm LLC - https://query.farm
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import { Schema } from "@query-farm/apache-arrow";
4
+ import { schema as makeSchema } from "./arrow/index.js";
5
+ import { parseProtocolVersion } from "./errors.js";
5
6
  import { inferParamTypes, type SchemaLike, toSchema } from "./schema.js";
6
7
  import {
7
8
  type ExchangeFn,
@@ -9,23 +10,43 @@ import {
9
10
  type HeaderInit,
10
11
  type MethodDefinition,
11
12
  MethodType,
13
+ type OnCancelFn,
12
14
  type ProducerFn,
13
15
  type ProducerInit,
14
16
  type UnaryHandler,
15
17
  } from "./types.js";
16
18
 
17
- const EMPTY_SCHEMA = new Schema([]);
19
+ const EMPTY_SCHEMA = makeSchema([]);
18
20
 
19
21
  /**
20
22
  * Fluent builder for defining RPC methods.
21
23
  * Register unary, producer, and exchange methods, then pass to `VgiRpcServer`.
22
24
  */
23
25
  export class Protocol {
26
+ /** Service / protocol name, exposed to clients via introspection. */
24
27
  readonly name: string;
28
+ /**
29
+ * Application protocol surface version. When non-empty, the server enforces
30
+ * exact major+minor match (patch ignored) against every request's
31
+ * `vgi_rpc.protocol_version` metadata; clients bound to this Protocol emit
32
+ * the value on every request. Format: canonical semver MAJOR.MINOR.PATCH.
33
+ * Mirrors Python's `Protocol.protocol_version` ClassVar.
34
+ */
35
+ readonly protocolVersion: string;
36
+ /** Parsed semver tuple; null when `protocolVersion` is unset. */
37
+ readonly protocolVersionParts: readonly [number, number, number] | null;
25
38
  private _methods: Map<string, MethodDefinition> = new Map();
26
39
 
27
- constructor(name: string) {
40
+ constructor(name: string, options?: { protocolVersion?: string }) {
28
41
  this.name = name;
42
+ const raw = options?.protocolVersion;
43
+ if (raw === undefined || raw === "") {
44
+ this.protocolVersion = "";
45
+ this.protocolVersionParts = null;
46
+ } else {
47
+ this.protocolVersion = raw;
48
+ this.protocolVersionParts = parseProtocolVersion(raw);
49
+ }
29
50
  }
30
51
 
31
52
  /**
@@ -74,6 +95,7 @@ export class Protocol {
74
95
  outputSchema: SchemaLike;
75
96
  init: ProducerInit<S>;
76
97
  produce: ProducerFn<S>;
98
+ onCancel?: OnCancelFn<S>;
77
99
  headerSchema?: SchemaLike;
78
100
  headerInit?: HeaderInit;
79
101
  doc?: string;
@@ -91,6 +113,7 @@ export class Protocol {
91
113
  inputSchema: EMPTY_SCHEMA,
92
114
  producerInit: config.init as ProducerInit,
93
115
  producerFn: config.produce as ProducerFn,
116
+ onCancel: config.onCancel as OnCancelFn | undefined,
94
117
  headerSchema: config.headerSchema ? toSchema(config.headerSchema) : undefined,
95
118
  headerInit: config.headerInit,
96
119
  doc: config.doc,
@@ -112,6 +135,7 @@ export class Protocol {
112
135
  outputSchema: SchemaLike;
113
136
  init: ExchangeInit<S>;
114
137
  exchange: ExchangeFn<S>;
138
+ onCancel?: OnCancelFn<S>;
115
139
  headerSchema?: SchemaLike;
116
140
  headerInit?: HeaderInit;
117
141
  doc?: string;
@@ -129,6 +153,7 @@ export class Protocol {
129
153
  outputSchema: toSchema(config.outputSchema),
130
154
  exchangeInit: config.init as ExchangeInit,
131
155
  exchangeFn: config.exchange as ExchangeFn,
156
+ onCancel: config.onCancel as OnCancelFn | undefined,
132
157
  headerSchema: config.headerSchema ? toSchema(config.headerSchema) : undefined,
133
158
  headerInit: config.headerInit,
134
159
  doc: config.doc,
@@ -138,6 +163,8 @@ export class Protocol {
138
163
  return this;
139
164
  }
140
165
 
166
+ /** Snapshot of the registered methods, keyed by method name. Returns a copy,
167
+ * so mutating it does not affect the protocol. */
141
168
  getMethods(): Map<string, MethodDefinition> {
142
169
  return new Map(this._methods);
143
170
  }
package/src/schema.ts CHANGED
@@ -2,109 +2,160 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import {
5
- Binary,
6
- Bool,
7
- DataType,
8
- Field,
9
- Float32,
10
- Float64,
11
- Int16,
12
- Int32,
13
- Int64,
14
- Schema,
15
- Utf8,
16
- } from "@query-farm/apache-arrow";
5
+ binary,
6
+ bool as boolType,
7
+ float32 as float32Type,
8
+ float64 as float64Type,
9
+ int8 as int8Type,
10
+ int16 as int16Type,
11
+ int32 as int32Type,
12
+ int64 as int64Type,
13
+ isBinary,
14
+ isBool,
15
+ isFloat,
16
+ isInt,
17
+ isUtf8,
18
+ field as makeField,
19
+ schema as makeSchema,
20
+ uint8 as uint8Type,
21
+ uint16 as uint16Type,
22
+ uint32 as uint32Type,
23
+ uint64 as uint64Type,
24
+ utf8,
25
+ type VgiDataType,
26
+ type VgiField,
27
+ type VgiSchema,
28
+ } from "./arrow/index.js";
17
29
 
18
30
  // ---------------------------------------------------------------------------
19
31
  // Convenient DataType singletons — re-export so users avoid arrow imports
20
32
  // ---------------------------------------------------------------------------
21
33
 
22
34
  /** Apache Arrow Utf8 type. Use as schema shorthand: `{ name: str }` */
23
- export const str = new Utf8();
35
+ export const str = utf8();
24
36
  /** Apache Arrow Binary type. Use as schema shorthand: `{ data: bytes }` */
25
- export const bytes = new Binary();
37
+ export const bytes = binary();
26
38
  /** Apache Arrow Int64 type. Use as schema shorthand: `{ count: int }` */
27
- export const int = new Int64();
39
+ export const int = int64Type();
28
40
  /** Apache Arrow Int32 type. Use as schema shorthand: `{ count: int32 }` */
29
- export const int32 = new Int32();
41
+ export const int32 = int32Type();
42
+ /** Apache Arrow Int16 type. Use as schema shorthand: `{ count: int16 }` */
43
+ export const int16 = int16Type();
44
+ /** Apache Arrow Int8 type. Use as schema shorthand: `{ count: int8 }` */
45
+ export const int8 = int8Type();
46
+ /** Apache Arrow Uint8 type. Use as schema shorthand: `{ count: uint8 }` */
47
+ export const uint8 = uint8Type();
48
+ /** Apache Arrow Uint16 type. Use as schema shorthand: `{ count: uint16 }` */
49
+ export const uint16 = uint16Type();
50
+ /** Apache Arrow Uint32 type. Use as schema shorthand: `{ count: uint32 }` */
51
+ export const uint32 = uint32Type();
52
+ /** Apache Arrow Uint64 type. Use as schema shorthand: `{ count: uint64 }` */
53
+ export const uint64 = uint64Type();
30
54
  /** Apache Arrow Float64 type. Use as schema shorthand: `{ value: float }` */
31
- export const float = new Float64();
55
+ export const float = float64Type();
32
56
  /** Apache Arrow Float32 type. Use as schema shorthand: `{ value: float32 }` */
33
- export const float32 = new Float32();
57
+ export const float32 = float32Type();
34
58
  /** Apache Arrow Bool type. Use as schema shorthand: `{ flag: bool }` */
35
- export const bool = new Bool();
59
+ export const bool = boolType();
36
60
 
37
61
  // ---------------------------------------------------------------------------
38
62
  // SchemaLike — shorthand for declaring schemas
39
63
  // ---------------------------------------------------------------------------
40
64
 
65
+ /**
66
+ * Structural minimum that any backend's Schema must satisfy. arrow-js's
67
+ * `Schema`, vgi-typescript's `VgiSchema`, and flechette's `Schema` all match
68
+ * this shape. Used so vgi-rpc consumers don't have to know which Arrow
69
+ * library is on the other side of the wire.
70
+ *
71
+ * Kept exported for backwards compatibility — equivalent to `VgiSchema`.
72
+ */
73
+ export interface SchemaShape {
74
+ readonly fields: ReadonlyArray<{
75
+ readonly name: string;
76
+ readonly type: { readonly typeId: number };
77
+ readonly nullable?: boolean;
78
+ readonly metadata?: Map<string, string>;
79
+ }>;
80
+ readonly metadata?: Map<string, string> | null;
81
+ }
82
+
41
83
  /**
42
84
  * A schema specification that accepts:
43
- * - A real `Schema` (passed through)
44
- * - A record mapping field names to `DataType` instances or `Field` instances
85
+ * - A real `VgiSchema` (passed through)
86
+ * - Anything structurally `SchemaShape`
87
+ * - A record mapping field names to `VgiDataType` instances or `VgiField` instances
45
88
  * - An empty `{}` for an empty schema
46
89
  */
47
- export type SchemaLike = Schema | Record<string, DataType | Field>;
90
+ export type SchemaLike = VgiSchema | SchemaShape | Record<string, VgiDataType | VgiField>;
91
+
92
+ function isField(x: unknown): x is VgiField {
93
+ return (
94
+ x != null &&
95
+ typeof (x as any).name === "string" &&
96
+ (x as any).type != null &&
97
+ typeof (x as any).nullable === "boolean"
98
+ );
99
+ }
100
+
101
+ function isDataType(x: unknown): x is VgiDataType {
102
+ return x != null && typeof (x as any).typeId === "number";
103
+ }
48
104
 
49
105
  /**
50
- * Convert a SchemaLike spec into a real `Schema`.
51
- *
52
- * - `Schema` → returned as-is
53
- * - `Record<string, DataType>` → each entry becomes `new Field(name, type, false)`
54
- * - `Record<string, Field>` → each entry is passed through
55
- * - `{}` → `new Schema([])`
106
+ * Convert a SchemaLike spec into a real `VgiSchema`.
56
107
  */
57
- export function toSchema(spec: SchemaLike): Schema {
58
- if (spec instanceof Schema) return spec;
108
+ export function toSchema(spec: SchemaLike): VgiSchema {
109
+ // VgiSchema / SchemaShape branch: anything with a `fields` array.
110
+ const maybeFields = (spec as { fields?: unknown }).fields;
111
+ if (Array.isArray(maybeFields)) {
112
+ const out: VgiField[] = [];
113
+ for (const f of maybeFields as ReadonlyArray<any>) {
114
+ if (isField(f)) {
115
+ out.push(f);
116
+ } else {
117
+ out.push(makeField(f.name, f.type, f.nullable ?? true, f.metadata));
118
+ }
119
+ }
120
+ return makeSchema(out);
121
+ }
59
122
 
60
- const fields: Field[] = [];
123
+ const fields: VgiField[] = [];
61
124
  for (const [name, value] of Object.entries(spec)) {
62
- if (value instanceof Field) {
125
+ if (isField(value)) {
63
126
  fields.push(value);
64
- } else if (value instanceof DataType) {
65
- fields.push(new Field(name, value, false));
127
+ } else if (isDataType(value)) {
128
+ fields.push(makeField(name, value, false));
66
129
  } else {
67
130
  throw new TypeError(`Invalid schema value for "${name}": expected DataType or Field, got ${typeof value}`);
68
131
  }
69
132
  }
70
- return new Schema(fields);
133
+ return makeSchema(fields);
71
134
  }
72
135
 
73
136
  // ---------------------------------------------------------------------------
74
137
  // inferParamTypes — derive paramTypes from a schema spec
75
138
  // ---------------------------------------------------------------------------
76
139
 
77
- const TYPE_MAP: [new (...args: any[]) => DataType, string][] = [
78
- [Utf8, "str"],
79
- [Binary, "bytes"],
80
- [Bool, "bool"],
81
- [Float64, "float"],
82
- [Float32, "float"],
83
- [Int64, "int"],
84
- [Int32, "int"],
85
- [Int16, "int"],
86
- ];
87
-
88
140
  /**
89
141
  * Derive a `paramTypes` record from a SchemaLike spec.
90
142
  * Maps common Arrow scalar types to Python-style type strings.
91
143
  * Returns `undefined` if any field has a complex type (List, Map_, Dictionary, etc.).
92
144
  */
93
145
  export function inferParamTypes(spec: SchemaLike): Record<string, string> | undefined {
94
- const schema = toSchema(spec);
95
- if (schema.fields.length === 0) return undefined;
146
+ const sch = toSchema(spec);
147
+ if (sch.fields.length === 0) return undefined;
96
148
 
97
149
  const result: Record<string, string> = {};
98
- for (const field of schema.fields) {
150
+ for (const f of sch.fields) {
99
151
  let mapped: string | undefined;
100
- for (const [ctor, name] of TYPE_MAP) {
101
- if (field.type instanceof ctor) {
102
- mapped = name;
103
- break;
104
- }
105
- }
152
+ if (isUtf8(f.type)) mapped = "str";
153
+ else if (isBinary(f.type)) mapped = "bytes";
154
+ else if (isBool(f.type)) mapped = "bool";
155
+ else if (isFloat(f.type)) mapped = "float";
156
+ else if (isInt(f.type)) mapped = "int";
106
157
  if (!mapped) return undefined;
107
- result[field.name] = mapped;
158
+ result[f.name] = mapped;
108
159
  }
109
160
  return result;
110
161
  }