totalum-sdk 0.1.0-dev.11 → 0.1.0-dev.12
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/dist/d1/index.d.ts +2 -1
- package/dist/d1/session.js +60 -6
- package/package.json +1 -1
package/dist/d1/index.d.ts
CHANGED
|
@@ -90,7 +90,8 @@ declare const READ_YOUR_WRITES_COOKIE = "__tlm_d1_w";
|
|
|
90
90
|
* Runs one request of a Worker with a replicated D1 (A3.23, research 46 §6.3): a `GET`/`HEAD` reads through a
|
|
91
91
|
* `first-unconstrained` session — the nearest replica — unless the browser wrote in the last 10 s; any other method
|
|
92
92
|
* uses the plain binding (writes go to the primary; measured faster than any session) and its response sets the short
|
|
93
|
-
* cookie that sends that browser's next reads to the primary.
|
|
93
|
+
* cookie that sends that browser's next reads to the primary. A replica read that takes over `REPLICA_READ_TIMEOUT_MS`
|
|
94
|
+
* is answered by the primary instead, and the rest of that request reads the primary. `run` receives the env to hand to the app, whose
|
|
94
95
|
* `totalumD1()` reads `TOTALUM_DB` from it on every statement. No D1 binding (Turso, a laptop): `run(env)` unchanged.
|
|
95
96
|
*/
|
|
96
97
|
declare function withTotalumSession<E extends object>(request: Request, env: E, run: (env: E) => Promise<Response>): Promise<Response>;
|
package/dist/d1/session.js
CHANGED
|
@@ -3,11 +3,21 @@ export const READ_YOUR_WRITES_COOKIE = '__tlm_d1_w';
|
|
|
3
3
|
/** Replicas lag the primary by 30–75 ms (Cloudflare); 10 s leaves a wide margin (research 46 §6.3). */
|
|
4
4
|
const WINDOW_SECONDS = 10;
|
|
5
5
|
const READS = new Set(['GET', 'HEAD']);
|
|
6
|
+
/**
|
|
7
|
+
* A replica read slower than this is re-run on the primary, and the rest of the request reads the primary. Replicas
|
|
8
|
+
* answer in tens of ms; measured stalls last 10 s or more while the primary stays fast (ui-evidence/preview-hang).
|
|
9
|
+
* D1's Sessions API documents no timeout or fallback of its own.
|
|
10
|
+
*/
|
|
11
|
+
export const REPLICA_READ_TIMEOUT_MS = 2000;
|
|
12
|
+
/** Only a `SELECT` is re-run: a write that stalled may still land, and running it twice would duplicate it. */
|
|
13
|
+
const PURE_READ = /^\s*select\b/i;
|
|
14
|
+
const LATE = Symbol('late');
|
|
6
15
|
/**
|
|
7
16
|
* Runs one request of a Worker with a replicated D1 (A3.23, research 46 §6.3): a `GET`/`HEAD` reads through a
|
|
8
17
|
* `first-unconstrained` session — the nearest replica — unless the browser wrote in the last 10 s; any other method
|
|
9
18
|
* uses the plain binding (writes go to the primary; measured faster than any session) and its response sets the short
|
|
10
|
-
* cookie that sends that browser's next reads to the primary.
|
|
19
|
+
* cookie that sends that browser's next reads to the primary. A replica read that takes over `REPLICA_READ_TIMEOUT_MS`
|
|
20
|
+
* is answered by the primary instead, and the rest of that request reads the primary. `run` receives the env to hand to the app, whose
|
|
11
21
|
* `totalumD1()` reads `TOTALUM_DB` from it on every statement. No D1 binding (Turso, a laptop): `run(env)` unchanged.
|
|
12
22
|
*/
|
|
13
23
|
export async function withTotalumSession(request, env, run) {
|
|
@@ -26,13 +36,57 @@ export async function withTotalumSession(request, env, run) {
|
|
|
26
36
|
const cookies = request.headers.get('cookie') ?? '';
|
|
27
37
|
if (new RegExp(`(?:^|;\\s*)${READ_YOUR_WRITES_COOKIE}=`).test(cookies))
|
|
28
38
|
return run(env);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
return run({ ...env, TOTALUM_DB: boundedReplica(binding, binding.withSession('first-unconstrained')) });
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The request's database on the nearest replica, each pure read bounded by `REPLICA_READ_TIMEOUT_MS`: past it, the read
|
|
43
|
+
* runs on the primary (the stalled replica call is abandoned) and so does every later statement of this request.
|
|
44
|
+
*/
|
|
45
|
+
function boundedReplica(binding, session) {
|
|
46
|
+
let primary = false;
|
|
47
|
+
const on = () => (primary ? binding : session);
|
|
48
|
+
async function read(sqls, call) {
|
|
49
|
+
if (primary || !sqls.every((sql) => PURE_READ.test(sql)))
|
|
50
|
+
return call(on());
|
|
51
|
+
let timer;
|
|
52
|
+
const late = new Promise((resolve) => {
|
|
53
|
+
timer = setTimeout(() => {
|
|
54
|
+
resolve(LATE);
|
|
55
|
+
}, REPLICA_READ_TIMEOUT_MS);
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
const out = await Promise.race([call(session), late]);
|
|
59
|
+
if (out !== LATE)
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
}
|
|
65
|
+
primary = true;
|
|
66
|
+
console.warn(JSON.stringify({ event: 'd1_replica_read_timeout', ms: REPLICA_READ_TIMEOUT_MS }));
|
|
67
|
+
return call(binding);
|
|
68
|
+
}
|
|
69
|
+
const statement = (sql, params = []) => {
|
|
70
|
+
const real = (db) => db.prepare(sql).bind(...params);
|
|
71
|
+
const bounded = (method) => (...args) => read([sql], (db) => real(db)[method](...args));
|
|
72
|
+
return {
|
|
73
|
+
sql,
|
|
74
|
+
real,
|
|
75
|
+
bind: (...values) => statement(sql, values),
|
|
76
|
+
first: bounded('first'),
|
|
77
|
+
run: bounded('run'),
|
|
78
|
+
all: bounded('all'),
|
|
79
|
+
raw: bounded('raw'),
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
prepare: (sql) => statement(sql),
|
|
84
|
+
batch: (statements) => {
|
|
85
|
+
const mine = statements;
|
|
86
|
+
return read(mine.map((s) => s.sql), (db) => db.batch(mine.map((s) => s.real(db))));
|
|
87
|
+
},
|
|
33
88
|
exec: (sql) => binding.exec(sql),
|
|
34
89
|
dump: () => binding.dump(),
|
|
35
90
|
withSession: (c) => binding.withSession(c),
|
|
36
91
|
};
|
|
37
|
-
return run({ ...env, TOTALUM_DB: replicaDb });
|
|
38
92
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "totalum-sdk",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.12",
|
|
4
4
|
"description": "The SDK generated Totalum apps import: every integration namespace over SDK-API (totalum-sdk, or one subpath each: /files, /ai, /web, …) and totalum-sdk/d1, the D1 driver",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|