okmodel 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +263 -23
  2. package/dist/contracts/catalog/document.d.ts +26 -2
  3. package/dist/contracts/driver.d.ts +7 -0
  4. package/dist/dialects/pg/ext/citext.js +1 -1
  5. package/dist/dialects/pg/ext/index.js +1 -1
  6. package/dist/dialects/pg/ext/pg-trgm.js +1 -1
  7. package/dist/dialects/pg/index.js +3 -3
  8. package/dist/dialects/pg/introspect.d.ts +6 -0
  9. package/dist/dialects/pg/role/sql.d.ts +21 -4
  10. package/dist/dialects/pg/schema.d.ts +15 -6
  11. package/dist/dialects/pg/table.d.ts +17 -1
  12. package/dist/dialects/pg/view/index.d.ts +20 -6
  13. package/dist/dialects/pg/view/index.js +1 -1
  14. package/dist/dialects/pg/view/scratch.d.ts +2 -1
  15. package/dist/runtime/catalog-cache.d.ts +17 -0
  16. package/dist/runtime/compat-words.d.ts +23 -0
  17. package/dist/runtime/drift-detail.d.ts +20 -4
  18. package/dist/runtime/drift.d.ts +4 -2
  19. package/dist/runtime/pg/bun.d.ts +12 -7
  20. package/dist/runtime/pg/bun.js +5 -2
  21. package/dist/runtime/pg/pg.d.ts +12 -6
  22. package/dist/runtime/pg/pg.js +5 -2
  23. package/dist/runtime/pg/pglite.d.ts +11 -3
  24. package/dist/runtime/pg/pglite.js +5 -2
  25. package/dist/runtime/pg/postgresjs.d.ts +12 -6
  26. package/dist/runtime/pg/postgresjs.js +5 -2
  27. package/dist/runtime/plan.d.ts +3 -1
  28. package/dist/runtime/tenancy/index.js +1 -1
  29. package/dist/runtime/topology.d.ts +191 -0
  30. package/dist/runtime/traits/index.js +7 -6
  31. package/dist/runtime/types.d.ts +59 -2
  32. package/dist/runtime/validate/index.js +2 -2
  33. package/dist/shared/{x6wysh83.js → 3p4b1fg0.js} +3 -5
  34. package/dist/shared/{ty3v3hfk.js → 47xzb9fc.js} +2 -2
  35. package/dist/shared/{fs0ex00p.js → 4ef7mmqf.js} +2 -2
  36. package/dist/shared/{qybh48nd.js → 5fqx9yh4.js} +6 -2
  37. package/dist/shared/5mtf6cj3.js +94 -0
  38. package/dist/shared/{999ade2q.js → 5vdm4sgp.js} +1 -1
  39. package/dist/shared/{bqha5cg6.js → 6p5k20sm.js} +2 -2
  40. package/dist/shared/{t616ynfn.js → 8bwbwy8h.js} +71 -13
  41. package/dist/shared/{th14ygv2.js → 8ktwd3rh.js} +29 -1
  42. package/dist/shared/{wekze34d.js → 8xsbagad.js} +74 -2
  43. package/dist/shared/{bryqk9k9.js → 922qbgnw.js} +3 -3
  44. package/dist/shared/{meck53jv.js → 990a291j.js} +6 -5
  45. package/dist/shared/{jns2wx4t.js → 9tgmr1gh.js} +1 -1
  46. package/dist/shared/9yn6a2vd.js +11 -0
  47. package/dist/shared/{2ssqrv66.js → ayz64vzj.js} +1037 -181
  48. package/dist/shared/{k34e0xf2.js → bt9sfq0z.js} +16 -14
  49. package/dist/shared/{scwxrpx2.js → bzsjtd7c.js} +84 -15
  50. package/dist/shared/{m9tv7xn7.js → ckjbyrt9.js} +4 -4
  51. package/dist/shared/{ydaz1sv3.js → dxjp14rk.js} +10 -2
  52. package/dist/shared/{49ypnfwt.js → fancm5pt.js} +24 -229
  53. package/dist/shared/{6bazvqwm.js → fb00y8fp.js} +2 -2
  54. package/dist/shared/{1tfdfvhh.js → fyqkk1m0.js} +6 -4
  55. package/dist/shared/g516zdsb.js +905 -0
  56. package/dist/shared/{jkc6ws1p.js → g7ez901n.js} +83 -2
  57. package/dist/shared/{kgnc8grt.js → gdh9mrtp.js} +1 -1
  58. package/dist/shared/{p24x2334.js → h4nqvjkp.js} +5 -3
  59. package/dist/shared/{edwqqryd.js → h8ne16fq.js} +1 -1
  60. package/dist/shared/htnaj77v.js +17 -0
  61. package/dist/shared/jdgqqx31.js +72 -0
  62. package/dist/shared/{6w5j844a.js → jsd07prz.js} +2 -2
  63. package/dist/shared/{m78s7898.js → k7cc9nzk.js} +24 -7
  64. package/dist/shared/kdkxh3kb.js +758 -0
  65. package/dist/shared/nkc9sfqb.js +1161 -0
  66. package/dist/shared/{xva8v7qk.js → p7pfx624.js} +5 -5
  67. package/dist/shared/{xrfnq5k8.js → qgvt8nx1.js} +11 -2
  68. package/dist/shared/{ne02ng5c.js → qy9rdq0g.js} +9 -5
  69. package/dist/shared/{3ekj9x92.js → r6pj4fq2.js} +5 -3
  70. package/dist/shared/r73pt594.js +426 -0
  71. package/dist/shared/rasvvzgz.js +21 -0
  72. package/dist/shared/{01yr20s5.js → sf8m6tfy.js} +2 -2
  73. package/dist/shared/{hcmdtwgh.js → szm0cksa.js} +4 -1
  74. package/dist/shared/{axhk06q1.js → tq9afp8d.js} +1 -1
  75. package/dist/shared/{xw1d69vd.js → wqem4jed.js} +6 -6
  76. package/dist/shared/wxn4b7pp.js +252 -0
  77. package/dist/shared/xdpj5kkm.js +2203 -0
  78. package/dist/shared/xhh1srw3.js +589 -0
  79. package/dist/shared/{mt5wswjz.js → xsq7v6wp.js} +2 -2
  80. package/dist/shared/{7fvtrkwr.js → ycatkxmq.js} +1 -1
  81. package/dist/shared/ykv7wh5b.js +14 -0
  82. package/dist/shared/{fptpmvxp.js → z2rnngj4.js} +26 -21
  83. package/dist/tooling/cli.js +46 -17
  84. package/dist/tooling/migrate/apply.d.ts +61 -5
  85. package/dist/tooling/migrate/backfill.d.ts +136 -0
  86. package/dist/tooling/migrate/check.d.ts +65 -0
  87. package/dist/tooling/migrate/classify.d.ts +108 -0
  88. package/dist/tooling/migrate/commands.d.ts +2 -2
  89. package/dist/tooling/migrate/config.d.ts +22 -0
  90. package/dist/tooling/migrate/doctor.d.ts +3 -0
  91. package/dist/tooling/migrate/drift.d.ts +18 -2
  92. package/dist/tooling/migrate/estimate.d.ts +50 -0
  93. package/dist/tooling/migrate/extensions.d.ts +2 -0
  94. package/dist/tooling/migrate/files.d.ts +19 -0
  95. package/dist/tooling/migrate/index.d.ts +3 -2
  96. package/dist/tooling/migrate/index.js +69 -1
  97. package/dist/tooling/migrate/lint-rules.d.ts +51 -0
  98. package/dist/tooling/migrate/lint.d.ts +71 -0
  99. package/dist/tooling/migrate/managed.d.ts +1 -1
  100. package/dist/tooling/migrate/plan.d.ts +64 -9
  101. package/dist/tooling/migrate/project.d.ts +42 -7
  102. package/dist/tooling/migrate/provision.d.ts +18 -0
  103. package/dist/tooling/migrate/reference.d.ts +40 -0
  104. package/dist/tooling/migrate/runner.d.ts +108 -0
  105. package/dist/tooling/migrate/safe.d.ts +77 -0
  106. package/dist/tooling/migrate/seed.d.ts +20 -0
  107. package/dist/tooling/migrate/snapshot.d.ts +78 -0
  108. package/dist/tooling/migrate/status.d.ts +25 -6
  109. package/dist/tooling/testing/factories.d.ts +117 -0
  110. package/dist/tooling/testing/facts.d.ts +75 -0
  111. package/dist/tooling/testing/index.d.ts +89 -0
  112. package/dist/tooling/testing/index.js +8 -0
  113. package/dist/tooling/testing/isolation.d.ts +30 -0
  114. package/dist/tooling/testing/random.d.ts +40 -0
  115. package/dist/tooling/testing/record.d.ts +38 -0
  116. package/package.json +6 -2
  117. package/dist/shared/5nmdenb6.js +0 -1229
  118. package/dist/shared/dbjej1tw.js +0 -456
  119. package/dist/shared/g4f5kcx9.js +0 -37
  120. package/dist/shared/qbxzvwwc.js +0 -88
  121. package/dist/shared/zwmqxb0g.js +0 -79
  122. /package/dist/shared/{75xkjv0t.js → fa9vt245.js} +0 -0
  123. /package/dist/shared/{0jc3ffzf.js → zvbjc949.js} +0 -0
