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.
@@ -0,0 +1,240 @@
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 generated_activities table
13
+ */
14
+ const initializeGeneratedActivitiesTable = 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 === 'generated_activities'));
21
+ if (!tableExists) {
22
+ await db.schema
23
+ .createTable('generated_activities')
24
+ .ifNotExists()
25
+ .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
26
+ .addColumn('content_id', 'integer', col => col.notNull())
27
+ .addColumn('activity_type', 'text', col => col.notNull())
28
+ .addColumn('activity_data', 'text', col => col.notNull())
29
+ .addColumn('generation_prompt', 'text')
30
+ .addColumn('created_at', 'text', col => col.notNull())
31
+ .execute();
32
+ // Create indexes
33
+ await db.schema
34
+ .createIndex('idx_generated_activities_content')
35
+ .ifNotExists()
36
+ .on('generated_activities')
37
+ .column('content_id')
38
+ .execute();
39
+ await db.schema
40
+ .createIndex('idx_generated_activities_type')
41
+ .ifNotExists()
42
+ .on('generated_activities')
43
+ .column('activity_type')
44
+ .execute();
45
+ }
46
+ };
47
+ /**
48
+ * Store a generated activity (flashcards, quiz, etc.)
49
+ */
50
+ const storeGeneratedActivity = async (activity) => {
51
+ await ensureDatabaseInitialized();
52
+ const db = getDb();
53
+ if (!db) {
54
+ throw new Error('Database not available');
55
+ }
56
+ const now = new Date().toISOString();
57
+ const insertValues = {
58
+ content_id: activity.content_id,
59
+ activity_type: activity.activity_type,
60
+ activity_data: JSON.stringify(activity.activity_data),
61
+ generation_prompt: activity.generation_prompt ?? null,
62
+ created_at: now,
63
+ };
64
+ const result = await db
65
+ .insertInto('generated_activities')
66
+ .values(insertValues)
67
+ .returningAll()
68
+ .executeTakeFirstOrThrow();
69
+ return result;
70
+ };
71
+ /**
72
+ * Get generated activity by ID
73
+ */
74
+ const getGeneratedActivityById = async (id) => {
75
+ await ensureDatabaseInitialized();
76
+ const db = getDb();
77
+ if (!db)
78
+ return null;
79
+ const result = await db.selectFrom('generated_activities').selectAll().where('id', '=', id).executeTakeFirst();
80
+ return result || null;
81
+ };
82
+ /**
83
+ * Get generated activities for a content item
84
+ */
85
+ const getGeneratedActivitiesByContentId = async (contentId) => {
86
+ await ensureDatabaseInitialized();
87
+ const db = getDb();
88
+ if (!db)
89
+ return [];
90
+ const results = await db
91
+ .selectFrom('generated_activities')
92
+ .selectAll()
93
+ .where('content_id', '=', contentId)
94
+ .orderBy('created_at', 'desc')
95
+ .execute();
96
+ return results;
97
+ };
98
+ /**
99
+ * Get generated activities by type
100
+ */
101
+ const getGeneratedActivitiesByType = async (activityType) => {
102
+ await ensureDatabaseInitialized();
103
+ const db = getDb();
104
+ if (!db)
105
+ return [];
106
+ const results = await db
107
+ .selectFrom('generated_activities')
108
+ .selectAll()
109
+ .where('activity_type', '=', activityType)
110
+ .orderBy('created_at', 'desc')
111
+ .execute();
112
+ return results;
113
+ };
114
+ /**
115
+ * Get generated activities for content with specific type
116
+ */
117
+ const getGeneratedActivityByContentAndType = async (contentId, activityType) => {
118
+ await ensureDatabaseInitialized();
119
+ const db = getDb();
120
+ if (!db)
121
+ return null;
122
+ const result = await db
123
+ .selectFrom('generated_activities')
124
+ .selectAll()
125
+ .where('content_id', '=', contentId)
126
+ .where('activity_type', '=', activityType)
127
+ .orderBy('created_at', 'desc')
128
+ .executeTakeFirst();
129
+ return result || null;
130
+ };
131
+ /**
132
+ * Get all generated activities with pagination
133
+ */
134
+ const getGeneratedActivities = async (page = 1, limit = 20, filters) => {
135
+ await ensureDatabaseInitialized();
136
+ const db = getDb();
137
+ if (!db)
138
+ return [];
139
+ let query = db.selectFrom('generated_activities').selectAll().orderBy('created_at', 'desc');
140
+ if (filters?.activity_type) {
141
+ query = query.where('activity_type', '=', filters.activity_type);
142
+ }
143
+ const results = await query
144
+ .offset((page - 1) * limit)
145
+ .limit(limit)
146
+ .execute();
147
+ return results;
148
+ };
149
+ /**
150
+ * Get count of generated activities
151
+ */
152
+ const getGeneratedActivityCount = async (filters) => {
153
+ await ensureDatabaseInitialized();
154
+ const db = getDb();
155
+ if (!db)
156
+ return 0;
157
+ let query = db.selectFrom('generated_activities').select(db.fn.count('id').as('count'));
158
+ if (filters?.activity_type) {
159
+ query = query.where('activity_type', '=', filters.activity_type);
160
+ }
161
+ const result = await query.executeTakeFirst();
162
+ return result?.count || 0;
163
+ };
164
+ /**
165
+ * Update generated activity data
166
+ */
167
+ const updateGeneratedActivityData = async (id, activityData) => {
168
+ await ensureDatabaseInitialized();
169
+ const db = getDb();
170
+ if (!db)
171
+ return;
172
+ await db
173
+ .updateTable('generated_activities')
174
+ .set({ activity_data: JSON.stringify(activityData) })
175
+ .where('id', '=', id)
176
+ .execute();
177
+ };
178
+ /**
179
+ * Delete a generated activity
180
+ */
181
+ const deleteGeneratedActivity = async (id) => {
182
+ await ensureDatabaseInitialized();
183
+ const db = getDb();
184
+ if (!db)
185
+ return;
186
+ await db.deleteFrom('generated_activities').where('id', '=', id).execute();
187
+ };
188
+ /**
189
+ * Delete generated activities by content ID
190
+ */
191
+ const deleteGeneratedActivitiesByContentId = async (contentId) => {
192
+ await ensureDatabaseInitialized();
193
+ const db = getDb();
194
+ if (!db)
195
+ return;
196
+ await db.deleteFrom('generated_activities').where('content_id', '=', contentId).execute();
197
+ };
198
+ /**
199
+ * Reset the generated activities table
200
+ */
201
+ const resetGeneratedActivitiesTable = async () => {
202
+ await ensureDatabaseInitialized();
203
+ const db = getDb();
204
+ if (!db)
205
+ return;
206
+ await db.schema.dropTable('generated_activities').ifExists().execute();
207
+ await initializeGeneratedActivitiesTable();
208
+ };
209
+ /**
210
+ * Parse activity data from JSON string
211
+ */
212
+ const parseActivityData = (jsonString) => JSON.parse(jsonString);
213
+ /**
214
+ * Get flashcard activity data
215
+ */
216
+ const getFlashcardData = async (id) => {
217
+ const activity = await getGeneratedActivityById(id);
218
+ if (!activity || activity.activity_type !== 'flashcard')
219
+ return null;
220
+ return parseActivityData(activity.activity_data);
221
+ };
222
+ /**
223
+ * Get multiple choice activity data
224
+ */
225
+ const getMultipleChoiceData = async (id) => {
226
+ const activity = await getGeneratedActivityById(id);
227
+ if (!activity || activity.activity_type !== 'multiple_choice')
228
+ return null;
229
+ return parseActivityData(activity.activity_data);
230
+ };
231
+ /**
232
+ * Get fill-in-the-blank activity data
233
+ */
234
+ const getFillBlankData = async (id) => {
235
+ const activity = await getGeneratedActivityById(id);
236
+ if (!activity || activity.activity_type !== 'fill_blank')
237
+ return null;
238
+ return parseActivityData(activity.activity_data);
239
+ };
240
+ export { initializeGeneratedActivitiesTable, storeGeneratedActivity, getGeneratedActivityById, getGeneratedActivitiesByContentId, getGeneratedActivitiesByType, getGeneratedActivityByContentAndType, getGeneratedActivities, getGeneratedActivityCount, updateGeneratedActivityData, deleteGeneratedActivity, deleteGeneratedActivitiesByContentId, resetGeneratedActivitiesTable, parseActivityData, getFlashcardData, getMultipleChoiceData, getFillBlankData, };
@@ -0,0 +1,13 @@
1
+ export * from './manual.js';
2
+ export * from './database-reset.js';
3
+ export * from './database-manager.js';
4
+ // xAPI LRS exports
5
+ export * from './actors.js';
6
+ export * from './learning-content.js';
7
+ export * from './activities.js';
8
+ export * from './statements.js';
9
+ export * from './generated-activities.js';
10
+ // Content importers
11
+ export * from './syllst-importer.js';
12
+ // SYLLST content importer
13
+ export * from './syllst-importer.js';
@@ -0,0 +1,230 @@
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 learning_content table
13
+ */
14
+ const initializeLearningContentTable = 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 === 'learning_content'));
21
+ if (!tableExists) {
22
+ await db.schema
23
+ .createTable('learning_content')
24
+ .ifNotExists()
25
+ .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
26
+ .addColumn('content_type', 'text', col => col.notNull())
27
+ .addColumn('raw_content', 'text', col => col.notNull())
28
+ .addColumn('metadata', 'text', col => col.notNull().defaultTo('{}'))
29
+ .addColumn('title', 'text', col => col.notNull())
30
+ .addColumn('source_url', 'text')
31
+ .addColumn('language', 'text')
32
+ .addColumn('created_at', 'text', col => col.notNull())
33
+ .addColumn('updated_at', 'text', col => col.notNull())
34
+ .execute();
35
+ // Create indexes
36
+ await db.schema
37
+ .createIndex('idx_learning_content_type')
38
+ .ifNotExists()
39
+ .on('learning_content')
40
+ .column('content_type')
41
+ .execute();
42
+ await db.schema
43
+ .createIndex('idx_learning_content_created')
44
+ .ifNotExists()
45
+ .on('learning_content')
46
+ .column('created_at')
47
+ .execute();
48
+ await db.schema
49
+ .createIndex('idx_learning_content_language')
50
+ .ifNotExists()
51
+ .on('learning_content')
52
+ .column('language')
53
+ .execute();
54
+ }
55
+ };
56
+ /**
57
+ * Add new learning content
58
+ */
59
+ const addLearningContent = async (content) => {
60
+ await ensureDatabaseInitialized();
61
+ const db = getDb();
62
+ if (!db) {
63
+ throw new Error('Database not available');
64
+ }
65
+ const now = new Date().toISOString();
66
+ const insertValues = {
67
+ content_type: content.content_type,
68
+ raw_content: content.raw_content,
69
+ title: content.title,
70
+ metadata: JSON.stringify(content.metadata || {}),
71
+ source_url: content.source_url ?? null,
72
+ language: content.language ?? null,
73
+ created_at: now,
74
+ updated_at: now,
75
+ };
76
+ const result = await db.insertInto('learning_content').values(insertValues).returningAll().executeTakeFirstOrThrow();
77
+ return result;
78
+ };
79
+ /**
80
+ * Get learning content by ID
81
+ */
82
+ const getLearningContentById = async (id) => {
83
+ await ensureDatabaseInitialized();
84
+ const db = getDb();
85
+ if (!db)
86
+ return null;
87
+ const result = await db.selectFrom('learning_content').selectAll().where('id', '=', id).executeTakeFirst();
88
+ return result || null;
89
+ };
90
+ /**
91
+ * Get all learning content with pagination
92
+ */
93
+ const getLearningContent = async (page = 1, limit = 20, filters) => {
94
+ await ensureDatabaseInitialized();
95
+ const db = getDb();
96
+ if (!db)
97
+ return [];
98
+ let query = db.selectFrom('learning_content').selectAll().orderBy('created_at', 'desc');
99
+ if (filters?.content_type) {
100
+ query = query.where('content_type', '=', filters.content_type);
101
+ }
102
+ if (filters?.language) {
103
+ query = query.where('language', '=', filters.language);
104
+ }
105
+ if (filters?.search) {
106
+ query = query.where(eb => eb.or([eb('title', 'like', `%${filters.search}%`), eb('raw_content', 'like', `%${filters.search}%`)]));
107
+ }
108
+ const results = await query
109
+ .offset((page - 1) * limit)
110
+ .limit(limit)
111
+ .execute();
112
+ return results;
113
+ };
114
+ /**
115
+ * Get total count of learning content
116
+ */
117
+ const getLearningContentCount = async (filters) => {
118
+ await ensureDatabaseInitialized();
119
+ const db = getDb();
120
+ if (!db)
121
+ return 0;
122
+ let query = db.selectFrom('learning_content').select(db.fn.count('id').as('count'));
123
+ if (filters?.content_type) {
124
+ query = query.where('content_type', '=', filters.content_type);
125
+ }
126
+ if (filters?.language) {
127
+ query = query.where('language', '=', filters.language);
128
+ }
129
+ if (filters?.search) {
130
+ query = query.where(eb => eb.or([eb('title', 'like', `%${filters.search}%`), eb('raw_content', 'like', `%${filters.search}%`)]));
131
+ }
132
+ const result = await query.executeTakeFirst();
133
+ return result?.count || 0;
134
+ };
135
+ /**
136
+ * Update learning content
137
+ */
138
+ const updateLearningContent = async (id, updates) => {
139
+ await ensureDatabaseInitialized();
140
+ const db = getDb();
141
+ if (!db)
142
+ return;
143
+ const now = new Date().toISOString();
144
+ const updateValues = { updated_at: now };
145
+ if (updates.title !== undefined)
146
+ updateValues.title = updates.title;
147
+ if (updates.raw_content !== undefined)
148
+ updateValues.raw_content = updates.raw_content;
149
+ if (updates.metadata !== undefined)
150
+ updateValues.metadata = JSON.stringify(updates.metadata);
151
+ if (updates.language !== undefined)
152
+ updateValues.language = updates.language;
153
+ await db.updateTable('learning_content').set(updateValues).where('id', '=', id).execute();
154
+ };
155
+ /**
156
+ * Delete learning content
157
+ */
158
+ const deleteLearningContent = async (id) => {
159
+ await ensureDatabaseInitialized();
160
+ const db = getDb();
161
+ if (!db)
162
+ return;
163
+ await db.deleteFrom('learning_content').where('id', '=', id).execute();
164
+ };
165
+ /**
166
+ * Delete multiple learning content items
167
+ */
168
+ const deleteLearningContentItems = async (ids) => {
169
+ await ensureDatabaseInitialized();
170
+ const db = getDb();
171
+ if (!db)
172
+ return;
173
+ await db.deleteFrom('learning_content').where('id', 'in', ids).execute();
174
+ };
175
+ /**
176
+ * Get recent learning content
177
+ */
178
+ const getRecentLearningContent = async (days = 7) => {
179
+ await ensureDatabaseInitialized();
180
+ const db = getDb();
181
+ if (!db)
182
+ return [];
183
+ const cutoffDate = new Date();
184
+ cutoffDate.setDate(cutoffDate.getDate() - days);
185
+ const cutoffISO = cutoffDate.toISOString();
186
+ const results = await db
187
+ .selectFrom('learning_content')
188
+ .selectAll()
189
+ .where('created_at', '>=', cutoffISO)
190
+ .orderBy('created_at', 'desc')
191
+ .execute();
192
+ return results;
193
+ };
194
+ /**
195
+ * Get learning content by source URL (useful for checking duplicates)
196
+ */
197
+ const getLearningContentBySourceUrl = async (sourceUrl) => {
198
+ await ensureDatabaseInitialized();
199
+ const db = getDb();
200
+ if (!db)
201
+ return null;
202
+ const result = await db
203
+ .selectFrom('learning_content')
204
+ .selectAll()
205
+ .where('source_url', '=', sourceUrl)
206
+ .executeTakeFirst();
207
+ return result || null;
208
+ };
209
+ /**
210
+ * Reset the learning content table
211
+ */
212
+ const resetLearningContentTable = async () => {
213
+ await ensureDatabaseInitialized();
214
+ const db = getDb();
215
+ if (!db)
216
+ return;
217
+ await db.schema.dropTable('learning_content').ifExists().execute();
218
+ await initializeLearningContentTable();
219
+ };
220
+ /**
221
+ * Clear all learning content
222
+ */
223
+ const clearAllLearningContent = async () => {
224
+ await ensureDatabaseInitialized();
225
+ const db = getDb();
226
+ if (!db)
227
+ return;
228
+ await db.deleteFrom('learning_content').execute();
229
+ };
230
+ export { initializeLearningContentTable, addLearningContent, getLearningContentById, getLearningContent, getLearningContentCount, updateLearningContent, deleteLearningContent, deleteLearningContentItems, getRecentLearningContent, getLearningContentBySourceUrl, resetLearningContentTable, clearAllLearningContent, };
@@ -0,0 +1,30 @@
1
+ import { Kysely } from 'kysely';
2
+ import { SQLocalKysely } from 'sqlocal/kysely';
3
+ export class ManualSqliteClient {
4
+ static instance = null;
5
+ db = null;
6
+ filename;
7
+ constructor() {
8
+ this.filename = 'database.sqlite3';
9
+ this.initializeDatabase();
10
+ }
11
+ static getInstance() {
12
+ if (!ManualSqliteClient.instance) {
13
+ ManualSqliteClient.instance = new ManualSqliteClient();
14
+ }
15
+ return ManualSqliteClient.instance;
16
+ }
17
+ initializeDatabase() {
18
+ const { dialect } = new SQLocalKysely(this.filename);
19
+ this.db = new Kysely({ dialect });
20
+ }
21
+ getDb() {
22
+ if (!this.db) {
23
+ throw new Error('Database not initialized');
24
+ }
25
+ return this.db;
26
+ }
27
+ static resetInstance() {
28
+ ManualSqliteClient.instance = null;
29
+ }
30
+ }