@query-farm/vgi 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.
- package/LICENSE +134 -0
- package/README.md +494 -0
- package/dist/__dbg.test-d.d.ts +1 -0
- package/dist/arguments/argument-spec.d.ts +51 -0
- package/dist/arguments/arguments.d.ts +13 -0
- package/dist/arrow/codec/branded.d.ts +83 -0
- package/dist/arrow/codec/registry.d.ts +29 -0
- package/dist/arrow/codec/repr.d.ts +23 -0
- package/dist/arrow/codec/type-descriptors.d.ts +109 -0
- package/dist/arrow/impl-arrowjs/build.d.ts +19 -0
- package/dist/arrow/impl-arrowjs/canonical.d.ts +15 -0
- package/dist/arrow/impl-arrowjs/empty.d.ts +7 -0
- package/dist/arrow/impl-arrowjs/filter.d.ts +11 -0
- package/dist/arrow/impl-arrowjs/index.d.ts +11 -0
- package/dist/arrow/impl-arrowjs/ipc.d.ts +24 -0
- package/dist/arrow/impl-arrowjs/iterate.d.ts +39 -0
- package/dist/arrow/impl-arrowjs/project.d.ts +10 -0
- package/dist/arrow/impl-arrowjs/schema.d.ts +62 -0
- package/dist/arrow/impl-arrowjs/statistics.d.ts +13 -0
- package/dist/arrow/impl-flechette/build.d.ts +14 -0
- package/dist/arrow/impl-flechette/canonical.d.ts +18 -0
- package/dist/arrow/impl-flechette/empty.d.ts +9 -0
- package/dist/arrow/impl-flechette/filter.d.ts +10 -0
- package/dist/arrow/impl-flechette/index.d.ts +11 -0
- package/dist/arrow/impl-flechette/ipc.d.ts +33 -0
- package/dist/arrow/impl-flechette/iterate.d.ts +29 -0
- package/dist/arrow/impl-flechette/message-meta.d.ts +10 -0
- package/dist/arrow/impl-flechette/normalize-type.d.ts +11 -0
- package/dist/arrow/impl-flechette/project.d.ts +3 -0
- package/dist/arrow/impl-flechette/schema.d.ts +47 -0
- package/dist/arrow/impl-flechette/statistics.d.ts +13 -0
- package/dist/arrow/index.d.ts +11 -0
- package/dist/arrow/predicates.d.ts +47 -0
- package/dist/arrow/repr.test-d.d.ts +52 -0
- package/dist/arrow/schema-types.d.ts +71 -0
- package/dist/arrow/types.d.ts +57 -0
- package/dist/catalog/attach-option.d.ts +33 -0
- package/dist/catalog/attach-options.d.ts +30 -0
- package/dist/catalog/composite.d.ts +33 -0
- package/dist/catalog/descriptors.d.ts +154 -0
- package/dist/catalog/interface.d.ts +239 -0
- package/dist/catalog/read-only.d.ts +33 -0
- package/dist/client/client.d.ts +189 -0
- package/dist/client/errors.d.ts +26 -0
- package/dist/client/helpers.d.ts +12 -0
- package/dist/client/protocol.d.ts +16 -0
- package/dist/client/types.d.ts +125 -0
- package/dist/client-entry.d.ts +11 -0
- package/dist/client-entry.js +48 -0
- package/dist/client-entry.js.map +9 -0
- package/dist/codec/asd.d.ts +25 -0
- package/dist/crypto.d.ts +36 -0
- package/dist/errors.d.ts +24 -0
- package/dist/filter-pushdown/collector.d.ts +32 -0
- package/dist/filter-pushdown/deserialize.d.ts +16 -0
- package/dist/filter-pushdown/evaluate.d.ts +51 -0
- package/dist/filter-pushdown/index.d.ts +4 -0
- package/dist/filter-pushdown/types.d.ts +84 -0
- package/dist/functions/aggregate.d.ts +129 -0
- package/dist/functions/copy-from.d.ts +81 -0
- package/dist/functions/copy-to.d.ts +121 -0
- package/dist/functions/registry.d.ts +16 -0
- package/dist/functions/scalar.d.ts +98 -0
- package/dist/functions/storage-cf-do.d.ts +47 -0
- package/dist/functions/storage.d.ts +86 -0
- package/dist/functions/table-buffering.d.ts +99 -0
- package/dist/functions/table-in-out.d.ts +64 -0
- package/dist/functions/table.d.ts +118 -0
- package/dist/functions/types.d.ts +157 -0
- package/dist/generated/vgi-client.d.ts +466 -0
- package/dist/generated/vgi-protocol-schemas.d.ts +115 -0
- package/dist/index.core.d.ts +42 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10888 -0
- package/dist/index.js.map +85 -0
- package/dist/metadata/resolve.d.ts +3 -0
- package/dist/metadata/serialize.d.ts +12 -0
- package/dist/metadata/types.d.ts +47 -0
- package/dist/protocol/dispatch.d.ts +23 -0
- package/dist/protocol/handlers/aggregate.d.ts +3 -0
- package/dist/protocol/handlers/catalog/admin.d.ts +3 -0
- package/dist/protocol/handlers/catalog/index.d.ts +3 -0
- package/dist/protocol/handlers/catalog/index_methods.d.ts +3 -0
- package/dist/protocol/handlers/catalog/macro.d.ts +3 -0
- package/dist/protocol/handlers/catalog/shared.d.ts +70 -0
- package/dist/protocol/handlers/catalog/table.d.ts +3 -0
- package/dist/protocol/handlers/catalog/view.d.ts +3 -0
- package/dist/protocol/handlers/function.d.ts +8 -0
- package/dist/protocol/handlers/shared.d.ts +31 -0
- package/dist/protocol/handlers/table-buffering.d.ts +3 -0
- package/dist/protocol/serialize.d.ts +4 -0
- package/dist/protocol/serializers/arguments.d.ts +3 -0
- package/dist/protocol/serializers/bind.d.ts +9 -0
- package/dist/protocol/serializers/cardinality.d.ts +7 -0
- package/dist/protocol/serializers/init.d.ts +9 -0
- package/dist/protocol/serializers/shared.d.ts +12 -0
- package/dist/protocol/state-serializer.d.ts +23 -0
- package/dist/protocol/types.d.ts +129 -0
- package/dist/secrets/helpers.d.ts +15 -0
- package/dist/types.d.ts +52 -0
- package/dist/util/arrow/index.d.ts +1 -0
- package/dist/util/bytes.d.ts +2 -0
- package/dist/util/statistics.d.ts +7 -0
- package/dist/worker-cf-entry.d.ts +30 -0
- package/dist/worker-cf-entry.js +8767 -0
- package/dist/worker-cf-entry.js.map +74 -0
- package/dist/worker.d.ts +36 -0
- package/package.json +96 -0
- package/src/arguments/argument-spec.ts +200 -0
- package/src/arguments/arguments.ts +81 -0
- package/src/arrow/codec/branded.ts +206 -0
- package/src/arrow/codec/registry.ts +674 -0
- package/src/arrow/codec/repr.ts +181 -0
- package/src/arrow/codec/type-descriptors.ts +137 -0
- package/src/arrow/impl-arrowjs/build.ts +88 -0
- package/src/arrow/impl-arrowjs/canonical.ts +509 -0
- package/src/arrow/impl-arrowjs/empty.ts +72 -0
- package/src/arrow/impl-arrowjs/filter.ts +53 -0
- package/src/arrow/impl-arrowjs/index.ts +54 -0
- package/src/arrow/impl-arrowjs/ipc.ts +67 -0
- package/src/arrow/impl-arrowjs/iterate.ts +159 -0
- package/src/arrow/impl-arrowjs/project.ts +73 -0
- package/src/arrow/impl-arrowjs/schema.ts +256 -0
- package/src/arrow/impl-arrowjs/statistics.ts +185 -0
- package/src/arrow/impl-flechette/build.ts +65 -0
- package/src/arrow/impl-flechette/canonical.ts +289 -0
- package/src/arrow/impl-flechette/empty.ts +20 -0
- package/src/arrow/impl-flechette/filter.ts +42 -0
- package/src/arrow/impl-flechette/index.ts +54 -0
- package/src/arrow/impl-flechette/ipc.ts +110 -0
- package/src/arrow/impl-flechette/iterate.ts +121 -0
- package/src/arrow/impl-flechette/message-meta.ts +175 -0
- package/src/arrow/impl-flechette/normalize-type.ts +99 -0
- package/src/arrow/impl-flechette/project.ts +64 -0
- package/src/arrow/impl-flechette/schema.ts +202 -0
- package/src/arrow/impl-flechette/statistics.ts +198 -0
- package/src/arrow/index.ts +129 -0
- package/src/arrow/predicates.ts +66 -0
- package/src/arrow/repr.test-d.ts +189 -0
- package/src/arrow/schema-types.ts +213 -0
- package/src/arrow/types.ts +74 -0
- package/src/catalog/attach-option.ts +107 -0
- package/src/catalog/attach-options.ts +79 -0
- package/src/catalog/composite.ts +180 -0
- package/src/catalog/descriptors.ts +164 -0
- package/src/catalog/interface.ts +721 -0
- package/src/catalog/read-only.ts +909 -0
- package/src/client/client.ts +1348 -0
- package/src/client/errors.ts +69 -0
- package/src/client/helpers.ts +56 -0
- package/src/client/protocol.ts +33 -0
- package/src/client/types.ts +140 -0
- package/src/client-entry.ts +112 -0
- package/src/codec/asd.ts +147 -0
- package/src/crypto.ts +143 -0
- package/src/errors.ts +61 -0
- package/src/filter-pushdown/collector.ts +147 -0
- package/src/filter-pushdown/deserialize.ts +253 -0
- package/src/filter-pushdown/evaluate.ts +591 -0
- package/src/filter-pushdown/index.ts +28 -0
- package/src/filter-pushdown/types.ts +91 -0
- package/src/functions/aggregate.ts +384 -0
- package/src/functions/copy-from.ts +282 -0
- package/src/functions/copy-to.ts +319 -0
- package/src/functions/registry.ts +258 -0
- package/src/functions/scalar.ts +356 -0
- package/src/functions/storage-cf-do.ts +303 -0
- package/src/functions/storage.ts +521 -0
- package/src/functions/table-buffering.ts +562 -0
- package/src/functions/table-in-out.ts +373 -0
- package/src/functions/table.ts +521 -0
- package/src/functions/types.ts +192 -0
- package/src/generated/vgi-client.ts +560 -0
- package/src/generated/vgi-protocol-schemas.ts +864 -0
- package/src/index.core.ts +267 -0
- package/src/index.ts +11 -0
- package/src/metadata/resolve.ts +109 -0
- package/src/metadata/serialize.ts +242 -0
- package/src/metadata/types.ts +60 -0
- package/src/protocol/dispatch.ts +52 -0
- package/src/protocol/handlers/aggregate.ts +355 -0
- package/src/protocol/handlers/catalog/admin.ts +368 -0
- package/src/protocol/handlers/catalog/index.ts +25 -0
- package/src/protocol/handlers/catalog/index_methods.ts +65 -0
- package/src/protocol/handlers/catalog/macro.ts +114 -0
- package/src/protocol/handlers/catalog/shared.ts +291 -0
- package/src/protocol/handlers/catalog/table.ts +401 -0
- package/src/protocol/handlers/catalog/view.ts +119 -0
- package/src/protocol/handlers/function.ts +302 -0
- package/src/protocol/handlers/shared.ts +80 -0
- package/src/protocol/handlers/table-buffering.ts +207 -0
- package/src/protocol/serialize.ts +23 -0
- package/src/protocol/serializers/arguments.ts +160 -0
- package/src/protocol/serializers/bind.ts +212 -0
- package/src/protocol/serializers/cardinality.ts +45 -0
- package/src/protocol/serializers/init.ts +247 -0
- package/src/protocol/serializers/shared.ts +46 -0
- package/src/protocol/state-serializer.ts +181 -0
- package/src/protocol/types.ts +153 -0
- package/src/secrets/helpers.ts +70 -0
- package/src/types.ts +71 -0
- package/src/util/arrow/index.ts +23 -0
- package/src/util/bytes.ts +14 -0
- package/src/util/statistics.ts +40 -0
- package/src/worker-cf-entry.ts +172 -0
- package/src/worker.ts +212 -0
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { VgiFunction } from "./functions/types.js";
|
|
2
|
+
import type { CatalogDescriptor } from "./catalog/descriptors.js";
|
|
3
|
+
import type { CatalogInterface } from "./catalog/interface.js";
|
|
4
|
+
import { ReadOnlyCatalogInterface } from "./catalog/read-only.js";
|
|
5
|
+
export interface WorkerConfig {
|
|
6
|
+
functions?: VgiFunction[];
|
|
7
|
+
catalog?: CatalogDescriptor;
|
|
8
|
+
catalogInterface?: CatalogInterface;
|
|
9
|
+
/** Factory that receives the built ReadOnlyCatalogInterface and returns a custom catalog. */
|
|
10
|
+
catalogInterfaceFactory?: (base: ReadOnlyCatalogInterface) => CatalogInterface;
|
|
11
|
+
catalogName?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare class Worker {
|
|
14
|
+
private _registry;
|
|
15
|
+
private _catalogInterface?;
|
|
16
|
+
private _catalogName?;
|
|
17
|
+
constructor(config: WorkerConfig);
|
|
18
|
+
run(argv?: readonly string[]): void;
|
|
19
|
+
/** Build the VGI protocol from this worker's registry + catalog interface. */
|
|
20
|
+
private buildProtocol;
|
|
21
|
+
/**
|
|
22
|
+
* Serve this worker over a caller-provided byte-stream pair, instead of the
|
|
23
|
+
* argv-selected stdio/unix/tcp transports `run()` uses. Resolves when the
|
|
24
|
+
* `readable` ends.
|
|
25
|
+
*
|
|
26
|
+
* This is the seam for transports that aren't a process/socket — most notably
|
|
27
|
+
* a **Web Worker**: bridge the worker's `MessagePort` to a Node `Duplex`
|
|
28
|
+
* (readable side ← `port.on('message')`, writable side → `port.postMessage`)
|
|
29
|
+
* and pass it as both arguments. The same code runs in a browser Web Worker.
|
|
30
|
+
*
|
|
31
|
+
* @param readable incoming request bytes (web `ReadableStream` or Node `Readable`).
|
|
32
|
+
* @param writable outgoing response sink (a `net.Socket`/`Duplex`, or an fd
|
|
33
|
+
* number). Omit for the stdout fd.
|
|
34
|
+
*/
|
|
35
|
+
serveStream(readable: ReadableStream<Uint8Array> | NodeJS.ReadableStream, writable?: number | import("node:net").Socket): Promise<void>;
|
|
36
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@query-farm/vgi",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "TypeScript framework for extending DuckDB with user-defined functions via the Vector Gateway Interface (VGI) over Arrow IPC.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"author": "Query Farm LLC (https://query.farm)",
|
|
7
|
+
"homepage": "https://github.com/Query-farm/vgi-typescript#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Query-farm/vgi-typescript.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Query-farm/vgi-typescript/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"duckdb",
|
|
17
|
+
"arrow",
|
|
18
|
+
"vgi",
|
|
19
|
+
"query-farm",
|
|
20
|
+
"udf",
|
|
21
|
+
"user-defined-function",
|
|
22
|
+
"hyperfederation",
|
|
23
|
+
"extension"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22.15"
|
|
30
|
+
},
|
|
31
|
+
"type": "module",
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"main": "dist/index.js",
|
|
34
|
+
"types": "dist/index.d.ts",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"workerd": "./src/index.ts",
|
|
39
|
+
"worker": "./src/index.ts",
|
|
40
|
+
"browser": "./src/index.ts",
|
|
41
|
+
"bun": "./src/index.ts",
|
|
42
|
+
"import": "./dist/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./client": {
|
|
45
|
+
"types": "./dist/client-entry.d.ts",
|
|
46
|
+
"bun": "./src/client-entry.ts",
|
|
47
|
+
"import": "./dist/client-entry.js"
|
|
48
|
+
},
|
|
49
|
+
"./worker-cf": {
|
|
50
|
+
"types": "./dist/worker-cf-entry.d.ts",
|
|
51
|
+
"workerd": "./src/worker-cf-entry.ts",
|
|
52
|
+
"worker": "./src/worker-cf-entry.ts",
|
|
53
|
+
"browser": "./src/worker-cf-entry.ts",
|
|
54
|
+
"bun": "./src/worker-cf-entry.ts",
|
|
55
|
+
"import": "./dist/worker-cf-entry.js"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"dist",
|
|
60
|
+
"src",
|
|
61
|
+
"!src/**/__tests__/**",
|
|
62
|
+
"!src/**/*.test.ts",
|
|
63
|
+
"!src/**/*.spec.ts"
|
|
64
|
+
],
|
|
65
|
+
"imports": {
|
|
66
|
+
"#arrow-impl": {
|
|
67
|
+
"workerd": "./src/arrow/impl-flechette/index.ts",
|
|
68
|
+
"worker": "./src/arrow/impl-flechette/index.ts",
|
|
69
|
+
"default": "./src/arrow/impl-arrowjs/index.ts"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@noble/ciphers": "^2.2.0",
|
|
74
|
+
"@query-farm/flechette": "^2.4.0"
|
|
75
|
+
},
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"@query-farm/apache-arrow": "^21.1.1",
|
|
78
|
+
"@query-farm/vgi-rpc": "^0.9.0"
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"@duckdb/node-api": "^1.5.2-r.1",
|
|
82
|
+
"@query-farm/apache-arrow": "^21.1.1",
|
|
83
|
+
"@query-farm/vgi-rpc": "^0.9.0",
|
|
84
|
+
"@types/bun": "latest",
|
|
85
|
+
"typescript": "^6.0.3"
|
|
86
|
+
},
|
|
87
|
+
"scripts": {
|
|
88
|
+
"build": "bun run build:types && bun run build:js",
|
|
89
|
+
"prepack": "bun run build",
|
|
90
|
+
"build:types": "bunx tsc -p tsconfig.build.json",
|
|
91
|
+
"build:js": "bun build ./src/index.ts --outdir dist --target node --format esm --sourcemap=external --external @query-farm/apache-arrow --external @query-farm/vgi-rpc && bun build ./src/client-entry.ts --outdir dist --target browser --format esm --sourcemap=external --external @query-farm/apache-arrow --external @query-farm/vgi-rpc && bun build ./src/worker-cf-entry.ts --outdir dist --target node --format esm --sourcemap=external --external @query-farm/apache-arrow --external @query-farm/vgi-rpc",
|
|
92
|
+
"postinstall": "node scripts/postinstall.cjs",
|
|
93
|
+
"build:browser-test": "bun build examples/browser/main.ts --outdir examples/browser/dist --target browser --format esm --sourcemap=external",
|
|
94
|
+
"test": "bun test"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
// ArgumentSpec: converts function argument definitions to Arrow schema with VGI metadata keys.
|
|
3
|
+
// Must produce byte-identical schemas to Python's argument_specs_to_schema().
|
|
4
|
+
|
|
5
|
+
import { type VgiSchema, schema as schema_, type VgiField, field as field_, type VgiDataType, nullType, deserializeBatch } from "../arrow/index.js";
|
|
6
|
+
import {
|
|
7
|
+
VGI_ARG_KEY,
|
|
8
|
+
VGI_ARG_NAMED,
|
|
9
|
+
VGI_TYPE_KEY,
|
|
10
|
+
VGI_TYPE_TABLE,
|
|
11
|
+
VGI_TYPE_ANY,
|
|
12
|
+
VGI_VARARGS_KEY,
|
|
13
|
+
VGI_VARARGS_TRUE,
|
|
14
|
+
VGI_CONST_KEY,
|
|
15
|
+
VGI_CONST_TRUE,
|
|
16
|
+
VGI_DOC_KEY,
|
|
17
|
+
} from "../types.js";
|
|
18
|
+
|
|
19
|
+
export interface ArgumentSpec {
|
|
20
|
+
name: string;
|
|
21
|
+
position: number | string;
|
|
22
|
+
arrowType: VgiDataType;
|
|
23
|
+
isTableInput?: boolean;
|
|
24
|
+
isAnyType?: boolean;
|
|
25
|
+
isVarargs?: boolean;
|
|
26
|
+
isConst?: boolean;
|
|
27
|
+
doc?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function argumentSpecSortKey(spec: ArgumentSpec): [number, number | string] {
|
|
31
|
+
if (typeof spec.position === "number") {
|
|
32
|
+
return [0, spec.position];
|
|
33
|
+
}
|
|
34
|
+
return [1, spec.position];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Convert ArgumentSpecs to an Arrow Schema with VGI metadata keys.
|
|
39
|
+
* Positional arguments come first (in order), named arguments follow.
|
|
40
|
+
*/
|
|
41
|
+
export function argumentSpecsToSchema(specs: ArgumentSpec[]): VgiSchema {
|
|
42
|
+
const sorted = [...specs].sort((a, b) => {
|
|
43
|
+
const ka = argumentSpecSortKey(a);
|
|
44
|
+
const kb = argumentSpecSortKey(b);
|
|
45
|
+
if (ka[0] !== kb[0]) return ka[0] - kb[0];
|
|
46
|
+
if (typeof ka[1] === "number" && typeof kb[1] === "number") {
|
|
47
|
+
return ka[1] - kb[1];
|
|
48
|
+
}
|
|
49
|
+
return String(ka[1]).localeCompare(String(kb[1]));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const fields: VgiField[] = [];
|
|
53
|
+
for (const spec of sorted) {
|
|
54
|
+
const metadata: Map<string, string> = new Map();
|
|
55
|
+
|
|
56
|
+
if (typeof spec.position === "string") {
|
|
57
|
+
metadata.set(VGI_ARG_KEY, VGI_ARG_NAMED);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (spec.isTableInput) {
|
|
61
|
+
metadata.set(VGI_TYPE_KEY, VGI_TYPE_TABLE);
|
|
62
|
+
} else if (spec.isAnyType) {
|
|
63
|
+
metadata.set(VGI_TYPE_KEY, VGI_TYPE_ANY);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (spec.isVarargs) {
|
|
67
|
+
metadata.set(VGI_VARARGS_KEY, VGI_VARARGS_TRUE);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (spec.isConst) {
|
|
71
|
+
metadata.set(VGI_CONST_KEY, VGI_CONST_TRUE);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Per-argument description (UTF-8; presence-only — omit when empty).
|
|
75
|
+
if (spec.doc) {
|
|
76
|
+
metadata.set(VGI_DOC_KEY, spec.doc);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const field = field_(
|
|
80
|
+
spec.name,
|
|
81
|
+
spec.arrowType,
|
|
82
|
+
true,
|
|
83
|
+
metadata.size > 0 ? metadata : undefined
|
|
84
|
+
);
|
|
85
|
+
fields.push(field);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return schema_(fields);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// Macro Argument Schemas
|
|
93
|
+
// =============================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Build a macro `arguments_schema` describing macro parameters.
|
|
97
|
+
*
|
|
98
|
+
* Mirrors the function `arguments_schema` mechanism: one Arrow field per macro
|
|
99
|
+
* parameter, in `parameters` order, each nullable. The per-parameter
|
|
100
|
+
* description is carried via the same `vgi_doc` field-metadata key functions use
|
|
101
|
+
* (UTF-8, presence-only — the key is omitted entirely when there is no doc). A
|
|
102
|
+
* parameter's field type is the type of its default value when one is known
|
|
103
|
+
* (decoded from `parameterDefaultValues`, a one-row RecordBatch serialized as
|
|
104
|
+
* IPC bytes), else `nullType()`.
|
|
105
|
+
*
|
|
106
|
+
* @param parameters Ordered list of macro parameter names.
|
|
107
|
+
* @param parameterDefaultValues Optional one-row RecordBatch (IPC bytes) whose
|
|
108
|
+
* columns are parameter names with typed default values; used to infer each
|
|
109
|
+
* parameter's field type.
|
|
110
|
+
* @param parameterDocs Optional mapping of parameter name to description. Empty
|
|
111
|
+
* or missing descriptions yield no `vgi_doc` metadata on the field.
|
|
112
|
+
* @returns Arrow schema with one nullable field per parameter, in order.
|
|
113
|
+
*/
|
|
114
|
+
export function macroArgumentsSchema(
|
|
115
|
+
parameters: string[],
|
|
116
|
+
parameterDefaultValues?: Uint8Array | null,
|
|
117
|
+
parameterDocs?: Record<string, string>,
|
|
118
|
+
): VgiSchema {
|
|
119
|
+
const docs = parameterDocs ?? {};
|
|
120
|
+
|
|
121
|
+
// Map parameter name -> Arrow type from the typed default values, if any.
|
|
122
|
+
const defaultTypes: Map<string, VgiDataType> = new Map();
|
|
123
|
+
if (parameterDefaultValues && parameterDefaultValues.length > 0) {
|
|
124
|
+
const batch = deserializeBatch(parameterDefaultValues);
|
|
125
|
+
for (const f of batch.schema.fields) {
|
|
126
|
+
defaultTypes.set(f.name, f.type);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const fields: VgiField[] = [];
|
|
131
|
+
for (const name of parameters) {
|
|
132
|
+
const metadata: Map<string, string> = new Map();
|
|
133
|
+
const doc = docs[name];
|
|
134
|
+
if (doc) {
|
|
135
|
+
metadata.set(VGI_DOC_KEY, doc);
|
|
136
|
+
}
|
|
137
|
+
const fieldType = defaultTypes.get(name) ?? nullType();
|
|
138
|
+
fields.push(field_(name, fieldType, true, metadata.size > 0 ? metadata : undefined));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return schema_(fields);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Extract per-parameter descriptions from a macro `arguments_schema`.
|
|
146
|
+
*
|
|
147
|
+
* Inverse of {@link macroArgumentsSchema}'s `vgi_doc` handling: reads the
|
|
148
|
+
* `vgi_doc` field metadata (UTF-8) for each field. Fields without the key
|
|
149
|
+
* (undocumented) are omitted from the result.
|
|
150
|
+
*
|
|
151
|
+
* @param schema A macro `arguments_schema` (one field per parameter).
|
|
152
|
+
* @returns Mapping of parameter name to description, for documented parameters only.
|
|
153
|
+
*/
|
|
154
|
+
export function macroParameterDocsFromSchema(schema: VgiSchema): Record<string, string> {
|
|
155
|
+
const docs: Record<string, string> = {};
|
|
156
|
+
for (const field of schema.fields) {
|
|
157
|
+
const doc = field.metadata.get(VGI_DOC_KEY);
|
|
158
|
+
if (doc) {
|
|
159
|
+
docs[field.name] = doc;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return docs;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Convert an Arrow Schema back to ArgumentSpecs.
|
|
167
|
+
*/
|
|
168
|
+
export function schemaToArgumentSpecs(schema: VgiSchema): ArgumentSpec[] {
|
|
169
|
+
const specs: ArgumentSpec[] = [];
|
|
170
|
+
let positionIndex = 0;
|
|
171
|
+
|
|
172
|
+
for (const field of schema.fields) {
|
|
173
|
+
const metadata = field.metadata;
|
|
174
|
+
|
|
175
|
+
const isNamed = metadata.get(VGI_ARG_KEY) === VGI_ARG_NAMED;
|
|
176
|
+
const position: number | string = isNamed
|
|
177
|
+
? field.name
|
|
178
|
+
: positionIndex++;
|
|
179
|
+
|
|
180
|
+
const vgiType = metadata.get(VGI_TYPE_KEY);
|
|
181
|
+
const isTableInput = vgiType === VGI_TYPE_TABLE;
|
|
182
|
+
const isAnyType = vgiType === VGI_TYPE_ANY;
|
|
183
|
+
const isVarargs = metadata.get(VGI_VARARGS_KEY) === VGI_VARARGS_TRUE;
|
|
184
|
+
const isConst = metadata.get(VGI_CONST_KEY) === VGI_CONST_TRUE;
|
|
185
|
+
const doc = metadata.get(VGI_DOC_KEY) ?? "";
|
|
186
|
+
|
|
187
|
+
specs.push({
|
|
188
|
+
name: field.name,
|
|
189
|
+
position,
|
|
190
|
+
arrowType: field.type,
|
|
191
|
+
isTableInput,
|
|
192
|
+
isAnyType,
|
|
193
|
+
isVarargs,
|
|
194
|
+
isConst,
|
|
195
|
+
doc,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return specs;
|
|
200
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
// Arguments container for VGI function invocations.
|
|
3
|
+
// Holds positional and named scalar values from DuckDB.
|
|
4
|
+
|
|
5
|
+
import type { VgiSchema } from "../arrow/index.js";
|
|
6
|
+
import { safeNumber } from "../util/arrow/index.js";
|
|
7
|
+
|
|
8
|
+
export class Arguments {
|
|
9
|
+
readonly positional: (any | null)[];
|
|
10
|
+
readonly named: Map<string, any | null>;
|
|
11
|
+
/** Schema of the arguments batch (preserves original Arrow types). */
|
|
12
|
+
readonly argumentsSchema: VgiSchema | null;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
positional: (any | null)[] = [],
|
|
16
|
+
named: Map<string, any | null> = new Map(),
|
|
17
|
+
argumentsSchema: VgiSchema | null = null,
|
|
18
|
+
) {
|
|
19
|
+
this.positional = positional;
|
|
20
|
+
this.named = named;
|
|
21
|
+
this.argumentsSchema = argumentsSchema;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get an argument by position (number) or name (string).
|
|
26
|
+
*/
|
|
27
|
+
get(position: number | string, defaultValue?: any): any {
|
|
28
|
+
if (typeof position === "number") {
|
|
29
|
+
if (position < this.positional.length) {
|
|
30
|
+
const val = this.positional[position];
|
|
31
|
+
if (val === null || val === undefined) {
|
|
32
|
+
return defaultValue !== undefined ? defaultValue : null;
|
|
33
|
+
}
|
|
34
|
+
return unwrapScalar(val);
|
|
35
|
+
}
|
|
36
|
+
if (defaultValue !== undefined) return defaultValue;
|
|
37
|
+
throw new Error(`Positional argument ${position} not found`);
|
|
38
|
+
}
|
|
39
|
+
if (this.named.has(position)) {
|
|
40
|
+
const val = this.named.get(position);
|
|
41
|
+
if (val === null || val === undefined) {
|
|
42
|
+
return defaultValue !== undefined ? defaultValue : null;
|
|
43
|
+
}
|
|
44
|
+
return unwrapScalar(val);
|
|
45
|
+
}
|
|
46
|
+
if (defaultValue !== undefined) return defaultValue;
|
|
47
|
+
throw new Error(`Named argument '${position}' not found`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get length(): number {
|
|
51
|
+
return this.positional.length;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Unwrap an Arrow Scalar to a plain JS value.
|
|
57
|
+
*/
|
|
58
|
+
function unwrapScalar(val: any): any {
|
|
59
|
+
if (val === null || val === undefined) return null;
|
|
60
|
+
// If it's already a primitive, return it
|
|
61
|
+
if (typeof val !== "object") return val;
|
|
62
|
+
// Arrow Scalar objects have a valueOf() method
|
|
63
|
+
if (typeof val.valueOf === "function") {
|
|
64
|
+
let v: any;
|
|
65
|
+
try {
|
|
66
|
+
v = val.valueOf();
|
|
67
|
+
} catch {
|
|
68
|
+
// valueOf() may throw for large Decimal values (e.g. HUGEINT)
|
|
69
|
+
return val;
|
|
70
|
+
}
|
|
71
|
+
// BigInt -> Number for safe integers
|
|
72
|
+
if (typeof v === "bigint") {
|
|
73
|
+
if (v >= Number.MIN_SAFE_INTEGER && v <= Number.MAX_SAFE_INTEGER) {
|
|
74
|
+
return Number(v);
|
|
75
|
+
}
|
|
76
|
+
return v;
|
|
77
|
+
}
|
|
78
|
+
return v;
|
|
79
|
+
}
|
|
80
|
+
return val;
|
|
81
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
//
|
|
3
|
+
// Branded "raw" scalar types — phase 3.
|
|
4
|
+
//
|
|
5
|
+
// The `raw` representation is a branded view layered directly on top of the
|
|
6
|
+
// CANONICAL value (see ./registry.ts). A branded raw value is, at runtime,
|
|
7
|
+
// exactly the underlying canonical primitive (a `number` or a `bigint`); the
|
|
8
|
+
// brand exists only at the type level so the compiler can distinguish, say, a
|
|
9
|
+
// `Date32` (days since epoch) from a plain `number` or a `TimestampMicros`
|
|
10
|
+
// (raw us) from a plain `bigint`. This carries the WIRE UNIT in the type and
|
|
11
|
+
// makes a wrong-unit mix-up a compile error in `raw` mode.
|
|
12
|
+
//
|
|
13
|
+
// Constructors (`asDate32`, `asTimestampMicros`, …) validate the underlying
|
|
14
|
+
// value by reusing the codec's validation (so a raw constructor enforces the
|
|
15
|
+
// same range/integrality rules as building a column) and then brand it.
|
|
16
|
+
// Unwrappers (`fromBranded`) strip the brand back to the plain primitive; they
|
|
17
|
+
// are identity at runtime.
|
|
18
|
+
//
|
|
19
|
+
// Rich mode (the phase-1 default) uses JS `Date` for date32/date64 and plain
|
|
20
|
+
// number/bigint everywhere else. Raw mode replaces those with the branded
|
|
21
|
+
// aliases below so every temporal/decimal/64-bit-int slot is unit-tagged.
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
int32 as int32Type,
|
|
25
|
+
int64 as int64Type,
|
|
26
|
+
uint64 as uint64Type,
|
|
27
|
+
decimal128 as decimal128Type,
|
|
28
|
+
TimeUnit,
|
|
29
|
+
} from "../schema-types.js";
|
|
30
|
+
import { codecFor } from "./registry.js";
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Brand machinery
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
declare const __brand: unique symbol;
|
|
37
|
+
|
|
38
|
+
/** A nominal brand over a base primitive. `Branded<number, 'date32'>` is a
|
|
39
|
+
* `number` at runtime but a distinct type at compile time. */
|
|
40
|
+
export type Branded<Base, Tag extends string> = Base & {
|
|
41
|
+
readonly [__brand]: Tag;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Branded scalar aliases (over the CANONICAL value, carrying unit info)
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
/** date32 raw: days since the Unix epoch. */
|
|
49
|
+
export type Date32 = Branded<number, "date32">;
|
|
50
|
+
/** date64 raw: milliseconds since the Unix epoch. */
|
|
51
|
+
export type Date64Ms = Branded<bigint, "date64ms">;
|
|
52
|
+
|
|
53
|
+
/** time32[s] raw: seconds since midnight. */
|
|
54
|
+
export type Time32S = Branded<number, "time32s">;
|
|
55
|
+
/** time32[ms] raw: milliseconds since midnight. */
|
|
56
|
+
export type Time32Ms = Branded<number, "time32ms">;
|
|
57
|
+
/** time64[us] raw: microseconds since midnight. */
|
|
58
|
+
export type Time64Us = Branded<bigint, "time64us">;
|
|
59
|
+
/** time64[ns] raw: nanoseconds since midnight. */
|
|
60
|
+
export type Time64Ns = Branded<bigint, "time64ns">;
|
|
61
|
+
|
|
62
|
+
/** timestamp[s] raw. */
|
|
63
|
+
export type TimestampSeconds = Branded<bigint, "ts_s">;
|
|
64
|
+
/** timestamp[ms] raw. */
|
|
65
|
+
export type TimestampMillis = Branded<bigint, "ts_ms">;
|
|
66
|
+
/** timestamp[us] raw. */
|
|
67
|
+
export type TimestampMicros = Branded<bigint, "ts_us">;
|
|
68
|
+
/** timestamp[ns] raw. */
|
|
69
|
+
export type TimestampNanos = Branded<bigint, "ts_ns">;
|
|
70
|
+
|
|
71
|
+
/** duration[s] raw. */
|
|
72
|
+
export type DurationSeconds = Branded<bigint, "dur_s">;
|
|
73
|
+
/** duration[ms] raw. */
|
|
74
|
+
export type DurationMillis = Branded<bigint, "dur_ms">;
|
|
75
|
+
/** duration[us] raw. */
|
|
76
|
+
export type DurationMicros = Branded<bigint, "dur_us">;
|
|
77
|
+
/** duration[ns] raw. */
|
|
78
|
+
export type DurationNanos = Branded<bigint, "dur_ns">;
|
|
79
|
+
|
|
80
|
+
/** A decimal's UNSCALED integer (the on-wire representation). */
|
|
81
|
+
export type UnscaledDecimal = Branded<bigint, "decimal_unscaled">;
|
|
82
|
+
|
|
83
|
+
/** int64 raw. (Rich int64 is already a plain bigint; the brand makes the
|
|
84
|
+
* declared width explicit in raw mode.) */
|
|
85
|
+
export type Int64 = Branded<bigint, "int64">;
|
|
86
|
+
/** uint64 raw. */
|
|
87
|
+
export type Uint64 = Branded<bigint, "uint64">;
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Validation-reusing constructors
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
// One codec instance per branded type, reused for validation. `richToCanonical`
|
|
94
|
+
// is the validating canonical-producer; for these scalar types rich===canonical
|
|
95
|
+
// (except date, which the raw constructor sidesteps by validating the numeric
|
|
96
|
+
// directly), so this is exactly the right range/integrality check.
|
|
97
|
+
|
|
98
|
+
const dec128Codec = codecFor(decimal128Type(38, 0));
|
|
99
|
+
|
|
100
|
+
/** Validate `n` against int32 integrality/range, returning it unbranded. */
|
|
101
|
+
function validateInt32(n: number): number {
|
|
102
|
+
codecFor(int32Type()).richToCanonical(n);
|
|
103
|
+
return n;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function validateBigInt(
|
|
107
|
+
codecType: ReturnType<typeof int64Type> | ReturnType<typeof uint64Type>,
|
|
108
|
+
b: bigint,
|
|
109
|
+
): bigint {
|
|
110
|
+
codecFor(codecType).richToCanonical(b);
|
|
111
|
+
return b;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** date32 days: validated as a 32-bit integer day-number, then branded. */
|
|
115
|
+
export function asDate32(days: number): Date32 {
|
|
116
|
+
if (!Number.isInteger(days)) {
|
|
117
|
+
throw new TypeError(`asDate32: expected an integer day-number, got ${days}`);
|
|
118
|
+
}
|
|
119
|
+
return validateInt32(days) as Date32;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** date64 ms: validated as a 64-bit integer, then branded. */
|
|
123
|
+
export function asDate64Ms(ms: bigint): Date64Ms {
|
|
124
|
+
return validateBigInt(int64Type(), ms) as unknown as Date64Ms;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function asTime32(label: string, n: number): number {
|
|
128
|
+
if (!Number.isInteger(n)) {
|
|
129
|
+
throw new TypeError(`${label}: expected an integer, got ${n}`);
|
|
130
|
+
}
|
|
131
|
+
return validateInt32(n);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** time32[s]. */
|
|
135
|
+
export const asTime32S = (n: number): Time32S => asTime32("asTime32S", n) as Time32S;
|
|
136
|
+
/** time32[ms]. */
|
|
137
|
+
export const asTime32Ms = (n: number): Time32Ms => asTime32("asTime32Ms", n) as Time32Ms;
|
|
138
|
+
|
|
139
|
+
/** time64[us]. */
|
|
140
|
+
export const asTime64Us = (b: bigint): Time64Us =>
|
|
141
|
+
validateBigInt(int64Type(), b) as unknown as Time64Us;
|
|
142
|
+
/** time64[ns]. */
|
|
143
|
+
export const asTime64Ns = (b: bigint): Time64Ns =>
|
|
144
|
+
validateBigInt(int64Type(), b) as unknown as Time64Ns;
|
|
145
|
+
|
|
146
|
+
/** timestamp[s]. */
|
|
147
|
+
export const asTimestampSeconds = (b: bigint): TimestampSeconds =>
|
|
148
|
+
validateBigInt(int64Type(), b) as unknown as TimestampSeconds;
|
|
149
|
+
/** timestamp[ms]. */
|
|
150
|
+
export const asTimestampMillis = (b: bigint): TimestampMillis =>
|
|
151
|
+
validateBigInt(int64Type(), b) as unknown as TimestampMillis;
|
|
152
|
+
/** timestamp[us]. */
|
|
153
|
+
export const asTimestampMicros = (b: bigint): TimestampMicros =>
|
|
154
|
+
validateBigInt(int64Type(), b) as unknown as TimestampMicros;
|
|
155
|
+
/** timestamp[ns]. */
|
|
156
|
+
export const asTimestampNanos = (b: bigint): TimestampNanos =>
|
|
157
|
+
validateBigInt(int64Type(), b) as unknown as TimestampNanos;
|
|
158
|
+
|
|
159
|
+
/** duration[s]. */
|
|
160
|
+
export const asDurationSeconds = (b: bigint): DurationSeconds =>
|
|
161
|
+
validateBigInt(int64Type(), b) as unknown as DurationSeconds;
|
|
162
|
+
/** duration[ms]. */
|
|
163
|
+
export const asDurationMillis = (b: bigint): DurationMillis =>
|
|
164
|
+
validateBigInt(int64Type(), b) as unknown as DurationMillis;
|
|
165
|
+
/** duration[us]. */
|
|
166
|
+
export const asDurationMicros = (b: bigint): DurationMicros =>
|
|
167
|
+
validateBigInt(int64Type(), b) as unknown as DurationMicros;
|
|
168
|
+
/** duration[ns]. */
|
|
169
|
+
export const asDurationNanos = (b: bigint): DurationNanos =>
|
|
170
|
+
validateBigInt(int64Type(), b) as unknown as DurationNanos;
|
|
171
|
+
|
|
172
|
+
/** A decimal's unscaled integer (validated as a bigint, then branded). */
|
|
173
|
+
export function asUnscaledDecimal(unscaled: bigint): UnscaledDecimal {
|
|
174
|
+
dec128Codec.richToCanonical(unscaled);
|
|
175
|
+
return unscaled as unknown as UnscaledDecimal;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** int64 (validated for signed 64-bit range). */
|
|
179
|
+
export const asInt64 = (b: bigint): Int64 =>
|
|
180
|
+
validateBigInt(int64Type(), b) as unknown as Int64;
|
|
181
|
+
/** uint64 (validated for unsigned 64-bit range). */
|
|
182
|
+
export const asUint64 = (b: bigint): Uint64 =>
|
|
183
|
+
validateBigInt(uint64Type(), b) as unknown as Uint64;
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Unwrappers — strip the brand back to the plain primitive (identity at runtime)
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
/** Strip a numeric brand, returning the plain `number`. */
|
|
190
|
+
export function fromBrandedNumber(v: Branded<number, string>): number {
|
|
191
|
+
return v as number;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Strip a bigint brand, returning the plain `bigint`. */
|
|
195
|
+
export function fromBrandedBigInt(v: Branded<bigint, string>): bigint {
|
|
196
|
+
return v as bigint;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Generic unbrand for either base type. */
|
|
200
|
+
export function fromBranded<Base, Tag extends string>(v: Branded<Base, Tag>): Base {
|
|
201
|
+
return v as Base;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Re-export TimeUnit for callers that want to pick a timestamp/duration unit
|
|
205
|
+
// when constructing facade types alongside branded values.
|
|
206
|
+
export { TimeUnit };
|