@@ -34,10 +34,15 @@ export type ViewOptions = {
34
34
  *
35
35
  * `contribute` builds the catalog record. Column dependencies are filled
36
36
  * from `pg_depend` by scratch verification, not at this call.
37
+ *
38
+ * @typeParam N - View name
39
+ * @typeParam C - Declared output columns
37
40
  */
38
- export type ViewDeclaration = {
39
- readonly name: string;
41
+ export type ViewDeclaration<N extends string = string, C extends readonly ViewColumnInput[] = readonly ViewColumnInput[]> = {
42
+ readonly name: N;
40
43
  readonly schema: string;
44
+ /** Declared columns, for types only. `db.views` reads them. */
45
+ readonly "~columns"?: C;
41
46
  /**
42
47
  * Catalog record for this declaration.
43
48
  *
@@ -73,10 +78,15 @@ export type MaterializedViewOptions = ViewOptions & {
73
78
  * One declared materialized view.
74
79
  *
75
80
  * Identity is `(namespace, name)`. Indexes are separate catalog objects.
81
+ *
82
+ * @typeParam N - Materialized view name
83
+ * @typeParam C - Declared output columns
76
84
  */
77
- export type MaterializedViewDeclaration = {
78
- readonly name: string;
85
+ export type MaterializedViewDeclaration<N extends string = string, C extends readonly ViewColumnInput[] = readonly ViewColumnInput[]> = {
86
+ readonly name: N;
79
87
  readonly schema: string;
88
+ /** Declared columns, for types only. `db.views` reads them. */
89
+ readonly "~columns"?: C;
80
90
  /**
81
91
  * Catalog records for this declaration.
82
92
  *
@@ -100,7 +110,9 @@ export type MaterializedViewDeclaration = {
100
110
  * @param options - Columns and query
101
111
  * @returns The definition object `schema({ views })` stores
102
112
  */
103
- export declare function view(name: string, options: ViewOptions): ViewDeclaration;
113
+ export declare function view<const N extends string, const C extends readonly ViewColumnInput[]>(name: N, options: ViewOptions & {
114
+ readonly columns: C;
115
+ }): ViewDeclaration<N, C>;
104
116
  /**
105
117
  * Declares a materialized view.
106
118
  *
@@ -111,4 +123,6 @@ export declare function view(name: string, options: ViewOptions): ViewDeclaratio
111
123
  * @param options - Columns, query, indexes, and refresh mode
112
124
  * @returns The definition object `schema({ views })` stores
113
125
  */
114
- export declare function materializedView(name: string, options: MaterializedViewOptions): MaterializedViewDeclaration;
126
+ export declare function materializedView<const N extends string, const C extends readonly ViewColumnInput[]>(name: N, options: MaterializedViewOptions & {
127
+ readonly columns: C;
128
+ }): MaterializedViewDeclaration<N, C>;
@@ -9,7 +9,7 @@ import {
9
9
  } from "../../../shared/dyj86cmj.js";
10
10
  import {
11
11
  snakeCase
12
- } from "../../../shared/6w5j844a.js";
12
+ } from "../../../shared/jsd07prz.js";
13
13
  import {
14
14
  encodeText,
15
15
  decodeText
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Verifies declared views on a scratch schema.
3
3
  *
4
- * Tables from the catalog are created there, then each view. `pg_get_viewdef`
4
+ * Domain and enum types from the catalog are created there, then the tables,
5
+ * then the functions a view may call, then each view. `pg_get_viewdef`
5
6
  * replaces the author query and `pg_depend` supplies column edges (D117).
6
7
  * The scratch schema is dropped before this returns. Plans never use it.
7
8
  */
@@ -0,0 +1,17 @@
1
+ /**
2
+ * One in-flight catalog load per key.
3
+ *
4
+ * Concurrent `connect()` calls in one process share the parse. A failure is
5
+ * dropped so the next call can try again. Success stays for the process.
6
+ */
7
+ /**
8
+ * Runs `load` once for `key`.
9
+ *
10
+ * The map is filled before `load` starts, so a second call on the same tick
11
+ * waits on the first promise instead of parsing again.
12
+ *
13
+ * @param key - Directory and hash, or the artifact hash
14
+ * @param load - Reads and trusts the catalog
15
+ * @returns The shared load
16
+ */
17
+ export declare function loadOnce(key: string, load: () => Promise<void>): Promise<void>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Words shared by `connect()` and `okm migrate status` (D197).
3
+ *
4
+ * The startup check and the status command name the same situations.
5
+ * A failed step adds the resume hint status prints.
6
+ */
7
+ /** The database and the app record the same catalog. */
8
+ export declare const CURRENT = "current";
9
+ /** Pending migrations are expand only. */
10
+ export declare const BEHIND_EXPAND = "behind by expand";
11
+ /** A pending migration is not expand. */
12
+ export declare const BEHIND_CONTRACT = "behind by contract";
13
+ /** The database has expand migrations the app does not. */
14
+ export declare const AHEAD_EXPAND = "ahead by expand";
15
+ /** The database has a contract migration the app does not. */
16
+ export declare const AHEAD_CONTRACT = "ahead by contract";
17
+ /**
18
+ * Status text for a migration that stopped mid-way.
19
+ *
20
+ * @param step - Zero-based step that has no `okm_history` row
21
+ * @returns The state, including the resume hint
22
+ */
23
+ export declare function failedAt(step: number): string;
@@ -2,25 +2,41 @@
2
2
  * Compatibility check used only when the catalog hashes differ.
3
3
  *
4
4
  * Ahead by expand is compatible. Ahead by a contract step, or behind, fails
5
- * closed (OKM1520).
5
+ * closed (OKM1520). The class is the value stored in `okm_history`.
6
6
  */
7
7
  import type { DriverPool, ExecuteOptions } from "../contracts/driver.js";
8
8
  /** One applied migration, in history order. */
9
9
  export type DriftMigration = {
10
10
  readonly id: string;
11
11
  readonly catalogHash: string;
12
- /** True when every step is expand. */
12
+ /** True when every stored step class is `expand`. */
13
13
  readonly expand: boolean;
14
14
  };
15
+ /** How far the database and the app have diverged. */
16
+ export type DriftVerdict = {
17
+ readonly state: "ok";
18
+ } | {
19
+ readonly state: "ahead by contract";
20
+ readonly migrationId: string;
21
+ } | {
22
+ readonly state: "behind by expand";
23
+ readonly migrationId: string;
24
+ } | {
25
+ readonly state: "behind by contract";
26
+ readonly migrationId: string;
27
+ } | {
28
+ readonly state: "behind";
29
+ readonly migrationId: string;
30
+ };
15
31
  /**
16
- * Classifies a hash mismatch.
32
+ * Classifies a hash mismatch from stored history classes.
17
33
  *
18
34
  * @param codeHash - Hash of the code's catalog
19
35
  * @param recordedHash - Hash stored in `okm_meta`
20
36
  * @param migrations - Applied migrations, oldest first
21
37
  * @returns `ok` when the database is ahead by expand only
22
38
  */
23
- export declare function driftVerdict(codeHash: string, recordedHash: string, migrations: readonly DriftMigration[]): "ok" | "behind" | "contract";
39
+ export declare function driftVerdict(codeHash: string, recordedHash: string, migrations: readonly DriftMigration[]): DriftVerdict;
24
40
  /**
25
41
  * Reads history and throws OKM1520 when the database is not expand-ahead.
26
42
  *
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Startup catalog check.
3
3
  *
4
- * The fast path compares two hashes. History, catalog JSON, and
5
- * {@link loadTrustedCatalog} load only when those hashes differ.
4
+ * The fast path compares two hashes and returns. History and
5
+ * `.okm/catalog.json` load only when those hashes differ. A hash read from
6
+ * `catalog.hash` requires that JSON file; a missing file or a hash that does
7
+ * not match is OKM1027. Concurrent loads share one parse.
6
8
  */
7
9
  import type { DriverPool, ExecuteOptions } from "../contracts/driver.js";
8
10
  import type { QuerySchema } from "../dialects/pg/model.js";
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * `connect` for Bun.sql (`okmodel/pg/bun`).
3
3
  *
4
- * One URL or an existing pool is one endpoint. The client is typed by the
5
- * schema passed in. This entry runs on Bun. Node cannot load the `bun` import
6
- * inside the adapter.
4
+ * A string or an existing pool is one endpoint. A `{ primary, replicas }` object
5
+ * loads the topology module. The client is typed by the schema passed in.
6
+ * This entry runs on Bun. Node cannot load the `bun` import inside the adapter.
7
7
  */
8
8
  import type { DriverPool } from "../../contracts/driver.js";
9
9
  import type { QuerySchema } from "../../dialects/pg/model.js";
10
10
  import { type BunSqlConfig } from "../../adapters/pg/bunsql.js";
11
+ import type { RoutedClient, TopologyInput, TopologyOptions } from "../topology.js";
11
12
  import type { ConnectOptions, Connected } from "../types.js";
12
13
  export { open, type BunSqlConfig, type BunSqlTls } from "../../adapters/pg/bunsql.js";
13
14
  export { capabilities, hasCapability, readCapability } from "../../adapters/pg/bunsql.js";
@@ -15,16 +16,20 @@ export { DriverError } from "../../adapters/error.js";
15
16
  /** A connection string or a pool from {@link open}. */
16
17
  export type BunSqlTarget = string | DriverPool;
17
18
  /** Driver options `connect` forwards to {@link open}. */
18
- export type BunSqlConnectOptions<S extends QuerySchema> = ConnectOptions<S> & Omit<BunSqlConfig, "url">;
19
+ export type BunSqlConnectOptions<S extends QuerySchema> = ConnectOptions<S> & Omit<BunSqlConfig, "url"> & TopologyOptions;
19
20
  /**
20
- * Connects one Postgres endpoint through Bun.sql and returns a client for `schema`.
21
+ * Connects one Postgres endpoint through Bun.sql, or a primary and its replicas.
22
+ *
23
+ * A string or a pool is one endpoint. A topology loads on demand and the promise
24
+ * resolves to the client. Reads use the first healthy replica.
21
25
  *
22
26
  * Named prepared statements are off unless `prepared` is `"named"`.
23
27
  * `prepared: "named"` is not for transaction-mode poolers.
24
28
  *
25
29
  * @typeParam S - Schema
26
- * @param target - URL or an existing pool
30
+ * @param target - URL, an existing pool, or `{ primary, replicas }`
27
31
  * @param options - Schema, errors, logger, and driver options
28
- * @returns The client. Await `connected` for the dialect check
32
+ * @returns The client. A topology returns a promise. Await `connected` for the dialect check
29
33
  */
34
+ export declare function connect<const S extends QuerySchema>(target: TopologyInput, options: BunSqlConnectOptions<S>): Promise<RoutedClient<S>>;
30
35
  export declare function connect<const S extends QuerySchema>(target: BunSqlTarget, options: BunSqlConnectOptions<S>): Connected<S>;
@@ -3,7 +3,7 @@ import {
3
3
  } from "../../shared/kyxb62mc.js";
4
4
  import {
5
5
  open5
6
- } from "../../shared/01yr20s5.js";
6
+ } from "../../shared/sf8m6tfy.js";
7
7
  import {
8
8
  BUNSQL_CAPABILITIES,
9
9
  readCapability2,
@@ -11,10 +11,13 @@ import {
11
11
  } from "../../shared/rrstp3kb.js";
12
12
  import {
13
13
  createClient
14
- } from "../../shared/k34e0xf2.js";
14
+ } from "../../shared/bt9sfq0z.js";
15
15
 
16
16
  // src/runtime/pg/bun.ts
17
17
  function connect(target, options) {
18
+ if (typeof target === "object" && !isPool(target)) {
19
+ return import("../../shared/nkc9sfqb.js").then((mod) => mod.default(target, options, open5));
20
+ }
18
21
  const owned = !isPool(target);
19
22
  const pool = isPool(target) ? target : open5({
20
23
  url: target,
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * `connect` for node-postgres (`okmodel/pg/pg`).
3
3
  *
4
- * One URL or an existing pool is one endpoint. The client is typed by the
5
- * schema passed in, not by `Register`.
4
+ * A string or an existing pool is one endpoint. A `{ primary, replicas }` object
5
+ * loads the topology module. The client is typed by the schema passed in, not
6
+ * by `Register`.
6
7
  */
7
8
  import type { DriverPool } from "../../contracts/driver.js";
8
9
  import type { QuerySchema } from "../../dialects/pg/model.js";
9
10
  import { type NodePostgresConfig } from "../../adapters/pg/nodepostgres.js";
11
+ import type { RoutedClient, TopologyInput, TopologyOptions } from "../topology.js";
10
12
  import type { ConnectOptions, Connected } from "../types.js";
11
13
  export { open, type NodePostgresConfig } from "../../adapters/pg/nodepostgres.js";
12
14
  export { capabilities, hasCapability, readCapability } from "../../adapters/pg/nodepostgres.js";
@@ -14,16 +16,20 @@ export { DriverError } from "../../adapters/error.js";
14
16
  /** A connection string or a pool from {@link open}. */
15
17
  export type NodePostgresTarget = string | DriverPool;
16
18
  /** Driver options `connect` forwards to {@link open}. */
17
- export type NodePostgresConnectOptions<S extends QuerySchema> = ConnectOptions<S> & Omit<NodePostgresConfig, "url">;
19
+ export type NodePostgresConnectOptions<S extends QuerySchema> = ConnectOptions<S> & Omit<NodePostgresConfig, "url"> & TopologyOptions;
18
20
  /**
19
- * Connects one Postgres endpoint through node-postgres and returns a client for `schema`.
21
+ * Connects one Postgres endpoint through node-postgres, or a primary and its replicas.
22
+ *
23
+ * A string or a pool is one endpoint. A topology loads on demand and the promise
24
+ * resolves to the client. Reads use the first healthy replica.
20
25
  *
21
26
  * Named prepared statements are off unless `prepared` is `"named"`.
22
27
  * `prepared: "named"` is not for transaction-mode poolers.
23
28
  *
24
29
  * @typeParam S - Schema
25
- * @param target - URL or an existing pool
30
+ * @param target - URL, an existing pool, or `{ primary, replicas }`
26
31
  * @param options - Schema, errors, logger, and driver options
27
- * @returns The client. Await `connected` for the dialect check
32
+ * @returns The client. A topology returns a promise. Await `connected` for the dialect check
28
33
  */
34
+ export declare function connect<const S extends QuerySchema>(target: TopologyInput, options: NodePostgresConnectOptions<S>): Promise<RoutedClient<S>>;
29
35
  export declare function connect<const S extends QuerySchema>(target: NodePostgresTarget, options: NodePostgresConnectOptions<S>): Connected<S>;
@@ -3,7 +3,7 @@ import {
3
3
  } from "../../shared/kyxb62mc.js";
4
4
  import {
5
5
  open4
6
- } from "../../shared/bqha5cg6.js";
6
+ } from "../../shared/6p5k20sm.js";
7
7
  import {
8
8
  NODE_POSTGRES_CAPABILITIES,
9
9
  readCapability2,
@@ -11,10 +11,13 @@ import {
11
11
  } from "../../shared/rrstp3kb.js";
12
12
  import {
13
13
  createClient
14
- } from "../../shared/k34e0xf2.js";
14
+ } from "../../shared/bt9sfq0z.js";
15
15
 
16
16
  // src/runtime/pg/pg.ts
17
17
  function connect(target, options) {
18
+ if (typeof target === "object" && !isPool(target)) {
19
+ return import("../../shared/nkc9sfqb.js").then((mod) => mod.default(target, options, open4));
20
+ }
18
21
  const owned = !isPool(target);
19
22
  const pool = isPool(target) ? target : open4({
20
23
  url: target,
@@ -2,10 +2,13 @@
2
2
  * `connect` for PGlite (`okmodel/pg/pglite`).
3
3
  *
4
4
  * Opening a database is async. An existing pool is reused and not closed.
5
+ * A `{ primary, replicas }` object loads the topology module. A replica URL is
6
+ * that endpoint's data directory.
5
7
  */
6
8
  import type { DriverPool } from "../../contracts/driver.js";
7
9
  import type { QuerySchema } from "../../dialects/pg/model.js";
8
10
  import { type PgliteConfig } from "../../adapters/pg/pglite.js";
11
+ import type { RoutedClient, TopologyInput, TopologyOptions } from "../topology.js";
9
12
  import type { ConnectOptions, Connected } from "../types.js";
10
13
  export { open, type PgliteConfig } from "../../adapters/pg/pglite.js";
11
14
  export { capabilities, hasCapability, readCapability } from "../../adapters/pg/pglite.js";
@@ -13,13 +16,18 @@ export { DriverError } from "../../adapters/error.js";
13
16
  /** A data directory, a pool from {@link open}, or omitted for memory. */
14
17
  export type PgliteTarget = string | DriverPool | undefined;
15
18
  /** Driver options `connect` forwards to {@link open}. */
16
- export type PgliteConnectOptions<S extends QuerySchema> = ConnectOptions<S> & Omit<PgliteConfig, "dataDir">;
19
+ export type PgliteConnectOptions<S extends QuerySchema> = ConnectOptions<S> & Omit<PgliteConfig, "dataDir"> & TopologyOptions;
17
20
  /**
18
- * Connects one PGlite endpoint and returns a client for `schema`.
21
+ * Connects one PGlite endpoint, or a primary and its replicas, and returns a client for `schema`.
22
+ *
23
+ * A string, a pool, or an omitted target is one endpoint. A topology loads on
24
+ * demand. Reads use the first healthy replica. A replica `url` is that
25
+ * endpoint's data directory.
19
26
  *
20
27
  * @typeParam S - Schema
21
- * @param target - Data directory, existing pool, or omitted for memory
28
+ * @param target - Data directory, existing pool, omitted for memory, or `{ primary, replicas }`
22
29
  * @param options - Schema, errors, and logger
23
30
  * @returns The client
24
31
  */
32
+ export declare function connect<const S extends QuerySchema>(target: TopologyInput, options: PgliteConnectOptions<S>): Promise<RoutedClient<S>>;
25
33
  export declare function connect<const S extends QuerySchema>(target: PgliteTarget, options: PgliteConnectOptions<S>): Promise<Connected<S>>;
@@ -3,7 +3,7 @@ import {
3
3
  } from "../../shared/kyxb62mc.js";
4
4
  import {
5
5
  open3
6
- } from "../../shared/75xkjv0t.js";
6
+ } from "../../shared/fa9vt245.js";
7
7
  import {
8
8
  PGLITE_CAPABILITIES,
9
9
  readCapability2,
@@ -11,10 +11,13 @@ import {
11
11
  } from "../../shared/rrstp3kb.js";
12
12
  import {
13
13
  createClient
14
- } from "../../shared/k34e0xf2.js";
14
+ } from "../../shared/bt9sfq0z.js";
15
15
 
16
16
  // src/runtime/pg/pglite.ts
17
17
  async function connect(target, options) {
18
+ if (typeof target === "object" && !isPool(target)) {
19
+ return import("../../shared/nkc9sfqb.js").then((mod) => mod.default(target, options, open3));
20
+ }
18
21
  const pool = isPool(target) ? target : await open3({
19
22
  ...target !== undefined ? { dataDir: target } : {},
20
23
  ...options.timeouts !== undefined ? { timeouts: options.timeouts } : {}
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * `connect` for postgres.js (`okmodel/pg/postgresjs`).
3
3
  *
4
- * One URL or an existing pool is one endpoint. The client is typed by the
5
- * schema passed in, not by `Register`.
4
+ * A string or an existing pool is one endpoint. A `{ primary, replicas }` object
5
+ * loads the topology module. The client is typed by the schema passed in, not
6
+ * by `Register`.
6
7
  */
7
8
  import type { DriverPool } from "../../contracts/driver.js";
8
9
  import type { QuerySchema } from "../../dialects/pg/model.js";
9
10
  import { type PostgresJsConfig } from "../../adapters/pg/postgresjs.js";
11
+ import type { RoutedClient, TopologyInput, TopologyOptions } from "../topology.js";
10
12
  import type { ConnectOptions, Connected } from "../types.js";
11
13
  export { open, type PostgresJsConfig } from "../../adapters/pg/postgresjs.js";
12
14
  export { capabilities, hasCapability, readCapability } from "../../adapters/pg/postgresjs.js";
@@ -14,16 +16,20 @@ export { DriverError } from "../../adapters/error.js";
14
16
  /** A connection string or a pool from {@link open}. */
15
17
  export type PostgresTarget = string | DriverPool;
16
18
  /** Driver options `connect` forwards to {@link open}. */
17
- export type PostgresConnectOptions<S extends QuerySchema> = ConnectOptions<S> & Omit<PostgresJsConfig, "url">;
19
+ export type PostgresConnectOptions<S extends QuerySchema> = ConnectOptions<S> & Omit<PostgresJsConfig, "url"> & TopologyOptions;
18
20
  /**
19
- * Connects one Postgres endpoint and returns a client for `schema`.
21
+ * Connects one Postgres endpoint, or a primary and its replicas, and returns a client for `schema`.
22
+ *
23
+ * A string or a pool is one endpoint. A topology loads on demand and the promise
24
+ * resolves to the client. Reads use the first healthy replica.
20
25
  *
21
26
  * Named prepared statements are off unless `prepared` is `"named"`.
22
27
  * `prepared: "named"` is not for transaction-mode poolers.
23
28
  *
24
29
  * @typeParam S - Schema
25
- * @param target - URL or an existing pool
30
+ * @param target - URL, an existing pool, or `{ primary, replicas }`
26
31
  * @param options - Schema, errors, logger, and driver options
27
- * @returns The client. Await `connected` for the dialect check
32
+ * @returns The client. A topology returns a promise. Await `connected` for the dialect check
28
33
  */
34
+ export declare function connect<const S extends QuerySchema>(target: TopologyInput, options: PostgresConnectOptions<S>): Promise<RoutedClient<S>>;
29
35
  export declare function connect<const S extends QuerySchema>(target: PostgresTarget, options: PostgresConnectOptions<S>): Connected<S>;
@@ -3,7 +3,7 @@ import {
3
3
  } from "../../shared/kyxb62mc.js";
4
4
  import {
5
5
  open2
6
- } from "../../shared/xw1d69vd.js";
6
+ } from "../../shared/wqem4jed.js";
7
7
  import {
8
8
  POSTGRESJS_CAPABILITIES,
9
9
  readCapability2,
@@ -11,10 +11,13 @@ import {
11
11
  } from "../../shared/rrstp3kb.js";
12
12
  import {
13
13
  createClient
14
- } from "../../shared/k34e0xf2.js";
14
+ } from "../../shared/bt9sfq0z.js";
15
15
 
16
16
  // src/runtime/pg/postgresjs.ts
17
17
  function connect(target, options) {
18
+ if (typeof target === "object" && !isPool(target)) {
19
+ return import("../../shared/nkc9sfqb.js").then((mod) => mod.default(target, options, open2));
20
+ }
18
21
  const owned = !isPool(target);
19
22
  const pool = isPool(target) ? target : open2({
20
23
  url: target,
@@ -41,9 +41,11 @@ export type ReadCall = {
41
41
  readonly build?: ReadBuild;
42
42
  /** Writes the end of the statement, after `limit`. A row lock sets it. */
43
43
  readonly tail?: (sink: Sink) => void;
44
- /** The caller's `signal` and `timeout`. They never enter the plan or its key. */
44
+ /** The caller's `signal`, `timeout`, and `route`. They never enter the plan or its key. */
45
45
  readonly signal?: AbortSignal;
46
46
  readonly timeout?: number;
47
+ /** `"primary"` or `"replica"`. Absent means the router classifies the statement. */
48
+ readonly route?: "primary" | "replica";
47
49
  /** Set by `for()` or `unscoped()`. Absent on a schema with no tenancy. */
48
50
  readonly scope?: CallScope;
49
51
  /** Inspect lines from the tenancy object. Absent when the schema has no tenancy. */
@@ -11,7 +11,7 @@ import {
11
11
  } from "../../shared/tmbdywx0.js";
12
12
  import {
13
13
  quote
14
- } from "../../shared/xrfnq5k8.js";
14
+ } from "../../shared/qgvt8nx1.js";
15
15
  import {
16
16
  assertIdentifier2
17
17
  } from "../../shared/nfzc5z0w.js";
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Primary and replica endpoints (spec §15.1).
3
+ *
4
+ * Loaded only when `connect` is given a topology object. The string and pool
5
+ * paths do not import this module. An automatic read chooses among eligible
6
+ * replicas. One watermark on this handle covers the root, `for()`, `unscoped()`,
7
+ * `using()`, and `reserve()`.
8
+ */
9
+ import type { DriverPool, DriverTimeouts } from "../contracts/driver.js";
10
+ import type { QuerySchema } from "../dialects/pg/model.js";
11
+ import type { ConnectOptions, Connected } from "./types.js";
12
+ /** `"primary"` or `"replica"`. `using` and `route` accept only these. */
13
+ export type RouteName = "primary" | "replica";
14
+ /**
15
+ * One routing choice.
16
+ *
17
+ * `onRoute` receives this. `inspect()` does not, until the dev inspector (M2).
18
+ */
19
+ export type RouteEvent = {
20
+ /** What the statement was classified as. */
21
+ readonly op: "read" | "write" | "tx" | "lock";
22
+ /** Endpoint name. The primary is `"primary"`. */
23
+ readonly endpoint: string;
24
+ /**
25
+ * `primary-required`, `constraint:primary`, `constraint:replica`,
26
+ * `auto:<name>`, or `fallback:<no-replicas | unhealthy | position-unknown | saturated | behind>`.
27
+ */
28
+ readonly reason: string;
29
+ };
30
+ /**
31
+ * One replica passed to a custom `routing.select`.
32
+ *
33
+ * `lag` is the replica's byte lag, or `null` when the primary insert position
34
+ * or the replica replay position has not been seen. `latencyMs` stays `null`
35
+ * until a probe or a successful read has been timed.
36
+ */
37
+ export type ReplicaCandidate = {
38
+ /** Endpoint name. */
39
+ readonly name: string;
40
+ /** Configured weight. Zero and below are OKM1120 at connect. */
41
+ readonly weight: number;
42
+ /** In-flight statements this router is running on the replica. */
43
+ readonly inflight: number;
44
+ /** EWMA of observed round-trip time, in milliseconds, or `null`. */
45
+ readonly latencyMs: number | null;
46
+ /** Byte lag behind the primary, or `null` when either position is unknown. */
47
+ readonly lag: number | null;
48
+ };
49
+ /**
50
+ * A topology client.
51
+ *
52
+ * `using` returns a client that forces one route. That client has no `close`
53
+ * and no `using`. Only the root closes the pools.
54
+ */
55
+ export type RoutedClient<S extends QuerySchema> = Connected<S> & {
56
+ /**
57
+ * Forces later operations onto the primary or a replica.
58
+ *
59
+ * @param route - `"primary"` or `"replica"`
60
+ * @returns A client with no `close` and no `using`
61
+ */
62
+ using(route: RouteName): Omit<Connected<S>, "close">;
63
+ };
64
+ /**
65
+ * Replay position for one replica.
66
+ *
67
+ * Tests supply this over independent databases. The probe still runs `SELECT 1`
68
+ * on the replica's pool. The position does not come from a mocked driver.
69
+ */
70
+ export type ReplicaState = {
71
+ /**
72
+ * @param endpoint - The replica being probed
73
+ * @returns The replay LSN, or `null` when the replica has not replayed
74
+ */
75
+ replayLsn(endpoint: {
76
+ readonly name: string;
77
+ }): string | null | Promise<string | null>;
78
+ /**
79
+ * Replay timestamp for a time `maxLag`.
80
+ *
81
+ * Omitted, and with no seam, the probe reads `pg_last_xact_replay_timestamp()`
82
+ * only when `maxLag` is a duration.
83
+ *
84
+ * @param endpoint - The replica being probed
85
+ * @returns A timestamp, or `null` when the replica has not replayed a transaction
86
+ */
87
+ replayTime?(endpoint: {
88
+ readonly name: string;
89
+ }): Date | string | null | Promise<Date | string | null>;
90
+ };
91
+ /** One replica in `{ primary, replicas }`. A string is a URL with weight 1. */
92
+ export type ReplicaInput = {
93
+ readonly url: string;
94
+ readonly weight?: number;
95
+ readonly name?: string;
96
+ readonly pool?: {
97
+ readonly max?: number;
98
+ };
99
+ };
100
+ /** The first argument of `connect` when it is a topology. */
101
+ export type TopologyInput = {
102
+ readonly primary: string;
103
+ readonly replicas?: readonly (string | ReplicaInput)[];
104
+ };
105
+ /** Options only the topology module reads. */
106
+ export type TopologyOptions = {
107
+ /**
108
+ * Background probe interval. `"1s"` when omitted.
109
+ *
110
+ * `select` chooses among eligible replicas. The default is `"weighted"`.
111
+ * `consistency` is `"session"` (the default) or `"eventual"`. `maxLag` is a
112
+ * duration (`"5s"`, `"500ms"`, `"2m"`) or a size (`"16MB"`, `"512KB"`,
113
+ * `"1GB"`, `"4096B"`). `fallback` is `"primary"` (the default) or `"error"`.
114
+ */
115
+ readonly routing?: {
116
+ readonly probe?: string | number;
117
+ readonly select?: "weighted" | "roundRobin" | "leastConnections" | "latencyAware" | ((candidates: readonly ReplicaCandidate[], ctx: {
118
+ readonly op: "read";
119
+ }) => ReplicaCandidate | string);
120
+ readonly consistency?: "session" | "eventual";
121
+ readonly fallback?: "primary" | "error";
122
+ readonly maxLag?: string;
123
+ };
124
+ /**
125
+ * Called after an endpoint is chosen.
126
+ *
127
+ * A throw is swallowed. The operation still runs.
128
+ */
129
+ readonly onRoute?: (event: RouteEvent) => void;
130
+ /** Test seam for replay positions. Omitted, the probe reads `pg_last_wal_replay_lsn()`. */
131
+ readonly replicaState?: ReplicaState;
132
+ };
133
+ /** One endpoint after connect. Tests read this. It is not the query path. */
134
+ export type EndpointView = {
135
+ readonly name: string;
136
+ readonly role: "primary" | "replica";
137
+ readonly weight: number;
138
+ /** `max` passed to the driver. Absent when connect did not set one. */
139
+ readonly max: number | undefined;
140
+ /** Whether `replication.position` was detected on this endpoint. */
141
+ readonly position: boolean;
142
+ readonly circuit: "closed" | "open";
143
+ readonly failures: number;
144
+ readonly replayLsn: string | null;
145
+ /** Delay until the next probe. The primary is 0. */
146
+ readonly nextDelayMs: number;
147
+ };
148
+ /** The topology a client was built with. */
149
+ export type TopologyView = {
150
+ readonly endpoints: readonly EndpointView[];
151
+ };
152
+ /** Fields each driver's `open` may read. Extra keys are ignored by that driver. */
153
+ type EndpointConfig = {
154
+ readonly url: string;
155
+ readonly dataDir: string;
156
+ readonly max?: number;
157
+ readonly timeouts?: DriverTimeouts;
158
+ readonly prepared?: "named" | "unnamed";
159
+ readonly searchPath?: string;
160
+ readonly ssl?: unknown;
161
+ };
162
+ /** Opens one endpoint. postgres.js reads `url`. PGlite reads `dataDir`. */
163
+ export type EndpointOpen = (config: EndpointConfig) => DriverPool | Promise<DriverPool>;
164
+ /**
165
+ * Opens one pool per endpoint, detects `replication.position`, and starts replica probes.
166
+ *
167
+ * An automatic read runs the selection pipeline and `routing.select`.
168
+ * `close()` stops the probes and closes every pool this call opened,
169
+ * including clients from `using`.
170
+ *
171
+ * @param target - `{ primary, replicas }`
172
+ * @param options - Schema, pool default, probe interval, and the optional seam
173
+ * @param open - The driver's `open`
174
+ * @returns The client
175
+ */
176
+ export declare function connectTopology<S extends QuerySchema>(target: object, options: ConnectOptions<S> & TopologyOptions & {
177
+ readonly max?: number;
178
+ readonly prepared?: "named" | "unnamed";
179
+ readonly searchPath?: string;
180
+ readonly ssl?: unknown;
181
+ }, open: EndpointOpen): Promise<RoutedClient<S>>;
182
+ /**
183
+ * Reads the endpoints recorded for a client this module built.
184
+ *
185
+ * A string `connect` has no record. Routing does not call this.
186
+ *
187
+ * @param client - The value `connect` returned
188
+ * @returns The endpoints, or `undefined` when `client` is not a topology client
189
+ */
190
+ export declare function readTopology(client: object): TopologyView | undefined;
191
+ export default connectTopology;