@puzzle-ui/storage-d1 0.0.0-stage → 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.
- package/README.md +12 -2
- package/dist/LICENSE +21 -0
- package/dist/d1.adapter.cjs +531 -0
- package/dist/d1.adapter.cjs.map +1 -0
- package/dist/d1.adapter.d.cts +30 -0
- package/dist/d1.adapter.d.ts +30 -0
- package/dist/d1.adapter.js +531 -0
- package/dist/d1.adapter.js.map +1 -0
- package/dist/d1.migrations.cjs +194 -0
- package/dist/d1.migrations.cjs.map +1 -0
- package/dist/d1.migrations.d.cts +19 -0
- package/dist/d1.migrations.d.ts +19 -0
- package/dist/d1.migrations.js +194 -0
- package/dist/d1.migrations.js.map +1 -0
- package/dist/d1.types.d.cts +24 -0
- package/dist/d1.types.d.ts +24 -0
- package/dist/index.cjs +5 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/package.json +47 -4
package/README.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @puzzle-ui/storage-d1
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Foundation package for Puzzle Auth.
|
|
4
|
+
|
|
5
|
+
This package establishes public entry points and dependency boundaries. Feature implementations are intentionally added in subsequent Puzzle Auth features.
|
|
6
|
+
|
|
7
|
+
## Build
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bun run build
|
|
11
|
+
bun run typecheck
|
|
12
|
+
bun run test
|
|
13
|
+
```
|
package/dist/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sjoerd van Den Berg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
let _puzzle_ui_storage = require("@puzzle-ui/storage");
|
|
2
|
+
// src/d1.adapter.ts
|
|
3
|
+
const DATE_MARKER = "__puzzle_storage_date__";
|
|
4
|
+
const serialize = (value) => JSON.stringify(value, (_key, current) => current instanceof Date ? { [DATE_MARKER]: current.toISOString() } : current);
|
|
5
|
+
const deserialize = (value) => JSON.parse(value, (_key, current) => {
|
|
6
|
+
if (typeof current === "object" && current !== null && DATE_MARKER in current) {
|
|
7
|
+
const date = current[DATE_MARKER];
|
|
8
|
+
if (typeof date !== "string") throw new _puzzle_ui_storage.StorageValidationError("Invalid serialized D1 date.");
|
|
9
|
+
return new Date(date);
|
|
10
|
+
}
|
|
11
|
+
return current;
|
|
12
|
+
});
|
|
13
|
+
const encodeCursor = (payload) => ({ value: btoa(encodeURIComponent(JSON.stringify(payload))) });
|
|
14
|
+
const decodeCursor = (cursor) => {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(decodeURIComponent(atob(cursor.value)));
|
|
17
|
+
} catch {
|
|
18
|
+
throw new _puzzle_ui_storage.StorageValidationError("Invalid D1 cursor.");
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const quoteIdentifier = (value) => {
|
|
22
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) throw new _puzzle_ui_storage.StorageValidationError(`Invalid SQL identifier: ${value}`);
|
|
23
|
+
return `"${value}"`;
|
|
24
|
+
};
|
|
25
|
+
const jsonPath = (field) => {
|
|
26
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) throw new _puzzle_ui_storage.StorageValidationError(`Unsupported JSON field name: ${field}`);
|
|
27
|
+
return `$."${field}"`;
|
|
28
|
+
};
|
|
29
|
+
const sqlField = (field) => {
|
|
30
|
+
if (field === "id") return "id";
|
|
31
|
+
if (field === "createdAt") return "created_at";
|
|
32
|
+
if (field === "updatedAt") return "updated_at";
|
|
33
|
+
if (field === "revision") return "revision";
|
|
34
|
+
return `json_extract(data, '${jsonPath(field)}')`;
|
|
35
|
+
};
|
|
36
|
+
const sqlValue = (value) => value instanceof Date ? value.toISOString() : value;
|
|
37
|
+
const buildComparison = (field, comparison) => {
|
|
38
|
+
const column = sqlField(field);
|
|
39
|
+
const value = comparison.value;
|
|
40
|
+
switch (comparison.operator) {
|
|
41
|
+
case "eq": return value === null ? {
|
|
42
|
+
sql: `${column} IS NULL`,
|
|
43
|
+
values: []
|
|
44
|
+
} : {
|
|
45
|
+
sql: `${column} = ?`,
|
|
46
|
+
values: [sqlValue(value)]
|
|
47
|
+
};
|
|
48
|
+
case "ne": return value === null ? {
|
|
49
|
+
sql: `${column} IS NOT NULL`,
|
|
50
|
+
values: []
|
|
51
|
+
} : {
|
|
52
|
+
sql: `${column} != ?`,
|
|
53
|
+
values: [sqlValue(value)]
|
|
54
|
+
};
|
|
55
|
+
case "gt": return {
|
|
56
|
+
sql: `${column} > ?`,
|
|
57
|
+
values: [sqlValue(value)]
|
|
58
|
+
};
|
|
59
|
+
case "gte": return {
|
|
60
|
+
sql: `${column} >= ?`,
|
|
61
|
+
values: [sqlValue(value)]
|
|
62
|
+
};
|
|
63
|
+
case "lt": return {
|
|
64
|
+
sql: `${column} < ?`,
|
|
65
|
+
values: [sqlValue(value)]
|
|
66
|
+
};
|
|
67
|
+
case "lte": return {
|
|
68
|
+
sql: `${column} <= ?`,
|
|
69
|
+
values: [sqlValue(value)]
|
|
70
|
+
};
|
|
71
|
+
case "exists": return {
|
|
72
|
+
sql: comparison.value ? `${column} IS NOT NULL` : `${column} IS NULL`,
|
|
73
|
+
values: []
|
|
74
|
+
};
|
|
75
|
+
case "in":
|
|
76
|
+
case "notIn": {
|
|
77
|
+
const values = value;
|
|
78
|
+
if (values.length === 0) return {
|
|
79
|
+
sql: comparison.operator === "in" ? "0 = 1" : "1 = 1",
|
|
80
|
+
values: []
|
|
81
|
+
};
|
|
82
|
+
const placeholders = values.map(() => "?").join(", ");
|
|
83
|
+
return {
|
|
84
|
+
sql: `${column} ${comparison.operator === "in" ? "IN" : "NOT IN"} (${placeholders})`,
|
|
85
|
+
values: values.map(sqlValue)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
default: throw new _puzzle_ui_storage.StorageValidationError(`Unsupported query operator: ${comparison.operator}`);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const buildWhere = (where) => {
|
|
92
|
+
if (!where) return {
|
|
93
|
+
sql: "1 = 1",
|
|
94
|
+
values: []
|
|
95
|
+
};
|
|
96
|
+
const parts = [];
|
|
97
|
+
const values = [];
|
|
98
|
+
for (const [field, comparison] of Object.entries(where)) {
|
|
99
|
+
if (field === "and" || field === "or") {
|
|
100
|
+
const nested = comparison.map((item) => buildWhere(item));
|
|
101
|
+
if (nested.length === 0) continue;
|
|
102
|
+
parts.push(`(${nested.map((item) => item.sql).join(field === "and" ? " AND " : " OR ")})`);
|
|
103
|
+
for (const item of nested) values.push(...item.values);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const value = comparison;
|
|
107
|
+
if (typeof value !== "object" || value === null || !("operator" in value) || typeof value.operator !== "string" || !("value" in value)) throw new _puzzle_ui_storage.StorageValidationError(`Invalid query comparison for field: ${field}`);
|
|
108
|
+
const result = buildComparison(field, {
|
|
109
|
+
operator: value.operator,
|
|
110
|
+
value: value.value
|
|
111
|
+
});
|
|
112
|
+
parts.push(result.sql);
|
|
113
|
+
values.push(...result.values);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
sql: parts.length ? parts.join(" AND ") : "1 = 1",
|
|
117
|
+
values
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
const buildOrderBy = (orderBy) => {
|
|
121
|
+
const items = (orderBy ?? []).map((item) => `${sqlField(String(item.field))} ${item.direction === "desc" ? "DESC" : "ASC"}`);
|
|
122
|
+
if (!items.some((item) => item.startsWith("id "))) items.push("id ASC");
|
|
123
|
+
return items.join(", ");
|
|
124
|
+
};
|
|
125
|
+
const buildCursorWhere = (orderBy, values) => {
|
|
126
|
+
const fields = [...orderBy.map((item) => ({
|
|
127
|
+
field: String(item.field),
|
|
128
|
+
direction: item.direction
|
|
129
|
+
}))];
|
|
130
|
+
if (!fields.some((item) => item.field === "id")) fields.push({
|
|
131
|
+
field: "id",
|
|
132
|
+
direction: "asc"
|
|
133
|
+
});
|
|
134
|
+
const branches = [];
|
|
135
|
+
const parameters = [];
|
|
136
|
+
for (const [index, field] of fields.entries()) {
|
|
137
|
+
const conditions = [];
|
|
138
|
+
for (const [previous, prefix] of fields.slice(0, index).entries()) {
|
|
139
|
+
conditions.push(`${sqlField(prefix.field)} = ?`);
|
|
140
|
+
parameters.push(sqlValue(values[previous]));
|
|
141
|
+
}
|
|
142
|
+
const operator = field.direction === "desc" ? "<" : ">";
|
|
143
|
+
conditions.push(`${sqlField(field.field)} ${operator} ?`);
|
|
144
|
+
parameters.push(sqlValue(values[index]));
|
|
145
|
+
branches.push(`(${conditions.join(" AND ")})`);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
sql: `(${branches.join(" OR ")})`,
|
|
149
|
+
values: parameters
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
const cleanChanges = (changes) => {
|
|
153
|
+
const result = { ...changes };
|
|
154
|
+
delete result.id;
|
|
155
|
+
delete result.createdAt;
|
|
156
|
+
delete result.updatedAt;
|
|
157
|
+
delete result.revision;
|
|
158
|
+
return result;
|
|
159
|
+
};
|
|
160
|
+
const toRecord = (row) => {
|
|
161
|
+
return {
|
|
162
|
+
...deserialize(row.data),
|
|
163
|
+
id: row.id,
|
|
164
|
+
createdAt: new Date(row.created_at),
|
|
165
|
+
updatedAt: new Date(row.updated_at),
|
|
166
|
+
revision: row.revision
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
const getChanges = (result) => Number(result.meta?.changes ?? 0);
|
|
170
|
+
const mapD1Error = (error) => {
|
|
171
|
+
const message = error instanceof Error ? error.message.toLowerCase() : "";
|
|
172
|
+
if (message.includes("unique constraint") || message.includes("primary key")) throw new _puzzle_ui_storage.StorageDuplicateError("d1_unique_constraint");
|
|
173
|
+
throw error;
|
|
174
|
+
};
|
|
175
|
+
var D1StorageAdapter = class {
|
|
176
|
+
capabilities = {
|
|
177
|
+
transactions: true,
|
|
178
|
+
atomicBatch: true,
|
|
179
|
+
conditionalWrites: true,
|
|
180
|
+
uniqueConstraints: true,
|
|
181
|
+
upsert: true,
|
|
182
|
+
cursorPagination: true,
|
|
183
|
+
migrations: true,
|
|
184
|
+
ttlIndexes: false
|
|
185
|
+
};
|
|
186
|
+
database;
|
|
187
|
+
prefix;
|
|
188
|
+
constructor(options) {
|
|
189
|
+
this.database = options.database;
|
|
190
|
+
this.prefix = options.tablePrefix ?? "puzzle_";
|
|
191
|
+
}
|
|
192
|
+
getCapabilities() {
|
|
193
|
+
return {
|
|
194
|
+
capabilities: this.capabilities,
|
|
195
|
+
limitations: ["D1 does not provide native TTL indexes. Expiration cleanup must be handled by an application or scheduled job."]
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
table(collection) {
|
|
199
|
+
return quoteIdentifier(`${this.prefix}${collection}`);
|
|
200
|
+
}
|
|
201
|
+
prepare(sql, values = []) {
|
|
202
|
+
return this.database.prepare(sql).bind(...values);
|
|
203
|
+
}
|
|
204
|
+
async run(sql, values = []) {
|
|
205
|
+
return this.prepare(sql, values).run();
|
|
206
|
+
}
|
|
207
|
+
async first(sql, values = []) {
|
|
208
|
+
return this.prepare(sql, values).first();
|
|
209
|
+
}
|
|
210
|
+
async all(sql, values = []) {
|
|
211
|
+
return (await this.prepare(sql, values).all()).results ?? [];
|
|
212
|
+
}
|
|
213
|
+
insertStatement(collection, record) {
|
|
214
|
+
const input = record;
|
|
215
|
+
const now = /* @__PURE__ */ new Date();
|
|
216
|
+
const createdAt = input.createdAt ?? now;
|
|
217
|
+
const updatedAt = input.updatedAt ?? now;
|
|
218
|
+
const revision = input.revision ?? 1;
|
|
219
|
+
const { id, createdAt: _createdAt, updatedAt: _updatedAt, revision: _revision, ...data } = input;
|
|
220
|
+
const result = {
|
|
221
|
+
id,
|
|
222
|
+
createdAt,
|
|
223
|
+
updatedAt,
|
|
224
|
+
revision,
|
|
225
|
+
...data
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
sql: `INSERT INTO ${this.table(collection)}
|
|
229
|
+
(id, created_at, updated_at, revision, data)
|
|
230
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
231
|
+
values: [
|
|
232
|
+
id,
|
|
233
|
+
createdAt.toISOString(),
|
|
234
|
+
updatedAt.toISOString(),
|
|
235
|
+
revision,
|
|
236
|
+
serialize(data)
|
|
237
|
+
],
|
|
238
|
+
record: result
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
async registerCollection(definition) {
|
|
242
|
+
const table = this.table(definition.name);
|
|
243
|
+
await this.run(`
|
|
244
|
+
CREATE TABLE IF NOT EXISTS ${table} (
|
|
245
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
246
|
+
created_at TEXT NOT NULL,
|
|
247
|
+
updated_at TEXT NOT NULL,
|
|
248
|
+
revision INTEGER NOT NULL CHECK (revision > 0),
|
|
249
|
+
data TEXT NOT NULL CHECK (json_valid(data))
|
|
250
|
+
)
|
|
251
|
+
`);
|
|
252
|
+
for (const index of definition.indexes ?? []) {
|
|
253
|
+
const fields = index.fields.map((field) => {
|
|
254
|
+
return sqlField(String(field));
|
|
255
|
+
});
|
|
256
|
+
const unique = index.unique ? "UNIQUE " : "";
|
|
257
|
+
const name = quoteIdentifier(`${this.prefix}${definition.name}_${index.name}`);
|
|
258
|
+
await this.run(`
|
|
259
|
+
CREATE ${unique}INDEX IF NOT EXISTS ${name}
|
|
260
|
+
ON ${table} (${fields.join(", ")})
|
|
261
|
+
`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async findById(collection, id) {
|
|
265
|
+
const row = await this.first(`SELECT * FROM ${this.table(collection)} WHERE id = ? LIMIT 1`, [id]);
|
|
266
|
+
return row ? toRecord(row) : null;
|
|
267
|
+
}
|
|
268
|
+
async findOne(collection, query) {
|
|
269
|
+
(0, _puzzle_ui_storage.validateQuery)(query);
|
|
270
|
+
const where = buildWhere(query.where);
|
|
271
|
+
const row = await this.first(`SELECT * FROM ${this.table(collection)}
|
|
272
|
+
WHERE ${where.sql}
|
|
273
|
+
ORDER BY ${buildOrderBy(query.orderBy)}
|
|
274
|
+
LIMIT 1`, where.values);
|
|
275
|
+
return row ? toRecord(row) : null;
|
|
276
|
+
}
|
|
277
|
+
async findMany(collection, query = {}) {
|
|
278
|
+
(0, _puzzle_ui_storage.validateQuery)(query);
|
|
279
|
+
const where = buildWhere(query.where);
|
|
280
|
+
const values = [...where.values];
|
|
281
|
+
let pagination = "";
|
|
282
|
+
if (query.limit !== void 0) {
|
|
283
|
+
pagination += " LIMIT ?";
|
|
284
|
+
values.push(query.limit);
|
|
285
|
+
} else if (query.offset !== void 0) pagination += " LIMIT -1";
|
|
286
|
+
if (query.offset !== void 0) {
|
|
287
|
+
pagination += " OFFSET ?";
|
|
288
|
+
values.push(query.offset);
|
|
289
|
+
}
|
|
290
|
+
return (await this.all(`SELECT * FROM ${this.table(collection)}
|
|
291
|
+
WHERE ${where.sql}
|
|
292
|
+
ORDER BY ${buildOrderBy(query.orderBy)}
|
|
293
|
+
${pagination}`, values)).map((row) => toRecord(row));
|
|
294
|
+
}
|
|
295
|
+
async findPage(collection, query) {
|
|
296
|
+
if (!Number.isInteger(query.first) || query.first < 1) throw new _puzzle_ui_storage.StorageValidationError("Cursor page size must be a positive integer.");
|
|
297
|
+
const orderBy = query.orderBy ?? [{
|
|
298
|
+
field: "id",
|
|
299
|
+
direction: "asc"
|
|
300
|
+
}];
|
|
301
|
+
const base = buildWhere(query.where);
|
|
302
|
+
let whereSql = base.sql;
|
|
303
|
+
const values = [...base.values];
|
|
304
|
+
if (query.after) {
|
|
305
|
+
const cursor = decodeCursor(query.after);
|
|
306
|
+
const after = buildCursorWhere(orderBy, cursor.values);
|
|
307
|
+
whereSql = `(${whereSql}) AND ${after.sql}`;
|
|
308
|
+
values.push(...after.values);
|
|
309
|
+
}
|
|
310
|
+
values.push(query.first + 1);
|
|
311
|
+
const rows = await this.all(`SELECT * FROM ${this.table(collection)}
|
|
312
|
+
WHERE ${whereSql}
|
|
313
|
+
ORDER BY ${buildOrderBy(orderBy)}
|
|
314
|
+
LIMIT ?`, values);
|
|
315
|
+
const hasNextPage = rows.length > query.first;
|
|
316
|
+
const items = rows.slice(0, query.first);
|
|
317
|
+
const last = items.at(-1);
|
|
318
|
+
const cursorFields = [...orderBy.map((item) => String(item.field))];
|
|
319
|
+
if (!cursorFields.includes("id")) cursorFields.push("id");
|
|
320
|
+
const nextCursor = hasNextPage && last ? encodeCursor({ values: cursorFields.map((field) => {
|
|
321
|
+
if (field === "id") return last.id;
|
|
322
|
+
if (field === "createdAt") return last.created_at;
|
|
323
|
+
if (field === "updatedAt") return last.updated_at;
|
|
324
|
+
if (field === "revision") return last.revision;
|
|
325
|
+
return deserialize(last.data)[field];
|
|
326
|
+
}) }) : null;
|
|
327
|
+
return {
|
|
328
|
+
items: items.map((row) => toRecord(row)),
|
|
329
|
+
hasNextPage,
|
|
330
|
+
nextCursor
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
async insert(collection, record) {
|
|
334
|
+
const statement = this.insertStatement(collection, record);
|
|
335
|
+
try {
|
|
336
|
+
await this.run(statement.sql, statement.values);
|
|
337
|
+
return statement.record;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
return mapD1Error(error);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async upsert(collection, query, record) {
|
|
343
|
+
const existing = await this.findOne(collection, query);
|
|
344
|
+
if (existing) {
|
|
345
|
+
const changes = { ...record };
|
|
346
|
+
const updated = await this.update(collection, existing.id, changes);
|
|
347
|
+
if (!updated) throw new _puzzle_ui_storage.StorageConflictError();
|
|
348
|
+
return updated;
|
|
349
|
+
}
|
|
350
|
+
return this.insert(collection, record);
|
|
351
|
+
}
|
|
352
|
+
buildUpdate(collection, id, changes, condition) {
|
|
353
|
+
const entries = Object.entries(changes);
|
|
354
|
+
entries.map(([field]) => `data = json_set(data, ?, json(?))`);
|
|
355
|
+
const assignments = [];
|
|
356
|
+
if (entries.length > 0) assignments.push(`data = ${entries.reduce((expression, _entry, index) => `json_set(${expression}, ?, json(?))`, "data")}`);
|
|
357
|
+
assignments.push("updated_at = ?");
|
|
358
|
+
assignments.push("revision = revision + 1");
|
|
359
|
+
const where = ["id = ?"];
|
|
360
|
+
const values = [];
|
|
361
|
+
for (const [field, value] of entries) values.push(jsonPath(field), serialize(value));
|
|
362
|
+
values.push((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
363
|
+
if (condition?.expectedRevision !== void 0) {
|
|
364
|
+
where.push("revision = ?");
|
|
365
|
+
values.push(condition.expectedRevision);
|
|
366
|
+
}
|
|
367
|
+
if (condition?.expectedUpdatedAt !== void 0) {
|
|
368
|
+
where.push("updated_at = ?");
|
|
369
|
+
values.push(condition.expectedUpdatedAt.toISOString());
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
sql: `UPDATE ${this.table(collection)}
|
|
373
|
+
SET ${assignments.join(", ")}
|
|
374
|
+
WHERE ${where.join(" AND ")}`,
|
|
375
|
+
values
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
async update(collection, id, changes, condition) {
|
|
379
|
+
const clean = cleanChanges(changes);
|
|
380
|
+
const statement = this.buildUpdate(collection, id, clean, condition);
|
|
381
|
+
const result = await this.run(statement.sql, statement.values);
|
|
382
|
+
if (getChanges(result) === 0) {
|
|
383
|
+
if (condition && await this.findById(collection, id)) throw new _puzzle_ui_storage.StorageConflictError();
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
return this.findById(collection, id);
|
|
387
|
+
}
|
|
388
|
+
async updateMany(collection, query, changes) {
|
|
389
|
+
(0, _puzzle_ui_storage.validateQuery)(query);
|
|
390
|
+
const clean = cleanChanges(changes);
|
|
391
|
+
const entries = Object.entries(clean);
|
|
392
|
+
const assignments = entries.length ? `data = ${entries.reduce((expression) => `json_set(${expression}, ?, json(?))`, "data")}, ` : "";
|
|
393
|
+
const values = [];
|
|
394
|
+
for (const [field, value] of entries) values.push(jsonPath(field), serialize(value));
|
|
395
|
+
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
396
|
+
const where = buildWhere(query.where);
|
|
397
|
+
values.push(...where.values);
|
|
398
|
+
const result = await this.run(`UPDATE ${this.table(collection)}
|
|
399
|
+
SET ${assignments}updated_at = ?, revision = revision + 1
|
|
400
|
+
WHERE ${where.sql}`, values);
|
|
401
|
+
return getChanges(result);
|
|
402
|
+
}
|
|
403
|
+
async delete(collection, id, condition) {
|
|
404
|
+
const where = ["id = ?"];
|
|
405
|
+
const values = [id];
|
|
406
|
+
if (condition?.expectedRevision !== void 0) {
|
|
407
|
+
where.push("revision = ?");
|
|
408
|
+
values.push(condition.expectedRevision);
|
|
409
|
+
}
|
|
410
|
+
if (condition?.expectedUpdatedAt !== void 0) {
|
|
411
|
+
where.push("updated_at = ?");
|
|
412
|
+
values.push(condition.expectedUpdatedAt.toISOString());
|
|
413
|
+
}
|
|
414
|
+
const result = await this.run(`DELETE FROM ${this.table(collection)}
|
|
415
|
+
WHERE ${where.join(" AND ")}`, values);
|
|
416
|
+
if (getChanges(result) > 0) return true;
|
|
417
|
+
if (condition && await this.findById(collection, id)) throw new _puzzle_ui_storage.StorageConflictError();
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
async deleteMany(collection, query) {
|
|
421
|
+
(0, _puzzle_ui_storage.validateQuery)(query);
|
|
422
|
+
const where = buildWhere(query.where);
|
|
423
|
+
const result = await this.run(`DELETE FROM ${this.table(collection)} WHERE ${where.sql}`, where.values);
|
|
424
|
+
return getChanges(result);
|
|
425
|
+
}
|
|
426
|
+
async exists(collection, query) {
|
|
427
|
+
(0, _puzzle_ui_storage.validateQuery)(query);
|
|
428
|
+
const where = buildWhere(query.where);
|
|
429
|
+
return await this.first(`SELECT 1 AS found FROM ${this.table(collection)}
|
|
430
|
+
WHERE ${where.sql} LIMIT 1`, where.values) !== null;
|
|
431
|
+
}
|
|
432
|
+
async count(collection, query = {}) {
|
|
433
|
+
(0, _puzzle_ui_storage.validateQuery)(query);
|
|
434
|
+
const where = buildWhere(query.where);
|
|
435
|
+
const result = await this.first(`SELECT COUNT(*) AS total FROM ${this.table(collection)}
|
|
436
|
+
WHERE ${where.sql}`, where.values);
|
|
437
|
+
return Number(result?.total ?? 0);
|
|
438
|
+
}
|
|
439
|
+
async atomicBatch(operations) {
|
|
440
|
+
const assertionTable = quoteIdentifier(`${this.prefix}atomic_assertions`);
|
|
441
|
+
await this.run(`
|
|
442
|
+
CREATE TABLE IF NOT EXISTS ${assertionTable} (
|
|
443
|
+
value INTEGER NOT NULL CHECK (value = 1)
|
|
444
|
+
)
|
|
445
|
+
`);
|
|
446
|
+
const statements = [];
|
|
447
|
+
const resultIndexes = [];
|
|
448
|
+
for (const operation of operations) {
|
|
449
|
+
const table = this.table(operation.collection);
|
|
450
|
+
if (operation.type !== "insert" && (operation.condition?.expectedRevision !== void 0 || operation.condition?.expectedUpdatedAt !== void 0)) {
|
|
451
|
+
const conditions = ["id = ?"];
|
|
452
|
+
const values = [operation.id];
|
|
453
|
+
if (operation.condition.expectedRevision !== void 0) {
|
|
454
|
+
conditions.push("revision = ?");
|
|
455
|
+
values.push(operation.condition.expectedRevision);
|
|
456
|
+
}
|
|
457
|
+
if (operation.condition.expectedUpdatedAt !== void 0) {
|
|
458
|
+
conditions.push("updated_at = ?");
|
|
459
|
+
values.push(operation.condition.expectedUpdatedAt.toISOString());
|
|
460
|
+
}
|
|
461
|
+
statements.push(this.prepare(`INSERT INTO ${assertionTable}(value)
|
|
462
|
+
SELECT CASE WHEN EXISTS (
|
|
463
|
+
SELECT 1 FROM ${table}
|
|
464
|
+
WHERE ${conditions.join(" AND ")}
|
|
465
|
+
) THEN 1 ELSE 0 END`, values));
|
|
466
|
+
statements.push(this.prepare(`DELETE FROM ${assertionTable}`));
|
|
467
|
+
}
|
|
468
|
+
if (operation.type === "insert") {
|
|
469
|
+
const statement = this.insertStatement(operation.collection, operation.record);
|
|
470
|
+
statements.push(this.prepare(statement.sql, statement.values));
|
|
471
|
+
resultIndexes.push(statements.length - 1);
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
if (operation.type === "update") {
|
|
475
|
+
const statement = this.buildUpdate(operation.collection, operation.id, cleanChanges(operation.changes), operation.condition);
|
|
476
|
+
statements.push(this.prepare(statement.sql, statement.values));
|
|
477
|
+
resultIndexes.push(statements.length - 1);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
const conditions = ["id = ?"];
|
|
481
|
+
const values = [operation.id];
|
|
482
|
+
if (operation.condition?.expectedRevision !== void 0) {
|
|
483
|
+
conditions.push("revision = ?");
|
|
484
|
+
values.push(operation.condition.expectedRevision);
|
|
485
|
+
}
|
|
486
|
+
if (operation.condition?.expectedUpdatedAt !== void 0) {
|
|
487
|
+
conditions.push("updated_at = ?");
|
|
488
|
+
values.push(operation.condition.expectedUpdatedAt.toISOString());
|
|
489
|
+
}
|
|
490
|
+
statements.push(this.prepare(`DELETE FROM ${table} WHERE ${conditions.join(" AND ")}`, values));
|
|
491
|
+
resultIndexes.push(statements.length - 1);
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
const batchResults = await this.database.batch(statements);
|
|
495
|
+
const results = [];
|
|
496
|
+
for (const [index, operation] of operations.entries()) {
|
|
497
|
+
const resultIndex = resultIndexes[index];
|
|
498
|
+
const result = resultIndex === void 0 ? void 0 : batchResults[resultIndex];
|
|
499
|
+
if (!result) throw new _puzzle_ui_storage.StorageAtomicityError("D1 batch did not return a result for every operation.");
|
|
500
|
+
if (operation.type === "insert") {
|
|
501
|
+
const input = operation.record;
|
|
502
|
+
const inserted = await this.findById(operation.collection, input.id);
|
|
503
|
+
if (!inserted) throw new _puzzle_ui_storage.StorageAtomicityError("D1 batch committed but the inserted record could not be read.");
|
|
504
|
+
results.push({
|
|
505
|
+
type: "insert",
|
|
506
|
+
record: inserted
|
|
507
|
+
});
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (operation.type === "update") {
|
|
511
|
+
results.push({
|
|
512
|
+
type: "update",
|
|
513
|
+
record: await this.findById(operation.collection, operation.id)
|
|
514
|
+
});
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
results.push({
|
|
518
|
+
type: "delete",
|
|
519
|
+
deleted: getChanges(result) > 0
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
return { results };
|
|
523
|
+
} catch (error) {
|
|
524
|
+
if ((error instanceof Error ? error.message.toLowerCase() : "").includes("check constraint")) throw new _puzzle_ui_storage.StorageConflictError();
|
|
525
|
+
return mapD1Error(error);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
exports.D1StorageAdapter = D1StorageAdapter;
|
|
530
|
+
|
|
531
|
+
//# sourceMappingURL=d1.adapter.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"d1.adapter.cjs","names":["StorageValidationError","StorageDuplicateError","StorageConflictError","StorageAtomicityError"],"sources":["../src/d1.adapter.ts"],"sourcesContent":["import {\n StorageAtomicityError,\n StorageConflictError,\n StorageDuplicateError,\n StorageValidationError,\n type StorageAdapter,\n type StorageBatchOperation,\n type StorageBatchResult,\n type StorageCollection,\n type StorageCollectionName,\n type StorageCondition,\n type StorageCursor,\n type StorageCursorQuery,\n type StoragePage,\n type StorageQuery,\n type StorageRecord,\n type StorageUpdate,\n type StorageWhere,\n type StorageCapabilities,\n type StorageCapabilityReport,\n validateQuery,\n} from \"@puzzle-ui/storage\";\n\nimport type {\n D1DatabaseLike,\n D1PreparedStatementLike,\n D1ResultLike,\n D1StorageAdapterOptions,\n} from \"./d1.types\";\n\ntype D1Row = {\n id: string;\n created_at: string;\n updated_at: string;\n revision: number;\n data: string;\n};\n\ntype CursorPayload = {\n readonly values: readonly unknown[];\n};\n\nconst DATE_MARKER = \"__puzzle_storage_date__\";\n\nconst serialize = (value: unknown): string =>\n JSON.stringify(value, (_key, current: unknown) =>\n current instanceof Date\n ? { [DATE_MARKER]: current.toISOString() }\n : current,\n );\n\nconst deserialize = <T>(value: string): T =>\n JSON.parse(value, (_key, current: unknown) => {\n if (\n typeof current === \"object\" &&\n current !== null &&\n DATE_MARKER in current\n ) {\n const date = current[DATE_MARKER];\n\n if (typeof date !== \"string\") {\n throw new StorageValidationError(\"Invalid serialized D1 date.\");\n }\n\n return new Date(date);\n }\n\n return current;\n }) as T;\n\nconst encodeCursor = (payload: CursorPayload): StorageCursor => ({\n value: btoa(encodeURIComponent(JSON.stringify(payload))),\n});\n\nconst decodeCursor = (cursor: StorageCursor): CursorPayload => {\n try {\n return JSON.parse(decodeURIComponent(atob(cursor.value))) as CursorPayload;\n } catch {\n throw new StorageValidationError(\"Invalid D1 cursor.\");\n }\n};\n\nconst quoteIdentifier = (value: string): string => {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {\n throw new StorageValidationError(`Invalid SQL identifier: ${value}`);\n }\n\n return `\"${value}\"`;\n};\n\nconst jsonPath = (field: string): string => {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {\n throw new StorageValidationError(`Unsupported JSON field name: ${field}`);\n }\n\n return `$.\"${field}\"`;\n};\n\nconst sqlField = (field: string): string => {\n if (field === \"id\") return \"id\";\n if (field === \"createdAt\") return \"created_at\";\n if (field === \"updatedAt\") return \"updated_at\";\n if (field === \"revision\") return \"revision\";\n\n return `json_extract(data, '${jsonPath(field)}')`;\n};\n\nconst sqlValue = (value: unknown): unknown =>\n value instanceof Date ? value.toISOString() : value;\n\nconst buildComparison = (\n field: string,\n comparison: {\n readonly operator: string;\n readonly value: unknown;\n },\n): { sql: string; values: unknown[] } => {\n const column = sqlField(field);\n const value = comparison.value;\n\n switch (comparison.operator) {\n case \"eq\":\n return value === null\n ? { sql: `${column} IS NULL`, values: [] }\n : { sql: `${column} = ?`, values: [sqlValue(value)] };\n\n case \"ne\":\n return value === null\n ? { sql: `${column} IS NOT NULL`, values: [] }\n : { sql: `${column} != ?`, values: [sqlValue(value)] };\n\n case \"gt\":\n return { sql: `${column} > ?`, values: [sqlValue(value)] };\n\n case \"gte\":\n return { sql: `${column} >= ?`, values: [sqlValue(value)] };\n\n case \"lt\":\n return { sql: `${column} < ?`, values: [sqlValue(value)] };\n\n case \"lte\":\n return { sql: `${column} <= ?`, values: [sqlValue(value)] };\n\n case \"exists\":\n return {\n sql: comparison.value ? `${column} IS NOT NULL` : `${column} IS NULL`,\n values: [],\n };\n\n case \"in\":\n case \"notIn\": {\n const values = value as readonly unknown[];\n\n if (values.length === 0) {\n return {\n sql: comparison.operator === \"in\" ? \"0 = 1\" : \"1 = 1\",\n values: [],\n };\n }\n\n const placeholders = values.map(() => \"?\").join(\", \");\n const operator = comparison.operator === \"in\" ? \"IN\" : \"NOT IN\";\n\n return {\n sql: `${column} ${operator} (${placeholders})`,\n values: values.map(sqlValue),\n };\n }\n\n default:\n throw new StorageValidationError(\n `Unsupported query operator: ${comparison.operator}`,\n );\n }\n};\n\nconst buildWhere = <T extends object>(\n where?: StorageWhere<T>,\n): { sql: string; values: unknown[] } => {\n if (!where) return { sql: \"1 = 1\", values: [] };\n\n const parts: string[] = [];\n const values: unknown[] = [];\n\n for (const [field, comparison] of Object.entries(where)) {\n if (field === \"and\" || field === \"or\") {\n const nested = (comparison as readonly StorageWhere<T>[]).map((item) =>\n buildWhere(item),\n );\n\n if (nested.length === 0) continue;\n\n parts.push(\n `(${nested\n .map((item) => item.sql)\n .join(field === \"and\" ? \" AND \" : \" OR \")})`,\n );\n\n for (const item of nested) values.push(...item.values);\n\n continue;\n }\n\n const value: unknown = comparison;\n\n if (\n typeof value !== \"object\" ||\n value === null ||\n !(\"operator\" in value) ||\n typeof value.operator !== \"string\" ||\n !(\"value\" in value)\n ) {\n throw new StorageValidationError(\n `Invalid query comparison for field: ${field}`,\n );\n }\n\n const result = buildComparison(field, {\n operator: value.operator,\n value: value.value,\n });\n\n parts.push(result.sql);\n values.push(...result.values);\n }\n\n return {\n sql: parts.length ? parts.join(\" AND \") : \"1 = 1\",\n values,\n };\n};\n\nconst buildOrderBy = <T extends StorageRecord>(\n orderBy?: StorageQuery<T>[\"orderBy\"],\n): string => {\n const items = (orderBy ?? []).map(\n (item) =>\n `${sqlField(String(item.field))} ${\n item.direction === \"desc\" ? \"DESC\" : \"ASC\"\n }`,\n );\n\n if (!items.some((item) => item.startsWith(\"id \"))) {\n items.push(\"id ASC\");\n }\n\n return items.join(\", \");\n};\n\nconst buildCursorWhere = <T extends StorageRecord>(\n orderBy: NonNullable<StorageQuery<T>[\"orderBy\"]>,\n values: readonly unknown[],\n): { sql: string; values: unknown[] } => {\n const fields = [\n ...orderBy.map((item) => ({\n field: String(item.field),\n direction: item.direction,\n })),\n ];\n\n if (!fields.some((item) => item.field === \"id\")) {\n fields.push({ field: \"id\", direction: \"asc\" });\n }\n\n const branches: string[] = [];\n const parameters: unknown[] = [];\n\n for (const [index, field] of fields.entries()) {\n const conditions: string[] = [];\n\n for (const [previous, prefix] of fields.slice(0, index).entries()) {\n conditions.push(`${sqlField(prefix.field)} = ?`);\n parameters.push(sqlValue(values[previous]));\n }\n\n const operator = field.direction === \"desc\" ? \"<\" : \">\";\n\n conditions.push(`${sqlField(field.field)} ${operator} ?`);\n parameters.push(sqlValue(values[index]));\n\n branches.push(`(${conditions.join(\" AND \")})`);\n }\n\n return {\n sql: `(${branches.join(\" OR \")})`,\n values: parameters,\n };\n};\n\nconst cleanChanges = <T extends StorageRecord>(\n changes: StorageUpdate<T>,\n): Record<string, unknown> => {\n const result = { ...changes } as Record<string, unknown>;\n\n delete result.id;\n delete result.createdAt;\n delete result.updatedAt;\n delete result.revision;\n\n return result;\n};\n\nconst toRecord = <T extends StorageRecord>(row: D1Row): T => {\n const data = deserialize<Record<string, unknown>>(row.data);\n\n return {\n ...data,\n id: row.id,\n createdAt: new Date(row.created_at),\n updatedAt: new Date(row.updated_at),\n revision: row.revision,\n } as T;\n};\n\nconst getChanges = (result: D1ResultLike): number =>\n Number(result.meta?.changes ?? 0);\n\nconst mapD1Error = (error: unknown): never => {\n const message = error instanceof Error ? error.message.toLowerCase() : \"\";\n\n if (\n message.includes(\"unique constraint\") ||\n message.includes(\"primary key\")\n ) {\n throw new StorageDuplicateError(\"d1_unique_constraint\");\n }\n\n throw error;\n};\n\nexport class D1StorageAdapter implements StorageAdapter {\n readonly capabilities: StorageCapabilities = {\n transactions: true,\n atomicBatch: true,\n conditionalWrites: true,\n uniqueConstraints: true,\n upsert: true,\n cursorPagination: true,\n migrations: true,\n ttlIndexes: false,\n };\n\n private readonly database: D1DatabaseLike;\n private readonly prefix: string;\n\n constructor(options: D1StorageAdapterOptions) {\n this.database = options.database;\n this.prefix = options.tablePrefix ?? \"puzzle_\";\n }\n\n getCapabilities(): StorageCapabilityReport {\n return {\n capabilities: this.capabilities,\n limitations: [\n \"D1 does not provide native TTL indexes. Expiration cleanup must be handled by an application or scheduled job.\",\n ],\n };\n }\n\n private table(collection: StorageCollectionName): string {\n return quoteIdentifier(`${this.prefix}${collection}`);\n }\n\n private prepare(\n sql: string,\n values: readonly unknown[] = [],\n ): D1PreparedStatementLike {\n return this.database.prepare(sql).bind(...values);\n }\n\n private async run(\n sql: string,\n values: readonly unknown[] = [],\n ): Promise<D1ResultLike> {\n return this.prepare(sql, values).run();\n }\n\n private async first<T>(\n sql: string,\n values: readonly unknown[] = [],\n ): Promise<T | null> {\n return this.prepare(sql, values).first<T>();\n }\n\n private async all<T>(\n sql: string,\n values: readonly unknown[] = [],\n ): Promise<readonly T[]> {\n const result = await this.prepare(sql, values).all<T>();\n\n return result.results ?? [];\n }\n\n private insertStatement<T extends StorageRecord>(\n collection: StorageCollectionName,\n record: Parameters<StorageAdapter[\"insert\"]>[1],\n ): {\n sql: string;\n values: readonly unknown[];\n record: T;\n } {\n const input = record as StorageRecord & {\n id: string;\n [key: string]: unknown;\n };\n\n const now = new Date();\n const createdAt = input.createdAt ?? now;\n const updatedAt = input.updatedAt ?? now;\n const revision = input.revision ?? 1;\n\n const {\n id,\n createdAt: _createdAt,\n updatedAt: _updatedAt,\n revision: _revision,\n ...data\n } = input;\n\n const result = {\n id,\n createdAt,\n updatedAt,\n revision,\n ...data,\n } as T;\n\n return {\n sql: `INSERT INTO ${this.table(collection)}\n (id, created_at, updated_at, revision, data)\n VALUES (?, ?, ?, ?, ?)`,\n values: [\n id,\n createdAt.toISOString(),\n updatedAt.toISOString(),\n revision,\n serialize(data),\n ],\n record: result,\n };\n }\n\n async registerCollection<T extends StorageRecord>(\n definition: StorageCollection<T>,\n ): Promise<void> {\n const table = this.table(definition.name);\n\n await this.run(`\n CREATE TABLE IF NOT EXISTS ${table} (\n id TEXT PRIMARY KEY NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n revision INTEGER NOT NULL CHECK (revision > 0),\n data TEXT NOT NULL CHECK (json_valid(data))\n )\n `);\n\n for (const index of definition.indexes ?? []) {\n const fields = index.fields.map((field) => {\n const fieldName = String(field);\n return sqlField(fieldName);\n });\n\n const unique = index.unique ? \"UNIQUE \" : \"\";\n const name = quoteIdentifier(\n `${this.prefix}${definition.name}_${index.name}`,\n );\n\n await this.run(`\n CREATE ${unique}INDEX IF NOT EXISTS ${name}\n ON ${table} (${fields.join(\", \")})\n `);\n }\n }\n\n async findById<T extends StorageRecord>(\n collection: StorageCollectionName,\n id: string,\n ): Promise<T | null> {\n const row = await this.first<D1Row>(\n `SELECT * FROM ${this.table(collection)} WHERE id = ? LIMIT 1`,\n [id],\n );\n\n return row ? toRecord<T>(row) : null;\n }\n\n async findOne<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n ): Promise<T | null> {\n validateQuery(query);\n\n const where = buildWhere(query.where);\n\n const row = await this.first<D1Row>(\n `SELECT * FROM ${this.table(collection)}\n WHERE ${where.sql}\n ORDER BY ${buildOrderBy(query.orderBy)}\n LIMIT 1`,\n where.values,\n );\n\n return row ? toRecord<T>(row) : null;\n }\n\n async findMany<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T> = {},\n ): Promise<readonly T[]> {\n validateQuery(query);\n\n const where = buildWhere(query.where);\n\n const values = [...where.values];\n let pagination = \"\";\n\n if (query.limit !== undefined) {\n pagination += \" LIMIT ?\";\n values.push(query.limit);\n } else if (query.offset !== undefined) {\n pagination += \" LIMIT -1\";\n }\n\n if (query.offset !== undefined) {\n pagination += \" OFFSET ?\";\n values.push(query.offset);\n }\n\n const rows = await this.all<D1Row>(\n `SELECT * FROM ${this.table(collection)}\n WHERE ${where.sql}\n ORDER BY ${buildOrderBy(query.orderBy)}\n ${pagination}`,\n values,\n );\n\n return rows.map((row) => toRecord<T>(row));\n }\n\n async findPage<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageCursorQuery<T>,\n ): Promise<StoragePage<T>> {\n if (!Number.isInteger(query.first) || query.first < 1) {\n throw new StorageValidationError(\n \"Cursor page size must be a positive integer.\",\n );\n }\n\n const orderBy = query.orderBy ?? [\n { field: \"id\" as keyof T & string, direction: \"asc\" as const },\n ];\n\n const base = buildWhere(query.where);\n let whereSql = base.sql;\n const values = [...base.values];\n\n if (query.after) {\n const cursor = decodeCursor(query.after);\n const after = buildCursorWhere(orderBy, cursor.values);\n\n whereSql = `(${whereSql}) AND ${after.sql}`;\n values.push(...after.values);\n }\n\n values.push(query.first + 1);\n\n const rows = await this.all<D1Row>(\n `SELECT * FROM ${this.table(collection)}\n WHERE ${whereSql}\n ORDER BY ${buildOrderBy(orderBy)}\n LIMIT ?`,\n values,\n );\n\n const hasNextPage = rows.length > query.first;\n const items = rows.slice(0, query.first);\n const last = items.at(-1);\n\n const cursorFields = [...orderBy.map((item) => String(item.field))];\n\n if (!cursorFields.includes(\"id\")) cursorFields.push(\"id\");\n\n const nextCursor =\n hasNextPage && last\n ? encodeCursor({\n values: cursorFields.map((field) => {\n if (field === \"id\") return last.id;\n if (field === \"createdAt\") return last.created_at;\n if (field === \"updatedAt\") return last.updated_at;\n if (field === \"revision\") return last.revision;\n\n return deserialize<Record<string, unknown>>(last.data)[field];\n }),\n })\n : null;\n\n return {\n items: items.map((row) => toRecord<T>(row)),\n hasNextPage,\n nextCursor,\n };\n }\n\n async insert<T extends StorageRecord>(\n collection: StorageCollectionName,\n record: Parameters<StorageAdapter[\"insert\"]>[1],\n ): Promise<T> {\n const statement = this.insertStatement<T>(collection, record);\n\n try {\n await this.run(statement.sql, statement.values);\n return statement.record;\n } catch (error) {\n return mapD1Error(error);\n }\n }\n\n async upsert<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n record: Parameters<StorageAdapter[\"insert\"]>[1],\n ): Promise<T> {\n const existing = await this.findOne<T>(collection, query);\n\n if (existing) {\n const changes = {\n ...(record as Record<string, unknown>),\n } as StorageUpdate<T>;\n\n const updated = await this.update<T>(collection, existing.id, changes);\n\n if (!updated) throw new StorageConflictError();\n\n return updated;\n }\n\n return this.insert<T>(collection, record);\n }\n\n private buildUpdate(\n collection: StorageCollectionName,\n id: string,\n changes: Record<string, unknown>,\n condition?: StorageCondition,\n ): { sql: string; values: unknown[] } {\n const entries = Object.entries(changes);\n const expressions = entries.map(\n ([field]) => `data = json_set(data, ?, json(?))`,\n );\n\n const assignments: string[] = [];\n\n if (entries.length > 0) {\n assignments.push(\n `data = ${entries.reduce(\n (expression, _entry, index) => `json_set(${expression}, ?, json(?))`,\n \"data\",\n )}`,\n );\n }\n\n assignments.push(\"updated_at = ?\");\n assignments.push(\"revision = revision + 1\");\n\n const where = [\"id = ?\"];\n const values: unknown[] = [];\n\n for (const [field, value] of entries) {\n values.push(jsonPath(field), serialize(value));\n }\n\n values.push(new Date().toISOString(), id);\n\n if (condition?.expectedRevision !== undefined) {\n where.push(\"revision = ?\");\n values.push(condition.expectedRevision);\n }\n\n if (condition?.expectedUpdatedAt !== undefined) {\n where.push(\"updated_at = ?\");\n values.push(condition.expectedUpdatedAt.toISOString());\n }\n\n return {\n sql: `UPDATE ${this.table(collection)}\n SET ${assignments.join(\", \")}\n WHERE ${where.join(\" AND \")}`,\n values,\n };\n }\n\n async update<T extends StorageRecord>(\n collection: StorageCollectionName,\n id: string,\n changes: StorageUpdate<T>,\n condition?: StorageCondition,\n ): Promise<T | null> {\n const clean = cleanChanges(changes);\n const statement = this.buildUpdate(collection, id, clean, condition);\n\n const result = await this.run(statement.sql, statement.values);\n\n if (getChanges(result) === 0) {\n if (condition && (await this.findById(collection, id))) {\n throw new StorageConflictError();\n }\n\n return null;\n }\n\n return this.findById<T>(collection, id);\n }\n\n async updateMany<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n changes: StorageUpdate<T>,\n ): Promise<number> {\n validateQuery(query);\n\n const clean = cleanChanges(changes);\n const entries = Object.entries(clean);\n const assignments = entries.length\n ? `data = ${entries.reduce(\n (expression) => `json_set(${expression}, ?, json(?))`,\n \"data\",\n )}, `\n : \"\";\n\n const values: unknown[] = [];\n\n for (const [field, value] of entries) {\n values.push(jsonPath(field), serialize(value));\n }\n\n values.push(new Date().toISOString());\n\n const where = buildWhere(query.where);\n values.push(...where.values);\n\n const result = await this.run(\n `UPDATE ${this.table(collection)}\n SET ${assignments}updated_at = ?, revision = revision + 1\n WHERE ${where.sql}`,\n values,\n );\n\n return getChanges(result);\n }\n\n async delete<T extends StorageRecord>(\n collection: StorageCollectionName,\n id: string,\n condition?: StorageCondition,\n ): Promise<boolean> {\n const where = [\"id = ?\"];\n const values: unknown[] = [id];\n\n if (condition?.expectedRevision !== undefined) {\n where.push(\"revision = ?\");\n values.push(condition.expectedRevision);\n }\n\n if (condition?.expectedUpdatedAt !== undefined) {\n where.push(\"updated_at = ?\");\n values.push(condition.expectedUpdatedAt.toISOString());\n }\n\n const result = await this.run(\n `DELETE FROM ${this.table(collection)}\n WHERE ${where.join(\" AND \")}`,\n values,\n );\n\n if (getChanges(result) > 0) return true;\n\n if (condition && (await this.findById(collection, id))) {\n throw new StorageConflictError();\n }\n\n return false;\n }\n\n async deleteMany<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n ): Promise<number> {\n validateQuery(query);\n\n const where = buildWhere(query.where);\n const result = await this.run(\n `DELETE FROM ${this.table(collection)} WHERE ${where.sql}`,\n where.values,\n );\n\n return getChanges(result);\n }\n\n async exists<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n ): Promise<boolean> {\n validateQuery(query);\n\n const where = buildWhere(query.where);\n const result = await this.first<{ found: number }>(\n `SELECT 1 AS found FROM ${this.table(collection)}\n WHERE ${where.sql} LIMIT 1`,\n where.values,\n );\n\n return result !== null;\n }\n\n async count<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T> = {},\n ): Promise<number> {\n validateQuery(query);\n\n const where = buildWhere(query.where);\n const result = await this.first<{ total: number }>(\n `SELECT COUNT(*) AS total FROM ${this.table(collection)}\n WHERE ${where.sql}`,\n where.values,\n );\n\n return Number(result?.total ?? 0);\n }\n\n async atomicBatch<T extends StorageRecord>(\n operations: readonly StorageBatchOperation<T>[],\n ): Promise<StorageBatchResult<T>> {\n const assertionTable = quoteIdentifier(`${this.prefix}atomic_assertions`);\n\n await this.run(`\n CREATE TABLE IF NOT EXISTS ${assertionTable} (\n value INTEGER NOT NULL CHECK (value = 1)\n )\n `);\n\n const statements: D1PreparedStatementLike[] = [];\n const resultIndexes: number[] = [];\n\n for (const operation of operations) {\n const table = this.table(operation.collection);\n\n if (\n operation.type !== \"insert\" &&\n (operation.condition?.expectedRevision !== undefined ||\n operation.condition?.expectedUpdatedAt !== undefined)\n ) {\n const conditions = [\"id = ?\"];\n const values: unknown[] = [operation.id];\n\n if (operation.condition.expectedRevision !== undefined) {\n conditions.push(\"revision = ?\");\n values.push(operation.condition.expectedRevision);\n }\n\n if (operation.condition.expectedUpdatedAt !== undefined) {\n conditions.push(\"updated_at = ?\");\n values.push(operation.condition.expectedUpdatedAt.toISOString());\n }\n\n statements.push(\n this.prepare(\n `INSERT INTO ${assertionTable}(value)\n SELECT CASE WHEN EXISTS (\n SELECT 1 FROM ${table}\n WHERE ${conditions.join(\" AND \")}\n ) THEN 1 ELSE 0 END`,\n values,\n ),\n );\n\n statements.push(this.prepare(`DELETE FROM ${assertionTable}`));\n }\n\n if (operation.type === \"insert\") {\n const statement = this.insertStatement<T>(\n operation.collection,\n operation.record,\n );\n\n statements.push(this.prepare(statement.sql, statement.values));\n\n resultIndexes.push(statements.length - 1);\n continue;\n }\n\n if (operation.type === \"update\") {\n const statement = this.buildUpdate(\n operation.collection,\n operation.id,\n cleanChanges(operation.changes),\n operation.condition,\n );\n\n statements.push(this.prepare(statement.sql, statement.values));\n\n resultIndexes.push(statements.length - 1);\n continue;\n }\n\n const conditions = [\"id = ?\"];\n const values: unknown[] = [operation.id];\n\n if (operation.condition?.expectedRevision !== undefined) {\n conditions.push(\"revision = ?\");\n values.push(operation.condition.expectedRevision);\n }\n\n if (operation.condition?.expectedUpdatedAt !== undefined) {\n conditions.push(\"updated_at = ?\");\n values.push(operation.condition.expectedUpdatedAt.toISOString());\n }\n\n statements.push(\n this.prepare(\n `DELETE FROM ${table} WHERE ${conditions.join(\" AND \")}`,\n values,\n ),\n );\n\n resultIndexes.push(statements.length - 1);\n }\n\n try {\n const batchResults = await this.database.batch(statements);\n const results: StorageBatchResult<T>[\"results\"][number][] = [];\n\n for (const [index, operation] of operations.entries()) {\n const resultIndex = resultIndexes[index];\n const result =\n resultIndex === undefined ? undefined : batchResults[resultIndex];\n\n if (!result) {\n throw new StorageAtomicityError(\n \"D1 batch did not return a result for every operation.\",\n );\n }\n\n if (operation.type === \"insert\") {\n const input = operation.record as StorageRecord & {\n id: string;\n };\n\n const inserted = await this.findById<T>(\n operation.collection,\n input.id,\n );\n\n if (!inserted) {\n throw new StorageAtomicityError(\n \"D1 batch committed but the inserted record could not be read.\",\n );\n }\n\n results.push({ type: \"insert\", record: inserted });\n continue;\n }\n\n if (operation.type === \"update\") {\n results.push({\n type: \"update\",\n record: await this.findById<T>(operation.collection, operation.id),\n });\n continue;\n }\n\n results.push({\n type: \"delete\",\n deleted: getChanges(result) > 0,\n });\n }\n\n return { results };\n } catch (error) {\n const message = error instanceof Error ? error.message.toLowerCase() : \"\";\n\n if (message.includes(\"check constraint\")) {\n throw new StorageConflictError();\n }\n\n return mapD1Error(error);\n }\n }\n}\n"],"mappings":"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"}
|