camusdb 0.1.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 +21 -0
- package/README.md +950 -0
- package/dist/auth/credentials.d.ts +30 -0
- package/dist/auth/credentials.d.ts.map +1 -0
- package/dist/auth/credentials.js +23 -0
- package/dist/auth/credentials.js.map +1 -0
- package/dist/auth/login-client.d.ts +37 -0
- package/dist/auth/login-client.d.ts.map +1 -0
- package/dist/auth/login-client.js +8 -0
- package/dist/auth/login-client.js.map +1 -0
- package/dist/auth/rest-login-client.d.ts +17 -0
- package/dist/auth/rest-login-client.d.ts.map +1 -0
- package/dist/auth/rest-login-client.js +71 -0
- package/dist/auth/rest-login-client.js.map +1 -0
- package/dist/auth/token-provider.d.ts +123 -0
- package/dist/auth/token-provider.d.ts.map +1 -0
- package/dist/auth/token-provider.js +209 -0
- package/dist/auth/token-provider.js.map +1 -0
- package/dist/backup.d.ts +98 -0
- package/dist/backup.d.ts.map +1 -0
- package/dist/backup.js +153 -0
- package/dist/backup.js.map +1 -0
- package/dist/cache.d.ts +90 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +121 -0
- package/dist/cache.js.map +1 -0
- package/dist/client.d.ts +406 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +724 -0
- package/dist/client.js.map +1 -0
- package/dist/column-type.d.ts +33 -0
- package/dist/column-type.d.ts.map +1 -0
- package/dist/column-type.js +50 -0
- package/dist/column-type.js.map +1 -0
- package/dist/column-value.d.ts +77 -0
- package/dist/column-value.d.ts.map +1 -0
- package/dist/column-value.js +60 -0
- package/dist/column-value.js.map +1 -0
- package/dist/config.d.ts +181 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +315 -0
- package/dist/config.js.map +1 -0
- package/dist/connection-string.d.ts +45 -0
- package/dist/connection-string.d.ts.map +1 -0
- package/dist/connection-string.js +170 -0
- package/dist/connection-string.js.map +1 -0
- package/dist/endpoint-pool.d.ts +70 -0
- package/dist/endpoint-pool.d.ts.map +1 -0
- package/dist/endpoint-pool.js +120 -0
- package/dist/endpoint-pool.js.map +1 -0
- package/dist/error-codes.d.ts +44 -0
- package/dist/error-codes.d.ts.map +1 -0
- package/dist/error-codes.js +43 -0
- package/dist/error-codes.js.map +1 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +33 -0
- package/dist/errors.js.map +1 -0
- package/dist/hlc.d.ts +19 -0
- package/dist/hlc.d.ts.map +1 -0
- package/dist/hlc.js +11 -0
- package/dist/hlc.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +41 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +345 -0
- package/dist/json.js.map +1 -0
- package/dist/object-id.d.ts +39 -0
- package/dist/object-id.d.ts.map +1 -0
- package/dist/object-id.js +99 -0
- package/dist/object-id.js.map +1 -0
- package/dist/options.d.ts +77 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +68 -0
- package/dist/options.js.map +1 -0
- package/dist/prepared/binder.d.ts +20 -0
- package/dist/prepared/binder.d.ts.map +1 -0
- package/dist/prepared/binder.js +32 -0
- package/dist/prepared/binder.js.map +1 -0
- package/dist/prepared/policy.d.ts +81 -0
- package/dist/prepared/policy.d.ts.map +1 -0
- package/dist/prepared/policy.js +164 -0
- package/dist/prepared/policy.js.map +1 -0
- package/dist/query-stream.d.ts +55 -0
- package/dist/query-stream.d.ts.map +1 -0
- package/dist/query-stream.js +94 -0
- package/dist/query-stream.js.map +1 -0
- package/dist/result-set.d.ts +62 -0
- package/dist/result-set.d.ts.map +1 -0
- package/dist/result-set.js +100 -0
- package/dist/result-set.js.map +1 -0
- package/dist/retry.d.ts +38 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +93 -0
- package/dist/retry.js.map +1 -0
- package/dist/routing/advice.d.ts +63 -0
- package/dist/routing/advice.d.ts.map +1 -0
- package/dist/routing/advice.js +61 -0
- package/dist/routing/advice.js.map +1 -0
- package/dist/routing/route-cache.d.ts +53 -0
- package/dist/routing/route-cache.d.ts.map +1 -0
- package/dist/routing/route-cache.js +123 -0
- package/dist/routing/route-cache.js.map +1 -0
- package/dist/routing/router.d.ts +53 -0
- package/dist/routing/router.d.ts.map +1 -0
- package/dist/routing/router.js +89 -0
- package/dist/routing/router.js.map +1 -0
- package/dist/row-source.d.ts +48 -0
- package/dist/row-source.d.ts.map +1 -0
- package/dist/row-source.js +35 -0
- package/dist/row-source.js.map +1 -0
- package/dist/runtime.d.ts +58 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +137 -0
- package/dist/runtime.js.map +1 -0
- package/dist/shared-registry.d.ts +32 -0
- package/dist/shared-registry.d.ts.map +1 -0
- package/dist/shared-registry.js +48 -0
- package/dist/shared-registry.js.map +1 -0
- package/dist/sql-syntax.d.ts +44 -0
- package/dist/sql-syntax.d.ts.map +1 -0
- package/dist/sql-syntax.js +83 -0
- package/dist/sql-syntax.js.map +1 -0
- package/dist/statements.d.ts +15 -0
- package/dist/statements.d.ts.map +1 -0
- package/dist/statements.js +90 -0
- package/dist/statements.js.map +1 -0
- package/dist/transaction.d.ts +117 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +204 -0
- package/dist/transaction.js.map +1 -0
- package/dist/transport/authenticating-transport.d.ts +63 -0
- package/dist/transport/authenticating-transport.d.ts.map +1 -0
- package/dist/transport/authenticating-transport.js +119 -0
- package/dist/transport/authenticating-transport.js.map +1 -0
- package/dist/transport/branch-statements.d.ts +39 -0
- package/dist/transport/branch-statements.d.ts.map +1 -0
- package/dist/transport/branch-statements.js +74 -0
- package/dist/transport/branch-statements.js.map +1 -0
- package/dist/transport/error-text.d.ts +26 -0
- package/dist/transport/error-text.d.ts.map +1 -0
- package/dist/transport/error-text.js +75 -0
- package/dist/transport/error-text.js.map +1 -0
- package/dist/transport/grpc/batcher.d.ts +127 -0
- package/dist/transport/grpc/batcher.d.ts.map +1 -0
- package/dist/transport/grpc/batcher.js +399 -0
- package/dist/transport/grpc/batcher.js.map +1 -0
- package/dist/transport/grpc/codec.d.ts +40 -0
- package/dist/transport/grpc/codec.d.ts.map +1 -0
- package/dist/transport/grpc/codec.js +231 -0
- package/dist/transport/grpc/codec.js.map +1 -0
- package/dist/transport/grpc/grpc-transport.d.ts +115 -0
- package/dist/transport/grpc/grpc-transport.d.ts.map +1 -0
- package/dist/transport/grpc/grpc-transport.js +577 -0
- package/dist/transport/grpc/grpc-transport.js.map +1 -0
- package/dist/transport/grpc/messages.d.ts +221 -0
- package/dist/transport/grpc/messages.d.ts.map +1 -0
- package/dist/transport/grpc/messages.js +56 -0
- package/dist/transport/grpc/messages.js.map +1 -0
- package/dist/transport/grpc/proto.d.ts +71 -0
- package/dist/transport/grpc/proto.d.ts.map +1 -0
- package/dist/transport/grpc/proto.js +67 -0
- package/dist/transport/grpc/proto.js.map +1 -0
- package/dist/transport/http.d.ts +62 -0
- package/dist/transport/http.d.ts.map +1 -0
- package/dist/transport/http.js +169 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/transport/ndjson-row-source.d.ts +38 -0
- package/dist/transport/ndjson-row-source.d.ts.map +1 -0
- package/dist/transport/ndjson-row-source.js +197 -0
- package/dist/transport/ndjson-row-source.js.map +1 -0
- package/dist/transport/rest-decode.d.ts +33 -0
- package/dist/transport/rest-decode.d.ts.map +1 -0
- package/dist/transport/rest-decode.js +162 -0
- package/dist/transport/rest-decode.js.map +1 -0
- package/dist/transport/rest-prepared-cache.d.ts +50 -0
- package/dist/transport/rest-prepared-cache.d.ts.map +1 -0
- package/dist/transport/rest-prepared-cache.js +101 -0
- package/dist/transport/rest-prepared-cache.js.map +1 -0
- package/dist/transport/rest-transport.d.ts +88 -0
- package/dist/transport/rest-transport.d.ts.map +1 -0
- package/dist/transport/rest-transport.js +388 -0
- package/dist/transport/rest-transport.js.map +1 -0
- package/dist/transport/transport-pool.d.ts +40 -0
- package/dist/transport/transport-pool.d.ts.map +1 -0
- package/dist/transport/transport-pool.js +48 -0
- package/dist/transport/transport-pool.js.map +1 -0
- package/dist/transport/transport.d.ts +199 -0
- package/dist/transport/transport.d.ts.map +1 -0
- package/dist/transport/transport.js +11 -0
- package/dist/transport/transport.js.map +1 -0
- package/dist/values/decode.d.ts +39 -0
- package/dist/values/decode.d.ts.map +1 -0
- package/dist/values/decode.js +75 -0
- package/dist/values/decode.js.map +1 -0
- package/dist/values/encode.d.ts +45 -0
- package/dist/values/encode.d.ts.map +1 -0
- package/dist/values/encode.js +271 -0
- package/dist/values/encode.js.map +1 -0
- package/dist/values/ticks.d.ts +31 -0
- package/dist/values/ticks.d.ts.map +1 -0
- package/dist/values/ticks.js +52 -0
- package/dist/values/ticks.js.map +1 -0
- package/dist/values/typed.d.ts +67 -0
- package/dist/values/typed.d.ts.map +1 -0
- package/dist/values/typed.js +134 -0
- package/dist/values/typed.js.map +1 -0
- package/dist/values/uuid.d.ts +27 -0
- package/dist/values/uuid.d.ts.map +1 -0
- package/dist/values/uuid.js +76 -0
- package/dist/values/uuid.js.map +1 -0
- package/dist/vector.d.ts +26 -0
- package/dist/vector.d.ts.map +1 -0
- package/dist/vector.js +48 -0
- package/dist/vector.js.map +1 -0
- package/package.json +86 -0
- package/proto/camus_sql.proto +566 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branch-statements.js","sourceRoot":"","sources":["../../src/transport/branch-statements.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD;;;;;;;;;;;GAWG;AAEH,uDAAuD;AACvD,MAAM,UAAU,uBAAuB,CACrC,UAAkB,EAClB,kBAA0B,EAC1B,WAAoB;IAEpB,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAEnF,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,gBAAgB,KAAK,CAAC,kBAAkB,CAAC,EAAE,CAAC;AAClF,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,WAAoB;IACtE,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAEnF,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;AACvC,CAAC;AAED,8BAA8B;AAC9B,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,iBAAiB,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC5C,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,sBAAsB,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;AACjD,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,OAAO,uBAAuB,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;AAClD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAsB;IAClD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IAExC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE1E,MAAM,IAAI,GAAqB,EAAE,CAAC;IAElC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAsB,EAAE;YAChD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YAE3C,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,QAAQ,IAAI,SAAS,CAAC;QACtD,CAAC,CAAC;QAEF,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAEvC,IAAI,CAAC,IAAI,CAAC;YACR,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC;YAC1B,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC;YACd,KAAK,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC;YAC1F,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC;YACtB,aAAa,EAAE,IAAI,CAAC,gBAAgB,CAAC;SACtC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,KAAK,CAAC,IAAY;IACzB,OAAO,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of CamusDB
|
|
3
|
+
*
|
|
4
|
+
* For the full copyright and license information, please view the LICENSE.txt
|
|
5
|
+
* file that was distributed with this source code.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Cleans the text that becomes a `CamusError` message.
|
|
9
|
+
*
|
|
10
|
+
* The message is built from whatever the far end returned: a server error body, a gRPC status
|
|
11
|
+
* detail, an HTTP client's own exception text. Applications log it almost without exception, so
|
|
12
|
+
* three properties of that text matter, and the sender guarantees none of them. It must not carry
|
|
13
|
+
* a credential. It must not carry line breaks, which let a sender forge a second log entry. It
|
|
14
|
+
* must not be unbounded.
|
|
15
|
+
*
|
|
16
|
+
* Only the error path pays for this, so the scan is one plain pass rather than a set of regular
|
|
17
|
+
* expressions.
|
|
18
|
+
*/
|
|
19
|
+
/** The longest message kept. A server error body is a sentence; anything longer is echoed content. */
|
|
20
|
+
export declare const MAX_ERROR_MESSAGE_LENGTH = 2048;
|
|
21
|
+
/**
|
|
22
|
+
* The text with control characters folded to spaces, credential-shaped runs masked, and the length
|
|
23
|
+
* bounded.
|
|
24
|
+
*/
|
|
25
|
+
export declare function sanitizeErrorText(text: string | undefined | null): string;
|
|
26
|
+
//# sourceMappingURL=error-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-text.d.ts","sourceRoot":"","sources":["../../src/transport/error-text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;GAWG;AAEH,sGAAsG;AACtG,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAY7C;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAyBzE"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of CamusDB
|
|
3
|
+
*
|
|
4
|
+
* For the full copyright and license information, please view the LICENSE.txt
|
|
5
|
+
* file that was distributed with this source code.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Cleans the text that becomes a `CamusError` message.
|
|
9
|
+
*
|
|
10
|
+
* The message is built from whatever the far end returned: a server error body, a gRPC status
|
|
11
|
+
* detail, an HTTP client's own exception text. Applications log it almost without exception, so
|
|
12
|
+
* three properties of that text matter, and the sender guarantees none of them. It must not carry
|
|
13
|
+
* a credential. It must not carry line breaks, which let a sender forge a second log entry. It
|
|
14
|
+
* must not be unbounded.
|
|
15
|
+
*
|
|
16
|
+
* Only the error path pays for this, so the scan is one plain pass rather than a set of regular
|
|
17
|
+
* expressions.
|
|
18
|
+
*/
|
|
19
|
+
/** The longest message kept. A server error body is a sentence; anything longer is echoed content. */
|
|
20
|
+
export const MAX_ERROR_MESSAGE_LENGTH = 2048;
|
|
21
|
+
const ELIDED = '…';
|
|
22
|
+
const MASKED = '***';
|
|
23
|
+
/** Prefixes shaped like a credential. The token that follows one is masked. */
|
|
24
|
+
const SECRET_PREFIXES = ['bearer ', 'password=', 'password":', 'pwd=', 'accesstoken=', 'token":'];
|
|
25
|
+
/** Characters that end the value a credential-shaped prefix introduces. */
|
|
26
|
+
const VALUE_TERMINATORS = new Set([' ', '"', "'", ',', ';', '}', ')', '\n', '\r']);
|
|
27
|
+
/**
|
|
28
|
+
* The text with control characters folded to spaces, credential-shaped runs masked, and the length
|
|
29
|
+
* bounded.
|
|
30
|
+
*/
|
|
31
|
+
export function sanitizeErrorText(text) {
|
|
32
|
+
if (text === undefined || text === null || text.length === 0)
|
|
33
|
+
return '';
|
|
34
|
+
const lower = text.toLowerCase();
|
|
35
|
+
let clean = '';
|
|
36
|
+
let index = 0;
|
|
37
|
+
while (index < text.length && clean.length < MAX_ERROR_MESSAGE_LENGTH) {
|
|
38
|
+
const prefixLength = matchSecretPrefix(lower, index);
|
|
39
|
+
if (prefixLength !== undefined) {
|
|
40
|
+
clean += text.slice(index, index + prefixLength) + MASKED;
|
|
41
|
+
index = skipSecretValue(text, index + prefixLength);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const character = text[index];
|
|
45
|
+
index++;
|
|
46
|
+
// A newline in a logged message lets the sender forge a second log entry, and another control
|
|
47
|
+
// character can rewrite a terminal. Both become a plain space.
|
|
48
|
+
clean += isControl(character) ? ' ' : character;
|
|
49
|
+
}
|
|
50
|
+
return index < text.length ? clean + ELIDED : clean;
|
|
51
|
+
}
|
|
52
|
+
function matchSecretPrefix(lowercase, index) {
|
|
53
|
+
for (const prefix of SECRET_PREFIXES) {
|
|
54
|
+
if (lowercase.startsWith(prefix, index))
|
|
55
|
+
return prefix.length;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Skips the value a credential-shaped prefix introduces: leading quotes and spaces, then
|
|
61
|
+
* everything up to the first character that cannot be part of a token or a quoted value.
|
|
62
|
+
*/
|
|
63
|
+
function skipSecretValue(text, index) {
|
|
64
|
+
while (index < text.length && (text[index] === ' ' || text[index] === '"' || text[index] === "'")) {
|
|
65
|
+
index++;
|
|
66
|
+
}
|
|
67
|
+
while (index < text.length && !VALUE_TERMINATORS.has(text[index]))
|
|
68
|
+
index++;
|
|
69
|
+
return index;
|
|
70
|
+
}
|
|
71
|
+
function isControl(character) {
|
|
72
|
+
const code = character.codePointAt(0);
|
|
73
|
+
return code < 0x20 || (code >= 0x7f && code <= 0x9f);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=error-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-text.js","sourceRoot":"","sources":["../../src/transport/error-text.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of CamusDB
|
|
3
|
+
*
|
|
4
|
+
* For the full copyright and license information, please view the LICENSE.txt
|
|
5
|
+
* file that was distributed with this source code.
|
|
6
|
+
*/
|
|
7
|
+
import type { GrpcBatchOptions } from '../../config.js';
|
|
8
|
+
import type { GrpcBatchExecuteRequest, GrpcBatchExecuteResponse, GrpcResultRow, GrpcResultSchema, GrpcSqlRequest, GrpcTxnHandle } from './messages.js';
|
|
9
|
+
import type { CamusCacheMetadata } from '../../cache.js';
|
|
10
|
+
import type { CamusRoutingAdvice } from '../../routing/advice.js';
|
|
11
|
+
/** The hybrid-logical-clock token a reply carried. All three components are load-bearing. */
|
|
12
|
+
export interface BatchCausalToken {
|
|
13
|
+
readonly n: number;
|
|
14
|
+
readonly l: bigint;
|
|
15
|
+
readonly c: bigint;
|
|
16
|
+
}
|
|
17
|
+
export declare const EMPTY_CAUSAL_TOKEN: BatchCausalToken;
|
|
18
|
+
/** One query, decoded from the schema, rows, and terminator that shared a request id. */
|
|
19
|
+
export interface BatchQueryResult {
|
|
20
|
+
readonly schema: GrpcResultSchema | undefined;
|
|
21
|
+
readonly rows: GrpcResultRow[];
|
|
22
|
+
readonly token: BatchCausalToken;
|
|
23
|
+
readonly cacheMetadata: CamusCacheMetadata | undefined;
|
|
24
|
+
readonly routing: CamusRoutingAdvice | undefined;
|
|
25
|
+
}
|
|
26
|
+
/** One write statement's reply. */
|
|
27
|
+
export interface BatchNonQueryResult {
|
|
28
|
+
readonly affectedRows: number;
|
|
29
|
+
readonly token: BatchCausalToken;
|
|
30
|
+
readonly routing: CamusRoutingAdvice | undefined;
|
|
31
|
+
}
|
|
32
|
+
/** A prepared statement registered on one stream: the handle, and which stream minted it. */
|
|
33
|
+
export interface PreparedSlotEntry {
|
|
34
|
+
readonly transportId: number;
|
|
35
|
+
readonly statementId: number;
|
|
36
|
+
readonly parameterNames: string[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Raised when a prepared execution is about to be written to a stream that is not the one its
|
|
40
|
+
* handle was registered on. The handle died with the stream that minted it, so sending it would
|
|
41
|
+
* only earn an "unknown statement" reply.
|
|
42
|
+
*/
|
|
43
|
+
export declare class PreparedStatementStaleError extends Error {
|
|
44
|
+
readonly name = "PreparedStatementStaleError";
|
|
45
|
+
constructor();
|
|
46
|
+
}
|
|
47
|
+
/** One `BatchExecute` duplex call, behind an interface a test can substitute for. */
|
|
48
|
+
export interface BatchStream {
|
|
49
|
+
/** Identifies this stream. A handle registered on it is invalid on any other. */
|
|
50
|
+
readonly id: number;
|
|
51
|
+
send(request: GrpcBatchExecuteRequest): void;
|
|
52
|
+
/** Registers the callbacks that drive the reader loop. Called once, right after construction. */
|
|
53
|
+
listen(handlers: {
|
|
54
|
+
onMessage: (response: GrpcBatchExecuteResponse) => void;
|
|
55
|
+
onClose: (error: Error) => void;
|
|
56
|
+
}): void;
|
|
57
|
+
close(): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Pipelines many statements over a small pool of long-lived `BatchExecute` streams.
|
|
61
|
+
*
|
|
62
|
+
* A unary call per statement costs a round trip per statement, and an application that issues six
|
|
63
|
+
* statements per unit of work pays six. Here every message carries a client-monotonic request id,
|
|
64
|
+
* replies for different ids interleave, and this class demultiplexes them by id. Statements that
|
|
65
|
+
* share a transaction go to one stream, in arrival order, so the server sees them in the order the
|
|
66
|
+
* caller wrote them; autocommit statements are free to use any stream.
|
|
67
|
+
*
|
|
68
|
+
* A stream that faults is rebuilt by the next write, and every operation that was in flight on the
|
|
69
|
+
* old one is failed so its caller can decide whether to replay it. Prepared statements are scoped
|
|
70
|
+
* to the stream that registered them, and a handle from a stream that is gone is refused at the
|
|
71
|
+
* last moment before it would be written.
|
|
72
|
+
*/
|
|
73
|
+
export declare class GrpcBatcher {
|
|
74
|
+
private readonly options;
|
|
75
|
+
private readonly streamFactory;
|
|
76
|
+
private readonly slots;
|
|
77
|
+
private readonly pending;
|
|
78
|
+
private requestIdSeq;
|
|
79
|
+
private roundRobin;
|
|
80
|
+
private transportIdSeq;
|
|
81
|
+
private disposed;
|
|
82
|
+
constructor(options: GrpcBatchOptions, streamFactory: (id: number) => BatchStream);
|
|
83
|
+
/** Reserves a stream for a transaction, so all of its operations land in one ordering chain. */
|
|
84
|
+
reserveSlot(): number;
|
|
85
|
+
enqueueQuery(request: GrpcSqlRequest, slotIndex: number | undefined, signal: AbortSignal | undefined, expectedTransportId?: number): Promise<BatchQueryResult>;
|
|
86
|
+
enqueueNonQuery(request: GrpcSqlRequest, slotIndex: number | undefined, signal: AbortSignal | undefined, expectedTransportId?: number): Promise<BatchNonQueryResult>;
|
|
87
|
+
enqueueStart(request: GrpcSqlRequest, slotIndex: number, signal: AbortSignal | undefined): Promise<GrpcTxnHandle>;
|
|
88
|
+
enqueueCommit(request: GrpcSqlRequest, slotIndex: number, signal: AbortSignal | undefined): Promise<BatchCausalToken>;
|
|
89
|
+
enqueueRollback(request: GrpcSqlRequest, slotIndex: number, signal: AbortSignal | undefined): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* The registration for a statement on one stream, creating it when there is none.
|
|
92
|
+
*
|
|
93
|
+
* A registration whose stream has since been rebuilt is dropped and made again: the handle died
|
|
94
|
+
* with the stream, and the server would not know it.
|
|
95
|
+
*/
|
|
96
|
+
ensurePrepared(slotIndex: number, database: string, sql: string, signal: AbortSignal | undefined): Promise<PreparedSlotEntry>;
|
|
97
|
+
private registerPrepared;
|
|
98
|
+
/** Drops a registration the server no longer honours, but only if it is still the current one. */
|
|
99
|
+
invalidatePrepared(slotIndex: number, database: string, sql: string, stale: PreparedSlotEntry): void;
|
|
100
|
+
/** Removes and reports every stream's registration of one statement, so each can be released. */
|
|
101
|
+
takePrepared(database: string, sql: string): Promise<{
|
|
102
|
+
slotIndex: number;
|
|
103
|
+
entry: PreparedSlotEntry;
|
|
104
|
+
}[]>;
|
|
105
|
+
/** Releases one registration on the stream that minted it. Best-effort. */
|
|
106
|
+
closePrepared(slotIndex: number, entry: PreparedSlotEntry, signal: AbortSignal | undefined): Promise<void>;
|
|
107
|
+
private enqueue;
|
|
108
|
+
private enqueueTracked;
|
|
109
|
+
/**
|
|
110
|
+
* Drains one slot's queue onto its stream.
|
|
111
|
+
*
|
|
112
|
+
* Only one pump runs per slot, so the slot's stream has exactly one writer and needs no lock.
|
|
113
|
+
*/
|
|
114
|
+
private pump;
|
|
115
|
+
private writeItem;
|
|
116
|
+
private reconnect;
|
|
117
|
+
private demux;
|
|
118
|
+
private complete;
|
|
119
|
+
private fault;
|
|
120
|
+
private settle;
|
|
121
|
+
/** Fails every operation still in flight on a stream that closed, so its caller can replay. */
|
|
122
|
+
private failStreamPending;
|
|
123
|
+
private nextRoundRobin;
|
|
124
|
+
/** Closes every stream and fails everything still in flight. */
|
|
125
|
+
dispose(): Promise<void>;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=batcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batcher.d.ts","sourceRoot":"","sources":["../../../src/transport/grpc/batcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of CamusDB
|
|
3
|
+
*
|
|
4
|
+
* For the full copyright and license information, please view the LICENSE.txt
|
|
5
|
+
* file that was distributed with this source code.
|
|
6
|
+
*/
|
|
7
|
+
import { CamusError } from '../../errors.js';
|
|
8
|
+
import { CamusErrorCode } from '../../error-codes.js';
|
|
9
|
+
import { sanitizeErrorText } from '../error-text.js';
|
|
10
|
+
import { decodeCacheMetadata, decodeRoutingAdvice, fromWire } from './codec.js';
|
|
11
|
+
import { GrpcBatchStatementKind } from './messages.js';
|
|
12
|
+
export const EMPTY_CAUSAL_TOKEN = Object.freeze({ n: 0, l: 0n, c: 0n });
|
|
13
|
+
/**
|
|
14
|
+
* Raised when a prepared execution is about to be written to a stream that is not the one its
|
|
15
|
+
* handle was registered on. The handle died with the stream that minted it, so sending it would
|
|
16
|
+
* only earn an "unknown statement" reply.
|
|
17
|
+
*/
|
|
18
|
+
export class PreparedStatementStaleError extends Error {
|
|
19
|
+
name = 'PreparedStatementStaleError';
|
|
20
|
+
constructor() {
|
|
21
|
+
super("The prepared statement's stream was rebuilt before this execution could be written.");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Pipelines many statements over a small pool of long-lived `BatchExecute` streams.
|
|
26
|
+
*
|
|
27
|
+
* A unary call per statement costs a round trip per statement, and an application that issues six
|
|
28
|
+
* statements per unit of work pays six. Here every message carries a client-monotonic request id,
|
|
29
|
+
* replies for different ids interleave, and this class demultiplexes them by id. Statements that
|
|
30
|
+
* share a transaction go to one stream, in arrival order, so the server sees them in the order the
|
|
31
|
+
* caller wrote them; autocommit statements are free to use any stream.
|
|
32
|
+
*
|
|
33
|
+
* A stream that faults is rebuilt by the next write, and every operation that was in flight on the
|
|
34
|
+
* old one is failed so its caller can decide whether to replay it. Prepared statements are scoped
|
|
35
|
+
* to the stream that registered them, and a handle from a stream that is gone is refused at the
|
|
36
|
+
* last moment before it would be written.
|
|
37
|
+
*/
|
|
38
|
+
export class GrpcBatcher {
|
|
39
|
+
options;
|
|
40
|
+
streamFactory;
|
|
41
|
+
slots;
|
|
42
|
+
pending = new Map();
|
|
43
|
+
requestIdSeq = 0;
|
|
44
|
+
roundRobin = -1;
|
|
45
|
+
transportIdSeq = 0;
|
|
46
|
+
disposed = false;
|
|
47
|
+
constructor(options, streamFactory) {
|
|
48
|
+
this.options = options;
|
|
49
|
+
this.streamFactory = streamFactory;
|
|
50
|
+
const poolSize = Math.max(1, options.channelPoolSize);
|
|
51
|
+
this.slots = new Array(poolSize);
|
|
52
|
+
for (let i = 0; i < poolSize; i++) {
|
|
53
|
+
this.slots[i] = { index: i, inbox: [], pumping: false, stream: undefined, prepared: new Map() };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Reserves a stream for a transaction, so all of its operations land in one ordering chain. */
|
|
57
|
+
reserveSlot() {
|
|
58
|
+
return this.nextRoundRobin();
|
|
59
|
+
}
|
|
60
|
+
// ─── The enqueue surface ──────────────────────────────────────────────────
|
|
61
|
+
enqueueQuery(request, slotIndex, signal, expectedTransportId) {
|
|
62
|
+
return this.enqueue(GrpcBatchStatementKind.Query, request, slotIndex, signal, expectedTransportId);
|
|
63
|
+
}
|
|
64
|
+
enqueueNonQuery(request, slotIndex, signal, expectedTransportId) {
|
|
65
|
+
return this.enqueue(GrpcBatchStatementKind.NonQuery, request, slotIndex, signal, expectedTransportId);
|
|
66
|
+
}
|
|
67
|
+
enqueueStart(request, slotIndex, signal) {
|
|
68
|
+
return this.enqueue(GrpcBatchStatementKind.Start, request, slotIndex, signal);
|
|
69
|
+
}
|
|
70
|
+
enqueueCommit(request, slotIndex, signal) {
|
|
71
|
+
return this.enqueue(GrpcBatchStatementKind.Commit, request, slotIndex, signal);
|
|
72
|
+
}
|
|
73
|
+
async enqueueRollback(request, slotIndex, signal) {
|
|
74
|
+
await this.enqueue(GrpcBatchStatementKind.Rollback, request, slotIndex, signal);
|
|
75
|
+
}
|
|
76
|
+
// ─── Prepared statements ──────────────────────────────────────────────────
|
|
77
|
+
/**
|
|
78
|
+
* The registration for a statement on one stream, creating it when there is none.
|
|
79
|
+
*
|
|
80
|
+
* A registration whose stream has since been rebuilt is dropped and made again: the handle died
|
|
81
|
+
* with the stream, and the server would not know it.
|
|
82
|
+
*/
|
|
83
|
+
async ensurePrepared(slotIndex, database, sql, signal) {
|
|
84
|
+
const slot = this.slots[slotIndex];
|
|
85
|
+
const key = statementKey(database, sql);
|
|
86
|
+
for (;;) {
|
|
87
|
+
const existing = slot.prepared.get(key);
|
|
88
|
+
if (existing !== undefined) {
|
|
89
|
+
let entry;
|
|
90
|
+
try {
|
|
91
|
+
entry = await existing.pending;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Whoever created it already reported the failure to its own caller. Drop the poisoned
|
|
95
|
+
// entry and take a fresh turn rather than failing every later execution.
|
|
96
|
+
forget(slot, key, existing);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (entry.transportId === slot.stream?.id)
|
|
100
|
+
return entry;
|
|
101
|
+
// The slot's stream was rebuilt since this was registered, so the handle died with it.
|
|
102
|
+
forget(slot, key, existing);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const pending = this.registerPrepared(slotIndex, database, sql, signal);
|
|
106
|
+
const registration = { pending, resolved: undefined };
|
|
107
|
+
slot.prepared.set(key, registration);
|
|
108
|
+
try {
|
|
109
|
+
const entry = await pending;
|
|
110
|
+
registration.resolved = entry;
|
|
111
|
+
return entry;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
forget(slot, key, registration);
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async registerPrepared(slotIndex, database, sql, signal) {
|
|
120
|
+
const { result, transportId } = await this.enqueueTracked(GrpcBatchStatementKind.Prepare, { database, sql }, slotIndex, signal, undefined);
|
|
121
|
+
return { transportId, statementId: result.statementId, parameterNames: [...result.parameterNames] };
|
|
122
|
+
}
|
|
123
|
+
/** Drops a registration the server no longer honours, but only if it is still the current one. */
|
|
124
|
+
invalidatePrepared(slotIndex, database, sql, stale) {
|
|
125
|
+
const slot = this.slots[slotIndex];
|
|
126
|
+
const key = statementKey(database, sql);
|
|
127
|
+
const existing = slot.prepared.get(key);
|
|
128
|
+
if (existing?.resolved?.statementId === stale.statementId &&
|
|
129
|
+
existing.resolved.transportId === stale.transportId) {
|
|
130
|
+
forget(slot, key, existing);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Removes and reports every stream's registration of one statement, so each can be released. */
|
|
134
|
+
async takePrepared(database, sql) {
|
|
135
|
+
const key = statementKey(database, sql);
|
|
136
|
+
const taken = [];
|
|
137
|
+
for (const slot of this.slots) {
|
|
138
|
+
const registration = slot.prepared.get(key);
|
|
139
|
+
if (registration === undefined)
|
|
140
|
+
continue;
|
|
141
|
+
slot.prepared.delete(key);
|
|
142
|
+
try {
|
|
143
|
+
taken.push({ slotIndex: slot.index, entry: await registration.pending });
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// A registration that never succeeded has no handle to release.
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return taken;
|
|
150
|
+
}
|
|
151
|
+
/** Releases one registration on the stream that minted it. Best-effort. */
|
|
152
|
+
async closePrepared(slotIndex, entry, signal) {
|
|
153
|
+
try {
|
|
154
|
+
await this.enqueue(GrpcBatchStatementKind.Close, { statementId: entry.statementId }, slotIndex, signal, entry.transportId);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// The stream is already gone, and the handle was released with it.
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// ─── Enqueue and pump ─────────────────────────────────────────────────────
|
|
161
|
+
async enqueue(kind, request, slotIndex, signal, expectedTransportId) {
|
|
162
|
+
const { result } = await this.enqueueTracked(kind, request, slotIndex, signal, expectedTransportId);
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
enqueueTracked(kind, request, slotIndex, signal, expectedTransportId) {
|
|
166
|
+
if (this.disposed) {
|
|
167
|
+
return Promise.reject(new CamusError(CamusErrorCode.Generic, 'This gRPC transport is closed.'));
|
|
168
|
+
}
|
|
169
|
+
const slot = this.slots[slotIndex ?? this.nextRoundRobin()];
|
|
170
|
+
const requestId = ++this.requestIdSeq;
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
const op = {
|
|
173
|
+
requestId,
|
|
174
|
+
settled: false,
|
|
175
|
+
transportId: 0,
|
|
176
|
+
resolve: (value) => resolve({ result: value, transportId: op.transportId }),
|
|
177
|
+
reject,
|
|
178
|
+
};
|
|
179
|
+
if (signal !== undefined) {
|
|
180
|
+
if (signal.aborted) {
|
|
181
|
+
reject(signal.reason);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
op.signal = signal;
|
|
185
|
+
op.onAbort = () => this.fault(op, signal.reason);
|
|
186
|
+
signal.addEventListener('abort', op.onAbort, { once: true });
|
|
187
|
+
}
|
|
188
|
+
this.pending.set(requestId, op);
|
|
189
|
+
slot.inbox.push({ request: { requestId, kind, request }, op, expectedTransportId });
|
|
190
|
+
void this.pump(slot);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Drains one slot's queue onto its stream.
|
|
195
|
+
*
|
|
196
|
+
* Only one pump runs per slot, so the slot's stream has exactly one writer and needs no lock.
|
|
197
|
+
*/
|
|
198
|
+
async pump(slot) {
|
|
199
|
+
if (slot.pumping)
|
|
200
|
+
return;
|
|
201
|
+
slot.pumping = true;
|
|
202
|
+
try {
|
|
203
|
+
for (;;) {
|
|
204
|
+
let drained = 0;
|
|
205
|
+
for (;;) {
|
|
206
|
+
const item = slot.inbox.shift();
|
|
207
|
+
if (item === undefined)
|
|
208
|
+
break;
|
|
209
|
+
this.writeItem(slot, item);
|
|
210
|
+
drained++;
|
|
211
|
+
}
|
|
212
|
+
// Coalescing: after writing a small burst, pause briefly so more operations accumulate
|
|
213
|
+
// before the next drain writes them together. It applies only after a drain of two or
|
|
214
|
+
// more. A one-item drain is a request-and-reply exchange — one caller, whose next
|
|
215
|
+
// statement cannot arrive until this one is answered — and sleeping there would add the
|
|
216
|
+
// whole delay to every round trip while gaining nothing.
|
|
217
|
+
if (drained >= 2 &&
|
|
218
|
+
this.options.coalescingThreshold > 1 &&
|
|
219
|
+
drained < this.options.coalescingThreshold &&
|
|
220
|
+
this.options.coalescingDelayMs > 0) {
|
|
221
|
+
await sleep(this.options.coalescingDelayMs);
|
|
222
|
+
}
|
|
223
|
+
if (slot.inbox.length === 0)
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
slot.pumping = false;
|
|
229
|
+
// An item queued while the pump was finishing must not be left waiting for the next caller.
|
|
230
|
+
if (slot.inbox.length > 0 && !this.disposed)
|
|
231
|
+
void this.pump(slot);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
writeItem(slot, item) {
|
|
235
|
+
try {
|
|
236
|
+
const stream = slot.stream ?? this.reconnect(slot);
|
|
237
|
+
// A prepared execution names a handle that exists only on the stream that registered it.
|
|
238
|
+
// This is the last moment the two can be compared — check any earlier and the stream could
|
|
239
|
+
// still be rebuilt in between — so refuse here rather than send an operation the server can
|
|
240
|
+
// only answer with "unknown statement".
|
|
241
|
+
if (item.expectedTransportId !== undefined && stream.id !== item.expectedTransportId) {
|
|
242
|
+
throw new PreparedStatementStaleError();
|
|
243
|
+
}
|
|
244
|
+
item.op.transportId = stream.id;
|
|
245
|
+
stream.send(item.request);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
this.fault(item.op, error instanceof Error ? error : new Error(String(error)));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
reconnect(slot) {
|
|
252
|
+
const stream = this.streamFactory(++this.transportIdSeq);
|
|
253
|
+
slot.stream = stream;
|
|
254
|
+
stream.listen({
|
|
255
|
+
onMessage: (response) => this.demux(response),
|
|
256
|
+
onClose: (error) => {
|
|
257
|
+
// Only clear the slot when this stream is still the current one: a later reconnect must
|
|
258
|
+
// not be undone by an older stream's close event.
|
|
259
|
+
if (slot.stream === stream)
|
|
260
|
+
slot.stream = undefined;
|
|
261
|
+
this.failStreamPending(stream.id, error);
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
return stream;
|
|
265
|
+
}
|
|
266
|
+
// ─── Demultiplex ──────────────────────────────────────────────────────────
|
|
267
|
+
demux(response) {
|
|
268
|
+
const op = this.pending.get(response.requestId);
|
|
269
|
+
// Cancelled, timed out, or already settled. Drop it.
|
|
270
|
+
if (op === undefined)
|
|
271
|
+
return;
|
|
272
|
+
switch (response.payload) {
|
|
273
|
+
case 'schema':
|
|
274
|
+
op.schema = response.schema;
|
|
275
|
+
return;
|
|
276
|
+
case 'row':
|
|
277
|
+
(op.rows ??= []).push(response.row);
|
|
278
|
+
return;
|
|
279
|
+
case 'queryComplete': {
|
|
280
|
+
const complete = response.queryComplete;
|
|
281
|
+
this.complete(op, {
|
|
282
|
+
schema: op.schema,
|
|
283
|
+
rows: op.rows ?? [],
|
|
284
|
+
token: {
|
|
285
|
+
n: complete.causalTokenN,
|
|
286
|
+
l: fromWire(complete.causalTokenL),
|
|
287
|
+
c: fromWire(complete.causalTokenC),
|
|
288
|
+
},
|
|
289
|
+
cacheMetadata: decodeCacheMetadata(complete.cacheMetadata),
|
|
290
|
+
routing: decodeRoutingAdvice(complete.routing),
|
|
291
|
+
});
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
case 'nonQuery': {
|
|
295
|
+
const reply = response.nonQuery;
|
|
296
|
+
this.complete(op, {
|
|
297
|
+
affectedRows: reply.affectedRows,
|
|
298
|
+
token: {
|
|
299
|
+
n: reply.causalTokenN,
|
|
300
|
+
l: fromWire(reply.causalTokenL),
|
|
301
|
+
c: fromWire(reply.causalTokenC),
|
|
302
|
+
},
|
|
303
|
+
routing: decodeRoutingAdvice(reply.routing),
|
|
304
|
+
});
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
case 'startReply':
|
|
308
|
+
this.complete(op, response.startReply);
|
|
309
|
+
return;
|
|
310
|
+
case 'commitReply': {
|
|
311
|
+
const reply = response.commitReply;
|
|
312
|
+
this.complete(op, {
|
|
313
|
+
n: reply.causalTokenN,
|
|
314
|
+
l: fromWire(reply.causalTokenL),
|
|
315
|
+
c: fromWire(reply.causalTokenC),
|
|
316
|
+
});
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
case 'prepareReply':
|
|
320
|
+
this.complete(op, response.prepareReply);
|
|
321
|
+
return;
|
|
322
|
+
case 'rollbackReply':
|
|
323
|
+
case 'closeReply':
|
|
324
|
+
this.complete(op, undefined);
|
|
325
|
+
return;
|
|
326
|
+
case 'error':
|
|
327
|
+
this.fault(op, new CamusError(response.error.code, sanitizeErrorText(response.error.message)));
|
|
328
|
+
return;
|
|
329
|
+
default:
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
complete(op, result) {
|
|
334
|
+
if (!this.settle(op))
|
|
335
|
+
return;
|
|
336
|
+
op.resolve(result);
|
|
337
|
+
}
|
|
338
|
+
fault(op, error) {
|
|
339
|
+
if (!this.settle(op))
|
|
340
|
+
return;
|
|
341
|
+
op.reject(error);
|
|
342
|
+
}
|
|
343
|
+
settle(op) {
|
|
344
|
+
if (op.settled)
|
|
345
|
+
return false;
|
|
346
|
+
op.settled = true;
|
|
347
|
+
this.pending.delete(op.requestId);
|
|
348
|
+
if (op.onAbort !== undefined)
|
|
349
|
+
op.signal?.removeEventListener('abort', op.onAbort);
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
/** Fails every operation still in flight on a stream that closed, so its caller can replay. */
|
|
353
|
+
failStreamPending(transportId, error) {
|
|
354
|
+
for (const op of [...this.pending.values()]) {
|
|
355
|
+
if (op.transportId === transportId)
|
|
356
|
+
this.fault(op, error);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
nextRoundRobin() {
|
|
360
|
+
this.roundRobin = (this.roundRobin + 1) % this.slots.length;
|
|
361
|
+
return this.roundRobin;
|
|
362
|
+
}
|
|
363
|
+
/** Closes every stream and fails everything still in flight. */
|
|
364
|
+
async dispose() {
|
|
365
|
+
if (this.disposed)
|
|
366
|
+
return;
|
|
367
|
+
this.disposed = true;
|
|
368
|
+
for (const slot of this.slots) {
|
|
369
|
+
const stream = slot.stream;
|
|
370
|
+
slot.stream = undefined;
|
|
371
|
+
slot.inbox.length = 0;
|
|
372
|
+
try {
|
|
373
|
+
stream?.close();
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
// Best effort: the stream may already be broken.
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const closed = new CamusError(CamusErrorCode.Generic, 'This gRPC transport is closed.');
|
|
380
|
+
for (const op of [...this.pending.values()])
|
|
381
|
+
this.fault(op, closed);
|
|
382
|
+
await Promise.resolve();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function forget(slot, key, expected) {
|
|
386
|
+
if (slot.prepared.get(key) === expected)
|
|
387
|
+
slot.prepared.delete(key);
|
|
388
|
+
}
|
|
389
|
+
// A newline cannot appear in a database name, so it separates the two parts without ambiguity.
|
|
390
|
+
function statementKey(database, sql) {
|
|
391
|
+
return `${database}\n${sql}`;
|
|
392
|
+
}
|
|
393
|
+
function sleep(milliseconds) {
|
|
394
|
+
return new Promise((resolve) => {
|
|
395
|
+
const timer = setTimeout(resolve, milliseconds);
|
|
396
|
+
timer.unref?.();
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
//# sourceMappingURL=batcher.js.map
|