@zintrust/core 2.4.7 → 2.4.9
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/package.json +1 -1
- package/src/cache/Cache.d.ts.map +1 -1
- package/src/cache/Cache.js +4 -4
- package/src/cli/d1/D1SqlMigrations.d.ts.map +1 -1
- package/src/cli/d1/D1SqlMigrations.js +4 -0
- package/src/config/features.d.ts +19 -0
- package/src/config/features.d.ts.map +1 -1
- package/src/config/features.js +40 -0
- package/src/functions/cloudflare.d.ts.map +1 -1
- package/src/functions/cloudflare.js +3 -1
- package/src/index.js +3 -3
- package/src/observability/PrometheusMetrics.d.ts.map +1 -1
- package/src/observability/PrometheusMetrics.js +3 -1
- package/src/orm/Database.d.ts +28 -1
- package/src/orm/Database.d.ts.map +1 -1
- package/src/orm/Database.js +26 -2
- package/src/orm/DatabaseAdapter.d.ts +69 -16
- package/src/orm/DatabaseAdapter.d.ts.map +1 -1
- package/src/orm/adapters/D1Adapter.d.ts.map +1 -1
- package/src/orm/adapters/D1Adapter.js +56 -16
- package/src/orm/adapters/D1ReadSession.d.ts +34 -0
- package/src/orm/adapters/D1ReadSession.d.ts.map +1 -0
- package/src/orm/adapters/D1ReadSession.js +69 -0
- package/src/proxy/d1/ZintrustD1Proxy.d.ts.map +1 -1
- package/src/proxy/d1/ZintrustD1Proxy.js +16 -4
- package/src/trace/SystemTraceBridge.d.ts +4 -1
- package/src/trace/SystemTraceBridge.d.ts.map +1 -1
- package/src/trace/SystemTraceBridge.js +3 -3
- package/src/trace/SystemTraceWorkerBridge.d.ts +4 -1
- package/src/trace/SystemTraceWorkerBridge.d.ts.map +1 -1
- package/src/trace/SystemTraceWorkerBridge.js +2 -2
package/package.json
CHANGED
package/src/cache/Cache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Cache.d.ts","sourceRoot":"","sources":["../../../src/cache/Cache.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"Cache.d.ts","sourceRoot":"","sources":["../../../src/cache/Cache.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA6QtD,KAAK,UAAU,GAAG,QAAQ,CAAC;IACzB,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3C,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IACnD,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7D,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7D,SAAS,EAAE,MAAM,WAAW,CAAC;CAC9B,CAAC,CAAC;AA0IH,eAAO,MAAM,KAAK;UArTC,CAAC,OAAO,MAAM,KAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;WAwDjC,CAAC,QAAQ,MAAM,EAAE,KAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;UAM1C,CAAC,OAAO,MAAM,SAAS,CAAC,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;kBAkBjD,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;iBAiBtB,OAAO,CAAC,IAAI,CAAC;eAgBb,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;qBAenB,MAAM,sBAAe,OAAO,CAAC,MAAM,CAAC;qBAapC,MAAM,sBAAe,OAAO,CAAC,MAAM,CAAC;qBAgB5C,WAAW;iCA6IG,MAAM,KAAG,OAAO;mBA3H9B,MAAM,KAAG,UAAU;iBAuHvB,IAAI;EA4BpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,KAAK;UAvUC,CAAC,OAAO,MAAM,KAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;WAwDjC,CAAC,QAAQ,MAAM,EAAE,KAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;UAM1C,CAAC,OAAO,MAAM,SAAS,CAAC,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;kBAkBjD,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;iBAiBtB,OAAO,CAAC,IAAI,CAAC;eAgBb,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;qBAenB,MAAM,sBAAe,OAAO,CAAC,MAAM,CAAC;qBAapC,MAAM,sBAAe,OAAO,CAAC,MAAM,CAAC;qBAgB5C,WAAW;iCA6IG,MAAM,KAAG,OAAO;mBA3H9B,MAAM,KAAG,UAAU;iBAuHvB,IAAI;EAiCI,CAAC"}
|
package/src/cache/Cache.js
CHANGED
|
@@ -102,14 +102,14 @@ const runMany = async (driver, keys, storeName) => {
|
|
|
102
102
|
return [];
|
|
103
103
|
const prefixedKeys = keys.map((key) => autoPrefixKey(key));
|
|
104
104
|
const startedAt = Date.now();
|
|
105
|
-
const
|
|
105
|
+
const batchedMany = driver.many;
|
|
106
106
|
let values;
|
|
107
|
-
const batched = typeof
|
|
107
|
+
const batched = typeof batchedMany === 'function';
|
|
108
108
|
if (batched) {
|
|
109
|
-
values = await
|
|
109
|
+
values = await batchedMany(prefixedKeys);
|
|
110
110
|
}
|
|
111
111
|
else {
|
|
112
|
-
values = await Promise.all(prefixedKeys.map(async (prefixedKey) => driver.get(prefixedKey)));
|
|
112
|
+
values = (await Promise.all(prefixedKeys.map(async (prefixedKey) => driver.get(prefixedKey))));
|
|
113
113
|
}
|
|
114
114
|
const elapsed = Date.now() - startedAt;
|
|
115
115
|
const normalized = prefixedKeys.map((_prefixedKey, index) => values[index] ?? null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"D1SqlMigrations.d.ts","sourceRoot":"","sources":["../../../../src/cli/d1/D1SqlMigrations.ts"],"names":[],"mappings":"AAYA,KAAK,6BAA6B,GAAG;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;
|
|
1
|
+
{"version":3,"file":"D1SqlMigrations.d.ts","sourceRoot":"","sources":["../../../../src/cli/d1/D1SqlMigrations.ts"],"names":[],"mappings":"AAYA,KAAK,6BAA6B,GAAG;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAmPF,eAAO,MAAM,eAAe;0BACE,6BAA6B,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;EAmC5F,CAAC"}
|
|
@@ -144,6 +144,10 @@ const createCaptureDb = (onSql) => {
|
|
|
144
144
|
await RESOLVED_VOID;
|
|
145
145
|
return callback(this);
|
|
146
146
|
},
|
|
147
|
+
async withReadSession(fn) {
|
|
148
|
+
const result = await fn(this);
|
|
149
|
+
return { result, bookmark: null };
|
|
150
|
+
},
|
|
147
151
|
table() {
|
|
148
152
|
throw ErrorFactory.createCliError('D1 SQL compilation does not support QueryBuilder-based migrations yet. Use db.query(...) with explicit SQL.');
|
|
149
153
|
},
|
package/src/config/features.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { D1ReadConstraint } from '../orm/DatabaseAdapter';
|
|
1
2
|
/**
|
|
2
3
|
* Feature Flags - Controls access to advanced/experimental features
|
|
3
4
|
* Sealed namespace for immutability
|
|
@@ -13,6 +14,16 @@ export declare const FeatureFlags: Readonly<{
|
|
|
13
14
|
* Returns cached flag value (no environment lookup)
|
|
14
15
|
*/
|
|
15
16
|
isRawQueryEnabled(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Whether D1 read replication (Sessions API) is enabled. When off,
|
|
19
|
+
* `IDatabase.withReadSession` is a passthrough and reads hit the primary.
|
|
20
|
+
*/
|
|
21
|
+
isD1ReadReplicationEnabled(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Default session constraint applied by `withReadSession` when the caller
|
|
24
|
+
* does not specify one.
|
|
25
|
+
*/
|
|
26
|
+
getD1ReadDefaultConstraint(): D1ReadConstraint;
|
|
16
27
|
/**
|
|
17
28
|
* Reset flags (primarily for testing)
|
|
18
29
|
*/
|
|
@@ -22,6 +33,14 @@ export declare const FeatureFlags: Readonly<{
|
|
|
22
33
|
* Primarily for testing to avoid 'as any' type casting
|
|
23
34
|
*/
|
|
24
35
|
setRawQueryEnabled(enabled: boolean): void;
|
|
36
|
+
/**
|
|
37
|
+
* Set D1 read replication enabled state (primarily for testing).
|
|
38
|
+
*/
|
|
39
|
+
setD1ReadReplicationEnabled(enabled: boolean): void;
|
|
40
|
+
/**
|
|
41
|
+
* Set the default D1 read constraint (primarily for testing).
|
|
42
|
+
*/
|
|
43
|
+
setD1ReadDefaultConstraint(constraint: D1ReadConstraint): void;
|
|
25
44
|
}>;
|
|
26
45
|
export default FeatureFlags;
|
|
27
46
|
//# sourceMappingURL=features.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../../../src/config/features.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../../../src/config/features.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAiB7D;;;GAGG;AACH,eAAO,MAAM,YAAY;IACvB;;;OAGG;kBACW,IAAI;IAyBlB;;;OAGG;yBACkB,OAAO;IAI5B;;;OAGG;kCAC2B,OAAO;IAIrC;;;OAGG;kCAC2B,gBAAgB;IAI9C;;OAEG;aACM,IAAI;IAMb;;;OAGG;gCACyB,OAAO,GAAG,IAAI;IAI1C;;OAEG;yCACkC,OAAO,GAAG,IAAI;IAInD;;OAEG;2CACoC,gBAAgB,GAAG,IAAI;EAG9D,CAAC;AAEH,eAAe,YAAY,CAAC"}
|
package/src/config/features.js
CHANGED
|
@@ -5,6 +5,13 @@ import { Logger } from './logger.js';
|
|
|
5
5
|
* Internal state managed by the module
|
|
6
6
|
*/
|
|
7
7
|
let _rawQueryEnabled = false;
|
|
8
|
+
const D1_READ_DEFAULT_CONSTRAINT = 'first-unconstrained';
|
|
9
|
+
let _d1ReadReplicationEnabled = false;
|
|
10
|
+
let _d1ReadDefaultConstraint = D1_READ_DEFAULT_CONSTRAINT;
|
|
11
|
+
const normalizeReadConstraint = (raw) => {
|
|
12
|
+
const value = raw.trim().toLowerCase();
|
|
13
|
+
return value === 'first-primary' ? 'first-primary' : 'first-unconstrained';
|
|
14
|
+
};
|
|
8
15
|
/**
|
|
9
16
|
* Feature Flags - Controls access to advanced/experimental features
|
|
10
17
|
* Sealed namespace for immutability
|
|
@@ -16,6 +23,11 @@ export const FeatureFlags = Object.freeze({
|
|
|
16
23
|
*/
|
|
17
24
|
initialize() {
|
|
18
25
|
_rawQueryEnabled = Env.get('USE_RAW_QRY') === 'true';
|
|
26
|
+
_d1ReadReplicationEnabled = Env.getBool('D1_READ_REPLICATION', false);
|
|
27
|
+
_d1ReadDefaultConstraint = normalizeReadConstraint(Env.get('D1_READ_DEFAULT_CONSTRAINT', D1_READ_DEFAULT_CONSTRAINT));
|
|
28
|
+
if (_d1ReadReplicationEnabled) {
|
|
29
|
+
Logger.info(`✓ D1 read replication ENABLED (default constraint: ${_d1ReadDefaultConstraint})`);
|
|
30
|
+
}
|
|
19
31
|
if (_rawQueryEnabled) {
|
|
20
32
|
Logger.warn('⚠️ FEATURE FLAG ENABLED: Raw SQL Queries are now available via adapter.rawQuery()');
|
|
21
33
|
Logger.warn('⚠️ This bypasses QueryBuilder safety - use only when necessary');
|
|
@@ -32,11 +44,27 @@ export const FeatureFlags = Object.freeze({
|
|
|
32
44
|
isRawQueryEnabled() {
|
|
33
45
|
return _rawQueryEnabled;
|
|
34
46
|
},
|
|
47
|
+
/**
|
|
48
|
+
* Whether D1 read replication (Sessions API) is enabled. When off,
|
|
49
|
+
* `IDatabase.withReadSession` is a passthrough and reads hit the primary.
|
|
50
|
+
*/
|
|
51
|
+
isD1ReadReplicationEnabled() {
|
|
52
|
+
return _d1ReadReplicationEnabled;
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
* Default session constraint applied by `withReadSession` when the caller
|
|
56
|
+
* does not specify one.
|
|
57
|
+
*/
|
|
58
|
+
getD1ReadDefaultConstraint() {
|
|
59
|
+
return _d1ReadDefaultConstraint;
|
|
60
|
+
},
|
|
35
61
|
/**
|
|
36
62
|
* Reset flags (primarily for testing)
|
|
37
63
|
*/
|
|
38
64
|
reset() {
|
|
39
65
|
_rawQueryEnabled = false;
|
|
66
|
+
_d1ReadReplicationEnabled = false;
|
|
67
|
+
_d1ReadDefaultConstraint = D1_READ_DEFAULT_CONSTRAINT;
|
|
40
68
|
},
|
|
41
69
|
/**
|
|
42
70
|
* Set raw query enabled state
|
|
@@ -45,5 +73,17 @@ export const FeatureFlags = Object.freeze({
|
|
|
45
73
|
setRawQueryEnabled(enabled) {
|
|
46
74
|
_rawQueryEnabled = enabled;
|
|
47
75
|
},
|
|
76
|
+
/**
|
|
77
|
+
* Set D1 read replication enabled state (primarily for testing).
|
|
78
|
+
*/
|
|
79
|
+
setD1ReadReplicationEnabled(enabled) {
|
|
80
|
+
_d1ReadReplicationEnabled = enabled;
|
|
81
|
+
},
|
|
82
|
+
/**
|
|
83
|
+
* Set the default D1 read constraint (primarily for testing).
|
|
84
|
+
*/
|
|
85
|
+
setD1ReadDefaultConstraint(constraint) {
|
|
86
|
+
_d1ReadDefaultConstraint = constraint;
|
|
87
|
+
},
|
|
48
88
|
});
|
|
49
89
|
export default FeatureFlags;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../../src/functions/cloudflare.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../../src/functions/cloudflare.ts"],"names":[],"mappings":";mBA2PuB,OAAO,QAAQ,OAAO,QAAQ,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;;AADhF,wBAoDE;AAGF,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA2B;gBAExC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO;IAKlC,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;CAkBjD"}
|
|
@@ -150,7 +150,9 @@ const injectIoredisModule = async () => {
|
|
|
150
150
|
if (globalAny.__zintrustIoredisModule !== undefined)
|
|
151
151
|
return;
|
|
152
152
|
try {
|
|
153
|
-
|
|
153
|
+
// Variable specifier so bundlers do not inline ioredis into the Workers bundle.
|
|
154
|
+
const ioredisPkg = 'ioredis';
|
|
155
|
+
const module = await import(ioredisPkg);
|
|
154
156
|
globalAny.__zintrustIoredisModule = module;
|
|
155
157
|
}
|
|
156
158
|
catch {
|
package/src/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @zintrust/core v2.4.
|
|
2
|
+
* @zintrust/core v2.4.9
|
|
3
3
|
*
|
|
4
4
|
* ZinTrust Framework - Production-Grade TypeScript Backend
|
|
5
5
|
* Built for performance, type safety, and exceptional developer experience
|
|
6
6
|
*
|
|
7
7
|
* Build Information:
|
|
8
|
-
* Built: 2026-06-
|
|
8
|
+
* Built: 2026-06-02T13:16:18.772Z
|
|
9
9
|
* Node: >=20.0.0
|
|
10
10
|
* License: MIT
|
|
11
11
|
*
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* Available at runtime for debugging and health checks
|
|
22
22
|
*/
|
|
23
23
|
export const ZINTRUST_VERSION = '0.1.41';
|
|
24
|
-
export const ZINTRUST_BUILD_DATE = '2026-06-
|
|
24
|
+
export const ZINTRUST_BUILD_DATE = '2026-06-02T13:16:18.729Z'; // Replaced during build
|
|
25
25
|
export { Application } from './boot/Application.js';
|
|
26
26
|
export { AwsSigV4 } from './common/index.js';
|
|
27
27
|
export { SignedRequest } from './security/SignedRequest.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PrometheusMetrics.d.ts","sourceRoot":"","sources":["../../../src/observability/PrometheusMetrics.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"PrometheusMetrics.d.ts","sourceRoot":"","sources":["../../../src/observability/PrometheusMetrics.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA+GH,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,iBAAiB;sBACJ,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;8BAWtC,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;0BAY3C,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;EAS/D,CAAC;AAEH,eAAe,iBAAiB,CAAC"}
|
|
@@ -28,7 +28,9 @@ async function ensureState() {
|
|
|
28
28
|
statePromise = (async () => {
|
|
29
29
|
let client;
|
|
30
30
|
try {
|
|
31
|
-
|
|
31
|
+
// Variable specifier so bundlers do not inline prom-client into the Workers bundle.
|
|
32
|
+
const promClientPkg = 'prom-client';
|
|
33
|
+
client = await import(promClientPkg);
|
|
32
34
|
}
|
|
33
35
|
catch {
|
|
34
36
|
// prom-client is intentionally optional at runtime.
|
package/src/orm/Database.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Central database connection management and query execution
|
|
4
4
|
*/
|
|
5
5
|
import type { SupportedDriver } from '../migrations/enum';
|
|
6
|
-
import { type DatabaseConfig, type IDatabaseAdapter, type QueryResult } from './DatabaseAdapter';
|
|
6
|
+
import { type D1ReadConstraint, type DatabaseConfig, type IDatabaseAdapter, type QueryResult } from './DatabaseAdapter';
|
|
7
7
|
import type { IQueryBuilder } from './QueryBuilder';
|
|
8
8
|
export type RawValue = {
|
|
9
9
|
__raw: string;
|
|
@@ -17,6 +17,33 @@ export interface IDatabase {
|
|
|
17
17
|
execute(sql: string, parameters?: unknown[], isRead?: boolean): Promise<QueryResult>;
|
|
18
18
|
raw(value: string): RawValue;
|
|
19
19
|
transaction<T>(callback: (db: IDatabase) => Promise<T>): Promise<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Scope a unit of work to a D1 read-replication session and surface the
|
|
22
|
+
* resulting bookmark for the caller to persist and replay.
|
|
23
|
+
*
|
|
24
|
+
* Reads inside the session observe sequential consistency; writes still hit
|
|
25
|
+
* the primary. The `constraint` only governs where the first read may be
|
|
26
|
+
* served, and defaults to `D1_READ_DEFAULT_CONSTRAINT`
|
|
27
|
+
* (`"first-unconstrained"`).
|
|
28
|
+
*
|
|
29
|
+
* Consistency guidance for adopters:
|
|
30
|
+
* - Persist a **per-actor bookmark** (e.g. a signed cookie/header) and pass
|
|
31
|
+
* it as `constraint` so a user always reads state at least as fresh as
|
|
32
|
+
* their own last write, across requests and regions.
|
|
33
|
+
* - Use `"first-primary"` for a read that immediately follows a write in the
|
|
34
|
+
* **same** request and must be read-your-writes without a bookmark.
|
|
35
|
+
* - Pure, staleness-tolerant reads (catalogs, listings, dashboards) can use
|
|
36
|
+
* `"first-unconstrained"` with no bookmark for the lowest latency.
|
|
37
|
+
*
|
|
38
|
+
* When `D1_READ_REPLICATION` is off or the driver has no session support,
|
|
39
|
+
* this runs `fn` directly against the primary and returns `bookmark: null`.
|
|
40
|
+
*/
|
|
41
|
+
withReadSession<T>(fn: (db: IDatabase) => Promise<T>, options?: {
|
|
42
|
+
constraint?: D1ReadConstraint;
|
|
43
|
+
}): Promise<{
|
|
44
|
+
result: T;
|
|
45
|
+
bookmark: string | null;
|
|
46
|
+
}>;
|
|
20
47
|
table(name: string): IQueryBuilder;
|
|
21
48
|
onBeforeQuery(handler: (query: string, params: unknown[]) => void): void;
|
|
22
49
|
onAfterQuery(handler: (query: string, params: unknown[], duration: number) => void): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Database.d.ts","sourceRoot":"","sources":["../../../src/orm/Database.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"Database.d.ts","sourceRoot":"","sources":["../../../src/orm/Database.ts"],"names":[],"mappings":"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"}
|
package/src/orm/Database.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { OpenTelemetry } from '../observability/OpenTelemetry.js';
|
|
6
6
|
import { Cloudflare } from '../config/cloudflare.js';
|
|
7
7
|
import { Env } from '../config/env.js';
|
|
8
|
+
import { FeatureFlags } from '../config/features.js';
|
|
8
9
|
import { Logger } from '../config/logger.js';
|
|
9
10
|
import { ErrorFactory } from '../exceptions/ZintrustError.js';
|
|
10
11
|
import { EventEmitter } from '../node-singletons/events.js';
|
|
@@ -175,6 +176,15 @@ const initializeAdapters = (dbConfig) => {
|
|
|
175
176
|
const readAdapters = [writeAdapter];
|
|
176
177
|
return { writeAdapter, readAdapters };
|
|
177
178
|
};
|
|
179
|
+
/**
|
|
180
|
+
* Read-replication routing metadata to surface in traces, when present.
|
|
181
|
+
*/
|
|
182
|
+
const routingMeta = (result) => {
|
|
183
|
+
if (result.servedByPrimary === undefined && result.servedByRegion === undefined) {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
return { servedByPrimary: result.servedByPrimary, servedByRegion: result.servedByRegion };
|
|
187
|
+
};
|
|
178
188
|
/**
|
|
179
189
|
* Execute a query with events and timing
|
|
180
190
|
*/
|
|
@@ -183,7 +193,7 @@ const executeQuery = async (adapter, eventEmitter, sql, parameters, method) => {
|
|
|
183
193
|
const startTime = Date.now();
|
|
184
194
|
const result = await adapter[method](sql, parameters);
|
|
185
195
|
const duration = Date.now() - startTime;
|
|
186
|
-
eventEmitter.emit('after-query', sql, parameters, duration);
|
|
196
|
+
eventEmitter.emit('after-query', sql, parameters, duration, routingMeta(result));
|
|
187
197
|
return BaseAdapter.normalizeRows(result.rows);
|
|
188
198
|
};
|
|
189
199
|
const executeFullQuery = async (adapter, eventEmitter, sql, parameters) => {
|
|
@@ -191,7 +201,7 @@ const executeFullQuery = async (adapter, eventEmitter, sql, parameters) => {
|
|
|
191
201
|
const startTime = Date.now();
|
|
192
202
|
const result = await adapter.query(sql, parameters);
|
|
193
203
|
const duration = Date.now() - startTime;
|
|
194
|
-
eventEmitter.emit('after-query', sql, parameters, duration);
|
|
204
|
+
eventEmitter.emit('after-query', sql, parameters, duration, routingMeta(result));
|
|
195
205
|
return BaseAdapter.normalizeQueryResult(result);
|
|
196
206
|
};
|
|
197
207
|
const executeQueryOne = async (adapter, eventEmitter, sql, parameters) => {
|
|
@@ -308,6 +318,18 @@ const createQueryHandlers = (writeAdapter, _readAdapters, eventEmitter, connecte
|
|
|
308
318
|
await db.connect();
|
|
309
319
|
return writeAdapter.transaction(async () => callback(db));
|
|
310
320
|
},
|
|
321
|
+
async withReadSession(fn, options) {
|
|
322
|
+
if (connected.value === false)
|
|
323
|
+
await db.connect();
|
|
324
|
+
// The write adapter owns the D1 binding; read adapters for D1 alias it.
|
|
325
|
+
const runReadSession = writeAdapter.runReadSession?.bind(writeAdapter);
|
|
326
|
+
if (FeatureFlags.isD1ReadReplicationEnabled() === false || runReadSession === undefined) {
|
|
327
|
+
const result = await fn(db);
|
|
328
|
+
return { result, bookmark: null };
|
|
329
|
+
}
|
|
330
|
+
const constraint = options?.constraint ?? FeatureFlags.getD1ReadDefaultConstraint();
|
|
331
|
+
return runReadSession(constraint, async () => fn(db));
|
|
332
|
+
},
|
|
311
333
|
};
|
|
312
334
|
};
|
|
313
335
|
const setupDbInstrumentation = (dbConfig, eventEmitter) => {
|
|
@@ -325,6 +347,7 @@ const applyQueryHandlers = (db, queryHandlers) => {
|
|
|
325
347
|
db.queryOne = queryHandlers.queryOne;
|
|
326
348
|
db.execute = queryHandlers.execute;
|
|
327
349
|
db.transaction = queryHandlers.transaction;
|
|
350
|
+
db.withReadSession = queryHandlers.withReadSession;
|
|
328
351
|
};
|
|
329
352
|
const createDbFacade = (input) => {
|
|
330
353
|
const { dbConfig, writeAdapter, eventEmitter, connected, getAdapter, connectionHandlers, queryHandlers, cleanupFunctions, } = input;
|
|
@@ -341,6 +364,7 @@ const createDbFacade = (input) => {
|
|
|
341
364
|
return { __raw: value };
|
|
342
365
|
},
|
|
343
366
|
transaction: queryHandlers.transaction,
|
|
367
|
+
withReadSession: queryHandlers.withReadSession,
|
|
344
368
|
table(name) {
|
|
345
369
|
return QueryBuilder.create(name, db);
|
|
346
370
|
},
|
|
@@ -4,24 +4,56 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { SupportedDriver } from '../migrations/enum';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Read-replication constraint for a D1 session.
|
|
8
|
+
*
|
|
9
|
+
* - `"first-primary"`: first query in the session goes to the primary
|
|
10
|
+
* (read-your-writes without a bookmark).
|
|
11
|
+
* - `"first-unconstrained"`: first query may be served by any replica
|
|
12
|
+
* (lowest latency; tolerates slightly stale reads).
|
|
13
|
+
* - any other `string`: an opaque D1 bookmark that resumes a prior session's
|
|
14
|
+
* consistency point.
|
|
15
|
+
*/
|
|
16
|
+
export type D1ReadConstraint = 'first-primary' | 'first-unconstrained' | (string & {});
|
|
17
|
+
/**
|
|
18
|
+
* Bound statement shape shared by the primary binding and a session handle.
|
|
19
|
+
*/
|
|
20
|
+
export interface ID1BoundStatement {
|
|
21
|
+
all<T = unknown>(): Promise<{
|
|
22
|
+
results?: T[];
|
|
23
|
+
success: boolean;
|
|
24
|
+
error?: string;
|
|
25
|
+
meta?: Record<string, unknown>;
|
|
26
|
+
}>;
|
|
27
|
+
first<T = unknown>(): Promise<T | null>;
|
|
28
|
+
run(): Promise<{
|
|
29
|
+
success: boolean;
|
|
30
|
+
error?: string;
|
|
31
|
+
meta?: Record<string, unknown>;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
export interface ID1PreparedStatement {
|
|
35
|
+
bind(...values: unknown[]): ID1BoundStatement;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Minimal D1 Database interface for type safety.
|
|
39
|
+
*
|
|
40
|
+
* `withSession` is optional: it is present when the bound database has read
|
|
41
|
+
* replication enabled. The driver tolerates its absence and falls back to
|
|
42
|
+
* direct-to-primary execution.
|
|
8
43
|
*/
|
|
9
44
|
export interface ID1Database {
|
|
10
|
-
prepare(sql: string):
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}>;
|
|
23
|
-
};
|
|
24
|
-
};
|
|
45
|
+
prepare(sql: string): ID1PreparedStatement;
|
|
46
|
+
withSession?(constraint?: D1ReadConstraint): ID1DatabaseSession;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Session-scoped handle returned by `ID1Database.withSession`.
|
|
50
|
+
*
|
|
51
|
+
* Carries a bookmark representing a point in the database's history so reads
|
|
52
|
+
* within the session observe sequential consistency.
|
|
53
|
+
*/
|
|
54
|
+
export interface ID1DatabaseSession {
|
|
55
|
+
prepare(sql: string): ID1PreparedStatement;
|
|
56
|
+
getBookmark(): string | null;
|
|
25
57
|
}
|
|
26
58
|
export interface DatabaseConfig {
|
|
27
59
|
d1?: ID1Database;
|
|
@@ -39,6 +71,13 @@ export interface QueryResult {
|
|
|
39
71
|
rows: Record<string, unknown>[];
|
|
40
72
|
rowCount: number;
|
|
41
73
|
lastInsertId?: string | number | bigint;
|
|
74
|
+
/**
|
|
75
|
+
* Read-replication routing metadata, when the driver exposes it (D1).
|
|
76
|
+
* `servedByPrimary` is `true` when the query hit the primary, `false` when a
|
|
77
|
+
* replica served it; `servedByRegion` is the replica/primary region code.
|
|
78
|
+
*/
|
|
79
|
+
servedByPrimary?: boolean;
|
|
80
|
+
servedByRegion?: string;
|
|
42
81
|
}
|
|
43
82
|
export interface IDatabaseAdapter {
|
|
44
83
|
/**
|
|
@@ -85,6 +124,20 @@ export interface IDatabaseAdapter {
|
|
|
85
124
|
* For SQL databases, this typically means dropping user tables.
|
|
86
125
|
*/
|
|
87
126
|
resetSchema?(): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* Run `fn` inside a read-replication session (D1 only).
|
|
129
|
+
*
|
|
130
|
+
* Every statement issued by `fn` is routed through a session-scoped handle so
|
|
131
|
+
* reads observe sequential consistency. Returns the function result together
|
|
132
|
+
* with the latest bookmark to persist and replay on the next request.
|
|
133
|
+
*
|
|
134
|
+
* Adapters without read-replication support leave this undefined; callers
|
|
135
|
+
* should fall back to executing `fn` directly with a `null` bookmark.
|
|
136
|
+
*/
|
|
137
|
+
runReadSession?<T>(constraint: D1ReadConstraint, fn: () => Promise<T>): Promise<{
|
|
138
|
+
result: T;
|
|
139
|
+
bookmark: string | null;
|
|
140
|
+
}>;
|
|
88
141
|
/**
|
|
89
142
|
* Get database type
|
|
90
143
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatabaseAdapter.d.ts","sourceRoot":"","sources":["../../../src/orm/DatabaseAdapter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD
|
|
1
|
+
{"version":3,"file":"DatabaseAdapter.d.ts","sourceRoot":"","sources":["../../../src/orm/DatabaseAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"D1Adapter.d.ts","sourceRoot":"","sources":["../../../../src/orm/adapters/D1Adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"D1Adapter.d.ts","sourceRoot":"","sources":["../../../../src/orm/adapters/D1Adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,KAAK,EAEV,cAAc,EAGd,gBAAgB,EAEjB,MAAM,sBAAsB,CAAC;AA2E9B;;GAEG;AACH,eAAO,MAAM,SAAS;IACpB;;OAEG;oBAEa,cAAc,GAAG,gBAAgB;EAiKjD,CAAC;AAEH,eAAe,SAAS,CAAC"}
|
|
@@ -8,6 +8,7 @@ import { ErrorFactory } from '../../exceptions/ZintrustError.js';
|
|
|
8
8
|
import { isObject } from '../../helper/index.js';
|
|
9
9
|
import { AdaptersEnum } from '../../migrations/enum/index.js';
|
|
10
10
|
import { BaseAdapter } from '../DatabaseAdapter.js';
|
|
11
|
+
import { createReadSessionScope, openSession } from '../adapters/D1ReadSession.js';
|
|
11
12
|
import { QueryBuilder } from '../QueryBuilder.js';
|
|
12
13
|
const isRecord = (value) => isObject(value);
|
|
13
14
|
const toNumber = (value) => {
|
|
@@ -44,6 +45,22 @@ const extractMeta = (value) => {
|
|
|
44
45
|
toInsertId(value['last_insert_rowid']);
|
|
45
46
|
return { changes, lastInsertId };
|
|
46
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* Extract read-replication routing metadata from a D1 result `meta`, when D1
|
|
50
|
+
* exposes it. Returns an empty object when replication is off (no such fields).
|
|
51
|
+
*/
|
|
52
|
+
const extractServedBy = (value) => {
|
|
53
|
+
if (!isRecord(value))
|
|
54
|
+
return {};
|
|
55
|
+
const out = {};
|
|
56
|
+
const primary = value['served_by_primary'];
|
|
57
|
+
if (typeof primary === 'boolean')
|
|
58
|
+
out.servedByPrimary = primary;
|
|
59
|
+
const region = value['served_by_region'];
|
|
60
|
+
if (typeof region === 'string' && region.trim() !== '')
|
|
61
|
+
out.servedByRegion = region;
|
|
62
|
+
return out;
|
|
63
|
+
};
|
|
47
64
|
/**
|
|
48
65
|
* Get D1 binding from config or global environment
|
|
49
66
|
*/
|
|
@@ -60,6 +77,22 @@ export const D1Adapter = Object.freeze({
|
|
|
60
77
|
// eslint-disable-next-line max-lines-per-function
|
|
61
78
|
create(_config) {
|
|
62
79
|
let connected = false;
|
|
80
|
+
const sessionScope = createReadSessionScope();
|
|
81
|
+
/**
|
|
82
|
+
* Resolve the statement source for the current operation: the active
|
|
83
|
+
* read-replication session handle when one is in scope, otherwise the raw
|
|
84
|
+
* binding. Throws a config error when no binding is available.
|
|
85
|
+
*/
|
|
86
|
+
const resolveExecutor = () => {
|
|
87
|
+
const active = sessionScope.peek();
|
|
88
|
+
if (active !== undefined)
|
|
89
|
+
return active.db;
|
|
90
|
+
const db = getD1Binding(_config);
|
|
91
|
+
if (db === null) {
|
|
92
|
+
throw ErrorFactory.createConfigError('D1 database binding not found');
|
|
93
|
+
}
|
|
94
|
+
return db;
|
|
95
|
+
};
|
|
63
96
|
return {
|
|
64
97
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
65
98
|
async connect() {
|
|
@@ -74,10 +107,7 @@ export const D1Adapter = Object.freeze({
|
|
|
74
107
|
async query(sql, parameters) {
|
|
75
108
|
if (!connected)
|
|
76
109
|
throw ErrorFactory.createConnectionError('Database not connected');
|
|
77
|
-
const db =
|
|
78
|
-
if (db === null) {
|
|
79
|
-
throw ErrorFactory.createConfigError('D1 database binding not found');
|
|
80
|
-
}
|
|
110
|
+
const db = resolveExecutor();
|
|
81
111
|
try {
|
|
82
112
|
const stmt = db.prepare(sql);
|
|
83
113
|
if (isMutatingSql(sql)) {
|
|
@@ -88,6 +118,7 @@ export const D1Adapter = Object.freeze({
|
|
|
88
118
|
rows: [],
|
|
89
119
|
rowCount: meta.changes,
|
|
90
120
|
lastInsertId: meta.lastInsertId,
|
|
121
|
+
...extractServedBy(runRecord.meta),
|
|
91
122
|
};
|
|
92
123
|
}
|
|
93
124
|
const result = await stmt.bind(...parameters).all();
|
|
@@ -99,6 +130,7 @@ export const D1Adapter = Object.freeze({
|
|
|
99
130
|
rows,
|
|
100
131
|
rowCount: rows.length > 0 ? rows.length : meta.changes,
|
|
101
132
|
lastInsertId: meta.lastInsertId,
|
|
133
|
+
...extractServedBy(metaValue),
|
|
102
134
|
};
|
|
103
135
|
}
|
|
104
136
|
catch (error) {
|
|
@@ -108,10 +140,7 @@ export const D1Adapter = Object.freeze({
|
|
|
108
140
|
async queryOne(sql, parameters) {
|
|
109
141
|
if (!connected)
|
|
110
142
|
throw ErrorFactory.createConnectionError('Database not connected');
|
|
111
|
-
const db =
|
|
112
|
-
if (db === null) {
|
|
113
|
-
throw ErrorFactory.createConfigError('D1 database binding not found');
|
|
114
|
-
}
|
|
143
|
+
const db = resolveExecutor();
|
|
115
144
|
try {
|
|
116
145
|
const stmt = db.prepare(sql);
|
|
117
146
|
const result = await stmt.bind(...parameters).first();
|
|
@@ -124,10 +153,7 @@ export const D1Adapter = Object.freeze({
|
|
|
124
153
|
async ping() {
|
|
125
154
|
if (!connected)
|
|
126
155
|
throw ErrorFactory.createConnectionError('Database not connected');
|
|
127
|
-
const db =
|
|
128
|
-
if (db === null) {
|
|
129
|
-
throw ErrorFactory.createConfigError('D1 database binding not found');
|
|
130
|
-
}
|
|
156
|
+
const db = resolveExecutor();
|
|
131
157
|
try {
|
|
132
158
|
// Use a minimal, side-effect-free query.
|
|
133
159
|
await db.prepare(QueryBuilder.create('').select('1').toSQL()).bind().first();
|
|
@@ -147,6 +173,23 @@ export const D1Adapter = Object.freeze({
|
|
|
147
173
|
throw ErrorFactory.createTryCatchError('Transaction failed', error);
|
|
148
174
|
}
|
|
149
175
|
},
|
|
176
|
+
async runReadSession(constraint, fn) {
|
|
177
|
+
if (!connected)
|
|
178
|
+
throw ErrorFactory.createConnectionError('Database not connected');
|
|
179
|
+
const db = getD1Binding(_config);
|
|
180
|
+
if (db === null) {
|
|
181
|
+
throw ErrorFactory.createConfigError('D1 database binding not found');
|
|
182
|
+
}
|
|
183
|
+
const handle = openSession(db, constraint);
|
|
184
|
+
if (handle === null) {
|
|
185
|
+
// Replication not enabled on the binding: run directly against the
|
|
186
|
+
// primary and report no bookmark.
|
|
187
|
+
const result = await fn();
|
|
188
|
+
return { result, bookmark: null };
|
|
189
|
+
}
|
|
190
|
+
const result = await sessionScope.run(handle, fn);
|
|
191
|
+
return { result, bookmark: handle.getBookmark() };
|
|
192
|
+
},
|
|
150
193
|
getType() {
|
|
151
194
|
return AdaptersEnum.d1;
|
|
152
195
|
},
|
|
@@ -160,10 +203,7 @@ export const D1Adapter = Object.freeze({
|
|
|
160
203
|
if (!connected) {
|
|
161
204
|
throw ErrorFactory.createConnectionError('Database not connected');
|
|
162
205
|
}
|
|
163
|
-
const db =
|
|
164
|
-
if (db === null) {
|
|
165
|
-
throw ErrorFactory.createConfigError('D1 database binding not found');
|
|
166
|
-
}
|
|
206
|
+
const db = resolveExecutor();
|
|
167
207
|
try {
|
|
168
208
|
Logger.warn(`Raw SQL Query executed: ${sql}`, Logger.withTraceSkipContext({ sql, parameters }));
|
|
169
209
|
const stmt = db.prepare(sql);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D1 read-replication session scoping.
|
|
3
|
+
*
|
|
4
|
+
* Opens a D1 Sessions API handle and scopes it to the async context running a
|
|
5
|
+
* unit of work, so the D1 adapter can transparently route statements through
|
|
6
|
+
* the session while it is active. Falls back to direct-to-primary execution
|
|
7
|
+
* when the bound database has no `withSession` (replication disabled).
|
|
8
|
+
*
|
|
9
|
+
* Works in both `zin s` (Node) and `zin s --wg` (Workers): it lazily upgrades
|
|
10
|
+
* to `AsyncLocalStorage` when available and otherwise uses a synchronous
|
|
11
|
+
* single-slot fallback, mirroring `@http/RequestContext`.
|
|
12
|
+
*/
|
|
13
|
+
import type { D1ReadConstraint, ID1Database, ID1DatabaseSession } from '../DatabaseAdapter';
|
|
14
|
+
export interface D1SessionHandle {
|
|
15
|
+
readonly db: ID1DatabaseSession;
|
|
16
|
+
getBookmark(): string | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Open a session against the bound database, or `null` when the binding does
|
|
20
|
+
* not support read replication (then reads simply come from the primary).
|
|
21
|
+
*/
|
|
22
|
+
export declare const openSession: (d1: ID1Database, constraint: D1ReadConstraint) => D1SessionHandle | null;
|
|
23
|
+
export interface ReadSessionScope {
|
|
24
|
+
/** Run `fn` with `handle` bound to the current async context. */
|
|
25
|
+
run<T>(handle: D1SessionHandle, fn: () => Promise<T>): Promise<T>;
|
|
26
|
+
/** Read the session bound to the current async context, if any. */
|
|
27
|
+
peek(): D1SessionHandle | undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create an independent session scope. One per adapter instance keeps separate
|
|
31
|
+
* database connections from sharing session state.
|
|
32
|
+
*/
|
|
33
|
+
export declare const createReadSessionScope: () => ReadSessionScope;
|
|
34
|
+
//# sourceMappingURL=D1ReadSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"D1ReadSession.d.ts","sourceRoot":"","sources":["../../../../src/orm/adapters/D1ReadSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,kBAAkB,CAAC;IAChC,WAAW,IAAI,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW,GACtB,IAAI,WAAW,EACf,YAAY,gBAAgB,KAC3B,eAAe,GAAG,IAIpB,CAAC;AAkCF,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAClE,mEAAmE;IACnE,IAAI,IAAI,eAAe,GAAG,SAAS,CAAC;CACrC;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAAO,gBAiBzC,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D1 read-replication session scoping.
|
|
3
|
+
*
|
|
4
|
+
* Opens a D1 Sessions API handle and scopes it to the async context running a
|
|
5
|
+
* unit of work, so the D1 adapter can transparently route statements through
|
|
6
|
+
* the session while it is active. Falls back to direct-to-primary execution
|
|
7
|
+
* when the bound database has no `withSession` (replication disabled).
|
|
8
|
+
*
|
|
9
|
+
* Works in both `zin s` (Node) and `zin s --wg` (Workers): it lazily upgrades
|
|
10
|
+
* to `AsyncLocalStorage` when available and otherwise uses a synchronous
|
|
11
|
+
* single-slot fallback, mirroring `@http/RequestContext`.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Open a session against the bound database, or `null` when the binding does
|
|
15
|
+
* not support read replication (then reads simply come from the primary).
|
|
16
|
+
*/
|
|
17
|
+
export const openSession = (d1, constraint) => {
|
|
18
|
+
if (typeof d1.withSession !== 'function')
|
|
19
|
+
return null;
|
|
20
|
+
const session = d1.withSession(constraint);
|
|
21
|
+
return { db: session, getBookmark: () => session.getBookmark() ?? null };
|
|
22
|
+
};
|
|
23
|
+
const createFallbackStorage = () => {
|
|
24
|
+
let store;
|
|
25
|
+
return {
|
|
26
|
+
run(handle, callback) {
|
|
27
|
+
const prev = store;
|
|
28
|
+
store = handle;
|
|
29
|
+
try {
|
|
30
|
+
return callback();
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
store = prev;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
getStore() {
|
|
37
|
+
return store;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
let alsClassPromise = null;
|
|
42
|
+
const loadAsyncLocalStorage = async () => {
|
|
43
|
+
alsClassPromise ??= import('../../node-singletons/async_hooks.js')
|
|
44
|
+
.then((mod) => mod.AsyncLocalStorage ?? null)
|
|
45
|
+
.catch(() => null);
|
|
46
|
+
return alsClassPromise;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Create an independent session scope. One per adapter instance keeps separate
|
|
50
|
+
* database connections from sharing session state.
|
|
51
|
+
*/
|
|
52
|
+
export const createReadSessionScope = () => {
|
|
53
|
+
let storage = createFallbackStorage();
|
|
54
|
+
const ready = loadAsyncLocalStorage()
|
|
55
|
+
.then((AsyncLocalStorage) => {
|
|
56
|
+
if (AsyncLocalStorage !== null)
|
|
57
|
+
storage = new AsyncLocalStorage();
|
|
58
|
+
})
|
|
59
|
+
.catch(() => undefined);
|
|
60
|
+
return {
|
|
61
|
+
async run(handle, fn) {
|
|
62
|
+
await ready;
|
|
63
|
+
return storage.run(handle, fn);
|
|
64
|
+
},
|
|
65
|
+
peek() {
|
|
66
|
+
return storage.getStore();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ZintrustD1Proxy.d.ts","sourceRoot":"","sources":["../../../../src/proxy/d1/ZintrustD1Proxy.ts"],"names":[],"mappings":"AAUA,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC;AAEjD,KAAK,qBAAqB,GAAG;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE;QACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;QACtC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QACrE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;KAChG,CAAC;IACF,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF,CAAC;AAEF,KAAK,WAAW,CAAC,CAAC,IAAI;IACpB,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;CACf,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,mBAAmB,CAAC;IACpD,GAAG,EAAE,CAAC,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,KAAK,EAAE,CAAC,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5C,GAAG,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;CACjC,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,mBAAmB,CAAC;CAC/C,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,EAAE,CAAC,EAAE,UAAU,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;
|
|
1
|
+
{"version":3,"file":"ZintrustD1Proxy.d.ts","sourceRoot":"","sources":["../../../../src/proxy/d1/ZintrustD1Proxy.ts"],"names":[],"mappings":"AAUA,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC;AAEjD,KAAK,qBAAqB,GAAG;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE;QACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;QACtC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QACrE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;KAChG,CAAC;IACF,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF,CAAC;AAEF,KAAK,WAAW,CAAC,CAAC,IAAI;IACpB,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;CACf,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,mBAAmB,CAAC;IACpD,GAAG,EAAE,CAAC,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,KAAK,EAAE,CAAC,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5C,GAAG,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;CACjC,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,mBAAmB,CAAC;CAC/C,CAAC;AAEF,KAAK,KAAK,GAAG;IACX,EAAE,CAAC,EAAE,UAAU,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AA2UF,eAAO,MAAM,eAAe;;;mBAGL,OAAO,OAAO,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC;EAqB5D,CAAC;AAEH,eAAe,eAAe,CAAC"}
|
|
@@ -76,6 +76,18 @@ const isMutatingSql = (sql) => {
|
|
|
76
76
|
normalized.startsWith('alter') ||
|
|
77
77
|
normalized.startsWith('replace'));
|
|
78
78
|
};
|
|
79
|
+
const routingFromMeta = (meta) => {
|
|
80
|
+
if (!isRecord(meta))
|
|
81
|
+
return undefined;
|
|
82
|
+
const out = {};
|
|
83
|
+
const primary = meta['served_by_primary'];
|
|
84
|
+
if (typeof primary === 'boolean')
|
|
85
|
+
out.servedByPrimary = primary;
|
|
86
|
+
const region = meta['served_by_region'];
|
|
87
|
+
if (typeof region === 'string' && region.trim() !== '')
|
|
88
|
+
out.servedByRegion = region;
|
|
89
|
+
return out.servedByPrimary === undefined && out.servedByRegion === undefined ? undefined : out;
|
|
90
|
+
};
|
|
79
91
|
const requireDb = (env) => {
|
|
80
92
|
const db = resolveD1Binding(env);
|
|
81
93
|
if (db === null) {
|
|
@@ -149,7 +161,7 @@ const handleQuery = async (request, env) => {
|
|
|
149
161
|
.prepare(resolved.sql)
|
|
150
162
|
.bind(...resolved.params)
|
|
151
163
|
.all();
|
|
152
|
-
SystemTraceWorkerBridge.emitQuery(resolved.sql, resolved.params, Date.now() - startedAt, 'd1-proxy');
|
|
164
|
+
SystemTraceWorkerBridge.emitQuery(resolved.sql, resolved.params, Date.now() - startedAt, 'd1-proxy', routingFromMeta(result.meta));
|
|
153
165
|
const rows = result.results ?? [];
|
|
154
166
|
return json(200, { rows, rowCount: rows.length });
|
|
155
167
|
}
|
|
@@ -186,7 +198,7 @@ const handleExec = async (request, env) => {
|
|
|
186
198
|
.prepare(resolved.sql)
|
|
187
199
|
.bind(...resolved.params)
|
|
188
200
|
.run();
|
|
189
|
-
SystemTraceWorkerBridge.emitQuery(resolved.sql, resolved.params, Date.now() - startedAt, 'd1-proxy');
|
|
201
|
+
SystemTraceWorkerBridge.emitQuery(resolved.sql, resolved.params, Date.now() - startedAt, 'd1-proxy', routingFromMeta(out.meta));
|
|
190
202
|
return json(200, { ok: true, meta: out.meta });
|
|
191
203
|
}
|
|
192
204
|
catch (error) {
|
|
@@ -230,14 +242,14 @@ const handleStatement = async (request, env) => {
|
|
|
230
242
|
.prepare(sql)
|
|
231
243
|
.bind(...parsed.params)
|
|
232
244
|
.run();
|
|
233
|
-
SystemTraceWorkerBridge.emitQuery(sql, parsed.params, Date.now() - startedAt, 'd1-proxy');
|
|
245
|
+
SystemTraceWorkerBridge.emitQuery(sql, parsed.params, Date.now() - startedAt, 'd1-proxy', routingFromMeta(out.meta));
|
|
234
246
|
return json(200, { ok: true, meta: out.meta });
|
|
235
247
|
}
|
|
236
248
|
const out = await resolved.db
|
|
237
249
|
.prepare(sql)
|
|
238
250
|
.bind(...parsed.params)
|
|
239
251
|
.all();
|
|
240
|
-
SystemTraceWorkerBridge.emitQuery(sql, parsed.params, Date.now() - startedAt, 'd1-proxy');
|
|
252
|
+
SystemTraceWorkerBridge.emitQuery(sql, parsed.params, Date.now() - startedAt, 'd1-proxy', routingFromMeta(out.meta));
|
|
241
253
|
const rows = out.results ?? [];
|
|
242
254
|
return json(200, { rows, rowCount: rows.length });
|
|
243
255
|
}
|
|
@@ -21,7 +21,10 @@ export declare const SystemTraceBridge: Readonly<{
|
|
|
21
21
|
emitJobProcessed: (name: string) => void;
|
|
22
22
|
emitMail: (to: string, subject: string, template?: string, text?: string, html?: string) => void;
|
|
23
23
|
emitNotification: (notification: string, channels: string[], notifiable?: string, message?: string, payload?: unknown) => void;
|
|
24
|
-
emitQuery: (query: string, params: unknown[], duration: number, connection?: string
|
|
24
|
+
emitQuery: (query: string, params: unknown[], duration: number, connection?: string, routing?: {
|
|
25
|
+
servedByPrimary?: boolean;
|
|
26
|
+
servedByRegion?: string;
|
|
27
|
+
}) => void;
|
|
25
28
|
emitRedis: (command: string, duration: number) => void;
|
|
26
29
|
}>;
|
|
27
30
|
export default SystemTraceBridge;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SystemTraceBridge.d.ts","sourceRoot":"","sources":["../../../src/trace/SystemTraceBridge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SystemTraceBridge.d.ts","sourceRoot":"","sources":["../../../src/trace/SystemTraceBridge.ts"],"names":[],"mappings":"AA6OA,eAAO,MAAM,iBAAiB;mBALJ,OAAO,CAAC,OAAO,CAAC;sBAlBjB,OAAO,GAAG,QAAQ,GAAG,QAAQ,WAAW,MAAM,KAAG,IAAI;2BAhGjE,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,KAAK,OAChD,MAAM,YACD,MAAM,QACV,OAAO,YACH,OAAO,UACT,MAAM,QACR,MAAM,KACX,IAAI;wBAgGC,MAAM,QACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YACnB,MAAM,YACN,MAAM,WACP,MAAM,KACd,IAAI;sBAlBkB,MAAM,iBAAiB,MAAM,YAAY,OAAO,KAAG,IAAI;8BAnC/C;QAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACvC,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACzC,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,KAAG,IAAI;4BArDuB,MAAM,SAAS,MAAM,cAAc,MAAM,SAAS,OAAO,KAAG,IAAI;0BAYlE,MAAM,SAAS,KAAK,KAAG,IAAI;6BANxB,MAAM,KAAG,IAAI;mBAyBvC,MAAM,WACD,MAAM,aACJ,MAAM,SACV,MAAM,SACN,MAAM,KACZ,IAAI;qCAjBS,MAAM,YACV,MAAM,EAAE,eACL,MAAM,YACT,MAAM,YACN,OAAO,KAChB,IAAI;uBAoCE,MAAM,UACL,OAAO,EAAE,YACP,MAAM,eACH,MAAM,YACT;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,KAC/D,IAAI;yBAMqB,MAAM,YAAY,MAAM,KAAG,IAAI;EAiDzD,CAAC;AAEH,eAAe,iBAAiB,CAAC"}
|
|
@@ -2,7 +2,7 @@ let cachedModule;
|
|
|
2
2
|
let loadPromise = null;
|
|
3
3
|
const importSystemTrace = async () => {
|
|
4
4
|
try {
|
|
5
|
-
return
|
|
5
|
+
return await import('@zintrust/trace');
|
|
6
6
|
}
|
|
7
7
|
catch {
|
|
8
8
|
return null;
|
|
@@ -74,9 +74,9 @@ const emitHttpClient = (payload) => {
|
|
|
74
74
|
module.HttpClientWatcher?.emit(payload);
|
|
75
75
|
});
|
|
76
76
|
};
|
|
77
|
-
const emitQuery = (query, params, duration, connection) => {
|
|
77
|
+
const emitQuery = (query, params, duration, connection, routing) => {
|
|
78
78
|
withSystemTrace((module) => {
|
|
79
|
-
module.QueryWatcher?.emit(query, params, duration, connection);
|
|
79
|
+
module.QueryWatcher?.emit(query, params, duration, connection, routing);
|
|
80
80
|
});
|
|
81
81
|
};
|
|
82
82
|
const emitRedis = (command, duration) => {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export declare const SystemTraceWorkerBridge: Readonly<{
|
|
2
2
|
emitCache: (operation: "get" | "set" | "delete" | "clear" | "has", key: string, duration: number, hit?: boolean, payload?: unknown, store?: string, ttl?: number) => void;
|
|
3
3
|
emitEvent: (name: string, listenerCount: number, payload?: unknown) => void;
|
|
4
|
-
emitQuery: (query: string, params: unknown[], duration: number, connection?: string
|
|
4
|
+
emitQuery: (query: string, params: unknown[], duration: number, connection?: string, routing?: {
|
|
5
|
+
servedByPrimary?: boolean;
|
|
6
|
+
servedByRegion?: string;
|
|
7
|
+
}) => void;
|
|
5
8
|
}>;
|
|
6
9
|
export default SystemTraceWorkerBridge;
|
|
7
10
|
//# sourceMappingURL=SystemTraceWorkerBridge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SystemTraceWorkerBridge.d.ts","sourceRoot":"","sources":["../../../src/trace/SystemTraceWorkerBridge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SystemTraceWorkerBridge.d.ts","sourceRoot":"","sources":["../../../src/trace/SystemTraceWorkerBridge.ts"],"names":[],"mappings":"AAuEA,eAAO,MAAM,uBAAuB;2BA/BvB,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,KAAK,OAChD,MAAM,YACD,MAAM,QACV,OAAO,YACH,OAAO,UACT,MAAM,QACR,MAAM,KACX,IAAI;sBAMkB,MAAM,iBAAiB,MAAM,YAAY,OAAO,KAAG,IAAI;uBAOvE,MAAM,UACL,OAAO,EAAE,YACP,MAAM,eACH,MAAM,YACT;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,KAC/D,IAAI;EAUL,CAAC;AAEH,eAAe,uBAAuB,CAAC"}
|
|
@@ -22,9 +22,9 @@ const emitEvent = (name, listenerCount, payload) => {
|
|
|
22
22
|
module.emitEvent?.(name, listenerCount, payload);
|
|
23
23
|
});
|
|
24
24
|
};
|
|
25
|
-
const emitQuery = (query, params, duration, connection) => {
|
|
25
|
+
const emitQuery = (query, params, duration, connection, routing) => {
|
|
26
26
|
withWorkerTraceModule((module) => {
|
|
27
|
-
module.emitQuery?.(query, params, duration, connection);
|
|
27
|
+
module.emitQuery?.(query, params, duration, connection, routing);
|
|
28
28
|
});
|
|
29
29
|
};
|
|
30
30
|
export const SystemTraceWorkerBridge = Object.freeze({
|