@b4moss/crudian 0.3.1
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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/bun-sqlite/crud.d.ts +9 -0
- package/dist/bun-sqlite/crud.d.ts.map +1 -0
- package/dist/bun-sqlite/crud.js +32 -0
- package/dist/bun-sqlite/index.d.ts +9 -0
- package/dist/bun-sqlite/index.d.ts.map +1 -0
- package/dist/bun-sqlite/index.js +8 -0
- package/dist/bun-sqlite/node-stub.d.ts +2 -0
- package/dist/bun-sqlite/node-stub.d.ts.map +1 -0
- package/dist/bun-sqlite/node-stub.js +2 -0
- package/dist/bun-sqlite/sql.d.ts +2 -0
- package/dist/bun-sqlite/sql.d.ts.map +1 -0
- package/dist/bun-sqlite/sql.js +1 -0
- package/dist/drizzle/crud.d.ts +14 -0
- package/dist/drizzle/crud.d.ts.map +1 -0
- package/dist/drizzle/crud.js +33 -0
- package/dist/drizzle/index.d.ts +8 -0
- package/dist/drizzle/index.d.ts.map +1 -0
- package/dist/drizzle/index.js +6 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/prisma/crud.d.ts +14 -0
- package/dist/prisma/crud.d.ts.map +1 -0
- package/dist/prisma/crud.js +53 -0
- package/dist/prisma/index.d.ts +8 -0
- package/dist/prisma/index.d.ts.map +1 -0
- package/dist/prisma/index.js +6 -0
- package/dist/sqlite/async-crud.d.ts +27 -0
- package/dist/sqlite/async-crud.d.ts.map +1 -0
- package/dist/sqlite/async-crud.js +237 -0
- package/dist/sqlite/sql.d.ts +8 -0
- package/dist/sqlite/sql.d.ts.map +1 -0
- package/dist/sqlite/sql.js +72 -0
- package/dist/sqlite/sync-crud.d.ts +27 -0
- package/dist/sqlite/sync-crud.d.ts.map +1 -0
- package/dist/sqlite/sync-crud.js +237 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/where.d.ts +36 -0
- package/dist/where.d.ts.map +1 -0
- package/dist/where.js +75 -0
- package/package.json +91 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { CrudianError, assertString, } from "../index.js";
|
|
2
|
+
import { compileWhere, quoteIdent, resolveWhere } from "./sql.js";
|
|
3
|
+
function requireWhere(query, label) {
|
|
4
|
+
if (query.where === undefined || query.where === null) {
|
|
5
|
+
throw new CrudianError(`${label} requires where`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function selectColumns(columns) {
|
|
9
|
+
if (!columns || columns.length === 0)
|
|
10
|
+
return "*";
|
|
11
|
+
return columns.map((c) => quoteIdent(c)).join(", ");
|
|
12
|
+
}
|
|
13
|
+
function rowFromObject(value) {
|
|
14
|
+
if (value === null || typeof value !== "object") {
|
|
15
|
+
throw new CrudianError("expected row object");
|
|
16
|
+
}
|
|
17
|
+
return { ...value };
|
|
18
|
+
}
|
|
19
|
+
export function createAsyncSqliteCrud(db, ex) {
|
|
20
|
+
const crud = {
|
|
21
|
+
db,
|
|
22
|
+
async create(table, cols) {
|
|
23
|
+
assertString(table, "table");
|
|
24
|
+
if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
|
|
25
|
+
throw new CrudianError("cols must be an object");
|
|
26
|
+
}
|
|
27
|
+
const keys = Object.keys(cols);
|
|
28
|
+
if (keys.length === 0) {
|
|
29
|
+
throw new CrudianError("cols must not be empty");
|
|
30
|
+
}
|
|
31
|
+
const tbl = quoteIdent(table);
|
|
32
|
+
const colSql = keys.map((k) => quoteIdent(k)).join(", ");
|
|
33
|
+
const placeholders = keys.map(() => "?").join(", ");
|
|
34
|
+
const args = keys.map((k) => cols[k]);
|
|
35
|
+
await ex.run(`INSERT INTO ${tbl} (${colSql}) VALUES (${placeholders})`, args);
|
|
36
|
+
const idRow = await ex.get("SELECT last_insert_rowid() AS id");
|
|
37
|
+
const id = Number(idRow?.id);
|
|
38
|
+
const row = await ex.get(`SELECT * FROM ${tbl} WHERE "id" = ?`, [id]);
|
|
39
|
+
return rowFromObject(row);
|
|
40
|
+
},
|
|
41
|
+
async read(table, query = {}) {
|
|
42
|
+
assertString(table, "table");
|
|
43
|
+
const tbl = quoteIdent(table);
|
|
44
|
+
const where = compileWhere(resolveWhere(query.where));
|
|
45
|
+
const sql = `SELECT ${selectColumns(query.columns)} FROM ${tbl}` +
|
|
46
|
+
(where.sql ? ` WHERE ${where.sql}` : "") +
|
|
47
|
+
` LIMIT 1`;
|
|
48
|
+
const row = await ex.get(sql, where.args);
|
|
49
|
+
return row == null ? null : rowFromObject(row);
|
|
50
|
+
},
|
|
51
|
+
async update(table, cols, query) {
|
|
52
|
+
assertString(table, "table");
|
|
53
|
+
requireWhere(query, "update");
|
|
54
|
+
if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
|
|
55
|
+
throw new CrudianError("cols must be an object");
|
|
56
|
+
}
|
|
57
|
+
const keys = Object.keys(cols);
|
|
58
|
+
if (keys.length === 0) {
|
|
59
|
+
throw new CrudianError("cols must not be empty");
|
|
60
|
+
}
|
|
61
|
+
const tbl = quoteIdent(table);
|
|
62
|
+
const where = compileWhere(resolveWhere(query.where));
|
|
63
|
+
if (!where.sql) {
|
|
64
|
+
throw new CrudianError("update requires where");
|
|
65
|
+
}
|
|
66
|
+
const sets = keys.map((k) => `${quoteIdent(k)} = ?`).join(", ");
|
|
67
|
+
const args = [...keys.map((k) => cols[k]), ...where.args];
|
|
68
|
+
const result = await ex.run(`UPDATE ${tbl} SET ${sets} WHERE ${where.sql}`, args);
|
|
69
|
+
if (result.changes === 0)
|
|
70
|
+
return null;
|
|
71
|
+
const row = await ex.get(`SELECT * FROM ${tbl} WHERE ${where.sql} LIMIT 1`, where.args);
|
|
72
|
+
return row == null ? null : rowFromObject(row);
|
|
73
|
+
},
|
|
74
|
+
async delete(table, query) {
|
|
75
|
+
assertString(table, "table");
|
|
76
|
+
requireWhere(query, "delete");
|
|
77
|
+
const tbl = quoteIdent(table);
|
|
78
|
+
const where = compileWhere(resolveWhere(query.where));
|
|
79
|
+
if (!where.sql) {
|
|
80
|
+
throw new CrudianError("delete requires where");
|
|
81
|
+
}
|
|
82
|
+
const result = await ex.run(`DELETE FROM ${tbl} WHERE ${where.sql}`, where.args);
|
|
83
|
+
return Number(result.changes ?? 0);
|
|
84
|
+
},
|
|
85
|
+
async search(table, query = {}) {
|
|
86
|
+
assertString(table, "table");
|
|
87
|
+
const limit = query.limit ?? 20;
|
|
88
|
+
if (typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0) {
|
|
89
|
+
throw new CrudianError("limit must be a positive number");
|
|
90
|
+
}
|
|
91
|
+
if (query.cursor != null &&
|
|
92
|
+
typeof query.cursor !== "number" &&
|
|
93
|
+
typeof query.cursor !== "string") {
|
|
94
|
+
throw new CrudianError("cursor must be a number, string, or null");
|
|
95
|
+
}
|
|
96
|
+
const tbl = quoteIdent(table);
|
|
97
|
+
const where = compileWhere(resolveWhere(query.where));
|
|
98
|
+
const args = [...where.args];
|
|
99
|
+
const parts = [];
|
|
100
|
+
if (where.sql)
|
|
101
|
+
parts.push(`(${where.sql})`);
|
|
102
|
+
if (query.cursor != null) {
|
|
103
|
+
parts.push(`${quoteIdent("id")} > ?`);
|
|
104
|
+
args.push(query.cursor);
|
|
105
|
+
}
|
|
106
|
+
const whereSql = parts.length > 0 ? ` WHERE ${parts.join(" AND ")}` : "";
|
|
107
|
+
const sql = `SELECT ${selectColumns(query.columns)} FROM ${tbl}` +
|
|
108
|
+
whereSql +
|
|
109
|
+
` ORDER BY ${quoteIdent("id")} ASC LIMIT ?`;
|
|
110
|
+
args.push(limit + 1);
|
|
111
|
+
const rows = (await ex.all(sql, args)).map((r) => rowFromObject(r));
|
|
112
|
+
const hasMore = rows.length > limit;
|
|
113
|
+
const items = hasMore ? rows.slice(0, limit) : rows;
|
|
114
|
+
const last = items[items.length - 1];
|
|
115
|
+
const nextCursor = hasMore && last != null && (typeof last.id === "number" || typeof last.id === "string")
|
|
116
|
+
? last.id
|
|
117
|
+
: null;
|
|
118
|
+
return { items, nextCursor, hasMore };
|
|
119
|
+
},
|
|
120
|
+
async list(table, query) {
|
|
121
|
+
return crud.search(table, query);
|
|
122
|
+
},
|
|
123
|
+
async upsert(table, cols) {
|
|
124
|
+
assertString(table, "table");
|
|
125
|
+
if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
|
|
126
|
+
throw new CrudianError("cols must be an object");
|
|
127
|
+
}
|
|
128
|
+
const keys = Object.keys(cols);
|
|
129
|
+
if (keys.length === 0) {
|
|
130
|
+
throw new CrudianError("cols must not be empty");
|
|
131
|
+
}
|
|
132
|
+
if (!Object.prototype.hasOwnProperty.call(cols, "id")) {
|
|
133
|
+
throw new CrudianError("upsert requires cols.id");
|
|
134
|
+
}
|
|
135
|
+
const id = cols.id;
|
|
136
|
+
const existing = await crud.read(table, {
|
|
137
|
+
where: { type: "cond", op: "eq", column: "id", value: id },
|
|
138
|
+
});
|
|
139
|
+
if (existing != null) {
|
|
140
|
+
const { id: _id, ...patch } = cols;
|
|
141
|
+
if (Object.keys(patch).length === 0)
|
|
142
|
+
return existing;
|
|
143
|
+
const updated = await crud.update(table, patch, {
|
|
144
|
+
where: { type: "cond", op: "eq", column: "id", value: id },
|
|
145
|
+
});
|
|
146
|
+
if (updated == null) {
|
|
147
|
+
throw new CrudianError("upsert update failed");
|
|
148
|
+
}
|
|
149
|
+
return updated;
|
|
150
|
+
}
|
|
151
|
+
return crud.create(table, cols);
|
|
152
|
+
},
|
|
153
|
+
async duplicate(table, query) {
|
|
154
|
+
assertString(table, "table");
|
|
155
|
+
requireWhere(query, "duplicate");
|
|
156
|
+
const source = await crud.read(table, { where: query.where });
|
|
157
|
+
if (source == null)
|
|
158
|
+
return null;
|
|
159
|
+
const { id: _id, ...rest } = source;
|
|
160
|
+
const overrides = query.overrides != null &&
|
|
161
|
+
typeof query.overrides === "object" &&
|
|
162
|
+
!Array.isArray(query.overrides)
|
|
163
|
+
? query.overrides
|
|
164
|
+
: {};
|
|
165
|
+
const cols = { ...rest, ...overrides };
|
|
166
|
+
delete cols.id;
|
|
167
|
+
return crud.create(table, cols);
|
|
168
|
+
},
|
|
169
|
+
async bulkCreate(table, rows) {
|
|
170
|
+
assertString(table, "table");
|
|
171
|
+
if (!Array.isArray(rows)) {
|
|
172
|
+
throw new CrudianError("rows must be an array");
|
|
173
|
+
}
|
|
174
|
+
if (rows.length === 0)
|
|
175
|
+
return 0;
|
|
176
|
+
let count = 0;
|
|
177
|
+
for (const row of rows) {
|
|
178
|
+
if (row == null || typeof row !== "object" || Array.isArray(row)) {
|
|
179
|
+
throw new CrudianError("each row must be an object");
|
|
180
|
+
}
|
|
181
|
+
await crud.create(table, row);
|
|
182
|
+
count += 1;
|
|
183
|
+
}
|
|
184
|
+
return count;
|
|
185
|
+
},
|
|
186
|
+
async bulkUpdate(table, cols, query) {
|
|
187
|
+
assertString(table, "table");
|
|
188
|
+
requireWhere(query, "bulkUpdate");
|
|
189
|
+
if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
|
|
190
|
+
throw new CrudianError("cols must be an object");
|
|
191
|
+
}
|
|
192
|
+
const keys = Object.keys(cols);
|
|
193
|
+
if (keys.length === 0) {
|
|
194
|
+
throw new CrudianError("cols must not be empty");
|
|
195
|
+
}
|
|
196
|
+
const tbl = quoteIdent(table);
|
|
197
|
+
const where = compileWhere(resolveWhere(query.where));
|
|
198
|
+
if (!where.sql) {
|
|
199
|
+
throw new CrudianError("bulkUpdate requires where");
|
|
200
|
+
}
|
|
201
|
+
const sets = keys.map((k) => `${quoteIdent(k)} = ?`).join(", ");
|
|
202
|
+
const args = [...keys.map((k) => cols[k]), ...where.args];
|
|
203
|
+
const result = await ex.run(`UPDATE ${tbl} SET ${sets} WHERE ${where.sql}`, args);
|
|
204
|
+
return Number(result.changes ?? 0);
|
|
205
|
+
},
|
|
206
|
+
async bulkDelete(table, query) {
|
|
207
|
+
return crud.delete(table, query);
|
|
208
|
+
},
|
|
209
|
+
async bulkUpsert(table, rows) {
|
|
210
|
+
assertString(table, "table");
|
|
211
|
+
if (!Array.isArray(rows)) {
|
|
212
|
+
throw new CrudianError("rows must be an array");
|
|
213
|
+
}
|
|
214
|
+
if (rows.length === 0)
|
|
215
|
+
return 0;
|
|
216
|
+
let count = 0;
|
|
217
|
+
for (const row of rows) {
|
|
218
|
+
if (row == null || typeof row !== "object" || Array.isArray(row)) {
|
|
219
|
+
throw new CrudianError("each row must be an object");
|
|
220
|
+
}
|
|
221
|
+
if (!Object.prototype.hasOwnProperty.call(row, "id")) {
|
|
222
|
+
throw new CrudianError("bulkUpsert requires each row to have id");
|
|
223
|
+
}
|
|
224
|
+
await crud.upsert(table, row);
|
|
225
|
+
count += 1;
|
|
226
|
+
}
|
|
227
|
+
return count;
|
|
228
|
+
},
|
|
229
|
+
async transaction(fn) {
|
|
230
|
+
if (typeof fn !== "function") {
|
|
231
|
+
throw new CrudianError("transaction callback must be a function");
|
|
232
|
+
}
|
|
233
|
+
return ex.transaction(fn);
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
return crud;
|
|
237
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type WhereInput, type WhereNode } from "../index.js";
|
|
2
|
+
export declare function quoteIdent(name: string): string;
|
|
3
|
+
export declare function resolveWhere(input: WhereInput | undefined): WhereNode | undefined;
|
|
4
|
+
export declare function compileWhere(node: WhereNode | undefined): {
|
|
5
|
+
sql: string;
|
|
6
|
+
args: unknown[];
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../src/sqlite/sql.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,SAAS,EACf,MAAM,aAAa,CAAA;AAEpB,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAIjF;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,EAAE,CAAA;CAAE,CAyD1F"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { CrudianError, assertString, isWhereBuilder, } from "../index.js";
|
|
2
|
+
export function quoteIdent(name) {
|
|
3
|
+
assertString(name, "identifier");
|
|
4
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
5
|
+
}
|
|
6
|
+
export function resolveWhere(input) {
|
|
7
|
+
if (input === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
if (isWhereBuilder(input))
|
|
10
|
+
return input.toNode();
|
|
11
|
+
return input;
|
|
12
|
+
}
|
|
13
|
+
export function compileWhere(node) {
|
|
14
|
+
if (!node)
|
|
15
|
+
return { sql: "", args: [] };
|
|
16
|
+
if (node.type === "and" || node.type === "or") {
|
|
17
|
+
if (node.children.length === 0)
|
|
18
|
+
return { sql: "", args: [] };
|
|
19
|
+
const parts = [];
|
|
20
|
+
const args = [];
|
|
21
|
+
for (const child of node.children) {
|
|
22
|
+
const compiled = compileWhere(child);
|
|
23
|
+
if (!compiled.sql)
|
|
24
|
+
continue;
|
|
25
|
+
parts.push(`(${compiled.sql})`);
|
|
26
|
+
args.push(...compiled.args);
|
|
27
|
+
}
|
|
28
|
+
if (parts.length === 0)
|
|
29
|
+
return { sql: "", args: [] };
|
|
30
|
+
if (parts.length === 1)
|
|
31
|
+
return { sql: parts[0].slice(1, -1), args };
|
|
32
|
+
const joiner = node.type === "and" ? " AND " : " OR ";
|
|
33
|
+
return { sql: parts.join(joiner), args };
|
|
34
|
+
}
|
|
35
|
+
if (node.type !== "cond") {
|
|
36
|
+
throw new CrudianError("invalid where node");
|
|
37
|
+
}
|
|
38
|
+
const col = quoteIdent(node.column);
|
|
39
|
+
switch (node.op) {
|
|
40
|
+
case "eq":
|
|
41
|
+
return { sql: `${col} = ?`, args: [node.value] };
|
|
42
|
+
case "ne":
|
|
43
|
+
return { sql: `${col} <> ?`, args: [node.value] };
|
|
44
|
+
case "lt":
|
|
45
|
+
return { sql: `${col} < ?`, args: [node.value] };
|
|
46
|
+
case "gt":
|
|
47
|
+
return { sql: `${col} > ?`, args: [node.value] };
|
|
48
|
+
case "lte":
|
|
49
|
+
return { sql: `${col} <= ?`, args: [node.value] };
|
|
50
|
+
case "gte":
|
|
51
|
+
return { sql: `${col} >= ?`, args: [node.value] };
|
|
52
|
+
case "like":
|
|
53
|
+
return { sql: `${col} LIKE ?`, args: [node.value] };
|
|
54
|
+
case "isNull":
|
|
55
|
+
return { sql: `${col} IS NULL`, args: [] };
|
|
56
|
+
case "isNotNull":
|
|
57
|
+
return { sql: `${col} IS NOT NULL`, args: [] };
|
|
58
|
+
case "in": {
|
|
59
|
+
const values = node.value;
|
|
60
|
+
if (!Array.isArray(values)) {
|
|
61
|
+
throw new CrudianError("in value must be an array");
|
|
62
|
+
}
|
|
63
|
+
if (values.length === 0) {
|
|
64
|
+
throw new CrudianError("in requires a non-empty array");
|
|
65
|
+
}
|
|
66
|
+
const placeholders = values.map(() => "?").join(", ");
|
|
67
|
+
return { sql: `${col} IN (${placeholders})`, args: values };
|
|
68
|
+
}
|
|
69
|
+
default:
|
|
70
|
+
throw new CrudianError(`unknown op: ${node.op}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type DeleteQuery, type DuplicateQuery, type ReadQuery, type Row, type SearchQuery, type SearchResult, type UpdateQuery } from "../index.js";
|
|
2
|
+
export type SyncSqliteExecutor = {
|
|
3
|
+
run(sql: string, args?: unknown[]): {
|
|
4
|
+
changes: number;
|
|
5
|
+
};
|
|
6
|
+
get(sql: string, args?: unknown[]): Row | undefined;
|
|
7
|
+
all(sql: string, args?: unknown[]): Row[];
|
|
8
|
+
transaction<T>(fn: () => T): T;
|
|
9
|
+
};
|
|
10
|
+
export type SyncSqliteCrud<TDb> = {
|
|
11
|
+
readonly db: TDb;
|
|
12
|
+
create<T extends Row = Row>(table: string, cols: Record<string, unknown>): T;
|
|
13
|
+
read<T extends Row = Row>(table: string, query?: ReadQuery): T | null;
|
|
14
|
+
update<T extends Row = Row>(table: string, cols: Record<string, unknown>, query: UpdateQuery): T | null;
|
|
15
|
+
delete(table: string, query: DeleteQuery): number;
|
|
16
|
+
upsert<T extends Row = Row>(table: string, cols: Record<string, unknown>): T;
|
|
17
|
+
duplicate<T extends Row = Row>(table: string, query: DuplicateQuery): T | null;
|
|
18
|
+
bulkCreate(table: string, rows: Record<string, unknown>[]): number;
|
|
19
|
+
bulkUpdate(table: string, cols: Record<string, unknown>, query: UpdateQuery): number;
|
|
20
|
+
bulkDelete(table: string, query: DeleteQuery): number;
|
|
21
|
+
bulkUpsert(table: string, rows: Record<string, unknown>[]): number;
|
|
22
|
+
search<T extends Row = Row>(table: string, query?: SearchQuery): SearchResult<T>;
|
|
23
|
+
list<T extends Row = Row>(table: string, query?: SearchQuery): SearchResult<T>;
|
|
24
|
+
transaction<T>(fn: () => T): T;
|
|
25
|
+
};
|
|
26
|
+
export declare function createSyncSqliteCrud<TDb>(db: TDb, ex: SyncSqliteExecutor): SyncSqliteCrud<TDb>;
|
|
27
|
+
//# sourceMappingURL=sync-crud.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-crud.d.ts","sourceRoot":"","sources":["../../src/sqlite/sync-crud.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { CrudianError, assertString, } from "../index.js";
|
|
2
|
+
import { compileWhere, quoteIdent, resolveWhere } from "./sql.js";
|
|
3
|
+
function requireWhere(query, label) {
|
|
4
|
+
if (query.where === undefined || query.where === null) {
|
|
5
|
+
throw new CrudianError(`${label} requires where`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function selectColumns(columns) {
|
|
9
|
+
if (!columns || columns.length === 0)
|
|
10
|
+
return "*";
|
|
11
|
+
return columns.map((c) => quoteIdent(c)).join(", ");
|
|
12
|
+
}
|
|
13
|
+
function rowFromObject(value) {
|
|
14
|
+
if (value === null || typeof value !== "object") {
|
|
15
|
+
throw new CrudianError("expected row object");
|
|
16
|
+
}
|
|
17
|
+
return { ...value };
|
|
18
|
+
}
|
|
19
|
+
export function createSyncSqliteCrud(db, ex) {
|
|
20
|
+
const crud = {
|
|
21
|
+
db,
|
|
22
|
+
create(table, cols) {
|
|
23
|
+
assertString(table, "table");
|
|
24
|
+
if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
|
|
25
|
+
throw new CrudianError("cols must be an object");
|
|
26
|
+
}
|
|
27
|
+
const keys = Object.keys(cols);
|
|
28
|
+
if (keys.length === 0) {
|
|
29
|
+
throw new CrudianError("cols must not be empty");
|
|
30
|
+
}
|
|
31
|
+
const tbl = quoteIdent(table);
|
|
32
|
+
const colSql = keys.map((k) => quoteIdent(k)).join(", ");
|
|
33
|
+
const placeholders = keys.map(() => "?").join(", ");
|
|
34
|
+
const args = keys.map((k) => cols[k]);
|
|
35
|
+
ex.run(`INSERT INTO ${tbl} (${colSql}) VALUES (${placeholders})`, args);
|
|
36
|
+
const idRow = ex.get("SELECT last_insert_rowid() AS id");
|
|
37
|
+
const id = Number(idRow?.id);
|
|
38
|
+
const row = ex.get(`SELECT * FROM ${tbl} WHERE "id" = ?`, [id]);
|
|
39
|
+
return rowFromObject(row);
|
|
40
|
+
},
|
|
41
|
+
read(table, query = {}) {
|
|
42
|
+
assertString(table, "table");
|
|
43
|
+
const tbl = quoteIdent(table);
|
|
44
|
+
const where = compileWhere(resolveWhere(query.where));
|
|
45
|
+
const sql = `SELECT ${selectColumns(query.columns)} FROM ${tbl}` +
|
|
46
|
+
(where.sql ? ` WHERE ${where.sql}` : "") +
|
|
47
|
+
` LIMIT 1`;
|
|
48
|
+
const row = ex.get(sql, where.args);
|
|
49
|
+
return row == null ? null : rowFromObject(row);
|
|
50
|
+
},
|
|
51
|
+
update(table, cols, query) {
|
|
52
|
+
assertString(table, "table");
|
|
53
|
+
requireWhere(query, "update");
|
|
54
|
+
if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
|
|
55
|
+
throw new CrudianError("cols must be an object");
|
|
56
|
+
}
|
|
57
|
+
const keys = Object.keys(cols);
|
|
58
|
+
if (keys.length === 0) {
|
|
59
|
+
throw new CrudianError("cols must not be empty");
|
|
60
|
+
}
|
|
61
|
+
const tbl = quoteIdent(table);
|
|
62
|
+
const where = compileWhere(resolveWhere(query.where));
|
|
63
|
+
if (!where.sql) {
|
|
64
|
+
throw new CrudianError("update requires where");
|
|
65
|
+
}
|
|
66
|
+
const sets = keys.map((k) => `${quoteIdent(k)} = ?`).join(", ");
|
|
67
|
+
const args = [...keys.map((k) => cols[k]), ...where.args];
|
|
68
|
+
const result = ex.run(`UPDATE ${tbl} SET ${sets} WHERE ${where.sql}`, args);
|
|
69
|
+
if (result.changes === 0)
|
|
70
|
+
return null;
|
|
71
|
+
const row = ex.get(`SELECT * FROM ${tbl} WHERE ${where.sql} LIMIT 1`, where.args);
|
|
72
|
+
return row == null ? null : rowFromObject(row);
|
|
73
|
+
},
|
|
74
|
+
delete(table, query) {
|
|
75
|
+
assertString(table, "table");
|
|
76
|
+
requireWhere(query, "delete");
|
|
77
|
+
const tbl = quoteIdent(table);
|
|
78
|
+
const where = compileWhere(resolveWhere(query.where));
|
|
79
|
+
if (!where.sql) {
|
|
80
|
+
throw new CrudianError("delete requires where");
|
|
81
|
+
}
|
|
82
|
+
const result = ex.run(`DELETE FROM ${tbl} WHERE ${where.sql}`, where.args);
|
|
83
|
+
return Number(result.changes ?? 0);
|
|
84
|
+
},
|
|
85
|
+
search(table, query = {}) {
|
|
86
|
+
assertString(table, "table");
|
|
87
|
+
const limit = query.limit ?? 20;
|
|
88
|
+
if (typeof limit !== "number" || !Number.isFinite(limit) || limit <= 0) {
|
|
89
|
+
throw new CrudianError("limit must be a positive number");
|
|
90
|
+
}
|
|
91
|
+
if (query.cursor != null &&
|
|
92
|
+
typeof query.cursor !== "number" &&
|
|
93
|
+
typeof query.cursor !== "string") {
|
|
94
|
+
throw new CrudianError("cursor must be a number, string, or null");
|
|
95
|
+
}
|
|
96
|
+
const tbl = quoteIdent(table);
|
|
97
|
+
const where = compileWhere(resolveWhere(query.where));
|
|
98
|
+
const args = [...where.args];
|
|
99
|
+
const parts = [];
|
|
100
|
+
if (where.sql)
|
|
101
|
+
parts.push(`(${where.sql})`);
|
|
102
|
+
if (query.cursor != null) {
|
|
103
|
+
parts.push(`${quoteIdent("id")} > ?`);
|
|
104
|
+
args.push(query.cursor);
|
|
105
|
+
}
|
|
106
|
+
const whereSql = parts.length > 0 ? ` WHERE ${parts.join(" AND ")}` : "";
|
|
107
|
+
const sql = `SELECT ${selectColumns(query.columns)} FROM ${tbl}` +
|
|
108
|
+
whereSql +
|
|
109
|
+
` ORDER BY ${quoteIdent("id")} ASC LIMIT ?`;
|
|
110
|
+
args.push(limit + 1);
|
|
111
|
+
const rows = ex.all(sql, args).map((r) => rowFromObject(r));
|
|
112
|
+
const hasMore = rows.length > limit;
|
|
113
|
+
const items = hasMore ? rows.slice(0, limit) : rows;
|
|
114
|
+
const last = items[items.length - 1];
|
|
115
|
+
const nextCursor = hasMore && last != null && (typeof last.id === "number" || typeof last.id === "string")
|
|
116
|
+
? last.id
|
|
117
|
+
: null;
|
|
118
|
+
return { items, nextCursor, hasMore };
|
|
119
|
+
},
|
|
120
|
+
list(table, query) {
|
|
121
|
+
return crud.search(table, query);
|
|
122
|
+
},
|
|
123
|
+
upsert(table, cols) {
|
|
124
|
+
assertString(table, "table");
|
|
125
|
+
if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
|
|
126
|
+
throw new CrudianError("cols must be an object");
|
|
127
|
+
}
|
|
128
|
+
const keys = Object.keys(cols);
|
|
129
|
+
if (keys.length === 0) {
|
|
130
|
+
throw new CrudianError("cols must not be empty");
|
|
131
|
+
}
|
|
132
|
+
if (!Object.prototype.hasOwnProperty.call(cols, "id")) {
|
|
133
|
+
throw new CrudianError("upsert requires cols.id");
|
|
134
|
+
}
|
|
135
|
+
const id = cols.id;
|
|
136
|
+
const existing = crud.read(table, {
|
|
137
|
+
where: { type: "cond", op: "eq", column: "id", value: id },
|
|
138
|
+
});
|
|
139
|
+
if (existing != null) {
|
|
140
|
+
const { id: _id, ...patch } = cols;
|
|
141
|
+
if (Object.keys(patch).length === 0)
|
|
142
|
+
return existing;
|
|
143
|
+
const updated = crud.update(table, patch, {
|
|
144
|
+
where: { type: "cond", op: "eq", column: "id", value: id },
|
|
145
|
+
});
|
|
146
|
+
if (updated == null) {
|
|
147
|
+
throw new CrudianError("upsert update failed");
|
|
148
|
+
}
|
|
149
|
+
return updated;
|
|
150
|
+
}
|
|
151
|
+
return crud.create(table, cols);
|
|
152
|
+
},
|
|
153
|
+
duplicate(table, query) {
|
|
154
|
+
assertString(table, "table");
|
|
155
|
+
requireWhere(query, "duplicate");
|
|
156
|
+
const source = crud.read(table, { where: query.where });
|
|
157
|
+
if (source == null)
|
|
158
|
+
return null;
|
|
159
|
+
const { id: _id, ...rest } = source;
|
|
160
|
+
const overrides = query.overrides != null &&
|
|
161
|
+
typeof query.overrides === "object" &&
|
|
162
|
+
!Array.isArray(query.overrides)
|
|
163
|
+
? query.overrides
|
|
164
|
+
: {};
|
|
165
|
+
const cols = { ...rest, ...overrides };
|
|
166
|
+
delete cols.id;
|
|
167
|
+
return crud.create(table, cols);
|
|
168
|
+
},
|
|
169
|
+
bulkCreate(table, rows) {
|
|
170
|
+
assertString(table, "table");
|
|
171
|
+
if (!Array.isArray(rows)) {
|
|
172
|
+
throw new CrudianError("rows must be an array");
|
|
173
|
+
}
|
|
174
|
+
if (rows.length === 0)
|
|
175
|
+
return 0;
|
|
176
|
+
let count = 0;
|
|
177
|
+
for (const row of rows) {
|
|
178
|
+
if (row == null || typeof row !== "object" || Array.isArray(row)) {
|
|
179
|
+
throw new CrudianError("each row must be an object");
|
|
180
|
+
}
|
|
181
|
+
crud.create(table, row);
|
|
182
|
+
count += 1;
|
|
183
|
+
}
|
|
184
|
+
return count;
|
|
185
|
+
},
|
|
186
|
+
bulkUpdate(table, cols, query) {
|
|
187
|
+
assertString(table, "table");
|
|
188
|
+
requireWhere(query, "bulkUpdate");
|
|
189
|
+
if (cols == null || typeof cols !== "object" || Array.isArray(cols)) {
|
|
190
|
+
throw new CrudianError("cols must be an object");
|
|
191
|
+
}
|
|
192
|
+
const keys = Object.keys(cols);
|
|
193
|
+
if (keys.length === 0) {
|
|
194
|
+
throw new CrudianError("cols must not be empty");
|
|
195
|
+
}
|
|
196
|
+
const tbl = quoteIdent(table);
|
|
197
|
+
const where = compileWhere(resolveWhere(query.where));
|
|
198
|
+
if (!where.sql) {
|
|
199
|
+
throw new CrudianError("bulkUpdate requires where");
|
|
200
|
+
}
|
|
201
|
+
const sets = keys.map((k) => `${quoteIdent(k)} = ?`).join(", ");
|
|
202
|
+
const args = [...keys.map((k) => cols[k]), ...where.args];
|
|
203
|
+
const result = ex.run(`UPDATE ${tbl} SET ${sets} WHERE ${where.sql}`, args);
|
|
204
|
+
return Number(result.changes ?? 0);
|
|
205
|
+
},
|
|
206
|
+
bulkDelete(table, query) {
|
|
207
|
+
return crud.delete(table, query);
|
|
208
|
+
},
|
|
209
|
+
bulkUpsert(table, rows) {
|
|
210
|
+
assertString(table, "table");
|
|
211
|
+
if (!Array.isArray(rows)) {
|
|
212
|
+
throw new CrudianError("rows must be an array");
|
|
213
|
+
}
|
|
214
|
+
if (rows.length === 0)
|
|
215
|
+
return 0;
|
|
216
|
+
let count = 0;
|
|
217
|
+
for (const row of rows) {
|
|
218
|
+
if (row == null || typeof row !== "object" || Array.isArray(row)) {
|
|
219
|
+
throw new CrudianError("each row must be an object");
|
|
220
|
+
}
|
|
221
|
+
if (!Object.prototype.hasOwnProperty.call(row, "id")) {
|
|
222
|
+
throw new CrudianError("bulkUpsert requires each row to have id");
|
|
223
|
+
}
|
|
224
|
+
crud.upsert(table, row);
|
|
225
|
+
count += 1;
|
|
226
|
+
}
|
|
227
|
+
return count;
|
|
228
|
+
},
|
|
229
|
+
transaction(fn) {
|
|
230
|
+
if (typeof fn !== "function") {
|
|
231
|
+
throw new CrudianError("transaction callback must be a function");
|
|
232
|
+
}
|
|
233
|
+
return ex.transaction(fn);
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
return crud;
|
|
237
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { WhereBuilder, WhereNode } from "./where.js";
|
|
2
|
+
export type Row = Record<string, unknown>;
|
|
3
|
+
export type SearchResult<T = Row> = {
|
|
4
|
+
items: T[];
|
|
5
|
+
nextCursor: number | string | null;
|
|
6
|
+
hasMore: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type WhereInput = WhereBuilder | WhereNode;
|
|
9
|
+
export type ReadQuery = {
|
|
10
|
+
columns?: string[];
|
|
11
|
+
where?: WhereInput;
|
|
12
|
+
};
|
|
13
|
+
export type SearchQuery = {
|
|
14
|
+
columns?: string[];
|
|
15
|
+
where?: WhereInput;
|
|
16
|
+
limit?: number;
|
|
17
|
+
/** Raw `id` cursor (keyset). */
|
|
18
|
+
cursor?: number | string | null;
|
|
19
|
+
};
|
|
20
|
+
export type DeleteQuery = {
|
|
21
|
+
where: WhereInput;
|
|
22
|
+
};
|
|
23
|
+
export type UpdateQuery = {
|
|
24
|
+
where: WhereInput;
|
|
25
|
+
};
|
|
26
|
+
export type DuplicateQuery = {
|
|
27
|
+
where: WhereInput;
|
|
28
|
+
overrides?: Record<string, unknown>;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAEzD,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEzC,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,GAAG,IAAI;IAClC,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAClC,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,SAAS,CAAA;AAEjD,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAChC,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,UAAU,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,UAAU,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC,CAAA"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/where.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type Op = "eq" | "ne" | "lt" | "gt" | "lte" | "gte" | "in" | "like" | "isNull" | "isNotNull";
|
|
2
|
+
export type CondNode = {
|
|
3
|
+
type: "cond";
|
|
4
|
+
op: Op;
|
|
5
|
+
column: string;
|
|
6
|
+
value?: unknown;
|
|
7
|
+
};
|
|
8
|
+
export type GroupNode = {
|
|
9
|
+
type: "and" | "or";
|
|
10
|
+
children: WhereNode[];
|
|
11
|
+
};
|
|
12
|
+
export type WhereNode = CondNode | GroupNode;
|
|
13
|
+
/** Public where builder. Tree shape is an internal representation via `toNode()`. */
|
|
14
|
+
export declare class WhereBuilder {
|
|
15
|
+
private readonly node;
|
|
16
|
+
private constructor();
|
|
17
|
+
static create(): WhereBuilder;
|
|
18
|
+
static from(node: WhereNode): WhereBuilder;
|
|
19
|
+
private appendCond;
|
|
20
|
+
eq(column: string, value: unknown): WhereBuilder;
|
|
21
|
+
ne(column: string, value: unknown): WhereBuilder;
|
|
22
|
+
lt(column: string, value: unknown): WhereBuilder;
|
|
23
|
+
gt(column: string, value: unknown): WhereBuilder;
|
|
24
|
+
lte(column: string, value: unknown): WhereBuilder;
|
|
25
|
+
gte(column: string, value: unknown): WhereBuilder;
|
|
26
|
+
in(column: string, value: unknown[]): WhereBuilder;
|
|
27
|
+
like(column: string, value: unknown): WhereBuilder;
|
|
28
|
+
isNull(column: string): WhereBuilder;
|
|
29
|
+
isNotNull(column: string): WhereBuilder;
|
|
30
|
+
and(...others: WhereBuilder[]): WhereBuilder;
|
|
31
|
+
or(...others: WhereBuilder[]): WhereBuilder;
|
|
32
|
+
toNode(): WhereNode;
|
|
33
|
+
}
|
|
34
|
+
export declare function where(): WhereBuilder;
|
|
35
|
+
export declare function isWhereBuilder(value: unknown): value is WhereBuilder;
|
|
36
|
+
//# sourceMappingURL=where.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"where.d.ts","sourceRoot":"","sources":["../src/where.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,EAAE,GACV,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,KAAK,GACL,KAAK,GACL,IAAI,GACJ,MAAM,GACN,QAAQ,GACR,WAAW,CAAA;AAEf,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,EAAE,CAAA;IACN,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,KAAK,GAAG,IAAI,CAAA;IAClB,QAAQ,EAAE,SAAS,EAAE,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;AAO5C,qFAAqF;AACrF,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAW;IAEhC,OAAO;IAIP,MAAM,CAAC,MAAM,IAAI,YAAY;IAI7B,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,YAAY;IAI1C,OAAO,CAAC,UAAU;IAQlB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY;IAIhD,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY;IAIhD,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY;IAIhD,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY;IAIhD,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY;IAIjD,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY;IAIjD,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,YAAY;IAOlD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY;IAIlD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY;IAIpC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY;IAIvC,GAAG,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY;IAK5C,EAAE,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY;IAK3C,MAAM,IAAI,SAAS;CAGpB;AAED,wBAAgB,KAAK,IAAI,YAAY,CAEpC;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAEpE"}
|