chishiki-sqlite 0.8.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/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/index.mjs +1 -0
- package/dist/lib/activities.js +210 -0
- package/dist/lib/actors.js +147 -0
- package/dist/lib/database-manager.js +70 -0
- package/dist/lib/database-reset.js +29 -0
- package/dist/lib/generated-activities.js +240 -0
- package/dist/lib/index.js +13 -0
- package/dist/lib/learning-content.js +230 -0
- package/dist/lib/manual.js +30 -0
- package/dist/lib/statements.js +342 -0
- package/dist/lib/syllst-importer.js +234 -0
- package/dist/lib/types.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Seo Jong Hak
|
|
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
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# SQLite Package
|
|
2
|
+
|
|
3
|
+
This package provides SQLite with OPFS (Origin Private File System) support for the Chrome extension.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- SQLite database with OPFS persistence
|
|
8
|
+
- Worker-based implementation for OPFS VFS
|
|
9
|
+
- React hooks for easy integration
|
|
10
|
+
- Client API for database operations
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
### In a React Component
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { useSqliteOpfs } from '@extension/sqlite';
|
|
18
|
+
|
|
19
|
+
const MyComponent = () => {
|
|
20
|
+
const {
|
|
21
|
+
isReady,
|
|
22
|
+
db,
|
|
23
|
+
exec,
|
|
24
|
+
openDatabase,
|
|
25
|
+
closeDatabase,
|
|
26
|
+
} = useSqliteOpfs();
|
|
27
|
+
|
|
28
|
+
const handleQuery = async () => {
|
|
29
|
+
const result = await exec('SELECT * FROM table;');
|
|
30
|
+
console.log(result);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div>
|
|
35
|
+
{isReady && <button onClick={handleQuery}>Run Query</button>}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Direct Client Usage
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { SqliteWorkerClient } from '@extension/sqlite';
|
|
45
|
+
|
|
46
|
+
const client = new SqliteWorkerClient();
|
|
47
|
+
await client.init();
|
|
48
|
+
await client.open('mydb.sqlite3');
|
|
49
|
+
const result = await client.exec('SELECT * FROM table;');
|
|
50
|
+
await client.close();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Requirements
|
|
54
|
+
|
|
55
|
+
- OPFS support (Chrome 109+, Edge 109+)
|
|
56
|
+
- Background service worker (for worker context)
|
|
57
|
+
- Storage permissions in manifest
|
|
58
|
+
|
|
59
|
+
## API
|
|
60
|
+
|
|
61
|
+
### useSqliteOpfs()
|
|
62
|
+
|
|
63
|
+
React hook for SQLite operations.
|
|
64
|
+
|
|
65
|
+
**Returns:**
|
|
66
|
+
- `isReady`: boolean - WASM initialized
|
|
67
|
+
- `db`: boolean - Database is open
|
|
68
|
+
- `dbFilename`: string - Current database filename
|
|
69
|
+
- `opfsFiles`: Array - List of OPFS files
|
|
70
|
+
- `opfsSupported`: boolean - OPFS available
|
|
71
|
+
- `persistent`: boolean - Using persistent storage
|
|
72
|
+
- `storage`: 'opfs' | 'jsstorage' | 'memory' - Storage type
|
|
73
|
+
- `openDatabase()`: Function - Open database
|
|
74
|
+
- `closeDatabase()`: Function - Close database
|
|
75
|
+
- `exec()`: Function - Execute SQL
|
|
76
|
+
- `runSampleSchema()`: Function - Seed sample data
|
|
77
|
+
- `listOpfsFiles()`: Function - List OPFS files
|
|
78
|
+
- `deleteOpfsFile()`: Function - Delete file
|
|
79
|
+
|
|
80
|
+
### SqliteWorkerClient
|
|
81
|
+
|
|
82
|
+
Direct client for SQLite operations.
|
|
83
|
+
|
|
84
|
+
**Methods:**
|
|
85
|
+
- `init()`: Initialize SQLite
|
|
86
|
+
- `open(filename)`: Open database
|
|
87
|
+
- `close()`: Close database
|
|
88
|
+
- `exec(sql)`: Execute SQL
|
|
89
|
+
- `seed()`: Seed sample schema
|
|
90
|
+
- `flush()`: Flush to disk
|
|
91
|
+
- `listOpfs()`: List OPFS files
|
|
92
|
+
- `deleteOpfs(filename)`: Delete file
|
|
93
|
+
- `dispose()`: Cleanup
|
|
94
|
+
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/index.js';
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { getDatabaseManager } from './database-manager.js';
|
|
2
|
+
import { ManualSqliteClient } from './manual.js';
|
|
3
|
+
const getDb = () => {
|
|
4
|
+
const client = ManualSqliteClient.getInstance();
|
|
5
|
+
return client.getDb();
|
|
6
|
+
};
|
|
7
|
+
const ensureDatabaseInitialized = async () => {
|
|
8
|
+
const dbManager = getDatabaseManager();
|
|
9
|
+
await dbManager.ensureInitialized();
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the activities table
|
|
13
|
+
*/
|
|
14
|
+
const initializeActivitiesTable = async () => {
|
|
15
|
+
const db = getDb();
|
|
16
|
+
if (!db)
|
|
17
|
+
return;
|
|
18
|
+
const tableExists = await db.introspection
|
|
19
|
+
.getTables()
|
|
20
|
+
.then((tables) => tables.some((table) => table.name === 'activities'));
|
|
21
|
+
if (!tableExists) {
|
|
22
|
+
await db.schema
|
|
23
|
+
.createTable('activities')
|
|
24
|
+
.ifNotExists()
|
|
25
|
+
.addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
|
|
26
|
+
.addColumn('activity_iri', 'text', col => col.notNull().unique())
|
|
27
|
+
.addColumn('name', 'text', col => col.notNull())
|
|
28
|
+
.addColumn('description', 'text')
|
|
29
|
+
.addColumn('activity_type', 'text', col => col.notNull())
|
|
30
|
+
.addColumn('content_id', 'integer')
|
|
31
|
+
.addColumn('created_at', 'text', col => col.notNull())
|
|
32
|
+
.execute();
|
|
33
|
+
// Create indexes
|
|
34
|
+
await db.schema.createIndex('idx_activities_iri').ifNotExists().on('activities').column('activity_iri').execute();
|
|
35
|
+
await db.schema.createIndex('idx_activities_content').ifNotExists().on('activities').column('content_id').execute();
|
|
36
|
+
await db.schema.createIndex('idx_activities_type').ifNotExists().on('activities').column('activity_type').execute();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Generate a unique activity IRI
|
|
41
|
+
*/
|
|
42
|
+
const generateActivityIri = (type, id) => {
|
|
43
|
+
const timestamp = Date.now();
|
|
44
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
45
|
+
const suffix = id || `${timestamp}-${random}`;
|
|
46
|
+
return `https://chishiki.app/activities/${type}/${suffix}`;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Create a new activity
|
|
50
|
+
*/
|
|
51
|
+
const createActivity = async (activity) => {
|
|
52
|
+
await ensureDatabaseInitialized();
|
|
53
|
+
const db = getDb();
|
|
54
|
+
if (!db) {
|
|
55
|
+
throw new Error('Database not available');
|
|
56
|
+
}
|
|
57
|
+
const now = new Date().toISOString();
|
|
58
|
+
const iri = activity.activity_iri || generateActivityIri(activity.activity_type);
|
|
59
|
+
const insertValues = {
|
|
60
|
+
activity_iri: iri,
|
|
61
|
+
name: activity.name,
|
|
62
|
+
description: activity.description ?? null,
|
|
63
|
+
activity_type: activity.activity_type,
|
|
64
|
+
content_id: activity.content_id ?? null,
|
|
65
|
+
created_at: now,
|
|
66
|
+
};
|
|
67
|
+
const result = await db.insertInto('activities').values(insertValues).returningAll().executeTakeFirstOrThrow();
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Get activity by ID
|
|
72
|
+
*/
|
|
73
|
+
const getActivityById = async (id) => {
|
|
74
|
+
await ensureDatabaseInitialized();
|
|
75
|
+
const db = getDb();
|
|
76
|
+
if (!db)
|
|
77
|
+
return null;
|
|
78
|
+
const result = await db.selectFrom('activities').selectAll().where('id', '=', id).executeTakeFirst();
|
|
79
|
+
return result || null;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Get activity by IRI
|
|
83
|
+
*/
|
|
84
|
+
const getActivityByIri = async (iri) => {
|
|
85
|
+
await ensureDatabaseInitialized();
|
|
86
|
+
const db = getDb();
|
|
87
|
+
if (!db)
|
|
88
|
+
return null;
|
|
89
|
+
const result = await db.selectFrom('activities').selectAll().where('activity_iri', '=', iri).executeTakeFirst();
|
|
90
|
+
return result || null;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Get or create an activity by IRI
|
|
94
|
+
*/
|
|
95
|
+
const getOrCreateActivity = async (activity) => {
|
|
96
|
+
const existing = await getActivityByIri(activity.activity_iri);
|
|
97
|
+
if (existing) {
|
|
98
|
+
return existing;
|
|
99
|
+
}
|
|
100
|
+
return createActivity(activity);
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Get activities for a content item
|
|
104
|
+
*/
|
|
105
|
+
const getActivitiesByContentId = async (contentId) => {
|
|
106
|
+
await ensureDatabaseInitialized();
|
|
107
|
+
const db = getDb();
|
|
108
|
+
if (!db)
|
|
109
|
+
return [];
|
|
110
|
+
const results = await db
|
|
111
|
+
.selectFrom('activities')
|
|
112
|
+
.selectAll()
|
|
113
|
+
.where('content_id', '=', contentId)
|
|
114
|
+
.orderBy('created_at', 'desc')
|
|
115
|
+
.execute();
|
|
116
|
+
return results;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Get activities by type
|
|
120
|
+
*/
|
|
121
|
+
const getActivitiesByType = async (activityType) => {
|
|
122
|
+
await ensureDatabaseInitialized();
|
|
123
|
+
const db = getDb();
|
|
124
|
+
if (!db)
|
|
125
|
+
return [];
|
|
126
|
+
const results = await db
|
|
127
|
+
.selectFrom('activities')
|
|
128
|
+
.selectAll()
|
|
129
|
+
.where('activity_type', '=', activityType)
|
|
130
|
+
.orderBy('created_at', 'desc')
|
|
131
|
+
.execute();
|
|
132
|
+
return results;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Get all activities with pagination
|
|
136
|
+
*/
|
|
137
|
+
const getActivities = async (page = 1, limit = 20) => {
|
|
138
|
+
await ensureDatabaseInitialized();
|
|
139
|
+
const db = getDb();
|
|
140
|
+
if (!db)
|
|
141
|
+
return [];
|
|
142
|
+
const results = await db
|
|
143
|
+
.selectFrom('activities')
|
|
144
|
+
.selectAll()
|
|
145
|
+
.orderBy('created_at', 'desc')
|
|
146
|
+
.offset((page - 1) * limit)
|
|
147
|
+
.limit(limit)
|
|
148
|
+
.execute();
|
|
149
|
+
return results;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Get activity count
|
|
153
|
+
*/
|
|
154
|
+
const getActivityCount = async () => {
|
|
155
|
+
await ensureDatabaseInitialized();
|
|
156
|
+
const db = getDb();
|
|
157
|
+
if (!db)
|
|
158
|
+
return 0;
|
|
159
|
+
const result = await db.selectFrom('activities').select(db.fn.count('id').as('count')).executeTakeFirst();
|
|
160
|
+
return result?.count || 0;
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Update an activity
|
|
164
|
+
*/
|
|
165
|
+
const updateActivity = async (id, updates) => {
|
|
166
|
+
await ensureDatabaseInitialized();
|
|
167
|
+
const db = getDb();
|
|
168
|
+
if (!db)
|
|
169
|
+
return;
|
|
170
|
+
const updateValues = {};
|
|
171
|
+
if (updates.name !== undefined)
|
|
172
|
+
updateValues.name = updates.name;
|
|
173
|
+
if (updates.description !== undefined)
|
|
174
|
+
updateValues.description = updates.description;
|
|
175
|
+
if (Object.keys(updateValues).length > 0) {
|
|
176
|
+
await db.updateTable('activities').set(updateValues).where('id', '=', id).execute();
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Delete an activity
|
|
181
|
+
*/
|
|
182
|
+
const deleteActivity = async (id) => {
|
|
183
|
+
await ensureDatabaseInitialized();
|
|
184
|
+
const db = getDb();
|
|
185
|
+
if (!db)
|
|
186
|
+
return;
|
|
187
|
+
await db.deleteFrom('activities').where('id', '=', id).execute();
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Delete activities by content ID
|
|
191
|
+
*/
|
|
192
|
+
const deleteActivitiesByContentId = async (contentId) => {
|
|
193
|
+
await ensureDatabaseInitialized();
|
|
194
|
+
const db = getDb();
|
|
195
|
+
if (!db)
|
|
196
|
+
return;
|
|
197
|
+
await db.deleteFrom('activities').where('content_id', '=', contentId).execute();
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* Reset the activities table
|
|
201
|
+
*/
|
|
202
|
+
const resetActivitiesTable = async () => {
|
|
203
|
+
await ensureDatabaseInitialized();
|
|
204
|
+
const db = getDb();
|
|
205
|
+
if (!db)
|
|
206
|
+
return;
|
|
207
|
+
await db.schema.dropTable('activities').ifExists().execute();
|
|
208
|
+
await initializeActivitiesTable();
|
|
209
|
+
};
|
|
210
|
+
export { initializeActivitiesTable, generateActivityIri, createActivity, getActivityById, getActivityByIri, getOrCreateActivity, getActivitiesByContentId, getActivitiesByType, getActivities, getActivityCount, updateActivity, deleteActivity, deleteActivitiesByContentId, resetActivitiesTable, };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { getDatabaseManager } from './database-manager.js';
|
|
2
|
+
import { ManualSqliteClient } from './manual.js';
|
|
3
|
+
const getDb = () => {
|
|
4
|
+
const client = ManualSqliteClient.getInstance();
|
|
5
|
+
return client.getDb();
|
|
6
|
+
};
|
|
7
|
+
const ensureDatabaseInitialized = async () => {
|
|
8
|
+
const dbManager = getDatabaseManager();
|
|
9
|
+
await dbManager.ensureInitialized();
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Initialize the actors table
|
|
13
|
+
*/
|
|
14
|
+
const initializeActorsTable = async () => {
|
|
15
|
+
const db = getDb();
|
|
16
|
+
if (!db)
|
|
17
|
+
return;
|
|
18
|
+
const tableExists = await db.introspection
|
|
19
|
+
.getTables()
|
|
20
|
+
.then((tables) => tables.some((table) => table.name === 'actors'));
|
|
21
|
+
if (!tableExists) {
|
|
22
|
+
await db.schema
|
|
23
|
+
.createTable('actors')
|
|
24
|
+
.ifNotExists()
|
|
25
|
+
.addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
|
|
26
|
+
.addColumn('mbox', 'text', col => col.notNull().unique())
|
|
27
|
+
.addColumn('name', 'text', col => col.notNull())
|
|
28
|
+
.addColumn('created_at', 'text', col => col.notNull())
|
|
29
|
+
.execute();
|
|
30
|
+
// Create index on mbox for faster lookups
|
|
31
|
+
await db.schema.createIndex('idx_actors_mbox').ifNotExists().on('actors').column('mbox').execute();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Get or create the default local actor
|
|
36
|
+
*/
|
|
37
|
+
const getOrCreateDefaultActor = async () => {
|
|
38
|
+
await ensureDatabaseInitialized();
|
|
39
|
+
const db = getDb();
|
|
40
|
+
if (!db) {
|
|
41
|
+
throw new Error('Database not available');
|
|
42
|
+
}
|
|
43
|
+
const defaultMbox = 'mailto:local@chishiki.app';
|
|
44
|
+
const defaultName = 'Local User';
|
|
45
|
+
// Try to find existing default actor
|
|
46
|
+
const existing = await db.selectFrom('actors').selectAll().where('mbox', '=', defaultMbox).executeTakeFirst();
|
|
47
|
+
if (existing) {
|
|
48
|
+
return existing;
|
|
49
|
+
}
|
|
50
|
+
// Create default actor
|
|
51
|
+
const now = new Date().toISOString();
|
|
52
|
+
const result = await db
|
|
53
|
+
.insertInto('actors')
|
|
54
|
+
.values({
|
|
55
|
+
mbox: defaultMbox,
|
|
56
|
+
name: defaultName,
|
|
57
|
+
created_at: now,
|
|
58
|
+
})
|
|
59
|
+
.returningAll()
|
|
60
|
+
.executeTakeFirstOrThrow();
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Get actor by ID
|
|
65
|
+
*/
|
|
66
|
+
const getActorById = async (id) => {
|
|
67
|
+
await ensureDatabaseInitialized();
|
|
68
|
+
const db = getDb();
|
|
69
|
+
if (!db)
|
|
70
|
+
return null;
|
|
71
|
+
const result = await db.selectFrom('actors').selectAll().where('id', '=', id).executeTakeFirst();
|
|
72
|
+
return result || null;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Get actor by mbox (email identifier)
|
|
76
|
+
*/
|
|
77
|
+
const getActorByMbox = async (mbox) => {
|
|
78
|
+
await ensureDatabaseInitialized();
|
|
79
|
+
const db = getDb();
|
|
80
|
+
if (!db)
|
|
81
|
+
return null;
|
|
82
|
+
const result = await db.selectFrom('actors').selectAll().where('mbox', '=', mbox).executeTakeFirst();
|
|
83
|
+
return result || null;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Create a new actor
|
|
87
|
+
*/
|
|
88
|
+
const createActor = async (actor) => {
|
|
89
|
+
await ensureDatabaseInitialized();
|
|
90
|
+
const db = getDb();
|
|
91
|
+
if (!db) {
|
|
92
|
+
throw new Error('Database not available');
|
|
93
|
+
}
|
|
94
|
+
const now = new Date().toISOString();
|
|
95
|
+
const result = await db
|
|
96
|
+
.insertInto('actors')
|
|
97
|
+
.values({
|
|
98
|
+
...actor,
|
|
99
|
+
created_at: now,
|
|
100
|
+
})
|
|
101
|
+
.returningAll()
|
|
102
|
+
.executeTakeFirstOrThrow();
|
|
103
|
+
return result;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Update an actor's name
|
|
107
|
+
*/
|
|
108
|
+
const updateActorName = async (id, name) => {
|
|
109
|
+
await ensureDatabaseInitialized();
|
|
110
|
+
const db = getDb();
|
|
111
|
+
if (!db)
|
|
112
|
+
return;
|
|
113
|
+
await db.updateTable('actors').set({ name }).where('id', '=', id).execute();
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Get all actors
|
|
117
|
+
*/
|
|
118
|
+
const getAllActors = async () => {
|
|
119
|
+
await ensureDatabaseInitialized();
|
|
120
|
+
const db = getDb();
|
|
121
|
+
if (!db)
|
|
122
|
+
return [];
|
|
123
|
+
const results = await db.selectFrom('actors').selectAll().orderBy('created_at', 'desc').execute();
|
|
124
|
+
return results;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Delete an actor (use with caution - will orphan statements)
|
|
128
|
+
*/
|
|
129
|
+
const deleteActor = async (id) => {
|
|
130
|
+
await ensureDatabaseInitialized();
|
|
131
|
+
const db = getDb();
|
|
132
|
+
if (!db)
|
|
133
|
+
return;
|
|
134
|
+
await db.deleteFrom('actors').where('id', '=', id).execute();
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Reset the actors table (for development/testing)
|
|
138
|
+
*/
|
|
139
|
+
const resetActorsTable = async () => {
|
|
140
|
+
await ensureDatabaseInitialized();
|
|
141
|
+
const db = getDb();
|
|
142
|
+
if (!db)
|
|
143
|
+
return;
|
|
144
|
+
await db.schema.dropTable('actors').ifExists().execute();
|
|
145
|
+
await initializeActorsTable();
|
|
146
|
+
};
|
|
147
|
+
export { initializeActorsTable, getOrCreateDefaultActor, getActorById, getActorByMbox, createActor, updateActorName, getAllActors, deleteActor, resetActorsTable, };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Database Manager - Singleton for centralized database initialization and management
|
|
2
|
+
import { initializeActivitiesTable } from './activities.js';
|
|
3
|
+
import { initializeActorsTable } from './actors.js';
|
|
4
|
+
import { initializeGeneratedActivitiesTable } from './generated-activities.js';
|
|
5
|
+
import { initializeLearningContentTable } from './learning-content.js';
|
|
6
|
+
import { ManualSqliteClient } from './manual.js';
|
|
7
|
+
import { initializeStatementsTable } from './statements.js';
|
|
8
|
+
export class DatabaseManager {
|
|
9
|
+
static instance = null;
|
|
10
|
+
initializationPromise = null;
|
|
11
|
+
isInitialized = false;
|
|
12
|
+
constructor() { }
|
|
13
|
+
static getInstance() {
|
|
14
|
+
if (!DatabaseManager.instance) {
|
|
15
|
+
DatabaseManager.instance = new DatabaseManager();
|
|
16
|
+
}
|
|
17
|
+
return DatabaseManager.instance;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Ensures the database is initialized. Returns a promise that resolves when initialization is complete.
|
|
21
|
+
* This method is idempotent - multiple calls will return the same promise.
|
|
22
|
+
*/
|
|
23
|
+
async ensureInitialized() {
|
|
24
|
+
if (this.isInitialized) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!this.initializationPromise) {
|
|
28
|
+
this.initializationPromise = this.initialize();
|
|
29
|
+
}
|
|
30
|
+
await this.initializationPromise;
|
|
31
|
+
this.isInitialized = true;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Performs the actual database initialization
|
|
35
|
+
*/
|
|
36
|
+
async initialize() {
|
|
37
|
+
try {
|
|
38
|
+
console.log('Initializing database...');
|
|
39
|
+
// Initialize all LRS database tables in parallel
|
|
40
|
+
await Promise.all([
|
|
41
|
+
initializeActorsTable(),
|
|
42
|
+
initializeLearningContentTable(),
|
|
43
|
+
initializeActivitiesTable(),
|
|
44
|
+
initializeStatementsTable(),
|
|
45
|
+
initializeGeneratedActivitiesTable(),
|
|
46
|
+
]);
|
|
47
|
+
console.log('Database initialization completed');
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error('Database initialization failed:', error);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resets the initialization state (useful for testing or reinitialization)
|
|
56
|
+
*/
|
|
57
|
+
reset() {
|
|
58
|
+
this.isInitialized = false;
|
|
59
|
+
this.initializationPromise = null;
|
|
60
|
+
ManualSqliteClient.resetInstance();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Checks if the database is currently initialized
|
|
64
|
+
*/
|
|
65
|
+
get initialized() {
|
|
66
|
+
return this.isInitialized;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Export a convenience function for easy access
|
|
70
|
+
export const getDatabaseManager = () => DatabaseManager.getInstance();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { resetActivitiesTable } from './activities.js';
|
|
2
|
+
import { resetActorsTable } from './actors.js';
|
|
3
|
+
import { getDatabaseManager } from './database-manager.js';
|
|
4
|
+
import { resetGeneratedActivitiesTable } from './generated-activities.js';
|
|
5
|
+
import { resetLearningContentTable } from './learning-content.js';
|
|
6
|
+
import { resetStatementsTable } from './statements.js';
|
|
7
|
+
/**
|
|
8
|
+
* Completely resets the entire database by dropping and recreating all tables
|
|
9
|
+
* This will delete ALL data in the database
|
|
10
|
+
*/
|
|
11
|
+
export const resetEntireDatabase = async () => {
|
|
12
|
+
console.log('Resetting entire database...');
|
|
13
|
+
try {
|
|
14
|
+
// Reset all LRS tables in order (statements first due to foreign keys)
|
|
15
|
+
await resetStatementsTable();
|
|
16
|
+
await resetGeneratedActivitiesTable();
|
|
17
|
+
await resetActivitiesTable();
|
|
18
|
+
await resetLearningContentTable();
|
|
19
|
+
await resetActorsTable();
|
|
20
|
+
// Reset the database manager singleton state
|
|
21
|
+
const dbManager = getDatabaseManager();
|
|
22
|
+
dbManager.reset();
|
|
23
|
+
console.log('Database reset completed successfully');
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.error('Error resetting database:', error);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
};
|