@zintrust/core 2.4.7 → 2.4.8

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/core",
3
- "version": "2.4.7",
3
+ "version": "2.4.8",
4
4
  "description": "Production-grade TypeScript backend framework for JavaScript",
5
5
  "homepage": "https://zintrust.com",
6
6
  "repository": {
@@ -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;AA2QtD,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;UAnTC,CAAC,OAAO,MAAM,KAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;WAsDjC,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;UArUC,CAAC,OAAO,MAAM,KAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;WAsDjC,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"}
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"}
@@ -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 many = driver.many;
105
+ const batchedMany = driver.many;
106
106
  let values;
107
- const batched = typeof many === 'function';
107
+ const batched = typeof batchedMany === 'function';
108
108
  if (batched) {
109
- values = await many(prefixedKeys);
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;AA6OF,eAAO,MAAM,eAAe;0BACE,6BAA6B,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;EAmC5F,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
  },
@@ -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":"AASA;;;GAGG;AACH,eAAO,MAAM,YAAY;IACvB;;;OAGG;kBACW,IAAI;IAclB;;;OAGG;yBACkB,OAAO;IAI5B;;OAEG;aACM,IAAI;IAIb;;;OAGG;gCACyB,OAAO,GAAG,IAAI;EAG1C,CAAC;AAEH,eAAe,YAAY,CAAC"}
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"}
@@ -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;
package/src/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * @zintrust/core v2.4.7
2
+ * @zintrust/core v2.4.8
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-01T22:49:09.622Z
8
+ * Built: 2026-06-02T08:44:54.755Z
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-01T22:49:09.586Z'; // Replaced during build
24
+ export const ZINTRUST_BUILD_DATE = '2026-06-02T08:44:54.704Z'; // 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';
@@ -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;AAOH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAWxD,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,MAAM,MAAM,QAAQ,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC,MAAM,WAAW,SAAS;IACxB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,WAAW,IAAI,OAAO,CAAC;IACvB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACjF,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClF,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACrF,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B,WAAW,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC;IACnC,aAAa,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IACzE,YAAY,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC1F,cAAc,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAC1E,aAAa,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC3F,kBAAkB,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAC;IACvD,OAAO,IAAI,eAAe,CAAC;IAC3B,SAAS,IAAI,cAAc,CAAC;IAC5B,OAAO,IAAI,IAAI,CAAC;CACjB;AAmgBD,eAAO,MAAM,QAAQ;IACnB;;OAEG;oBACa,cAAc,GAAG,SAAS;EAI1C,CAAC;AAIH,eAAO,MAAM,oBAAoB,GAC/B,SAAQ,cAAc,GAAG,SAAqB,EAC9C,uBAA0B,KACzB,OAAO,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAMxC,CAAC;AAEF,wBAAgB,WAAW,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,UAAU,SAAY,GAAG,SAAS,CAmCtF;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAQ1F;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAanD"}
1
+ {"version":3,"file":"Database.d.ts","sourceRoot":"","sources":["../../../src/orm/Database.ts"],"names":[],"mappings":"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"}
@@ -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
- * Minimal D1 Database interface for type safety
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
- bind(...values: unknown[]): {
12
- all<T = unknown>(): Promise<{
13
- results?: T[];
14
- success: boolean;
15
- error?: string;
16
- meta?: Record<string, unknown>;
17
- }>;
18
- first<T = unknown>(): Promise<T | null>;
19
- run(): Promise<{
20
- success: boolean;
21
- error?: string;
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":"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"}
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,EACV,cAAc,EAEd,gBAAgB,EAEjB,MAAM,sBAAsB,CAAC;AA0D9B;;GAEG;AACH,eAAO,MAAM,SAAS;IACpB;;OAEG;oBAEa,cAAc,GAAG,gBAAgB;EAqIjD,CAAC;AAEH,eAAe,SAAS,CAAC"}
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 = getD1Binding(_config);
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 = getD1Binding(_config);
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 = getD1Binding(_config);
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 = getD1Binding(_config);
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;AAiTF,eAAO,MAAM,eAAe;;;mBAGL,OAAO,OAAO,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC;EAqB5D,CAAC;AAEH,eAAe,eAAe,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) => void;
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":"AAsOA,eAAO,MAAM,iBAAiB;mBALJ,OAAO,CAAC,OAAO,CAAC;sBAlBjB,OAAO,GAAG,QAAQ,GAAG,QAAQ,WAAW,MAAM,KAAG,IAAI;2BA/FjE,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,KAAK,OAChD,MAAM,YACD,MAAM,QACV,OAAO,YACH,OAAO,UACT,MAAM,QACR,MAAM,KACX,IAAI;wBA+FC,MAAM,QACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YACnB,MAAM,YACN,MAAM,WACP,MAAM,KACd,IAAI;sBAlBkB,MAAM,iBAAiB,MAAM,YAAY,OAAO,KAAG,IAAI;8BAlC/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,KAClB,IAAI;yBAMqB,MAAM,YAAY,MAAM,KAAG,IAAI;EAiDzD,CAAC;AAEH,eAAe,iBAAiB,CAAC"}
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 (await import('@zintrust/trace'));
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) => void;
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":"AAgEA,eAAO,MAAM,uBAAuB;2BA9BvB,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,KAClB,IAAI;EAUL,CAAC;AAEH,eAAe,uBAAuB,CAAC"}
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({