@alstate/sqlite 0.1.0 → 0.1.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 -21
- package/README.md +53 -3
- package/dist/migrations.js +61 -61
- package/dist/sqlite-learning-store.js +72 -72
- package/package.json +34 -34
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Alstate contributors
|
|
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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alstate contributors
|
|
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.md
CHANGED
|
@@ -3,13 +3,63 @@
|
|
|
3
3
|
Experimental SQLite persistence adapter for `@alstate/core`, implemented with
|
|
4
4
|
Node.js `node:sqlite`.
|
|
5
5
|
|
|
6
|
+
## Requirements and install
|
|
7
|
+
|
|
8
|
+
Node.js 22.13 or later is required.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @alstate/core @alstate/sqlite
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Add a scheduling algorithm separately; `@alstate/fsrs` is the first-party
|
|
15
|
+
choice.
|
|
16
|
+
|
|
17
|
+
## Use
|
|
18
|
+
|
|
6
19
|
```ts
|
|
20
|
+
import { LearningEngine } from "@alstate/core";
|
|
21
|
+
import { FsrsAlgorithm } from "@alstate/fsrs";
|
|
7
22
|
import { SqliteLearningStore } from "@alstate/sqlite";
|
|
8
23
|
|
|
9
|
-
const
|
|
24
|
+
const engine = await LearningEngine.create({
|
|
25
|
+
store: new SqliteLearningStore("data/learning.db"),
|
|
26
|
+
algorithm: new FsrsAlgorithm(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const item = await engine.add({ prompt: "2 + 2", answer: "4" });
|
|
31
|
+
await engine.review(item.id, "good");
|
|
32
|
+
} finally {
|
|
33
|
+
engine.close();
|
|
34
|
+
}
|
|
10
35
|
```
|
|
11
36
|
|
|
12
|
-
|
|
13
|
-
|
|
37
|
+
The constructor defaults to an in-memory database when no path is supplied:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const store = new SqliteLearningStore(); // ":memory:"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For a file path, missing parent directories are created automatically. The
|
|
44
|
+
adapter opens or creates the database, enables foreign keys, applies pending
|
|
45
|
+
schema migrations and sets a 5-second busy timeout.
|
|
46
|
+
|
|
47
|
+
## Behavior
|
|
48
|
+
|
|
49
|
+
- item creation and initial-state creation share one transaction;
|
|
50
|
+
- state update and review insertion share one transaction;
|
|
51
|
+
- reviews compare and increment a state revision, rejecting stale writers with
|
|
52
|
+
`ConcurrentReviewError`;
|
|
53
|
+
- operations are scoped to the registered algorithm;
|
|
54
|
+
- deleting an item cascades to state and review history;
|
|
55
|
+
- items are ordered by ID, due items by due time then ID, and history newest
|
|
56
|
+
first;
|
|
57
|
+
- timestamps are persisted as ISO 8601 strings and returned as `Date` objects.
|
|
58
|
+
|
|
59
|
+
`engine.close()` closes its store. Do not reuse the connection afterward.
|
|
60
|
+
|
|
61
|
+
- [SQLite and FSRS adapter guide](https://github.com/laiqfun/alstate/blob/main/docs/adapters.md)
|
|
62
|
+
- [SQLite data model](https://github.com/laiqfun/alstate/blob/main/docs/data-model.md)
|
|
63
|
+
- [Core API reference](https://github.com/laiqfun/alstate/blob/main/docs/api-reference.md)
|
|
14
64
|
|
|
15
65
|
MIT
|
package/dist/migrations.js
CHANGED
|
@@ -2,80 +2,80 @@ const migrations = [
|
|
|
2
2
|
{
|
|
3
3
|
version: 1,
|
|
4
4
|
name: "learning_engine_schema",
|
|
5
|
-
sql: `
|
|
6
|
-
CREATE TABLE engine_items (
|
|
7
|
-
id INTEGER PRIMARY KEY,
|
|
8
|
-
data_json TEXT NOT NULL CHECK (json_valid(data_json))
|
|
9
|
-
) STRICT;
|
|
10
|
-
|
|
11
|
-
CREATE TABLE engine_algorithms (
|
|
12
|
-
id INTEGER PRIMARY KEY,
|
|
13
|
-
name TEXT NOT NULL UNIQUE CHECK (length(trim(name)) > 0),
|
|
14
|
-
description TEXT,
|
|
15
|
-
version TEXT NOT NULL CHECK (length(trim(version)) > 0),
|
|
16
|
-
config_json TEXT NOT NULL CHECK (json_valid(config_json))
|
|
17
|
-
) STRICT;
|
|
18
|
-
|
|
19
|
-
CREATE TABLE engine_states (
|
|
20
|
-
id INTEGER PRIMARY KEY,
|
|
21
|
-
learning_item_id INTEGER NOT NULL,
|
|
22
|
-
algorithm_id INTEGER NOT NULL,
|
|
23
|
-
due_at TEXT NOT NULL,
|
|
24
|
-
state_json TEXT NOT NULL CHECK (json_valid(state_json)),
|
|
25
|
-
UNIQUE (learning_item_id, algorithm_id),
|
|
26
|
-
FOREIGN KEY (learning_item_id)
|
|
27
|
-
REFERENCES engine_items (id) ON DELETE CASCADE,
|
|
28
|
-
FOREIGN KEY (algorithm_id)
|
|
29
|
-
REFERENCES engine_algorithms (id) ON DELETE RESTRICT
|
|
30
|
-
) STRICT;
|
|
31
|
-
|
|
32
|
-
CREATE INDEX engine_states_due_idx
|
|
33
|
-
ON engine_states (algorithm_id, due_at, learning_item_id);
|
|
34
|
-
|
|
35
|
-
CREATE TABLE engine_reviews (
|
|
36
|
-
id INTEGER PRIMARY KEY,
|
|
37
|
-
learning_item_id INTEGER NOT NULL,
|
|
38
|
-
algorithm_id INTEGER NOT NULL,
|
|
39
|
-
rating TEXT NOT NULL CHECK (length(trim(rating)) > 0),
|
|
40
|
-
review_json TEXT NOT NULL CHECK (json_valid(review_json)),
|
|
41
|
-
response_time_ms INTEGER CHECK (
|
|
42
|
-
response_time_ms IS NULL OR response_time_ms >= 0
|
|
43
|
-
),
|
|
44
|
-
reviewed_at TEXT NOT NULL,
|
|
45
|
-
FOREIGN KEY (learning_item_id)
|
|
46
|
-
REFERENCES engine_items (id) ON DELETE CASCADE,
|
|
47
|
-
FOREIGN KEY (algorithm_id)
|
|
48
|
-
REFERENCES engine_algorithms (id) ON DELETE RESTRICT
|
|
49
|
-
) STRICT;
|
|
50
|
-
|
|
51
|
-
CREATE INDEX engine_reviews_item_time_idx
|
|
52
|
-
ON engine_reviews (learning_item_id, reviewed_at DESC, id DESC);
|
|
5
|
+
sql: `
|
|
6
|
+
CREATE TABLE engine_items (
|
|
7
|
+
id INTEGER PRIMARY KEY,
|
|
8
|
+
data_json TEXT NOT NULL CHECK (json_valid(data_json))
|
|
9
|
+
) STRICT;
|
|
10
|
+
|
|
11
|
+
CREATE TABLE engine_algorithms (
|
|
12
|
+
id INTEGER PRIMARY KEY,
|
|
13
|
+
name TEXT NOT NULL UNIQUE CHECK (length(trim(name)) > 0),
|
|
14
|
+
description TEXT,
|
|
15
|
+
version TEXT NOT NULL CHECK (length(trim(version)) > 0),
|
|
16
|
+
config_json TEXT NOT NULL CHECK (json_valid(config_json))
|
|
17
|
+
) STRICT;
|
|
18
|
+
|
|
19
|
+
CREATE TABLE engine_states (
|
|
20
|
+
id INTEGER PRIMARY KEY,
|
|
21
|
+
learning_item_id INTEGER NOT NULL,
|
|
22
|
+
algorithm_id INTEGER NOT NULL,
|
|
23
|
+
due_at TEXT NOT NULL,
|
|
24
|
+
state_json TEXT NOT NULL CHECK (json_valid(state_json)),
|
|
25
|
+
UNIQUE (learning_item_id, algorithm_id),
|
|
26
|
+
FOREIGN KEY (learning_item_id)
|
|
27
|
+
REFERENCES engine_items (id) ON DELETE CASCADE,
|
|
28
|
+
FOREIGN KEY (algorithm_id)
|
|
29
|
+
REFERENCES engine_algorithms (id) ON DELETE RESTRICT
|
|
30
|
+
) STRICT;
|
|
31
|
+
|
|
32
|
+
CREATE INDEX engine_states_due_idx
|
|
33
|
+
ON engine_states (algorithm_id, due_at, learning_item_id);
|
|
34
|
+
|
|
35
|
+
CREATE TABLE engine_reviews (
|
|
36
|
+
id INTEGER PRIMARY KEY,
|
|
37
|
+
learning_item_id INTEGER NOT NULL,
|
|
38
|
+
algorithm_id INTEGER NOT NULL,
|
|
39
|
+
rating TEXT NOT NULL CHECK (length(trim(rating)) > 0),
|
|
40
|
+
review_json TEXT NOT NULL CHECK (json_valid(review_json)),
|
|
41
|
+
response_time_ms INTEGER CHECK (
|
|
42
|
+
response_time_ms IS NULL OR response_time_ms >= 0
|
|
43
|
+
),
|
|
44
|
+
reviewed_at TEXT NOT NULL,
|
|
45
|
+
FOREIGN KEY (learning_item_id)
|
|
46
|
+
REFERENCES engine_items (id) ON DELETE CASCADE,
|
|
47
|
+
FOREIGN KEY (algorithm_id)
|
|
48
|
+
REFERENCES engine_algorithms (id) ON DELETE RESTRICT
|
|
49
|
+
) STRICT;
|
|
50
|
+
|
|
51
|
+
CREATE INDEX engine_reviews_item_time_idx
|
|
52
|
+
ON engine_reviews (learning_item_id, reviewed_at DESC, id DESC);
|
|
53
53
|
`,
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
56
|
version: 2,
|
|
57
57
|
name: "optimistic_review_concurrency",
|
|
58
|
-
sql: `
|
|
59
|
-
ALTER TABLE engine_states
|
|
60
|
-
ADD COLUMN revision INTEGER NOT NULL DEFAULT 0
|
|
61
|
-
CHECK (revision >= 0);
|
|
58
|
+
sql: `
|
|
59
|
+
ALTER TABLE engine_states
|
|
60
|
+
ADD COLUMN revision INTEGER NOT NULL DEFAULT 0
|
|
61
|
+
CHECK (revision >= 0);
|
|
62
62
|
`,
|
|
63
63
|
},
|
|
64
64
|
];
|
|
65
65
|
export function migrateDatabase(database) {
|
|
66
|
-
database.exec(`
|
|
67
|
-
CREATE TABLE IF NOT EXISTS engine_schema_migrations (
|
|
68
|
-
version INTEGER PRIMARY KEY,
|
|
69
|
-
name TEXT NOT NULL UNIQUE,
|
|
70
|
-
applied_at TEXT NOT NULL
|
|
71
|
-
) STRICT;
|
|
66
|
+
database.exec(`
|
|
67
|
+
CREATE TABLE IF NOT EXISTS engine_schema_migrations (
|
|
68
|
+
version INTEGER PRIMARY KEY,
|
|
69
|
+
name TEXT NOT NULL UNIQUE,
|
|
70
|
+
applied_at TEXT NOT NULL
|
|
71
|
+
) STRICT;
|
|
72
72
|
`);
|
|
73
73
|
const applied = new Set(database
|
|
74
74
|
.prepare("SELECT version FROM engine_schema_migrations")
|
|
75
75
|
.all().map((row) => row.version));
|
|
76
|
-
const record = database.prepare(`
|
|
77
|
-
INSERT INTO engine_schema_migrations (version, name, applied_at)
|
|
78
|
-
VALUES (?, ?, ?)
|
|
76
|
+
const record = database.prepare(`
|
|
77
|
+
INSERT INTO engine_schema_migrations (version, name, applied_at)
|
|
78
|
+
VALUES (?, ?, ?)
|
|
79
79
|
`);
|
|
80
80
|
for (const migration of migrations) {
|
|
81
81
|
if (applied.has(migration.version)) {
|
|
@@ -21,10 +21,10 @@ export class SqliteLearningStore {
|
|
|
21
21
|
requireNonBlank(registration.name, "algorithm name");
|
|
22
22
|
requireNonBlank(registration.version, "algorithm version");
|
|
23
23
|
const existing = this.#database
|
|
24
|
-
.prepare(`
|
|
25
|
-
SELECT id, name, version, description, config_json
|
|
26
|
-
FROM engine_algorithms
|
|
27
|
-
WHERE name = ?
|
|
24
|
+
.prepare(`
|
|
25
|
+
SELECT id, name, version, description, config_json
|
|
26
|
+
FROM engine_algorithms
|
|
27
|
+
WHERE name = ?
|
|
28
28
|
`)
|
|
29
29
|
.get(registration.name);
|
|
30
30
|
if (existing !== undefined) {
|
|
@@ -37,10 +37,10 @@ export class SqliteLearningStore {
|
|
|
37
37
|
return stored;
|
|
38
38
|
}
|
|
39
39
|
const result = this.#database
|
|
40
|
-
.prepare(`
|
|
41
|
-
INSERT INTO engine_algorithms
|
|
42
|
-
(name, version, description, config_json)
|
|
43
|
-
VALUES (?, ?, ?, ?)
|
|
40
|
+
.prepare(`
|
|
41
|
+
INSERT INTO engine_algorithms
|
|
42
|
+
(name, version, description, config_json)
|
|
43
|
+
VALUES (?, ?, ?, ?)
|
|
44
44
|
`)
|
|
45
45
|
.run(registration.name, registration.version, registration.description ?? null, JSON.stringify(registration.configuration));
|
|
46
46
|
return Object.freeze({
|
|
@@ -55,10 +55,10 @@ export class SqliteLearningStore {
|
|
|
55
55
|
.run(JSON.stringify(input.data));
|
|
56
56
|
const id = learningItemId(toSafeInteger(itemResult.lastInsertRowid));
|
|
57
57
|
this.#database
|
|
58
|
-
.prepare(`
|
|
59
|
-
INSERT INTO engine_states
|
|
60
|
-
(learning_item_id, algorithm_id, due_at, state_json)
|
|
61
|
-
VALUES (?, ?, ?, ?)
|
|
58
|
+
.prepare(`
|
|
59
|
+
INSERT INTO engine_states
|
|
60
|
+
(learning_item_id, algorithm_id, due_at, state_json)
|
|
61
|
+
VALUES (?, ?, ?, ?)
|
|
62
62
|
`)
|
|
63
63
|
.run(id, input.algorithmId, isoDate(input.dueAt, "initial due time"), JSON.stringify(input.stateData));
|
|
64
64
|
return freezeItem({ id, data: input.data });
|
|
@@ -66,11 +66,11 @@ export class SqliteLearningStore {
|
|
|
66
66
|
}
|
|
67
67
|
async findItem(id, registeredAlgorithmId) {
|
|
68
68
|
const row = this.#database
|
|
69
|
-
.prepare(`
|
|
70
|
-
SELECT item.id, item.data_json
|
|
71
|
-
FROM engine_items item
|
|
72
|
-
JOIN engine_states state ON state.learning_item_id = item.id
|
|
73
|
-
WHERE item.id = ? AND state.algorithm_id = ?
|
|
69
|
+
.prepare(`
|
|
70
|
+
SELECT item.id, item.data_json
|
|
71
|
+
FROM engine_items item
|
|
72
|
+
JOIN engine_states state ON state.learning_item_id = item.id
|
|
73
|
+
WHERE item.id = ? AND state.algorithm_id = ?
|
|
74
74
|
`)
|
|
75
75
|
.get(id, registeredAlgorithmId);
|
|
76
76
|
return row === undefined ? null : mapItem(row);
|
|
@@ -78,12 +78,12 @@ export class SqliteLearningStore {
|
|
|
78
78
|
async listItems(registeredAlgorithmId, query = {}) {
|
|
79
79
|
validatePage(query);
|
|
80
80
|
const parameters = [registeredAlgorithmId];
|
|
81
|
-
let sql = `
|
|
82
|
-
SELECT item.id, item.data_json
|
|
83
|
-
FROM engine_items item
|
|
84
|
-
JOIN engine_states state ON state.learning_item_id = item.id
|
|
85
|
-
WHERE state.algorithm_id = ?
|
|
86
|
-
ORDER BY item.id
|
|
81
|
+
let sql = `
|
|
82
|
+
SELECT item.id, item.data_json
|
|
83
|
+
FROM engine_items item
|
|
84
|
+
JOIN engine_states state ON state.learning_item_id = item.id
|
|
85
|
+
WHERE state.algorithm_id = ?
|
|
86
|
+
ORDER BY item.id
|
|
87
87
|
`;
|
|
88
88
|
if (query.limit !== undefined) {
|
|
89
89
|
sql += " LIMIT ?";
|
|
@@ -101,13 +101,13 @@ export class SqliteLearningStore {
|
|
|
101
101
|
}
|
|
102
102
|
async updateItem(id, registeredAlgorithmId, data) {
|
|
103
103
|
const result = this.#database
|
|
104
|
-
.prepare(`
|
|
105
|
-
UPDATE engine_items SET data_json = ?
|
|
106
|
-
WHERE id = ? AND EXISTS (
|
|
107
|
-
SELECT 1 FROM engine_states state
|
|
108
|
-
WHERE state.learning_item_id = engine_items.id
|
|
109
|
-
AND state.algorithm_id = ?
|
|
110
|
-
)
|
|
104
|
+
.prepare(`
|
|
105
|
+
UPDATE engine_items SET data_json = ?
|
|
106
|
+
WHERE id = ? AND EXISTS (
|
|
107
|
+
SELECT 1 FROM engine_states state
|
|
108
|
+
WHERE state.learning_item_id = engine_items.id
|
|
109
|
+
AND state.algorithm_id = ?
|
|
110
|
+
)
|
|
111
111
|
`)
|
|
112
112
|
.run(JSON.stringify(data), id, registeredAlgorithmId);
|
|
113
113
|
return toSafeInteger(result.changes) === 0
|
|
@@ -116,23 +116,23 @@ export class SqliteLearningStore {
|
|
|
116
116
|
}
|
|
117
117
|
async deleteItem(id, registeredAlgorithmId) {
|
|
118
118
|
const result = this.#database
|
|
119
|
-
.prepare(`
|
|
120
|
-
DELETE FROM engine_items
|
|
121
|
-
WHERE id = ? AND EXISTS (
|
|
122
|
-
SELECT 1 FROM engine_states state
|
|
123
|
-
WHERE state.learning_item_id = engine_items.id
|
|
124
|
-
AND state.algorithm_id = ?
|
|
125
|
-
)
|
|
119
|
+
.prepare(`
|
|
120
|
+
DELETE FROM engine_items
|
|
121
|
+
WHERE id = ? AND EXISTS (
|
|
122
|
+
SELECT 1 FROM engine_states state
|
|
123
|
+
WHERE state.learning_item_id = engine_items.id
|
|
124
|
+
AND state.algorithm_id = ?
|
|
125
|
+
)
|
|
126
126
|
`)
|
|
127
127
|
.run(id, registeredAlgorithmId);
|
|
128
128
|
return toSafeInteger(result.changes) > 0;
|
|
129
129
|
}
|
|
130
130
|
async findState(itemId, registeredAlgorithmId) {
|
|
131
131
|
const row = this.#database
|
|
132
|
-
.prepare(`
|
|
133
|
-
SELECT id, learning_item_id, algorithm_id, revision, due_at, state_json
|
|
134
|
-
FROM engine_states
|
|
135
|
-
WHERE learning_item_id = ? AND algorithm_id = ?
|
|
132
|
+
.prepare(`
|
|
133
|
+
SELECT id, learning_item_id, algorithm_id, revision, due_at, state_json
|
|
134
|
+
FROM engine_states
|
|
135
|
+
WHERE learning_item_id = ? AND algorithm_id = ?
|
|
136
136
|
`)
|
|
137
137
|
.get(itemId, registeredAlgorithmId);
|
|
138
138
|
return row === undefined ? null : mapState(row);
|
|
@@ -143,19 +143,19 @@ export class SqliteLearningStore {
|
|
|
143
143
|
input.algorithmId,
|
|
144
144
|
isoDate(input.dueAtOrBefore, "due query time"),
|
|
145
145
|
];
|
|
146
|
-
let sql = `
|
|
147
|
-
SELECT
|
|
148
|
-
item.id AS item_id,
|
|
149
|
-
item.data_json,
|
|
150
|
-
state.id AS state_id,
|
|
151
|
-
state.algorithm_id,
|
|
152
|
-
state.revision,
|
|
153
|
-
state.due_at,
|
|
154
|
-
state.state_json
|
|
155
|
-
FROM engine_states state
|
|
156
|
-
JOIN engine_items item ON item.id = state.learning_item_id
|
|
157
|
-
WHERE state.algorithm_id = ? AND state.due_at <= ?
|
|
158
|
-
ORDER BY state.due_at, item.id
|
|
146
|
+
let sql = `
|
|
147
|
+
SELECT
|
|
148
|
+
item.id AS item_id,
|
|
149
|
+
item.data_json,
|
|
150
|
+
state.id AS state_id,
|
|
151
|
+
state.algorithm_id,
|
|
152
|
+
state.revision,
|
|
153
|
+
state.due_at,
|
|
154
|
+
state.state_json
|
|
155
|
+
FROM engine_states state
|
|
156
|
+
JOIN engine_items item ON item.id = state.learning_item_id
|
|
157
|
+
WHERE state.algorithm_id = ? AND state.due_at <= ?
|
|
158
|
+
ORDER BY state.due_at, item.id
|
|
159
159
|
`;
|
|
160
160
|
if (input.limit !== undefined) {
|
|
161
161
|
sql += " LIMIT ?";
|
|
@@ -184,10 +184,10 @@ export class SqliteLearningStore {
|
|
|
184
184
|
throw new TypeError("Review identity must match the state being updated.");
|
|
185
185
|
}
|
|
186
186
|
const update = this.#database
|
|
187
|
-
.prepare(`
|
|
188
|
-
UPDATE engine_states
|
|
189
|
-
SET due_at = ?, state_json = ?, revision = revision + 1
|
|
190
|
-
WHERE id = ? AND revision = ?
|
|
187
|
+
.prepare(`
|
|
188
|
+
UPDATE engine_states
|
|
189
|
+
SET due_at = ?, state_json = ?, revision = revision + 1
|
|
190
|
+
WHERE id = ? AND revision = ?
|
|
191
191
|
`)
|
|
192
192
|
.run(isoDate(input.state.dueAt, "next due time"), JSON.stringify(input.state.data), input.state.state.id, input.state.state.revision);
|
|
193
193
|
if (toSafeInteger(update.changes) !== 1) {
|
|
@@ -196,13 +196,13 @@ export class SqliteLearningStore {
|
|
|
196
196
|
const review = input.review;
|
|
197
197
|
validateResponseTime(review.responseTimeMs);
|
|
198
198
|
const result = this.#database
|
|
199
|
-
.prepare(`
|
|
200
|
-
INSERT INTO engine_reviews
|
|
201
|
-
(
|
|
202
|
-
learning_item_id, algorithm_id, rating, review_json,
|
|
203
|
-
response_time_ms, reviewed_at
|
|
204
|
-
)
|
|
205
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
199
|
+
.prepare(`
|
|
200
|
+
INSERT INTO engine_reviews
|
|
201
|
+
(
|
|
202
|
+
learning_item_id, algorithm_id, rating, review_json,
|
|
203
|
+
response_time_ms, reviewed_at
|
|
204
|
+
)
|
|
205
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
206
206
|
`)
|
|
207
207
|
.run(review.learningItemId, review.algorithmId, requireNonBlank(review.rating, "rating"), JSON.stringify(review.data), review.responseTimeMs ?? null, isoDate(review.reviewedAt, "review time"));
|
|
208
208
|
return freezeReview({
|
|
@@ -214,13 +214,13 @@ export class SqliteLearningStore {
|
|
|
214
214
|
async listReviews(itemId, registeredAlgorithmId, query = {}) {
|
|
215
215
|
validatePage(query);
|
|
216
216
|
const parameters = [itemId, registeredAlgorithmId];
|
|
217
|
-
let sql = `
|
|
218
|
-
SELECT
|
|
219
|
-
id, learning_item_id, algorithm_id, rating, review_json,
|
|
220
|
-
response_time_ms, reviewed_at
|
|
221
|
-
FROM engine_reviews
|
|
222
|
-
WHERE learning_item_id = ? AND algorithm_id = ?
|
|
223
|
-
ORDER BY reviewed_at DESC, id DESC
|
|
217
|
+
let sql = `
|
|
218
|
+
SELECT
|
|
219
|
+
id, learning_item_id, algorithm_id, rating, review_json,
|
|
220
|
+
response_time_ms, reviewed_at
|
|
221
|
+
FROM engine_reviews
|
|
222
|
+
WHERE learning_item_id = ? AND algorithm_id = ?
|
|
223
|
+
ORDER BY reviewed_at DESC, id DESC
|
|
224
224
|
`;
|
|
225
225
|
if (query.limit !== undefined) {
|
|
226
226
|
sql += " LIMIT ?";
|
package/package.json
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@alstate/sqlite",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "SQLite persistence adapter for Alstate.",
|
|
5
|
-
"keywords": ["learning", "scheduling", "sqlite", "spaced-repetition"],
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "./dist/index.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"types": "./dist/index.d.ts",
|
|
12
|
-
"import": "./dist/index.js"
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"files": ["dist", "README.md", "LICENSE"],
|
|
16
|
-
"sideEffects": false,
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "tsc --project tsconfig.build.json",
|
|
19
|
-
"clean": "node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true }); require('node:fs').rmSync('.test-dist', { recursive: true, force: true })\"",
|
|
20
|
-
"test": "tsc --project tsconfig.test.json && node --test \".test-dist/test/**/*.test.js\"",
|
|
21
|
-
"typecheck": "tsc --project tsconfig.json --noEmit"
|
|
22
|
-
},
|
|
23
|
-
"dependencies": { "@alstate/core": "0.1.
|
|
24
|
-
"engines": { "node": ">=22.13.0" },
|
|
25
|
-
"license": "MIT",
|
|
26
|
-
"repository": {
|
|
27
|
-
"type": "git",
|
|
28
|
-
"url": "git+https://github.com/laiqfun/alstate.git",
|
|
29
|
-
"directory": "packages/sqlite"
|
|
30
|
-
},
|
|
31
|
-
"homepage": "https://github.com/laiqfun/alstate#readme",
|
|
32
|
-
"bugs": { "url": "https://github.com/laiqfun/alstate/issues" },
|
|
33
|
-
"publishConfig": { "access": "public" }
|
|
34
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@alstate/sqlite",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "SQLite persistence adapter for Alstate.",
|
|
5
|
+
"keywords": ["learning", "scheduling", "sqlite", "spaced-repetition"],
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc --project tsconfig.build.json",
|
|
19
|
+
"clean": "node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true }); require('node:fs').rmSync('.test-dist', { recursive: true, force: true })\"",
|
|
20
|
+
"test": "tsc --project tsconfig.test.json && node --test \".test-dist/test/**/*.test.js\"",
|
|
21
|
+
"typecheck": "tsc --project tsconfig.json --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": { "@alstate/core": "0.1.1" },
|
|
24
|
+
"engines": { "node": ">=22.13.0" },
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/laiqfun/alstate.git",
|
|
29
|
+
"directory": "packages/sqlite"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/laiqfun/alstate#readme",
|
|
32
|
+
"bugs": { "url": "https://github.com/laiqfun/alstate/issues" },
|
|
33
|
+
"publishConfig": { "access": "public" }
|
|
34
|
+
}
|