okmodel 0.4.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 (64) hide show
  1. package/README.md +86 -6
  2. package/dist/contracts/catalog/document.d.ts +20 -0
  3. package/dist/contracts/driver.d.ts +7 -0
  4. package/dist/dialects/pg/introspect.d.ts +6 -0
  5. package/dist/dialects/pg/role/sql.d.ts +10 -0
  6. package/dist/dialects/pg/schema.d.ts +15 -6
  7. package/dist/dialects/pg/view/index.d.ts +20 -6
  8. package/dist/dialects/pg/view/scratch.d.ts +2 -2
  9. package/dist/runtime/pg/bun.d.ts +12 -7
  10. package/dist/runtime/pg/bun.js +4 -1
  11. package/dist/runtime/pg/pg.d.ts +12 -6
  12. package/dist/runtime/pg/pg.js +4 -1
  13. package/dist/runtime/pg/pglite.d.ts +11 -3
  14. package/dist/runtime/pg/pglite.js +4 -1
  15. package/dist/runtime/pg/postgresjs.d.ts +12 -6
  16. package/dist/runtime/pg/postgresjs.js +4 -1
  17. package/dist/runtime/plan.d.ts +3 -1
  18. package/dist/runtime/tenancy/index.js +1 -1
  19. package/dist/runtime/topology.d.ts +191 -0
  20. package/dist/runtime/traits/index.js +6 -6
  21. package/dist/runtime/types.d.ts +57 -2
  22. package/dist/runtime/validate/index.js +1 -1
  23. package/dist/shared/{nxb42q7h.js → 47xzb9fc.js} +2 -2
  24. package/dist/shared/{6693j9rs.js → 5fqx9yh4.js} +6 -2
  25. package/dist/shared/{8jb4ymh2.js → 8bwbwy8h.js} +12 -1
  26. package/dist/shared/{41kv2xdm.js → 8xsbagad.js} +1 -1
  27. package/dist/shared/{sr5ms7b3.js → 922qbgnw.js} +3 -3
  28. package/dist/shared/{tny2t3sm.js → 990a291j.js} +4 -4
  29. package/dist/shared/{jns2wx4t.js → 9tgmr1gh.js} +1 -1
  30. package/dist/shared/{n5y9zyj1.js → ayz64vzj.js} +13 -12
  31. package/dist/shared/{kkzwp3d2.js → bt9sfq0z.js} +16 -14
  32. package/dist/shared/{c5g81ngb.js → bzsjtd7c.js} +7 -5
  33. package/dist/shared/{radd3z4g.js → ckjbyrt9.js} +1 -1
  34. package/dist/shared/{zg24x0hm.js → dxjp14rk.js} +10 -2
  35. package/dist/shared/{xghkt7a9.js → fancm5pt.js} +6 -233
  36. package/dist/shared/{yjw02enp.js → fyqkk1m0.js} +4 -4
  37. package/dist/shared/{ygem90rn.js → g516zdsb.js} +8 -3
  38. package/dist/shared/{v7tpnhnz.js → h4nqvjkp.js} +5 -3
  39. package/dist/shared/{edwqqryd.js → h8ne16fq.js} +1 -1
  40. package/dist/shared/{ady1ms74.js → jdgqqx31.js} +5 -5
  41. package/dist/shared/{kafaa5zv.js → k7cc9nzk.js} +24 -7
  42. package/dist/shared/{7nk68fj8.js → kdkxh3kb.js} +17 -10
  43. package/dist/shared/nkc9sfqb.js +1161 -0
  44. package/dist/shared/{awrsqrn7.js → p7pfx624.js} +2 -2
  45. package/dist/shared/{xrfnq5k8.js → qgvt8nx1.js} +11 -2
  46. package/dist/shared/{84ptn35q.js → qy9rdq0g.js} +6 -5
  47. package/dist/shared/{qn0srwwq.js → r6pj4fq2.js} +5 -3
  48. package/dist/shared/{2321avmn.js → r73pt594.js} +21 -13
  49. package/dist/shared/{kj680nxv.js → rasvvzgz.js} +8 -6
  50. package/dist/shared/{hcmdtwgh.js → szm0cksa.js} +4 -1
  51. package/dist/shared/{axhk06q1.js → tq9afp8d.js} +1 -1
  52. package/dist/shared/wxn4b7pp.js +252 -0
  53. package/dist/shared/{q6zsyh31.js → xdpj5kkm.js} +19 -8
  54. package/dist/shared/{s4mb6a4b.js → xhh1srw3.js} +54 -10
  55. package/dist/shared/{jgxzjzcz.js → xsq7v6wp.js} +2 -2
  56. package/dist/shared/{hqjsxnz7.js → ykv7wh5b.js} +5 -4
  57. package/dist/shared/{q795afwa.js → z2rnngj4.js} +3 -3
  58. package/dist/tooling/cli.js +12 -12
  59. package/dist/tooling/migrate/apply.d.ts +2 -0
  60. package/dist/tooling/migrate/files.d.ts +7 -0
  61. package/dist/tooling/migrate/index.js +8 -6
  62. package/dist/tooling/testing/facts.d.ts +3 -0
  63. package/dist/tooling/testing/index.js +3 -3
  64. package/package.json +1 -1
