@query-farm/vgi-rpc 0.6.3 → 0.7.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/dist/access-log.d.ts +50 -0
- package/dist/access-log.d.ts.map +1 -0
- package/dist/arrow/impl-arrowjs/index.d.ts +96 -0
- package/dist/arrow/impl-arrowjs/index.d.ts.map +1 -0
- package/dist/arrow/impl-flechette/index.d.ts +102 -0
- package/dist/arrow/impl-flechette/index.d.ts.map +1 -0
- package/dist/arrow/impl-flechette/message-meta.d.ts +11 -0
- package/dist/arrow/impl-flechette/message-meta.d.ts.map +1 -0
- package/dist/arrow/index.d.ts +4 -0
- package/dist/arrow/index.d.ts.map +1 -0
- package/dist/arrow/predicates.d.ts +44 -0
- package/dist/arrow/predicates.d.ts.map +1 -0
- package/dist/arrow/types.d.ts +62 -0
- package/dist/arrow/types.d.ts.map +1 -0
- package/dist/client/capabilities.d.ts +25 -0
- package/dist/client/capabilities.d.ts.map +1 -0
- package/dist/client/connect.d.ts.map +1 -1
- package/dist/client/introspect.d.ts +7 -0
- package/dist/client/introspect.d.ts.map +1 -1
- package/dist/client/ipc.d.ts +8 -2
- package/dist/client/ipc.d.ts.map +1 -1
- package/dist/client/pipe.d.ts.map +1 -1
- package/dist/client/stream.d.ts +11 -2
- package/dist/client/stream.d.ts.map +1 -1
- package/dist/client/uploadUrl.d.ts +25 -0
- package/dist/client/uploadUrl.d.ts.map +1 -0
- package/dist/constants.d.ts +15 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/crypto.d.ts +22 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/dispatch/describe.d.ts +10 -6
- package/dist/dispatch/describe.d.ts.map +1 -1
- package/dist/dispatch/stream.d.ts +2 -2
- package/dist/dispatch/stream.d.ts.map +1 -1
- package/dist/dispatch/unary.d.ts +2 -2
- package/dist/dispatch/unary.d.ts.map +1 -1
- package/dist/errors.d.ts +46 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/external.d.ts +25 -5
- package/dist/external.d.ts.map +1 -1
- package/dist/http/bearer.d.ts.map +1 -1
- package/dist/http/common.d.ts +42 -7
- package/dist/http/common.d.ts.map +1 -1
- package/dist/http/dispatch.d.ts +20 -2
- package/dist/http/dispatch.d.ts.map +1 -1
- package/dist/http/handler.d.ts.map +1 -1
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/mtls.d.ts +2 -1
- package/dist/http/mtls.d.ts.map +1 -1
- package/dist/http/oauth-pkce.d.ts +141 -0
- package/dist/http/oauth-pkce.d.ts.map +1 -0
- package/dist/http/pages.d.ts +3 -0
- package/dist/http/pages.d.ts.map +1 -1
- package/dist/http/sticky.d.ts +124 -0
- package/dist/http/sticky.d.ts.map +1 -0
- package/dist/http/token.d.ts +38 -12
- package/dist/http/token.d.ts.map +1 -1
- package/dist/http/types.d.ts +68 -5
- package/dist/http/types.d.ts.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1275 -3507
- package/dist/index.js.map +19 -37
- package/dist/launcher/hash.d.ts +22 -0
- package/dist/launcher/hash.d.ts.map +1 -0
- package/dist/launcher/index.d.ts +23 -0
- package/dist/launcher/index.d.ts.map +1 -0
- package/dist/launcher/launch.d.ts +27 -0
- package/dist/launcher/launch.d.ts.map +1 -0
- package/dist/launcher/lock.d.ts +19 -0
- package/dist/launcher/lock.d.ts.map +1 -0
- package/dist/launcher/serve-unix.d.ts +54 -0
- package/dist/launcher/serve-unix.d.ts.map +1 -0
- package/dist/launcher/state.d.ts +59 -0
- package/dist/launcher/state.d.ts.map +1 -0
- package/dist/otel.d.ts.map +1 -1
- package/dist/protocol.d.ts +16 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/schema.d.ts +45 -18
- package/dist/schema.d.ts.map +1 -1
- package/dist/server.d.ts +23 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/types.d.ts +216 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/util/gzip.d.ts +10 -0
- package/dist/util/gzip.d.ts.map +1 -0
- package/dist/util/schema.d.ts +3 -15
- package/dist/util/schema.d.ts.map +1 -1
- package/dist/util/web-crypto.d.ts +22 -0
- package/dist/util/web-crypto.d.ts.map +1 -0
- package/dist/util/zstd.d.ts +26 -3
- package/dist/util/zstd.d.ts.map +1 -1
- package/dist/wire/opaque.d.ts +11 -0
- package/dist/wire/opaque.d.ts.map +1 -0
- package/dist/wire/reader.d.ts +5 -5
- package/dist/wire/reader.d.ts.map +1 -1
- package/dist/wire/request.d.ts +11 -3
- package/dist/wire/request.d.ts.map +1 -1
- package/dist/wire/response.d.ts +6 -6
- package/dist/wire/response.d.ts.map +1 -1
- package/dist/wire/writer.d.ts +49 -39
- package/dist/wire/writer.d.ts.map +1 -1
- package/package.json +24 -10
- package/src/access-log.ts +195 -0
- package/src/arrow/impl-arrowjs/index.ts +433 -0
- package/src/arrow/impl-flechette/index.ts +414 -0
- package/src/arrow/impl-flechette/message-meta.ts +174 -0
- package/src/arrow/index.ts +89 -0
- package/src/arrow/predicates.ts +56 -0
- package/src/arrow/types.ts +73 -0
- package/src/client/capabilities.ts +84 -0
- package/src/client/connect.ts +103 -26
- package/src/client/introspect.ts +60 -38
- package/src/client/ipc.ts +37 -27
- package/src/client/pipe.ts +12 -9
- package/src/client/stream.ts +34 -19
- package/src/client/uploadUrl.ts +169 -0
- package/src/constants.ts +18 -1
- package/src/crypto.ts +95 -0
- package/src/dispatch/describe.ts +146 -107
- package/src/dispatch/stream.ts +53 -24
- package/src/dispatch/unary.ts +5 -4
- package/src/errors.ts +76 -0
- package/src/external.ts +43 -29
- package/src/http/bearer.ts +2 -5
- package/src/http/common.ts +90 -23
- package/src/http/dispatch.ts +373 -46
- package/src/http/handler.ts +794 -68
- package/src/http/index.ts +1 -0
- package/src/http/mtls.ts +18 -3
- package/src/http/oauth-pkce.ts +1035 -0
- package/src/http/pages.ts +30 -15
- package/src/http/sticky.ts +429 -0
- package/src/http/token.ts +165 -75
- package/src/http/types.ts +69 -5
- package/src/index.ts +40 -1
- package/src/launcher/hash.ts +104 -0
- package/src/launcher/index.ts +35 -0
- package/src/launcher/launch.ts +284 -0
- package/src/launcher/lock.ts +171 -0
- package/src/launcher/serve-unix.ts +385 -0
- package/src/launcher/state.ts +245 -0
- package/src/otel.ts +39 -33
- package/src/protocol.ts +27 -3
- package/src/schema.ts +107 -56
- package/src/server.ts +196 -20
- package/src/types.ts +322 -18
- package/src/util/gzip.ts +63 -0
- package/src/util/schema.ts +4 -22
- package/src/util/web-crypto.ts +98 -0
- package/src/util/zstd.ts +133 -14
- package/src/wire/opaque.ts +37 -0
- package/src/wire/reader.ts +5 -4
- package/src/wire/request.ts +67 -8
- package/src/wire/response.ts +51 -85
- package/src/wire/writer.ts +165 -69
- package/dist/util/conform.d.ts +0 -18
- package/dist/util/conform.d.ts.map +0 -1
- package/src/util/conform.ts +0 -94
package/dist/wire/writer.d.ts
CHANGED
|
@@ -1,59 +1,69 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { Socket } from "node:net";
|
|
2
|
+
import type { VgiBatch, VgiSchema } from "../arrow/index.js";
|
|
3
|
+
type WriterTarget = {
|
|
4
|
+
kind: "fd";
|
|
5
|
+
fd: number;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "socket";
|
|
8
|
+
socket: Socket;
|
|
9
|
+
};
|
|
2
10
|
/**
|
|
3
|
-
* Writes sequential IPC streams to
|
|
4
|
-
* Each call to writeStream() writes a
|
|
11
|
+
* Writes sequential IPC streams to either an fd (stdio subprocess transport)
|
|
12
|
+
* or a Node Socket (AF_UNIX transport). Each call to writeStream() writes a
|
|
13
|
+
* complete IPC stream: schema + batches + EOS.
|
|
5
14
|
*
|
|
6
|
-
* All
|
|
7
|
-
*
|
|
15
|
+
* All public methods are async. The fd path resolves immediately after a
|
|
16
|
+
* synchronous writeSync; the socket path awaits real `'drain'` events on
|
|
17
|
+
* backpressure so the event loop stays responsive to other connections.
|
|
8
18
|
*/
|
|
9
19
|
export declare class IpcStreamWriter {
|
|
10
|
-
private readonly
|
|
11
|
-
|
|
20
|
+
private readonly target;
|
|
21
|
+
/**
|
|
22
|
+
* Construct from a file descriptor (stdio transport) or a Node net.Socket
|
|
23
|
+
* (AF_UNIX transport). The default targets stdout for legacy stdio servers
|
|
24
|
+
* that didn't pass an fd.
|
|
25
|
+
*/
|
|
26
|
+
constructor(fdOrSocket?: number | Socket);
|
|
12
27
|
/**
|
|
13
28
|
* Write a complete IPC stream with the given schema and batches.
|
|
14
29
|
* Creates schema message, writes all batches (with their metadata), writes EOS.
|
|
15
30
|
*/
|
|
16
|
-
writeStream(schema:
|
|
31
|
+
writeStream(schema: VgiSchema, batches: VgiBatch[]): Promise<void>;
|
|
17
32
|
/**
|
|
18
33
|
* Open an incremental IPC stream for writing batches one at a time.
|
|
19
|
-
* Used for streaming methods where output batches are produced incrementally.
|
|
20
|
-
* Bytes are written synchronously after each batch.
|
|
21
34
|
*/
|
|
22
|
-
openStream(schema:
|
|
35
|
+
openStream(schema: VgiSchema): IncrementalStream;
|
|
23
36
|
}
|
|
24
37
|
/**
|
|
25
38
|
* An open IPC stream that supports incremental batch writes.
|
|
26
39
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* and
|
|
30
|
-
*
|
|
31
|
-
*
|
|
40
|
+
* Drives a backend {@link IncrementalEncoder} and flushes its bytes through
|
|
41
|
+
* the same target (fd or socket) as the parent IpcStreamWriter. The write()
|
|
42
|
+
* and close() methods are async so the socket path can yield on backpressure
|
|
43
|
+
* — critical under AF_UNIX where the kernel send buffer (~8 KB on macOS)
|
|
44
|
+
* fills quickly and any synchronous busy-wait would starve every other
|
|
45
|
+
* connection sharing this event loop.
|
|
46
|
+
*
|
|
47
|
+
* The encoder is obtained from the Arrow facade, so this file no longer
|
|
48
|
+
* imports arrow-js directly — keeping arrow-js out of the flechette
|
|
49
|
+
* (workerd/browser) bundle. The flechette encoder throws on construction;
|
|
50
|
+
* the stdio exchange protocol is lockstep (the client reads each response
|
|
51
|
+
* batch before sending the next input) which needs an incremental writer
|
|
52
|
+
* flechette doesn't provide. workerd/browser deployments use HTTP (no
|
|
53
|
+
* stdio), so the flechette path is never reached there; `flechette-pipe`
|
|
54
|
+
* conformance is xfailed for streams.
|
|
32
55
|
*/
|
|
33
56
|
export declare class IncrementalStream {
|
|
34
|
-
private
|
|
35
|
-
private readonly
|
|
57
|
+
private readonly encoder;
|
|
58
|
+
private readonly target;
|
|
36
59
|
private closed;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* nullability), silently dropping the batch. Since our output schema is
|
|
45
|
-
* set at stream open time and all batches are structurally compatible,
|
|
46
|
-
* we skip the comparison.
|
|
47
|
-
*/
|
|
48
|
-
write(batch: RecordBatch): void;
|
|
49
|
-
/**
|
|
50
|
-
* Close the stream (writes EOS marker synchronously).
|
|
51
|
-
*/
|
|
52
|
-
close(): void;
|
|
53
|
-
/**
|
|
54
|
-
* Drain buffered bytes from the Arrow writer's internal queue
|
|
55
|
-
* and write them synchronously to the output fd.
|
|
56
|
-
*/
|
|
57
|
-
private drain;
|
|
60
|
+
private writeChain;
|
|
61
|
+
constructor(target: WriterTarget, schema: VgiSchema);
|
|
62
|
+
/** Write a single batch. Resolves once the bytes are queued/flushed. */
|
|
63
|
+
write(batch: VgiBatch): Promise<void>;
|
|
64
|
+
/** Close the stream (writes EOS marker). */
|
|
65
|
+
close(): Promise<void>;
|
|
66
|
+
private enqueue;
|
|
58
67
|
}
|
|
68
|
+
export {};
|
|
59
69
|
//# sourceMappingURL=writer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/wire/writer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/wire/writer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAsB,QAAQ,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AA2GjF,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpF;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IAEtC;;;;OAIG;gBACS,UAAU,GAAE,MAAM,GAAG,MAAkB;IAQnD;;;OAGG;IACG,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAexE;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,iBAAiB;CAGjD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,MAAM,CAAS;IAIvB,OAAO,CAAC,UAAU,CAAoC;gBAE1C,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS;IAQnD,wEAAwE;IAClE,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3C,4CAA4C;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B,OAAO,CAAC,OAAO;CAchB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@query-farm/vgi-rpc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://vgi-rpc-typescript.query.farm",
|
|
6
6
|
"repository": {
|
|
@@ -8,36 +8,50 @@
|
|
|
8
8
|
"url": "https://github.com/Query-farm/vgi-rpc-typescript"
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
|
+
"sideEffects": false,
|
|
11
12
|
"main": "dist/index.js",
|
|
12
13
|
"types": "dist/index.d.ts",
|
|
13
14
|
"exports": {
|
|
14
15
|
".": {
|
|
15
|
-
"
|
|
16
|
+
"workerd": "./src/index.ts",
|
|
17
|
+
"worker": "./src/index.ts",
|
|
18
|
+
"browser": "./src/index.ts",
|
|
19
|
+
"bun": "./src/index.ts",
|
|
16
20
|
"types": "./dist/index.d.ts",
|
|
17
|
-
"
|
|
21
|
+
"import": "./dist/index.js"
|
|
18
22
|
},
|
|
19
23
|
"./otel": {
|
|
20
|
-
"
|
|
24
|
+
"bun": "./src/otel.ts",
|
|
21
25
|
"types": "./dist/otel.d.ts",
|
|
22
|
-
"
|
|
26
|
+
"import": "./dist/otel.js"
|
|
23
27
|
},
|
|
24
28
|
"./s3": {
|
|
25
|
-
"
|
|
29
|
+
"bun": "./src/s3.ts",
|
|
26
30
|
"types": "./dist/s3.d.ts",
|
|
27
|
-
"
|
|
31
|
+
"import": "./dist/s3.js"
|
|
28
32
|
},
|
|
29
33
|
"./gcs": {
|
|
30
|
-
"
|
|
34
|
+
"bun": "./src/gcs.ts",
|
|
31
35
|
"types": "./dist/gcs.d.ts",
|
|
32
|
-
"
|
|
36
|
+
"import": "./dist/gcs.js"
|
|
33
37
|
}
|
|
34
38
|
},
|
|
35
39
|
"files": [
|
|
36
40
|
"dist",
|
|
37
41
|
"src"
|
|
38
42
|
],
|
|
43
|
+
"imports": {
|
|
44
|
+
"#vgi-rpc-arrow": {
|
|
45
|
+
"workerd": "./src/arrow/impl-flechette/index.ts",
|
|
46
|
+
"worker": "./src/arrow/impl-flechette/index.ts",
|
|
47
|
+
"default": "./src/arrow/impl-arrowjs/index.ts"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
39
50
|
"dependencies": {
|
|
51
|
+
"@noble/ciphers": "^2.2.0",
|
|
40
52
|
"@query-farm/apache-arrow": "*",
|
|
53
|
+
"@uwdata/flechette": "github:Query-farm/flechette#fix/timestamp-bigint-encode",
|
|
54
|
+
"fzstd": "^0.1.1",
|
|
41
55
|
"oauth4webapi": "^3.8.5"
|
|
42
56
|
},
|
|
43
57
|
"peerDependencies": {
|
|
@@ -76,7 +90,7 @@
|
|
|
76
90
|
"build": "bun run build:types && bun run build:js",
|
|
77
91
|
"build:types": "bunx tsc -p tsconfig.build.json",
|
|
78
92
|
"build:js": "bun build ./src/index.ts --outdir dist --target node --format esm --sourcemap=external --external @query-farm/apache-arrow",
|
|
79
|
-
"postinstall": "cd node_modules/@query-farm/apache-arrow && node -e \"const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));p.main='src/Arrow.node.ts';fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\"",
|
|
93
|
+
"postinstall": "cd node_modules/@query-farm/apache-arrow && node -e \"const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));p.main='src/Arrow.node.ts';fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\\n')\" && cd ../../.. && bun scripts/patch-flechette.mjs",
|
|
80
94
|
"test": "bun test",
|
|
81
95
|
"lint": "biome check .",
|
|
82
96
|
"lint:fix": "biome check --write .",
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// © Copyright 2025-2026, Query.Farm LLC - https://query.farm
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cross-language conformance access-log hook.
|
|
6
|
+
*
|
|
7
|
+
* Emits one JSON record per RPC dispatch to a {@link Sink} (typically a file
|
|
8
|
+
* descriptor opened in append mode). The record shape conforms to the
|
|
9
|
+
* vgi-rpc access-log specification (`docs/access-log-spec.md` and
|
|
10
|
+
* `vgi_rpc/access_log.schema.json` in the Python reference repo).
|
|
11
|
+
*
|
|
12
|
+
* Use {@link AccessLogHook} to align this implementation with `vgi-rpc-test
|
|
13
|
+
* --access-log` so worker behaviour is checked across language ports by the
|
|
14
|
+
* same tool that gates the conformance suite.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { CallStatistics, DispatchHook, DispatchInfo, HookToken } from "./types.js";
|
|
18
|
+
|
|
19
|
+
/** Where the hook writes formatted JSON lines. */
|
|
20
|
+
export interface AccessLogSink {
|
|
21
|
+
write(line: string): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Indirect-string require so esbuild can't pull node:fs into the bundle.
|
|
25
|
+
// Workers should use a custom sink (e.g., one backed by `console.log`).
|
|
26
|
+
const _NODE_FS_MOD = "node:fs";
|
|
27
|
+
function _loadWriteSync(): (fd: number, data: Uint8Array, offset?: number, len?: number) => number {
|
|
28
|
+
const req: any = (import.meta as any).require ?? (globalThis as any).require ?? null;
|
|
29
|
+
if (!req) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"FdSink requires Node.js or Bun (node:fs.writeSync). For other runtimes, " +
|
|
32
|
+
"supply a custom AccessLogSink that wraps console.log or your logger.",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return req(_NODE_FS_MOD).writeSync;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A sink backed by a file descriptor; uses synchronous writes for ordering. */
|
|
39
|
+
export class FdSink implements AccessLogSink {
|
|
40
|
+
private readonly _writeSync = _loadWriteSync();
|
|
41
|
+
constructor(private readonly fd: number) {}
|
|
42
|
+
write(line: string): void {
|
|
43
|
+
const buf = new TextEncoder().encode(line);
|
|
44
|
+
let offset = 0;
|
|
45
|
+
while (offset < buf.length) {
|
|
46
|
+
const n = this._writeSync(this.fd, buf, offset, buf.length - offset);
|
|
47
|
+
if (n <= 0) throw new Error(`access-log writeSync returned ${n}`);
|
|
48
|
+
offset += n;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface StartToken {
|
|
54
|
+
startNs: bigint;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function rfc3339Utc(): string {
|
|
58
|
+
const d = new Date();
|
|
59
|
+
const yyyy = d.getUTCFullYear().toString().padStart(4, "0");
|
|
60
|
+
const mm = (d.getUTCMonth() + 1).toString().padStart(2, "0");
|
|
61
|
+
const dd = d.getUTCDate().toString().padStart(2, "0");
|
|
62
|
+
const hh = d.getUTCHours().toString().padStart(2, "0");
|
|
63
|
+
const mi = d.getUTCMinutes().toString().padStart(2, "0");
|
|
64
|
+
const ss = d.getUTCSeconds().toString().padStart(2, "0");
|
|
65
|
+
const ms = d.getUTCMilliseconds().toString().padStart(3, "0");
|
|
66
|
+
return `${yyyy}-${mm}-${dd}T${hh}:${mi}:${ss}.${ms}Z`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function base64(bytes: Uint8Array): string {
|
|
70
|
+
return Buffer.from(bytes).toString("base64");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Round to 2 decimal places. */
|
|
74
|
+
function roundTo2(f: number): number {
|
|
75
|
+
return Math.round(f * 100) / 100;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Options for {@link AccessLogHook}.
|
|
80
|
+
*
|
|
81
|
+
* `level` matches Python's logger-level gating in `_emit_access_log`:
|
|
82
|
+
* at "INFO" the heavy `request_data` field (a base64 of the full
|
|
83
|
+
* request batch — typically 8+ KiB per init RPC) is replaced with a
|
|
84
|
+
* `truncated: true` marker plus `original_request_bytes`, so the
|
|
85
|
+
* access-log schema's "unary requires request_data unless truncated"
|
|
86
|
+
* invariant still holds. Bump to "DEBUG" to capture full payloads for
|
|
87
|
+
* replay/audit.
|
|
88
|
+
*/
|
|
89
|
+
export interface AccessLogOptions {
|
|
90
|
+
/** Server version string (optional). */
|
|
91
|
+
serverVersion?: string;
|
|
92
|
+
/** Verbosity for heavy fields. Default: "INFO". */
|
|
93
|
+
level?: "INFO" | "DEBUG";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export class AccessLogHook implements DispatchHook {
|
|
97
|
+
private readonly serverVersion: string;
|
|
98
|
+
private readonly level: "INFO" | "DEBUG";
|
|
99
|
+
|
|
100
|
+
constructor(
|
|
101
|
+
private readonly sink: AccessLogSink,
|
|
102
|
+
options: AccessLogOptions | string = {},
|
|
103
|
+
) {
|
|
104
|
+
// Backward compatibility: the original signature accepted a bare
|
|
105
|
+
// serverVersion string as the second arg.
|
|
106
|
+
if (typeof options === "string") {
|
|
107
|
+
this.serverVersion = options;
|
|
108
|
+
this.level = "INFO";
|
|
109
|
+
} else {
|
|
110
|
+
this.serverVersion = options.serverVersion ?? "";
|
|
111
|
+
this.level = options.level ?? "INFO";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
onDispatchStart(_info: DispatchInfo): HookToken {
|
|
116
|
+
const token: StartToken = { startNs: process.hrtime.bigint() };
|
|
117
|
+
return token;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
onDispatchEnd(token: HookToken, info: DispatchInfo, stats: CallStatistics, error?: Error): void {
|
|
121
|
+
const t = token as StartToken | undefined;
|
|
122
|
+
const durationMs = t ? roundTo2(Number(process.hrtime.bigint() - t.startNs) / 1_000_000) : 0;
|
|
123
|
+
|
|
124
|
+
const status = error ? "error" : "ok";
|
|
125
|
+
const errType = error ? ((error as Error & { type?: string }).type ?? error.constructor.name) : "";
|
|
126
|
+
const errMsg = error?.message ?? "";
|
|
127
|
+
|
|
128
|
+
const protocol = info.protocol ?? "";
|
|
129
|
+
const rec: Record<string, unknown> = {
|
|
130
|
+
timestamp: rfc3339Utc(),
|
|
131
|
+
level: "INFO",
|
|
132
|
+
logger: "vgi_rpc.access",
|
|
133
|
+
message: `${protocol}.${info.method} ${status}`,
|
|
134
|
+
server_id: info.serverId,
|
|
135
|
+
protocol,
|
|
136
|
+
protocol_hash: info.protocolHash ?? "",
|
|
137
|
+
method: info.method,
|
|
138
|
+
method_type: info.methodType,
|
|
139
|
+
principal: info.principal ?? "",
|
|
140
|
+
auth_domain: info.authDomain ?? "",
|
|
141
|
+
authenticated: info.authenticated ?? false,
|
|
142
|
+
remote_addr: info.remoteAddr ?? "",
|
|
143
|
+
duration_ms: durationMs,
|
|
144
|
+
status,
|
|
145
|
+
error_type: errType,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
if (errMsg) rec.error_message = errMsg;
|
|
149
|
+
if (this.serverVersion) rec.server_version = this.serverVersion;
|
|
150
|
+
if (info.protocolVersion) rec.protocol_version = info.protocolVersion;
|
|
151
|
+
if (info.requestId) rec.request_id = info.requestId;
|
|
152
|
+
if (info.requestData && info.requestData.length > 0) {
|
|
153
|
+
// At INFO, the per-request base64 payload dominates record size
|
|
154
|
+
// (an init RPC commonly logs 8+ KiB of base64 per call) and audit
|
|
155
|
+
// consumers rarely need the bytes — they care about who/what/when.
|
|
156
|
+
// Replace with a `truncated: true` marker so the access-log schema's
|
|
157
|
+
// "unary requires request_data unless truncated" invariant holds.
|
|
158
|
+
// Bump level to DEBUG to re-enable the full payload.
|
|
159
|
+
const encoded = base64(info.requestData);
|
|
160
|
+
if (this.level === "DEBUG") {
|
|
161
|
+
rec.request_data = encoded;
|
|
162
|
+
} else {
|
|
163
|
+
rec.original_request_bytes = encoded.length;
|
|
164
|
+
rec.truncated = true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (info.methodType === "stream") {
|
|
168
|
+
rec.stream_id = info.streamId ?? "00000000000000000000000000000000";
|
|
169
|
+
}
|
|
170
|
+
if (info.cancelled) rec.cancelled = true;
|
|
171
|
+
|
|
172
|
+
if (
|
|
173
|
+
stats.inputBatches +
|
|
174
|
+
stats.outputBatches +
|
|
175
|
+
stats.inputRows +
|
|
176
|
+
stats.outputRows +
|
|
177
|
+
stats.inputBytes +
|
|
178
|
+
stats.outputBytes !==
|
|
179
|
+
0
|
|
180
|
+
) {
|
|
181
|
+
rec.input_batches = stats.inputBatches;
|
|
182
|
+
rec.output_batches = stats.outputBatches;
|
|
183
|
+
rec.input_rows = stats.inputRows;
|
|
184
|
+
rec.output_rows = stats.outputRows;
|
|
185
|
+
rec.input_bytes = stats.inputBytes;
|
|
186
|
+
rec.output_bytes = stats.outputBytes;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
this.sink.write(`${JSON.stringify(rec)}\n`);
|
|
191
|
+
} catch {
|
|
192
|
+
// best-effort
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|