@query-farm/vgi 0.30.0 → 0.32.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 (48) hide show
  1. package/README.md +1 -1
  2. package/dist/catalog/item-encoding.d.ts +17 -0
  3. package/dist/catalog/read-only.d.ts +16 -0
  4. package/dist/client-entry.js +19 -1
  5. package/dist/client-entry.js.map +3 -3
  6. package/dist/filter-pushdown/evaluate.d.ts +9 -0
  7. package/dist/filter-pushdown/history.d.ts +66 -0
  8. package/dist/filter-pushdown/index.d.ts +1 -0
  9. package/dist/functions/storage-cf-do.d.ts +2 -2
  10. package/dist/functions/storage.d.ts +39 -3
  11. package/dist/functions/table-in-out.d.ts +13 -8
  12. package/dist/functions/types.d.ts +14 -1
  13. package/dist/http/fetch.d.ts +1 -1
  14. package/dist/index.core.d.ts +1 -1
  15. package/dist/index.js +1280 -924
  16. package/dist/index.js.map +21 -17
  17. package/dist/protocol/carried-init-request.d.ts +28 -0
  18. package/dist/protocol/dispatch.d.ts +26 -7
  19. package/dist/protocol/handlers/function.d.ts +0 -1
  20. package/dist/protocol/handlers/shared.d.ts +0 -7
  21. package/dist/protocol/state-serializer.d.ts +29 -2
  22. package/dist/serve-entry.js +395 -188
  23. package/dist/serve-entry.js.map +16 -12
  24. package/dist/util/zstd-sync.d.ts +7 -0
  25. package/dist/worker-cf-entry.js +5047 -4710
  26. package/dist/worker-cf-entry.js.map +25 -21
  27. package/package.json +5 -4
  28. package/src/catalog/item-encoding.ts +59 -0
  29. package/src/catalog/read-only.ts +87 -19
  30. package/src/client/errors.ts +19 -0
  31. package/src/filter-pushdown/evaluate.ts +55 -8
  32. package/src/filter-pushdown/history.ts +239 -0
  33. package/src/filter-pushdown/index.ts +8 -0
  34. package/src/functions/storage-cf-do.ts +7 -12
  35. package/src/functions/storage.ts +54 -11
  36. package/src/functions/table-buffering.ts +0 -1
  37. package/src/functions/table-in-out.ts +99 -60
  38. package/src/functions/table.ts +41 -38
  39. package/src/functions/types.ts +14 -1
  40. package/src/http/fetch.ts +1 -9
  41. package/src/index.core.ts +1 -0
  42. package/src/protocol/carried-init-request.ts +139 -0
  43. package/src/protocol/dispatch.ts +28 -9
  44. package/src/protocol/handlers/catalog/admin.ts +5 -2
  45. package/src/protocol/handlers/function.ts +18 -15
  46. package/src/protocol/handlers/shared.ts +0 -22
  47. package/src/protocol/state-serializer.ts +109 -37
  48. package/src/util/zstd-sync.ts +50 -0