@@ -0,0 +1,1161 @@
1
+ import {
2
+ isConnectionFailure
3
+ } from "./tbszx8mz.js";
4
+ import {
5
+ createClient
6
+ } from "./bt9sfq0z.js";
7
+ import {
8
+ OkmError2
9
+ } from "./52szzakm.js";
10
+
11
+ // src/runtime/topology.ts
12
+ var CIRCUIT_AT = 2;
13
+ var BACKOFF_CAP_MS = 30000;
14
+ var DEFAULT_PROBE_MS = 1000;
15
+ var CAPABILITY_SQL = "select case when to_regprocedure('pg_last_wal_replay_lsn()') is null then false when to_regprocedure('pg_current_wal_insert_lsn()') is null then false else has_function_privilege(to_regprocedure('pg_last_wal_replay_lsn()'), 'execute') and has_function_privilege(to_regprocedure('pg_current_wal_insert_lsn()'), 'execute') end";
16
+ var HEALTH_SQL = "select 1, pg_last_wal_replay_lsn()::text";
17
+ var HEALTH_TIME_SQL = "select 1, pg_last_wal_replay_lsn()::text, pg_last_xact_replay_timestamp()::text";
18
+ var INSERT_LSN_SQL = "select pg_current_wal_insert_lsn()::text";
19
+ var PING_SQL = "select 1";
20
+ var SELECT_NAMES = ["weighted", "roundRobin", "leastConnections", "latencyAware"];
21
+ var LATENCY_ALPHA = 0.2;
22
+ var ROUTING_KEYS = "probe, select, fallback, consistency, maxLag";
23
+ var handles = new WeakMap;
24
+ async function connectTopology(target, options, open) {
25
+ if (Object.prototype.hasOwnProperty.call(options.schema.model, "using")) {
26
+ throw new OkmError2("OKM1120", 'Table "using" collides with client.using(). Rename the table.');
27
+ }
28
+ refuseRouting(options.routing);
29
+ const consistency = readConsistency(options.routing?.consistency);
30
+ const maxLag = readMaxLag(options.routing?.maxLag);
31
+ const select = readSelect(options.routing?.select);
32
+ const primaryUrl = readPrimary(target);
33
+ const replicas = readReplicas(target);
34
+ const probeMs = readProbe(options.routing?.probe);
35
+ const defaults = driverFields(options);
36
+ const opened = [];
37
+ try {
38
+ opened.push(await openEndpoint(open, primaryUrl, defaults.max, defaults, "primary", "primary", 1, probeMs));
39
+ for (let index = 0;index < replicas.length; index += 1) {
40
+ const replica = replicas[index];
41
+ if (replica === undefined)
42
+ continue;
43
+ const name = replica.name ?? `replica-${String(index + 1)}`;
44
+ opened.push(await openEndpoint(open, replica.url, replica.max ?? defaults.max, defaults, name, "replica", replica.weight, probeMs));
45
+ }
46
+ assertNames(opened);
47
+ await Promise.all(opened.map((endpoint) => detectPosition(endpoint)));
48
+ const replicaEndpoints = opened.filter((endpoint) => endpoint.role === "replica");
49
+ const handle = {
50
+ closed: false,
51
+ probeMs,
52
+ consistency,
53
+ maxLag,
54
+ hasReplicas: replicaEndpoints.length > 0,
55
+ watermark: undefined,
56
+ positionUnknown: false,
57
+ unknownEpoch: 0,
58
+ primaryLsn: undefined,
59
+ fallback: readFallback(options.routing?.fallback),
60
+ select,
61
+ rrLast: undefined,
62
+ scratch: [],
63
+ onRoute: options.onRoute,
64
+ replicaState: options.replicaState,
65
+ endpoints: opened,
66
+ closing: undefined
67
+ };
68
+ await Promise.all([
69
+ ...replicaEndpoints.map((endpoint) => sample(handle, endpoint)),
70
+ samplePrimary(handle)
71
+ ]);
72
+ for (const endpoint of replicaEndpoints) {
73
+ endpoint.nextDelayMs = delayFor(probeMs, endpoint);
74
+ schedule(handle, endpoint);
75
+ }
76
+ schedulePrimary(handle);
77
+ const primary = opened[0];
78
+ if (primary === undefined) {
79
+ throw new OkmError2("OKM1120", "A topology needs one primary URL.");
80
+ }
81
+ const shared = {
82
+ http: options.errors?.http,
83
+ includeValues: options.errors?.includeValues,
84
+ logger: options.logger,
85
+ signal: options.signal,
86
+ timeout: options.timeout,
87
+ timeouts: options.timeouts,
88
+ hookm: options.hookm,
89
+ catalog: options.catalog,
90
+ catalogDir: options.catalogDir,
91
+ requireMeta: options.requireMeta,
92
+ generators: options.generators
93
+ };
94
+ const router = routePool(handle);
95
+ const client = createClient(options.schema, router, { ownsPool: true, ...shared });
96
+ const routed = client;
97
+ routed.using = (route) => derive(options.schema, handle, router, shared, route);
98
+ handles.set(routed, handle);
99
+ return routed;
100
+ } catch (error) {
101
+ for (const endpoint of opened) {
102
+ if (endpoint.timer !== undefined)
103
+ clearTimeout(endpoint.timer);
104
+ endpoint.timer = undefined;
105
+ }
106
+ await Promise.all(opened.map((endpoint) => endpoint.pool.close()));
107
+ throw error;
108
+ }
109
+ }
110
+ function refuseRouting(routing) {
111
+ if (routing === undefined)
112
+ return;
113
+ if (typeof routing !== "object" || routing === null || Array.isArray(routing)) {
114
+ throw new OkmError2("OKM1120", `routing must be an object. Accepted keys: ${ROUTING_KEYS}.`);
115
+ }
116
+ for (const key of Object.keys(routing)) {
117
+ if (key === "probe" || key === "fallback" || key === "select" || key === "consistency" || key === "maxLag") {
118
+ continue;
119
+ }
120
+ throw new OkmError2("OKM1120", `routing.${key} is not a routing option. Accepted keys: ${ROUTING_KEYS}.`);
121
+ }
122
+ }
123
+ function readSelect(value) {
124
+ if (value === undefined)
125
+ return "weighted";
126
+ if (typeof value === "function")
127
+ return value;
128
+ if (typeof value === "string" && isSelectName(value))
129
+ return value;
130
+ throw new OkmError2("OKM1120", `routing.select must be "${SELECT_NAMES.join('", "')}", or a function.`);
131
+ }
132
+ function isSelectName(value) {
133
+ return SELECT_NAMES.includes(value);
134
+ }
135
+ function readConsistency(value) {
136
+ if (value === undefined || value === "session")
137
+ return "session";
138
+ if (value === "eventual")
139
+ return "eventual";
140
+ throw new OkmError2("OKM1120", `routing.consistency must be "session" or "eventual". ${shown(value)} is not one of those.`);
141
+ }
142
+ function readMaxLag(value) {
143
+ if (value === undefined)
144
+ return;
145
+ if (typeof value !== "string") {
146
+ throw new OkmError2("OKM1120", maxLagMessage(shown(value)));
147
+ }
148
+ const text = value.trim();
149
+ const time = /^(\d+(?:\.\d+)?)(ms|s|m)$/.exec(text);
150
+ if (time !== null) {
151
+ const amount = Number(time[1]);
152
+ const unit = time[2];
153
+ if (!Number.isFinite(amount) || amount < 0 || unit === undefined) {
154
+ throw new OkmError2("OKM1120", maxLagMessage(value));
155
+ }
156
+ const ms = unit === "ms" ? amount : unit === "s" ? amount * 1000 : amount * 60000;
157
+ return { kind: "time", ms };
158
+ }
159
+ const size = /^(\d+(?:\.\d+)?)(B|KB|MB|GB)$/.exec(text);
160
+ if (size !== null) {
161
+ const amount = Number(size[1]);
162
+ const unit = size[2];
163
+ if (!Number.isFinite(amount) || amount < 0 || unit === undefined) {
164
+ throw new OkmError2("OKM1120", maxLagMessage(value));
165
+ }
166
+ return { kind: "bytes", bytes: scaleBytes(amount, unit) };
167
+ }
168
+ throw new OkmError2("OKM1120", maxLagMessage(value));
169
+ }
170
+ function maxLagMessage(value) {
171
+ return `routing.maxLag must be a duration ("5s", "500ms", "2m") or a size ("16MB", "512KB", "1GB", "4096B"). ${value} is not one of those forms.`;
172
+ }
173
+ function scaleBytes(amount, unit) {
174
+ const scale = unit === "B" ? 1n : unit === "KB" ? 1024n : unit === "MB" ? 1048576n : 1073741824n;
175
+ if (Number.isInteger(amount))
176
+ return BigInt(amount) * scale;
177
+ return BigInt(Math.trunc(amount * Number(scale)));
178
+ }
179
+ function readPrimary(target) {
180
+ const value = fields(target);
181
+ for (const key of Object.keys(value)) {
182
+ if (key !== "primary" && key !== "replicas") {
183
+ throw new OkmError2("OKM1120", `Topology key ${key} is not accepted. Accepted keys: primary, replicas.`);
184
+ }
185
+ }
186
+ const primary = value.primary;
187
+ if (typeof primary !== "string" || primary.length === 0) {
188
+ throw new OkmError2("OKM1120", "A topology needs one primary URL. Accepted shape: { primary, replicas }.");
189
+ }
190
+ return primary;
191
+ }
192
+ function readReplicas(target) {
193
+ const value = fields(target).replicas;
194
+ if (value === undefined)
195
+ return [];
196
+ if (!Array.isArray(value)) {
197
+ throw new OkmError2("OKM1120", "replicas must be an array of URLs or { url, weight, name, pool }.");
198
+ }
199
+ return value.map((entry, index) => readReplica(entry, index));
200
+ }
201
+ function readReplica(entry, index) {
202
+ if (typeof entry === "string") {
203
+ if (entry.length === 0) {
204
+ throw new OkmError2("OKM1120", `replicas[${String(index)}] is an empty URL.`);
205
+ }
206
+ return { url: entry, weight: 1, name: undefined, max: undefined };
207
+ }
208
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
209
+ throw new OkmError2("OKM1120", `replicas[${String(index)}] must be a URL or { url, weight, name, pool }.`);
210
+ }
211
+ const value = fields(entry);
212
+ for (const key of Object.keys(value)) {
213
+ if (key !== "url" && key !== "weight" && key !== "name" && key !== "pool") {
214
+ throw new OkmError2("OKM1120", `replicas[${String(index)}].${key} is not accepted. Accepted keys: url, weight, name, pool.`);
215
+ }
216
+ }
217
+ const url = value.url;
218
+ if (typeof url !== "string" || url.length === 0) {
219
+ throw new OkmError2("OKM1120", `replicas[${String(index)}] needs a url.`);
220
+ }
221
+ return {
222
+ url,
223
+ weight: readWeight(value.weight, index),
224
+ name: readName(value.name, index),
225
+ max: readPoolMax(value.pool, index)
226
+ };
227
+ }
228
+ function readWeight(value, index) {
229
+ if (value === undefined)
230
+ return 1;
231
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
232
+ throw new OkmError2("OKM1120", `replicas[${String(index)}].weight must be a positive number.`);
233
+ }
234
+ return value;
235
+ }
236
+ function readName(value, index) {
237
+ if (value === undefined)
238
+ return;
239
+ if (typeof value !== "string" || value.length === 0) {
240
+ throw new OkmError2("OKM1120", `replicas[${String(index)}].name must be a non-empty string.`);
241
+ }
242
+ return value;
243
+ }
244
+ function readPoolMax(value, index) {
245
+ if (value === undefined)
246
+ return;
247
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
248
+ throw new OkmError2("OKM1120", `replicas[${String(index)}].pool must be { max }.`);
249
+ }
250
+ const pool = fields(value);
251
+ for (const key of Object.keys(pool)) {
252
+ if (key !== "max") {
253
+ throw new OkmError2("OKM1120", `replicas[${String(index)}].pool.${key} is not accepted. Accepted key: max.`);
254
+ }
255
+ }
256
+ const max = pool.max;
257
+ if (typeof max !== "number" || !Number.isInteger(max) || max < 1) {
258
+ throw new OkmError2("OKM1120", `replicas[${String(index)}].pool.max must be a positive integer.`);
259
+ }
260
+ return max;
261
+ }
262
+ function readProbe(value) {
263
+ if (value === undefined)
264
+ return DEFAULT_PROBE_MS;
265
+ if (typeof value === "number") {
266
+ if (!Number.isFinite(value) || value <= 0) {
267
+ throw new OkmError2("OKM1120", "routing.probe must be a positive number of milliseconds.");
268
+ }
269
+ return value;
270
+ }
271
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m)$/.exec(value.trim());
272
+ const amount = match?.[1];
273
+ const unit = match?.[2];
274
+ if (amount === undefined || unit === undefined) {
275
+ throw new OkmError2("OKM1120", 'routing.probe must be a number of milliseconds, or "1s", "500ms", or "1m".');
276
+ }
277
+ const parsed = Number(amount);
278
+ if (!Number.isFinite(parsed) || parsed <= 0) {
279
+ throw new OkmError2("OKM1120", "routing.probe must be positive.");
280
+ }
281
+ if (unit === "ms")
282
+ return parsed;
283
+ if (unit === "s")
284
+ return parsed * 1000;
285
+ return parsed * 60000;
286
+ }
287
+ function assertNames(endpoints) {
288
+ const seen = new Set;
289
+ for (const endpoint of endpoints) {
290
+ if (seen.has(endpoint.name)) {
291
+ throw new OkmError2("OKM1120", `Endpoint name ${endpoint.name} is used twice.`);
292
+ }
293
+ seen.add(endpoint.name);
294
+ }
295
+ }
296
+ function driverFields(options) {
297
+ const value = fields(options);
298
+ const prepared = value.prepared;
299
+ return {
300
+ max: typeof value.max === "number" ? value.max : undefined,
301
+ timeouts: typeof value.timeouts === "object" && value.timeouts !== null ? value.timeouts : undefined,
302
+ prepared: prepared === "named" || prepared === "unnamed" ? prepared : undefined,
303
+ searchPath: typeof value.searchPath === "string" ? value.searchPath : undefined,
304
+ ssl: value.ssl
305
+ };
306
+ }
307
+ async function openEndpoint(open, url, max, defaults, name, role, weight, probeMs) {
308
+ const pool = await open({
309
+ url,
310
+ dataDir: url,
311
+ ...max !== undefined ? { max } : {},
312
+ ...defaults.timeouts !== undefined ? { timeouts: defaults.timeouts } : {},
313
+ ...defaults.prepared !== undefined ? { prepared: defaults.prepared } : {},
314
+ ...defaults.searchPath !== undefined ? { searchPath: defaults.searchPath } : {},
315
+ ...defaults.ssl !== undefined ? { ssl: defaults.ssl } : {}
316
+ });
317
+ return {
318
+ name,
319
+ role,
320
+ weight,
321
+ max,
322
+ pool,
323
+ position: false,
324
+ circuit: "closed",
325
+ failures: 0,
326
+ replayLsn: null,
327
+ replayPos: undefined,
328
+ replayAt: null,
329
+ nextDelayMs: role === "replica" ? probeMs : 0,
330
+ timer: undefined,
331
+ current: 0,
332
+ inflight: 0,
333
+ latencyMs: null
334
+ };
335
+ }
336
+ async function detectPosition(endpoint) {
337
+ try {
338
+ const result = await endpoint.pool.execute(CAPABILITY_SQL);
339
+ endpoint.position = truth(result.rows[0]?.[0]);
340
+ } catch {
341
+ endpoint.position = false;
342
+ }
343
+ }
344
+ async function sample(handle, endpoint) {
345
+ try {
346
+ const sampled = await readPosition(handle, endpoint);
347
+ endpoint.failures = 0;
348
+ endpoint.circuit = "closed";
349
+ applySample(endpoint, sampled);
350
+ } catch {
351
+ endpoint.failures += 1;
352
+ if (endpoint.failures >= CIRCUIT_AT)
353
+ endpoint.circuit = "open";
354
+ }
355
+ }
356
+ async function readPosition(handle, endpoint) {
357
+ const state = handle.replicaState;
358
+ const started = performance.now();
359
+ if (state === undefined) {
360
+ const sql = handle.maxLag?.kind === "time" ? HEALTH_TIME_SQL : HEALTH_SQL;
361
+ const result = await endpoint.pool.execute(sql);
362
+ observe(endpoint, performance.now() - started);
363
+ const row = result.rows[0];
364
+ return { lsn: row?.[1] ?? null, time: row?.[2] ?? null };
365
+ }
366
+ await endpoint.pool.execute(PING_SQL);
367
+ observe(endpoint, performance.now() - started);
368
+ const lsn = await state.replayLsn({ name: endpoint.name });
369
+ if (typeof lsn !== "string" && lsn !== null) {
370
+ throw new OkmError2("OKM1120", "ReplicaState.replayLsn must return a WAL position or null.");
371
+ }
372
+ return { lsn, time: await seamTime(handle, endpoint, state) };
373
+ }
374
+ async function seamTime(handle, endpoint, state) {
375
+ if (handle.maxLag?.kind !== "time" || state.replayTime === undefined)
376
+ return null;
377
+ const value = await state.replayTime({ name: endpoint.name });
378
+ if (value instanceof Date)
379
+ return value.toISOString();
380
+ if (typeof value === "string" || value === null)
381
+ return value;
382
+ throw new OkmError2("OKM1120", "ReplicaState.replayTime must return a timestamp or null.");
383
+ }
384
+ function applySample(endpoint, sampled) {
385
+ if (sampled.lsn !== null)
386
+ noteReplay(endpoint, sampled.lsn);
387
+ if (sampled.time !== null)
388
+ noteTime(endpoint, sampled.time);
389
+ }
390
+ function noteReplay(endpoint, text) {
391
+ const pos = parseLsn(text);
392
+ if (pos === undefined)
393
+ return;
394
+ if (endpoint.replayPos !== undefined && pos < endpoint.replayPos)
395
+ return;
396
+ endpoint.replayPos = pos;
397
+ endpoint.replayLsn = text.trim();
398
+ }
399
+ function noteTime(endpoint, text) {
400
+ const parsed = parseTime(text);
401
+ if (parsed === undefined)
402
+ return;
403
+ endpoint.replayAt = parsed;
404
+ }
405
+ function observe(endpoint, sampleMs) {
406
+ const previous = endpoint.latencyMs;
407
+ endpoint.latencyMs = previous === null ? sampleMs : LATENCY_ALPHA * sampleMs + (1 - LATENCY_ALPHA) * previous;
408
+ }
409
+ function delayFor(probeMs, endpoint) {
410
+ if (endpoint.circuit === "closed")
411
+ return probeMs;
412
+ const steps = Math.min(Math.max(endpoint.failures - 1, 1), 8);
413
+ const delay = probeMs * 2 ** steps;
414
+ return delay > BACKOFF_CAP_MS ? BACKOFF_CAP_MS : delay;
415
+ }
416
+ function schedule(handle, endpoint) {
417
+ if (handle.closed || endpoint.role !== "replica")
418
+ return;
419
+ const timer = setTimeout(() => {
420
+ endpoint.timer = undefined;
421
+ runProbe(handle, endpoint);
422
+ }, endpoint.nextDelayMs);
423
+ timer.unref();
424
+ endpoint.timer = timer;
425
+ }
426
+ function schedulePrimary(handle) {
427
+ const primary = handle.endpoints[0];
428
+ if (handle.closed || primary === undefined || !probesPrimary(handle))
429
+ return;
430
+ const timer = setTimeout(() => {
431
+ primary.timer = undefined;
432
+ runPrimary(handle);
433
+ }, handle.probeMs);
434
+ timer.unref();
435
+ primary.timer = timer;
436
+ }
437
+ async function runPrimary(handle) {
438
+ if (handle.closed)
439
+ return;
440
+ await samplePrimary(handle);
441
+ if (handle.closed)
442
+ return;
443
+ schedulePrimary(handle);
444
+ }
445
+ async function samplePrimary(handle) {
446
+ if (!probesPrimary(handle))
447
+ return;
448
+ const primary = handle.endpoints[0];
449
+ if (primary === undefined)
450
+ return;
451
+ const epoch = handle.unknownEpoch;
452
+ const wasUnknown = handle.positionUnknown;
453
+ try {
454
+ const lsn = await readInsertLsn(primary.pool);
455
+ if (lsn === undefined)
456
+ return;
457
+ if (wasUnknown && handle.unknownEpoch === epoch && handle.positionUnknown) {
458
+ raiseWatermark(handle, lsn);
459
+ return;
460
+ }
461
+ notePrimary(handle, lsn);
462
+ } catch {}
463
+ }
464
+ function probesPrimary(handle) {
465
+ if (!handle.hasReplicas)
466
+ return false;
467
+ const primary = handle.endpoints[0];
468
+ if (primary === undefined || !primary.position)
469
+ return false;
470
+ return handle.consistency === "session" || handle.maxLag !== undefined;
471
+ }
472
+ async function runProbe(handle, endpoint) {
473
+ if (handle.closed)
474
+ return;
475
+ await sample(handle, endpoint);
476
+ if (handle.closed)
477
+ return;
478
+ endpoint.nextDelayMs = delayFor(handle.probeMs, endpoint);
479
+ schedule(handle, endpoint);
480
+ }
481
+ function closeHandle(handle) {
482
+ handle.closing ??= shutdown(handle);
483
+ return handle.closing;
484
+ }
485
+ async function shutdown(handle) {
486
+ handle.closed = true;
487
+ for (const endpoint of handle.endpoints) {
488
+ if (endpoint.timer !== undefined)
489
+ clearTimeout(endpoint.timer);
490
+ endpoint.timer = undefined;
491
+ }
492
+ await Promise.all(handle.endpoints.map((endpoint) => endpoint.pool.close()));
493
+ }
494
+ function routePool(handle) {
495
+ const primary = handle.endpoints[0];
496
+ if (primary === undefined) {
497
+ throw new OkmError2("OKM1120", "A topology needs one primary URL.");
498
+ }
499
+ const pool = {
500
+ capabilities: primary.pool.capabilities,
501
+ execute: async (text, params, options) => {
502
+ if (handle.closed)
503
+ closed();
504
+ const choice = await choose(handle, text, options?.route);
505
+ const result = await attempt(handle, choice, (endpoint) => endpoint.pool.execute(text, params, strip(options)));
506
+ if (choice.op === "write")
507
+ await noteWrite(handle, primary.pool);
508
+ return result;
509
+ },
510
+ batch: async (statements, options) => {
511
+ if (handle.closed)
512
+ closed();
513
+ if (options?.route === "replica")
514
+ replicaRefused();
515
+ const reason = options?.route === "primary" ? "constraint:primary" : "primary-required";
516
+ tell(handle, { op: "write", endpoint: primary.name, reason });
517
+ const result = await primary.pool.batch(statements, strip(options));
518
+ await noteWrite(handle, primary.pool);
519
+ return result;
520
+ },
521
+ stats: () => primary.pool.stats(),
522
+ close: () => closeHandle(handle)
523
+ };
524
+ if (primary.pool.reserve !== undefined) {
525
+ pool.reserve = async () => {
526
+ if (handle.closed)
527
+ closed();
528
+ tell(handle, { op: "tx", endpoint: primary.name, reason: "primary-required" });
529
+ return wrap(handle, await primary.pool.reserve());
530
+ };
531
+ }
532
+ if (primary.pool.describe !== undefined) {
533
+ pool.describe = async (text, params) => {
534
+ const choice = await choose(handle, text, undefined);
535
+ tell(handle, { op: choice.op, endpoint: choice.endpoint.name, reason: choice.reason });
536
+ if (choice.endpoint.pool.describe !== undefined) {
537
+ return choice.endpoint.pool.describe(text, params);
538
+ }
539
+ return primary.pool.describe(text, params);
540
+ };
541
+ }
542
+ if (primary.pool.stream !== undefined) {
543
+ pool.stream = (text, params) => readStream(handle, text, undefined, params, primary.pool);
544
+ }
545
+ if (primary.pool.listen !== undefined) {
546
+ pool.listen = (channel, onNotify) => {
547
+ tell(handle, { op: "read", endpoint: primary.name, reason: "primary-required" });
548
+ return primary.pool.listen(channel, onNotify);
549
+ };
550
+ }
551
+ if (primary.pool.cancel !== undefined) {
552
+ pool.cancel = () => {
553
+ for (const endpoint of handle.endpoints)
554
+ endpoint.pool.cancel?.();
555
+ };
556
+ }
557
+ return pool;
558
+ }
559
+ function derive(schema, handle, router, shared, route) {
560
+ const name = readRoute(route);
561
+ const client = createClient(schema, force(handle, router, name), { ...shared, ownsPool: false });
562
+ delete client.close;
563
+ return client;
564
+ }
565
+ function force(handle, router, route) {
566
+ const pool = {
567
+ forcedRoute: route,
568
+ capabilities: router.capabilities,
569
+ execute: (text, params, options) => router.execute(text, params, { ...options, route }),
570
+ batch: (statements, options) => router.batch(statements, { ...options, route }),
571
+ stats: () => router.stats(),
572
+ close: () => Promise.resolve()
573
+ };
574
+ if (router.reserve !== undefined) {
575
+ pool.reserve = () => {
576
+ if (route === "replica")
577
+ replicaRefused();
578
+ return router.reserve();
579
+ };
580
+ }
581
+ if (router.describe !== undefined) {
582
+ pool.describe = async (text, params) => {
583
+ const choice = await choose(handle, text, route);
584
+ tell(handle, { op: choice.op, endpoint: choice.endpoint.name, reason: choice.reason });
585
+ if (choice.endpoint.pool.describe !== undefined) {
586
+ return choice.endpoint.pool.describe(text, params);
587
+ }
588
+ return router.describe(text, params);
589
+ };
590
+ }
591
+ if (router.stream !== undefined) {
592
+ pool.stream = (text, params) => readStream(handle, text, route, params, router);
593
+ }
594
+ if (router.listen !== undefined) {
595
+ pool.listen = (channel, onNotify) => {
596
+ if (route === "replica")
597
+ replicaRefused();
598
+ return router.listen(channel, onNotify);
599
+ };
600
+ }
601
+ if (router.cancel !== undefined)
602
+ pool.cancel = () => router.cancel();
603
+ return pool;
604
+ }
605
+ function classOf(text) {
606
+ const head = text.trimStart().toLowerCase().replace(/\s+/g, " ");
607
+ if (/^(begin|start transaction|commit|rollback|savepoint|release|set|reset)\b/.test(head)) {
608
+ return "tx";
609
+ }
610
+ if (head.includes("pg_advisory"))
611
+ return "lock";
612
+ if (/\sfor (no key update|key share|update|share)\b/.test(head))
613
+ return "lock";
614
+ if (/^(insert|update|delete|with|create|alter|drop|truncate|grant|revoke|comment|vacuum|analyze|reindex|copy)\b/.test(head)) {
615
+ return "write";
616
+ }
617
+ return "read";
618
+ }
619
+ async function choose(handle, text, route) {
620
+ if (route !== undefined && route !== "primary" && route !== "replica") {
621
+ throw new OkmError2("OKM1120", 'route must be "primary" or "replica".');
622
+ }
623
+ const op = classOf(text);
624
+ const primary = handle.endpoints[0];
625
+ if (primary === undefined)
626
+ throw new OkmError2("OKM1120", "A topology needs one primary URL.");
627
+ if (text.trimStart().toLowerCase().startsWith("select current_setting")) {
628
+ return { endpoint: primary, op: "read", reason: "primary-required" };
629
+ }
630
+ if (route === "replica" && op !== "read")
631
+ replicaRefused();
632
+ if (op !== "read") {
633
+ return {
634
+ endpoint: primary,
635
+ op,
636
+ reason: route === "primary" ? "constraint:primary" : "primary-required"
637
+ };
638
+ }
639
+ if (route === "primary")
640
+ return { endpoint: primary, op, reason: "constraint:primary" };
641
+ if (handle.positionUnknown && handle.consistency === "session" && handle.hasReplicas) {
642
+ if (route === "replica")
643
+ noReplica();
644
+ if (handle.fallback === "error")
645
+ fallbackRefused("position-unknown");
646
+ return { endpoint: primary, op, reason: "fallback:position-unknown" };
647
+ }
648
+ const explicit = route === "replica";
649
+ const gated = await gate(handle, undefined, !explicit);
650
+ const replica = pick(handle, gated.ready, !explicit);
651
+ if (replica !== undefined) {
652
+ return {
653
+ endpoint: replica,
654
+ op,
655
+ reason: explicit ? "constraint:replica" : `auto:${replica.name}`
656
+ };
657
+ }
658
+ if (explicit)
659
+ noReplica();
660
+ if (handle.fallback === "error")
661
+ fallbackRefused(gated.miss);
662
+ return { endpoint: primary, op, reason: `fallback:${gated.miss}` };
663
+ }
664
+ async function* readStream(handle, text, route, params, fallback) {
665
+ const choice = await choose(handle, text, route);
666
+ tell(handle, { op: choice.op, endpoint: choice.endpoint.name, reason: choice.reason });
667
+ const pool = choice.endpoint.pool.stream !== undefined ? choice.endpoint.pool : fallback;
668
+ if (pool.stream === undefined)
669
+ return;
670
+ yield* pool.stream(text, params);
671
+ }
672
+ async function attempt(handle, choice, run) {
673
+ tell(handle, { op: choice.op, endpoint: choice.endpoint.name, reason: choice.reason });
674
+ try {
675
+ return await tracked(choice.endpoint, choice.op === "read", () => run(choice.endpoint));
676
+ } catch (error) {
677
+ if (choice.op !== "read" || !isConnectionFailure(error))
678
+ throw error;
679
+ const next = await retryOf(handle, choice);
680
+ if (next === undefined)
681
+ throw error;
682
+ tell(handle, { op: "read", endpoint: next.endpoint.name, reason: next.reason });
683
+ return tracked(next.endpoint, true, () => run(next.endpoint));
684
+ }
685
+ }
686
+ async function tracked(endpoint, time, run) {
687
+ const replica = endpoint.role === "replica";
688
+ if (replica)
689
+ endpoint.inflight += 1;
690
+ const started = replica && time ? performance.now() : 0;
691
+ try {
692
+ const result = await run();
693
+ if (replica && time)
694
+ observe(endpoint, performance.now() - started);
695
+ return result;
696
+ } finally {
697
+ if (replica)
698
+ endpoint.inflight -= 1;
699
+ }
700
+ }
701
+ async function retryOf(handle, choice) {
702
+ if (choice.endpoint.role === "replica")
703
+ noteFailure(choice.endpoint);
704
+ const explicit = choice.reason.startsWith("constraint:");
705
+ const gated = await gate(handle, choice.endpoint, !explicit);
706
+ const replica = pick(handle, gated.ready, !explicit);
707
+ if (replica !== undefined) {
708
+ return {
709
+ endpoint: replica,
710
+ op: "read",
711
+ reason: explicit ? "constraint:replica" : `auto:${replica.name}`
712
+ };
713
+ }
714
+ if (explicit)
715
+ return;
716
+ if (handle.fallback === "error")
717
+ fallbackRefused(gated.miss);
718
+ const primary = handle.endpoints[0];
719
+ if (primary === undefined)
720
+ return;
721
+ return { endpoint: primary, op: "read", reason: `fallback:${gated.miss}` };
722
+ }
723
+ async function gate(handle, except, capacity) {
724
+ const healthy = takeHealthy(handle, except);
725
+ if (healthy.length === 0)
726
+ return { ready: healthy, miss: emptyReason(handle, capacity) };
727
+ const lagged = await consistent(handle, healthy);
728
+ if (lagged.length === 0)
729
+ return { ready: lagged, miss: "behind" };
730
+ if (!capacity)
731
+ return { ready: lagged, miss: "behind" };
732
+ const room = [];
733
+ for (const endpoint of lagged) {
734
+ if (!saturated(endpoint))
735
+ room.push(endpoint);
736
+ }
737
+ if (room.length === 0)
738
+ return { ready: room, miss: "saturated" };
739
+ return { ready: room, miss: "behind" };
740
+ }
741
+ function takeHealthy(handle, except) {
742
+ const scratch = handle.scratch;
743
+ scratch.length = 0;
744
+ for (const endpoint of handle.endpoints) {
745
+ if (endpoint.role !== "replica" || endpoint === except || endpoint.circuit !== "closed") {
746
+ continue;
747
+ }
748
+ scratch.push(endpoint);
749
+ }
750
+ const copy = scratch.slice();
751
+ scratch.length = 0;
752
+ return copy;
753
+ }
754
+ async function consistent(handle, healthy) {
755
+ if (!filters(handle))
756
+ return healthy;
757
+ const kept = [];
758
+ for (const endpoint of healthy) {
759
+ if (freshEnough(handle, endpoint)) {
760
+ kept.push(endpoint);
761
+ continue;
762
+ }
763
+ if (!endpoint.position)
764
+ continue;
765
+ try {
766
+ applySample(endpoint, await readPosition(handle, endpoint));
767
+ } catch (error) {
768
+ if (error instanceof OkmError2)
769
+ throw error;
770
+ noteFailure(endpoint);
771
+ continue;
772
+ }
773
+ if (freshEnough(handle, endpoint))
774
+ kept.push(endpoint);
775
+ }
776
+ return kept;
777
+ }
778
+ function filters(handle) {
779
+ if (handle.maxLag !== undefined)
780
+ return true;
781
+ return handle.consistency === "session" && handle.watermark !== undefined;
782
+ }
783
+ function freshEnough(handle, endpoint) {
784
+ return coversWatermark(handle, endpoint) && coversLag(handle, endpoint);
785
+ }
786
+ function coversWatermark(handle, endpoint) {
787
+ if (handle.consistency !== "session" || handle.watermark === undefined)
788
+ return true;
789
+ if (!endpoint.position)
790
+ return false;
791
+ const replay = endpoint.replayPos;
792
+ if (replay === undefined)
793
+ return false;
794
+ return replay >= handle.watermark;
795
+ }
796
+ function coversLag(handle, endpoint) {
797
+ const lag = handle.maxLag;
798
+ if (lag === undefined)
799
+ return true;
800
+ if (!endpoint.position)
801
+ return false;
802
+ if (lag.kind === "bytes") {
803
+ const diff = byteLag(handle, endpoint);
804
+ return diff !== null && diff <= lag.bytes;
805
+ }
806
+ const ms = timeLag(handle, endpoint);
807
+ return ms !== null && ms <= lag.ms;
808
+ }
809
+ function saturated(endpoint) {
810
+ const max = endpoint.max;
811
+ if (max === undefined)
812
+ return false;
813
+ const stats = endpoint.pool.stats();
814
+ return stats.idle === 0 && stats.waiting > 0 && stats.size >= max;
815
+ }
816
+ function emptyReason(handle, capacity) {
817
+ let replicas = 0;
818
+ let healthy = 0;
819
+ let room = 0;
820
+ for (const endpoint of handle.endpoints) {
821
+ if (endpoint.role !== "replica")
822
+ continue;
823
+ replicas += 1;
824
+ if (endpoint.circuit !== "closed")
825
+ continue;
826
+ healthy += 1;
827
+ if (!capacity || !saturated(endpoint))
828
+ room += 1;
829
+ }
830
+ if (replicas === 0)
831
+ return "no-replicas";
832
+ if (healthy === 0)
833
+ return "unhealthy";
834
+ if (capacity && room === 0)
835
+ return "saturated";
836
+ return "unhealthy";
837
+ }
838
+ function pick(handle, candidates, automatic) {
839
+ if (candidates.length === 0)
840
+ return;
841
+ const select = handle.select;
842
+ if (typeof select === "function") {
843
+ if (!automatic || candidates.length < 2)
844
+ return candidates[0];
845
+ return fromSelect(handle, select, candidates);
846
+ }
847
+ if (select === "roundRobin")
848
+ return roundRobin(handle, candidates);
849
+ if (select === "leastConnections")
850
+ return leastConnections(candidates);
851
+ if (select === "latencyAware")
852
+ return latencyAware(candidates);
853
+ return weighted(handle, candidates);
854
+ }
855
+ function weighted(handle, candidates) {
856
+ const live = new Set(candidates);
857
+ for (const endpoint of handle.endpoints) {
858
+ if (endpoint.role === "replica" && !live.has(endpoint))
859
+ endpoint.current = 0;
860
+ }
861
+ const first = candidates[0];
862
+ if (first === undefined)
863
+ throw new Error("selection saw no candidate");
864
+ let best = first;
865
+ let total = 0;
866
+ for (const endpoint of candidates) {
867
+ endpoint.current += endpoint.weight;
868
+ total += endpoint.weight;
869
+ if (endpoint.current > best.current)
870
+ best = endpoint;
871
+ }
872
+ best.current -= total;
873
+ return best;
874
+ }
875
+ function roundRobin(handle, candidates) {
876
+ const next = after(handle, handle.rrLast, candidates);
877
+ handle.rrLast = next;
878
+ return next;
879
+ }
880
+ function after(handle, last, candidates) {
881
+ const first = candidates[0];
882
+ if (first === undefined)
883
+ throw new Error("selection saw no candidate");
884
+ if (last === undefined)
885
+ return first;
886
+ const start = handle.endpoints.indexOf(last);
887
+ if (start === -1)
888
+ return first;
889
+ for (let step = 1;step <= handle.endpoints.length; step += 1) {
890
+ const endpoint = handle.endpoints[(start + step) % handle.endpoints.length];
891
+ if (endpoint !== undefined && candidates.includes(endpoint))
892
+ return endpoint;
893
+ }
894
+ return first;
895
+ }
896
+ function leastConnections(candidates) {
897
+ let best = candidates[0];
898
+ if (best === undefined)
899
+ throw new Error("selection saw no candidate");
900
+ for (let index = 1;index < candidates.length; index += 1) {
901
+ const endpoint = candidates[index];
902
+ if (endpoint !== undefined && endpoint.inflight < best.inflight)
903
+ best = endpoint;
904
+ }
905
+ return best;
906
+ }
907
+ function latencyAware(candidates) {
908
+ let best = candidates[0];
909
+ if (best === undefined)
910
+ throw new Error("selection saw no candidate");
911
+ for (let index = 1;index < candidates.length; index += 1) {
912
+ const endpoint = candidates[index];
913
+ if (endpoint !== undefined && lowerLatency(endpoint, best))
914
+ best = endpoint;
915
+ }
916
+ return best;
917
+ }
918
+ function lowerLatency(endpoint, best) {
919
+ if (endpoint.latencyMs === null)
920
+ return false;
921
+ if (best.latencyMs === null)
922
+ return true;
923
+ return endpoint.latencyMs < best.latencyMs;
924
+ }
925
+ function fromSelect(handle, select, candidates) {
926
+ const endpoints = candidates.slice();
927
+ const offered = [];
928
+ for (const endpoint of endpoints) {
929
+ offered.push({
930
+ name: endpoint.name,
931
+ weight: endpoint.weight,
932
+ inflight: endpoint.inflight,
933
+ latencyMs: endpoint.latencyMs,
934
+ lag: byteLagNumber(handle, endpoint)
935
+ });
936
+ }
937
+ const returned = select(offered, { op: "read" });
938
+ if (typeof returned === "string") {
939
+ const endpoint = endpoints.find((item) => item.name === returned);
940
+ if (endpoint !== undefined)
941
+ return endpoint;
942
+ } else if (offered.includes(returned)) {
943
+ const index = offered.indexOf(returned);
944
+ const endpoint = endpoints[index];
945
+ if (endpoint !== undefined)
946
+ return endpoint;
947
+ }
948
+ throw new OkmError2("OKM1120", `routing.select returned ${shown(returned)}. It must return a candidate or its name.`);
949
+ }
950
+ function shown(value) {
951
+ if (typeof value === "string")
952
+ return JSON.stringify(value);
953
+ if (value === null)
954
+ return "null";
955
+ if (value === undefined)
956
+ return "undefined";
957
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
958
+ return String(value);
959
+ }
960
+ if (typeof value === "object") {
961
+ const name = Reflect.get(value, "name");
962
+ if (typeof name === "string")
963
+ return `an object named ${JSON.stringify(name)}`;
964
+ return "an object";
965
+ }
966
+ return typeof value;
967
+ }
968
+ function noteFailure(endpoint) {
969
+ endpoint.failures += 1;
970
+ if (endpoint.failures >= CIRCUIT_AT)
971
+ endpoint.circuit = "open";
972
+ }
973
+ function strip(options) {
974
+ if (options === undefined || options.route === undefined)
975
+ return options;
976
+ const { route: _route, ...rest } = options;
977
+ if (rest.signal === undefined && rest.timeout === undefined)
978
+ return;
979
+ return rest;
980
+ }
981
+ async function noteWrite(handle, runner) {
982
+ if (!tracks(handle))
983
+ return;
984
+ const primary = handle.endpoints[0];
985
+ if (primary === undefined || !primary.position) {
986
+ markUnknown(handle);
987
+ return;
988
+ }
989
+ try {
990
+ const lsn = await readInsertLsn(runner);
991
+ if (lsn === undefined) {
992
+ markUnknown(handle);
993
+ return;
994
+ }
995
+ raiseWatermark(handle, lsn);
996
+ } catch {
997
+ markUnknown(handle);
998
+ }
999
+ }
1000
+ function markUnknown(handle) {
1001
+ handle.positionUnknown = true;
1002
+ handle.unknownEpoch += 1;
1003
+ }
1004
+ function tracks(handle) {
1005
+ return handle.hasReplicas && handle.consistency === "session";
1006
+ }
1007
+ async function readInsertLsn(runner) {
1008
+ const result = await runner.execute(INSERT_LSN_SQL);
1009
+ const text = result.rows[0]?.[0];
1010
+ if (text === null || text === undefined)
1011
+ return;
1012
+ return parseLsn(text);
1013
+ }
1014
+ function notePrimary(handle, lsn) {
1015
+ if (handle.primaryLsn === undefined || lsn > handle.primaryLsn)
1016
+ handle.primaryLsn = lsn;
1017
+ }
1018
+ function raiseWatermark(handle, lsn) {
1019
+ notePrimary(handle, lsn);
1020
+ if (handle.watermark === undefined || lsn > handle.watermark)
1021
+ handle.watermark = lsn;
1022
+ handle.positionUnknown = false;
1023
+ }
1024
+ function byteLag(handle, endpoint) {
1025
+ const primary = handle.primaryLsn;
1026
+ const replay = endpoint.replayPos;
1027
+ if (primary === undefined || replay === undefined)
1028
+ return null;
1029
+ if (replay >= primary)
1030
+ return 0n;
1031
+ return primary - replay;
1032
+ }
1033
+ function byteLagNumber(handle, endpoint) {
1034
+ const diff = byteLag(handle, endpoint);
1035
+ if (diff === null)
1036
+ return null;
1037
+ if (diff > BigInt(Number.MAX_SAFE_INTEGER))
1038
+ return Number.MAX_SAFE_INTEGER;
1039
+ return Number(diff);
1040
+ }
1041
+ function timeLag(handle, endpoint) {
1042
+ const primary = handle.primaryLsn;
1043
+ const replay = endpoint.replayPos;
1044
+ if (primary === undefined || replay === undefined)
1045
+ return null;
1046
+ if (replay >= primary)
1047
+ return 0;
1048
+ if (endpoint.replayAt === null)
1049
+ return null;
1050
+ const lag = Date.now() - endpoint.replayAt;
1051
+ return lag < 0 ? 0 : lag;
1052
+ }
1053
+ function parseLsn(text) {
1054
+ const match = /^([0-9A-Fa-f]+)\/([0-9A-Fa-f]+)$/.exec(text.trim());
1055
+ const high = match?.[1];
1056
+ const low = match?.[2];
1057
+ if (high === undefined || low === undefined)
1058
+ return;
1059
+ return (BigInt(`0x${high}`) << 32n) + BigInt(`0x${low}`);
1060
+ }
1061
+ function parseTime(text) {
1062
+ const direct = Date.parse(text);
1063
+ if (!Number.isNaN(direct))
1064
+ return direct;
1065
+ const iso = Date.parse(text.replace(" ", "T"));
1066
+ if (!Number.isNaN(iso))
1067
+ return iso;
1068
+ return;
1069
+ }
1070
+ function controlKind(text) {
1071
+ const head = text.trimStart().toLowerCase().replace(/\s+/g, " ");
1072
+ if (/^commit\b/.test(head))
1073
+ return "commit";
1074
+ if (/^rollback\b/.test(head) && !/^rollback\s+to\b/.test(head))
1075
+ return "rollback";
1076
+ return "other";
1077
+ }
1078
+ function wrap(handle, conn) {
1079
+ let pending = false;
1080
+ return {
1081
+ execute: async (text, params, options) => {
1082
+ if (options?.route === "replica")
1083
+ replicaRefused();
1084
+ const result = await conn.execute(text, params, strip(options));
1085
+ const kind = controlKind(text);
1086
+ if (kind === "commit") {
1087
+ if (pending) {
1088
+ pending = false;
1089
+ await noteWrite(handle, conn);
1090
+ }
1091
+ } else if (kind === "rollback") {
1092
+ pending = false;
1093
+ } else if (classOf(text) === "write") {
1094
+ pending = true;
1095
+ }
1096
+ return result;
1097
+ },
1098
+ batch: async (statements, options) => {
1099
+ if (options?.route === "replica")
1100
+ replicaRefused();
1101
+ const result = await conn.batch(statements, strip(options));
1102
+ pending = true;
1103
+ return result;
1104
+ },
1105
+ release: () => conn.release(),
1106
+ ...conn.cancel !== undefined ? { cancel: () => conn.cancel() } : {}
1107
+ };
1108
+ }
1109
+ function tell(handle, event) {
1110
+ if (handle.onRoute === undefined)
1111
+ return;
1112
+ try {
1113
+ handle.onRoute(event);
1114
+ } catch {}
1115
+ }
1116
+ function readRoute(route) {
1117
+ if (route === "primary" || route === "replica")
1118
+ return route;
1119
+ throw new OkmError2("OKM1120", 'using takes "primary" or "replica".');
1120
+ }
1121
+ function readFallback(value) {
1122
+ if (value === undefined || value === "primary")
1123
+ return "primary";
1124
+ if (value === "error")
1125
+ return "error";
1126
+ throw new OkmError2("OKM1120", 'routing.fallback must be "primary" or "error".');
1127
+ }
1128
+ function closed() {
1129
+ throw new OkmError2("OKM1120", "The client is closed.");
1130
+ }
1131
+ function replicaRefused() {
1132
+ throw new OkmError2("OKM1840", "This operation needs the primary.", {
1133
+ fix: {
1134
+ summary: "Run the operation on the primary. Writes, batch, locks, and tx() never go to a replica."
1135
+ }
1136
+ });
1137
+ }
1138
+ function noReplica() {
1139
+ throw new OkmError2("OKM1843", "No replica is eligible. This call does not read the primary.", {
1140
+ fix: {
1141
+ summary: "Configure a replica or drop the route. This call does not read the primary."
1142
+ }
1143
+ });
1144
+ }
1145
+ function fallbackRefused(why) {
1146
+ throw new OkmError2("OKM1844", `No replica is eligible (${why}) and fallback is error.`, {
1147
+ fix: {
1148
+ summary: "Restore a replica, or set fallback to primary if the primary should absorb the read."
1149
+ }
1150
+ });
1151
+ }
1152
+ function truth(value) {
1153
+ return value === "t" || value === "true" || value === "1";
1154
+ }
1155
+ function fields(value) {
1156
+ return value;
1157
+ }
1158
+ var topology_default = connectTopology;
1159
+ export {
1160
+ topology_default as default
1161
+ };