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,342 @@
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
+ * Generate a UUID v4
13
+ */
14
+ const generateUUID = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
15
+ const r = (Math.random() * 16) | 0;
16
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
17
+ return v.toString(16);
18
+ });
19
+ /**
20
+ * Initialize the statements table
21
+ */
22
+ const initializeStatementsTable = async () => {
23
+ const db = getDb();
24
+ if (!db)
25
+ return;
26
+ const tableExists = await db.introspection
27
+ .getTables()
28
+ .then((tables) => tables.some((table) => table.name === 'statements'));
29
+ if (!tableExists) {
30
+ await db.schema
31
+ .createTable('statements')
32
+ .ifNotExists()
33
+ .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
34
+ .addColumn('statement_id', 'text', col => col.notNull().unique())
35
+ .addColumn('actor_id', 'integer', col => col.notNull())
36
+ .addColumn('verb_id', 'text', col => col.notNull())
37
+ .addColumn('activity_id', 'integer', col => col.notNull())
38
+ .addColumn('result', 'text')
39
+ .addColumn('context', 'text')
40
+ .addColumn('timestamp', 'text', col => col.notNull())
41
+ .addColumn('stored', 'text', col => col.notNull())
42
+ .execute();
43
+ // Create indexes for common queries
44
+ await db.schema.createIndex('idx_statements_actor').ifNotExists().on('statements').column('actor_id').execute();
45
+ await db.schema.createIndex('idx_statements_verb').ifNotExists().on('statements').column('verb_id').execute();
46
+ await db.schema
47
+ .createIndex('idx_statements_activity')
48
+ .ifNotExists()
49
+ .on('statements')
50
+ .column('activity_id')
51
+ .execute();
52
+ await db.schema
53
+ .createIndex('idx_statements_timestamp')
54
+ .ifNotExists()
55
+ .on('statements')
56
+ .column('timestamp')
57
+ .execute();
58
+ await db.schema.createIndex('idx_statements_stored').ifNotExists().on('statements').column('stored').execute();
59
+ }
60
+ };
61
+ /**
62
+ * Record a new xAPI statement
63
+ */
64
+ const recordStatement = async (statement) => {
65
+ await ensureDatabaseInitialized();
66
+ const db = getDb();
67
+ if (!db) {
68
+ throw new Error('Database not available');
69
+ }
70
+ const now = new Date().toISOString();
71
+ const insertValues = {
72
+ statement_id: generateUUID(),
73
+ actor_id: statement.actor_id,
74
+ verb_id: statement.verb_id,
75
+ activity_id: statement.activity_id,
76
+ result: statement.result ? JSON.stringify(statement.result) : null,
77
+ context: statement.context ? JSON.stringify(statement.context) : null,
78
+ timestamp: statement.timestamp || now,
79
+ stored: now,
80
+ };
81
+ const result = await db.insertInto('statements').values(insertValues).returningAll().executeTakeFirstOrThrow();
82
+ return result;
83
+ };
84
+ /**
85
+ * Get statement by ID
86
+ */
87
+ const getStatementById = async (id) => {
88
+ await ensureDatabaseInitialized();
89
+ const db = getDb();
90
+ if (!db)
91
+ return null;
92
+ const result = await db.selectFrom('statements').selectAll().where('id', '=', id).executeTakeFirst();
93
+ return result || null;
94
+ };
95
+ /**
96
+ * Get statement by statement_id (UUID)
97
+ */
98
+ const getStatementByStatementId = async (statementId) => {
99
+ await ensureDatabaseInitialized();
100
+ const db = getDb();
101
+ if (!db)
102
+ return null;
103
+ const result = await db
104
+ .selectFrom('statements')
105
+ .selectAll()
106
+ .where('statement_id', '=', statementId)
107
+ .executeTakeFirst();
108
+ return result || null;
109
+ };
110
+ /**
111
+ * Get statements with filters and pagination
112
+ */
113
+ const getStatements = async (page = 1, limit = 50, filters) => {
114
+ await ensureDatabaseInitialized();
115
+ const db = getDb();
116
+ if (!db)
117
+ return [];
118
+ let query = db.selectFrom('statements').selectAll().orderBy('timestamp', 'desc');
119
+ if (filters?.actor_id) {
120
+ query = query.where('actor_id', '=', filters.actor_id);
121
+ }
122
+ if (filters?.verb_id) {
123
+ query = query.where('verb_id', '=', filters.verb_id);
124
+ }
125
+ if (filters?.activity_id) {
126
+ query = query.where('activity_id', '=', filters.activity_id);
127
+ }
128
+ if (filters?.since) {
129
+ query = query.where('timestamp', '>=', filters.since);
130
+ }
131
+ if (filters?.until) {
132
+ query = query.where('timestamp', '<=', filters.until);
133
+ }
134
+ const results = await query
135
+ .offset((page - 1) * limit)
136
+ .limit(limit)
137
+ .execute();
138
+ return results;
139
+ };
140
+ /**
141
+ * Get statement count
142
+ */
143
+ const getStatementCount = async (filters) => {
144
+ await ensureDatabaseInitialized();
145
+ const db = getDb();
146
+ if (!db)
147
+ return 0;
148
+ let query = db.selectFrom('statements').select(db.fn.count('id').as('count'));
149
+ if (filters?.actor_id) {
150
+ query = query.where('actor_id', '=', filters.actor_id);
151
+ }
152
+ if (filters?.verb_id) {
153
+ query = query.where('verb_id', '=', filters.verb_id);
154
+ }
155
+ if (filters?.activity_id) {
156
+ query = query.where('activity_id', '=', filters.activity_id);
157
+ }
158
+ if (filters?.since) {
159
+ query = query.where('timestamp', '>=', filters.since);
160
+ }
161
+ if (filters?.until) {
162
+ query = query.where('timestamp', '<=', filters.until);
163
+ }
164
+ const result = await query.executeTakeFirst();
165
+ return result?.count || 0;
166
+ };
167
+ /**
168
+ * Get statements for an activity
169
+ */
170
+ const getStatementsByActivityId = async (activityId) => {
171
+ await ensureDatabaseInitialized();
172
+ const db = getDb();
173
+ if (!db)
174
+ return [];
175
+ const results = await db
176
+ .selectFrom('statements')
177
+ .selectAll()
178
+ .where('activity_id', '=', activityId)
179
+ .orderBy('timestamp', 'desc')
180
+ .execute();
181
+ return results;
182
+ };
183
+ /**
184
+ * Get statements by verb
185
+ */
186
+ const getStatementsByVerb = async (verbId, limit) => {
187
+ await ensureDatabaseInitialized();
188
+ const db = getDb();
189
+ if (!db)
190
+ return [];
191
+ let query = db.selectFrom('statements').selectAll().where('verb_id', '=', verbId).orderBy('timestamp', 'desc');
192
+ if (limit) {
193
+ query = query.limit(limit);
194
+ }
195
+ const results = await query.execute();
196
+ return results;
197
+ };
198
+ /**
199
+ * Get recent statements
200
+ */
201
+ const getRecentStatements = async (days = 7, limit) => {
202
+ await ensureDatabaseInitialized();
203
+ const db = getDb();
204
+ if (!db)
205
+ return [];
206
+ const cutoffDate = new Date();
207
+ cutoffDate.setDate(cutoffDate.getDate() - days);
208
+ const cutoffISO = cutoffDate.toISOString();
209
+ let query = db.selectFrom('statements').selectAll().where('timestamp', '>=', cutoffISO).orderBy('timestamp', 'desc');
210
+ if (limit) {
211
+ query = query.limit(limit);
212
+ }
213
+ const results = await query.execute();
214
+ return results;
215
+ };
216
+ /**
217
+ * Get learning statistics
218
+ */
219
+ const getLearningStats = async (actorId, since) => {
220
+ await ensureDatabaseInitialized();
221
+ const db = getDb();
222
+ if (!db) {
223
+ return {
224
+ totalStatements: 0,
225
+ completedActivities: 0,
226
+ passedActivities: 0,
227
+ failedActivities: 0,
228
+ averageScore: null,
229
+ };
230
+ }
231
+ let baseQuery = db.selectFrom('statements').where('actor_id', '=', actorId);
232
+ if (since) {
233
+ baseQuery = baseQuery.where('timestamp', '>=', since);
234
+ }
235
+ // Total statements
236
+ const totalResult = await baseQuery.select(db.fn.count('id').as('count')).executeTakeFirst();
237
+ const totalStatements = totalResult?.count || 0;
238
+ // Completed (using verb URI)
239
+ const completedResult = await baseQuery
240
+ .where('verb_id', '=', 'http://adlnet.gov/expapi/verbs/completed')
241
+ .select(db.fn.count('id').as('count'))
242
+ .executeTakeFirst();
243
+ const completedActivities = completedResult?.count || 0;
244
+ // Passed
245
+ const passedResult = await baseQuery
246
+ .where('verb_id', '=', 'http://adlnet.gov/expapi/verbs/passed')
247
+ .select(db.fn.count('id').as('count'))
248
+ .executeTakeFirst();
249
+ const passedActivities = passedResult?.count || 0;
250
+ // Failed
251
+ const failedResult = await baseQuery
252
+ .where('verb_id', '=', 'http://adlnet.gov/expapi/verbs/failed')
253
+ .select(db.fn.count('id').as('count'))
254
+ .executeTakeFirst();
255
+ const failedActivities = failedResult?.count || 0;
256
+ // Average score - need to parse JSON results
257
+ const statementsWithScores = await baseQuery.select(['result']).where('result', 'is not', null).execute();
258
+ let totalScore = 0;
259
+ let scoreCount = 0;
260
+ for (const stmt of statementsWithScores) {
261
+ if (stmt.result) {
262
+ try {
263
+ const result = JSON.parse(stmt.result);
264
+ if (result.score?.scaled !== undefined) {
265
+ totalScore += result.score.scaled;
266
+ scoreCount++;
267
+ }
268
+ }
269
+ catch {
270
+ // Ignore invalid JSON
271
+ }
272
+ }
273
+ }
274
+ const averageScore = scoreCount > 0 ? totalScore / scoreCount : null;
275
+ return {
276
+ totalStatements,
277
+ completedActivities,
278
+ passedActivities,
279
+ failedActivities,
280
+ averageScore,
281
+ };
282
+ };
283
+ /**
284
+ * Delete a statement
285
+ */
286
+ const deleteStatement = async (id) => {
287
+ await ensureDatabaseInitialized();
288
+ const db = getDb();
289
+ if (!db)
290
+ return;
291
+ await db.deleteFrom('statements').where('id', '=', id).execute();
292
+ };
293
+ /**
294
+ * Delete statements by activity ID
295
+ */
296
+ const deleteStatementsByActivityId = async (activityId) => {
297
+ await ensureDatabaseInitialized();
298
+ const db = getDb();
299
+ if (!db)
300
+ return;
301
+ await db.deleteFrom('statements').where('activity_id', '=', activityId).execute();
302
+ };
303
+ /**
304
+ * Reset the statements table
305
+ */
306
+ const resetStatementsTable = async () => {
307
+ await ensureDatabaseInitialized();
308
+ const db = getDb();
309
+ if (!db)
310
+ return;
311
+ await db.schema.dropTable('statements').ifExists().execute();
312
+ await initializeStatementsTable();
313
+ };
314
+ /**
315
+ * Clear all statements
316
+ */
317
+ const clearAllStatements = async () => {
318
+ await ensureDatabaseInitialized();
319
+ const db = getDb();
320
+ if (!db)
321
+ return;
322
+ await db.deleteFrom('statements').execute();
323
+ };
324
+ /**
325
+ * Export statements in xAPI format (for external LRS compatibility)
326
+ */
327
+ const exportStatementsAsXAPI = async (filters) => {
328
+ const statements = await getStatements(1, 10000, filters);
329
+ // This would need to be expanded to include full actor/activity details
330
+ // For now, return raw statements
331
+ return statements.map(s => ({
332
+ id: s.statement_id,
333
+ actor: { id: s.actor_id }, // Would need to fetch full actor
334
+ verb: { id: s.verb_id },
335
+ object: { id: s.activity_id }, // Would need to fetch full activity
336
+ result: s.result ? JSON.parse(s.result) : undefined,
337
+ context: s.context ? JSON.parse(s.context) : undefined,
338
+ timestamp: s.timestamp,
339
+ stored: s.stored,
340
+ }));
341
+ };
342
+ export { initializeStatementsTable, generateUUID, recordStatement, getStatementById, getStatementByStatementId, getStatements, getStatementCount, getStatementsByActivityId, getStatementsByVerb, getRecentStatements, getLearningStats, deleteStatement, deleteStatementsByActivityId, resetStatementsTable, clearAllStatements, exportStatementsAsXAPI, };
@@ -0,0 +1,234 @@
1
+ /**
2
+ * SYLLST Content Importer
3
+ *
4
+ * Imports learning content from SYLLST MDX format (polyglot-bundles packages)
5
+ * and creates corresponding flashcard activities in Chishiki.
6
+ */
7
+ import { getDatabaseManager } from './database-manager.js';
8
+ import { ManualSqliteClient } from './manual.js';
9
+ import { addLearningContent } from './learning-content.js';
10
+ import { createActivity } from './activities.js';
11
+ import { storeGeneratedActivity } from './generated-activities.js';
12
+ const getDb = () => {
13
+ const client = ManualSqliteClient.getInstance();
14
+ return client.getDb();
15
+ };
16
+ const ensureDatabaseInitialized = async () => {
17
+ const dbManager = getDatabaseManager();
18
+ await dbManager.ensureInitialized();
19
+ };
20
+ /**
21
+ * Parse SYLLST MDX content and extract frontmatter + learning items
22
+ */
23
+ export function parseSyllstMdx(mdxContent) {
24
+ // Extract frontmatter (YAML between --- markers)
25
+ const frontmatterMatch = mdxContent.match(/^---\n([\s\S]*?)\n---/);
26
+ const frontmatterRaw = frontmatterMatch ? frontmatterMatch[1] : '';
27
+ // Simple YAML parsing for frontmatter
28
+ const frontmatter = {};
29
+ const lines = frontmatterRaw.split('\n');
30
+ let currentKey = null;
31
+ let currentArray = [];
32
+ let inArray = false;
33
+ for (const line of lines) {
34
+ const trimmed = line.trim();
35
+ // Check for array start
36
+ if (trimmed.match(/^(\w+):\s*$/)) {
37
+ const keyMatch = trimmed.match(/^(\w+):\s*$/);
38
+ if (!keyMatch)
39
+ continue;
40
+ const key = keyMatch[1];
41
+ currentKey = key;
42
+ currentArray = [];
43
+ inArray = true;
44
+ continue;
45
+ }
46
+ // Check for array item
47
+ if (inArray && trimmed.startsWith('- ')) {
48
+ currentArray.push(trimmed.substring(2).trim());
49
+ continue;
50
+ }
51
+ // Check for key: value
52
+ const kvMatch = trimmed.match(/^(\w+):\s*(.+)$/);
53
+ if (kvMatch) {
54
+ const [, key, value] = kvMatch;
55
+ // Remove quotes if present
56
+ const cleanValue = value.replace(/^["']|["']$/g, '');
57
+ frontmatter[key] = cleanValue;
58
+ // Handle nested learningObjectives array
59
+ if (key === 'learningObjectives') {
60
+ frontmatter[key] = [];
61
+ }
62
+ currentKey = null;
63
+ inArray = false;
64
+ }
65
+ // End array on empty line or new key
66
+ if (inArray && (trimmed === '' || trimmed.match(/^\w+:/))) {
67
+ if (currentKey) {
68
+ frontmatter[currentKey] = currentArray;
69
+ }
70
+ inArray = false;
71
+ }
72
+ }
73
+ // Extract vocabulary items (look for ::vocabulary blocks)
74
+ const vocabularyItems = [];
75
+ const vocabBlockRegex = /::vocabulary\{([^}]+)\}/g;
76
+ let match;
77
+ while ((match = vocabBlockRegex.exec(mdxContent)) !== null) {
78
+ const attrs = match[1];
79
+ const idMatch = attrs.match(/id="([^"]+)"/);
80
+ const wordMatch = attrs.match(/word="([^"]+)"/);
81
+ const translationMatch = attrs.match(/translation="([^"]+)"/);
82
+ const romanizationMatch = attrs.match(/romanization="([^"]+)"/);
83
+ if (idMatch && wordMatch && translationMatch) {
84
+ vocabularyItems.push({
85
+ id: idMatch[1],
86
+ word: wordMatch[1],
87
+ translation: translationMatch[1],
88
+ romanization: romanizationMatch?.[1],
89
+ });
90
+ }
91
+ }
92
+ // Extract character items (look for ::character blocks)
93
+ const characterItems = [];
94
+ const charBlockRegex = /::character\{([^}]+)\}/g;
95
+ let charMatch;
96
+ while ((charMatch = charBlockRegex.exec(mdxContent)) !== null) {
97
+ const attrs = charMatch[1];
98
+ const idMatch = attrs.match(/id="([^"]+)"/);
99
+ const charMatchRegex = attrs.match(/char="([^"]+)"/);
100
+ const nameMatch = attrs.match(/name="([^"]+)"/);
101
+ const romanizationMatch = attrs.match(/romanization="([^"]+)"/);
102
+ const typeMatch = attrs.match(/type="([^"]+)"/);
103
+ if (idMatch && charMatchRegex && nameMatch) {
104
+ characterItems.push({
105
+ id: idMatch[1],
106
+ char: charMatchRegex[1],
107
+ name: nameMatch[1],
108
+ romanization: romanizationMatch?.[1],
109
+ charType: typeMatch?.[1] || 'unknown',
110
+ });
111
+ }
112
+ }
113
+ return {
114
+ frontmatter: frontmatter,
115
+ vocabularyItems,
116
+ characterItems,
117
+ rawMdx: mdxContent,
118
+ };
119
+ }
120
+ /**
121
+ * Import SYLLST MDX content into Chishiki
122
+ * Creates learning content and generates flashcard activities
123
+ */
124
+ export async function importSyllstContent(mdxContent, syllabusId, lessonNumber) {
125
+ await ensureDatabaseInitialized();
126
+ const parsed = parseSyllstMdx(mdxContent);
127
+ const title = parsed.frontmatter.title || `Lesson ${lessonNumber}`;
128
+ const language = parsed.frontmatter.language || 'unknown';
129
+ // Create learning content entry
130
+ const content = await addLearningContent({
131
+ content_type: 'lesson',
132
+ raw_content: mdxContent,
133
+ title: `${syllabusId} - ${title}`,
134
+ metadata: {
135
+ title: `${syllabusId} - ${title}`,
136
+ source_url: undefined,
137
+ syllabusId,
138
+ lessonNumber,
139
+ language,
140
+ frontmatter: parsed.frontmatter,
141
+ extractedAt: new Date().toISOString(),
142
+ },
143
+ language,
144
+ });
145
+ const contentId = String(content.id);
146
+ const activityIds = [];
147
+ // Collect flashcards for vocabulary
148
+ const vocabFlashcards = [];
149
+ for (const vocab of parsed.vocabularyItems) {
150
+ vocabFlashcards.push({
151
+ id: vocab.id,
152
+ front: vocab.word,
153
+ back: vocab.translation,
154
+ tags: vocab.romanization ? [`roman:${vocab.romanization}`] : undefined,
155
+ });
156
+ }
157
+ // Store vocabulary flashcard activity
158
+ if (vocabFlashcards.length > 0) {
159
+ const vocabActivity = await createActivity({
160
+ activity_iri: `https://chishiki.app/activities/flashcard/${syllabusId}-vocab-${lessonNumber}`,
161
+ name: `Vocabulary Flashcards: ${title}`,
162
+ description: `Vocabulary flashcards for ${syllabusId} lesson ${lessonNumber}`,
163
+ activity_type: 'flashcard',
164
+ content_id: content.id,
165
+ });
166
+ activityIds.push(String(vocabActivity.id));
167
+ await storeGeneratedActivity({
168
+ content_id: content.id,
169
+ activity_type: 'flashcard',
170
+ activity_data: {
171
+ flashcards: vocabFlashcards,
172
+ },
173
+ });
174
+ }
175
+ // Collect flashcards for characters
176
+ const charFlashcards = [];
177
+ for (const char of parsed.characterItems) {
178
+ charFlashcards.push({
179
+ id: char.id,
180
+ front: char.char,
181
+ back: char.name,
182
+ tags: [
183
+ `type:${char.charType}`,
184
+ ...(char.romanization ? [`roman:${char.romanization}`] : []),
185
+ ],
186
+ });
187
+ }
188
+ // Store character flashcard activity
189
+ if (charFlashcards.length > 0) {
190
+ const charActivity = await createActivity({
191
+ activity_iri: `https://chishiki.app/activities/flashcard/${syllabusId}-char-${lessonNumber}`,
192
+ name: `Character Flashcards: ${title}`,
193
+ description: `Character flashcards for ${syllabusId} lesson ${lessonNumber}`,
194
+ activity_type: 'flashcard',
195
+ content_id: content.id,
196
+ });
197
+ activityIds.push(String(charActivity.id));
198
+ await storeGeneratedActivity({
199
+ content_id: content.id,
200
+ activity_type: 'flashcard',
201
+ activity_data: {
202
+ flashcards: charFlashcards,
203
+ },
204
+ });
205
+ }
206
+ return {
207
+ contentId,
208
+ activityIds,
209
+ vocabularyCount: parsed.vocabularyItems.length,
210
+ characterCount: parsed.characterItems.length,
211
+ };
212
+ }
213
+ /**
214
+ * Batch import multiple SYLLST lessons
215
+ */
216
+ export async function batchImportSyllstLessons(lessons) {
217
+ const results = [];
218
+ let totalActivities = 0;
219
+ for (const lesson of lessons) {
220
+ const result = await importSyllstContent(lesson.mdx, lesson.syllabusId, lesson.lessonNumber);
221
+ results.push({
222
+ syllabusId: lesson.syllabusId,
223
+ lessonNumber: lesson.lessonNumber,
224
+ contentId: result.contentId,
225
+ activityCount: result.activityIds.length,
226
+ });
227
+ totalActivities += result.activityIds.length;
228
+ }
229
+ return {
230
+ totalContent: lessons.length,
231
+ totalActivities,
232
+ results,
233
+ };
234
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ {"root":["../index.mts","../lib/activities.ts","../lib/actors.ts","../lib/database-manager.ts","../lib/database-reset.ts","../lib/generated-activities.ts","../lib/index.ts","../lib/learning-content.ts","../lib/manual.ts","../lib/statements.ts","../lib/syllst-importer.ts","../lib/types.ts"],"version":"5.8.3"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "chishiki-sqlite",
3
+ "version": "0.8.0",
4
+ "description": "chrome extension - sqlite opfs integration",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "files": [
8
+ "dist/**"
9
+ ],
10
+ "types": "dist/index.d.mts",
11
+ "main": "dist/index.mjs",
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "dependencies": {
16
+ "kysely": "^0.28.8",
17
+ "sqlocal": "^0.15.2",
18
+ "@extension/shared": "0.5.0"
19
+ },
20
+ "devDependencies": {
21
+ "tsc-alias": "^1.8.16",
22
+ "@extension/tsconfig": "0.6.0"
23
+ },
24
+ "scripts": {
25
+ "clean:bundle": "rimraf dist",
26
+ "clean:node_modules": "pnpx rimraf node_modules",
27
+ "clean:turbo": "rimraf .turbo",
28
+ "clean": "pnpm clean:bundle && pnpm clean:node_modules && pnpm clean:turbo",
29
+ "ready": "tsc -b && tsc-alias -p tsconfig.json",
30
+ "lint": "eslint .",
31
+ "lint:fix": "pnpm lint --fix",
32
+ "format": "prettier . --write --ignore-path ../../.prettierignore",
33
+ "type-check": "tsc --noEmit"
34
+ }
35
+ }