@@ -11,6 +11,15 @@ export declare class PushdownFilters {
11
11
  constructor(predicates: FilterPredicate[], evaluationContext: EvaluationContext, options: DeserializeFilterOptions, revisions?: Map<string, number>, requiredIds?: Set<string>);
12
12
  get filters(): FilterExpression[];
13
13
  applyDelta(batch: VgiBatch): PushdownFilters;
14
+ /**
15
+ * These filters with their predicates arranged in `order`.
16
+ *
17
+ * Only the order changes, and `order` must name exactly the live predicate
18
+ * ids. Restores the order a stream had before its delta history was
19
+ * compacted: an id removed and later re-added moves to the end, which a
20
+ * shorter replay does not reproduce by itself.
21
+ */
22
+ withPredicateOrder(order: readonly string[]): PushdownFilters;
14
23
  evaluate(batch: VgiBatch): Uint8Array;
15
24
  apply(batch: VgiBatch): VgiBatch;
16
25
  filteredColumns(): string[];
@@ -0,0 +1,66 @@
1
+ import { type VgiBatch } from "../arrow/index.js";
2
+ import type { PushdownFilters } from "./evaluate.js";
3
+ /**
4
+ * What an HTTP stream carries between turns to rebuild its dynamic filters.
5
+ *
6
+ * Both halves are plain data so the state serializer can put them in the
7
+ * cursor, and both are bounded by the number of predicate ids the scan has
8
+ * seen (see {@link recordFilterDelta}).
9
+ */
10
+ export interface FilterDeltaHistory {
11
+ /** Arrow IPC bytes of each delta still needed, in arrival order. */
12
+ readonly deltas: readonly Uint8Array[];
13
+ /** The live predicate ids, in the order the stream had them. */
14
+ readonly order: readonly string[];
15
+ }
16
+ /** A stream that has applied no delta. */
17
+ export declare const EMPTY_FILTER_HISTORY: FilterDeltaHistory;
18
+ /**
19
+ * Decode the base64 `vgi_pushdown_filters` metadata value into delta IPC bytes.
20
+ *
21
+ * Validates the encoding strictly and enforces the encoded and decoded size
22
+ * limits before anything is parsed.
23
+ */
24
+ export declare function decodeDynamicFilterMetadata(encoded: string): Uint8Array;
25
+ /** Read one delta's IPC bytes into its single document batch. */
26
+ export declare function decodeFilterDelta(bytes: Uint8Array): VgiBatch;
27
+ /**
28
+ * Apply one tick's delta and return the new filters plus the history a cursor
29
+ * must carry to rebuild them.
30
+ *
31
+ * The history is compacted to the deltas that installed some predicate's
32
+ * CURRENT revision, tombstones included: for each `(id, revision)` of the live
33
+ * state, the first delta (in arrival order) carrying it. That delta is the one
34
+ * that was applied -- any later delta carrying the same revision was stale on
35
+ * arrival -- and a delta none of whose updates is current can never matter
36
+ * again, because revisions only move forward.
37
+ *
38
+ * @param current - The stream's filters before this delta.
39
+ * @param history - The compacted history those filters were rebuilt from.
40
+ * @param deltaBytes - The IPC bytes of the delta this tick carried.
41
+ */
42
+ export declare function recordFilterDelta(current: PushdownFilters, history: FilterDeltaHistory, deltaBytes: Uint8Array): {
43
+ filters: PushdownFilters;
44
+ history: FilterDeltaHistory;
45
+ };
46
+ /**
47
+ * Encode a history for the state token, or `null` when there is nothing to
48
+ * carry (no delta applied, which is every stream without dynamic filters).
49
+ *
50
+ * Layout (little-endian): `u8 version | u32 n | n x (u32 len, delta IPC) |
51
+ * u32 m | m x (u32 len, utf8 id)`.
52
+ */
53
+ export declare function encodeFilterHistory(history: FilterDeltaHistory | null | undefined): Uint8Array | null;
54
+ /** Decode {@link encodeFilterHistory}'s bytes; `null`/empty is the empty history. */
55
+ export declare function decodeFilterHistory(bytes: Uint8Array | null | undefined): FilterDeltaHistory;
56
+ /**
57
+ * Rebuild a turn's filters from the init snapshot plus a carried history.
58
+ *
59
+ * Every delta here was validated when it first arrived and has ridden a
60
+ * worker-sealed token since, but it is re-applied through the same strict
61
+ * parser -- a TS delta parse is structural, with no bind to skip.
62
+ *
63
+ * @param filters - The filters parsed from the init request's snapshot.
64
+ * @param history - The history {@link recordFilterDelta} produced.
65
+ */
66
+ export declare function replayFilterHistory(filters: PushdownFilters | undefined, history: FilterDeltaHistory | null | undefined): PushdownFilters | undefined;
@@ -1,4 +1,5 @@
1
1
  export { FILTER_ENCODING, FILTER_VERSION, DUCKDB_STANDARD_V1, NO_EVALUATION_CONTEXT, DUCKDB_SESSION_CONTEXT, ComparisonOp, type ArithmeticOp, type EvaluationContext, type Filter, type AndFilter, type ConstantFilter, type ExpressionFilter, type ExprNode, type InFilter, type IsNotNullFilter, type IsNullFilter, type OrFilter, type StructFilter, type FilterExpression, type FilterPredicate, type FilterSet, type FunctionIdentity, type PredicateMode, type PredicateSource, type StandardFilterFunction, } from "./types.js";
2
2
  export { PushdownFilters } from "./evaluate.js";
3
3
  export { FilterV2Error, applyFilterDelta, buildJoinKeysLookup, deserializeFilters } from "./deserialize.js";
4
+ export { EMPTY_FILTER_HISTORY, type FilterDeltaHistory, decodeDynamicFilterMetadata, recordFilterDelta, replayFilterHistory, } from "./history.js";
4
5
  export { FilteringOutputCollector, formatPushedFilters, reprPushedFilters, } from "./collector.js";
@@ -1,4 +1,4 @@
1
- import type { FunctionStorage } from "./storage.js";
1
+ import type { FunctionStorage, WorkerStateKey } from "./storage.js";
2
2
  /** Minimal fetch signature satisfied by both the global `fetch` and a
3
3
  * Cloudflare service-binding Fetcher (`env.<BINDING>`). */
4
4
  export type FetchLike = (input: string, init?: {
@@ -29,7 +29,7 @@ export declare class FunctionStorageCfDo implements FunctionStorage {
29
29
  * per logical ATTACH. Shares the URL/token; only the shard key differs.
30
30
  */
31
31
  forShard(shardKey: string): FunctionStorageCfDo;
32
- workerPut(executionId: Uint8Array, workerId: number, state: Uint8Array): Promise<void>;
32
+ workerPut(executionId: Uint8Array, worker: WorkerStateKey, state: Uint8Array): Promise<void>;
33
33
  workerCollect(executionId: Uint8Array): Promise<Uint8Array[]>;
34
34
  workerScan(executionId: Uint8Array): Promise<Array<[number, Uint8Array]>>;
35
35
  queuePush(executionId: Uint8Array, items: Uint8Array[]): Promise<number>;
@@ -1,9 +1,21 @@
1
1
  export declare class UnknownInvocationError extends Error {
2
2
  constructor(message?: string | Uint8Array);
3
3
  }
4
+ /**
5
+ * Which slot of an execution's worker state a row occupies: a table-in-out
6
+ * substream's client-minted `substream_id` (raw bytes), or -- only for a client
7
+ * that sent none -- a numeric worker id, the process id (an int64 key). See
8
+ * {@link BoundStorage.put}.
9
+ */
10
+ export type WorkerStateKey = number | Uint8Array;
4
11
  export interface FunctionStorage {
5
- workerPut(executionId: Uint8Array, workerId: number, state: Uint8Array): Promise<void>;
12
+ /** Upsert `state` into the execution's worker-state slot `worker`. */
13
+ workerPut(executionId: Uint8Array, worker: WorkerStateKey, state: Uint8Array): Promise<void>;
14
+ /** Drain every worker-state slot of the execution, whatever its key. */
6
15
  workerCollect(executionId: Uint8Array): Promise<Uint8Array[]>;
16
+ /** Read every worker-state slot without draining. A slot keyed by a
17
+ * numeric worker id reports that id; one keyed by bytes (a substream id)
18
+ * has none and reports 0. */
7
19
  workerScan(executionId: Uint8Array): Promise<Array<[number, Uint8Array]>>;
8
20
  queuePush(executionId: Uint8Array, items: Uint8Array[]): Promise<number>;
9
21
  queuePop(executionId: Uint8Array): Promise<Uint8Array | null>;
@@ -23,6 +35,9 @@ export declare const FrameworkNS: {
23
35
  };
24
36
  /** Encode an int as 8-byte little-endian, matching Python's pack_int_key. */
25
37
  export declare function packIntKey(i: number | bigint): Uint8Array;
38
+ /** The state key for a worker-state slot: a substream id as-is, a numeric
39
+ * worker id as an int64. */
40
+ export declare function workerStateKey(worker: WorkerStateKey): Uint8Array;
26
41
  export declare class FunctionStorageSqlite implements FunctionStorage {
27
42
  readonly dbPath: string;
28
43
  private _db;
@@ -34,7 +49,7 @@ export declare class FunctionStorageSqlite implements FunctionStorage {
34
49
  stateAppend(scopeId: Uint8Array, ns: Uint8Array, key: Uint8Array, item: Uint8Array): Promise<number>;
35
50
  stateLogScan(scopeId: Uint8Array, ns: Uint8Array, key: Uint8Array, afterId?: number, limit?: number | null): Promise<Array<[number, Uint8Array]>>;
36
51
  executionClear(scopeId: Uint8Array): Promise<number>;
37
- workerPut(executionId: Uint8Array, workerId: number, state: Uint8Array): Promise<void>;
52
+ workerPut(executionId: Uint8Array, worker: WorkerStateKey, state: Uint8Array): Promise<void>;
38
53
  workerCollect(executionId: Uint8Array): Promise<Uint8Array[]>;
39
54
  workerScan(executionId: Uint8Array): Promise<Array<[number, Uint8Array]>>;
40
55
  queuePush(executionId: Uint8Array, items: Uint8Array[]): Promise<number>;
@@ -44,7 +59,28 @@ export declare class FunctionStorageSqlite implements FunctionStorage {
44
59
  export declare class BoundStorage {
45
60
  private _base;
46
61
  private _executionId;
47
- constructor(storage: FunctionStorage, executionId: Uint8Array);
62
+ private _substreamId;
63
+ /**
64
+ * @param substreamId The table-in-out substream this storage serves -- the
65
+ * client-minted `InitRequest.substream_id`, identical across the
66
+ * substream's init, every tick and its finalize -- or `null` when the
67
+ * client sent none. Keys {@link put}.
68
+ */
69
+ constructor(storage: FunctionStorage, executionId: Uint8Array, substreamId?: Uint8Array | null);
70
+ /**
71
+ * Upsert this substream's state into the execution's worker state, which
72
+ * {@link collect} drains at finalize.
73
+ *
74
+ * One row per **substream**, keyed by its `substream_id`. Storage is
75
+ * already scoped to the execution, and every connection of a fanned-out
76
+ * scan shares that execution -- so the key has to tell those connections
77
+ * apart. The process id only does when each connection is its own process;
78
+ * under the launcher, TCP or HTTP one process serves many of them, and a
79
+ * per-process key let them overwrite each other's state so finish()
80
+ * undercounted. The pid remains only for a client that sends no
81
+ * `substream_id`, which it keys as before. Mirrors vgi-python's
82
+ * `TableInOutFunction.process` (072e543).
83
+ */
48
84
  put(state: Uint8Array): Promise<void>;
49
85
  collect(): Promise<Uint8Array[]>;
50
86
  workerScan(): Promise<Array<[number, Uint8Array]>>;
@@ -26,12 +26,14 @@ export interface TableInOutProcessParams<TArgs = Record<string, any>> {
26
26
  storage: BoundStorage;
27
27
  /**
28
28
  * Stable client-minted id for this streaming table-in-out substream.
29
- * Present (identical across init / every process() / finalize) when the
30
- * client fanned this function out across per-substream workers; use it to
31
- * key per-substream accumulated state in shared storage so a finalize()
32
- * that lands on a different HTTP backend than the process() calls still
33
- * finds it. `null`/`undefined` for the serial path or an old client.
34
- * Mirrors vgi-python's `ProcessParams.substream_id`.
29
+ * Present (identical across init / every process() / finalize) whenever the
30
+ * client sent one -- the DuckDB extension does for every substream. The
31
+ * framework keys the accumulated state it auto-persists by it
32
+ * ({@link BoundStorage.put}), so substreams of one execution that one
33
+ * process serves keep separate states and finalize() receives each; use it
34
+ * likewise for any per-substream state you keep in shared storage yourself.
35
+ * `null`/`undefined` for a client that sent none. Mirrors vgi-python's
36
+ * `ProcessParams.substream_id`.
35
37
  */
36
38
  substreamId?: Uint8Array | null;
37
39
  /**
@@ -132,14 +134,17 @@ export interface RowTransformConfig<TArgs = Record<string, any>> {
132
134
  /**
133
135
  * Positional args = the per-row INPUT COLUMNS (real typed args on the wire,
134
136
  * no synthetic TABLE placeholder). Read from `batch` by declared name in
135
- * process(); NOT surfaced on `params.args`.
137
+ * process(); NOT surfaced on `params.args`. A `Null` type declares the
138
+ * column ANY: the input column then arrives with whatever type DuckDB
139
+ * resolved for the call (see `params.bindCall.input_schema` in onBind).
136
140
  */
137
141
  args?: Record<string, VgiDataType>;
138
142
  /**
139
143
  * Trailing VARARGS input columns: the per-row input is N columns of the
140
144
  * declared type. A varargs blended function has no per-column declared
141
145
  * names (the C++ bind names them col0..colN-1), so process() reads the
142
- * columns POSITIONALLY off `batch`.
146
+ * columns POSITIONALLY off `batch`. A `Null` element type declares ANY
147
+ * varargs: each runtime column resolves its own type.
143
148
  */
144
149
  varargs?: {
145
150
  name: string;
@@ -7,6 +7,12 @@ import type { ArgumentSpec } from "../arguments/argument-spec.js";
7
7
  export interface HandlerState<T = any> {
8
8
  /** Serializable user state for HTTP exchange round-trips. */
9
9
  state: T;
10
+ /**
11
+ * The compacted dynamic-filter history, for a stream that applies
12
+ * `vgi_pushdown_filters` deltas. Carried in the HTTP cursor so the next turn
13
+ * rebuilds the filters this one left.
14
+ */
15
+ filterHistory?: import("../filter-pushdown/history.js").FilterDeltaHistory;
10
16
  [key: string]: any;
11
17
  }
12
18
  export interface FunctionExample {
@@ -196,7 +202,14 @@ export interface VgiFunction {
196
202
  defaultOutputSchema?: VgiSchema;
197
203
  bind(request: BindRequest): BindResponse | Promise<BindResponse>;
198
204
  globalInit(request: InitRequest): GlobalInitResponse | Promise<GlobalInitResponse>;
199
- createStreamHandlers(request: InitRequest, response: GlobalInitResponse, accumulatedState?: any): StreamHandlers;
205
+ /**
206
+ * Build the stream's handlers. On an HTTP continuation `filterHistory` is
207
+ * the compacted dynamic-filter history the cursor carried: a function that
208
+ * applies `vgi_pushdown_filters` deltas replays it onto the init snapshot and
209
+ * keeps the (updated) history on its handler state as `filterHistory`, which
210
+ * the dispatcher writes back into the next cursor.
211
+ */
212
+ createStreamHandlers(request: InitRequest, response: GlobalInitResponse, filterHistory?: import("../filter-pushdown/history.js").FilterDeltaHistory | null): StreamHandlers;
200
213
  cardinality?(request: TableFunctionCardinalityRequest): TableCardinality | Promise<TableCardinality>;
201
214
  /**
202
215
  * Divide a scan into named, independently redeemable splits. Absent means the
@@ -3,7 +3,7 @@ import { type ProtocolConfig } from "../protocol/dispatch.js";
3
3
  import { type LandingInfo } from "./landing.js";
4
4
  export interface VgiFetchOptions {
5
5
  /** Wire-protocol config (registry + catalogInterface). */
6
- protocol: Omit<ProtocolConfig, "recoverExchangeState">;
6
+ protocol: ProtocolConfig;
7
7
  /** HMAC key for state-token signing. Pass a stable, secret 32-byte key
8
8
  * (e.g. derived from a Wrangler secret). Required because Workers don't
9
9
  * preserve in-memory state across requests/instances. */
@@ -36,7 +36,7 @@ export { serializeSchema, deserializeSchema, serializeBatch, deserializeBatch, }
36
36
  export { schema, field, nullType, int8, int16, int64, uint8, uint16, uint32, uint64, float16, float64, utf8, largeUtf8, binary, largeBinary, fixedSizeBinary, decimal, decimal128, decimal256, date, dateDay, dateMillisecond, time, timeSecond, timeMillisecond, timeMicrosecond, timeNanosecond, timestamp, duration, interval, timestampSeconds, timestampMillis, timestampMicros, timestampNanos, durationSeconds, durationMillis, durationMicros, durationNanos, list, fixedSizeList, struct, map, dictionary, union, sparseUnion, denseUnion, TimeUnit, DateUnit, IntervalUnit, UnionMode, codecFor, asDate32, asDate64Ms, asTime32S, asTime32Ms, asTime64Us, asTime64Ns, asTimestampSeconds, asTimestampMillis, asTimestampMicros, asTimestampNanos, asDurationSeconds, asDurationMillis, asDurationMicros, asDurationNanos, asUnscaledDecimal, asInt64, asUint64, fromBranded, fromBrandedNumber, fromBrandedBigInt, TypeId, isNull, isInt, isFloat, isBinary, isUtf8, isBool, isDecimal, isDate, isTime, isTimestamp, isInterval, isList, isStruct, isUnion, isFixedSizeBinary, isFixedSizeList, isMap, isDuration, isDictionary, isBatch, typeSignature, readCanonicalValue, columnFromArray, decodeDictValue, backend, adoptArrowJsShape, } from "./arrow/index.js";
37
37
  export type { VgiTypeId, VgiDataType, VgiField, VgiSchema, VgiColumn, VgiColumnData, VgiBatch, VgiBackendInfo, TaggedUnion, NullDescriptor, BoolType, IntType, Int8Type, Int16Type, Int32Type, Int64Type, Uint8Type, Uint16Type, Uint32Type, Uint64Type, FloatType, Utf8Type, LargeUtf8Type, BinaryType, LargeBinaryType, FixedSizeBinaryType, DecimalType, Date32Type, Date64Type, Time32Type, Time64Type, TimestampType, DurationType, IntervalType, ListType, FixedSizeListType, StructType, MapType, DictionaryType, UnionType, TypedField, TUnit, RichValue, RawValue, ValueFor, Repr, Codec, Branded, Date32, Date64Ms, Time32S, Time32Ms, Time64Us, Time64Ns, TimestampSeconds, TimestampMillis, TimestampMicros, TimestampNanos, DurationSeconds, DurationMillis, DurationMicros, DurationNanos, UnscaledDecimal, Int64Raw, Uint64Raw, } from "./arrow/index.js";
38
38
  export type { ScalarComputeRow, ScalarOutputValue, ScalarComputeResult } from "./functions/scalar.js";
39
- export { type FunctionStorage, FunctionStorageSqlite, BoundStorage, UnknownInvocationError, resolveStorageFromEnv, setStorage, storage as functionStorage, } from "./functions/storage.js";
39
+ export { type FunctionStorage, type WorkerStateKey, FunctionStorageSqlite, BoundStorage, UnknownInvocationError, resolveStorageFromEnv, setStorage, storage as functionStorage, } from "./functions/storage.js";
40
40
  export { FunctionStorageCfDo, type FetchLike } from "./functions/storage-cf-do.js";
41
41
  export { VgiClient, VgiClientError } from "./client/client.js";
42
42
  export type { VgiClientOptions, TableFunctionOptions, ScalarFunctionOptions, TableInOutFunctionOptions, OnCreateConflict, CatalogFunctionType as ClientCatalogFunctionType, CatalogAttachOptions, AttachOptionValue, } from "./client/types.js";