opacacms 0.1.9 → 0.1.11
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/api.js +1 -1
- package/dist/{chunk-hmhcense.js → chunk-0nf7fe26.js} +7 -2
- package/dist/chunk-eqtsfyjf.js +499 -0
- package/dist/{chunk-dmhhf96k.js → chunk-n8aekdnr.js} +4 -1
- package/dist/db/better-sqlite.js +14 -0
- package/dist/db/index.js +9 -491
- package/dist/db/sqlite.js +1 -1
- package/dist/runtimes/bun.js +1 -1
- package/dist/runtimes/cloudflare-workers.js +1 -1
- package/dist/runtimes/next.js +1 -1
- package/dist/runtimes/node.js +1 -1
- package/dist/server/middlewares/rate-limit.d.ts +1 -1
- package/dist/server.js +1 -1
- package/package.json +1 -1
- package/src/db/better-sqlite.ts +10 -8
- package/src/db/sqlite.ts +10 -8
- package/src/schema/index.ts +0 -1
- package/src/server/middlewares/rate-limit.ts +9 -3
package/dist/api.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
init_system_schema
|
|
15
15
|
} from "./chunk-ybbbqj63.js";
|
|
16
16
|
import {
|
|
17
|
+
__require,
|
|
17
18
|
__toCommonJS
|
|
18
19
|
} from "./chunk-8sqjbsgt.js";
|
|
19
20
|
|
|
@@ -1000,7 +1001,6 @@ function createDatabaseInitMiddleware(config, state) {
|
|
|
1000
1001
|
}
|
|
1001
1002
|
|
|
1002
1003
|
// src/server/middlewares/rate-limit.ts
|
|
1003
|
-
import { WorkersKVStore } from "@hono-rate-limiter/cloudflare";
|
|
1004
1004
|
import { rateLimiter } from "hono-rate-limiter";
|
|
1005
1005
|
function createRateLimitMiddleware(config) {
|
|
1006
1006
|
const rateLimitConfig = config.api?.rateLimit;
|
|
@@ -1028,7 +1028,12 @@ function createRateLimitMiddleware(config) {
|
|
|
1028
1028
|
if (!resolvedStore && c.env) {
|
|
1029
1029
|
const kvBindingKey = Object.keys(c.env).find((key) => key.startsWith("OPACA_") && c.env[key]?.put && c.env[key]?.get);
|
|
1030
1030
|
if (kvBindingKey) {
|
|
1031
|
-
|
|
1031
|
+
try {
|
|
1032
|
+
const { WorkersKVStore } = await import("@hono-rate-limiter/cloudflare");
|
|
1033
|
+
resolvedStore = new WorkersKVStore({ namespace: c.env[kvBindingKey] });
|
|
1034
|
+
} catch (e) {
|
|
1035
|
+
logger.error("Failed to load @hono-rate-limiter/cloudflare dynamic import. Make sure you are in a Cloudflare environment.", e);
|
|
1036
|
+
}
|
|
1032
1037
|
}
|
|
1033
1038
|
}
|
|
1034
1039
|
const limiter = rateLimiter({
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildKyselyWhere,
|
|
3
|
+
flattenPayload,
|
|
4
|
+
pushSchema,
|
|
5
|
+
unflattenRow
|
|
6
|
+
} from "./chunk-6ew02s0c.js";
|
|
7
|
+
import {
|
|
8
|
+
BaseDatabaseAdapter
|
|
9
|
+
} from "./chunk-s8mqwnm1.js";
|
|
10
|
+
import {
|
|
11
|
+
logger
|
|
12
|
+
} from "./chunk-62ev8gnc.js";
|
|
13
|
+
import {
|
|
14
|
+
flattenFields,
|
|
15
|
+
toSnakeCase
|
|
16
|
+
} from "./chunk-cvdd4eqh.js";
|
|
17
|
+
import {
|
|
18
|
+
__require
|
|
19
|
+
} from "./chunk-8sqjbsgt.js";
|
|
20
|
+
|
|
21
|
+
// src/db/better-sqlite.ts
|
|
22
|
+
import fs from "node:fs/promises";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { createRequire } from "node:module";
|
|
25
|
+
import { CompiledQuery, FileMigrationProvider, Kysely, Migrator, SqliteDialect } from "kysely";
|
|
26
|
+
var require2 = createRequire(import.meta.url);
|
|
27
|
+
var Database = require2("better-sqlite3");
|
|
28
|
+
|
|
29
|
+
class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
30
|
+
name = "better-sqlite3";
|
|
31
|
+
_rawDb;
|
|
32
|
+
_db;
|
|
33
|
+
_collections = [];
|
|
34
|
+
push;
|
|
35
|
+
migrationDir;
|
|
36
|
+
pushDestructive;
|
|
37
|
+
get raw() {
|
|
38
|
+
return this._rawDb;
|
|
39
|
+
}
|
|
40
|
+
get db() {
|
|
41
|
+
return this._db;
|
|
42
|
+
}
|
|
43
|
+
constructor(path2, options) {
|
|
44
|
+
super();
|
|
45
|
+
this._rawDb = new Database(path2);
|
|
46
|
+
this._db = new Kysely({
|
|
47
|
+
dialect: new SqliteDialect({
|
|
48
|
+
database: this._rawDb
|
|
49
|
+
})
|
|
50
|
+
});
|
|
51
|
+
this.push = options?.push ?? true;
|
|
52
|
+
this.pushDestructive = options?.pushDestructive ?? false;
|
|
53
|
+
this.migrationDir = options?.migrationDir ?? "./migrations";
|
|
54
|
+
}
|
|
55
|
+
async connect() {}
|
|
56
|
+
async disconnect() {
|
|
57
|
+
await this._db.destroy();
|
|
58
|
+
}
|
|
59
|
+
async unsafe(query, params) {
|
|
60
|
+
const compiled = CompiledQuery.raw(query, params || []);
|
|
61
|
+
const result = await this._db.executeQuery(compiled);
|
|
62
|
+
return result.rows;
|
|
63
|
+
}
|
|
64
|
+
async coerceData(collection, data) {
|
|
65
|
+
const colDef = this._collections.find((c) => c.slug === collection);
|
|
66
|
+
if (!colDef)
|
|
67
|
+
return data;
|
|
68
|
+
const result = { ...data };
|
|
69
|
+
const { flattenFields: flattenFields2 } = await import("./chunk-xg35h5a3.js");
|
|
70
|
+
const allFields = flattenFields2(colDef.fields);
|
|
71
|
+
for (const field of allFields) {
|
|
72
|
+
const colName = toSnakeCase(field.name);
|
|
73
|
+
if (!(colName in result))
|
|
74
|
+
continue;
|
|
75
|
+
const value = result[colName];
|
|
76
|
+
if (value === undefined || value === null)
|
|
77
|
+
continue;
|
|
78
|
+
switch (field.type) {
|
|
79
|
+
case "boolean":
|
|
80
|
+
result[colName] = value ? 1 : 0;
|
|
81
|
+
break;
|
|
82
|
+
case "number":
|
|
83
|
+
result[colName] = Number(value);
|
|
84
|
+
break;
|
|
85
|
+
case "date":
|
|
86
|
+
if (value instanceof Date) {
|
|
87
|
+
result[colName] = value.toISOString();
|
|
88
|
+
} else if (typeof value === "string") {
|
|
89
|
+
result[colName] = new Date(value).toISOString();
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
case "richtext":
|
|
93
|
+
case "json":
|
|
94
|
+
case "file":
|
|
95
|
+
if (typeof value === "object") {
|
|
96
|
+
result[colName] = JSON.stringify(value);
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
async count(collection, query) {
|
|
104
|
+
let qb = this._db.selectFrom(collection).select((eb) => eb.fn.count("id").as("count"));
|
|
105
|
+
if (query && Object.keys(query).length > 0) {
|
|
106
|
+
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
107
|
+
}
|
|
108
|
+
const result = await qb.executeTakeFirst();
|
|
109
|
+
return Number(result?.count || 0);
|
|
110
|
+
}
|
|
111
|
+
async create(collection, data) {
|
|
112
|
+
return this._db.transaction().execute(async (tx) => {
|
|
113
|
+
const colDef = this._collections.find((c) => c.slug === collection);
|
|
114
|
+
const jsonFields = colDef?.fields.filter((f) => ["richtext", "json", "file"].includes(f.type)).map((f) => f.name) || [];
|
|
115
|
+
const flatData = flattenPayload(data, "", jsonFields);
|
|
116
|
+
for (const field of jsonFields) {
|
|
117
|
+
if (flatData[field] && typeof flatData[field] === "object") {
|
|
118
|
+
flatData[field] = JSON.stringify(flatData[field]);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const hasManyData = {};
|
|
122
|
+
const blocksData = {};
|
|
123
|
+
for (const key in flatData) {
|
|
124
|
+
if (Array.isArray(flatData[key])) {
|
|
125
|
+
const isHasMany = colDef?.fields.some((f) => f.type === "relationship" && ("hasMany" in f) && f.hasMany && f.name === key);
|
|
126
|
+
if (isHasMany) {
|
|
127
|
+
hasManyData[key] = flatData[key];
|
|
128
|
+
} else {
|
|
129
|
+
blocksData[key] = flatData[key];
|
|
130
|
+
}
|
|
131
|
+
delete flatData[key];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!flatData.id)
|
|
135
|
+
flatData.id = crypto.randomUUID();
|
|
136
|
+
const dbFields = flattenFields(colDef?.fields || []);
|
|
137
|
+
const validCols = new Set(dbFields.map((f) => toSnakeCase(f.name)));
|
|
138
|
+
validCols.add("id");
|
|
139
|
+
const ts = colDef?.timestamps !== false;
|
|
140
|
+
if (ts) {
|
|
141
|
+
const config = typeof colDef?.timestamps === "object" ? colDef.timestamps : {};
|
|
142
|
+
validCols.add(toSnakeCase(config.createdAt || "createdAt"));
|
|
143
|
+
validCols.add(toSnakeCase(config.updatedAt || "updatedAt"));
|
|
144
|
+
}
|
|
145
|
+
const filteredData = {};
|
|
146
|
+
for (const col of Object.keys(flatData)) {
|
|
147
|
+
if (validCols.has(col)) {
|
|
148
|
+
filteredData[col] = flatData[col];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const coercedData = await this.coerceData(collection, filteredData);
|
|
152
|
+
await tx.insertInto(collection).values(coercedData).execute();
|
|
153
|
+
for (const [key, values] of Object.entries(hasManyData)) {
|
|
154
|
+
const joinTableName = `${collection}_${toSnakeCase(key)}_relations`.toLowerCase();
|
|
155
|
+
if (values.length > 0) {
|
|
156
|
+
const joinData = values.map((val, idx) => {
|
|
157
|
+
const tId = typeof val === "object" ? val.id : val;
|
|
158
|
+
return { id: crypto.randomUUID(), source_id: flatData.id, target_id: tId, order: idx };
|
|
159
|
+
});
|
|
160
|
+
await tx.insertInto(joinTableName).values(joinData).execute();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const [key, blocks] of Object.entries(blocksData)) {
|
|
164
|
+
for (let i = 0;i < blocks.length; i++) {
|
|
165
|
+
const block = blocks[i];
|
|
166
|
+
if (!block.blockType)
|
|
167
|
+
continue;
|
|
168
|
+
const blockTableName = `${collection}_${toSnakeCase(key)}_${block.blockType}`.toLowerCase();
|
|
169
|
+
const bId = block.id || crypto.randomUUID();
|
|
170
|
+
const blockDef = colDef?.fields.find((f) => f.name === key && f.type === "blocks");
|
|
171
|
+
const blockConfig = blockDef?.blocks?.find((b) => b.slug === block.blockType);
|
|
172
|
+
const blockJsonFields = blockConfig?.fields.filter((f) => ["richtext", "json", "file"].includes(f.type)).map((f) => f.name) || [];
|
|
173
|
+
const blockFlatData = flattenPayload({ ...block, id: bId }, "", blockJsonFields);
|
|
174
|
+
for (const f of blockJsonFields) {
|
|
175
|
+
if (blockFlatData[f] && typeof blockFlatData[f] === "object") {
|
|
176
|
+
blockFlatData[f] = JSON.stringify(blockFlatData[f]);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
delete blockFlatData.blockType;
|
|
180
|
+
const coercedBlockData = await this.coerceData(blockTableName, {
|
|
181
|
+
...blockFlatData,
|
|
182
|
+
_parent_id: flatData.id,
|
|
183
|
+
_order: i,
|
|
184
|
+
block_type: block.blockType
|
|
185
|
+
});
|
|
186
|
+
await tx.insertInto(blockTableName).values(coercedBlockData).execute();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return this.findOne(collection, { id: flatData.id }, tx);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
async findOne(collection, query, tx) {
|
|
193
|
+
const executor = tx || this._db;
|
|
194
|
+
let qb = executor.selectFrom(collection).selectAll();
|
|
195
|
+
if (query && Object.keys(query).length > 0) {
|
|
196
|
+
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
197
|
+
}
|
|
198
|
+
const row = await qb.executeTakeFirst();
|
|
199
|
+
if (!row)
|
|
200
|
+
return null;
|
|
201
|
+
const unflattened = unflattenRow(row);
|
|
202
|
+
const colDef = this._collections.find((c) => c.slug === collection);
|
|
203
|
+
if (colDef) {
|
|
204
|
+
const { getRelationalFields, toSnakeCase: toSnakeCase2 } = await import("./chunk-xg35h5a3.js");
|
|
205
|
+
const relationalFields = getRelationalFields(colDef.fields);
|
|
206
|
+
for (const field of relationalFields) {
|
|
207
|
+
if (!field.name)
|
|
208
|
+
continue;
|
|
209
|
+
const snakeName = toSnakeCase2(field.name);
|
|
210
|
+
if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
|
|
211
|
+
const joinTableName = `${collection}_${snakeName}_relations`.toLowerCase();
|
|
212
|
+
try {
|
|
213
|
+
const relations = await executor.selectFrom(joinTableName).selectAll().where("source_id", "=", row.id).orderBy("order", "asc").execute();
|
|
214
|
+
unflattened[field.name] = relations.map((r) => r.target_id);
|
|
215
|
+
} catch (e) {}
|
|
216
|
+
} else if (field.type === "blocks" && field.blocks) {
|
|
217
|
+
const blockData = [];
|
|
218
|
+
for (const b of field.blocks) {
|
|
219
|
+
const blockTableName = `${collection}_${snakeName}_${b.slug}`.toLowerCase();
|
|
220
|
+
try {
|
|
221
|
+
const blocks = await executor.selectFrom(blockTableName).selectAll().where("_parent_id", "=", row.id).execute();
|
|
222
|
+
for (const blk of blocks) {
|
|
223
|
+
const uf = unflattenRow(blk);
|
|
224
|
+
uf.blockType = blk.block_type;
|
|
225
|
+
blockData.push(uf);
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {}
|
|
228
|
+
}
|
|
229
|
+
blockData.sort((a, b) => a._order - b._order);
|
|
230
|
+
blockData.forEach((b) => {
|
|
231
|
+
delete b._order;
|
|
232
|
+
delete b._parent_id;
|
|
233
|
+
});
|
|
234
|
+
unflattened[field.name] = blockData;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return unflattened;
|
|
239
|
+
}
|
|
240
|
+
async find(collection, query, options) {
|
|
241
|
+
const page = options?.page || 1;
|
|
242
|
+
const limit = options?.limit || 10;
|
|
243
|
+
const offset = (page - 1) * limit;
|
|
244
|
+
const total = await this.count(collection, query);
|
|
245
|
+
let qb = this._db.selectFrom(collection).selectAll().limit(limit).offset(offset);
|
|
246
|
+
if (query && Object.keys(query).length > 0) {
|
|
247
|
+
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
248
|
+
}
|
|
249
|
+
if (options?.sort) {
|
|
250
|
+
const isDesc = options.sort.startsWith("-");
|
|
251
|
+
const col = isDesc ? options.sort.substring(1) : options.sort;
|
|
252
|
+
qb = qb.orderBy(col, isDesc ? "desc" : "asc");
|
|
253
|
+
} else {
|
|
254
|
+
qb = qb.orderBy("created_at", "desc");
|
|
255
|
+
}
|
|
256
|
+
const rows = await qb.execute();
|
|
257
|
+
const docs = await Promise.all(rows.map((row) => this.findOne(collection, { id: row.id })));
|
|
258
|
+
const totalPages = Math.ceil(total / limit);
|
|
259
|
+
return {
|
|
260
|
+
docs: docs.filter(Boolean),
|
|
261
|
+
totalDocs: total,
|
|
262
|
+
limit,
|
|
263
|
+
totalPages,
|
|
264
|
+
page,
|
|
265
|
+
pagingCounter: offset + 1,
|
|
266
|
+
hasNextPage: page * limit < total,
|
|
267
|
+
hasPrevPage: page > 1,
|
|
268
|
+
prevPage: page > 1 ? page - 1 : null,
|
|
269
|
+
nextPage: page < totalPages ? page + 1 : null
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
async update(collection, query, data) {
|
|
273
|
+
return this._db.transaction().execute(async (tx) => {
|
|
274
|
+
let normalizedQuery = query;
|
|
275
|
+
if (typeof query !== "object" || query === null) {
|
|
276
|
+
normalizedQuery = { id: query };
|
|
277
|
+
}
|
|
278
|
+
const current = await this.findOne(collection, normalizedQuery, tx);
|
|
279
|
+
if (!current)
|
|
280
|
+
throw new Error("Document not found");
|
|
281
|
+
const colDef = this._collections.find((c) => c.slug === collection);
|
|
282
|
+
const jsonFields = colDef?.fields.filter((f) => ["richtext", "json", "file"].includes(f.type)).map((f) => f.name) || [];
|
|
283
|
+
const flatData = flattenPayload(data, "", jsonFields);
|
|
284
|
+
for (const field of jsonFields) {
|
|
285
|
+
if (flatData[field] && typeof flatData[field] === "object") {
|
|
286
|
+
flatData[field] = JSON.stringify(flatData[field]);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const hasManyData = {};
|
|
290
|
+
const blocksData = {};
|
|
291
|
+
for (const key in flatData) {
|
|
292
|
+
if (Array.isArray(flatData[key])) {
|
|
293
|
+
const isHasMany = colDef?.fields.some((f) => f.type === "relationship" && ("hasMany" in f) && f.hasMany && f.name === key);
|
|
294
|
+
if (isHasMany) {
|
|
295
|
+
hasManyData[key] = flatData[key];
|
|
296
|
+
} else {
|
|
297
|
+
blocksData[key] = flatData[key];
|
|
298
|
+
}
|
|
299
|
+
delete flatData[key];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
flatData.updated_at = new Date().toISOString();
|
|
303
|
+
if (Object.keys(flatData).length > 0) {
|
|
304
|
+
const coercedData = await this.coerceData(collection, flatData);
|
|
305
|
+
await tx.updateTable(collection).set(coercedData).where("id", "=", current.id).execute();
|
|
306
|
+
}
|
|
307
|
+
for (const [key, values] of Object.entries(hasManyData)) {
|
|
308
|
+
const joinTableName = `${collection}_${toSnakeCase(key)}_relations`.toLowerCase();
|
|
309
|
+
await tx.deleteFrom(joinTableName).where("source_id", "=", current.id).execute();
|
|
310
|
+
if (values.length > 0) {
|
|
311
|
+
const joinData = values.map((val, idx) => {
|
|
312
|
+
const tId = typeof val === "object" ? val.id : val;
|
|
313
|
+
return { id: crypto.randomUUID(), source_id: current.id, target_id: tId, order: idx };
|
|
314
|
+
});
|
|
315
|
+
await tx.insertInto(joinTableName).values(joinData).execute();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
for (const [key, blocks] of Object.entries(blocksData)) {
|
|
319
|
+
const fieldDef = colDef?.fields.find((f) => f.name === key);
|
|
320
|
+
if (fieldDef?.type === "blocks" && fieldDef.blocks) {
|
|
321
|
+
for (const b of fieldDef.blocks) {
|
|
322
|
+
const blockTableName = `${collection}_${toSnakeCase(key)}_${b.slug}`.toLowerCase();
|
|
323
|
+
try {
|
|
324
|
+
await tx.deleteFrom(blockTableName).where("_parent_id", "=", current.id).execute();
|
|
325
|
+
} catch (e) {}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
for (let i = 0;i < blocks.length; i++) {
|
|
329
|
+
const block = blocks[i];
|
|
330
|
+
if (!block.blockType)
|
|
331
|
+
continue;
|
|
332
|
+
const blockTableName = `${collection}_${toSnakeCase(key)}_${block.blockType}`.toLowerCase();
|
|
333
|
+
const bId = block.id || crypto.randomUUID();
|
|
334
|
+
const blockConfig = fieldDef?.blocks?.find((b) => b.slug === block.blockType);
|
|
335
|
+
const blockJsonFields = blockConfig?.fields.filter((f) => ["richtext", "json", "file"].includes(f.type)).map((f) => f.name) || [];
|
|
336
|
+
const blockFlatData = flattenPayload({ ...block, id: bId }, "", blockJsonFields);
|
|
337
|
+
for (const f of blockJsonFields) {
|
|
338
|
+
if (blockFlatData[f] && typeof blockFlatData[f] === "object") {
|
|
339
|
+
blockFlatData[f] = JSON.stringify(blockFlatData[f]);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
delete blockFlatData.blockType;
|
|
343
|
+
const coercedBlockData = await this.coerceData(blockTableName, {
|
|
344
|
+
...blockFlatData,
|
|
345
|
+
_parent_id: current.id,
|
|
346
|
+
_order: i,
|
|
347
|
+
block_type: block.blockType
|
|
348
|
+
});
|
|
349
|
+
await tx.insertInto(blockTableName).values(coercedBlockData).execute();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return this.findOne(collection, { id: current.id }, tx);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
async delete(collection, query) {
|
|
356
|
+
let normalizedQuery = query;
|
|
357
|
+
if (typeof query !== "object" || query === null) {
|
|
358
|
+
normalizedQuery = { id: query };
|
|
359
|
+
}
|
|
360
|
+
const current = await this.findOne(collection, normalizedQuery);
|
|
361
|
+
if (!current)
|
|
362
|
+
return false;
|
|
363
|
+
await this._db.transaction().execute(async (tx) => {
|
|
364
|
+
const colDef = this._collections.find((c) => c.slug === collection);
|
|
365
|
+
if (colDef) {
|
|
366
|
+
for (const field of colDef.fields) {
|
|
367
|
+
const snakeName = toSnakeCase(field.name);
|
|
368
|
+
if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
|
|
369
|
+
const joinTableName = `${collection}_${snakeName}_relations`.toLowerCase();
|
|
370
|
+
try {
|
|
371
|
+
await tx.deleteFrom(joinTableName).where("source_id", "=", current.id).execute();
|
|
372
|
+
} catch (e) {}
|
|
373
|
+
} else if (field.type === "blocks" && field.blocks) {
|
|
374
|
+
for (const b of field.blocks) {
|
|
375
|
+
const blockTableName = `${collection}_${snakeName}_${b.slug}`.toLowerCase();
|
|
376
|
+
try {
|
|
377
|
+
await tx.deleteFrom(blockTableName).where("_parent_id", "=", current.id).execute();
|
|
378
|
+
} catch (e) {}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
await tx.deleteFrom(collection).where("id", "=", current.id).execute();
|
|
384
|
+
});
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
async updateMany(collection, query, data) {
|
|
388
|
+
return this._db.transaction().execute(async (tx) => {
|
|
389
|
+
let qb = tx.updateTable(collection);
|
|
390
|
+
if (query && Object.keys(query).length > 0) {
|
|
391
|
+
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
392
|
+
}
|
|
393
|
+
const colDef = this._collections.find((c) => c.slug === collection);
|
|
394
|
+
const jsonFields = colDef?.fields.filter((f) => ["richtext", "json", "file"].includes(f.type)).map((f) => f.name) || [];
|
|
395
|
+
const flatData = flattenPayload(data, "", jsonFields);
|
|
396
|
+
for (const field of jsonFields) {
|
|
397
|
+
if (flatData[field] && typeof flatData[field] === "object") {
|
|
398
|
+
flatData[field] = JSON.stringify(flatData[field]);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
for (const key in flatData) {
|
|
402
|
+
if (Array.isArray(flatData[key])) {
|
|
403
|
+
delete flatData[key];
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
flatData.updated_at = new Date().toISOString();
|
|
407
|
+
if (Object.keys(flatData).length > 0) {
|
|
408
|
+
const coercedData = await this.coerceData(collection, flatData);
|
|
409
|
+
const result = await qb.set(coercedData).executeTakeFirst();
|
|
410
|
+
return Number(result.numUpdatedRows || 0);
|
|
411
|
+
}
|
|
412
|
+
return 0;
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
async deleteMany(collection, query) {
|
|
416
|
+
return this._db.transaction().execute(async (tx) => {
|
|
417
|
+
let selectQb = tx.selectFrom(collection).select("id");
|
|
418
|
+
if (query && Object.keys(query).length > 0) {
|
|
419
|
+
selectQb = selectQb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
420
|
+
}
|
|
421
|
+
const docs = await selectQb.execute();
|
|
422
|
+
if (docs.length === 0)
|
|
423
|
+
return 0;
|
|
424
|
+
const ids = docs.map((d) => d.id);
|
|
425
|
+
const colDef = this._collections.find((c) => c.slug === collection);
|
|
426
|
+
if (colDef) {
|
|
427
|
+
for (const field of colDef.fields) {
|
|
428
|
+
const snakeName = toSnakeCase(field.name);
|
|
429
|
+
if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
|
|
430
|
+
const joinTableName = `${collection}_${snakeName}_relations`.toLowerCase();
|
|
431
|
+
try {
|
|
432
|
+
await tx.deleteFrom(joinTableName).where("source_id", "in", ids).execute();
|
|
433
|
+
} catch (e) {}
|
|
434
|
+
} else if (field.type === "blocks" && field.blocks) {
|
|
435
|
+
for (const b of field.blocks) {
|
|
436
|
+
const blockTableName = `${collection}_${snakeName}_${b.slug}`.toLowerCase();
|
|
437
|
+
try {
|
|
438
|
+
await tx.deleteFrom(blockTableName).where("_parent_id", "in", ids).execute();
|
|
439
|
+
} catch (e) {}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const result = await tx.deleteFrom(collection).where("id", "in", ids).executeTakeFirst();
|
|
445
|
+
return Number(result.numDeletedRows || 0);
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
async findGlobal(slug) {
|
|
449
|
+
const row = await this._db.selectFrom(slug).selectAll().limit(1).executeTakeFirst();
|
|
450
|
+
return row ? unflattenRow(row) : null;
|
|
451
|
+
}
|
|
452
|
+
async updateGlobal(slug, data) {
|
|
453
|
+
const existing = await this.findGlobal(slug);
|
|
454
|
+
const flatData = flattenPayload(data);
|
|
455
|
+
flatData.updated_at = new Date().toISOString();
|
|
456
|
+
if (!existing) {
|
|
457
|
+
if (!flatData.id)
|
|
458
|
+
flatData.id = "global";
|
|
459
|
+
await this._db.insertInto(slug).values(flatData).execute();
|
|
460
|
+
} else {
|
|
461
|
+
await this._db.updateTable(slug).set(flatData).where("id", "=", existing.id).execute();
|
|
462
|
+
}
|
|
463
|
+
return this.findGlobal(slug);
|
|
464
|
+
}
|
|
465
|
+
async runMigrations() {
|
|
466
|
+
const migrator = new Migrator({
|
|
467
|
+
db: this._db,
|
|
468
|
+
provider: new FileMigrationProvider({
|
|
469
|
+
fs,
|
|
470
|
+
path,
|
|
471
|
+
migrationFolder: path.resolve(process.cwd(), this.migrationDir)
|
|
472
|
+
})
|
|
473
|
+
});
|
|
474
|
+
const { error, results } = await migrator.migrateToLatest();
|
|
475
|
+
results?.forEach((it) => {
|
|
476
|
+
if (it.status === "Success") {
|
|
477
|
+
logger.success(`\uD83D\uDE80 Migration "${it.migrationName}" was executed successfully`);
|
|
478
|
+
} else if (it.status === "Error") {
|
|
479
|
+
logger.error(`❌ Migration "${it.migrationName}" failed`);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
if (error) {
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async migrate(collections, globals = []) {
|
|
487
|
+
this._collections = collections;
|
|
488
|
+
if (this.push) {
|
|
489
|
+
await pushSchema(this._db, "sqlite", collections, globals, {
|
|
490
|
+
pushDestructive: this.pushDestructive
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function createBetterSQLiteAdapter(path2, options) {
|
|
496
|
+
return new BetterSQLiteAdapter(path2, options);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export { BetterSQLiteAdapter, createBetterSQLiteAdapter };
|
|
@@ -19,8 +19,11 @@ import {
|
|
|
19
19
|
// src/db/sqlite.ts
|
|
20
20
|
import fs from "node:fs/promises";
|
|
21
21
|
import path from "node:path";
|
|
22
|
-
import
|
|
22
|
+
import { createRequire } from "node:module";
|
|
23
23
|
import { CompiledQuery, FileMigrationProvider, Kysely, Migrator, SqliteDialect } from "kysely";
|
|
24
|
+
var require2 = createRequire(import.meta.url);
|
|
25
|
+
var Database = require2("better-sqlite3");
|
|
26
|
+
|
|
24
27
|
class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
25
28
|
name = "sqlite";
|
|
26
29
|
_rawDb;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BetterSQLiteAdapter,
|
|
3
|
+
createBetterSQLiteAdapter
|
|
4
|
+
} from "../chunk-eqtsfyjf.js";
|
|
5
|
+
import"../chunk-6ew02s0c.js";
|
|
6
|
+
import"../chunk-s8mqwnm1.js";
|
|
7
|
+
import"../chunk-62ev8gnc.js";
|
|
8
|
+
import"../chunk-cvdd4eqh.js";
|
|
9
|
+
import"../chunk-ybbbqj63.js";
|
|
10
|
+
import"../chunk-8sqjbsgt.js";
|
|
11
|
+
export {
|
|
12
|
+
createBetterSQLiteAdapter,
|
|
13
|
+
BetterSQLiteAdapter
|
|
14
|
+
};
|
package/dist/db/index.js
CHANGED
|
@@ -2,6 +2,10 @@ import {
|
|
|
2
2
|
BunSQLiteAdapter,
|
|
3
3
|
createBunSQLiteAdapter
|
|
4
4
|
} from "../chunk-gjjcc4hm.js";
|
|
5
|
+
import {
|
|
6
|
+
BetterSQLiteAdapter,
|
|
7
|
+
createBetterSQLiteAdapter
|
|
8
|
+
} from "../chunk-eqtsfyjf.js";
|
|
5
9
|
import {
|
|
6
10
|
D1Adapter,
|
|
7
11
|
createD1Adapter
|
|
@@ -13,501 +17,15 @@ import {
|
|
|
13
17
|
import {
|
|
14
18
|
SQLiteAdapter,
|
|
15
19
|
createSQLiteAdapter
|
|
16
|
-
} from "../chunk-
|
|
17
|
-
import
|
|
18
|
-
buildKyselyWhere,
|
|
19
|
-
flattenPayload,
|
|
20
|
-
pushSchema,
|
|
21
|
-
unflattenRow
|
|
22
|
-
} from "../chunk-6ew02s0c.js";
|
|
20
|
+
} from "../chunk-n8aekdnr.js";
|
|
21
|
+
import"../chunk-6ew02s0c.js";
|
|
23
22
|
import {
|
|
24
23
|
BaseDatabaseAdapter
|
|
25
24
|
} from "../chunk-s8mqwnm1.js";
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
} from "../chunk-62ev8gnc.js";
|
|
29
|
-
import {
|
|
30
|
-
flattenFields,
|
|
31
|
-
toSnakeCase
|
|
32
|
-
} from "../chunk-cvdd4eqh.js";
|
|
25
|
+
import"../chunk-62ev8gnc.js";
|
|
26
|
+
import"../chunk-cvdd4eqh.js";
|
|
33
27
|
import"../chunk-ybbbqj63.js";
|
|
34
|
-
import
|
|
35
|
-
__require
|
|
36
|
-
} from "../chunk-8sqjbsgt.js";
|
|
37
|
-
// src/db/better-sqlite.ts
|
|
38
|
-
import fs from "node:fs/promises";
|
|
39
|
-
import path from "node:path";
|
|
40
|
-
import Database from "better-sqlite3";
|
|
41
|
-
import { CompiledQuery, FileMigrationProvider, Kysely, Migrator, SqliteDialect } from "kysely";
|
|
42
|
-
class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
43
|
-
name = "better-sqlite3";
|
|
44
|
-
_rawDb;
|
|
45
|
-
_db;
|
|
46
|
-
_collections = [];
|
|
47
|
-
push;
|
|
48
|
-
migrationDir;
|
|
49
|
-
pushDestructive;
|
|
50
|
-
get raw() {
|
|
51
|
-
return this._rawDb;
|
|
52
|
-
}
|
|
53
|
-
get db() {
|
|
54
|
-
return this._db;
|
|
55
|
-
}
|
|
56
|
-
constructor(path2, options) {
|
|
57
|
-
super();
|
|
58
|
-
this._rawDb = new Database(path2);
|
|
59
|
-
this._db = new Kysely({
|
|
60
|
-
dialect: new SqliteDialect({
|
|
61
|
-
database: this._rawDb
|
|
62
|
-
})
|
|
63
|
-
});
|
|
64
|
-
this.push = options?.push ?? true;
|
|
65
|
-
this.pushDestructive = options?.pushDestructive ?? false;
|
|
66
|
-
this.migrationDir = options?.migrationDir ?? "./migrations";
|
|
67
|
-
}
|
|
68
|
-
async connect() {}
|
|
69
|
-
async disconnect() {
|
|
70
|
-
await this._db.destroy();
|
|
71
|
-
}
|
|
72
|
-
async unsafe(query, params) {
|
|
73
|
-
const compiled = CompiledQuery.raw(query, params || []);
|
|
74
|
-
const result = await this._db.executeQuery(compiled);
|
|
75
|
-
return result.rows;
|
|
76
|
-
}
|
|
77
|
-
async coerceData(collection, data) {
|
|
78
|
-
const colDef = this._collections.find((c) => c.slug === collection);
|
|
79
|
-
if (!colDef)
|
|
80
|
-
return data;
|
|
81
|
-
const result = { ...data };
|
|
82
|
-
const { flattenFields: flattenFields2 } = await import("../chunk-xg35h5a3.js");
|
|
83
|
-
const allFields = flattenFields2(colDef.fields);
|
|
84
|
-
for (const field of allFields) {
|
|
85
|
-
const colName = toSnakeCase(field.name);
|
|
86
|
-
if (!(colName in result))
|
|
87
|
-
continue;
|
|
88
|
-
const value = result[colName];
|
|
89
|
-
if (value === undefined || value === null)
|
|
90
|
-
continue;
|
|
91
|
-
switch (field.type) {
|
|
92
|
-
case "boolean":
|
|
93
|
-
result[colName] = value ? 1 : 0;
|
|
94
|
-
break;
|
|
95
|
-
case "number":
|
|
96
|
-
result[colName] = Number(value);
|
|
97
|
-
break;
|
|
98
|
-
case "date":
|
|
99
|
-
if (value instanceof Date) {
|
|
100
|
-
result[colName] = value.toISOString();
|
|
101
|
-
} else if (typeof value === "string") {
|
|
102
|
-
result[colName] = new Date(value).toISOString();
|
|
103
|
-
}
|
|
104
|
-
break;
|
|
105
|
-
case "richtext":
|
|
106
|
-
case "json":
|
|
107
|
-
case "file":
|
|
108
|
-
if (typeof value === "object") {
|
|
109
|
-
result[colName] = JSON.stringify(value);
|
|
110
|
-
}
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return result;
|
|
115
|
-
}
|
|
116
|
-
async count(collection, query) {
|
|
117
|
-
let qb = this._db.selectFrom(collection).select((eb) => eb.fn.count("id").as("count"));
|
|
118
|
-
if (query && Object.keys(query).length > 0) {
|
|
119
|
-
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
120
|
-
}
|
|
121
|
-
const result = await qb.executeTakeFirst();
|
|
122
|
-
return Number(result?.count || 0);
|
|
123
|
-
}
|
|
124
|
-
async create(collection, data) {
|
|
125
|
-
return this._db.transaction().execute(async (tx) => {
|
|
126
|
-
const colDef = this._collections.find((c) => c.slug === collection);
|
|
127
|
-
const jsonFields = colDef?.fields.filter((f) => ["richtext", "json", "file"].includes(f.type)).map((f) => f.name) || [];
|
|
128
|
-
const flatData = flattenPayload(data, "", jsonFields);
|
|
129
|
-
for (const field of jsonFields) {
|
|
130
|
-
if (flatData[field] && typeof flatData[field] === "object") {
|
|
131
|
-
flatData[field] = JSON.stringify(flatData[field]);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const hasManyData = {};
|
|
135
|
-
const blocksData = {};
|
|
136
|
-
for (const key in flatData) {
|
|
137
|
-
if (Array.isArray(flatData[key])) {
|
|
138
|
-
const isHasMany = colDef?.fields.some((f) => f.type === "relationship" && ("hasMany" in f) && f.hasMany && f.name === key);
|
|
139
|
-
if (isHasMany) {
|
|
140
|
-
hasManyData[key] = flatData[key];
|
|
141
|
-
} else {
|
|
142
|
-
blocksData[key] = flatData[key];
|
|
143
|
-
}
|
|
144
|
-
delete flatData[key];
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
if (!flatData.id)
|
|
148
|
-
flatData.id = crypto.randomUUID();
|
|
149
|
-
const dbFields = flattenFields(colDef?.fields || []);
|
|
150
|
-
const validCols = new Set(dbFields.map((f) => toSnakeCase(f.name)));
|
|
151
|
-
validCols.add("id");
|
|
152
|
-
const ts = colDef?.timestamps !== false;
|
|
153
|
-
if (ts) {
|
|
154
|
-
const config = typeof colDef?.timestamps === "object" ? colDef.timestamps : {};
|
|
155
|
-
validCols.add(toSnakeCase(config.createdAt || "createdAt"));
|
|
156
|
-
validCols.add(toSnakeCase(config.updatedAt || "updatedAt"));
|
|
157
|
-
}
|
|
158
|
-
const filteredData = {};
|
|
159
|
-
for (const col of Object.keys(flatData)) {
|
|
160
|
-
if (validCols.has(col)) {
|
|
161
|
-
filteredData[col] = flatData[col];
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
const coercedData = await this.coerceData(collection, filteredData);
|
|
165
|
-
await tx.insertInto(collection).values(coercedData).execute();
|
|
166
|
-
for (const [key, values] of Object.entries(hasManyData)) {
|
|
167
|
-
const joinTableName = `${collection}_${toSnakeCase(key)}_relations`.toLowerCase();
|
|
168
|
-
if (values.length > 0) {
|
|
169
|
-
const joinData = values.map((val, idx) => {
|
|
170
|
-
const tId = typeof val === "object" ? val.id : val;
|
|
171
|
-
return { id: crypto.randomUUID(), source_id: flatData.id, target_id: tId, order: idx };
|
|
172
|
-
});
|
|
173
|
-
await tx.insertInto(joinTableName).values(joinData).execute();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
for (const [key, blocks] of Object.entries(blocksData)) {
|
|
177
|
-
for (let i = 0;i < blocks.length; i++) {
|
|
178
|
-
const block = blocks[i];
|
|
179
|
-
if (!block.blockType)
|
|
180
|
-
continue;
|
|
181
|
-
const blockTableName = `${collection}_${toSnakeCase(key)}_${block.blockType}`.toLowerCase();
|
|
182
|
-
const bId = block.id || crypto.randomUUID();
|
|
183
|
-
const blockDef = colDef?.fields.find((f) => f.name === key && f.type === "blocks");
|
|
184
|
-
const blockConfig = blockDef?.blocks?.find((b) => b.slug === block.blockType);
|
|
185
|
-
const blockJsonFields = blockConfig?.fields.filter((f) => ["richtext", "json", "file"].includes(f.type)).map((f) => f.name) || [];
|
|
186
|
-
const blockFlatData = flattenPayload({ ...block, id: bId }, "", blockJsonFields);
|
|
187
|
-
for (const f of blockJsonFields) {
|
|
188
|
-
if (blockFlatData[f] && typeof blockFlatData[f] === "object") {
|
|
189
|
-
blockFlatData[f] = JSON.stringify(blockFlatData[f]);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
delete blockFlatData.blockType;
|
|
193
|
-
const coercedBlockData = await this.coerceData(blockTableName, {
|
|
194
|
-
...blockFlatData,
|
|
195
|
-
_parent_id: flatData.id,
|
|
196
|
-
_order: i,
|
|
197
|
-
block_type: block.blockType
|
|
198
|
-
});
|
|
199
|
-
await tx.insertInto(blockTableName).values(coercedBlockData).execute();
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return this.findOne(collection, { id: flatData.id }, tx);
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
async findOne(collection, query, tx) {
|
|
206
|
-
const executor = tx || this._db;
|
|
207
|
-
let qb = executor.selectFrom(collection).selectAll();
|
|
208
|
-
if (query && Object.keys(query).length > 0) {
|
|
209
|
-
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
210
|
-
}
|
|
211
|
-
const row = await qb.executeTakeFirst();
|
|
212
|
-
if (!row)
|
|
213
|
-
return null;
|
|
214
|
-
const unflattened = unflattenRow(row);
|
|
215
|
-
const colDef = this._collections.find((c) => c.slug === collection);
|
|
216
|
-
if (colDef) {
|
|
217
|
-
const { getRelationalFields, toSnakeCase: toSnakeCase2 } = await import("../chunk-xg35h5a3.js");
|
|
218
|
-
const relationalFields = getRelationalFields(colDef.fields);
|
|
219
|
-
for (const field of relationalFields) {
|
|
220
|
-
if (!field.name)
|
|
221
|
-
continue;
|
|
222
|
-
const snakeName = toSnakeCase2(field.name);
|
|
223
|
-
if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
|
|
224
|
-
const joinTableName = `${collection}_${snakeName}_relations`.toLowerCase();
|
|
225
|
-
try {
|
|
226
|
-
const relations = await executor.selectFrom(joinTableName).selectAll().where("source_id", "=", row.id).orderBy("order", "asc").execute();
|
|
227
|
-
unflattened[field.name] = relations.map((r) => r.target_id);
|
|
228
|
-
} catch (e) {}
|
|
229
|
-
} else if (field.type === "blocks" && field.blocks) {
|
|
230
|
-
const blockData = [];
|
|
231
|
-
for (const b of field.blocks) {
|
|
232
|
-
const blockTableName = `${collection}_${snakeName}_${b.slug}`.toLowerCase();
|
|
233
|
-
try {
|
|
234
|
-
const blocks = await executor.selectFrom(blockTableName).selectAll().where("_parent_id", "=", row.id).execute();
|
|
235
|
-
for (const blk of blocks) {
|
|
236
|
-
const uf = unflattenRow(blk);
|
|
237
|
-
uf.blockType = blk.block_type;
|
|
238
|
-
blockData.push(uf);
|
|
239
|
-
}
|
|
240
|
-
} catch (e) {}
|
|
241
|
-
}
|
|
242
|
-
blockData.sort((a, b) => a._order - b._order);
|
|
243
|
-
blockData.forEach((b) => {
|
|
244
|
-
delete b._order;
|
|
245
|
-
delete b._parent_id;
|
|
246
|
-
});
|
|
247
|
-
unflattened[field.name] = blockData;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
return unflattened;
|
|
252
|
-
}
|
|
253
|
-
async find(collection, query, options) {
|
|
254
|
-
const page = options?.page || 1;
|
|
255
|
-
const limit = options?.limit || 10;
|
|
256
|
-
const offset = (page - 1) * limit;
|
|
257
|
-
const total = await this.count(collection, query);
|
|
258
|
-
let qb = this._db.selectFrom(collection).selectAll().limit(limit).offset(offset);
|
|
259
|
-
if (query && Object.keys(query).length > 0) {
|
|
260
|
-
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
261
|
-
}
|
|
262
|
-
if (options?.sort) {
|
|
263
|
-
const isDesc = options.sort.startsWith("-");
|
|
264
|
-
const col = isDesc ? options.sort.substring(1) : options.sort;
|
|
265
|
-
qb = qb.orderBy(col, isDesc ? "desc" : "asc");
|
|
266
|
-
} else {
|
|
267
|
-
qb = qb.orderBy("created_at", "desc");
|
|
268
|
-
}
|
|
269
|
-
const rows = await qb.execute();
|
|
270
|
-
const docs = await Promise.all(rows.map((row) => this.findOne(collection, { id: row.id })));
|
|
271
|
-
const totalPages = Math.ceil(total / limit);
|
|
272
|
-
return {
|
|
273
|
-
docs: docs.filter(Boolean),
|
|
274
|
-
totalDocs: total,
|
|
275
|
-
limit,
|
|
276
|
-
totalPages,
|
|
277
|
-
page,
|
|
278
|
-
pagingCounter: offset + 1,
|
|
279
|
-
hasNextPage: page * limit < total,
|
|
280
|
-
hasPrevPage: page > 1,
|
|
281
|
-
prevPage: page > 1 ? page - 1 : null,
|
|
282
|
-
nextPage: page < totalPages ? page + 1 : null
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
async update(collection, query, data) {
|
|
286
|
-
return this._db.transaction().execute(async (tx) => {
|
|
287
|
-
let normalizedQuery = query;
|
|
288
|
-
if (typeof query !== "object" || query === null) {
|
|
289
|
-
normalizedQuery = { id: query };
|
|
290
|
-
}
|
|
291
|
-
const current = await this.findOne(collection, normalizedQuery, tx);
|
|
292
|
-
if (!current)
|
|
293
|
-
throw new Error("Document not found");
|
|
294
|
-
const colDef = this._collections.find((c) => c.slug === collection);
|
|
295
|
-
const jsonFields = colDef?.fields.filter((f) => ["richtext", "json", "file"].includes(f.type)).map((f) => f.name) || [];
|
|
296
|
-
const flatData = flattenPayload(data, "", jsonFields);
|
|
297
|
-
for (const field of jsonFields) {
|
|
298
|
-
if (flatData[field] && typeof flatData[field] === "object") {
|
|
299
|
-
flatData[field] = JSON.stringify(flatData[field]);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const hasManyData = {};
|
|
303
|
-
const blocksData = {};
|
|
304
|
-
for (const key in flatData) {
|
|
305
|
-
if (Array.isArray(flatData[key])) {
|
|
306
|
-
const isHasMany = colDef?.fields.some((f) => f.type === "relationship" && ("hasMany" in f) && f.hasMany && f.name === key);
|
|
307
|
-
if (isHasMany) {
|
|
308
|
-
hasManyData[key] = flatData[key];
|
|
309
|
-
} else {
|
|
310
|
-
blocksData[key] = flatData[key];
|
|
311
|
-
}
|
|
312
|
-
delete flatData[key];
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
flatData.updated_at = new Date().toISOString();
|
|
316
|
-
if (Object.keys(flatData).length > 0) {
|
|
317
|
-
const coercedData = await this.coerceData(collection, flatData);
|
|
318
|
-
await tx.updateTable(collection).set(coercedData).where("id", "=", current.id).execute();
|
|
319
|
-
}
|
|
320
|
-
for (const [key, values] of Object.entries(hasManyData)) {
|
|
321
|
-
const joinTableName = `${collection}_${toSnakeCase(key)}_relations`.toLowerCase();
|
|
322
|
-
await tx.deleteFrom(joinTableName).where("source_id", "=", current.id).execute();
|
|
323
|
-
if (values.length > 0) {
|
|
324
|
-
const joinData = values.map((val, idx) => {
|
|
325
|
-
const tId = typeof val === "object" ? val.id : val;
|
|
326
|
-
return { id: crypto.randomUUID(), source_id: current.id, target_id: tId, order: idx };
|
|
327
|
-
});
|
|
328
|
-
await tx.insertInto(joinTableName).values(joinData).execute();
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
for (const [key, blocks] of Object.entries(blocksData)) {
|
|
332
|
-
const fieldDef = colDef?.fields.find((f) => f.name === key);
|
|
333
|
-
if (fieldDef?.type === "blocks" && fieldDef.blocks) {
|
|
334
|
-
for (const b of fieldDef.blocks) {
|
|
335
|
-
const blockTableName = `${collection}_${toSnakeCase(key)}_${b.slug}`.toLowerCase();
|
|
336
|
-
try {
|
|
337
|
-
await tx.deleteFrom(blockTableName).where("_parent_id", "=", current.id).execute();
|
|
338
|
-
} catch (e) {}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
for (let i = 0;i < blocks.length; i++) {
|
|
342
|
-
const block = blocks[i];
|
|
343
|
-
if (!block.blockType)
|
|
344
|
-
continue;
|
|
345
|
-
const blockTableName = `${collection}_${toSnakeCase(key)}_${block.blockType}`.toLowerCase();
|
|
346
|
-
const bId = block.id || crypto.randomUUID();
|
|
347
|
-
const blockConfig = fieldDef?.blocks?.find((b) => b.slug === block.blockType);
|
|
348
|
-
const blockJsonFields = blockConfig?.fields.filter((f) => ["richtext", "json", "file"].includes(f.type)).map((f) => f.name) || [];
|
|
349
|
-
const blockFlatData = flattenPayload({ ...block, id: bId }, "", blockJsonFields);
|
|
350
|
-
for (const f of blockJsonFields) {
|
|
351
|
-
if (blockFlatData[f] && typeof blockFlatData[f] === "object") {
|
|
352
|
-
blockFlatData[f] = JSON.stringify(blockFlatData[f]);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
delete blockFlatData.blockType;
|
|
356
|
-
const coercedBlockData = await this.coerceData(blockTableName, {
|
|
357
|
-
...blockFlatData,
|
|
358
|
-
_parent_id: current.id,
|
|
359
|
-
_order: i,
|
|
360
|
-
block_type: block.blockType
|
|
361
|
-
});
|
|
362
|
-
await tx.insertInto(blockTableName).values(coercedBlockData).execute();
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return this.findOne(collection, { id: current.id }, tx);
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
async delete(collection, query) {
|
|
369
|
-
let normalizedQuery = query;
|
|
370
|
-
if (typeof query !== "object" || query === null) {
|
|
371
|
-
normalizedQuery = { id: query };
|
|
372
|
-
}
|
|
373
|
-
const current = await this.findOne(collection, normalizedQuery);
|
|
374
|
-
if (!current)
|
|
375
|
-
return false;
|
|
376
|
-
await this._db.transaction().execute(async (tx) => {
|
|
377
|
-
const colDef = this._collections.find((c) => c.slug === collection);
|
|
378
|
-
if (colDef) {
|
|
379
|
-
for (const field of colDef.fields) {
|
|
380
|
-
const snakeName = toSnakeCase(field.name);
|
|
381
|
-
if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
|
|
382
|
-
const joinTableName = `${collection}_${snakeName}_relations`.toLowerCase();
|
|
383
|
-
try {
|
|
384
|
-
await tx.deleteFrom(joinTableName).where("source_id", "=", current.id).execute();
|
|
385
|
-
} catch (e) {}
|
|
386
|
-
} else if (field.type === "blocks" && field.blocks) {
|
|
387
|
-
for (const b of field.blocks) {
|
|
388
|
-
const blockTableName = `${collection}_${snakeName}_${b.slug}`.toLowerCase();
|
|
389
|
-
try {
|
|
390
|
-
await tx.deleteFrom(blockTableName).where("_parent_id", "=", current.id).execute();
|
|
391
|
-
} catch (e) {}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
await tx.deleteFrom(collection).where("id", "=", current.id).execute();
|
|
397
|
-
});
|
|
398
|
-
return true;
|
|
399
|
-
}
|
|
400
|
-
async updateMany(collection, query, data) {
|
|
401
|
-
return this._db.transaction().execute(async (tx) => {
|
|
402
|
-
let qb = tx.updateTable(collection);
|
|
403
|
-
if (query && Object.keys(query).length > 0) {
|
|
404
|
-
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
405
|
-
}
|
|
406
|
-
const colDef = this._collections.find((c) => c.slug === collection);
|
|
407
|
-
const jsonFields = colDef?.fields.filter((f) => ["richtext", "json", "file"].includes(f.type)).map((f) => f.name) || [];
|
|
408
|
-
const flatData = flattenPayload(data, "", jsonFields);
|
|
409
|
-
for (const field of jsonFields) {
|
|
410
|
-
if (flatData[field] && typeof flatData[field] === "object") {
|
|
411
|
-
flatData[field] = JSON.stringify(flatData[field]);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
for (const key in flatData) {
|
|
415
|
-
if (Array.isArray(flatData[key])) {
|
|
416
|
-
delete flatData[key];
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
flatData.updated_at = new Date().toISOString();
|
|
420
|
-
if (Object.keys(flatData).length > 0) {
|
|
421
|
-
const coercedData = await this.coerceData(collection, flatData);
|
|
422
|
-
const result = await qb.set(coercedData).executeTakeFirst();
|
|
423
|
-
return Number(result.numUpdatedRows || 0);
|
|
424
|
-
}
|
|
425
|
-
return 0;
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
async deleteMany(collection, query) {
|
|
429
|
-
return this._db.transaction().execute(async (tx) => {
|
|
430
|
-
let selectQb = tx.selectFrom(collection).select("id");
|
|
431
|
-
if (query && Object.keys(query).length > 0) {
|
|
432
|
-
selectQb = selectQb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
433
|
-
}
|
|
434
|
-
const docs = await selectQb.execute();
|
|
435
|
-
if (docs.length === 0)
|
|
436
|
-
return 0;
|
|
437
|
-
const ids = docs.map((d) => d.id);
|
|
438
|
-
const colDef = this._collections.find((c) => c.slug === collection);
|
|
439
|
-
if (colDef) {
|
|
440
|
-
for (const field of colDef.fields) {
|
|
441
|
-
const snakeName = toSnakeCase(field.name);
|
|
442
|
-
if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
|
|
443
|
-
const joinTableName = `${collection}_${snakeName}_relations`.toLowerCase();
|
|
444
|
-
try {
|
|
445
|
-
await tx.deleteFrom(joinTableName).where("source_id", "in", ids).execute();
|
|
446
|
-
} catch (e) {}
|
|
447
|
-
} else if (field.type === "blocks" && field.blocks) {
|
|
448
|
-
for (const b of field.blocks) {
|
|
449
|
-
const blockTableName = `${collection}_${snakeName}_${b.slug}`.toLowerCase();
|
|
450
|
-
try {
|
|
451
|
-
await tx.deleteFrom(blockTableName).where("_parent_id", "in", ids).execute();
|
|
452
|
-
} catch (e) {}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
const result = await tx.deleteFrom(collection).where("id", "in", ids).executeTakeFirst();
|
|
458
|
-
return Number(result.numDeletedRows || 0);
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
async findGlobal(slug) {
|
|
462
|
-
const row = await this._db.selectFrom(slug).selectAll().limit(1).executeTakeFirst();
|
|
463
|
-
return row ? unflattenRow(row) : null;
|
|
464
|
-
}
|
|
465
|
-
async updateGlobal(slug, data) {
|
|
466
|
-
const existing = await this.findGlobal(slug);
|
|
467
|
-
const flatData = flattenPayload(data);
|
|
468
|
-
flatData.updated_at = new Date().toISOString();
|
|
469
|
-
if (!existing) {
|
|
470
|
-
if (!flatData.id)
|
|
471
|
-
flatData.id = "global";
|
|
472
|
-
await this._db.insertInto(slug).values(flatData).execute();
|
|
473
|
-
} else {
|
|
474
|
-
await this._db.updateTable(slug).set(flatData).where("id", "=", existing.id).execute();
|
|
475
|
-
}
|
|
476
|
-
return this.findGlobal(slug);
|
|
477
|
-
}
|
|
478
|
-
async runMigrations() {
|
|
479
|
-
const migrator = new Migrator({
|
|
480
|
-
db: this._db,
|
|
481
|
-
provider: new FileMigrationProvider({
|
|
482
|
-
fs,
|
|
483
|
-
path,
|
|
484
|
-
migrationFolder: path.resolve(process.cwd(), this.migrationDir)
|
|
485
|
-
})
|
|
486
|
-
});
|
|
487
|
-
const { error, results } = await migrator.migrateToLatest();
|
|
488
|
-
results?.forEach((it) => {
|
|
489
|
-
if (it.status === "Success") {
|
|
490
|
-
logger.success(`\uD83D\uDE80 Migration "${it.migrationName}" was executed successfully`);
|
|
491
|
-
} else if (it.status === "Error") {
|
|
492
|
-
logger.error(`❌ Migration "${it.migrationName}" failed`);
|
|
493
|
-
}
|
|
494
|
-
});
|
|
495
|
-
if (error) {
|
|
496
|
-
throw error;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
async migrate(collections, globals = []) {
|
|
500
|
-
this._collections = collections;
|
|
501
|
-
if (this.push) {
|
|
502
|
-
await pushSchema(this._db, "sqlite", collections, globals, {
|
|
503
|
-
pushDestructive: this.pushDestructive
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
function createBetterSQLiteAdapter(path2, options) {
|
|
509
|
-
return new BetterSQLiteAdapter(path2, options);
|
|
510
|
-
}
|
|
28
|
+
import"../chunk-8sqjbsgt.js";
|
|
511
29
|
export {
|
|
512
30
|
createSQLiteAdapter,
|
|
513
31
|
createPostgresAdapter,
|
package/dist/db/sqlite.js
CHANGED
package/dist/runtimes/bun.js
CHANGED
package/dist/runtimes/next.js
CHANGED
package/dist/runtimes/node.js
CHANGED
package/dist/server.js
CHANGED
package/package.json
CHANGED
package/src/db/better-sqlite.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const Database = require("better-sqlite3");
|
|
4
6
|
import { CompiledQuery, FileMigrationProvider, Kysely, Migrator, SqliteDialect, sql } from "kysely";
|
|
5
7
|
import type { Collection, FindOptions, Global, PaginatedResult } from "../types";
|
|
6
8
|
import { logger } from "../utils/logger";
|
|
@@ -251,7 +253,7 @@ export class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
251
253
|
.orderBy("order", "asc")
|
|
252
254
|
.execute();
|
|
253
255
|
unflattened[field.name!] = relations.map((r: any) => r.target_id);
|
|
254
|
-
} catch (e) {}
|
|
256
|
+
} catch (e) { }
|
|
255
257
|
} else if (field.type === "blocks" && field.blocks) {
|
|
256
258
|
const blockData: any[] = [];
|
|
257
259
|
for (const b of field.blocks) {
|
|
@@ -268,7 +270,7 @@ export class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
268
270
|
uf.blockType = blk.block_type;
|
|
269
271
|
blockData.push(uf);
|
|
270
272
|
}
|
|
271
|
-
} catch (e) {}
|
|
273
|
+
} catch (e) { }
|
|
272
274
|
}
|
|
273
275
|
blockData.sort((a: any, b: any) => a._order - b._order);
|
|
274
276
|
blockData.forEach((b: any) => {
|
|
@@ -399,7 +401,7 @@ export class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
399
401
|
const blockTableName = `${collection}_${toSnakeCase(key)}_${b.slug}`.toLowerCase();
|
|
400
402
|
try {
|
|
401
403
|
await tx.deleteFrom(blockTableName).where("_parent_id", "=", current.id).execute();
|
|
402
|
-
} catch (e) {}
|
|
404
|
+
} catch (e) { }
|
|
403
405
|
}
|
|
404
406
|
}
|
|
405
407
|
|
|
@@ -462,13 +464,13 @@ export class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
462
464
|
const joinTableName = `${collection}_${snakeName}_relations`.toLowerCase();
|
|
463
465
|
try {
|
|
464
466
|
await tx.deleteFrom(joinTableName).where("source_id", "=", current.id).execute();
|
|
465
|
-
} catch (e) {}
|
|
467
|
+
} catch (e) { }
|
|
466
468
|
} else if (field.type === "blocks" && field.blocks) {
|
|
467
469
|
for (const b of field.blocks) {
|
|
468
470
|
const blockTableName = `${collection}_${snakeName}_${b.slug}`.toLowerCase();
|
|
469
471
|
try {
|
|
470
472
|
await tx.deleteFrom(blockTableName).where("_parent_id", "=", current.id).execute();
|
|
471
|
-
} catch (e) {}
|
|
473
|
+
} catch (e) { }
|
|
472
474
|
}
|
|
473
475
|
}
|
|
474
476
|
}
|
|
@@ -540,13 +542,13 @@ export class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
540
542
|
const joinTableName = `${collection}_${snakeName}_relations`.toLowerCase();
|
|
541
543
|
try {
|
|
542
544
|
await tx.deleteFrom(joinTableName).where("source_id", "in", ids).execute();
|
|
543
|
-
} catch (e) {}
|
|
545
|
+
} catch (e) { }
|
|
544
546
|
} else if (field.type === "blocks" && field.blocks) {
|
|
545
547
|
for (const b of field.blocks) {
|
|
546
548
|
const blockTableName = `${collection}_${snakeName}_${b.slug}`.toLowerCase();
|
|
547
549
|
try {
|
|
548
550
|
await tx.deleteFrom(blockTableName).where("_parent_id", "in", ids).execute();
|
|
549
|
-
} catch (e) {}
|
|
551
|
+
} catch (e) { }
|
|
550
552
|
}
|
|
551
553
|
}
|
|
552
554
|
}
|
package/src/db/sqlite.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const Database = require("better-sqlite3");
|
|
4
6
|
import { CompiledQuery, FileMigrationProvider, Kysely, Migrator, SqliteDialect, sql } from "kysely";
|
|
5
7
|
import type { Collection, FindOptions, Global, PaginatedResult } from "../types";
|
|
6
8
|
import { logger } from "../utils/logger";
|
|
@@ -253,7 +255,7 @@ export class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
253
255
|
current = current[parts[i]!];
|
|
254
256
|
}
|
|
255
257
|
current[parts[parts.length - 1]!] = relations.map((r: any) => r.target_id);
|
|
256
|
-
} catch (e) {}
|
|
258
|
+
} catch (e) { }
|
|
257
259
|
} else if (field.type === "blocks" && field.blocks) {
|
|
258
260
|
const blockData: any[] = [];
|
|
259
261
|
for (const b of field.blocks) {
|
|
@@ -271,7 +273,7 @@ export class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
271
273
|
uf.blockType = blk.block_type;
|
|
272
274
|
blockData.push(uf);
|
|
273
275
|
}
|
|
274
|
-
} catch (e) {}
|
|
276
|
+
} catch (e) { }
|
|
275
277
|
}
|
|
276
278
|
blockData.sort((a: any, b: any) => a._order - b._order);
|
|
277
279
|
blockData.forEach((b: any) => {
|
|
@@ -408,7 +410,7 @@ export class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
408
410
|
`${collection}_${toSnakeCase(key)}_${toSnakeCase(b.slug)}`.toLowerCase();
|
|
409
411
|
try {
|
|
410
412
|
await tx.deleteFrom(blockTableName).where("_parent_id", "=", current.id).execute();
|
|
411
|
-
} catch (e) {}
|
|
413
|
+
} catch (e) { }
|
|
412
414
|
}
|
|
413
415
|
}
|
|
414
416
|
|
|
@@ -473,14 +475,14 @@ export class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
473
475
|
const joinTableName = `${collection}_${snakeName}_relations`.toLowerCase();
|
|
474
476
|
try {
|
|
475
477
|
await tx.deleteFrom(joinTableName).where("source_id", "=", current.id).execute();
|
|
476
|
-
} catch (e) {}
|
|
478
|
+
} catch (e) { }
|
|
477
479
|
} else if (field.type === "blocks" && field.blocks) {
|
|
478
480
|
for (const b of field.blocks) {
|
|
479
481
|
const blockTableName =
|
|
480
482
|
`${collection}_${snakeName}_${toSnakeCase(b.slug)}`.toLowerCase();
|
|
481
483
|
try {
|
|
482
484
|
await tx.deleteFrom(blockTableName).where("_parent_id", "=", current.id).execute();
|
|
483
|
-
} catch (e) {}
|
|
485
|
+
} catch (e) { }
|
|
484
486
|
}
|
|
485
487
|
}
|
|
486
488
|
}
|
|
@@ -553,14 +555,14 @@ export class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
553
555
|
const joinTableName = `${collection}_${snakeName}_relations`.toLowerCase();
|
|
554
556
|
try {
|
|
555
557
|
await tx.deleteFrom(joinTableName).where("source_id", "in", ids).execute();
|
|
556
|
-
} catch (e) {}
|
|
558
|
+
} catch (e) { }
|
|
557
559
|
} else if (field.type === "blocks" && field.blocks) {
|
|
558
560
|
for (const b of field.blocks) {
|
|
559
561
|
const blockTableName =
|
|
560
562
|
`${collection}_${snakeName}_${toSnakeCase(b.slug)}`.toLowerCase();
|
|
561
563
|
try {
|
|
562
564
|
await tx.deleteFrom(blockTableName).where("_parent_id", "in", ids).execute();
|
|
563
|
-
} catch (e) {}
|
|
565
|
+
} catch (e) { }
|
|
564
566
|
}
|
|
565
567
|
}
|
|
566
568
|
}
|
package/src/schema/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
import type { Context, MiddlewareHandler } from "hono";
|
|
3
3
|
import { rateLimiter } from "hono-rate-limiter";
|
|
4
|
-
import type { OpacaConfig } from "
|
|
4
|
+
import type { OpacaConfig } from "@/types";
|
|
5
|
+
import { logger } from "@/utils/logger";
|
|
5
6
|
|
|
6
7
|
export function createRateLimitMiddleware(config: OpacaConfig): MiddlewareHandler {
|
|
7
8
|
const rateLimitConfig = config.api?.rateLimit;
|
|
@@ -50,7 +51,12 @@ export function createRateLimitMiddleware(config: OpacaConfig): MiddlewareHandle
|
|
|
50
51
|
);
|
|
51
52
|
|
|
52
53
|
if (kvBindingKey) {
|
|
53
|
-
|
|
54
|
+
try {
|
|
55
|
+
const { WorkersKVStore } = await import("@hono-rate-limiter/cloudflare");
|
|
56
|
+
resolvedStore = new WorkersKVStore({ namespace: (c.env as any)[kvBindingKey] });
|
|
57
|
+
} catch (e) {
|
|
58
|
+
logger.error("Failed to load @hono-rate-limiter/cloudflare dynamic import. Make sure you are in a Cloudflare environment.", e);
|
|
59
|
+
}
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
62
|
|