cl-orm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +12 -0
- package/.gitattributes +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc +34 -0
- package/.vscode/settings.json +14 -0
- package/README.md +105 -0
- package/mcr.config.ts +12 -0
- package/package.json +30 -0
- package/src/drivers/_utils.ts +31 -0
- package/src/drivers/d1.ts +70 -0
- package/src/drivers/do.ts +81 -0
- package/src/index.ts +13 -0
- package/src/queries/_utils.ts +10 -0
- package/src/queries/delete.ts +26 -0
- package/src/queries/insert.ts +22 -0
- package/src/queries/select.ts +64 -0
- package/src/queries/update.ts +28 -0
- package/src/queries/where/operators.ts +96 -0
- package/src/queries/where/where.ts +50 -0
- package/src/types.ts +76 -0
- package/test/__fixtures__/d1/worker.ts +70 -0
- package/test/__fixtures__/d1/wrangler.jsonc +13 -0
- package/test/__fixtures__/do/worker.ts +106 -0
- package/test/__fixtures__/do/wrangler.jsonc +20 -0
- package/test/e2e/d1.test.ts +113 -0
- package/test/e2e/do.test.ts +116 -0
- package/test/unit/buildDelete.test.ts +36 -0
- package/test/unit/buildInsert.test.ts +36 -0
- package/test/unit/buildSelect.test.ts +137 -0
- package/test/unit/buildUpdate.test.ts +34 -0
- package/test/unit/buildWhere.test.ts +249 -0
- package/test/unit/operators.test.ts +98 -0
- package/test/unit/prepare.test.ts +22 -0
- package/test/unit/quoteIdentifier.test.ts +12 -0
- package/test/unit/returnsRows.test.ts +41 -0
- package/test/unit/setMeta.test.ts +31 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +34 -0
- package/website/.prettierignore +4 -0
- package/website/.prettierrc +34 -0
- package/website/README.md +3 -0
- package/website/docs/documentation/connection.mdx +123 -0
- package/website/docs/documentation/queries/_category_.json +7 -0
- package/website/docs/documentation/queries/delete.mdx +51 -0
- package/website/docs/documentation/queries/insert.mdx +63 -0
- package/website/docs/documentation/queries/operators/_category_.json +7 -0
- package/website/docs/documentation/queries/operators/between.mdx +17 -0
- package/website/docs/documentation/queries/operators/eq.mdx +22 -0
- package/website/docs/documentation/queries/operators/gt.mdx +22 -0
- package/website/docs/documentation/queries/operators/gte.mdx +22 -0
- package/website/docs/documentation/queries/operators/in.mdx +28 -0
- package/website/docs/documentation/queries/operators/is-not-null.mdx +22 -0
- package/website/docs/documentation/queries/operators/is-null.mdx +22 -0
- package/website/docs/documentation/queries/operators/like.mdx +27 -0
- package/website/docs/documentation/queries/operators/lt.mdx +22 -0
- package/website/docs/documentation/queries/operators/lte.mdx +22 -0
- package/website/docs/documentation/queries/operators/ne.mdx +22 -0
- package/website/docs/documentation/queries/operators/not-between.mdx +17 -0
- package/website/docs/documentation/queries/operators/not-like.mdx +27 -0
- package/website/docs/documentation/queries/operators/notIn.mdx +28 -0
- package/website/docs/documentation/queries/select.mdx +294 -0
- package/website/docs/documentation/queries/update.mdx +61 -0
- package/website/docs/documentation/queries/where.mdx +176 -0
- package/website/docs/index.mdx +228 -0
- package/website/docusaurus.config.ts +82 -0
- package/website/package-lock.json +21348 -0
- package/website/package.json +59 -0
- package/website/plugins/locale.ts +16 -0
- package/website/sidebars.ts +18 -0
- package/website/src/components/FAQ.tsx +35 -0
- package/website/src/components/Loading.tsx +4 -0
- package/website/src/components/PageTitle.tsx +29 -0
- package/website/src/css/_faq.scss +39 -0
- package/website/src/css/_loading.scss +43 -0
- package/website/src/css/_mixins.scss +19 -0
- package/website/src/css/custom.scss +149 -0
- package/website/src/pages/index.tsx +17 -0
- package/website/static/.nojekyll +0 -0
- package/website/static/img/favicon.svg +1 -0
- package/website/test/unit/check-extensions.test.ts +48 -0
- package/website/tsconfig.json +14 -0
package/.editorconfig
ADDED
package/.gitattributes
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
website/** linguist-documentation
|
package/.prettierignore
ADDED
package/.prettierrc
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"printWidth": 80,
|
|
3
|
+
"tabWidth": 2,
|
|
4
|
+
"useTabs": false,
|
|
5
|
+
"semi": true,
|
|
6
|
+
"singleQuote": true,
|
|
7
|
+
"quoteProps": "as-needed",
|
|
8
|
+
"jsxSingleQuote": true,
|
|
9
|
+
"trailingComma": "es5",
|
|
10
|
+
"bracketSpacing": true,
|
|
11
|
+
"bracketSameLine": false,
|
|
12
|
+
"arrowParens": "always",
|
|
13
|
+
"proseWrap": "preserve",
|
|
14
|
+
"endOfLine": "auto",
|
|
15
|
+
"embeddedLanguageFormatting": "auto",
|
|
16
|
+
"singleAttributePerLine": false,
|
|
17
|
+
"plugins": ["@ianvs/prettier-plugin-sort-imports"],
|
|
18
|
+
"importOrder": [
|
|
19
|
+
"<TYPES>^(node:)",
|
|
20
|
+
"<TYPES>",
|
|
21
|
+
"<TYPES>^[.]",
|
|
22
|
+
"<BUILTIN_MODULES>",
|
|
23
|
+
"<THIRD_PARTY_MODULES>",
|
|
24
|
+
"^[.]"
|
|
25
|
+
],
|
|
26
|
+
"overrides": [
|
|
27
|
+
{
|
|
28
|
+
"files": "*.jsonc",
|
|
29
|
+
"options": {
|
|
30
|
+
"trailingComma": "none"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"editor.trimAutoWhitespace": true,
|
|
3
|
+
"editor.indentSize": 2,
|
|
4
|
+
"editor.tabSize": 2,
|
|
5
|
+
"editor.formatOnSave": true,
|
|
6
|
+
"files.trimTrailingWhitespace": true,
|
|
7
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
8
|
+
"editor.codeActionsOnSave": {
|
|
9
|
+
"source.fixAll": "explicit"
|
|
10
|
+
},
|
|
11
|
+
"[markdown]": {
|
|
12
|
+
"files.trimTrailingWhitespace": false
|
|
13
|
+
}
|
|
14
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# CL ORM
|
|
2
|
+
|
|
3
|
+
<img align="right" width="64" height="64" alt="Logo" src="website/static/img/favicon.svg">
|
|
4
|
+
|
|
5
|
+
A lightweight **ORM** for **Cloudflare Workers** (**D1** and **Durable Objects**), designed to be intuitive, productive and focused on essential functionality.
|
|
6
|
+
|
|
7
|
+
- This project supports **Cloudflare D1** and **Durable Objects SQL Storage** as database drivers.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
📘 [**Documentation**](https://wellwelwel.github.io/cl-orm/docs/)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
- Supports both **Cloudflare D1** and **Durable Objects** SQL Storage.
|
|
18
|
+
- Unified **Connection** interface across different database drivers.
|
|
19
|
+
- An user-friendly ORM for **INSERT**, **SELECT**, **UPDATE**, **DELETE** and **WHERE** clauses.
|
|
20
|
+
- Automatic **Prepared Statements** (including **LIMIT** and **OFFSET**).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Documentation
|
|
25
|
+
|
|
26
|
+
See detailed specifications and usage in [**Documentation**](https://wellwelwel.github.io/cl-orm/docs/category/documentation) section for queries, advanced concepts and much more.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
### Installation
|
|
33
|
+
|
|
34
|
+
```shell
|
|
35
|
+
npm i cl-orm
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### Connect
|
|
41
|
+
|
|
42
|
+
#### D1
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { useD1 } from 'cl-orm';
|
|
46
|
+
|
|
47
|
+
export default {
|
|
48
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
49
|
+
const db = useD1(env.DB);
|
|
50
|
+
|
|
51
|
+
await db.query('SELECT 1');
|
|
52
|
+
// ...
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Durable Objects
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { useDO } from 'cl-orm';
|
|
61
|
+
import { DurableObject } from 'cloudflare:workers';
|
|
62
|
+
|
|
63
|
+
export class MyDurableObject extends DurableObject {
|
|
64
|
+
async fetch(request: Request): Promise<Response> {
|
|
65
|
+
const db = useDO(this.ctx.storage.sql);
|
|
66
|
+
|
|
67
|
+
await db.query('SELECT 1');
|
|
68
|
+
// ...
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### Basic Usage Example
|
|
76
|
+
|
|
77
|
+
The following example is based on **TypeScript** and **ES Modules**.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import type { Connection } from 'cl-orm';
|
|
81
|
+
import { OP } from 'cl-orm';
|
|
82
|
+
|
|
83
|
+
export const getUser = async (db: Connection, id: number) => {
|
|
84
|
+
const user = await db.select({
|
|
85
|
+
table: 'users',
|
|
86
|
+
where: OP.eq('id', id),
|
|
87
|
+
limit: 1,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return user;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Usage: await getUser(db, 15);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- See all available operators (**OP**) [here](https://wellwelwel.github.io/cl-orm/docs/category/operators).
|
|
97
|
+
- Due to `limit: 1`, it returns a direct object with the row result or `null`.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Acknowledgements
|
|
102
|
+
|
|
103
|
+
- The operator names **eq**, **ne**, **gt**, **lt**, **gte** and **lte** are inspired by [**Sequelize**](https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#operators).
|
|
104
|
+
- [**Contributors**](https://github.com/wellwelwel/cl-orm/graphs/contributors).
|
|
105
|
+
- This project is adapted from [**MySQL2 ORM**](https://github.com/wellwelwel/mysql2-orm).
|
package/mcr.config.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CoverageReportOptions } from 'monocart-coverage-reports';
|
|
2
|
+
|
|
3
|
+
const coverageOptions: CoverageReportOptions = {
|
|
4
|
+
reports: ['v8', 'console-details', 'codecov'],
|
|
5
|
+
entryFilter: {
|
|
6
|
+
'**/node_modules/**': false,
|
|
7
|
+
'**/test/**': false,
|
|
8
|
+
'**/src/**': true,
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default coverageOptions;
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cl-orm",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lightweight ORM for Cloudflare Workers (D1 and Durable Objects), designed to be intuitive, productive and focused on essential functionality",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev:d1": "wrangler dev --config test/__fixtures__/d1/wrangler.jsonc --port 8787 --inspector-port 9229",
|
|
7
|
+
"dev:do": "wrangler dev --config test/__fixtures__/do/wrangler.jsonc --port 8788 --inspector-port 9230",
|
|
8
|
+
"prebuild": "rm -rf lib",
|
|
9
|
+
"build": "tsc -p tsconfig.build.json",
|
|
10
|
+
"lint": "prettier --check .",
|
|
11
|
+
"lint:fix": "prettier --write .",
|
|
12
|
+
"test": "poku test",
|
|
13
|
+
"test:coverage": "mcr --import tsx --config mcr.config.ts npm run test",
|
|
14
|
+
"typecheck": "tsc --noEmit",
|
|
15
|
+
"update": "pu && npm i && npm update && (npm audit fix || true)",
|
|
16
|
+
"postupdate": "npm run lint:fix"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@cloudflare/workers-types": "^4.20260130.0",
|
|
20
|
+
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
|
21
|
+
"@types/node": "^25.1.0",
|
|
22
|
+
"monocart-coverage-reports": "^2.12.9",
|
|
23
|
+
"packages-update": "^2.0.0",
|
|
24
|
+
"poku": "^3.0.3-canary.13a996a9",
|
|
25
|
+
"prettier": "^3.8.1",
|
|
26
|
+
"tsx": "^4.21.0",
|
|
27
|
+
"typescript": "^5.9.3",
|
|
28
|
+
"wrangler": "^4.61.1"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Meta } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export const returnsRows = (sql: string): boolean => {
|
|
4
|
+
const trimmed = sql.trimStart().toUpperCase();
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
trimmed.indexOf('SELECT') === 0 ||
|
|
8
|
+
trimmed.indexOf('WITH') === 0 ||
|
|
9
|
+
trimmed.indexOf('PRAGMA') === 0 ||
|
|
10
|
+
trimmed.indexOf('RETURNING') !== -1
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const prepare = (
|
|
15
|
+
db: D1Database,
|
|
16
|
+
sql: string,
|
|
17
|
+
params: unknown[]
|
|
18
|
+
): D1PreparedStatement =>
|
|
19
|
+
params.length > 0 ? db.prepare(sql).bind(...params) : db.prepare(sql);
|
|
20
|
+
|
|
21
|
+
export const setMeta = (
|
|
22
|
+
cursor: SqlStorageCursor<Record<string, SqlStorageValue>>
|
|
23
|
+
): Meta => ({
|
|
24
|
+
duration: 0,
|
|
25
|
+
size_after: 0,
|
|
26
|
+
rows_read: cursor.rowsRead,
|
|
27
|
+
rows_written: cursor.rowsWritten,
|
|
28
|
+
last_row_id: 0,
|
|
29
|
+
changed_db: cursor.rowsWritten > 0,
|
|
30
|
+
changes: cursor.rowsWritten,
|
|
31
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Connection,
|
|
3
|
+
DeleteOptions,
|
|
4
|
+
InsertOptions,
|
|
5
|
+
Meta,
|
|
6
|
+
QueryResult,
|
|
7
|
+
SelectOptions,
|
|
8
|
+
UpdateOptions,
|
|
9
|
+
} from '../types.js';
|
|
10
|
+
import { buildDelete } from '../queries/delete.js';
|
|
11
|
+
import { buildInsert } from '../queries/insert.js';
|
|
12
|
+
import { buildSelect } from '../queries/select.js';
|
|
13
|
+
import { buildUpdate } from '../queries/update.js';
|
|
14
|
+
import { prepare, returnsRows } from './_utils.js';
|
|
15
|
+
|
|
16
|
+
export const useD1 = (db: D1Database): Connection => {
|
|
17
|
+
const query = async <T = Record<string, unknown>>(
|
|
18
|
+
sql: string,
|
|
19
|
+
values?: unknown[]
|
|
20
|
+
): Promise<QueryResult<T>> => {
|
|
21
|
+
const statement = values
|
|
22
|
+
? db.prepare(sql).bind(...values)
|
|
23
|
+
: db.prepare(sql);
|
|
24
|
+
|
|
25
|
+
if (returnsRows(sql)) {
|
|
26
|
+
const { results, meta } = await statement.all<T>();
|
|
27
|
+
return { rows: results, meta: meta as Meta };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { meta } = await statement.run();
|
|
31
|
+
return { rows: [], meta: meta as Meta };
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const select = async <T = Record<string, unknown>>(
|
|
35
|
+
options: SelectOptions
|
|
36
|
+
): Promise<T | null | T[]> => {
|
|
37
|
+
const { sql, params } = buildSelect(options);
|
|
38
|
+
const statement = prepare(db, sql, params);
|
|
39
|
+
|
|
40
|
+
if (options.limit === 1) return statement.first<T>();
|
|
41
|
+
|
|
42
|
+
const { results } = await statement.all<T>();
|
|
43
|
+
return results;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const insert = async (options: InsertOptions): Promise<Meta> => {
|
|
47
|
+
const { sql, params } = buildInsert(options);
|
|
48
|
+
const { meta } = await query(sql, params);
|
|
49
|
+
|
|
50
|
+
return meta;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const update = async (options: UpdateOptions): Promise<Meta> => {
|
|
54
|
+
const { sql, params } = buildUpdate(options);
|
|
55
|
+
const statement = prepare(db, sql, params);
|
|
56
|
+
const { meta } = await statement.run();
|
|
57
|
+
|
|
58
|
+
return meta as Meta;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const del = async (options: DeleteOptions): Promise<Meta> => {
|
|
62
|
+
const { sql, params } = buildDelete(options);
|
|
63
|
+
const statement = prepare(db, sql, params);
|
|
64
|
+
const { meta } = await statement.run();
|
|
65
|
+
|
|
66
|
+
return meta as Meta;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return { query, select, insert, update, delete: del };
|
|
70
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Connection,
|
|
3
|
+
DeleteOptions,
|
|
4
|
+
InsertOptions,
|
|
5
|
+
Meta,
|
|
6
|
+
QueryResult,
|
|
7
|
+
SelectOptions,
|
|
8
|
+
UpdateOptions,
|
|
9
|
+
} from '../types.js';
|
|
10
|
+
import { buildDelete } from '../queries/delete.js';
|
|
11
|
+
import { buildInsert } from '../queries/insert.js';
|
|
12
|
+
import { buildSelect } from '../queries/select.js';
|
|
13
|
+
import { buildUpdate } from '../queries/update.js';
|
|
14
|
+
import { returnsRows, setMeta } from './_utils.js';
|
|
15
|
+
|
|
16
|
+
export const useDO = (sql: SqlStorage): Connection => {
|
|
17
|
+
const exec = <T extends Record<string, SqlStorageValue>>(
|
|
18
|
+
query: string,
|
|
19
|
+
values?: unknown[]
|
|
20
|
+
) => (values ? sql.exec<T>(query, ...values) : sql.exec<T>(query));
|
|
21
|
+
|
|
22
|
+
const query = async <T = Record<string, unknown>>(
|
|
23
|
+
queryStr: string,
|
|
24
|
+
values?: unknown[]
|
|
25
|
+
): Promise<QueryResult<T>> => {
|
|
26
|
+
const cursor = exec(queryStr, values);
|
|
27
|
+
|
|
28
|
+
if (returnsRows(queryStr)) {
|
|
29
|
+
const rows = cursor.toArray() as T[];
|
|
30
|
+
return { rows, meta: setMeta(cursor) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { rows: [], meta: setMeta(cursor) };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const select = async <T = Record<string, unknown>>(
|
|
37
|
+
options: SelectOptions
|
|
38
|
+
): Promise<T | null | T[]> => {
|
|
39
|
+
const built = buildSelect(options);
|
|
40
|
+
const cursor = exec(
|
|
41
|
+
built.sql,
|
|
42
|
+
built.params.length > 0 ? built.params : undefined
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (options.limit === 1) {
|
|
46
|
+
const rows = cursor.toArray() as T[];
|
|
47
|
+
return rows[0] ?? null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return cursor.toArray() as T[];
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const insert = async (options: InsertOptions): Promise<Meta> => {
|
|
54
|
+
const built = buildInsert(options);
|
|
55
|
+
const cursor = exec(built.sql, built.params);
|
|
56
|
+
|
|
57
|
+
return setMeta(cursor);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const update = async (options: UpdateOptions): Promise<Meta> => {
|
|
61
|
+
const built = buildUpdate(options);
|
|
62
|
+
const cursor = exec(
|
|
63
|
+
built.sql,
|
|
64
|
+
built.params.length > 0 ? built.params : undefined
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return setMeta(cursor);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const del = async (options: DeleteOptions): Promise<Meta> => {
|
|
71
|
+
const built = buildDelete(options);
|
|
72
|
+
const cursor = exec(
|
|
73
|
+
built.sql,
|
|
74
|
+
built.params.length > 0 ? built.params : undefined
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return setMeta(cursor);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return { query, select, insert, update, delete: del };
|
|
81
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { useD1 } from './drivers/d1.js';
|
|
2
|
+
export { useDO } from './drivers/do.js';
|
|
3
|
+
export { OP } from './queries/where/operators.js';
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
QueryResult,
|
|
7
|
+
Connection,
|
|
8
|
+
Meta,
|
|
9
|
+
Condition,
|
|
10
|
+
Param,
|
|
11
|
+
WhereClause,
|
|
12
|
+
WhereItem,
|
|
13
|
+
} from './types.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DeleteOptions } from '../types.js';
|
|
2
|
+
import { quoteIdentifier } from './_utils.js';
|
|
3
|
+
import { buildWhere } from './where/where.js';
|
|
4
|
+
|
|
5
|
+
export const buildDelete = (
|
|
6
|
+
options: DeleteOptions
|
|
7
|
+
): { sql: string; params: unknown[] } => {
|
|
8
|
+
const parts: string[] = ['DELETE FROM', quoteIdentifier(options.table)];
|
|
9
|
+
const params: unknown[] = [];
|
|
10
|
+
|
|
11
|
+
if (options.where) {
|
|
12
|
+
const where = buildWhere(options.where);
|
|
13
|
+
|
|
14
|
+
parts.push('WHERE', where.sql);
|
|
15
|
+
params.push(...where.params);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (options.limit !== undefined) {
|
|
19
|
+
parts.push('LIMIT ?');
|
|
20
|
+
params.push(options.limit);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (options.params) params.unshift(...options.params);
|
|
24
|
+
|
|
25
|
+
return { sql: parts.join(' '), params };
|
|
26
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { InsertOptions } from '../types.js';
|
|
2
|
+
import { quoteIdentifier } from './_utils.js';
|
|
3
|
+
|
|
4
|
+
export const buildInsert = (
|
|
5
|
+
options: InsertOptions
|
|
6
|
+
): { sql: string; params: unknown[] } => {
|
|
7
|
+
const rows = Array.isArray(options.values)
|
|
8
|
+
? options.values
|
|
9
|
+
: [options.values];
|
|
10
|
+
const columns = Object.keys(rows[0]);
|
|
11
|
+
|
|
12
|
+
const columnsSql = columns.map(quoteIdentifier).join(', ');
|
|
13
|
+
const placeholders = `(${columns.map(() => '?').join(', ')})`;
|
|
14
|
+
const valuesSql = rows.map(() => placeholders).join(', ');
|
|
15
|
+
|
|
16
|
+
const params = rows.flatMap((row) => columns.map((col) => row[col]));
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
sql: `INSERT INTO ${quoteIdentifier(options.table)} (${columnsSql}) VALUES ${valuesSql}`,
|
|
20
|
+
params,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { JoinOptions, SelectOptions } from '../types.js';
|
|
2
|
+
import { quoteIdentifier } from './_utils.js';
|
|
3
|
+
import { buildWhere } from './where/where.js';
|
|
4
|
+
|
|
5
|
+
const buildColumns = (columns?: string | string[]): string => {
|
|
6
|
+
if (!columns) return '*';
|
|
7
|
+
if (typeof columns === 'string') return columns;
|
|
8
|
+
|
|
9
|
+
return columns.map(quoteIdentifier).join(', ');
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const buildJoin = (join: JoinOptions): string => {
|
|
13
|
+
const type = join.outer
|
|
14
|
+
? `${join.type.toUpperCase()} OUTER`
|
|
15
|
+
: join.type.toUpperCase();
|
|
16
|
+
|
|
17
|
+
return `${type} JOIN ${quoteIdentifier(join.table)} ON ${quoteIdentifier(join.on.a)} = ${quoteIdentifier(join.on.b)}`;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const buildSelect = (
|
|
21
|
+
options: SelectOptions
|
|
22
|
+
): { sql: string; params: unknown[] } => {
|
|
23
|
+
const parts: string[] = ['SELECT'];
|
|
24
|
+
const params: unknown[] = [];
|
|
25
|
+
|
|
26
|
+
if (options.distinct) parts.push('DISTINCT');
|
|
27
|
+
|
|
28
|
+
parts.push(buildColumns(options.columns));
|
|
29
|
+
parts.push('FROM', quoteIdentifier(options.table));
|
|
30
|
+
|
|
31
|
+
if (options.join) {
|
|
32
|
+
const joins = Array.isArray(options.join) ? options.join : [options.join];
|
|
33
|
+
for (const join of joins) {
|
|
34
|
+
parts.push(buildJoin(join));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (options.where) {
|
|
39
|
+
const where = buildWhere(options.where);
|
|
40
|
+
parts.push('WHERE', where.sql);
|
|
41
|
+
params.push(...where.params);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (options.groupBy) parts.push('GROUP BY', quoteIdentifier(options.groupBy));
|
|
45
|
+
|
|
46
|
+
if (options.orderBy) {
|
|
47
|
+
const [column, direction = 'ASC'] = options.orderBy;
|
|
48
|
+
parts.push('ORDER BY', quoteIdentifier(column), direction);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (options.limit !== undefined) {
|
|
52
|
+
parts.push('LIMIT ?');
|
|
53
|
+
params.push(options.limit);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (options.offset !== undefined) {
|
|
57
|
+
parts.push('OFFSET ?');
|
|
58
|
+
params.push(options.offset);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (options.params) params.unshift(...options.params);
|
|
62
|
+
|
|
63
|
+
return { sql: parts.join(' '), params };
|
|
64
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { UpdateOptions } from '../types.js';
|
|
2
|
+
import { quoteIdentifier } from './_utils.js';
|
|
3
|
+
import { buildWhere } from './where/where.js';
|
|
4
|
+
|
|
5
|
+
export const buildUpdate = (
|
|
6
|
+
options: UpdateOptions
|
|
7
|
+
): { sql: string; params: unknown[] } => {
|
|
8
|
+
const columns = Object.keys(options.set);
|
|
9
|
+
const setSql = columns.map((col) => `${quoteIdentifier(col)} = ?`).join(', ');
|
|
10
|
+
const params: unknown[] = columns.map((col) => options.set[col]);
|
|
11
|
+
|
|
12
|
+
const parts: string[] = [
|
|
13
|
+
'UPDATE',
|
|
14
|
+
quoteIdentifier(options.table),
|
|
15
|
+
'SET',
|
|
16
|
+
setSql,
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
if (options.where) {
|
|
20
|
+
const where = buildWhere(options.where);
|
|
21
|
+
parts.push('WHERE', where.sql);
|
|
22
|
+
params.push(...where.params);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (options.params) params.push(...options.params);
|
|
26
|
+
|
|
27
|
+
return { sql: parts.join(' '), params };
|
|
28
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Condition, Param } from '../../types.js';
|
|
2
|
+
import { quoteIdentifier } from '../_utils.js';
|
|
3
|
+
|
|
4
|
+
const comparison = (
|
|
5
|
+
column: string,
|
|
6
|
+
operator: string,
|
|
7
|
+
param: Param
|
|
8
|
+
): Condition => ({
|
|
9
|
+
condition: `${quoteIdentifier(column)} ${operator} ?`,
|
|
10
|
+
params: [param],
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const OP = {
|
|
14
|
+
eq: (column: string, param: Param): Condition =>
|
|
15
|
+
comparison(column, '=', param),
|
|
16
|
+
ne: (column: string, param: Param): Condition =>
|
|
17
|
+
comparison(column, '!=', param),
|
|
18
|
+
gt: (column: string, param: Param): Condition =>
|
|
19
|
+
comparison(column, '>', param),
|
|
20
|
+
lt: (column: string, param: Param): Condition =>
|
|
21
|
+
comparison(column, '<', param),
|
|
22
|
+
gte: (column: string, param: Param): Condition =>
|
|
23
|
+
comparison(column, '>=', param),
|
|
24
|
+
lte: (column: string, param: Param): Condition =>
|
|
25
|
+
comparison(column, '<=', param),
|
|
26
|
+
like: (column: string, param: Param): Condition =>
|
|
27
|
+
comparison(column, 'LIKE', param),
|
|
28
|
+
notLike: (column: string, param: Param): Condition =>
|
|
29
|
+
comparison(column, 'NOT LIKE', param),
|
|
30
|
+
|
|
31
|
+
isNull: (column: string): Condition => ({
|
|
32
|
+
condition: `${quoteIdentifier(column)} IS NULL`,
|
|
33
|
+
params: [],
|
|
34
|
+
}),
|
|
35
|
+
|
|
36
|
+
isNotNull: (column: string): Condition => ({
|
|
37
|
+
condition: `${quoteIdentifier(column)} IS NOT NULL`,
|
|
38
|
+
params: [],
|
|
39
|
+
}),
|
|
40
|
+
|
|
41
|
+
in: ((
|
|
42
|
+
column: string,
|
|
43
|
+
valuesOrSubquery: Param[] | string,
|
|
44
|
+
subqueryParams?: Param[]
|
|
45
|
+
): Condition => {
|
|
46
|
+
if (typeof valuesOrSubquery === 'string') {
|
|
47
|
+
return {
|
|
48
|
+
condition: `${quoteIdentifier(column)} IN (${valuesOrSubquery})`,
|
|
49
|
+
params: subqueryParams ?? [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const placeholders = valuesOrSubquery.map(() => '?').join(', ');
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
condition: `${quoteIdentifier(column)} IN (${placeholders})`,
|
|
57
|
+
params: valuesOrSubquery,
|
|
58
|
+
};
|
|
59
|
+
}) as {
|
|
60
|
+
(column: string, params: Param[]): Condition;
|
|
61
|
+
(column: string, subquery: string, params: Param[]): Condition;
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
notIn: ((
|
|
65
|
+
column: string,
|
|
66
|
+
valuesOrSubquery: Param[] | string,
|
|
67
|
+
subqueryParams?: Param[]
|
|
68
|
+
): Condition => {
|
|
69
|
+
if (typeof valuesOrSubquery === 'string') {
|
|
70
|
+
return {
|
|
71
|
+
condition: `${quoteIdentifier(column)} NOT IN (${valuesOrSubquery})`,
|
|
72
|
+
params: subqueryParams ?? [],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const placeholders = valuesOrSubquery.map(() => '?').join(', ');
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
condition: `${quoteIdentifier(column)} NOT IN (${placeholders})`,
|
|
80
|
+
params: valuesOrSubquery,
|
|
81
|
+
};
|
|
82
|
+
}) as {
|
|
83
|
+
(column: string, params: Param[]): Condition;
|
|
84
|
+
(column: string, subquery: string, params: Param[]): Condition;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
between: (column: string, params: [Param, Param]): Condition => ({
|
|
88
|
+
condition: `${quoteIdentifier(column)} BETWEEN ? AND ?`,
|
|
89
|
+
params,
|
|
90
|
+
}),
|
|
91
|
+
|
|
92
|
+
notBetween: (column: string, params: [Param, Param]): Condition => ({
|
|
93
|
+
condition: `${quoteIdentifier(column)} NOT BETWEEN ? AND ?`,
|
|
94
|
+
params,
|
|
95
|
+
}),
|
|
96
|
+
};
|