@cequrebackends/plugin-sqlite 0.12.2
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 +21 -0
- package/dist/adapter.d.ts +40 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/ddl/schema.d.ts +27 -0
- package/dist/ddl/schema.d.ts.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +551 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @cequrebackends/plugin-sqlite
|
|
2
|
+
|
|
3
|
+
SQLite database driver plugin for Cequre backends.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @cequrebackends/plugin-sqlite
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { CequreRuntime } from "@cequrebackends/cequre-ts";
|
|
15
|
+
import { sqlite } from "@cequrebackends/plugin-sqlite";
|
|
16
|
+
|
|
17
|
+
const runtime = new CequreRuntime(schema, {
|
|
18
|
+
adapter: sqlite({ filename: "app.db" }),
|
|
19
|
+
plugins: []
|
|
20
|
+
});
|
|
21
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CequreAdapter, QueryArgs, PaginatedResult as QueryResult } from "@cequrebackends/cequre-ts";
|
|
2
|
+
import { BaseSqlAdapter } from "@cequrebackends/cequre-ts";
|
|
3
|
+
export declare class SQLiteAdapter extends BaseSqlAdapter {
|
|
4
|
+
readonly adapterType = "sqlite";
|
|
5
|
+
readonly supportsGeneratedConstraintSQL = true;
|
|
6
|
+
private db;
|
|
7
|
+
private url;
|
|
8
|
+
constructor(url: string);
|
|
9
|
+
private initDb;
|
|
10
|
+
private closeDb;
|
|
11
|
+
connect(): Promise<void>;
|
|
12
|
+
disconnect(): Promise<void>;
|
|
13
|
+
transaction<R>(callback: (trxAdapter: CequreAdapter) => Promise<R>): Promise<R>;
|
|
14
|
+
ping(): Promise<void>;
|
|
15
|
+
getSystemTableStatements(): string[];
|
|
16
|
+
getCurrentSchema(): Promise<{
|
|
17
|
+
tables: any[];
|
|
18
|
+
columns: Record<string, string[]>;
|
|
19
|
+
}>;
|
|
20
|
+
recordMigration(version: string, description: string): Promise<void>;
|
|
21
|
+
getCurrentMigrationVersion(): Promise<string | null>;
|
|
22
|
+
createTableDDL(table: unknown): string;
|
|
23
|
+
renameColumnDDL(table: string, oldName: string, newName: string): string;
|
|
24
|
+
addColumnDDL(table: string, column: unknown): string | null;
|
|
25
|
+
dropColumnDDL(table: string, column: string): string;
|
|
26
|
+
dropTableDDL(table: string): string;
|
|
27
|
+
private deserializeRow;
|
|
28
|
+
private buildWhere;
|
|
29
|
+
find(collection: string, query: QueryArgs<any>): Promise<QueryResult>;
|
|
30
|
+
findById(collection: string, id: string): Promise<Record<string, unknown> | null>;
|
|
31
|
+
findByIds(collection: string, ids: string[]): Promise<Record<string, unknown>[]>;
|
|
32
|
+
create(collection: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
33
|
+
createMany(collection: string, docs: Record<string, unknown>[]): Promise<Record<string, unknown>[]>;
|
|
34
|
+
update(collection: string, id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
35
|
+
delete(collection: string, id: string): Promise<void>;
|
|
36
|
+
deleteMany(collection: string, ids: string[]): Promise<void>;
|
|
37
|
+
count(collection: string, query?: Pick<QueryArgs<any>, "where">): Promise<number>;
|
|
38
|
+
raw(sql: string, params?: unknown[]): Promise<unknown>;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CollectionConfig, FieldConfig } from "@cequrebackends/cequre-ts";
|
|
2
|
+
export type SQLiteType = "TEXT" | "INTEGER" | "REAL" | "BLOB";
|
|
3
|
+
export interface ColumnDefinition {
|
|
4
|
+
name: string;
|
|
5
|
+
type: SQLiteType;
|
|
6
|
+
required: boolean;
|
|
7
|
+
unique: boolean;
|
|
8
|
+
defaultValue?: any;
|
|
9
|
+
foreignKey?: {
|
|
10
|
+
table: string;
|
|
11
|
+
column: string;
|
|
12
|
+
onDelete?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface TableDefinition {
|
|
16
|
+
name: string;
|
|
17
|
+
columns: ColumnDefinition[];
|
|
18
|
+
primaryKey: string[];
|
|
19
|
+
}
|
|
20
|
+
export declare function fieldTypeToSQLite(field: FieldConfig): SQLiteType;
|
|
21
|
+
export declare function buildColumns(collection: CollectionConfig): ColumnDefinition[];
|
|
22
|
+
export declare function buildTable(collection: CollectionConfig): TableDefinition;
|
|
23
|
+
export declare function generateCreateTable(table: TableDefinition): string;
|
|
24
|
+
export declare function generateRenameColumn(tableName: string, oldName: string, newName: string): string;
|
|
25
|
+
export declare function generateAddColumn(tableName: string, column: ColumnDefinition): string;
|
|
26
|
+
export declare function generateSystemTables(): string[];
|
|
27
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/ddl/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE/E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACnE;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,CAUhE;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAiD7E;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,gBAAgB,GAAG,eAAe,CAMxE;AAsBD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAuBlE;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhG;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAErF;AAED,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAsB/C"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,MAAM,CAAC,MAAM,EAAE,YAAY,iBAE1C;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/adapter.ts
|
|
3
|
+
import { Database } from "bun:sqlite";
|
|
4
|
+
import { mkdirSync } from "fs";
|
|
5
|
+
import { dirname } from "path";
|
|
6
|
+
import { ulid, BaseSqlAdapter, sqlIdentifier } from "@cequrebackends/cequre-ts";
|
|
7
|
+
|
|
8
|
+
// src/ddl/schema.ts
|
|
9
|
+
function fieldTypeToSQLite(field) {
|
|
10
|
+
switch (field.type) {
|
|
11
|
+
case "boolean":
|
|
12
|
+
case "integer":
|
|
13
|
+
return "INTEGER";
|
|
14
|
+
case "number":
|
|
15
|
+
return "REAL";
|
|
16
|
+
default:
|
|
17
|
+
return "TEXT";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function buildColumns(collection) {
|
|
21
|
+
const columns = [];
|
|
22
|
+
columns.push({
|
|
23
|
+
name: "id",
|
|
24
|
+
type: "TEXT",
|
|
25
|
+
required: true,
|
|
26
|
+
unique: false
|
|
27
|
+
});
|
|
28
|
+
for (const field of collection.fields) {
|
|
29
|
+
if (field.type === "link")
|
|
30
|
+
continue;
|
|
31
|
+
const col = {
|
|
32
|
+
name: field.name,
|
|
33
|
+
type: fieldTypeToSQLite(field),
|
|
34
|
+
required: !field.optional,
|
|
35
|
+
unique: field.unique || false,
|
|
36
|
+
defaultValue: field.default
|
|
37
|
+
};
|
|
38
|
+
if (field.type === "relationship" && field.target) {
|
|
39
|
+
col.foreignKey = {
|
|
40
|
+
table: field.target,
|
|
41
|
+
column: "id",
|
|
42
|
+
onDelete: "SET NULL"
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
columns.push(col);
|
|
46
|
+
}
|
|
47
|
+
columns.push({
|
|
48
|
+
name: "createdAt",
|
|
49
|
+
type: "TEXT",
|
|
50
|
+
required: true,
|
|
51
|
+
unique: false,
|
|
52
|
+
defaultValue: "CURRENT_TIMESTAMP"
|
|
53
|
+
});
|
|
54
|
+
columns.push({
|
|
55
|
+
name: "updatedAt",
|
|
56
|
+
type: "TEXT",
|
|
57
|
+
required: true,
|
|
58
|
+
unique: false,
|
|
59
|
+
defaultValue: "CURRENT_TIMESTAMP"
|
|
60
|
+
});
|
|
61
|
+
return columns;
|
|
62
|
+
}
|
|
63
|
+
function buildTable(collection) {
|
|
64
|
+
return {
|
|
65
|
+
name: collection.slug,
|
|
66
|
+
columns: buildColumns(collection),
|
|
67
|
+
primaryKey: ["id"]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function formatDefaultValue(value) {
|
|
71
|
+
if (value === "CURRENT_TIMESTAMP")
|
|
72
|
+
return "CURRENT_TIMESTAMP";
|
|
73
|
+
if (value === undefined || value === null)
|
|
74
|
+
return "NULL";
|
|
75
|
+
if (typeof value === "string")
|
|
76
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
77
|
+
if (typeof value === "boolean")
|
|
78
|
+
return value ? "1" : "0";
|
|
79
|
+
if (typeof value === "number")
|
|
80
|
+
return String(value);
|
|
81
|
+
if (value instanceof Date)
|
|
82
|
+
return `'${value.toISOString()}'`;
|
|
83
|
+
return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
|
|
84
|
+
}
|
|
85
|
+
function generateColumnDefinition(column) {
|
|
86
|
+
let def = `"${column.name}" ${column.type}`;
|
|
87
|
+
if (column.required)
|
|
88
|
+
def += " NOT NULL";
|
|
89
|
+
if (column.defaultValue !== undefined) {
|
|
90
|
+
def += ` DEFAULT ${formatDefaultValue(column.defaultValue)}`;
|
|
91
|
+
}
|
|
92
|
+
if (column.unique)
|
|
93
|
+
def += " UNIQUE";
|
|
94
|
+
return def;
|
|
95
|
+
}
|
|
96
|
+
function generateCreateTable(table) {
|
|
97
|
+
const columnDefs = table.columns.map((col) => generateColumnDefinition(col)).join(`,
|
|
98
|
+
`);
|
|
99
|
+
const fkConstraints = table.columns.filter((col) => col.foreignKey).map((col) => {
|
|
100
|
+
const fk = col.foreignKey;
|
|
101
|
+
return ` CONSTRAINT ${table.name}_${col.name}_fkey FOREIGN KEY ("${col.name}") REFERENCES ${fk.table}("${fk.column}") ON DELETE ${fk.onDelete || "SET NULL"}`;
|
|
102
|
+
});
|
|
103
|
+
const allDefs = [
|
|
104
|
+
` ${columnDefs}`,
|
|
105
|
+
` PRIMARY KEY (${table.primaryKey.map((pk) => `"${pk}"`).join(", ")})`,
|
|
106
|
+
...fkConstraints
|
|
107
|
+
].join(`,
|
|
108
|
+
`);
|
|
109
|
+
const fkIndexes = table.columns.filter((col) => col.foreignKey).map((col) => `CREATE INDEX IF NOT EXISTS ${table.name}_${col.name}_idx ON ${table.name}("${col.name}");`);
|
|
110
|
+
return [
|
|
111
|
+
`CREATE TABLE IF NOT EXISTS ${table.name} (
|
|
112
|
+
${allDefs}
|
|
113
|
+
);`,
|
|
114
|
+
...fkIndexes
|
|
115
|
+
].join(`
|
|
116
|
+
`);
|
|
117
|
+
}
|
|
118
|
+
function generateRenameColumn(tableName, oldName, newName) {
|
|
119
|
+
return `ALTER TABLE ${tableName} RENAME COLUMN "${oldName}" TO "${newName}";`;
|
|
120
|
+
}
|
|
121
|
+
function generateAddColumn(tableName, column) {
|
|
122
|
+
return `ALTER TABLE ${tableName} ADD COLUMN ${generateColumnDefinition(column)};`;
|
|
123
|
+
}
|
|
124
|
+
function generateSystemTables() {
|
|
125
|
+
const statements = [];
|
|
126
|
+
statements.push(`CREATE TABLE IF NOT EXISTS cequre_migrations (
|
|
127
|
+
id TEXT PRIMARY KEY,
|
|
128
|
+
version TEXT NOT NULL UNIQUE,
|
|
129
|
+
applied_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
130
|
+
description TEXT
|
|
131
|
+
);`);
|
|
132
|
+
statements.push(`CREATE TABLE IF NOT EXISTS cequre_refresh_tokens (
|
|
133
|
+
id TEXT PRIMARY KEY,
|
|
134
|
+
"tokenHash" TEXT NOT NULL,
|
|
135
|
+
"userId" TEXT NOT NULL,
|
|
136
|
+
collection TEXT NOT NULL,
|
|
137
|
+
role TEXT,
|
|
138
|
+
"expiresAt" INTEGER NOT NULL,
|
|
139
|
+
"createdAt" TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
140
|
+
"updatedAt" TEXT DEFAULT CURRENT_TIMESTAMP
|
|
141
|
+
);`);
|
|
142
|
+
return statements;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/adapter.ts
|
|
146
|
+
class SQLiteAdapter extends BaseSqlAdapter {
|
|
147
|
+
adapterType = "sqlite";
|
|
148
|
+
supportsGeneratedConstraintSQL = true;
|
|
149
|
+
db = null;
|
|
150
|
+
url;
|
|
151
|
+
constructor(url) {
|
|
152
|
+
super();
|
|
153
|
+
this.url = url === "sqlite::memory:" ? ":memory:" : url;
|
|
154
|
+
}
|
|
155
|
+
initDb() {
|
|
156
|
+
let finalPath = this.url;
|
|
157
|
+
if (this.url !== ":memory:") {
|
|
158
|
+
let filepath = this.url.split("?")[0] || "";
|
|
159
|
+
if (filepath.startsWith("sqlite:"))
|
|
160
|
+
filepath = filepath.slice(7);
|
|
161
|
+
if (filepath.startsWith("file:"))
|
|
162
|
+
filepath = filepath.slice(5);
|
|
163
|
+
if (filepath.startsWith("//"))
|
|
164
|
+
filepath = filepath.slice(2);
|
|
165
|
+
const dir = dirname(filepath);
|
|
166
|
+
if (dir && dir !== "." && dir !== "/") {
|
|
167
|
+
try {
|
|
168
|
+
mkdirSync(dir, { recursive: true });
|
|
169
|
+
} catch {}
|
|
170
|
+
}
|
|
171
|
+
finalPath = filepath;
|
|
172
|
+
}
|
|
173
|
+
this.db = new Database(finalPath);
|
|
174
|
+
this.db.run("PRAGMA journal_mode = WAL;");
|
|
175
|
+
this.db.run("PRAGMA synchronous = NORMAL;");
|
|
176
|
+
}
|
|
177
|
+
async closeDb(dbInstance) {
|
|
178
|
+
if (dbInstance)
|
|
179
|
+
dbInstance.close();
|
|
180
|
+
}
|
|
181
|
+
async connect() {
|
|
182
|
+
this.initDb();
|
|
183
|
+
this.db.query("SELECT 1").get();
|
|
184
|
+
}
|
|
185
|
+
async disconnect() {
|
|
186
|
+
await this.closeDb(this.db);
|
|
187
|
+
this.db = null;
|
|
188
|
+
}
|
|
189
|
+
async transaction(callback) {
|
|
190
|
+
if (!this.db)
|
|
191
|
+
throw new Error("Database not connected");
|
|
192
|
+
this.db.query("BEGIN TRANSACTION").run();
|
|
193
|
+
try {
|
|
194
|
+
const trxAdapter = Object.create(this);
|
|
195
|
+
const result = await callback(trxAdapter);
|
|
196
|
+
this.db.query("COMMIT").run();
|
|
197
|
+
return result;
|
|
198
|
+
} catch (err) {
|
|
199
|
+
if (this.db) {
|
|
200
|
+
try {
|
|
201
|
+
this.db.query("ROLLBACK").run();
|
|
202
|
+
} catch {}
|
|
203
|
+
}
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async ping() {
|
|
208
|
+
try {
|
|
209
|
+
this.db.query("SELECT 1").get();
|
|
210
|
+
} catch (err) {
|
|
211
|
+
throw err;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
getSystemTableStatements() {
|
|
215
|
+
return generateSystemTables();
|
|
216
|
+
}
|
|
217
|
+
async getCurrentSchema() {
|
|
218
|
+
const tablesRaw = this.db.query("SELECT name FROM sqlite_master WHERE type='table'").all();
|
|
219
|
+
const tables = tablesRaw.map((r) => r.name);
|
|
220
|
+
const columns = {};
|
|
221
|
+
for (const table of tables) {
|
|
222
|
+
const colsRaw = this.db.query(`PRAGMA table_info("${table}")`).all();
|
|
223
|
+
columns[table] = colsRaw.map((c) => c.name);
|
|
224
|
+
}
|
|
225
|
+
return { tables, columns };
|
|
226
|
+
}
|
|
227
|
+
async recordMigration(version, description) {
|
|
228
|
+
const id = ulid();
|
|
229
|
+
this.db.query(`INSERT INTO cequre_migrations (id, version, description) VALUES (?, ?, ?)`).run(id, version, description);
|
|
230
|
+
}
|
|
231
|
+
async getCurrentMigrationVersion() {
|
|
232
|
+
const res = this.db.query(`SELECT version FROM cequre_migrations ORDER BY applied_at DESC LIMIT 1`).all();
|
|
233
|
+
return res[0]?.version || null;
|
|
234
|
+
}
|
|
235
|
+
createTableDDL(table) {
|
|
236
|
+
return generateCreateTable(buildTable(table));
|
|
237
|
+
}
|
|
238
|
+
renameColumnDDL(table, oldName, newName) {
|
|
239
|
+
return generateRenameColumn(table, oldName, newName);
|
|
240
|
+
}
|
|
241
|
+
addColumnDDL(table, column) {
|
|
242
|
+
const cols = buildColumns({ slug: table, fields: [column] });
|
|
243
|
+
const colDef = cols.find((c) => c.name === column.name);
|
|
244
|
+
if (!colDef)
|
|
245
|
+
return null;
|
|
246
|
+
return generateAddColumn(table, colDef);
|
|
247
|
+
}
|
|
248
|
+
dropColumnDDL(table, column) {
|
|
249
|
+
return `ALTER TABLE ${sqlIdentifier(table)} DROP COLUMN "${column}";`;
|
|
250
|
+
}
|
|
251
|
+
dropTableDDL(table) {
|
|
252
|
+
return `DROP TABLE IF EXISTS ${sqlIdentifier(table)};`;
|
|
253
|
+
}
|
|
254
|
+
deserializeRow(collection, row) {
|
|
255
|
+
if (!row)
|
|
256
|
+
return row;
|
|
257
|
+
const nums = this._numericFields.get(collection);
|
|
258
|
+
const jsons = this._jsonFields.get(collection);
|
|
259
|
+
const obj = { ...row };
|
|
260
|
+
if (nums) {
|
|
261
|
+
for (const k of nums) {
|
|
262
|
+
if (typeof obj[k] === "string") {
|
|
263
|
+
const parsed = Number(obj[k]);
|
|
264
|
+
if (!isNaN(parsed))
|
|
265
|
+
obj[k] = parsed;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (jsons) {
|
|
270
|
+
for (const k of jsons) {
|
|
271
|
+
if (typeof obj[k] === "string") {
|
|
272
|
+
try {
|
|
273
|
+
obj[k] = JSON.parse(obj[k]);
|
|
274
|
+
} catch {}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return obj;
|
|
279
|
+
}
|
|
280
|
+
buildWhere(where, collection, params) {
|
|
281
|
+
if (!where || Object.keys(where).length === 0)
|
|
282
|
+
return "";
|
|
283
|
+
const clauses = [];
|
|
284
|
+
if (where.OR) {
|
|
285
|
+
const orClauses = where.OR.map((cond) => this.buildWhere(cond, collection, params)).filter(Boolean);
|
|
286
|
+
if (orClauses.length > 0)
|
|
287
|
+
clauses.push(`(${orClauses.join(" OR ")})`);
|
|
288
|
+
}
|
|
289
|
+
if (where.AND) {
|
|
290
|
+
const andClauses = where.AND.map((cond) => this.buildWhere(cond, collection, params)).filter(Boolean);
|
|
291
|
+
if (andClauses.length > 0)
|
|
292
|
+
clauses.push(`(${andClauses.join(" AND ")})`);
|
|
293
|
+
}
|
|
294
|
+
const known = this._knownFields.get(collection);
|
|
295
|
+
for (const [key, value] of Object.entries(where)) {
|
|
296
|
+
if (key === "OR" || key === "AND")
|
|
297
|
+
continue;
|
|
298
|
+
let columnRef = `"${key}"`;
|
|
299
|
+
if (key.includes(".")) {
|
|
300
|
+
const parts = key.split(".");
|
|
301
|
+
const root = parts.shift();
|
|
302
|
+
const path = `$.${parts.join(".")}`;
|
|
303
|
+
columnRef = `json_extract("${root}", '${path}')`;
|
|
304
|
+
}
|
|
305
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
306
|
+
const ops = value;
|
|
307
|
+
const isSensitive = ops.mode === "sensitive";
|
|
308
|
+
for (const [op, val] of Object.entries(ops)) {
|
|
309
|
+
if (op === "mode")
|
|
310
|
+
continue;
|
|
311
|
+
if (op === "eq") {
|
|
312
|
+
params.push(val);
|
|
313
|
+
clauses.push(`${columnRef} = ?`);
|
|
314
|
+
} else if (op === "neq") {
|
|
315
|
+
params.push(val);
|
|
316
|
+
clauses.push(`${columnRef} != ?`);
|
|
317
|
+
} else if (op === "gt") {
|
|
318
|
+
params.push(val);
|
|
319
|
+
clauses.push(`${columnRef} > ?`);
|
|
320
|
+
} else if (op === "gte") {
|
|
321
|
+
params.push(val);
|
|
322
|
+
clauses.push(`${columnRef} >= ?`);
|
|
323
|
+
} else if (op === "lt") {
|
|
324
|
+
params.push(val);
|
|
325
|
+
clauses.push(`${columnRef} < ?`);
|
|
326
|
+
} else if (op === "lte") {
|
|
327
|
+
params.push(val);
|
|
328
|
+
clauses.push(`${columnRef} <= ?`);
|
|
329
|
+
} else if (op === "in" && Array.isArray(val) && val.length > 0) {
|
|
330
|
+
const placeholders = val.map((v) => {
|
|
331
|
+
params.push(v);
|
|
332
|
+
return "?";
|
|
333
|
+
}).join(", ");
|
|
334
|
+
clauses.push(`${columnRef} IN (${placeholders})`);
|
|
335
|
+
} else if (op === "nin" && Array.isArray(val) && val.length > 0) {
|
|
336
|
+
const placeholders = val.map((v) => {
|
|
337
|
+
params.push(v);
|
|
338
|
+
return "?";
|
|
339
|
+
}).join(", ");
|
|
340
|
+
clauses.push(`${columnRef} NOT IN (${placeholders})`);
|
|
341
|
+
} else if (op === "any" && Array.isArray(val) && val.length > 0) {
|
|
342
|
+
const placeholders = val.map((v) => {
|
|
343
|
+
params.push(v);
|
|
344
|
+
return "?";
|
|
345
|
+
}).join(", ");
|
|
346
|
+
clauses.push(`EXISTS (SELECT 1 FROM json_each(${columnRef}) WHERE value IN (${placeholders}))`);
|
|
347
|
+
} else if (op === "all" && Array.isArray(val) && val.length > 0) {
|
|
348
|
+
const allClauses = val.map((v) => {
|
|
349
|
+
params.push(v);
|
|
350
|
+
return `EXISTS (SELECT 1 FROM json_each(${columnRef}) WHERE value = ?)`;
|
|
351
|
+
});
|
|
352
|
+
clauses.push(`(${allClauses.join(" AND ")})`);
|
|
353
|
+
} else if (op === "like") {
|
|
354
|
+
params.push(val);
|
|
355
|
+
clauses.push(`${columnRef} ${isSensitive ? "GLOB" : "LIKE"} ?`);
|
|
356
|
+
} else if (op === "contains") {
|
|
357
|
+
params.push(isSensitive ? `*${val}*` : `%${val}%`);
|
|
358
|
+
clauses.push(`${columnRef} ${isSensitive ? "GLOB" : "LIKE"} ?`);
|
|
359
|
+
} else if (op === "startsWith") {
|
|
360
|
+
params.push(isSensitive ? `${val}*` : `${val}%`);
|
|
361
|
+
clauses.push(`${columnRef} ${isSensitive ? "GLOB" : "LIKE"} ?`);
|
|
362
|
+
} else if (op === "endsWith") {
|
|
363
|
+
params.push(isSensitive ? `*${val}` : `%${val}`);
|
|
364
|
+
clauses.push(`${columnRef} ${isSensitive ? "GLOB" : "LIKE"} ?`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
params.push(value);
|
|
369
|
+
clauses.push(`${columnRef} = ?`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return clauses.join(" AND ");
|
|
373
|
+
}
|
|
374
|
+
async find(collection, query) {
|
|
375
|
+
if (!this.db)
|
|
376
|
+
throw new Error("Database not connected");
|
|
377
|
+
const params = [];
|
|
378
|
+
let sqlStr = `SELECT * FROM ${sqlIdentifier(collection)}`;
|
|
379
|
+
const whereStr = this.buildWhere(query.where, collection, params);
|
|
380
|
+
if (whereStr)
|
|
381
|
+
sqlStr += ` WHERE ${whereStr}`;
|
|
382
|
+
if (query.sort) {
|
|
383
|
+
const isDesc = query.sort.startsWith("-");
|
|
384
|
+
const field = isDesc ? query.sort.substring(1) : query.sort;
|
|
385
|
+
sqlStr += ` ORDER BY "${field}" ${isDesc ? "DESC" : "ASC"}`;
|
|
386
|
+
}
|
|
387
|
+
const limit = query.limit || 10;
|
|
388
|
+
const page = query.page || 1;
|
|
389
|
+
const offset = (page - 1) * limit;
|
|
390
|
+
sqlStr += ` LIMIT ${limit} OFFSET ${offset}`;
|
|
391
|
+
const stmt = this.db.query(sqlStr);
|
|
392
|
+
const results = stmt.all(...params);
|
|
393
|
+
let countSql = `SELECT COUNT(*) as count FROM ${sqlIdentifier(collection)}`;
|
|
394
|
+
if (whereStr)
|
|
395
|
+
countSql += ` WHERE ${whereStr}`;
|
|
396
|
+
const countRes = this.db.query(countSql).all(...params);
|
|
397
|
+
const totalDocs = Number(countRes[0]?.count || 0);
|
|
398
|
+
const totalPages = Math.ceil(totalDocs / limit);
|
|
399
|
+
return {
|
|
400
|
+
docs: results.map((r) => this.deserializeRow(collection, r)),
|
|
401
|
+
totalDocs,
|
|
402
|
+
limit,
|
|
403
|
+
page,
|
|
404
|
+
totalPages,
|
|
405
|
+
hasNextPage: page < totalPages,
|
|
406
|
+
hasPrevPage: page > 1
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
async findById(collection, id) {
|
|
410
|
+
if (!this.db)
|
|
411
|
+
throw new Error("Database not connected");
|
|
412
|
+
const results = this.db.query(`SELECT * FROM ${sqlIdentifier(collection)} WHERE id = ?`).all(id);
|
|
413
|
+
if (!results || results.length === 0)
|
|
414
|
+
return null;
|
|
415
|
+
return this.deserializeRow(collection, results[0]);
|
|
416
|
+
}
|
|
417
|
+
async findByIds(collection, ids) {
|
|
418
|
+
if (!this.db)
|
|
419
|
+
throw new Error("Database not connected");
|
|
420
|
+
if (!ids.length)
|
|
421
|
+
return [];
|
|
422
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
423
|
+
const results = this.db.query(`SELECT * FROM ${sqlIdentifier(collection)} WHERE id IN (${placeholders})`).all(...ids);
|
|
424
|
+
return results.map((r) => this.deserializeRow(collection, r));
|
|
425
|
+
}
|
|
426
|
+
async create(collection, data) {
|
|
427
|
+
if (!this.db)
|
|
428
|
+
throw new Error("Database not connected");
|
|
429
|
+
if (!("id" in data) || data.id === undefined || data.id === null) {
|
|
430
|
+
data = { id: ulid(), ...data };
|
|
431
|
+
}
|
|
432
|
+
const keys = Object.keys(data);
|
|
433
|
+
const values = Object.values(data).map((v) => typeof v === "object" && v !== null && !(v instanceof Date) ? JSON.stringify(v) : v);
|
|
434
|
+
const keyStr = keys.map((k) => `"${k}"`).join(", ");
|
|
435
|
+
const valStr = keys.map(() => "?").join(", ");
|
|
436
|
+
const sqlStr = `INSERT INTO ${sqlIdentifier(collection)} (${keyStr}) VALUES (${valStr}) RETURNING *`;
|
|
437
|
+
const results = this.db.query(sqlStr).all(...values);
|
|
438
|
+
return this.deserializeRow(collection, results[0]);
|
|
439
|
+
}
|
|
440
|
+
async createMany(collection, docs) {
|
|
441
|
+
if (!this.db)
|
|
442
|
+
throw new Error("Database not connected");
|
|
443
|
+
if (!docs.length)
|
|
444
|
+
return [];
|
|
445
|
+
const knownFields = this._knownFields.get(collection);
|
|
446
|
+
const now = new Date().toISOString();
|
|
447
|
+
docs = docs.map((doc) => {
|
|
448
|
+
const paddedDoc = { ...doc };
|
|
449
|
+
if (!("id" in paddedDoc) || paddedDoc.id === undefined || paddedDoc.id === null) {
|
|
450
|
+
paddedDoc.id = ulid();
|
|
451
|
+
}
|
|
452
|
+
if (knownFields) {
|
|
453
|
+
for (const field of knownFields) {
|
|
454
|
+
if (!(field in paddedDoc))
|
|
455
|
+
paddedDoc[field] = null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (!knownFields || knownFields.has("createdAt"))
|
|
459
|
+
paddedDoc.createdAt = paddedDoc.createdAt ?? now;
|
|
460
|
+
if (!knownFields || knownFields.has("updatedAt"))
|
|
461
|
+
paddedDoc.updatedAt = now;
|
|
462
|
+
return paddedDoc;
|
|
463
|
+
});
|
|
464
|
+
const keys = Object.keys(docs[0]);
|
|
465
|
+
const keyStr = keys.map((k) => `"${k}"`).join(", ");
|
|
466
|
+
const allValues = [];
|
|
467
|
+
const valStrs = docs.map((doc) => {
|
|
468
|
+
keys.forEach((k) => {
|
|
469
|
+
const v = doc[k];
|
|
470
|
+
allValues.push(typeof v === "object" && v !== null && !(v instanceof Date) ? JSON.stringify(v) : v);
|
|
471
|
+
});
|
|
472
|
+
return `(${keys.map(() => "?").join(", ")})`;
|
|
473
|
+
});
|
|
474
|
+
const sqlStr = `INSERT INTO ${sqlIdentifier(collection)} (${keyStr}) VALUES ${valStrs.join(", ")} RETURNING *`;
|
|
475
|
+
const results = this.db.query(sqlStr).all(...allValues);
|
|
476
|
+
return results.map((r) => this.deserializeRow(collection, r));
|
|
477
|
+
}
|
|
478
|
+
async update(collection, id, data) {
|
|
479
|
+
if (!this.db)
|
|
480
|
+
throw new Error("Database not connected");
|
|
481
|
+
const keys = Object.keys(data);
|
|
482
|
+
if (!keys.length)
|
|
483
|
+
return this.findById(collection, id);
|
|
484
|
+
const jsonFields = this._jsonFields.get(collection);
|
|
485
|
+
const setStrs = [];
|
|
486
|
+
const values = [];
|
|
487
|
+
for (const [k, v] of Object.entries(data)) {
|
|
488
|
+
const isJson = jsonFields?.has(k);
|
|
489
|
+
const isObject = typeof v === "object" && v !== null && !(v instanceof Date);
|
|
490
|
+
if (isJson && isObject) {
|
|
491
|
+
setStrs.push(`"${k}" = json_patch(IFNULL("${k}", '{}'), ?)`);
|
|
492
|
+
values.push(JSON.stringify(v));
|
|
493
|
+
} else {
|
|
494
|
+
setStrs.push(`"${k}" = ?`);
|
|
495
|
+
values.push(isObject ? JSON.stringify(v) : v);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
values.push(id);
|
|
499
|
+
const setStr = setStrs.join(", ");
|
|
500
|
+
const sqlStr = `UPDATE ${sqlIdentifier(collection)} SET ${setStr} WHERE id = ? RETURNING *`;
|
|
501
|
+
const results = this.db.query(sqlStr).all(...values);
|
|
502
|
+
if (!results || results.length === 0)
|
|
503
|
+
throw new Error("Document not found");
|
|
504
|
+
return this.deserializeRow(collection, results[0]);
|
|
505
|
+
}
|
|
506
|
+
async delete(collection, id) {
|
|
507
|
+
if (!this.db)
|
|
508
|
+
throw new Error("Database not connected");
|
|
509
|
+
this.db.query(`DELETE FROM ${sqlIdentifier(collection)} WHERE id = ?`).run(id);
|
|
510
|
+
}
|
|
511
|
+
async deleteMany(collection, ids) {
|
|
512
|
+
if (!this.db)
|
|
513
|
+
throw new Error("Database not connected");
|
|
514
|
+
if (!ids.length)
|
|
515
|
+
return;
|
|
516
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
517
|
+
this.db.query(`DELETE FROM ${sqlIdentifier(collection)} WHERE id IN (${placeholders})`).run(...ids);
|
|
518
|
+
}
|
|
519
|
+
async count(collection, query) {
|
|
520
|
+
if (!this.db)
|
|
521
|
+
throw new Error("Database not connected");
|
|
522
|
+
const params = [];
|
|
523
|
+
let sqlStr = `SELECT COUNT(*) as count FROM ${sqlIdentifier(collection)}`;
|
|
524
|
+
if (query?.where) {
|
|
525
|
+
const whereStr = this.buildWhere(query.where, collection, params);
|
|
526
|
+
if (whereStr)
|
|
527
|
+
sqlStr += ` WHERE ${whereStr}`;
|
|
528
|
+
}
|
|
529
|
+
const countRes = this.db.query(sqlStr).all(...params);
|
|
530
|
+
return Number(countRes[0]?.count || 0);
|
|
531
|
+
}
|
|
532
|
+
async raw(sql, params) {
|
|
533
|
+
if (!this.db)
|
|
534
|
+
throw new Error("Database not connected");
|
|
535
|
+
const bindings = params || [];
|
|
536
|
+
if (sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("PRAGMA")) {
|
|
537
|
+
return this.db.query(sql).all(...bindings);
|
|
538
|
+
} else {
|
|
539
|
+
return this.db.query(sql).run(...bindings);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// src/index.ts
|
|
545
|
+
function sqlite(config) {
|
|
546
|
+
return new SQLiteAdapter(config.url);
|
|
547
|
+
}
|
|
548
|
+
export {
|
|
549
|
+
sqlite,
|
|
550
|
+
SQLiteAdapter
|
|
551
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cequrebackends/plugin-sqlite",
|
|
3
|
+
"version": "0.12.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "rm -rf dist && bun build ./src/index.ts --outdir ./dist --target bun --packages external && tsc --emitDeclarationOnly"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"tsup": "^8.5.1",
|
|
16
|
+
"typescript": "^7.0.1-rc",
|
|
17
|
+
"@cequrebackends/cequre-ts": "0.12.2"
|
|
18
|
+
},
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public",
|
|
29
|
+
"registry": "https://registry.npmjs.org/"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/cequrebackends/cequre.git"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@cequrebackends/cequre-ts": "0.12.2"
|
|
37
|
+
}
|
|
38
|
+
}
|