@dee-wan/cache 1.1.0 → 2.0.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.
- package/README.md +31 -1
- package/dist/{chunk-MOJBHU3S.js → chunk-LAXBP5DQ.js} +72 -173
- package/dist/chunk-LAXBP5DQ.js.map +1 -0
- package/dist/{chunk-ZREX7LLC.js → chunk-M4UOCIDQ.js} +18 -6
- package/dist/chunk-M4UOCIDQ.js.map +1 -0
- package/dist/chunk-NFBMWKY5.js +54 -0
- package/dist/chunk-NFBMWKY5.js.map +1 -0
- package/dist/{chunk-HGAUU4EU.js → chunk-O3KHMFHX.js} +51 -14
- package/dist/chunk-O3KHMFHX.js.map +1 -0
- package/dist/chunk-WJNCFFF3.js +637 -0
- package/dist/chunk-WJNCFFF3.js.map +1 -0
- package/dist/{chunk-OOPPXK7Q.js → chunk-WKZ7TKGM.js} +3 -54
- package/dist/chunk-WKZ7TKGM.js.map +1 -0
- package/dist/chunk-ZBYAKDNZ.js +45 -0
- package/dist/chunk-ZBYAKDNZ.js.map +1 -0
- package/dist/client.d.ts +14 -1
- package/dist/client.js +37 -1
- package/dist/client.js.map +1 -1
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/index.js +3 -3
- package/dist/d1/index.d.ts +18 -2
- package/dist/d1/index.js +33 -11
- package/dist/d1/index.js.map +1 -1
- package/dist/d1/response-lease.js +10 -8
- package/dist/d1/response-lease.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/response-lease.js +7 -2
- package/dist/{types-D-Gd11to.d.ts → types-CtErcz2a.d.ts} +13 -1
- package/package.json +1 -1
- package/dist/chunk-D2EESWMC.js +0 -19
- package/dist/chunk-D2EESWMC.js.map +0 -1
- package/dist/chunk-HGAUU4EU.js.map +0 -1
- package/dist/chunk-MOJBHU3S.js.map +0 -1
- package/dist/chunk-OOPPXK7Q.js.map +0 -1
- package/dist/chunk-ZREX7LLC.js.map +0 -1
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import { CacheUnavailableError } from './chunk-M4UOCIDQ.js';
|
|
2
|
+
import { parseD1Bookmarks, D1_BOOKMARK_HEADER, serializeD1Bookmarks, decodeD1Bookmarks, mergeD1Bookmarks } from './chunk-ZBYAKDNZ.js';
|
|
3
|
+
import { boundedD1Store } from './chunk-WKZ7TKGM.js';
|
|
4
|
+
import { isD1BindingName } from './chunk-AENVFT2S.js';
|
|
5
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
6
|
+
|
|
7
|
+
// src/d1/adapter.ts
|
|
8
|
+
var fence = ["g"];
|
|
9
|
+
var lexemes = /\s+|--[^\n]*|\/\*[\s\S]*?(?:\*\/|$)|'(?:[^']|'')*'?|`(?:[^`]|``)*`?|"(?:[^"]|"")*"?|\[[^\]]*\]?|[A-Za-z_][A-Za-z0-9_$]*|[\s\S]/g;
|
|
10
|
+
var safePragmas = /* @__PURE__ */ new Set(["database_list", "table_list", "table_info", "table_xinfo", "index_list", "index_info", "index_xinfo", "foreign_key_list"]);
|
|
11
|
+
function operationOf(tokens, start = 0) {
|
|
12
|
+
const word = (index2) => tokens[index2]?.toLowerCase();
|
|
13
|
+
if (word(start) !== "with") return word(start);
|
|
14
|
+
let index = start + 1;
|
|
15
|
+
if (word(index) === "recursive") index++;
|
|
16
|
+
const group = () => {
|
|
17
|
+
if (tokens[index] !== "(") return false;
|
|
18
|
+
let depth = 0;
|
|
19
|
+
do {
|
|
20
|
+
if (tokens[index] === "(") depth++;
|
|
21
|
+
if (tokens[index] === ")") depth--;
|
|
22
|
+
index++;
|
|
23
|
+
} while (index < tokens.length && depth > 0);
|
|
24
|
+
return depth === 0;
|
|
25
|
+
};
|
|
26
|
+
while (index < tokens.length) {
|
|
27
|
+
if (!/^(?:[A-Za-z_][A-Za-z0-9_$]*|`[^`]+`|"[^"]+"|\[[^\]]+\])$/.test(tokens[index] ?? "")) return void 0;
|
|
28
|
+
index++;
|
|
29
|
+
if (tokens[index] === "(" && !group()) return void 0;
|
|
30
|
+
if (word(index++) !== "as") return void 0;
|
|
31
|
+
if (word(index) === "not") index++;
|
|
32
|
+
if (word(index) === "materialized") index++;
|
|
33
|
+
if (tokens[index] !== "(" || !["select", "with"].includes(word(index + 1) ?? "")) return void 0;
|
|
34
|
+
if (!group()) return void 0;
|
|
35
|
+
if (tokens[index] !== ",") return word(index);
|
|
36
|
+
index++;
|
|
37
|
+
}
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
function isReadonly(sql) {
|
|
41
|
+
const tokens = [...sql.matchAll(lexemes)].map(([token = ""]) => token).filter((token) => !/^(?:\s|--|\/\*)/.test(token));
|
|
42
|
+
if (tokens.at(-1) === ";") tokens.pop();
|
|
43
|
+
const first = tokens[0]?.toLowerCase();
|
|
44
|
+
const pragmaName = first === "pragma" ? (tokens[2] === "." ? tokens[3] : tokens[1])?.toLowerCase() : void 0;
|
|
45
|
+
const readonlyPragma = first === "pragma" && pragmaName !== void 0 && safePragmas.has(pragmaName) && !tokens.includes("=");
|
|
46
|
+
return !tokens.includes(";") && (operationOf(tokens) === "select" || readonlyPragma);
|
|
47
|
+
}
|
|
48
|
+
function needsWriteFence(sql, unrelatedWrites) {
|
|
49
|
+
const tokens = [...sql.matchAll(lexemes)].map(([token = ""]) => token).filter((token) => !/^(?:\s|--|\/\*)/.test(token));
|
|
50
|
+
if (tokens.at(-1) === ";") tokens.pop();
|
|
51
|
+
if (tokens.includes(";")) return true;
|
|
52
|
+
let index = 0;
|
|
53
|
+
const word = () => tokens[index]?.toLowerCase();
|
|
54
|
+
const operation = word();
|
|
55
|
+
if (operation !== "insert" && operation !== "update" && operation !== "delete") return true;
|
|
56
|
+
index++;
|
|
57
|
+
if ((operation === "insert" || operation === "update") && word() === "or") {
|
|
58
|
+
index++;
|
|
59
|
+
if (!["rollback", "abort", "replace", "fail", "ignore"].includes(word() ?? "")) return true;
|
|
60
|
+
index++;
|
|
61
|
+
}
|
|
62
|
+
if (operation === "insert") {
|
|
63
|
+
if (word() !== "into") return true;
|
|
64
|
+
index++;
|
|
65
|
+
}
|
|
66
|
+
if (operation === "delete") {
|
|
67
|
+
if (word() !== "from") return true;
|
|
68
|
+
index++;
|
|
69
|
+
}
|
|
70
|
+
const identifier = (token) => {
|
|
71
|
+
if (!token) return null;
|
|
72
|
+
if (/^[A-Za-z_][A-Za-z0-9_$]*$/.test(token)) return token.toLowerCase();
|
|
73
|
+
if (/^"[A-Za-z_][A-Za-z0-9_$]*"$/.test(token) || /^`[A-Za-z_][A-Za-z0-9_$]*`$/.test(token) || /^\[[A-Za-z_][A-Za-z0-9_$]*\]$/.test(token)) return token.slice(1, -1).toLowerCase();
|
|
74
|
+
return null;
|
|
75
|
+
};
|
|
76
|
+
let target = identifier(tokens[index++]);
|
|
77
|
+
if (tokens[index] === ".") {
|
|
78
|
+
if (target !== "main") return true;
|
|
79
|
+
index++;
|
|
80
|
+
target = identifier(tokens[index++]);
|
|
81
|
+
}
|
|
82
|
+
if (!target || !unrelatedWrites.has(target)) return true;
|
|
83
|
+
if (operation === "update" && word() !== "set") return true;
|
|
84
|
+
if (operation === "delete" && !["where", "returning"].includes(word() ?? "") && index !== tokens.length) return true;
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
function fenceD1Writes(db, options) {
|
|
88
|
+
const { generation, policy } = options;
|
|
89
|
+
const store = boundedD1Store(options.store);
|
|
90
|
+
const unrelatedWrites = new Set(policy.unfencedWriteTables.map((name) => name.toLowerCase()));
|
|
91
|
+
if (!generation) throw new TypeError("cache generation is required");
|
|
92
|
+
const statements = /* @__PURE__ */ new WeakMap();
|
|
93
|
+
const withWrite = async (operation) => {
|
|
94
|
+
const token = crypto.randomUUID();
|
|
95
|
+
try {
|
|
96
|
+
await store.beginWrite({ fences: fence, generation, leaseMs: 6e5, token });
|
|
97
|
+
} catch (error) {
|
|
98
|
+
await store.endWrite({ fences: fence, token }).catch(() => void 0);
|
|
99
|
+
throw new CacheUnavailableError(error);
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
return await operation();
|
|
103
|
+
} finally {
|
|
104
|
+
await store.endWrite({ fences: fence, token }).catch(() => void 0);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const wrapStatement = (raw, sql) => {
|
|
108
|
+
const requiresFence = !isReadonly(sql) && needsWriteFence(sql, unrelatedWrites);
|
|
109
|
+
const execute = (operation) => requiresFence ? withWrite(operation) : operation();
|
|
110
|
+
function rawRows(rawOptions) {
|
|
111
|
+
return execute(() => rawOptions?.columnNames === true ? raw.raw({ columnNames: true }) : raw.raw(rawOptions?.columnNames === false ? { columnNames: false } : void 0));
|
|
112
|
+
}
|
|
113
|
+
const wrapped = {
|
|
114
|
+
bind: (...next) => wrapStatement(raw.bind(...next), sql),
|
|
115
|
+
first: (column) => execute(() => column === void 0 ? raw.first() : raw.first(column)),
|
|
116
|
+
all: () => execute(() => raw.all()),
|
|
117
|
+
run: () => execute(() => raw.run()),
|
|
118
|
+
raw: rawRows
|
|
119
|
+
};
|
|
120
|
+
statements.set(wrapped, { raw, fence: requiresFence });
|
|
121
|
+
return wrapped;
|
|
122
|
+
};
|
|
123
|
+
const batch = (target, items) => {
|
|
124
|
+
const resolved = items.map((item) => statements.get(item));
|
|
125
|
+
const raw = items.map((item, index) => resolved[index]?.raw ?? item);
|
|
126
|
+
const operation = () => target.batch(raw);
|
|
127
|
+
return resolved.every((item) => item !== void 0 && !item.fence) ? operation() : withWrite(operation);
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
prepare: (sql) => wrapStatement(db.prepare(sql), sql),
|
|
131
|
+
batch: (items) => batch(db, items),
|
|
132
|
+
exec: (sql) => isReadonly(sql) || !needsWriteFence(sql, unrelatedWrites) ? db.exec(sql) : withWrite(() => db.exec(sql)),
|
|
133
|
+
dump: () => db.dump(),
|
|
134
|
+
withSession: (constraint) => {
|
|
135
|
+
const session = db.withSession(constraint);
|
|
136
|
+
return {
|
|
137
|
+
prepare: (sql) => wrapStatement(session.prepare(sql), sql),
|
|
138
|
+
batch: (items) => batch(session, items),
|
|
139
|
+
getBookmark: () => session.getBookmark()
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/d1/request-freshness.ts
|
|
146
|
+
var validFreshnessHeader = (name) => /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name);
|
|
147
|
+
var hasFreshnessCookie = (request, name) => (request.headers.get("cookie") ?? "").split(";").some((part) => part.split("=", 1)[0]?.trim() === name);
|
|
148
|
+
var hasFreshnessGeneration = (request, name) => {
|
|
149
|
+
const matches = (request.headers.get("cookie") ?? "").split(";").map((part) => part.trim()).filter((part) => part.split("=", 1)[0]?.trim() === name);
|
|
150
|
+
const value = matches[0]?.slice(matches[0].indexOf("=") + 1).trim() ?? "";
|
|
151
|
+
return matches.length === 1 && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
152
|
+
};
|
|
153
|
+
var hasCacheBypass = (request) => (request.headers.get("cache-control") ?? "").split(",").some((part) => /^\s*(?:no-cache|no-store)(?:\s*=|\s*$)/i.test(part));
|
|
154
|
+
function regionalD1MetadataTtl(request, policy) {
|
|
155
|
+
if (request.method !== "GET" || hasFreshnessCookie(request, policy.freshnessCookie)) return 0;
|
|
156
|
+
if (policy.freshnessHeader !== void 0 && (!validFreshnessHeader(policy.freshnessHeader) || request.headers.has(policy.freshnessHeader))) return 0;
|
|
157
|
+
if (!policy.ignoreRequestCacheControl && hasCacheBypass(request)) return 0;
|
|
158
|
+
return 5e3;
|
|
159
|
+
}
|
|
160
|
+
var storage = new AsyncLocalStorage();
|
|
161
|
+
var tracking = new AsyncLocalStorage();
|
|
162
|
+
var primarySource = new AsyncLocalStorage();
|
|
163
|
+
var admissionScope = new AsyncLocalStorage();
|
|
164
|
+
var prismaScope = new AsyncLocalStorage();
|
|
165
|
+
var trackerGroups = /* @__PURE__ */ new WeakMap();
|
|
166
|
+
var ambiguousTrackers = /* @__PURE__ */ new WeakSet();
|
|
167
|
+
var infos = /* @__PURE__ */ new WeakMap();
|
|
168
|
+
var facades = /* @__PURE__ */ new WeakMap();
|
|
169
|
+
var queues = /* @__PURE__ */ new Map();
|
|
170
|
+
var admitted = /* @__PURE__ */ new Map();
|
|
171
|
+
var prismaFacades = /* @__PURE__ */ new WeakMap();
|
|
172
|
+
function registerD1SessionAlias(alias, original) {
|
|
173
|
+
const info = infos.get(original);
|
|
174
|
+
if (info) infos.set(alias, info);
|
|
175
|
+
}
|
|
176
|
+
function runPrimaryD1SessionSource(operation) {
|
|
177
|
+
return storage.getStore() ? primarySource.run(/* @__PURE__ */ new Set(), operation) : operation();
|
|
178
|
+
}
|
|
179
|
+
function runD1PrismaScope(operation) {
|
|
180
|
+
return prismaScope.run({}, operation);
|
|
181
|
+
}
|
|
182
|
+
function addTracker(group, tracker) {
|
|
183
|
+
if (tracker) {
|
|
184
|
+
group.trackers.set(tracker, (group.trackers.get(tracker) ?? 0) + 1);
|
|
185
|
+
if (group.ambiguous) ambiguousTrackers.add(tracker);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function removeTracker(group, tracker) {
|
|
189
|
+
if (!tracker) return;
|
|
190
|
+
const count = group.trackers.get(tracker) ?? 0;
|
|
191
|
+
if (count <= 1) group.trackers.delete(tracker);
|
|
192
|
+
else group.trackers.set(tracker, count - 1);
|
|
193
|
+
}
|
|
194
|
+
async function runD1SessionOperation(database, operation) {
|
|
195
|
+
const info = infos.get(database);
|
|
196
|
+
const current = storage.getStore();
|
|
197
|
+
const context = current && !current.closed ? current : void 0;
|
|
198
|
+
if (!info) return operation();
|
|
199
|
+
const token = context ?? prismaScope.getStore();
|
|
200
|
+
let gate = queues.get(info.id);
|
|
201
|
+
if (!gate) {
|
|
202
|
+
gate = { waiting: [] };
|
|
203
|
+
queues.set(info.id, gate);
|
|
204
|
+
}
|
|
205
|
+
const scope = gate;
|
|
206
|
+
const active = scope.active;
|
|
207
|
+
const tail = scope.waiting.at(-1);
|
|
208
|
+
const group = token && tail?.token === token ? tail : token && !tail && active?.token === token ? active : { token, context, pending: 0, waiting: [], tracker: {}, trackers: /* @__PURE__ */ new Map(), ambiguous: false };
|
|
209
|
+
const startGroup = (next) => {
|
|
210
|
+
scope.active = next;
|
|
211
|
+
admitted.set(info.id, next);
|
|
212
|
+
trackerGroups.set(next.tracker, next);
|
|
213
|
+
for (const start of next.waiting.splice(0)) start();
|
|
214
|
+
};
|
|
215
|
+
let timer;
|
|
216
|
+
try {
|
|
217
|
+
await new Promise((resolve, reject) => {
|
|
218
|
+
let cancelled = false;
|
|
219
|
+
const start = () => {
|
|
220
|
+
if (cancelled) return;
|
|
221
|
+
if (group.pending > 0) {
|
|
222
|
+
group.ambiguous = true;
|
|
223
|
+
for (const tracker2 of group.trackers.keys()) ambiguousTrackers.add(tracker2);
|
|
224
|
+
}
|
|
225
|
+
group.pending++;
|
|
226
|
+
resolve();
|
|
227
|
+
};
|
|
228
|
+
if (group === active) start();
|
|
229
|
+
else {
|
|
230
|
+
group.waiting.push(start);
|
|
231
|
+
if (group !== tail) {
|
|
232
|
+
if (!active) startGroup(group);
|
|
233
|
+
else scope.waiting.push(group);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (!group.pending) timer = setTimeout(() => {
|
|
237
|
+
cancelled = true;
|
|
238
|
+
const index = group.waiting.indexOf(start);
|
|
239
|
+
if (index >= 0) group.waiting.splice(index, 1);
|
|
240
|
+
if (!group.pending && !group.waiting.length) {
|
|
241
|
+
const position = scope.waiting.indexOf(group);
|
|
242
|
+
if (position >= 0) scope.waiting.splice(position, 1);
|
|
243
|
+
}
|
|
244
|
+
reject(new Error("D1 Prisma admission timed out"));
|
|
245
|
+
}, 3e4);
|
|
246
|
+
});
|
|
247
|
+
} finally {
|
|
248
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
249
|
+
}
|
|
250
|
+
const tracker = tracking.getStore();
|
|
251
|
+
const primary = primarySource.getStore();
|
|
252
|
+
if (primary && !group.primary) group.primary = primary;
|
|
253
|
+
addTracker(group, tracker);
|
|
254
|
+
try {
|
|
255
|
+
return await admissionScope.run({ group, tracker }, operation);
|
|
256
|
+
} finally {
|
|
257
|
+
removeTracker(group, tracker);
|
|
258
|
+
group.pending--;
|
|
259
|
+
if (!group.pending) {
|
|
260
|
+
const next = scope.waiting.shift();
|
|
261
|
+
if (next) startGroup(next);
|
|
262
|
+
else {
|
|
263
|
+
scope.active = void 0;
|
|
264
|
+
admitted.delete(info.id);
|
|
265
|
+
queues.delete(info.id);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function prismaD1SessionDatabase(database) {
|
|
271
|
+
const existing = prismaFacades.get(database);
|
|
272
|
+
if (existing) return existing;
|
|
273
|
+
const info = infos.get(database);
|
|
274
|
+
if (!info) return database;
|
|
275
|
+
const invoke = (operation) => {
|
|
276
|
+
const admission = admitted.get(info.id);
|
|
277
|
+
return storage.run(admission?.context, () => admission ? tracking.run(admission.tracker, () => admission.primary ? primarySource.run(admission.primary, operation) : operation()) : operation());
|
|
278
|
+
};
|
|
279
|
+
const statements = /* @__PURE__ */ new WeakMap();
|
|
280
|
+
const wrapStatement = (statement) => {
|
|
281
|
+
function raw(options) {
|
|
282
|
+
return invoke(() => options?.columnNames ? statement.raw({ columnNames: true }) : statement.raw());
|
|
283
|
+
}
|
|
284
|
+
const wrapped2 = {
|
|
285
|
+
bind: (...values) => invoke(() => wrapStatement(statement.bind(...values))),
|
|
286
|
+
first: (column) => invoke(() => column === void 0 ? statement.first() : statement.first(column)),
|
|
287
|
+
all: () => invoke(() => statement.all()),
|
|
288
|
+
run: () => invoke(() => statement.run()),
|
|
289
|
+
raw
|
|
290
|
+
};
|
|
291
|
+
statements.set(wrapped2, statement);
|
|
292
|
+
return wrapped2;
|
|
293
|
+
};
|
|
294
|
+
const wrapSession = (session) => ({
|
|
295
|
+
prepare: (sql) => invoke(() => wrapStatement(session.prepare(sql))),
|
|
296
|
+
batch: (items) => invoke(() => session.batch(items.map((item) => statements.get(item) ?? item))),
|
|
297
|
+
getBookmark: () => invoke(() => session.getBookmark())
|
|
298
|
+
});
|
|
299
|
+
const wrapped = {
|
|
300
|
+
prepare: (sql) => invoke(() => wrapStatement(database.prepare(sql))),
|
|
301
|
+
batch: (items) => invoke(() => database.batch(items.map((item) => statements.get(item) ?? item))),
|
|
302
|
+
withSession: (constraint) => invoke(() => wrapSession(database.withSession(constraint))),
|
|
303
|
+
exec: (sql) => invoke(() => database.exec(sql)),
|
|
304
|
+
dump: () => invoke(() => database.dump())
|
|
305
|
+
};
|
|
306
|
+
infos.set(wrapped, info);
|
|
307
|
+
prismaFacades.set(database, wrapped);
|
|
308
|
+
return wrapped;
|
|
309
|
+
}
|
|
310
|
+
function contextFor() {
|
|
311
|
+
const context = storage.getStore();
|
|
312
|
+
return context && !context.closed ? context : void 0;
|
|
313
|
+
}
|
|
314
|
+
function sessionFor(info, context, write) {
|
|
315
|
+
let session = context.sessions.get(info.id);
|
|
316
|
+
const primary = primarySource.getStore();
|
|
317
|
+
const reanchor = !!primary && !primary.has(info.id);
|
|
318
|
+
if (reanchor) {
|
|
319
|
+
primary.add(info.id);
|
|
320
|
+
session = void 0;
|
|
321
|
+
}
|
|
322
|
+
if (!session) {
|
|
323
|
+
session = info.db.withSession(reanchor || write || info.primary || context.primary || context.primaryIds.has(info.id) ? "first-primary" : context.input[info.id] ?? "first-unconstrained");
|
|
324
|
+
context.sessions.set(info.id, session);
|
|
325
|
+
}
|
|
326
|
+
return session;
|
|
327
|
+
}
|
|
328
|
+
function capture(info, context, session) {
|
|
329
|
+
const bookmark = session.getBookmark();
|
|
330
|
+
if (bookmark && !context.closed) {
|
|
331
|
+
const combined = { ...context.input };
|
|
332
|
+
if (!mergeD1Bookmarks(combined, context.bookmarks) || !mergeD1Bookmarks(combined, { [info.id]: bookmark })) throw new RangeError("D1 bookmark capacity exceeded");
|
|
333
|
+
mergeD1Bookmarks(context.bookmarks, { [info.id]: bookmark });
|
|
334
|
+
const tracker = tracking.getStore();
|
|
335
|
+
if (tracker) {
|
|
336
|
+
mergeD1Bookmarks(tracker, { [info.id]: bookmark });
|
|
337
|
+
const group = trackerGroups.get(tracker);
|
|
338
|
+
if (group) for (const target of group.trackers.keys()) mergeD1Bookmarks(target, { [info.id]: bookmark });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function runIsolatedD1Session(request, operation) {
|
|
343
|
+
const parent = storage.getStore();
|
|
344
|
+
if (!parent) return operation();
|
|
345
|
+
const input = Object.fromEntries(Object.entries(parseD1Bookmarks(request.headers.get(D1_BOOKMARK_HEADER))).filter(([id]) => parent.allowedIds.has(id)));
|
|
346
|
+
const context = { input, sessions: /* @__PURE__ */ new Map(), bookmarks: {}, primaryIds: /* @__PURE__ */ new Set(), allowedIds: parent.allowedIds, operations: /* @__PURE__ */ new Map(), primary: false, closed: false };
|
|
347
|
+
return storage.run(context, () => tracking.run({}, async () => {
|
|
348
|
+
try {
|
|
349
|
+
return await operation();
|
|
350
|
+
} finally {
|
|
351
|
+
context.closed = true;
|
|
352
|
+
}
|
|
353
|
+
}));
|
|
354
|
+
}
|
|
355
|
+
function d1SessionWarmupRequest(request, response) {
|
|
356
|
+
const bookmarks = responseD1SessionBookmarks(response.headers, tracking.getStore() ?? d1SessionBookmarks());
|
|
357
|
+
if (!bookmarks) return request;
|
|
358
|
+
const headers = new Headers(request.headers);
|
|
359
|
+
const encoded = serializeD1Bookmarks(bookmarks);
|
|
360
|
+
if (encoded) headers.set(D1_BOOKMARK_HEADER, encoded);
|
|
361
|
+
return new Request(request, { headers });
|
|
362
|
+
}
|
|
363
|
+
function d1SessionBookmarkFloor() {
|
|
364
|
+
const context = storage.getStore();
|
|
365
|
+
return context && !context.closed ? { ...context.input } : void 0;
|
|
366
|
+
}
|
|
367
|
+
function d1SessionBookmarks() {
|
|
368
|
+
const context = storage.getStore();
|
|
369
|
+
return context && !context.closed ? { ...context.bookmarks } : void 0;
|
|
370
|
+
}
|
|
371
|
+
function varyD1Bookmarks(headers) {
|
|
372
|
+
const vary = headers.get("vary");
|
|
373
|
+
const names = vary ? vary.split(",").map((name) => name.trim()).filter(Boolean) : [];
|
|
374
|
+
if (!names.some((name) => name === "*" || name.toLowerCase() === D1_BOOKMARK_HEADER.toLowerCase())) headers.set("vary", [...names, D1_BOOKMARK_HEADER].join(", "));
|
|
375
|
+
}
|
|
376
|
+
function responseD1SessionBookmarks(headers, source) {
|
|
377
|
+
const context = storage.getStore();
|
|
378
|
+
if (!context || context.closed) return void 0;
|
|
379
|
+
const result = { ...context.input };
|
|
380
|
+
const upstream = decodeD1Bookmarks(headers.get(D1_BOOKMARK_HEADER));
|
|
381
|
+
if (upstream && !mergeD1Bookmarks(result, Object.fromEntries(Object.entries(upstream).filter(([id]) => context.allowedIds.has(id))))) throw new RangeError("D1 bookmark capacity exceeded");
|
|
382
|
+
if (source && !mergeD1Bookmarks(result, source)) throw new RangeError("D1 bookmark capacity exceeded");
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
function d1SessionResponseBookmarks() {
|
|
386
|
+
const context = storage.getStore();
|
|
387
|
+
if (!context || context.closed) return void 0;
|
|
388
|
+
const result = { ...context.input };
|
|
389
|
+
mergeD1Bookmarks(result, tracking.getStore() ?? context.bookmarks);
|
|
390
|
+
return result;
|
|
391
|
+
}
|
|
392
|
+
function advanceD1SessionBookmarks(bookmarks) {
|
|
393
|
+
const context = storage.getStore();
|
|
394
|
+
if (context && !context.closed) {
|
|
395
|
+
const filtered = Object.fromEntries(Object.entries(bookmarks).filter(([id]) => context.allowedIds.has(id)));
|
|
396
|
+
const combined = { ...context.input };
|
|
397
|
+
if (!mergeD1Bookmarks(combined, context.bookmarks) || !mergeD1Bookmarks(combined, filtered)) throw new RangeError("D1 bookmark capacity exceeded");
|
|
398
|
+
mergeD1Bookmarks(context.bookmarks, filtered);
|
|
399
|
+
const tracker = tracking.getStore();
|
|
400
|
+
if (tracker) mergeD1Bookmarks(tracker, filtered);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
async function trackD1SessionSource(operation) {
|
|
404
|
+
const context = storage.getStore();
|
|
405
|
+
if (!context || context.closed) return { value: await operation() };
|
|
406
|
+
const parent = tracking.getStore();
|
|
407
|
+
const tracker = {};
|
|
408
|
+
const admission = admissionScope.getStore();
|
|
409
|
+
const previous = admission?.tracker;
|
|
410
|
+
if (admission) {
|
|
411
|
+
removeTracker(admission.group, previous);
|
|
412
|
+
admission.tracker = tracker;
|
|
413
|
+
addTracker(admission.group, tracker);
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
const value = await tracking.run(tracker, operation);
|
|
417
|
+
if (parent) mergeD1Bookmarks(parent, tracker);
|
|
418
|
+
return { value, bookmarks: tracker };
|
|
419
|
+
} finally {
|
|
420
|
+
if (admission) {
|
|
421
|
+
removeTracker(admission.group, tracker);
|
|
422
|
+
admission.tracker = previous;
|
|
423
|
+
addTracker(admission.group, previous);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function serialSession(context, id, operation) {
|
|
428
|
+
const previous = context.operations.get(id) ?? Promise.resolve();
|
|
429
|
+
let release;
|
|
430
|
+
const next = new Promise((resolve) => {
|
|
431
|
+
release = resolve;
|
|
432
|
+
});
|
|
433
|
+
const tail = previous.then(() => next);
|
|
434
|
+
context.operations.set(id, tail);
|
|
435
|
+
await previous;
|
|
436
|
+
try {
|
|
437
|
+
return await operation();
|
|
438
|
+
} finally {
|
|
439
|
+
release();
|
|
440
|
+
if (context.operations.get(id) === tail) context.operations.delete(id);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function wrapD1SessionCacheValue(value, source) {
|
|
444
|
+
const bookmarks = source ?? tracking.getStore() ?? d1SessionBookmarks();
|
|
445
|
+
if (bookmarks && (ambiguousTrackers.has(bookmarks) || admissionScope.getStore()?.group.ambiguous)) return { d1SessionCache: 0 };
|
|
446
|
+
return bookmarks ? { d1SessionCache: 1, bookmarks, value } : value;
|
|
447
|
+
}
|
|
448
|
+
function unwrapD1SessionCacheValue(value, databaseIds) {
|
|
449
|
+
const floor = d1SessionBookmarkFloor();
|
|
450
|
+
if (!floor) return { value };
|
|
451
|
+
if (!value || typeof value !== "object" || Reflect.get(value, "d1SessionCache") !== 1 || !Reflect.has(value, "value")) return null;
|
|
452
|
+
const raw = Reflect.get(value, "bookmarks");
|
|
453
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
454
|
+
const serialized = JSON.stringify(raw);
|
|
455
|
+
const bookmarks = parseD1Bookmarks(serialized);
|
|
456
|
+
if (Object.keys(bookmarks).length !== Object.keys(raw).length) return null;
|
|
457
|
+
if (databaseIds.some((id) => floor[id] !== void 0 && (!bookmarks[id] || bookmarks[id] < floor[id]))) return null;
|
|
458
|
+
return { value: Reflect.get(value, "value"), bookmarks };
|
|
459
|
+
}
|
|
460
|
+
function d1SessionCacheHooks(database) {
|
|
461
|
+
return {
|
|
462
|
+
key: () => d1SessionBookmarkFloor() ? "d1-session-v1" : null,
|
|
463
|
+
read(value) {
|
|
464
|
+
const info = infos.get(database);
|
|
465
|
+
if (d1SessionBookmarkFloor() && !info) return null;
|
|
466
|
+
const cached = unwrapD1SessionCacheValue(value, info ? [info.id] : []);
|
|
467
|
+
if (d1SessionBookmarkFloor() && info && !cached?.bookmarks?.[info.id]) return null;
|
|
468
|
+
if (cached?.bookmarks) advanceD1SessionBookmarks(cached.bookmarks);
|
|
469
|
+
return cached;
|
|
470
|
+
},
|
|
471
|
+
background(operation) {
|
|
472
|
+
const bookmarks = d1SessionResponseBookmarks();
|
|
473
|
+
if (!bookmarks) return operation();
|
|
474
|
+
const encoded = serializeD1Bookmarks(bookmarks);
|
|
475
|
+
const request = new Request("https://d1-session.invalid/", { headers: encoded ? { [D1_BOOKMARK_HEADER]: encoded } : {} });
|
|
476
|
+
return runIsolatedD1Session(request, operation);
|
|
477
|
+
},
|
|
478
|
+
async fill(load) {
|
|
479
|
+
const tracked = await trackD1SessionSource(() => runPrimaryD1SessionSource(load));
|
|
480
|
+
const active = d1SessionBookmarkFloor();
|
|
481
|
+
const info = infos.get(database);
|
|
482
|
+
const cached = active && (!info || !tracked.bookmarks?.[info.id]) ? { d1SessionCache: 0 } : wrapD1SessionCacheValue(tracked.value, tracked.bookmarks);
|
|
483
|
+
return { value: tracked.value, cached };
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function facade(info) {
|
|
488
|
+
const statements = /* @__PURE__ */ new WeakMap();
|
|
489
|
+
const prepare = (sql, values = []) => {
|
|
490
|
+
const execute = async (action) => {
|
|
491
|
+
const context = contextFor();
|
|
492
|
+
if (!context) return action(info.db.prepare(sql).bind(...values));
|
|
493
|
+
return serialSession(context, info.id, async () => {
|
|
494
|
+
const session = sessionFor(info, context, !isReadonly(sql));
|
|
495
|
+
const result2 = await action(session.prepare(sql).bind(...values));
|
|
496
|
+
capture(info, context, session);
|
|
497
|
+
return result2;
|
|
498
|
+
});
|
|
499
|
+
};
|
|
500
|
+
function raw(options) {
|
|
501
|
+
return execute((statement) => options?.columnNames ? statement.raw({ columnNames: true }) : statement.raw());
|
|
502
|
+
}
|
|
503
|
+
const result = {
|
|
504
|
+
bind: (...next) => prepare(sql, next),
|
|
505
|
+
first: (column) => execute((statement) => column === void 0 ? statement.first() : statement.first(column)),
|
|
506
|
+
run: () => execute((statement) => statement.run()),
|
|
507
|
+
all: () => execute((statement) => statement.all()),
|
|
508
|
+
raw
|
|
509
|
+
};
|
|
510
|
+
statements.set(result, { sql, values });
|
|
511
|
+
return result;
|
|
512
|
+
};
|
|
513
|
+
const batch = async (items) => {
|
|
514
|
+
const definitions = items.map((item) => statements.get(item));
|
|
515
|
+
const context = contextFor();
|
|
516
|
+
if (!context) return info.db.batch(items.map((item, index) => {
|
|
517
|
+
const definition = definitions[index];
|
|
518
|
+
return definition ? info.db.prepare(definition.sql).bind(...definition.values) : item;
|
|
519
|
+
}));
|
|
520
|
+
if (definitions.some((item) => !item)) throw new TypeError("D1 batch statements must use the same database facade");
|
|
521
|
+
const execute = async (target, session) => {
|
|
522
|
+
const raw = definitions.map((item) => {
|
|
523
|
+
if (!item) throw new TypeError("Invalid D1 statement");
|
|
524
|
+
return target.prepare(item.sql).bind(...item.values);
|
|
525
|
+
});
|
|
526
|
+
const result = await target.batch(raw);
|
|
527
|
+
if (session && context) capture(info, context, session);
|
|
528
|
+
return result;
|
|
529
|
+
};
|
|
530
|
+
return serialSession(context, info.id, () => {
|
|
531
|
+
const session = sessionFor(info, context, definitions.some((item) => item && !isReadonly(item.sql)));
|
|
532
|
+
return execute(session, session);
|
|
533
|
+
});
|
|
534
|
+
};
|
|
535
|
+
const database = {
|
|
536
|
+
prepare,
|
|
537
|
+
batch,
|
|
538
|
+
exec: async (sql) => {
|
|
539
|
+
const context = contextFor();
|
|
540
|
+
if (!context) return info.db.exec(sql);
|
|
541
|
+
return serialSession(context, info.id, async () => {
|
|
542
|
+
const result = await info.db.exec(sql);
|
|
543
|
+
const session = info.db.withSession("first-primary");
|
|
544
|
+
await session.prepare("SELECT 1").first();
|
|
545
|
+
context.sessions.set(info.id, session);
|
|
546
|
+
capture(info, context, session);
|
|
547
|
+
return result;
|
|
548
|
+
});
|
|
549
|
+
},
|
|
550
|
+
dump: () => info.db.dump(),
|
|
551
|
+
withSession: (constraint) => {
|
|
552
|
+
const context = contextFor();
|
|
553
|
+
if (!context) return info.db.withSession(constraint);
|
|
554
|
+
if (constraint && constraint !== "first-unconstrained" && constraint !== context.input[info.id]) {
|
|
555
|
+
if (constraint === "first-primary" && (!context.sessions.has(info.id) || context.primary || info.primary || context.primaryIds.has(info.id))) {
|
|
556
|
+
context.primaryIds.add(info.id);
|
|
557
|
+
return { prepare, batch, getBookmark: () => context.sessions.get(info.id)?.getBookmark() ?? null };
|
|
558
|
+
}
|
|
559
|
+
throw new Error("Explicit D1 session constraint conflicts with request session");
|
|
560
|
+
}
|
|
561
|
+
return { prepare, batch, getBookmark: () => context.sessions.get(info.id)?.getBookmark() ?? null };
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
infos.set(database, info);
|
|
565
|
+
return database;
|
|
566
|
+
}
|
|
567
|
+
function sessionD1Env(env, options) {
|
|
568
|
+
const raw = Reflect.get(env, options.databaseIdsEnv);
|
|
569
|
+
if (raw === void 0) return env;
|
|
570
|
+
let parsed;
|
|
571
|
+
try {
|
|
572
|
+
parsed = typeof raw === "string" ? JSON.parse(raw) : null;
|
|
573
|
+
} catch {
|
|
574
|
+
parsed = null;
|
|
575
|
+
}
|
|
576
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`Invalid ${options.databaseIdsEnv}`);
|
|
577
|
+
const ids = Object.fromEntries(Object.entries(parsed).map(([name, id]) => {
|
|
578
|
+
if (!isD1BindingName(name, { ...options, envPrefix: "", coordinatorNamePrefix: "", coordinatorClass: "", readModels: [], unfencedWriteTables: [] }) || typeof id !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) throw new Error("Invalid D1 database identity");
|
|
579
|
+
return [name, id.toLowerCase()];
|
|
580
|
+
}));
|
|
581
|
+
const primaryIds = new Set((options.primaryBindings ?? []).map((name) => ids[name]).filter((id) => typeof id === "string"));
|
|
582
|
+
const scoped = { ...env };
|
|
583
|
+
for (const name of options.bindingNames) if (!Reflect.has(env, name)) throw new Error(`Missing D1 binding: ${name}`);
|
|
584
|
+
for (const [name, db] of Object.entries(env)) {
|
|
585
|
+
if (options.bindingPrefixes.some((prefix) => name.startsWith(prefix)) && !isD1BindingName(name, { ...options, envPrefix: "", coordinatorNamePrefix: "", coordinatorClass: "", readModels: [], unfencedWriteTables: [] })) throw new Error(`Invalid D1 binding: ${name}`);
|
|
586
|
+
if (!isD1BindingName(name, { ...options, envPrefix: "", coordinatorNamePrefix: "", coordinatorClass: "", readModels: [], unfencedWriteTables: [] })) continue;
|
|
587
|
+
const id = ids[name];
|
|
588
|
+
if (!id || !db || typeof db !== "object" || typeof Reflect.get(db, "withSession") !== "function") throw new Error(`Missing D1 identity or binding: ${name}`);
|
|
589
|
+
const primary = primaryIds.has(id);
|
|
590
|
+
let variants = facades.get(db);
|
|
591
|
+
if (!variants) {
|
|
592
|
+
variants = /* @__PURE__ */ new Map();
|
|
593
|
+
facades.set(db, variants);
|
|
594
|
+
}
|
|
595
|
+
const key = `${id}:${primary}`;
|
|
596
|
+
let wrapped = variants.get(key);
|
|
597
|
+
if (!wrapped) {
|
|
598
|
+
wrapped = facade({ id, db, primary });
|
|
599
|
+
variants.set(key, wrapped);
|
|
600
|
+
}
|
|
601
|
+
Reflect.set(scoped, name, wrapped);
|
|
602
|
+
}
|
|
603
|
+
return scoped;
|
|
604
|
+
}
|
|
605
|
+
function wrapD1ReadSessionHandler(handler, options) {
|
|
606
|
+
return async (request, env, context) => {
|
|
607
|
+
if (request.headers.get("upgrade")?.toLowerCase() === "websocket") return handler(request, env, context);
|
|
608
|
+
if (Reflect.get(env, options.databaseIdsEnv) === void 0) return handler(request, env, context);
|
|
609
|
+
const scoped = sessionD1Env(env, options);
|
|
610
|
+
const decoded = decodeD1Bookmarks(request.headers.get(D1_BOOKMARK_HEADER));
|
|
611
|
+
if (!decoded) return new Response("Invalid D1 bookmarks", { status: 400 });
|
|
612
|
+
const configured = new Set(Object.entries(scoped).flatMap(([name, value]) => isD1BindingName(name, { ...options, envPrefix: "", coordinatorNamePrefix: "", coordinatorClass: "", readModels: [], unfencedWriteTables: [] }) && value && typeof value === "object" ? [infos.get(value)?.id].filter((id) => !!id) : []));
|
|
613
|
+
const input = Object.fromEntries(Object.entries(decoded).filter(([id]) => configured.has(id)));
|
|
614
|
+
const sessionContext = { input, sessions: /* @__PURE__ */ new Map(), bookmarks: {}, primaryIds: /* @__PURE__ */ new Set(), allowedIds: configured, operations: /* @__PURE__ */ new Map(), primary: !["GET", "HEAD", "OPTIONS"].includes(request.method.toUpperCase()) || options.ignoreRequestCacheControl !== true && hasCacheBypass(request) || options.freshnessHeader !== void 0 && request.headers.has(options.freshnessHeader), closed: false };
|
|
615
|
+
return storage.run(sessionContext, async () => {
|
|
616
|
+
try {
|
|
617
|
+
const tracked = await trackD1SessionSource(() => handler(request, scoped, context));
|
|
618
|
+
const response = tracked.value;
|
|
619
|
+
if (response.status === 101) return response;
|
|
620
|
+
const headers = new Headers(response.headers);
|
|
621
|
+
varyD1Bookmarks(headers);
|
|
622
|
+
const responseBookmarks = responseD1SessionBookmarks(headers, tracked.bookmarks) ?? {};
|
|
623
|
+
sessionContext.closed = true;
|
|
624
|
+
const encoded = serializeD1Bookmarks(responseBookmarks);
|
|
625
|
+
if (encoded && encoded !== "{}") headers.set(D1_BOOKMARK_HEADER, encoded);
|
|
626
|
+
else headers.delete(D1_BOOKMARK_HEADER);
|
|
627
|
+
return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
|
|
628
|
+
} finally {
|
|
629
|
+
sessionContext.closed = true;
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export { advanceD1SessionBookmarks, d1SessionBookmarkFloor, d1SessionCacheHooks, d1SessionWarmupRequest, fenceD1Writes, hasCacheBypass, hasFreshnessCookie, hasFreshnessGeneration, prismaD1SessionDatabase, regionalD1MetadataTtl, registerD1SessionAlias, responseD1SessionBookmarks, runD1PrismaScope, runD1SessionOperation, runIsolatedD1Session, runPrimaryD1SessionSource, sessionD1Env, trackD1SessionSource, unwrapD1SessionCacheValue, validFreshnessHeader, varyD1Bookmarks, wrapD1ReadSessionHandler, wrapD1SessionCacheValue };
|
|
636
|
+
//# sourceMappingURL=chunk-WJNCFFF3.js.map
|
|
637
|
+
//# sourceMappingURL=chunk-WJNCFFF3.js.map
|