@rljson/io-sqlite-node 1.0.3
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.architecture.md +9 -0
- package/README.blog.md +11 -0
- package/README.contributors.md +32 -0
- package/README.md +24 -0
- package/README.piano.md +42 -0
- package/README.public.md +15 -0
- package/README.trouble.md +23 -0
- package/dist/README.architecture.md +9 -0
- package/dist/README.blog.md +11 -0
- package/dist/README.contributors.md +32 -0
- package/dist/README.md +24 -0
- package/dist/README.piano.md +42 -0
- package/dist/README.public.md +15 -0
- package/dist/README.trouble.md +23 -0
- package/dist/example.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +619 -0
- package/dist/io-sqlite-node.d.ts +64 -0
- package/dist/random-db-name.d.ts +1 -0
- package/dist/sql-statements.d.ts +40 -0
- package/dist/src/example.ts +26 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Rljson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.blog.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@license
|
|
3
|
+
Copyright (c) 2025 Rljson
|
|
4
|
+
|
|
5
|
+
Use of this source code is governed by terms that can be
|
|
6
|
+
found in the LICENSE file in the root of this package.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# Contributors Guide
|
|
10
|
+
|
|
11
|
+
- [Prepare](#prepare)
|
|
12
|
+
- [Develop](#develop)
|
|
13
|
+
- [Administrate](#administrate)
|
|
14
|
+
- [Fast Coding](#fast-coding)
|
|
15
|
+
|
|
16
|
+
## Prepare
|
|
17
|
+
|
|
18
|
+
Read [prepare.md](doc/prepare.md)
|
|
19
|
+
|
|
20
|
+
<!-- ........................................................................-->
|
|
21
|
+
|
|
22
|
+
## Develop
|
|
23
|
+
|
|
24
|
+
Read [develop.md](doc/develop.md)
|
|
25
|
+
|
|
26
|
+
## Administrate
|
|
27
|
+
|
|
28
|
+
Read [create-new-repo.md](doc/create-new-repo.md)
|
|
29
|
+
|
|
30
|
+
## Fast Coding
|
|
31
|
+
|
|
32
|
+
Read [fast-coding-guide.md](doc/fast-coding-guide.md)
|
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@license
|
|
3
|
+
Copyright (c) 2025 Rljson
|
|
4
|
+
|
|
5
|
+
Use of this source code is governed by terms that can be
|
|
6
|
+
found in the LICENSE file in the root of this package.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# @rljson/io-sqlite
|
|
10
|
+
|
|
11
|
+
## Users
|
|
12
|
+
|
|
13
|
+
| File | Purpose |
|
|
14
|
+
| ------------------------------------ | --------------------------- |
|
|
15
|
+
| [README.public.md](README.public.md) | Install and use the package |
|
|
16
|
+
|
|
17
|
+
## Contributors
|
|
18
|
+
|
|
19
|
+
| File | Purpose |
|
|
20
|
+
| ------------------------------------------------ | ----------------------------- |
|
|
21
|
+
| [README.contributors.md](README.contributors.md) | Run, debug, build and publish |
|
|
22
|
+
| [README.architecture.md](README.architecture.md) | Software architecture guide |
|
|
23
|
+
| [README.trouble.md](README.trouble.md) | Errors & solutions |
|
|
24
|
+
| [README.blog.md](README.blog.md) | Blog |
|
package/README.piano.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@license
|
|
3
|
+
Copyright (c) 2025 Rljson
|
|
4
|
+
|
|
5
|
+
Use of this source code is governed by terms that can be
|
|
6
|
+
found in the LICENSE file in the root of this package.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# Coding like a pianist
|
|
10
|
+
|
|
11
|
+
Tricks and tips for high performance coders
|
|
12
|
+
|
|
13
|
+
## Content <!-- omit in toc -->
|
|
14
|
+
|
|
15
|
+
- [Vscode](#vscode)
|
|
16
|
+
- [Quick open files](#quick-open-files)
|
|
17
|
+
- [Multi cursor search and replace](#multi-cursor-search-and-replace)
|
|
18
|
+
|
|
19
|
+
## Vscode
|
|
20
|
+
|
|
21
|
+
### Quick open files
|
|
22
|
+
|
|
23
|
+
❌ Don't open files by clicking through the folder hierarchy.
|
|
24
|
+
|
|
25
|
+
✅ Quick open files by pressing `Cmd+Shift+P`, entering fragments of the
|
|
26
|
+
file name and pressing `enter`.
|
|
27
|
+
|
|
28
|
+
### Multi cursor search and replace
|
|
29
|
+
|
|
30
|
+
❌ Don't use `Cmd+F` to search and replace files
|
|
31
|
+
|
|
32
|
+
✅ Double click a word to be searched and replaced
|
|
33
|
+
|
|
34
|
+
Press `Ctrl+ddd...` to create a multi cursor on all occurrences of the word
|
|
35
|
+
|
|
36
|
+
Modify the word
|
|
37
|
+
|
|
38
|
+
Press `ESC` to finish multi cursor edit
|
|
39
|
+
|
|
40
|
+
When doing this the first time, search will not be case sensitive
|
|
41
|
+
|
|
42
|
+
Press `Cmd+F`, click `Aa` to make `Ctrl+d` multi case sensitive
|
package/README.public.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@license
|
|
3
|
+
Copyright (c) 2025 Rljson
|
|
4
|
+
|
|
5
|
+
Use of this source code is governed by terms that can be
|
|
6
|
+
found in the LICENSE file in the root of this package.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# @rljson/io-sqlite
|
|
10
|
+
|
|
11
|
+
Todo: Add description here
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
[src/example.ts](src/example.ts)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@license
|
|
3
|
+
Copyright (c) 2025 Rljson
|
|
4
|
+
|
|
5
|
+
Use of this source code is governed by terms that can be
|
|
6
|
+
found in the LICENSE file in the root of this package.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# Trouble shooting
|
|
10
|
+
|
|
11
|
+
## Table of contents <!-- omit in toc -->
|
|
12
|
+
|
|
13
|
+
- [Vscode Windows: Debugging is not working](#vscode-windows-debugging-is-not-working)
|
|
14
|
+
|
|
15
|
+
## Vscode Windows: Debugging is not working
|
|
16
|
+
|
|
17
|
+
Date: 2025-03-08
|
|
18
|
+
|
|
19
|
+
⚠️ IMPORTANT: On Windows, please check out the repo on drive C. There is a bug
|
|
20
|
+
in the VS Code Vitest extension (v1.14.4), which prevents test debugging from
|
|
21
|
+
working: <https://github.com/vitest-dev/vscode/issues/548> Please check from
|
|
22
|
+
time to time if the issue has been fixed and remove this note once it is
|
|
23
|
+
resolved.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@license
|
|
3
|
+
Copyright (c) 2025 Rljson
|
|
4
|
+
|
|
5
|
+
Use of this source code is governed by terms that can be
|
|
6
|
+
found in the LICENSE file in the root of this package.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# Contributors Guide
|
|
10
|
+
|
|
11
|
+
- [Prepare](#prepare)
|
|
12
|
+
- [Develop](#develop)
|
|
13
|
+
- [Administrate](#administrate)
|
|
14
|
+
- [Fast Coding](#fast-coding)
|
|
15
|
+
|
|
16
|
+
## Prepare
|
|
17
|
+
|
|
18
|
+
Read [prepare.md](doc/prepare.md)
|
|
19
|
+
|
|
20
|
+
<!-- ........................................................................-->
|
|
21
|
+
|
|
22
|
+
## Develop
|
|
23
|
+
|
|
24
|
+
Read [develop.md](doc/develop.md)
|
|
25
|
+
|
|
26
|
+
## Administrate
|
|
27
|
+
|
|
28
|
+
Read [create-new-repo.md](doc/create-new-repo.md)
|
|
29
|
+
|
|
30
|
+
## Fast Coding
|
|
31
|
+
|
|
32
|
+
Read [fast-coding-guide.md](doc/fast-coding-guide.md)
|
package/dist/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@license
|
|
3
|
+
Copyright (c) 2025 Rljson
|
|
4
|
+
|
|
5
|
+
Use of this source code is governed by terms that can be
|
|
6
|
+
found in the LICENSE file in the root of this package.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# @rljson/io-sqlite
|
|
10
|
+
|
|
11
|
+
## Users
|
|
12
|
+
|
|
13
|
+
| File | Purpose |
|
|
14
|
+
| ------------------------------------ | --------------------------- |
|
|
15
|
+
| [README.public.md](README.public.md) | Install and use the package |
|
|
16
|
+
|
|
17
|
+
## Contributors
|
|
18
|
+
|
|
19
|
+
| File | Purpose |
|
|
20
|
+
| ------------------------------------------------ | ----------------------------- |
|
|
21
|
+
| [README.contributors.md](README.contributors.md) | Run, debug, build and publish |
|
|
22
|
+
| [README.architecture.md](README.architecture.md) | Software architecture guide |
|
|
23
|
+
| [README.trouble.md](README.trouble.md) | Errors & solutions |
|
|
24
|
+
| [README.blog.md](README.blog.md) | Blog |
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@license
|
|
3
|
+
Copyright (c) 2025 Rljson
|
|
4
|
+
|
|
5
|
+
Use of this source code is governed by terms that can be
|
|
6
|
+
found in the LICENSE file in the root of this package.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# Coding like a pianist
|
|
10
|
+
|
|
11
|
+
Tricks and tips for high performance coders
|
|
12
|
+
|
|
13
|
+
## Content <!-- omit in toc -->
|
|
14
|
+
|
|
15
|
+
- [Vscode](#vscode)
|
|
16
|
+
- [Quick open files](#quick-open-files)
|
|
17
|
+
- [Multi cursor search and replace](#multi-cursor-search-and-replace)
|
|
18
|
+
|
|
19
|
+
## Vscode
|
|
20
|
+
|
|
21
|
+
### Quick open files
|
|
22
|
+
|
|
23
|
+
❌ Don't open files by clicking through the folder hierarchy.
|
|
24
|
+
|
|
25
|
+
✅ Quick open files by pressing `Cmd+Shift+P`, entering fragments of the
|
|
26
|
+
file name and pressing `enter`.
|
|
27
|
+
|
|
28
|
+
### Multi cursor search and replace
|
|
29
|
+
|
|
30
|
+
❌ Don't use `Cmd+F` to search and replace files
|
|
31
|
+
|
|
32
|
+
✅ Double click a word to be searched and replaced
|
|
33
|
+
|
|
34
|
+
Press `Ctrl+ddd...` to create a multi cursor on all occurrences of the word
|
|
35
|
+
|
|
36
|
+
Modify the word
|
|
37
|
+
|
|
38
|
+
Press `ESC` to finish multi cursor edit
|
|
39
|
+
|
|
40
|
+
When doing this the first time, search will not be case sensitive
|
|
41
|
+
|
|
42
|
+
Press `Cmd+F`, click `Aa` to make `Ctrl+d` multi case sensitive
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@license
|
|
3
|
+
Copyright (c) 2025 Rljson
|
|
4
|
+
|
|
5
|
+
Use of this source code is governed by terms that can be
|
|
6
|
+
found in the LICENSE file in the root of this package.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# @rljson/io-sqlite
|
|
10
|
+
|
|
11
|
+
Todo: Add description here
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
[src/example.ts](src/example.ts)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@license
|
|
3
|
+
Copyright (c) 2025 Rljson
|
|
4
|
+
|
|
5
|
+
Use of this source code is governed by terms that can be
|
|
6
|
+
found in the LICENSE file in the root of this package.
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# Trouble shooting
|
|
10
|
+
|
|
11
|
+
## Table of contents <!-- omit in toc -->
|
|
12
|
+
|
|
13
|
+
- [Vscode Windows: Debugging is not working](#vscode-windows-debugging-is-not-working)
|
|
14
|
+
|
|
15
|
+
## Vscode Windows: Debugging is not working
|
|
16
|
+
|
|
17
|
+
Date: 2025-03-08
|
|
18
|
+
|
|
19
|
+
⚠️ IMPORTANT: On Windows, please check out the repo on drive C. There is a bug
|
|
20
|
+
in the VS Code Vitest extension (v1.14.4), which prevents test debugging from
|
|
21
|
+
working: <https://github.com/vitest-dev/vscode/issues/548> Please check from
|
|
22
|
+
time to time if the issue has been fixed and remove this note once it is
|
|
23
|
+
resolved.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const example: () => void;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
import { hip, hsh } from "@rljson/hash";
|
|
2
|
+
import { IoDbNameMapping, IoTools } from "@rljson/io";
|
|
3
|
+
import { IsReady } from "@rljson/is-ready";
|
|
4
|
+
import { iterateTables } from "@rljson/rljson";
|
|
5
|
+
import { mkdirSync } from "node:fs";
|
|
6
|
+
import { unlink } from "node:fs/promises";
|
|
7
|
+
import { dirname } from "node:path";
|
|
8
|
+
import { DatabaseSync } from "node:sqlite";
|
|
9
|
+
function randomDbName() {
|
|
10
|
+
return (() => {
|
|
11
|
+
const randomCode = Math.floor(Math.random() * 1e10).toString().padStart(10, "0");
|
|
12
|
+
return `test_${randomCode}.db`;
|
|
13
|
+
})();
|
|
14
|
+
}
|
|
15
|
+
class SqlStatements {
|
|
16
|
+
_map = new IoDbNameMapping();
|
|
17
|
+
/**
|
|
18
|
+
* Converts a JSON value type to an SQLite data type.
|
|
19
|
+
* @param dataType - The JSON value type to convert.
|
|
20
|
+
* @returns - The corresponding SQLite data type.
|
|
21
|
+
*/
|
|
22
|
+
jsonToSqlType(dataType) {
|
|
23
|
+
switch (dataType) {
|
|
24
|
+
case "string":
|
|
25
|
+
return "TEXT";
|
|
26
|
+
case "jsonArray":
|
|
27
|
+
return "TEXT";
|
|
28
|
+
case "json":
|
|
29
|
+
return "TEXT";
|
|
30
|
+
case "number":
|
|
31
|
+
return "REAL";
|
|
32
|
+
case "boolean":
|
|
33
|
+
return "INTEGER";
|
|
34
|
+
case "jsonValue":
|
|
35
|
+
return "TEXT";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
get tableKey() {
|
|
39
|
+
return `SELECT name FROM sqlite_master WHERE type='table' AND name=?`;
|
|
40
|
+
}
|
|
41
|
+
get tableKeys() {
|
|
42
|
+
return `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`;
|
|
43
|
+
}
|
|
44
|
+
rowCount(tableKey) {
|
|
45
|
+
return `SELECT COUNT(*) AS RECORDCOUNT FROM ${this._map.addTableSuffix(
|
|
46
|
+
tableKey
|
|
47
|
+
)}`;
|
|
48
|
+
}
|
|
49
|
+
allData(tableKey, namedColumns) {
|
|
50
|
+
if (!namedColumns) {
|
|
51
|
+
namedColumns = `*`;
|
|
52
|
+
}
|
|
53
|
+
return `SELECT ${namedColumns} FROM ${tableKey}`;
|
|
54
|
+
}
|
|
55
|
+
get tableCfg() {
|
|
56
|
+
return `SELECT * FROM ${this._map.addTableSuffix(
|
|
57
|
+
this._map.tableNames.main
|
|
58
|
+
)} WHERE ${this._map.addColumnSuffix("key")} = ?`;
|
|
59
|
+
}
|
|
60
|
+
get tableCfgs() {
|
|
61
|
+
return `SELECT * FROM ${this._map.addTableSuffix(
|
|
62
|
+
this._map.tableNames.main
|
|
63
|
+
)}`;
|
|
64
|
+
}
|
|
65
|
+
/* v8 ignore next -- @preserve */
|
|
66
|
+
get currentTableCfg() {
|
|
67
|
+
const sql = [
|
|
68
|
+
"WITH versions AS (",
|
|
69
|
+
" SELECT _hash_col, key_col, MAX(json_each.key) AS max_val",
|
|
70
|
+
" FROM tableCfgs_tbl, json_each(columns_col)",
|
|
71
|
+
" WHERE json_each.value IS NOT NULL",
|
|
72
|
+
" AND key_col = ? GROUP BY _hash_col, key_col)",
|
|
73
|
+
"SELECT * FROM tableCfgs_tbl tt",
|
|
74
|
+
" LEFT JOIN versions ON tt._hash_col = versions._hash_col",
|
|
75
|
+
" WHERE versions.max_val = (SELECT MAX(max_val) FROM versions);"
|
|
76
|
+
];
|
|
77
|
+
return sql.join("\n");
|
|
78
|
+
}
|
|
79
|
+
get currentTableCfgs() {
|
|
80
|
+
const sql = [
|
|
81
|
+
"SELECT",
|
|
82
|
+
" *",
|
|
83
|
+
"FROM",
|
|
84
|
+
" tableCfgs_tbl",
|
|
85
|
+
"WHERE",
|
|
86
|
+
" _hash_col IN (",
|
|
87
|
+
" WITH",
|
|
88
|
+
" column_count AS (",
|
|
89
|
+
" SELECT",
|
|
90
|
+
" _hash_col,",
|
|
91
|
+
" key_col,",
|
|
92
|
+
" MAX(json_each.key) AS max_val",
|
|
93
|
+
" FROM",
|
|
94
|
+
" tableCfgs_tbl,",
|
|
95
|
+
" json_each (columns_col)",
|
|
96
|
+
" WHERE",
|
|
97
|
+
" json_each.value IS NOT NULL",
|
|
98
|
+
" GROUP BY",
|
|
99
|
+
" _hash_col,",
|
|
100
|
+
" key_col",
|
|
101
|
+
" ),",
|
|
102
|
+
" max_tables AS (",
|
|
103
|
+
" SELECT",
|
|
104
|
+
" key_col,",
|
|
105
|
+
" MAX(max_val) AS newest",
|
|
106
|
+
" FROM",
|
|
107
|
+
" column_count",
|
|
108
|
+
" GROUP BY",
|
|
109
|
+
" key_col",
|
|
110
|
+
" )",
|
|
111
|
+
" SELECT",
|
|
112
|
+
" cc._hash_col",
|
|
113
|
+
" FROM",
|
|
114
|
+
" column_count cc",
|
|
115
|
+
" LEFT JOIN max_tables mt ON cc.key_col = mt.key_col",
|
|
116
|
+
" AND cc.max_val = mt.newest",
|
|
117
|
+
" WHERE",
|
|
118
|
+
" mt.newest IS NOT NULL",
|
|
119
|
+
" );"
|
|
120
|
+
];
|
|
121
|
+
return sql.join("\n");
|
|
122
|
+
}
|
|
123
|
+
get articleExists() {
|
|
124
|
+
return "SELECT cl.layer, ar.assign FROM catalogLayers cl\nLEFT JOIN articleSets ar\nON cl.articleSetsRef = ar._hash\nWHERE cl.winNumber = ? ";
|
|
125
|
+
}
|
|
126
|
+
get catalogExists() {
|
|
127
|
+
return "SELECT 1 FROM catalogLayers WHERE winNumber = ?";
|
|
128
|
+
}
|
|
129
|
+
contentType() {
|
|
130
|
+
const sourceTable = this._map.addTableSuffix(this._map.tableNames.main);
|
|
131
|
+
const resultCol = this._map.addColumnSuffix("type");
|
|
132
|
+
const sql = `SELECT ${resultCol} as contentType FROM [${sourceTable}] WHERE key_col =?`;
|
|
133
|
+
return sql;
|
|
134
|
+
}
|
|
135
|
+
insertTableCfg() {
|
|
136
|
+
const columnKeys = IoTools.tableCfgsTableCfg.columns.map((col) => col.key);
|
|
137
|
+
const columnKeysWithPostfix = columnKeys.map(
|
|
138
|
+
(col) => this._map.addColumnSuffix(col)
|
|
139
|
+
);
|
|
140
|
+
const columnsSql = columnKeysWithPostfix.join(", ");
|
|
141
|
+
const valuesSql = "?, ".repeat(columnKeys.length - 1) + "?";
|
|
142
|
+
return `INSERT OR IGNORE INTO ${this._map.addTableSuffix(
|
|
143
|
+
this._map.tableNames.main
|
|
144
|
+
)} ( ${columnsSql} ) VALUES (${valuesSql})`;
|
|
145
|
+
}
|
|
146
|
+
tableExists() {
|
|
147
|
+
return `SELECT name FROM sqlite_master WHERE type='table' AND name=?;`;
|
|
148
|
+
}
|
|
149
|
+
get tableType() {
|
|
150
|
+
return `SELECT ${this._map.addColumnSuffix(
|
|
151
|
+
"type"
|
|
152
|
+
)} AS type FROM ${this._map.addTableSuffix(this._map.tableNames.main)}
|
|
153
|
+
WHERE ${this._map.addColumnSuffix("key")} = ?
|
|
154
|
+
AND ${this._map.addColumnSuffix("version")}
|
|
155
|
+
= (SELECT MAX(${this._map.addColumnSuffix("version")})
|
|
156
|
+
FROM ${this._map.addTableSuffix(this._map.tableNames.main)}
|
|
157
|
+
WHERE ${this._map.addColumnSuffix("key")} = ?)`;
|
|
158
|
+
}
|
|
159
|
+
columnKeys(tableKey) {
|
|
160
|
+
return `PRAGMA table_info(${tableKey})`;
|
|
161
|
+
}
|
|
162
|
+
createFullTable(tableKey, columnsDefinition, foreignKeys) {
|
|
163
|
+
return `CREATE TABLE ${tableKey} (${columnsDefinition}, ${foreignKeys})`;
|
|
164
|
+
}
|
|
165
|
+
dropTable(tableKey) {
|
|
166
|
+
return `DROP TABLE IF EXISTS ${this._map.addTableSuffix(tableKey)}`;
|
|
167
|
+
}
|
|
168
|
+
createTempTable(tableKey) {
|
|
169
|
+
return `CREATE TABLE ${this._map.addTmpSuffix(
|
|
170
|
+
tableKey
|
|
171
|
+
)} AS SELECT * FROM ${this._map.addTableSuffix(tableKey)}`;
|
|
172
|
+
}
|
|
173
|
+
dropTempTable(tableKey) {
|
|
174
|
+
return `DROP TABLE IF EXISTS ${this._map.addTmpSuffix(tableKey)}`;
|
|
175
|
+
}
|
|
176
|
+
fillTable(tableKey, commonColumns) {
|
|
177
|
+
return `INSERT OR IGNORE INTO ${this._map.addTableSuffix(
|
|
178
|
+
tableKey
|
|
179
|
+
)} (${commonColumns}) SELECT ${commonColumns} FROM ${this._map.addTmpSuffix(
|
|
180
|
+
tableKey
|
|
181
|
+
)}`;
|
|
182
|
+
}
|
|
183
|
+
deleteFromTable(tableKey, winNumber) {
|
|
184
|
+
return `DELETE FROM ${tableKey} WHERE winNumber = '${winNumber}'`;
|
|
185
|
+
}
|
|
186
|
+
addColumn(tableKey, columnName, columnType) {
|
|
187
|
+
return `ALTER TABLE ${tableKey} ADD COLUMN ${columnName} ${columnType}`;
|
|
188
|
+
}
|
|
189
|
+
selection(tableKey, columns, whereClause) {
|
|
190
|
+
return `SELECT ${columns} FROM ${tableKey} WHERE ${whereClause}`;
|
|
191
|
+
}
|
|
192
|
+
articleSetsRefs(winNumber) {
|
|
193
|
+
return `SELECT layer, articleSetsRef FROM catalogLayers WHERE winNumber = '${winNumber}'`;
|
|
194
|
+
}
|
|
195
|
+
currentCount(tableKey) {
|
|
196
|
+
return `SELECT COUNT(*) FROM ${this._map.addTableSuffix(tableKey)}`;
|
|
197
|
+
}
|
|
198
|
+
createTable(tableCfg) {
|
|
199
|
+
const sqltableKey = this._map.addTableSuffix(tableCfg.key);
|
|
200
|
+
const columnsCfg = tableCfg.columns;
|
|
201
|
+
const sqlCreateColumns = columnsCfg.map((col) => {
|
|
202
|
+
const sqliteType = this.jsonToSqlType(col.type);
|
|
203
|
+
return `${this._map.addColumnSuffix(col.key)} ${sqliteType}`;
|
|
204
|
+
}).join(", ");
|
|
205
|
+
const conKey = `${this._map.addColumnSuffix(
|
|
206
|
+
this._map.primaryKeyColumn
|
|
207
|
+
)} TEXT`;
|
|
208
|
+
const primaryKey = `${conKey} PRIMARY KEY NOT NULL`;
|
|
209
|
+
const colsWithPrimaryKey = sqlCreateColumns.replace(conKey, primaryKey);
|
|
210
|
+
return `CREATE TABLE IF NOT EXISTS ${sqltableKey} (${colsWithPrimaryKey})`;
|
|
211
|
+
}
|
|
212
|
+
alterTable(tableKey, addedColumns) {
|
|
213
|
+
const tableKeyWithSuffix = this._map.addTableSuffix(tableKey);
|
|
214
|
+
const statements = [];
|
|
215
|
+
for (const col of addedColumns) {
|
|
216
|
+
const columnKey = this._map.addColumnSuffix(col.key);
|
|
217
|
+
const columnType = this.jsonToSqlType(col.type);
|
|
218
|
+
statements.push(
|
|
219
|
+
`ALTER TABLE ${tableKeyWithSuffix} ADD COLUMN ${columnKey} ${columnType};`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
return statements;
|
|
223
|
+
}
|
|
224
|
+
get createTableCfgsTable() {
|
|
225
|
+
return this.createTable(IoTools.tableCfgsTableCfg);
|
|
226
|
+
}
|
|
227
|
+
get tableTypeCheck() {
|
|
228
|
+
return `SELECT ${this._map.addColumnSuffix(
|
|
229
|
+
"type"
|
|
230
|
+
)} FROM ${this._map.addTableSuffix(
|
|
231
|
+
this._map.tableNames.main
|
|
232
|
+
)} WHERE ${this._map.addColumnSuffix("key")} = ?`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
class IoSqliteNode {
|
|
236
|
+
_ioTools;
|
|
237
|
+
_isReady = new IsReady();
|
|
238
|
+
_sql;
|
|
239
|
+
_map = new IoDbNameMapping();
|
|
240
|
+
_persistence = true;
|
|
241
|
+
_dbFileName;
|
|
242
|
+
_undeletedFile;
|
|
243
|
+
constructor() {
|
|
244
|
+
this._sql = new SqlStatements();
|
|
245
|
+
}
|
|
246
|
+
get isOpen() {
|
|
247
|
+
return this._isOpen;
|
|
248
|
+
}
|
|
249
|
+
async init() {
|
|
250
|
+
this._openOrCreateDatabase();
|
|
251
|
+
this._ioTools = new IoTools(this);
|
|
252
|
+
this._initTableCfgs();
|
|
253
|
+
await this._ioTools.initRevisionsTable();
|
|
254
|
+
this._isReady.resolve();
|
|
255
|
+
}
|
|
256
|
+
db;
|
|
257
|
+
static example = async () => {
|
|
258
|
+
const ioSqliteServer = new IoSqliteNode();
|
|
259
|
+
ioSqliteServer.dbFileName = randomDbName();
|
|
260
|
+
return ioSqliteServer;
|
|
261
|
+
};
|
|
262
|
+
async close() {
|
|
263
|
+
this._isOpen = false;
|
|
264
|
+
try {
|
|
265
|
+
this.db.close();
|
|
266
|
+
} catch (err) {
|
|
267
|
+
if (err.code === "ERR_INVALID_STATE") {
|
|
268
|
+
return;
|
|
269
|
+
} else {
|
|
270
|
+
throw err;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
isReady() {
|
|
275
|
+
return this._isReady.promise;
|
|
276
|
+
}
|
|
277
|
+
dump() {
|
|
278
|
+
return this._dump();
|
|
279
|
+
}
|
|
280
|
+
dumpTable(request) {
|
|
281
|
+
return this._dumpTable(request);
|
|
282
|
+
}
|
|
283
|
+
async contentType(request) {
|
|
284
|
+
const result = this.db.prepare(this._sql.contentType()).get(request.table);
|
|
285
|
+
return result?.contentType;
|
|
286
|
+
}
|
|
287
|
+
async tableExists(tableKey) {
|
|
288
|
+
return this._tableExists(tableKey);
|
|
289
|
+
}
|
|
290
|
+
async createOrExtendTable(request) {
|
|
291
|
+
return this._createOrExtendTable(request);
|
|
292
|
+
}
|
|
293
|
+
async rawTableCfgs() {
|
|
294
|
+
const tableCfg = IoTools.tableCfgsTableCfg;
|
|
295
|
+
const resultSet = this.db.prepare(this._sql.tableCfgs).all();
|
|
296
|
+
const rowArray = [];
|
|
297
|
+
for (const row of resultSet) {
|
|
298
|
+
rowArray.push(row);
|
|
299
|
+
}
|
|
300
|
+
const parsedReturnValue = this._parseData(rowArray, tableCfg);
|
|
301
|
+
return parsedReturnValue;
|
|
302
|
+
}
|
|
303
|
+
async write(request) {
|
|
304
|
+
await this._write(request);
|
|
305
|
+
}
|
|
306
|
+
readRows(request) {
|
|
307
|
+
return this._readRows(request);
|
|
308
|
+
}
|
|
309
|
+
async rowCount(table) {
|
|
310
|
+
await this._ioTools.throwWhenTableDoesNotExist(table);
|
|
311
|
+
const resultSet = this.db.prepare(this._sql.rowCount(table)).all();
|
|
312
|
+
const countRaw = resultSet[0]["RECORDCOUNT"];
|
|
313
|
+
const count = Number(countRaw);
|
|
314
|
+
return count;
|
|
315
|
+
}
|
|
316
|
+
// Sqlite-specific functions************************************************
|
|
317
|
+
async execute(sql) {
|
|
318
|
+
try {
|
|
319
|
+
const stmt = this.db.prepare(sql);
|
|
320
|
+
if (sql.trim().toUpperCase().startsWith("SELECT")) {
|
|
321
|
+
return stmt.all();
|
|
322
|
+
} else {
|
|
323
|
+
return stmt.run();
|
|
324
|
+
}
|
|
325
|
+
} catch (error) {
|
|
326
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
327
|
+
throw new Error(`Malformed SQL statement: ${errorMessage}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
get undeletedFile() {
|
|
331
|
+
return this._undeletedFile;
|
|
332
|
+
}
|
|
333
|
+
set dbFileName(fileName) {
|
|
334
|
+
if (!fileName) {
|
|
335
|
+
this._persistence = false;
|
|
336
|
+
this._dbFileName = void 0;
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
this._persistence = true;
|
|
340
|
+
this._dbFileName = `./data/${fileName}`;
|
|
341
|
+
}
|
|
342
|
+
get dbFileName() {
|
|
343
|
+
return this._dbFileName;
|
|
344
|
+
}
|
|
345
|
+
async openOrCreateDatabase() {
|
|
346
|
+
this._openOrCreateDatabase();
|
|
347
|
+
}
|
|
348
|
+
_openOrCreateDatabase() {
|
|
349
|
+
if (!this._dbFileName) {
|
|
350
|
+
this._persistence = false;
|
|
351
|
+
this.db = new DatabaseSync(":memory:");
|
|
352
|
+
} else {
|
|
353
|
+
mkdirSync(dirname(`${this._dbFileName}`), { recursive: true });
|
|
354
|
+
this.db = new DatabaseSync(`${this._dbFileName}`);
|
|
355
|
+
this._persistence = true;
|
|
356
|
+
}
|
|
357
|
+
this._isOpen = true;
|
|
358
|
+
return this._persistence ? this._dbFileName : "in-memory";
|
|
359
|
+
}
|
|
360
|
+
async deleteDatabase() {
|
|
361
|
+
if (this._isOpen) {
|
|
362
|
+
this.db.close();
|
|
363
|
+
this._isOpen = false;
|
|
364
|
+
}
|
|
365
|
+
if (this._persistence) {
|
|
366
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
367
|
+
let deleted = false;
|
|
368
|
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
369
|
+
try {
|
|
370
|
+
await unlink(this._dbFileName);
|
|
371
|
+
deleted = true;
|
|
372
|
+
break;
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (error.code === "ENOENT") {
|
|
375
|
+
deleted = true;
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
if (attempt < 5) {
|
|
379
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (!deleted) {
|
|
384
|
+
this._undeletedFile = this._dbFileName;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
//********Private section */
|
|
389
|
+
_isOpen = false;
|
|
390
|
+
async _dump() {
|
|
391
|
+
const returnFile = {};
|
|
392
|
+
const resultSet = this.db.prepare(this._sql.tableKeys).all();
|
|
393
|
+
const tableNames = resultSet.map((row) => row.name).filter((name) => name != null);
|
|
394
|
+
for (const table of tableNames) {
|
|
395
|
+
const tableDump = await this._dumpTable({
|
|
396
|
+
table: this._map.removeTableSuffix(table)
|
|
397
|
+
});
|
|
398
|
+
returnFile[this._map.removeTableSuffix(table)] = tableDump[this._map.removeTableSuffix(table)];
|
|
399
|
+
}
|
|
400
|
+
this._addMissingHashes(returnFile);
|
|
401
|
+
return returnFile;
|
|
402
|
+
}
|
|
403
|
+
_addMissingHashes(rljson) {
|
|
404
|
+
hip(rljson, { updateExistingHashes: false, throwOnWrongHashes: false });
|
|
405
|
+
}
|
|
406
|
+
_parseData(data, tableCfg) {
|
|
407
|
+
const columnTypes = tableCfg.columns.map((col) => col.type);
|
|
408
|
+
const columnKeys = tableCfg.columns.map((col) => col.key);
|
|
409
|
+
const convertedResult = [];
|
|
410
|
+
for (const row of data) {
|
|
411
|
+
const convertedRow = {};
|
|
412
|
+
for (let colNum = 0; colNum < columnKeys.length; colNum++) {
|
|
413
|
+
const key = columnKeys[colNum];
|
|
414
|
+
const keyWithSuffix = this._map.addColumnSuffix(key);
|
|
415
|
+
const type = columnTypes[colNum];
|
|
416
|
+
const val = row[keyWithSuffix];
|
|
417
|
+
if (val === void 0) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (val === null) {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
switch (type) {
|
|
424
|
+
case "boolean":
|
|
425
|
+
convertedRow[key] = val !== 0;
|
|
426
|
+
break;
|
|
427
|
+
case "jsonArray":
|
|
428
|
+
case "json":
|
|
429
|
+
convertedRow[key] = JSON.parse(val);
|
|
430
|
+
break;
|
|
431
|
+
case "string":
|
|
432
|
+
case "number":
|
|
433
|
+
convertedRow[key] = val;
|
|
434
|
+
break;
|
|
435
|
+
/* v8 ignore next -- @preserve */
|
|
436
|
+
default:
|
|
437
|
+
throw new Error("Unsupported column type " + type);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
convertedResult.push(convertedRow);
|
|
441
|
+
}
|
|
442
|
+
return convertedResult;
|
|
443
|
+
}
|
|
444
|
+
_initTableCfgs = () => {
|
|
445
|
+
const tableCfg = IoTools.tableCfgsTableCfg;
|
|
446
|
+
this.db.prepare(this._sql.createTable(tableCfg)).run();
|
|
447
|
+
const valuesFromTableCfg = this._serializeRow(tableCfg, tableCfg);
|
|
448
|
+
this.db.prepare(this._sql.insertTableCfg()).run(...valuesFromTableCfg);
|
|
449
|
+
};
|
|
450
|
+
_serializeRow(rowAsJson, tableCfg) {
|
|
451
|
+
const result = [];
|
|
452
|
+
for (const col of tableCfg.columns) {
|
|
453
|
+
const key = col.key;
|
|
454
|
+
let value = rowAsJson[key] ?? null;
|
|
455
|
+
const valueType = typeof value;
|
|
456
|
+
if (value !== null && valueType === "object") {
|
|
457
|
+
value = JSON.stringify(value);
|
|
458
|
+
} else if (valueType === "boolean") {
|
|
459
|
+
value = value ? 1 : 0;
|
|
460
|
+
}
|
|
461
|
+
result.push(value);
|
|
462
|
+
}
|
|
463
|
+
return result;
|
|
464
|
+
}
|
|
465
|
+
async _dumpTable(request) {
|
|
466
|
+
await this._ioTools.throwWhenTableDoesNotExist(request.table);
|
|
467
|
+
const tableKey = this._map.addTableSuffix(request.table);
|
|
468
|
+
const tableCfg = await this._ioTools.tableCfg(request.table);
|
|
469
|
+
const columnKeys = tableCfg.columns.map((col) => col.key);
|
|
470
|
+
const columnKeysWithSuffix = columnKeys.map(
|
|
471
|
+
(col) => this._map.addColumnSuffix(col)
|
|
472
|
+
);
|
|
473
|
+
const resultSet = this.db.prepare(this._sql.allData(tableKey, columnKeysWithSuffix.join(", "))).all();
|
|
474
|
+
const parsedReturnData = this._parseData(resultSet, tableCfg);
|
|
475
|
+
const tableCfgHash = tableCfg._hash;
|
|
476
|
+
const table = {
|
|
477
|
+
_type: tableCfg.type,
|
|
478
|
+
_data: parsedReturnData,
|
|
479
|
+
_tableCfg: tableCfgHash,
|
|
480
|
+
_hash: ""
|
|
481
|
+
};
|
|
482
|
+
this._ioTools.sortTableDataAndUpdateHash(table);
|
|
483
|
+
const returnFile = {};
|
|
484
|
+
returnFile[request.table] = table;
|
|
485
|
+
return returnFile;
|
|
486
|
+
}
|
|
487
|
+
// ...........................................................................
|
|
488
|
+
async _write(request) {
|
|
489
|
+
const hashedData = hsh(request.data);
|
|
490
|
+
const errorStore = /* @__PURE__ */ new Map();
|
|
491
|
+
let errorCount = 0;
|
|
492
|
+
await this._ioTools.throwWhenTablesDoNotExist(request.data);
|
|
493
|
+
await this._ioTools.throwWhenTableDataDoesNotMatchCfg(request.data);
|
|
494
|
+
await iterateTables(hashedData, async (tableName, tableData) => {
|
|
495
|
+
const tableCfg = await this._ioTools.tableCfg(tableName);
|
|
496
|
+
const tableKeyWithSuffix = this._map.addTableSuffix(tableName);
|
|
497
|
+
for (const row of tableData._data) {
|
|
498
|
+
const columnKeys = tableCfg.columns.map((col) => col.key);
|
|
499
|
+
const columnKeysWithPostfix = columnKeys.map(
|
|
500
|
+
(column) => this._map.addColumnSuffix(column)
|
|
501
|
+
);
|
|
502
|
+
const placeholders = columnKeys.map(() => "?").join(", ");
|
|
503
|
+
const query = `INSERT OR IGNORE INTO ${tableKeyWithSuffix} (${columnKeysWithPostfix.join(
|
|
504
|
+
", "
|
|
505
|
+
)}) VALUES (${placeholders})`;
|
|
506
|
+
const serializedRow = this._serializeRow(row, tableCfg);
|
|
507
|
+
try {
|
|
508
|
+
this.db.prepare(query).run(...serializedRow);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
514
|
+
errorCount++;
|
|
515
|
+
errorStore.set(
|
|
516
|
+
errorCount,
|
|
517
|
+
`Error inserting into table ${tableName}: ${errorMessage}`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
if (errorCount > 0) {
|
|
523
|
+
const errorMessages = Array.from(errorStore.values()).join(", ");
|
|
524
|
+
throw new Error(`Errors occurred: ${errorMessages}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
async _tableExists(tableKey) {
|
|
528
|
+
const tableKeyWithSuffix = this._map.addTableSuffix(tableKey);
|
|
529
|
+
const result = this.db.prepare(this._sql.tableExists()).get(tableKeyWithSuffix);
|
|
530
|
+
return result?.name === tableKeyWithSuffix ? true : false;
|
|
531
|
+
}
|
|
532
|
+
_whereString(whereClause) {
|
|
533
|
+
let whereString = " ";
|
|
534
|
+
for (const [column, value] of whereClause) {
|
|
535
|
+
const columnWithFix = this._map.addColumnSuffix(column);
|
|
536
|
+
if (typeof value === "string") {
|
|
537
|
+
whereString += `${columnWithFix} = '${value}' AND `;
|
|
538
|
+
} else if (typeof value === "number") {
|
|
539
|
+
whereString += `${columnWithFix} = ${value} AND `;
|
|
540
|
+
} else if (typeof value === "boolean") {
|
|
541
|
+
whereString += `${columnWithFix} = ${value ? 1 : 0} AND `;
|
|
542
|
+
} else if (value === null) {
|
|
543
|
+
whereString += `${columnWithFix} IS NULL AND `;
|
|
544
|
+
} else if (typeof value === "object") {
|
|
545
|
+
whereString += `${columnWithFix} = '${JSON.stringify(value)}' AND `;
|
|
546
|
+
} else {
|
|
547
|
+
throw new Error(`Unsupported value type for column ${column}`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
whereString = whereString.endsWith("AND ") ? whereString.slice(0, -5) : whereString;
|
|
551
|
+
return whereString;
|
|
552
|
+
}
|
|
553
|
+
async _createOrExtendTable(request) {
|
|
554
|
+
await this._ioTools.throwWhenTableIsNotCompatible(request.tableCfg);
|
|
555
|
+
const tableKey = request.tableCfg.key;
|
|
556
|
+
const tableCfgHashed = hsh(request.tableCfg);
|
|
557
|
+
const stmt = this.db.prepare(this._sql.tableCfg);
|
|
558
|
+
const exists = stmt.get(tableKey);
|
|
559
|
+
if (!exists) {
|
|
560
|
+
this._createTable(tableCfgHashed, request);
|
|
561
|
+
} else {
|
|
562
|
+
await this._extendTable(tableCfgHashed);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
async _readRows(request) {
|
|
566
|
+
await this._ioTools.throwWhenTableDoesNotExist(request.table);
|
|
567
|
+
await this._ioTools.throwWhenColumnDoesNotExist(request.table, [
|
|
568
|
+
...Object.keys(request.where)
|
|
569
|
+
]);
|
|
570
|
+
const tableKeyWithSuffix = this._map.addTableSuffix(request.table);
|
|
571
|
+
const tableCfg = await this._ioTools.tableCfg(request.table);
|
|
572
|
+
const whereString = this._whereString(Object.entries(request.where));
|
|
573
|
+
const query = `SELECT * FROM ${tableKeyWithSuffix} WHERE${whereString}`;
|
|
574
|
+
const resultSet = this.db.prepare(query).all();
|
|
575
|
+
const convertedResult = this._parseData(resultSet, tableCfg);
|
|
576
|
+
const table = {
|
|
577
|
+
_data: convertedResult,
|
|
578
|
+
_type: tableCfg.type
|
|
579
|
+
};
|
|
580
|
+
this._ioTools.sortTableDataAndUpdateHash(table);
|
|
581
|
+
const result = {
|
|
582
|
+
[request.table]: table
|
|
583
|
+
};
|
|
584
|
+
return result;
|
|
585
|
+
}
|
|
586
|
+
_createTable(tableCfgHashed, request) {
|
|
587
|
+
this._insertTableCfg(tableCfgHashed);
|
|
588
|
+
this.db.exec(this._sql.createTable(request.tableCfg));
|
|
589
|
+
}
|
|
590
|
+
_insertTableCfg(tableCfgHashed) {
|
|
591
|
+
hip(tableCfgHashed);
|
|
592
|
+
const values = this._serializeRow(
|
|
593
|
+
tableCfgHashed,
|
|
594
|
+
IoTools.tableCfgsTableCfg
|
|
595
|
+
);
|
|
596
|
+
this.db.prepare(this._sql.insertTableCfg()).run(...values);
|
|
597
|
+
}
|
|
598
|
+
async _extendTable(newTableCfg) {
|
|
599
|
+
const tableKey = newTableCfg.key;
|
|
600
|
+
const oldTableCfg = await this._ioTools.tableCfg(tableKey);
|
|
601
|
+
const addedColumns = [];
|
|
602
|
+
for (let i = oldTableCfg.columns.length; i < newTableCfg.columns.length; i++) {
|
|
603
|
+
const newColumn = newTableCfg.columns[i];
|
|
604
|
+
addedColumns.push(newColumn);
|
|
605
|
+
}
|
|
606
|
+
if (addedColumns.length === 0) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
this._insertTableCfg(newTableCfg);
|
|
610
|
+
const alter = this._sql.alterTable(tableKey, addedColumns);
|
|
611
|
+
for (const statement of alter) {
|
|
612
|
+
this.db.prepare(statement).run();
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
export {
|
|
617
|
+
IoSqliteNode,
|
|
618
|
+
SqlStatements
|
|
619
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Io } from '@rljson/io';
|
|
2
|
+
import { Json, JsonValue } from '@rljson/json';
|
|
3
|
+
import { ContentType, Rljson, TableCfg, TableKey } from '@rljson/rljson';
|
|
4
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
5
|
+
export declare class IoSqliteNode implements Io {
|
|
6
|
+
private _ioTools;
|
|
7
|
+
private _isReady;
|
|
8
|
+
private _sql;
|
|
9
|
+
private _map;
|
|
10
|
+
private _persistence;
|
|
11
|
+
private _dbFileName;
|
|
12
|
+
private _undeletedFile;
|
|
13
|
+
constructor();
|
|
14
|
+
get isOpen(): boolean;
|
|
15
|
+
init(): Promise<void>;
|
|
16
|
+
db: DatabaseSync;
|
|
17
|
+
static example: () => Promise<IoSqliteNode>;
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
isReady(): Promise<void>;
|
|
20
|
+
dump(): Promise<Rljson>;
|
|
21
|
+
dumpTable(request: {
|
|
22
|
+
table: string;
|
|
23
|
+
}): Promise<Rljson>;
|
|
24
|
+
contentType(request: {
|
|
25
|
+
table: string;
|
|
26
|
+
}): Promise<ContentType>;
|
|
27
|
+
tableExists(tableKey: TableKey): Promise<boolean>;
|
|
28
|
+
createOrExtendTable(request: {
|
|
29
|
+
tableCfg: TableCfg;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
rawTableCfgs(): Promise<TableCfg[]>;
|
|
32
|
+
write(request: {
|
|
33
|
+
data: Rljson;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
readRows(request: {
|
|
36
|
+
table: string;
|
|
37
|
+
where: {
|
|
38
|
+
[column: string]: JsonValue;
|
|
39
|
+
};
|
|
40
|
+
}): Promise<Rljson>;
|
|
41
|
+
rowCount(table: string): Promise<number>;
|
|
42
|
+
execute(sql: string): Promise<any>;
|
|
43
|
+
get undeletedFile(): string | undefined;
|
|
44
|
+
set dbFileName(fileName: string | undefined);
|
|
45
|
+
get dbFileName(): string | undefined;
|
|
46
|
+
openOrCreateDatabase(): Promise<void>;
|
|
47
|
+
private _openOrCreateDatabase;
|
|
48
|
+
deleteDatabase(): Promise<void>;
|
|
49
|
+
private _isOpen;
|
|
50
|
+
private _dump;
|
|
51
|
+
_addMissingHashes(rljson: Json): void;
|
|
52
|
+
private _parseData;
|
|
53
|
+
private _initTableCfgs;
|
|
54
|
+
private _serializeRow;
|
|
55
|
+
private _dumpTable;
|
|
56
|
+
private _write;
|
|
57
|
+
_tableExists(tableKey: string): Promise<boolean>;
|
|
58
|
+
_whereString(whereClause: [string, JsonValue][]): string;
|
|
59
|
+
private _createOrExtendTable;
|
|
60
|
+
private _readRows;
|
|
61
|
+
private _createTable;
|
|
62
|
+
private _insertTableCfg;
|
|
63
|
+
private _extendTable;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function randomDbName(): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { JsonValueType } from '@rljson/json';
|
|
2
|
+
import { ColumnCfg, TableCfg, TableKey } from '@rljson/rljson';
|
|
3
|
+
export declare class SqlStatements {
|
|
4
|
+
private _map;
|
|
5
|
+
/**
|
|
6
|
+
* Converts a JSON value type to an SQLite data type.
|
|
7
|
+
* @param dataType - The JSON value type to convert.
|
|
8
|
+
* @returns - The corresponding SQLite data type.
|
|
9
|
+
*/
|
|
10
|
+
jsonToSqlType(dataType: JsonValueType): string;
|
|
11
|
+
get tableKey(): string;
|
|
12
|
+
get tableKeys(): string;
|
|
13
|
+
rowCount(tableKey: string): string;
|
|
14
|
+
allData(tableKey: string, namedColumns?: string): string;
|
|
15
|
+
get tableCfg(): string;
|
|
16
|
+
get tableCfgs(): string;
|
|
17
|
+
get currentTableCfg(): string;
|
|
18
|
+
get currentTableCfgs(): string;
|
|
19
|
+
get articleExists(): string;
|
|
20
|
+
get catalogExists(): string;
|
|
21
|
+
contentType(): string;
|
|
22
|
+
insertTableCfg(): string;
|
|
23
|
+
tableExists(): string;
|
|
24
|
+
get tableType(): string;
|
|
25
|
+
columnKeys(tableKey: string): string;
|
|
26
|
+
createFullTable(tableKey: string, columnsDefinition: string, foreignKeys: string): string;
|
|
27
|
+
dropTable(tableKey: string): string;
|
|
28
|
+
createTempTable(tableKey: string): string;
|
|
29
|
+
dropTempTable(tableKey: string): string;
|
|
30
|
+
fillTable(tableKey: string, commonColumns: string): string;
|
|
31
|
+
deleteFromTable(tableKey: string, winNumber: string): string;
|
|
32
|
+
addColumn(tableKey: string, columnName: string, columnType: string): string;
|
|
33
|
+
selection(tableKey: string, columns: string, whereClause: string): string;
|
|
34
|
+
articleSetsRefs(winNumber: string): string;
|
|
35
|
+
currentCount(tableKey: string): string;
|
|
36
|
+
createTable(tableCfg: TableCfg): string;
|
|
37
|
+
alterTable(tableKey: TableKey, addedColumns: ColumnCfg[]): string[];
|
|
38
|
+
get createTableCfgsTable(): string;
|
|
39
|
+
get tableTypeCheck(): string;
|
|
40
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// @license
|
|
2
|
+
// Copyright (c) 2025 Rljson
|
|
3
|
+
//
|
|
4
|
+
// Use of this source code is governed by terms that can be
|
|
5
|
+
// found in the LICENSE file in the root of this package.
|
|
6
|
+
|
|
7
|
+
import { IoSqliteNode } from './io-sqlite-node.ts';
|
|
8
|
+
|
|
9
|
+
export const example = () => {
|
|
10
|
+
// Print methods
|
|
11
|
+
const l = console.log;
|
|
12
|
+
const h1 = (text: string) => l(`${text}`);
|
|
13
|
+
const h2 = (text: string) => l(` ${text.split('\n')}`);
|
|
14
|
+
const p = (text: string) => l(` ${text}`);
|
|
15
|
+
|
|
16
|
+
// Example
|
|
17
|
+
h1('IoSqliteNode.example');
|
|
18
|
+
h2('Returns an instance of the io-sqlite-node.');
|
|
19
|
+
const example = IoSqliteNode.example();
|
|
20
|
+
p(JSON.stringify(example, null, 2));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
// Run via "npx vite-node src/example.ts"
|
|
25
|
+
example();
|
|
26
|
+
*/
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rljson/io-sqlite-node",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "IoSql rljson files",
|
|
5
|
+
"homepage": "https://github.com/rljson/io-sqlite_node",
|
|
6
|
+
"bugs": "https://github.com/rljson/io-sqlite-node/issues",
|
|
7
|
+
"private": false,
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=22.14.0"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/rljson/io-sqlite-node.git"
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/io-sqlite-node.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "pnpm vite build && tsc && node scripts/copy-readme-to-dist.js && node scripts/copy-file.js src/example.ts dist/src",
|
|
24
|
+
"test": "pnpm installConformanceTests && pnpm vitest --coverage --no-file-parallelism --run && pnpm run lint",
|
|
25
|
+
"prebuild": "pnpm run test",
|
|
26
|
+
"prepublishOnly": "pnpm run build",
|
|
27
|
+
"lint": "pnpm eslint",
|
|
28
|
+
"updateGoldens": "cross-env UPDATE_GOLDENS=true pnpm test",
|
|
29
|
+
"installConformanceTests": "node scripts/install-conformance-tests.js"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
33
|
+
"@types/node": "^25.0.8",
|
|
34
|
+
"@types/sql.js": "^1.4.9",
|
|
35
|
+
"@typescript-eslint/eslint-plugin": "^8.53.0",
|
|
36
|
+
"@typescript-eslint/parser": "^8.53.0",
|
|
37
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
38
|
+
"cross-env": "^10.1.0",
|
|
39
|
+
"eslint": "^9.39.2",
|
|
40
|
+
"eslint-plugin-jsdoc": "^62.0.0",
|
|
41
|
+
"eslint-plugin-tsdoc": "^0.5.0",
|
|
42
|
+
"globals": "^17.0.0",
|
|
43
|
+
"jsdoc": "^4.0.5",
|
|
44
|
+
"read-pkg": "^10.0.0",
|
|
45
|
+
"typescript": "~5.9.3",
|
|
46
|
+
"typescript-eslint": "^8.53.0",
|
|
47
|
+
"vite": "^7.3.1",
|
|
48
|
+
"vite-node": "^5.2.0",
|
|
49
|
+
"vite-plugin-dts": "^4.5.4",
|
|
50
|
+
"vite-plugin-node-polyfills": "^0.25.0",
|
|
51
|
+
"vite-tsconfig-paths": "^6.0.4",
|
|
52
|
+
"vitest": "^4.0.17",
|
|
53
|
+
"vitest-dom": "^0.1.1"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@rljson/hash": "^0.0.17",
|
|
57
|
+
"@rljson/io": "^0.0.63",
|
|
58
|
+
"@rljson/is-ready": "^0.0.17",
|
|
59
|
+
"@rljson/json": "^0.0.23",
|
|
60
|
+
"@rljson/rljson": "^0.0.73",
|
|
61
|
+
"path-browserify": "^1.0.1",
|
|
62
|
+
"shx": "^0.4.0",
|
|
63
|
+
"sql.js": "^1.13.0"
|
|
64
|
+
},
|
|
65
|
+
"pnpm": {
|
|
66
|
+
"onlyBuiltDependencies": [
|
|
67
|
+
"esbuild"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"packageManager": "pnpm@10.21.0"
|
|
71
|
+
}
|